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