Skip to main content

camera_stream/platform/macos/
ext.rs

1use std::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) -> Vec<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) -> Vec<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::Message(e.to_string())))?;
68        Ok(ConfigLockGuard {
69            device: &self.device,
70        })
71    }
72
73    fn focus_modes(&self) -> Vec<MacosFocusMode> {
74        let mut modes = Vec::new();
75        let candidates = [
76            AVCaptureFocusMode(0), // Locked
77            AVCaptureFocusMode(1), // AutoFocus
78            AVCaptureFocusMode(2), // ContinuousAutoFocus
79        ];
80        for mode in &candidates {
81            if unsafe { self.device.isFocusModeSupported(*mode) } {
82                modes.push(*mode);
83            }
84        }
85        modes
86    }
87
88    fn set_focus_mode(&self, mode: MacosFocusMode) -> Result<(), Error> {
89        let _guard = self.lock_for_configuration()?;
90        unsafe { self.device.setFocusMode(mode) };
91        Ok(())
92    }
93
94    fn set_focus_point(&self, x: f64, y: f64) -> Result<(), Error> {
95        if !unsafe { self.device.isFocusPointOfInterestSupported() } {
96            return Err(Error::Platform(PlatformError::Message(
97                "focus point of interest not supported".into(),
98            )));
99        }
100        let _guard = self.lock_for_configuration()?;
101        unsafe {
102            self.device.setFocusPointOfInterest(CGPoint { x, y });
103        }
104        Ok(())
105    }
106
107    fn exposure_modes(&self) -> Vec<MacosExposureMode> {
108        let mut modes = Vec::new();
109        let candidates = [
110            AVCaptureExposureMode(0), // Locked
111            AVCaptureExposureMode(1), // AutoExpose
112            AVCaptureExposureMode(2), // ContinuousAutoExposure
113            AVCaptureExposureMode(3), // Custom
114        ];
115        for mode in &candidates {
116            if unsafe { self.device.isExposureModeSupported(*mode) } {
117                modes.push(*mode);
118            }
119        }
120        modes
121    }
122
123    fn set_exposure_mode(&self, mode: MacosExposureMode) -> Result<(), Error> {
124        let _guard = self.lock_for_configuration()?;
125        unsafe { self.device.setExposureMode(mode) };
126        Ok(())
127    }
128
129    fn set_exposure_point(&self, x: f64, y: f64) -> Result<(), Error> {
130        if !unsafe { self.device.isExposurePointOfInterestSupported() } {
131            return Err(Error::Platform(PlatformError::Message(
132                "exposure point of interest not supported".into(),
133            )));
134        }
135        let _guard = self.lock_for_configuration()?;
136        unsafe {
137            self.device.setExposurePointOfInterest(CGPoint { x, y });
138        }
139        Ok(())
140    }
141
142    fn set_exposure_target_bias(&self, bias: f32) -> Result<(), Error> {
143        let _guard = self.lock_for_configuration()?;
144        unsafe {
145            self.device
146                .setExposureTargetBias_completionHandler(bias, None);
147        }
148        Ok(())
149    }
150
151    fn set_white_balance_mode(&self, mode: MacosWhiteBalanceMode) -> Result<(), Error> {
152        if !unsafe { self.device.isWhiteBalanceModeSupported(mode) } {
153            return Err(Error::Platform(PlatformError::Message(
154                "white balance mode not supported".into(),
155            )));
156        }
157        let _guard = self.lock_for_configuration()?;
158        unsafe { self.device.setWhiteBalanceMode(mode) };
159        Ok(())
160    }
161
162    fn has_torch(&self) -> bool {
163        unsafe { self.device.hasTorch() }
164    }
165
166    fn set_torch_mode(&self, mode: MacosTorchMode) -> Result<(), Error> {
167        if !unsafe { self.device.isTorchModeSupported(mode) } {
168            return Err(Error::Platform(PlatformError::Message(
169                "torch mode not supported".into(),
170            )));
171        }
172        let _guard = self.lock_for_configuration()?;
173        unsafe { self.device.setTorchMode(mode) };
174        Ok(())
175    }
176
177    fn max_zoom_factor(&self) -> f64 {
178        unsafe { self.device.maxAvailableVideoZoomFactor() }
179    }
180
181    fn set_zoom_factor(&self, factor: f64) -> Result<(), Error> {
182        let _guard = self.lock_for_configuration()?;
183        unsafe { self.device.setVideoZoomFactor(factor) };
184        Ok(())
185    }
186
187    fn set_active_frame_rate(&self, rate: FrameRate) -> Result<(), Error> {
188        let _guard = self.lock_for_configuration()?;
189        let duration = objc2_core_media::CMTime {
190            value: rate.denominator as i64,
191            timescale: rate.numerator as i32,
192            flags: objc2_core_media::CMTimeFlags(1),
193            epoch: 0,
194        };
195        unsafe { self.device.setActiveVideoMinFrameDuration(duration) };
196        unsafe { self.device.setActiveVideoMaxFrameDuration(duration) };
197        Ok(())
198    }
199}
200
201/// macOS-specific frame data.
202pub trait MacosFrameExt {
203    fn sample_buffer_ptr(&self) -> *const c_void;
204}
205
206impl MacosFrameExt for MacosFrame<'_> {
207    fn sample_buffer_ptr(&self) -> *const c_void {
208        self.pixel_buffer_ptr()
209    }
210}