openipc_video/backends/macos/
surface.rs1use std::ffi::c_void;
2
3use objc2_core_foundation::CFRetained;
4use objc2_core_video::{
5 kCVPixelFormatType_32BGRA, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
6 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, kCVReturnSuccess, CVPixelBuffer,
7 CVPixelBufferGetBaseAddress, CVPixelBufferGetBaseAddressOfPlane, CVPixelBufferGetBytesPerRow,
8 CVPixelBufferGetBytesPerRowOfPlane, CVPixelBufferGetHeight, CVPixelBufferGetHeightOfPlane,
9 CVPixelBufferGetPixelFormatType, CVPixelBufferGetPlaneCount, CVPixelBufferGetWidth,
10 CVPixelBufferLockBaseAddress, CVPixelBufferLockFlags, CVPixelBufferUnlockBaseAddress,
11};
12
13use crate::{DecodedSurface, FrameDimensions, PixelFormat, VideoError};
14
15#[derive(Debug, Clone, Copy)]
17pub struct MacOsMappedPlane<'a> {
18 data: &'a [u8],
19 stride: usize,
20}
21
22impl<'a> MacOsMappedPlane<'a> {
23 pub const fn data(&self) -> &'a [u8] {
25 self.data
26 }
27
28 pub const fn stride(&self) -> usize {
30 self.stride
31 }
32}
33
34struct PixelBufferLock<'a>(&'a CVPixelBuffer);
35
36impl Drop for PixelBufferLock<'_> {
37 fn drop(&mut self) {
38 let _ = unsafe { CVPixelBufferUnlockBaseAddress(self.0, CVPixelBufferLockFlags::ReadOnly) };
41 }
42}
43
44pub struct MacOsVideoFrame {
46 pixel_buffer: CFRetained<CVPixelBuffer>,
47 decode_info_flags: u32,
48}
49
50impl std::fmt::Debug for MacOsVideoFrame {
51 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 formatter
53 .debug_struct("MacOsVideoFrame")
54 .field("pixel_buffer", &CFRetained::as_ptr(&self.pixel_buffer))
55 .field("decode_info_flags", &self.decode_info_flags)
56 .finish()
57 }
58}
59
60unsafe impl Send for MacOsVideoFrame {}
64unsafe impl Sync for MacOsVideoFrame {}
65
66impl MacOsVideoFrame {
67 pub(crate) fn new(pixel_buffer: CFRetained<CVPixelBuffer>, decode_info_flags: u32) -> Self {
68 Self {
69 pixel_buffer,
70 decode_info_flags,
71 }
72 }
73
74 pub fn pixel_buffer(&self) -> &CVPixelBuffer {
76 &self.pixel_buffer
77 }
78
79 pub const fn decode_info_flags(&self) -> u32 {
81 self.decode_info_flags
82 }
83
84 pub fn is_io_surface_backed(&self) -> bool {
86 unsafe extern "C-unwind" {
87 fn CVPixelBufferGetIOSurface(pixel_buffer: &CVPixelBuffer) -> *mut c_void;
88 }
89 !unsafe { CVPixelBufferGetIOSurface(&self.pixel_buffer) }.is_null()
92 }
93
94 pub fn with_mapped_planes<R>(
99 &self,
100 callback: impl FnOnce(&[MacOsMappedPlane<'_>]) -> R,
101 ) -> Result<R, VideoError> {
102 let status = unsafe {
105 CVPixelBufferLockBaseAddress(&self.pixel_buffer, CVPixelBufferLockFlags::ReadOnly)
106 };
107 if status != kCVReturnSuccess {
108 return Err(VideoError::Platform {
109 api: "CVPixelBufferLockBaseAddress",
110 status,
111 });
112 }
113 let _lock = PixelBufferLock(&self.pixel_buffer);
114 let plane_count = CVPixelBufferGetPlaneCount(&self.pixel_buffer);
115 let mut planes = Vec::with_capacity(plane_count.max(1));
116
117 if plane_count == 0 {
118 let pointer = CVPixelBufferGetBaseAddress(&self.pixel_buffer).cast::<u8>();
119 let stride = CVPixelBufferGetBytesPerRow(&self.pixel_buffer);
120 let length = stride.saturating_mul(CVPixelBufferGetHeight(&self.pixel_buffer));
121 if pointer.is_null() && length != 0 {
122 return Err(VideoError::Backend {
123 backend: "videotoolbox",
124 operation: "map pixel buffer",
125 message: "CoreVideo returned a null base address".to_owned(),
126 });
127 }
128 let data = unsafe { std::slice::from_raw_parts(pointer, length) };
131 planes.push(MacOsMappedPlane { data, stride });
132 } else {
133 for index in 0..plane_count {
134 let pointer =
135 CVPixelBufferGetBaseAddressOfPlane(&self.pixel_buffer, index).cast::<u8>();
136 let stride = CVPixelBufferGetBytesPerRowOfPlane(&self.pixel_buffer, index);
137 let length =
138 stride.saturating_mul(CVPixelBufferGetHeightOfPlane(&self.pixel_buffer, index));
139 if pointer.is_null() && length != 0 {
140 return Err(VideoError::Backend {
141 backend: "videotoolbox",
142 operation: "map pixel-buffer plane",
143 message: format!("CoreVideo returned a null address for plane {index}"),
144 });
145 }
146 let data = unsafe { std::slice::from_raw_parts(pointer, length) };
149 planes.push(MacOsMappedPlane { data, stride });
150 }
151 }
152
153 Ok(callback(&planes))
154 }
155}
156
157impl DecodedSurface for MacOsVideoFrame {
158 fn dimensions(&self) -> FrameDimensions {
159 FrameDimensions {
160 width: u32::try_from(CVPixelBufferGetWidth(&self.pixel_buffer)).unwrap_or(u32::MAX),
161 height: u32::try_from(CVPixelBufferGetHeight(&self.pixel_buffer)).unwrap_or(u32::MAX),
162 }
163 }
164
165 fn pixel_format(&self) -> PixelFormat {
166 let value = CVPixelBufferGetPixelFormatType(&self.pixel_buffer);
167 if value == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange {
168 PixelFormat::Nv12VideoRange
169 } else if value == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange {
170 PixelFormat::Nv12FullRange
171 } else if value == kCVPixelFormatType_32BGRA {
172 PixelFormat::Bgra8
173 } else {
174 PixelFormat::Native(value)
175 }
176 }
177}