Skip to main content

camera_stream/platform/macos/
frame.rs

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