V4L2: auto-restart the stream after consecutive undersized frames instead of losing the source - #1573
Open
deimelperez wants to merge 2 commits into
Open
Conversation
Some USB capture devices intermittently emit a burst of buffers whose
bytesused is smaller than the negotiated frame size ("Frame too small"
errors). Those frames are dropped before they ever reach the workers, so
no image is emitted while the burst lasts. If it lasts longer than the
video-control liveness window (~800 ms), the muxer marks the input
inactive, priority falls to LOWEST_PRIORITY and the LED device is
switched off ("No source left"), requiring a manual toggle to recover
unless automatic resume is enabled.
Treat frame-size mismatches as a transient capture error distinct from
signal loss: count consecutive occurrences in process_image() and, after
FRAME_SIZE_MISMATCH_RESTART_THRESHOLD (8) in a row, re-initialize the
V4L2 stream (stop/start: STREAMOFF, munmap, close, reopen, re-request
buffers) from a queued call on the grabber thread, guarded by the same
_synchro semaphore used by Grabber::revive(). At 30-60 fps the restart
triggers after ~130-270 ms, well before the liveness timeout declares
the source dead. The counter resets on any good frame, so isolated
glitches never trigger a restart.
Linux V4L2 path only; other backends are untouched.
If start() fails right after the automatic re-initialization (USB capture devices can need time to settle after close/reopen), the stream notifier is gone and nothing ever calls read_frame() again, so the grabber stays dead until manually toggled. Retry the restart every 3s, up to MAX_RESTART_ATTEMPTS (5), then give up with a clear error so a pending retry cannot indefinitely resurrect a grabber the user disabled.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On a Raspberry Pi 4 with a USB3.0 HDMI capture grabber (HyperHDR 22.0.0beta1, feeding WLED), the device intermittently emits a burst of V4L2 buffers whose
bytesusedis smaller than the negotiated frame size. Each one produces:When such a burst lasts long enough, HyperHDR ends up logging
No source left -> switch LED-Device offand the LED device stays off. The only recovery is manually toggling the LED device (or the grabber) in the web UI.Root cause analysis
Tracing the failure path:
V4L2Grabber::process_image()(sources/grabber/linux/v4l2/V4L2Grabber.cpp) drops any non-MJPEG frame withbytesused < _frameByteSize— it logs "Frame too small" and returnsfalse. The buffer is simply re-queued byread_frame()and no image is ever emitted for that frame.Contrary to what the log output suggests, these dropped frames never reach the signal-detection counters (
DetectionManual/DetectionAutomaticonly run insideGrabber::handleNewFrame(), which is never called for a dropped frame). What actually kills the source is the liveness timeout inVideoControl: every incoming image sets_alive = true, and an 800 ms_usbInactiveTimercallssetUsbInactive()when no image arrived in the last period.setUsbInactive()callssetInputInactive()on the muxer; the muxer then skips the inactive input, the current priority falls toMuxer::LOWEST_PRIORITY, andHyperHdrInstance::handlePriorityChangedLedDevice()switches the LED device off ("No source left").There is an existing recovery path —
VideoControlrequestsGrabberWrapper::revive(), which restarts the grabber after 3 s — but it only runs when the automatic resume option is enabled, and it reacts long after the LED device was already disabled.So a burst of undersized frames — a transient capture-device error — is indistinguishable, at the video-control level, from the capture source genuinely going away.
Fix
Treat frame-size mismatches as a distinct transient capture error inside the V4L2 grabber itself, and recover the stream before the liveness timeout ever fires:
process_image(). Any good frame resets the counter, so isolated glitches change nothing.FRAME_SIZE_MISMATCH_RESTART_THRESHOLD(8) undersized frames in a row, log a warning and re-initialize the V4L2 stream:stop()+start()(STREAMOFF, munmap, close, reopen, re-negotiate format, re-request buffers).QTimer::singleShot(0, this, …)so it runs on the grabber's own thread after the currentread_frame()completes (the in-flight buffer is still re-queued against the old, valid file descriptor), and it is guarded by the same_synchrosemaphore thatGrabber::revive()uses, so the two restart paths cannot interleave.At typical 30–60 fps, 8 consecutive bad frames trigger the restart after ~130–270 ms — well inside the 800 ms liveness window, so the muxer never sees the input go inactive and the LED device stays on.
Scope
Linux V4L2 path only (
sources/grabber/linux/v4l2/,include/grabber/linux/v4l2/). No changes to signal detection, HDR/LUT processing, or the other capture backends (Media Foundation, AVFoundation, etc.), which have their own copies of the "Frame too small" check.Testing
v4l2-grabbertarget builds cleanly with the patch.