Skip to content

V4L2: auto-restart the stream after consecutive undersized frames instead of losing the source - #1573

Open
deimelperez wants to merge 2 commits into
awawa-dev:masterfrom
deimelperez:fix/v4l2-frame-size-mismatch-reinit
Open

V4L2: auto-restart the stream after consecutive undersized frames instead of losing the source#1573
deimelperez wants to merge 2 commits into
awawa-dev:masterfrom
deimelperez:fix/v4l2-frame-size-mismatch-reinit

Conversation

@deimelperez

Copy link
Copy Markdown

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 bytesused is smaller than the negotiated frame size. Each one produces:

Frame too small: 0 != 691200

When such a burst lasts long enough, HyperHDR ends up logging No source left -> switch LED-Device off and 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:

  1. V4L2Grabber::process_image() (sources/grabber/linux/v4l2/V4L2Grabber.cpp) drops any non-MJPEG frame with bytesused < _frameByteSize — it logs "Frame too small" and returns false. The buffer is simply re-queued by read_frame() and no image is ever emitted for that frame.

  2. Contrary to what the log output suggests, these dropped frames never reach the signal-detection counters (DetectionManual/DetectionAutomatic only run inside Grabber::handleNewFrame(), which is never called for a dropped frame). What actually kills the source is the liveness timeout in VideoControl: every incoming image sets _alive = true, and an 800 ms _usbInactiveTimer calls setUsbInactive() when no image arrived in the last period.

  3. setUsbInactive() calls setInputInactive() on the muxer; the muxer then skips the inactive input, the current priority falls to Muxer::LOWEST_PRIORITY, and HyperHdrInstance::handlePriorityChangedLedDevice() switches the LED device off ("No source left").

  4. There is an existing recovery path — VideoControl requests GrabberWrapper::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:

  • Count consecutive undersized frames in process_image(). Any good frame resets the counter, so isolated glitches change nothing.
  • After 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).
  • The restart is queued with QTimer::singleShot(0, this, …) so it runs on the grabber's own thread after the current read_frame() completes (the in-flight buffer is still re-queued against the old, valid file descriptor), and it is guarded by the same _synchro semaphore that Grabber::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

  • Compile-verified on Linux x86_64 (Ubuntu 22.04, Qt 6.2): the v4l2-grabber target builds cleanly with the patch.
  • Runtime testing in progress on the affected setup (RPi4 + USB3.0 HDMI grabber): reproducing the "Frame too small" burst and confirming the stream restarts instead of the LED device being switched off.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant