Skip to main content

camera_stream/platform/macos/
ext.rs

1use core::ffi::c_void;
2
3use objc2_av_foundation::{AVCaptureDevice, AVCaptureExposureMode, AVCaptureFocusMode};
4use objc2_core_foundation::CGPoint;
5
6use crate::error::{Error, PlatformError};
7use crate::platform::macos::device::MacosCameraDevice;
8use crate::platform::macos::frame::MacosFrame;
9use crate::types::FrameRate;
10
11// Re-export platform-specific enums for convenience
12pub use objc2_av_foundation::{
13    AVCaptureExposureMode as MacosExposureMode, AVCaptureFocusMode as MacosFocusMode,
14    AVCaptureTorchMode as MacosTorchMode, AVCaptureWhiteBalanceMode as MacosWhiteBalanceMode,
15};
16
17/// RAII guard for `AVCaptureDevice` configuration lock.
18pub struct ConfigLockGuard<'a> {
19    device: &'a AVCaptureDevice,
20}
21
22impl<'a> ConfigLockGuard<'a> {
23    pub fn device(&self) -> &AVCaptureDevice {
24        self.device
25    }
26}
27
28impl<'a> Drop for ConfigLockGuard<'a> {
29    fn drop(&mut self) {
30        unsafe { self.device.unlockForConfiguration() };
31    }
32}
33
34/// macOS-specific camera device controls.
35pub trait MacosCameraDeviceExt {
36    fn lock_for_configuration(&self) -> Result<ConfigLockGuard<'_>, Error>;
37
38    // Focus
39    fn focus_modes(&self) -> impl Iterator<Item = MacosFocusMode>;
40    fn set_focus_mode(&self, mode: MacosFocusMode) -> Result<(), Error>;
41    fn set_focus_point(&self, x: f64, y: f64) -> Result<(), Error>;
42
43    // Exposure
44    fn exposure_modes(&self) -> impl Iterator<Item = MacosExposureMode>;
45    fn set_exposure_mode(&self, mode: MacosExposureMode) -> Result<(), Error>;
46    fn set_exposure_point(&self, x: f64, y: f64) -> Result<(), Error>;
47    fn set_exposure_target_bias(&self, bias: f32) -> Result<(), Error>;
48
49    // White balance
50    fn set_white_balance_mode(&self, mode: MacosWhiteBalanceMode) -> Result<(), Error>;
51
52    // Torch
53    fn has_torch(&self) -> bool;
54    fn set_torch_mode(&self, mode: MacosTorchMode) -> Result<(), Error>;
55
56    // Zoom
57    fn max_zoom_factor(&self) -> f64;
58    fn set_zoom_factor(&self, factor: f64) -> Result<(), Error>;
59
60    // Active format / frame rate
61    fn set_active_frame_rate(&self, rate: FrameRate) -> Result<(), Error>;
62}
63
64impl MacosCameraDeviceExt for MacosCameraDevice {
65    fn lock_for_configuration(&self) -> Result<ConfigLockGuard<'_>, Error> {
66        unsafe { self.device.lockForConfiguration() }
67            .map_err(|e| Error::Platform(PlatformError::NsError(e)))?;
68        Ok(ConfigLockGuard {
69            device: &self.device,
70        })
71    }
72
73    fn focus_modes(&self) -> impl Iterator<Item = MacosFocusMode> {
74        let device = &self.device;
75        [
76            AVCaptureFocusMode(0), // Locked
77            AVCaptureFocusMode(1), // AutoFocus
78            AVCaptureFocusMode(2), // ContinuousAutoFocus
79        ]
80        .into_iter()
81        .filter(move |mode| unsafe { device.isFocusModeSupported(*mode) })
82    }
83
84    fn set_focus_mode(&self, mode: MacosFocusMode) -> Result<(), Error> {
85        let _guard = self.lock_for_configuration()?;
86        unsafe { self.device.setFocusMode(mode) };
87        Ok(())
88    }
89
90    fn set_focus_point(&self, x: f64, y: f64) -> Result<(), Error> {
91        if !unsafe { self.device.isFocusPointOfInterestSupported() } {
92            return Err(Error::Platform(PlatformError::Message(
93                "focus point of interest not supported",
94            )));
95        }
96        let _guard = self.lock_for_configuration()?;
97        unsafe {
98            self.device.setFocusPointOfInterest(CGPoint { x, y });
99        }
100        Ok(())
101    }
102
103    fn exposure_modes(&self) -> impl Iterator<Item = MacosExposureMode> {
104        let device = &self.device;
105        [
106            AVCaptureExposureMode(0), // Locked
107            AVCaptureExposureMode(1), // AutoExpose
108            AVCaptureExposureMode(2), // ContinuousAutoExposure
109            AVCaptureExposureMode(3), // Custom
110        ]
111        .into_iter()
112        .filter(move |mode| unsafe { device.isExposureModeSupported(*mode) })
113    }
114
115    fn set_exposure_mode(&self, mode: MacosExposureMode) -> Result<(), Error> {
116        let _guard = self.lock_for_configuration()?;
117        unsafe { self.device.setExposureMode(mode) };
118        Ok(())
119    }
120
121    fn set_exposure_point(&self, x: f64, y: f64) -> Result<(), Error> {
122        if !unsafe { self.device.isExposurePointOfInterestSupported() } {
123            return Err(Error::Platform(PlatformError::Message(
124                "exposure point of interest not supported",
125            )));
126        }
127        let _guard = self.lock_for_configuration()?;
128        unsafe {
129            self.device.setExposurePointOfInterest(CGPoint { x, y });
130        }
131        Ok(())
132    }
133
134    fn set_exposure_target_bias(&self, bias: f32) -> Result<(), Error> {
135        let _guard = self.lock_for_configuration()?;
136        unsafe {
137            self.device
138                .setExposureTargetBias_completionHandler(bias, None);
139        }
140        Ok(())
141    }
142
143    fn set_white_balance_mode(&self, mode: MacosWhiteBalanceMode) -> Result<(), Error> {
144        if !unsafe { self.device.isWhiteBalanceModeSupported(mode) } {
145            return Err(Error::Platform(PlatformError::Message(
146                "white balance mode not supported",
147            )));
148        }
149        let _guard = self.lock_for_configuration()?;
150        unsafe { self.device.setWhiteBalanceMode(mode) };
151        Ok(())
152    }
153
154    fn has_torch(&self) -> bool {
155        unsafe { self.device.hasTorch() }
156    }
157
158    fn set_torch_mode(&self, mode: MacosTorchMode) -> Result<(), Error> {
159        if !unsafe { self.device.isTorchModeSupported(mode) } {
160            return Err(Error::Platform(PlatformError::Message(
161                "torch mode not supported",
162            )));
163        }
164        let _guard = self.lock_for_configuration()?;
165        unsafe { self.device.setTorchMode(mode) };
166        Ok(())
167    }
168
169    fn max_zoom_factor(&self) -> f64 {
170        unsafe { self.device.maxAvailableVideoZoomFactor() }
171    }
172
173    fn set_zoom_factor(&self, factor: f64) -> Result<(), Error> {
174        let _guard = self.lock_for_configuration()?;
175        unsafe { self.device.setVideoZoomFactor(factor) };
176        Ok(())
177    }
178
179    fn set_active_frame_rate(&self, rate: FrameRate) -> Result<(), Error> {
180        let _guard = self.lock_for_configuration()?;
181        let duration = objc2_core_media::CMTime {
182            value: rate.denominator as i64,
183            timescale: rate.numerator as i32,
184            flags: objc2_core_media::CMTimeFlags(1),
185            epoch: 0,
186        };
187        unsafe { self.device.setActiveVideoMinFrameDuration(duration) };
188        unsafe { self.device.setActiveVideoMaxFrameDuration(duration) };
189        Ok(())
190    }
191}
192
193/// macOS-specific frame data.
194pub trait MacosFrameExt {
195    fn sample_buffer_ptr(&self) -> *const c_void;
196}
197
198impl MacosFrameExt for MacosFrame<'_> {
199    fn sample_buffer_ptr(&self) -> *const c_void {
200        self.pixel_buffer_ptr()
201    }
202}