Reopen a dropped video stream instead of spinning on a dead capture - #105
Merged
Conversation
The capture thread called grab() in a tight loop and ignored the result. Once the stream goes away (camera reboot, network blip, RTSP session timeout) every grab() fails immediately, so the thread spins at 100% of a core -- measured 140% here, because get_frame_jpg() then contends for capture_mutex on every publish -- and video never comes back, since nothing rebuilds the capture. Only a stop/start of the whole streamer (module unload/load from the platform) recovered it. - _run() checks grab()'s return value and, on failure, releases and rebuilds the capture with bounded backoff (0.5s -> 10s), logging the first failure of a streak and the recovery. - The backoff waits on a _closing Event set by close(), so teardown is never held up by a pending retry. - get_frame_jpg() returns no frame while the capture is being rebuilt, rather than raising on a None capture. - URL sources now request cv2.CAP_FFMPEG explicitly (overridable with the new api_preference kwarg): with automatic backend selection OpenCV may ignore OPENCV_FFMPEG_CAPTURE_OPTIONS, which is where deployments set the RTSP transport and the socket timeout that bounds a stalled read. Tests: 3 new cases in test_video.py (no spin + reopen, no frame while reopening, backend preference).
leandropineda
force-pushed
the
fix/video-capture-recovery
branch
from
July 29, 2026 11:23
3f09480 to
3d3d6be
Compare
miguelgarcia
approved these changes
Jul 29, 2026
This was referenced Jul 29, 2026
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.
OpenCVCamera._run()callsgrab()in a tight loop and ignores the result. When the stream goes away — camera reboot, network blip, RTSP session timeout — everygrab()returns immediately, so the capture thread spins at 100% of a core (measured 140% on 3.1.0, sinceget_frame_jpg()then fights it forcapture_mutex) and video never comes back: nothing rebuilds the capture, so only a module unload/load or a process restart recovers it._run()checksgrab()'s result and, on failure, releases and rebuilds the capture with bounded backoff (0.5s → 10s), logging the first failure of a streak and the recovery._closingEvent set byclose(), so teardown is never delayed by a pending retry.get_frame_jpg()returns no frame while the capture is being rebuilt instead of raising on aNonecapture.cv2.CAP_FFMPEGexplicitly (overridable via the newapi_preferencekwarg): with automatic selection OpenCV may ignoreOPENCV_FFMPEG_CAPTURE_OPTIONS, where deployments set the RTSP transport and the socket timeout that bounds a stalled read.Same dead stream after: 0.6% of a core, and video resumes on its own.
Tests: 3 new cases in
test_video.py. Suite green, flake8/black clean.First of a train: #105 recovery → #106 decode throttling → #107 staleness → #108 health counters.