camera_stream/platform/macos/
stream.rs1use std::panic::AssertUnwindSafe;
2use std::sync::{Arc, Mutex};
3
4use objc2::rc::Retained;
5use objc2::runtime::AnyObject;
6use objc2::runtime::ProtocolObject;
7use objc2::{AllocAnyThread, DefinedClass, define_class, msg_send};
8use objc2_av_foundation::{
9 AVCaptureConnection, AVCaptureDevice, AVCaptureDeviceFormat, AVCaptureDeviceInput,
10 AVCaptureOutput, AVCaptureSession, AVCaptureVideoDataOutput,
11 AVCaptureVideoDataOutputSampleBufferDelegate,
12};
13use objc2_core_media::CMSampleBuffer;
14use objc2_core_video::{
15 CVPixelBufferLockBaseAddress, CVPixelBufferLockFlags, CVPixelBufferUnlockBaseAddress,
16 kCVPixelBufferPixelFormatTypeKey,
17};
18use objc2_foundation::{NSDictionary, NSNumber, NSObjectProtocol, NSString};
19
20use crate::error::{Error, PlatformError};
21use crate::platform::macos::device::pixel_format_to_fourcc;
22use crate::platform::macos::frame::{MacosFrame, MacosTimestamp};
23use crate::stream::CameraStream;
24use crate::types::StreamConfig;
25
26fn catch_objc<R>(f: impl FnOnce() -> R + std::panic::UnwindSafe) -> Result<R, Error> {
28 objc2::exception::catch(f)
29 .map_err(|exception| Error::Platform(PlatformError::ObjCException(exception)))
30}
31
32type FrameCallback = Box<dyn FnMut(&MacosFrame<'_>) + Send + 'static>;
33
34struct DelegateIvars {
35 callback: Arc<Mutex<Option<FrameCallback>>>,
36}
37
38define_class!(
39 #[unsafe(super(objc2_foundation::NSObject))]
40 #[ivars = DelegateIvars]
41 #[name = "CameraStreamSampleBufferDelegate"]
42 struct SampleBufferDelegate;
43
44 impl SampleBufferDelegate {
45 }
46
47 unsafe impl NSObjectProtocol for SampleBufferDelegate {}
48
49 unsafe impl AVCaptureVideoDataOutputSampleBufferDelegate for SampleBufferDelegate {
50 #[unsafe(method(captureOutput:didOutputSampleBuffer:fromConnection:))]
51 #[allow(non_snake_case)]
52 unsafe fn captureOutput_didOutputSampleBuffer_fromConnection(
53 &self,
54 _output: &AVCaptureOutput,
55 sample_buffer: &CMSampleBuffer,
56 _connection: &AVCaptureConnection,
57 ) {
58 let pixel_buffer = match unsafe { sample_buffer.image_buffer() } {
60 Some(pb) => pb,
61 None => return,
62 };
63
64 let cm_time = unsafe { sample_buffer.presentation_time_stamp() };
66 let timestamp = MacosTimestamp {
67 value: cm_time.value,
68 timescale: cm_time.timescale,
69 flags: cm_time.flags.0,
70 epoch: cm_time.epoch,
71 };
72
73 let lock_flags = CVPixelBufferLockFlags::ReadOnly;
75 unsafe {
76 CVPixelBufferLockBaseAddress(&pixel_buffer, lock_flags);
77 }
78
79 let frame = unsafe { MacosFrame::from_locked_pixel_buffer(&pixel_buffer, timestamp) };
80
81 if let Ok(mut guard) = self.ivars().callback.lock()
82 && let Some(ref mut cb) = *guard {
83 cb(&frame);
84 }
85
86 unsafe {
87 CVPixelBufferUnlockBaseAddress(&pixel_buffer, lock_flags);
88 }
89 }
90 }
91);
92
93impl SampleBufferDelegate {
94 fn new(callback: FrameCallback) -> Retained<Self> {
95 let ivars = DelegateIvars {
96 callback: Arc::new(Mutex::new(Some(callback))),
97 };
98 let obj = Self::alloc().set_ivars(ivars);
99 unsafe { msg_send![super(obj), init] }
100 }
101}
102
103pub struct MacosCameraStream {
105 session: Retained<AVCaptureSession>,
106 device: Retained<AVCaptureDevice>,
107 output: Retained<AVCaptureVideoDataOutput>,
108 delegate: Option<Retained<SampleBufferDelegate>>,
109 config_locked: bool,
111 running: bool,
112}
113
114impl MacosCameraStream {
115 pub(crate) fn new(
116 device: Retained<AVCaptureDevice>,
117 config: &StreamConfig,
118 ) -> Result<Self, Error> {
119 let session = unsafe { AVCaptureSession::new() };
120
121 let input = unsafe { AVCaptureDeviceInput::deviceInputWithDevice_error(&device) }
123 .map_err(|e| Error::Platform(PlatformError::NsError(e)))?;
124
125 let output = unsafe { AVCaptureVideoDataOutput::new() };
127
128 let target_fourcc = pixel_format_to_fourcc(&config.pixel_format);
131 unsafe {
132 let key: &NSString = std::mem::transmute::<&objc2_core_foundation::CFString, &NSString>(
133 kCVPixelBufferPixelFormatTypeKey,
134 );
135 let value = NSNumber::new_u32(target_fourcc);
136 let settings: Retained<NSDictionary<NSString, AnyObject>> =
137 NSDictionary::dictionaryWithObject_forKey(&value, ProtocolObject::from_ref(key));
138 output.setVideoSettings(Some(&settings));
139 }
140
141 let formats = unsafe { device.formats() };
143 let mut matched_format: Option<Retained<AVCaptureDeviceFormat>> = None;
144
145 for format in formats.iter() {
146 let desc = unsafe { format.formatDescription() };
147 let sub_type = unsafe { desc.media_sub_type() };
148 let dims = unsafe { objc2_core_media::CMVideoFormatDescriptionGetDimensions(&desc) };
149
150 if sub_type == target_fourcc
151 && dims.width as u32 == config.size.width
152 && dims.height as u32 == config.size.height
153 {
154 matched_format = Some(format.clone());
155 break;
156 }
157 }
158
159 let matched = matched_format.ok_or(Error::UnsupportedFormat)?;
160
161 let frame_duration = objc2_core_media::CMTime {
162 value: config.frame_rate.denominator as i64,
163 timescale: config.frame_rate.numerator as i32,
164 flags: objc2_core_media::CMTimeFlags(1), epoch: 0,
166 };
167
168 catch_objc(AssertUnwindSafe(|| unsafe {
169 session.beginConfiguration();
170
171 if !session.canAddInput(&input) {
173 session.commitConfiguration();
174 return Err(Error::Platform(PlatformError::Message(
175 "cannot add input to session",
176 )));
177 }
178 session.addInput(&input);
179
180 if !session.canAddOutput(&output) {
182 session.commitConfiguration();
183 return Err(Error::Platform(PlatformError::Message(
184 "cannot add output to session",
185 )));
186 }
187 session.addOutput(&output);
188
189 session.commitConfiguration();
190 Ok::<(), Error>(())
191 }))??;
192
193 unsafe { device.lockForConfiguration() }
198 .map_err(|e| Error::Platform(PlatformError::NsError(e)))?;
199
200 catch_objc(AssertUnwindSafe(|| unsafe {
201 device.setActiveFormat(&matched);
202 device.setActiveVideoMinFrameDuration(frame_duration);
203 device.setActiveVideoMaxFrameDuration(frame_duration);
204 }))?;
205
206 Ok(MacosCameraStream {
207 session,
208 device,
209 output,
210 delegate: None,
211 config_locked: true,
212 running: false,
213 })
214 }
215}
216
217impl CameraStream for MacosCameraStream {
218 type Frame<'a> = MacosFrame<'a>;
219 type Error = Error;
220
221 fn start<F>(&mut self, callback: F) -> Result<(), Self::Error>
222 where
223 F: FnMut(&Self::Frame<'_>) + Send + 'static,
224 {
225 if self.running {
226 return Err(Error::AlreadyStarted);
227 }
228
229 let delegate = SampleBufferDelegate::new(Box::new(callback));
230
231 let queue = dispatch2::DispatchQueue::new(
232 "camera-stream.callback",
233 dispatch2::DispatchQueueAttr::SERIAL,
234 );
235
236 unsafe {
237 self.output.setSampleBufferDelegate_queue(
238 Some(ProtocolObject::from_ref(&*delegate)),
239 Some(&queue),
240 );
241 }
242
243 self.delegate = Some(delegate);
244
245 catch_objc(AssertUnwindSafe(|| unsafe { self.session.startRunning() }))?;
246 self.running = true;
247
248 if self.config_locked {
251 unsafe { self.device.unlockForConfiguration() };
252 self.config_locked = false;
253 }
254
255 Ok(())
256 }
257
258 fn stop(&mut self) -> Result<(), Self::Error> {
259 if !self.running {
260 return Err(Error::NotStarted);
261 }
262
263 unsafe { self.session.stopRunning() };
264
265 unsafe {
266 self.output.setSampleBufferDelegate_queue(None, None);
267 }
268
269 if let Some(ref delegate) = self.delegate
271 && let Ok(mut guard) = delegate.ivars().callback.lock()
272 {
273 *guard = None;
274 }
275 self.delegate = None;
276 self.running = false;
277
278 Ok(())
279 }
280}
281
282impl Drop for MacosCameraStream {
283 fn drop(&mut self) {
284 if self.running {
285 let _ = self.stop();
286 }
287 if self.config_locked {
288 unsafe { self.device.unlockForConfiguration() };
289 self.config_locked = false;
290 }
291 }
292}