Skip to main content

opencv_rs_core/
video_capture.rs

1//! Video capture port and associated DTOs.
2
3use std::path::Path;
4use std::sync::Arc;
5
6use crate::{MatView, PixelFormat, VideoCaptureError};
7
8/// Selector for the underlying capture backend.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum Backend {
11    /// Let the backend pick whichever implementation is available.
12    Auto,
13    /// Force an FFmpeg-backed capture pipeline.
14    Ffmpeg,
15}
16
17/// A single frame returned by a [`VideoStream`].
18#[derive(Debug, Clone)]
19pub struct CapturedFrame {
20    /// Frame width in pixels.
21    pub width: u32,
22    /// Frame height in pixels.
23    pub height: u32,
24    /// Pixel layout of [`Self::data`].
25    pub pixel_format: PixelFormat,
26    /// Shared-ownership pixel buffer.
27    pub data: Arc<[u8]>,
28}
29
30impl MatView for CapturedFrame {
31    fn width(&self) -> u32 {
32        self.width
33    }
34    fn height(&self) -> u32 {
35        self.height
36    }
37    fn channels(&self) -> u32 {
38        self.pixel_format.channels()
39    }
40    fn pixel_format(&self) -> PixelFormat {
41        self.pixel_format
42    }
43    fn data(&self) -> &[u8] {
44        &self.data
45    }
46}
47
48/// Opens video sources and produces [`VideoStream`] instances.
49pub trait VideoCapturePort: Send + Sync {
50    /// Open the given path/URL with the chosen backend.
51    fn open(
52        &self,
53        path: &Path,
54        backend: Backend,
55    ) -> Result<Box<dyn VideoStream>, VideoCaptureError>;
56}
57
58/// An open video stream producing frames on demand.
59pub trait VideoStream: Send {
60    /// Reads the next frame. Returns
61    /// [`VideoCaptureError::EndOfStream`] when the stream is exhausted.
62    fn read_frame(&mut self) -> Result<CapturedFrame, VideoCaptureError>;
63    /// Frames-per-second as reported by the backend.
64    fn fps(&self) -> Result<f64, VideoCaptureError>;
65    /// Rewind the stream so the next [`Self::read_frame`] returns the first frame.
66    fn seek_to_start(&mut self) -> Result<(), VideoCaptureError>;
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn captured_frame_exposes_mat_view() {
75        // height=3, Bgr8 -> channels=3. Distinct from 1 so the `height -> 1`
76        // and `channels -> 1` mutants cannot survive.
77        let frame = CapturedFrame {
78            width: 2,
79            height: 3,
80            pixel_format: PixelFormat::Bgr8,
81            data: Arc::from(vec![0u8; 2 * 3 * 3].into_boxed_slice()),
82        };
83        assert_eq!(frame.width(), 2);
84        assert_eq!(frame.height(), 3);
85        assert_eq!(frame.channels(), 3);
86        assert_eq!(frame.pixel_format(), PixelFormat::Bgr8);
87        assert_eq!(frame.data().len(), 18);
88    }
89
90    #[test]
91    fn backend_equality_is_value_wise() {
92        assert_eq!(Backend::Auto, Backend::Auto);
93        assert_ne!(Backend::Auto, Backend::Ffmpeg);
94    }
95}