Skip to content

Commit e41a3d4

Browse files
authored
Add video device index (#51)
* Add video device index * Add unit tests
1 parent 13a60d6 commit e41a3d4

2 files changed

Lines changed: 205 additions & 0 deletions

File tree

src/om1_vlm/video/video_stream.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class VideoStream:
3838
By default (640, 480)
3939
jpeg_quality : int, optional
4040
JPEG quality for encoding frames, by default 70
41+
device_index : int, optional
42+
Index of the camera device to use, by default 0
4143
"""
4244

4345
def __init__(
@@ -47,6 +49,7 @@ def __init__(
4749
fps: Optional[int] = 30,
4850
resolution: Optional[Tuple[int, int]] = (640, 480),
4951
jpeg_quality: int = 70,
52+
device_index: int = 0,
5053
):
5154
self._video_thread: Optional[threading.Thread] = None
5255

@@ -55,6 +58,7 @@ def __init__(
5558
self.register_frame_callback(frame_callback)
5659

5760
# Video capture device
61+
self.device_index = device_index
5862
self._cap = None
5963

6064
self.running: bool = True
@@ -96,6 +100,13 @@ def on_video(self):
96100
camindex = 0 if devices else 0
97101
else:
98102
camindex = "/dev/video" + str(devices[0][0]) if devices else "/dev/video0"
103+
104+
if self.device_index != 0:
105+
if platform.system() == "Darwin":
106+
camindex = self.device_index
107+
else:
108+
camindex = f"/dev/video{self.device_index}"
109+
99110
logger.info(f"Using camera: {camindex}")
100111

101112
self._cap = cv2.VideoCapture(camindex)

tests/om1_vlm/video/test_video_stream.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,3 +360,197 @@ def callback(frame_data):
360360
assert (
361361
ts > 1600000000
362362
), "Timestamp should be a reasonable Unix timestamp"
363+
364+
365+
def test_device_index_default():
366+
"""Test that default device_index (0) works correctly."""
367+
captured_device_indices = []
368+
369+
def mock_video_capture(device_index):
370+
captured_device_indices.append(device_index)
371+
return MockVideoCapture(device_index)
372+
373+
mock_devices = [(0, "Mock Camera"), (1, "Another Camera")]
374+
375+
with (
376+
patch(
377+
"om1_vlm.video.video_stream.enumerate_video_devices",
378+
return_value=mock_devices,
379+
),
380+
patch("platform.system", return_value="Linux"),
381+
patch("cv2.VideoCapture", side_effect=mock_video_capture),
382+
patch("cv2.imencode", return_value=(True, b"fake_image_data")),
383+
):
384+
# Test default device_index (0)
385+
stream = VideoStream(device_index=0)
386+
stream.start()
387+
388+
# Wait briefly for initialization
389+
time.sleep(0.1)
390+
stream.stop()
391+
392+
# Verify that the default device was used (Linux uses /dev/video0 for device 0)
393+
assert len(captured_device_indices) > 0, "VideoCapture should have been called"
394+
assert (
395+
captured_device_indices[0] == "/dev/video0"
396+
), f"Expected /dev/video0, got {captured_device_indices[0]}"
397+
398+
399+
def test_device_index_custom_linux():
400+
"""Test that custom device_index works correctly on Linux."""
401+
captured_device_indices = []
402+
403+
def mock_video_capture(device_index):
404+
captured_device_indices.append(device_index)
405+
return MockVideoCapture(device_index)
406+
407+
mock_devices = [(0, "Mock Camera"), (1, "Another Camera"), (2, "Third Camera")]
408+
409+
with (
410+
patch(
411+
"om1_vlm.video.video_stream.enumerate_video_devices",
412+
return_value=mock_devices,
413+
),
414+
patch("platform.system", return_value="Linux"),
415+
patch("cv2.VideoCapture", side_effect=mock_video_capture),
416+
patch("cv2.imencode", return_value=(True, b"fake_image_data")),
417+
):
418+
# Test custom device_index (2)
419+
stream = VideoStream(device_index=2)
420+
stream.start()
421+
422+
# Wait briefly for initialization
423+
time.sleep(0.1)
424+
stream.stop()
425+
426+
# Verify that the correct device was used (Linux uses /dev/video2 for device 2)
427+
assert len(captured_device_indices) > 0, "VideoCapture should have been called"
428+
assert (
429+
captured_device_indices[0] == "/dev/video2"
430+
), f"Expected /dev/video2, got {captured_device_indices[0]}"
431+
432+
433+
def test_device_index_custom_macos():
434+
"""Test that custom device_index works correctly on macOS."""
435+
captured_device_indices = []
436+
437+
def mock_video_capture(device_index):
438+
captured_device_indices.append(device_index)
439+
return MockVideoCapture(device_index)
440+
441+
mock_devices = [(0, "Mock Camera"), (1, "Another Camera"), (2, "Third Camera")]
442+
443+
with (
444+
patch(
445+
"om1_vlm.video.video_stream.enumerate_video_devices",
446+
return_value=mock_devices,
447+
),
448+
patch("platform.system", return_value="Darwin"),
449+
patch("cv2.VideoCapture", side_effect=mock_video_capture),
450+
patch("cv2.imencode", return_value=(True, b"fake_image_data")),
451+
):
452+
# Test custom device_index (1)
453+
stream = VideoStream(device_index=1)
454+
stream.start()
455+
456+
# Wait briefly for initialization
457+
time.sleep(0.1)
458+
stream.stop()
459+
460+
# Verify that the correct device was used (macOS uses device_index directly)
461+
assert len(captured_device_indices) > 0, "VideoCapture should have been called"
462+
assert (
463+
captured_device_indices[0] == 1
464+
), f"Expected 1, got {captured_device_indices[0]}"
465+
466+
467+
def test_device_index_zero_macos():
468+
"""Test that device_index 0 works correctly on macOS."""
469+
captured_device_indices = []
470+
471+
def mock_video_capture(device_index):
472+
captured_device_indices.append(device_index)
473+
return MockVideoCapture(device_index)
474+
475+
mock_devices = [(0, "Mock Camera"), (1, "Another Camera")]
476+
477+
with (
478+
patch(
479+
"om1_vlm.video.video_stream.enumerate_video_devices",
480+
return_value=mock_devices,
481+
),
482+
patch("platform.system", return_value="Darwin"),
483+
patch("cv2.VideoCapture", side_effect=mock_video_capture),
484+
patch("cv2.imencode", return_value=(True, b"fake_image_data")),
485+
):
486+
# Test device_index 0 on macOS
487+
stream = VideoStream(device_index=0)
488+
stream.start()
489+
490+
# Wait briefly for initialization
491+
time.sleep(0.1)
492+
stream.stop()
493+
494+
# Verify that device 0 was used (macOS uses device_index directly)
495+
assert len(captured_device_indices) > 0, "VideoCapture should have been called"
496+
assert (
497+
captured_device_indices[0] == 0
498+
), f"Expected 0, got {captured_device_indices[0]}"
499+
500+
501+
def test_device_index_no_devices_available():
502+
"""Test device_index behavior when no devices are available."""
503+
captured_device_indices = []
504+
505+
def mock_video_capture(device_index):
506+
captured_device_indices.append(device_index)
507+
return MockVideoCapture(device_index)
508+
509+
# Empty device list
510+
mock_devices = []
511+
512+
with (
513+
patch(
514+
"om1_vlm.video.video_stream.enumerate_video_devices",
515+
return_value=mock_devices,
516+
),
517+
patch("platform.system", return_value="Linux"),
518+
patch("cv2.VideoCapture", side_effect=mock_video_capture),
519+
patch("cv2.imencode", return_value=(True, b"fake_image_data")),
520+
):
521+
# Test with custom device_index when no devices are available
522+
stream = VideoStream(device_index=1)
523+
stream.start()
524+
525+
# Wait briefly for initialization
526+
time.sleep(0.1)
527+
stream.stop()
528+
529+
# Should still use the specified device_index
530+
assert len(captured_device_indices) > 0, "VideoCapture should have been called"
531+
assert (
532+
captured_device_indices[0] == "/dev/video1"
533+
), f"Expected /dev/video1, got {captured_device_indices[0]}"
534+
535+
536+
def test_device_index_initialization():
537+
"""Test that device_index is properly stored during initialization."""
538+
# Test default device_index
539+
stream = VideoStream()
540+
assert stream.device_index == 0, "Default device_index should be 0"
541+
542+
# Test custom device_index
543+
stream_custom = VideoStream(device_index=3)
544+
assert (
545+
stream_custom.device_index == 3
546+
), "Custom device_index should be stored correctly"
547+
548+
# Test with other parameters
549+
stream_complex = VideoStream(
550+
device_index=5, fps=60, resolution=(1920, 1080), jpeg_quality=90
551+
)
552+
assert (
553+
stream_complex.device_index == 5
554+
), "Device index should be preserved with other parameters"
555+
assert stream_complex.fps == 60, "Other parameters should also be preserved"
556+
assert stream_complex.resolution == (1920, 1080), "Resolution should be preserved"

0 commit comments

Comments
 (0)