Skip to content

Reopen a dropped video stream instead of spinning on a dead capture - #105

Merged
leandropineda merged 1 commit into
mainfrom
fix/video-capture-recovery
Jul 29, 2026
Merged

Reopen a dropped video stream instead of spinning on a dead capture#105
leandropineda merged 1 commit into
mainfrom
fix/video-capture-recovery

Conversation

@leandropineda

@leandropineda leandropineda commented Jul 29, 2026

Copy link
Copy Markdown
Member

OpenCVCamera._run() calls grab() in a tight loop and ignores the result. When the stream goes away — camera reboot, network blip, RTSP session timeout — every grab() returns immediately, so the capture thread spins at 100% of a core (measured 140% on 3.1.0, since get_frame_jpg() then fights it for capture_mutex) and video never comes back: nothing rebuilds the capture, so only a module unload/load or a process restart recovers it.

  • _run() checks grab()'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.
  • The backoff waits on a _closing Event set by close(), 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 a None capture.
  • URL sources request cv2.CAP_FFMPEG explicitly (overridable via the new api_preference kwarg): with automatic selection OpenCV may ignore OPENCV_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.

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
leandropineda force-pushed the fix/video-capture-recovery branch from 3f09480 to 3d3d6be Compare July 29, 2026 11:23
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.

2 participants