Skip to content

Commit a96eaf1

Browse files
ENT-1677: Apply RTSPS TLS options to the OpenCV capture open
Wire opencv_rtsps_tls_env into CV2VideoFrameProducer so non-Jetson OpenCV/FFmpeg RTSPS opens honor rfdm-injected CA bundle and TLS flags. - Enter TLS env before capture_process_stderr (lock outside stderr redirect). - Widen helper signatures to Union[str, int] for the producer call site. - Document RTSPS-vs-RTSPS env isolation in the helper docstring.
1 parent a6caf5e commit a96eaf1

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

inference/core/interfaces/camera/rtsp_opencv_tls.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import threading
77
from contextlib import contextmanager
8-
from typing import Dict, Iterator, Optional
8+
from typing import Dict, Iterator, Optional, Union
99

1010
from inference.core.interfaces.camera.rtsp_tls import (
1111
GST_SSL_CA_CERTIFICATE_ENV_VAR,
@@ -67,7 +67,7 @@ def merge_opencv_ffmpeg_capture_options(
6767
return _format_opencv_ffmpeg_capture_options(merged)
6868

6969

70-
def build_opencv_ffmpeg_capture_options(video: str) -> Optional[str]:
70+
def build_opencv_ffmpeg_capture_options(video: Union[str, int]) -> Optional[str]:
7171
"""Build OPENCV_FFMPEG_CAPTURE_OPTIONS for RTSPS sources."""
7272
if not is_rtsps_url(video):
7373
return None
@@ -80,12 +80,13 @@ def build_opencv_ffmpeg_capture_options(video: str) -> Optional[str]:
8080

8181

8282
@contextmanager
83-
def opencv_rtsps_tls_env(video: str) -> Iterator[None]:
83+
def opencv_rtsps_tls_env(video: Union[str, int]) -> Iterator[None]:
8484
"""Hold OPENCV_FFMPEG_CAPTURE_OPTIONS for one VideoCapture open.
8585
8686
OpenCV reads this env var at capture-open time. The lock spans set →
8787
VideoCapture open → restore so concurrent RTSPS sources cannot clobber each
88-
other's TLS options.
88+
other's TLS options. Isolation is RTSPS-vs-RTSPS only; concurrent non-RTSPS
89+
opens do not take the lock and may briefly inherit these options.
8990
"""
9091
options = build_opencv_ffmpeg_capture_options(video)
9192
if options is None:

inference/core/interfaces/camera/video_source.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
EndOfStreamError,
3737
StreamOperationNotAllowedError,
3838
)
39+
from inference.core.interfaces.camera.rtsp_opencv_tls import opencv_rtsps_tls_env
3940
from inference.core.interfaces.camera.source_reference_sanitizer import (
4041
redact_credentials_in_text,
4142
sanitize_source_reference,
@@ -149,7 +150,7 @@ class CV2VideoFrameProducer(VideoFrameProducer):
149150
def __init__(self, video: Union[str, int]):
150151
self._source_ref = video
151152
self._connection_error_message = ""
152-
with capture_process_stderr() as captured_stderr:
153+
with opencv_rtsps_tls_env(video), capture_process_stderr() as captured_stderr:
153154
if _consumes_camera_on_jetson(video=video):
154155
self.stream = cv2.VideoCapture(video, cv2.CAP_V4L2)
155156
else:

tests/inference/unit_tests/core/interfaces/camera/test_video_source.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import time
23
from datetime import datetime
34
from functools import partial
@@ -23,6 +24,10 @@
2324
SourceConnectionError,
2425
StreamOperationNotAllowedError,
2526
)
27+
from inference.core.interfaces.camera.rtsp_opencv_tls import (
28+
OPENCV_FFMPEG_CAPTURE_OPTIONS_ENV_VAR,
29+
)
30+
from inference.core.interfaces.camera.rtsp_tls import RTSP_TLS_VALIDATION_FLAGS_ENV_VAR
2631
from inference.core.interfaces.camera.stream_error_codes import StreamErrorCode
2732
from inference.core.interfaces.camera.video_source import (
2833
BufferConsumptionStrategy,
@@ -1880,3 +1885,26 @@ def test_consume_video_emits_poison_pill_on_consume_error() -> None:
18801885
assert source._state is StreamState.ERROR
18811886
with pytest.raises(EndOfStreamError):
18821887
source.read_frame(timeout=0.0)
1888+
1889+
1890+
def test_cv2_producer_sets_ffmpeg_tls_options_only_for_rtsps(
1891+
monkeypatch: pytest.MonkeyPatch,
1892+
) -> None:
1893+
# given
1894+
monkeypatch.delenv(OPENCV_FFMPEG_CAPTURE_OPTIONS_ENV_VAR, raising=False)
1895+
monkeypatch.delenv(RTSP_TLS_VALIDATION_FLAGS_ENV_VAR, raising=False)
1896+
seen = {}
1897+
1898+
def fake_video_capture(video, *args, **kwargs):
1899+
seen[video] = os.environ.get(OPENCV_FFMPEG_CAPTURE_OPTIONS_ENV_VAR)
1900+
return MagicMock()
1901+
1902+
# when
1903+
with patch.object(video_source.cv2, "VideoCapture", fake_video_capture):
1904+
CV2VideoFrameProducer("rtsps://camera.example/stream")
1905+
CV2VideoFrameProducer("rtsp://camera.example/stream")
1906+
1907+
# then - OpenCV reads the env var at capture-open time, so it must be set
1908+
# while VideoCapture runs for RTSPS, and left alone for plain RTSP
1909+
assert "tls_verify;1" in seen["rtsps://camera.example/stream"]
1910+
assert seen["rtsp://camera.example/stream"] is None

0 commit comments

Comments
 (0)