Skip to main content

camera_stream/platform/macos/
frame.rs

1use std::ffi::c_void;
2
3use objc2_core_video::{
4    CVPixelBuffer, CVPixelBufferGetBaseAddress, CVPixelBufferGetBaseAddressOfPlane,
5    CVPixelBufferGetBytesPerRow, CVPixelBufferGetBytesPerRowOfPlane, CVPixelBufferGetHeight,
6    CVPixelBufferGetHeightOfPlane, CVPixelBufferGetPixelFormatType, CVPixelBufferGetPlaneCount,
7    CVPixelBufferGetWidth,
8};
9
10use crate::frame::{Frame, Plane, Timestamp};
11use crate::platform::macos::device::fourcc_to_pixel_format;
12use crate::types::{PixelFormat, Size};
13
14/// A presentation timestamp mirroring Core Media's `CMTime`.
15///
16/// Preserves the full precision and semantics of the underlying `CMTime`,
17/// including flags and epoch. For a quick seconds value, use
18/// [`as_secs_f64()`](Timestamp::as_secs_f64).
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct MacosTimestamp {
21    /// The numerator of the time value (ticks).
22    pub value: i64,
23    /// Ticks per second.
24    pub timescale: i32,
25    /// CMTime flags (valid, has been rounded, positive/negative infinity, indefinite).
26    pub flags: u32,
27    /// Distinguishes separate timelines that may restart from zero.
28    pub epoch: i64,
29}
30
31impl Timestamp for MacosTimestamp {
32    fn as_secs_f64(&self) -> f64 {
33        if self.timescale > 0 {
34            self.value as f64 / self.timescale as f64
35        } else {
36            0.0
37        }
38    }
39}
40
41/// A video frame backed by a `CVPixelBuffer`.
42/// Only valid within the callback scope.
43pub struct MacosFrame<'a> {
44    pixel_buffer: &'a CVPixelBuffer,
45    planes: Vec<Plane<'a>>,
46    pixel_format: PixelFormat,
47    size: Size,
48    timestamp: MacosTimestamp,
49}
50
51impl<'a> MacosFrame<'a> {
52    /// Create a frame from a locked pixel buffer.
53    /// SAFETY: The pixel buffer base address must be locked for the lifetime 'a.
54    pub(crate) unsafe fn from_locked_pixel_buffer(
55        pixel_buffer: &'a CVPixelBuffer,
56        timestamp: MacosTimestamp,
57    ) -> Self {
58        let width = CVPixelBufferGetWidth(pixel_buffer);
59        let height = CVPixelBufferGetHeight(pixel_buffer);
60        let fourcc = CVPixelBufferGetPixelFormatType(pixel_buffer);
61        let pixel_format = fourcc_to_pixel_format(fourcc).unwrap_or(PixelFormat::Nv12);
62        let size = Size {
63            width: width as u32,
64            height: height as u32,
65        };
66
67        let plane_count = CVPixelBufferGetPlaneCount(pixel_buffer);
68        let planes = if plane_count == 0 {
69            // Non-planar: single plane
70            let base = CVPixelBufferGetBaseAddress(pixel_buffer);
71            let bytes_per_row = CVPixelBufferGetBytesPerRow(pixel_buffer);
72            if base.is_null() {
73                vec![]
74            } else {
75                let len = bytes_per_row * height;
76                let data = unsafe { std::slice::from_raw_parts(base as *const u8, len) };
77                vec![Plane {
78                    data,
79                    bytes_per_row,
80                }]
81            }
82        } else {
83            (0..plane_count)
84                .filter_map(|i| {
85                    let base = CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, i);
86                    if base.is_null() {
87                        return None;
88                    }
89                    let bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, i);
90                    let h = CVPixelBufferGetHeightOfPlane(pixel_buffer, i);
91                    let len = bytes_per_row * h;
92                    let data = unsafe { std::slice::from_raw_parts(base as *const u8, len) };
93                    Some(Plane {
94                        data,
95                        bytes_per_row,
96                    })
97                })
98                .collect()
99        };
100
101        MacosFrame {
102            pixel_buffer,
103            planes,
104            pixel_format,
105            size,
106            timestamp,
107        }
108    }
109
110    /// Raw pointer to the backing `CVPixelBuffer` (escape hatch).
111    pub fn pixel_buffer_ptr(&self) -> *const c_void {
112        self.pixel_buffer as *const CVPixelBuffer as *const c_void
113    }
114}
115
116impl<'a> Frame for MacosFrame<'a> {
117    type Timestamp = MacosTimestamp;
118
119    fn pixel_format(&self) -> PixelFormat {
120        self.pixel_format
121    }
122
123    fn size(&self) -> Size {
124        self.size
125    }
126
127    fn planes(&self) -> &[Plane<'_>] {
128        &self.planes
129    }
130
131    fn timestamp(&self) -> MacosTimestamp {
132        self.timestamp
133    }
134}