ccap/
frame.rs

1use crate::{error::CcapError, sys, types::*};
2use std::ffi::CStr;
3
4/// Device information structure
5#[derive(Debug, Clone)]
6pub struct DeviceInfo {
7    /// Device name
8    pub name: String,
9    /// Supported pixel formats
10    pub supported_pixel_formats: Vec<PixelFormat>,
11    /// Supported resolutions
12    pub supported_resolutions: Vec<Resolution>,
13}
14
15impl DeviceInfo {
16    /// Create DeviceInfo from C structure
17    pub fn from_c_struct(info: &sys::CcapDeviceInfo) -> Result<Self, CcapError> {
18        let name_cstr = unsafe { CStr::from_ptr(info.deviceName.as_ptr()) };
19        let name = name_cstr
20            .to_str()
21            .map_err(|e| CcapError::StringConversionError(e.to_string()))?
22            .to_string();
23
24        // Ensure we don't exceed array bounds
25        let format_count = (info.pixelFormatCount).min(info.supportedPixelFormats.len());
26        let supported_pixel_formats = info.supportedPixelFormats[..format_count]
27            .iter()
28            .map(|&format| PixelFormat::from_c_enum(format))
29            .collect();
30
31        let resolution_count = (info.resolutionCount).min(info.supportedResolutions.len());
32        let supported_resolutions = info.supportedResolutions[..resolution_count]
33            .iter()
34            .map(|&res| Resolution::from(res))
35            .collect();
36
37        Ok(DeviceInfo {
38            name,
39            supported_pixel_formats,
40            supported_resolutions,
41        })
42    }
43}
44
45/// Video frame wrapper
46pub struct VideoFrame {
47    frame: *mut sys::CcapVideoFrame,
48    owns_frame: bool, // Whether we own the frame and should release it
49}
50
51impl VideoFrame {
52    pub(crate) fn from_c_ptr(frame: *mut sys::CcapVideoFrame) -> Self {
53        VideoFrame {
54            frame,
55            owns_frame: true,
56        }
57    }
58
59    /// Create frame from raw pointer without owning it (for callbacks)
60    pub(crate) fn from_c_ptr_ref(frame: *mut sys::CcapVideoFrame) -> Self {
61        VideoFrame {
62            frame,
63            owns_frame: false,
64        }
65    }
66
67    /// Get the internal C pointer (for internal use)
68    #[allow(dead_code)]
69    pub(crate) fn as_c_ptr(&self) -> *const sys::CcapVideoFrame {
70        self.frame as *const sys::CcapVideoFrame
71    }
72
73    /// Create frame from raw pointer (for internal use)
74    #[allow(dead_code)]
75    pub(crate) fn from_raw(frame: *mut sys::CcapVideoFrame) -> Option<Self> {
76        if frame.is_null() {
77            None
78        } else {
79            Some(VideoFrame {
80                frame,
81                owns_frame: true,
82            })
83        }
84    }
85
86    /// Get frame information
87    pub fn info<'a>(&'a self) -> crate::error::Result<VideoFrameInfo<'a>> {
88        let mut info = sys::CcapVideoFrameInfo::default();
89
90        let success = unsafe { sys::ccap_video_frame_get_info(self.frame, &mut info) };
91
92        if success {
93            // Calculate proper plane sizes based on pixel format
94            // For plane 0 (Y or main): stride * height
95            // For chroma planes (UV): stride * height/2 for most formats
96            let plane0_size = (info.stride[0] as usize) * (info.height as usize);
97            let plane1_size = if info.stride[1] > 0 {
98                (info.stride[1] as usize) * ((info.height as usize + 1) / 2)
99            } else {
100                0
101            };
102            let plane2_size = if info.stride[2] > 0 {
103                (info.stride[2] as usize) * ((info.height as usize + 1) / 2)
104            } else {
105                0
106            };
107
108            Ok(VideoFrameInfo {
109                width: info.width,
110                height: info.height,
111                pixel_format: PixelFormat::from(info.pixelFormat),
112                size_in_bytes: info.sizeInBytes,
113                timestamp: info.timestamp,
114                frame_index: info.frameIndex,
115                orientation: FrameOrientation::from(info.orientation),
116                data_planes: [
117                    if info.data[0].is_null() {
118                        None
119                    } else {
120                        Some(unsafe { std::slice::from_raw_parts(info.data[0], plane0_size) })
121                    },
122                    if info.data[1].is_null() {
123                        None
124                    } else {
125                        Some(unsafe { std::slice::from_raw_parts(info.data[1], plane1_size) })
126                    },
127                    if info.data[2].is_null() {
128                        None
129                    } else {
130                        Some(unsafe { std::slice::from_raw_parts(info.data[2], plane2_size) })
131                    },
132                ],
133                strides: [info.stride[0], info.stride[1], info.stride[2]],
134            })
135        } else {
136            Err(CcapError::FrameGrabFailed)
137        }
138    }
139
140    /// Get all frame data as a slice
141    pub fn data(&self) -> crate::error::Result<&[u8]> {
142        let mut info = sys::CcapVideoFrameInfo::default();
143
144        let success = unsafe { sys::ccap_video_frame_get_info(self.frame, &mut info) };
145
146        if success && !info.data[0].is_null() {
147            Ok(unsafe { std::slice::from_raw_parts(info.data[0], info.sizeInBytes as usize) })
148        } else {
149            Err(CcapError::FrameGrabFailed)
150        }
151    }
152
153    /// Get frame width (convenience method)
154    pub fn width(&self) -> u32 {
155        self.info().map(|info| info.width).unwrap_or(0)
156    }
157
158    /// Get frame height (convenience method)
159    pub fn height(&self) -> u32 {
160        self.info().map(|info| info.height).unwrap_or(0)
161    }
162
163    /// Get pixel format (convenience method)
164    pub fn pixel_format(&self) -> PixelFormat {
165        self.info()
166            .map(|info| info.pixel_format)
167            .unwrap_or(PixelFormat::Unknown)
168    }
169
170    /// Get data size in bytes (convenience method)
171    pub fn data_size(&self) -> u32 {
172        self.info().map(|info| info.size_in_bytes).unwrap_or(0)
173    }
174
175    /// Get frame index (convenience method)
176    pub fn index(&self) -> u64 {
177        self.info().map(|info| info.frame_index).unwrap_or(0)
178    }
179}
180
181impl Drop for VideoFrame {
182    fn drop(&mut self) {
183        if self.owns_frame {
184            unsafe {
185                sys::ccap_video_frame_release(self.frame);
186            }
187        }
188    }
189}
190
191// # Thread Safety Analysis for VideoFrame
192//
193// ## Send Implementation
194//
195// SAFETY: VideoFrame is Send because:
196// 1. The frame data pointer is obtained via ccap_video_frame_grab() which returns
197//    a new frame that is independent of the Provider
198// 2. The frame memory is managed by the C library with proper reference counting
199// 3. Once created, the frame data is immutable from the Rust side
200// 4. The ccap_video_frame_release() function is safe to call from any thread
201//
202// ## Why NOT Sync
203//
204// VideoFrame does NOT implement Sync because:
205// 1. The underlying C++ VideoFrame object may have internal mutable state
206// 2. Concurrent read access from multiple threads is not verified safe
207// 3. The C++ library does not document thread-safety guarantees for frame access
208//
209// ## Usage Guidelines
210//
211// - Safe: Moving a VideoFrame to another thread (Send)
212// - Safe: Dropping a VideoFrame on any thread
213// - NOT Safe: Sharing &VideoFrame between threads (no Sync)
214// - For multi-threaded access: Clone the frame data to owned Vec<u8> first
215//
216// ## Verification Status
217//
218// This thread-safety analysis is based on code inspection of the C++ implementation.
219// The ccap C++ library does not provide formal thread-safety documentation.
220// If you encounter issues with cross-thread frame usage, please report them at:
221// https://github.com/wysaid/CameraCapture/issues
222unsafe impl Send for VideoFrame {}
223
224/// High-level video frame information
225#[derive(Debug)]
226pub struct VideoFrameInfo<'a> {
227    /// Frame width in pixels
228    pub width: u32,
229    /// Frame height in pixels
230    pub height: u32,
231    /// Pixel format of the frame
232    pub pixel_format: PixelFormat,
233    /// Size of frame data in bytes
234    pub size_in_bytes: u32,
235    /// Frame timestamp
236    pub timestamp: u64,
237    /// Frame sequence index
238    pub frame_index: u64,
239    /// Frame orientation
240    pub orientation: FrameOrientation,
241    /// Frame data planes (up to 3 planes)
242    pub data_planes: [Option<&'a [u8]>; 3],
243    /// Stride values for each plane
244    pub strides: [u32; 3],
245}