camera_stream/platform/macos/
ext.rs1use std::panic::AssertUnwindSafe;
2
3use objc2_av_foundation::{AVCaptureDevice, AVCaptureExposureMode, AVCaptureFocusMode};
4use objc2_core_foundation::CGPoint;
5use objc2_core_video::CVPixelBuffer;
6
7use crate::error::{Error, PlatformError};
8use crate::platform::macos::catch_objc;
9use crate::platform::macos::device::MacosCameraDevice;
10use crate::platform::macos::frame::MacosFrame;
11use crate::types::Ratio;
12
13pub use objc2_av_foundation::{
15 AVCaptureExposureMode as MacosExposureMode, AVCaptureFocusMode as MacosFocusMode,
16 AVCaptureTorchMode as MacosTorchMode, AVCaptureWhiteBalanceMode as MacosWhiteBalanceMode,
17};
18
19pub struct ConfigLockGuard<'a> {
21 device: &'a AVCaptureDevice,
22}
23
24impl<'a> ConfigLockGuard<'a> {
25 pub fn device(&self) -> &AVCaptureDevice {
26 self.device
27 }
28}
29
30impl<'a> Drop for ConfigLockGuard<'a> {
31 fn drop(&mut self) {
32 unsafe { self.device.unlockForConfiguration() };
33 }
34}
35
36pub trait MacosCameraDeviceExt {
38 fn lock_for_configuration(&self) -> Result<ConfigLockGuard<'_>, Error>;
39
40 fn focus_modes(&self) -> impl Iterator<Item = MacosFocusMode>;
42 fn set_focus_mode(&self, mode: MacosFocusMode) -> Result<(), Error>;
43 fn set_focus_point(&self, x: f64, y: f64) -> Result<(), Error>;
44
45 fn exposure_modes(&self) -> impl Iterator<Item = MacosExposureMode>;
47 fn set_exposure_mode(&self, mode: MacosExposureMode) -> Result<(), Error>;
48 fn set_exposure_point(&self, x: f64, y: f64) -> Result<(), Error>;
49 fn set_exposure_target_bias(&self, bias: f32) -> Result<(), Error>;
50
51 fn set_white_balance_mode(&self, mode: MacosWhiteBalanceMode) -> Result<(), Error>;
53
54 fn has_torch(&self) -> bool;
56 fn set_torch_mode(&self, mode: MacosTorchMode) -> Result<(), Error>;
57
58 fn max_zoom_factor(&self) -> f64;
60 fn set_zoom_factor(&self, factor: f64) -> Result<(), Error>;
61
62 fn set_active_video_min_frame_duration(&self, duration: Ratio) -> Result<(), Error>;
64 fn set_active_video_max_frame_duration(&self, duration: Ratio) -> Result<(), Error>;
65}
66
67impl MacosCameraDeviceExt for MacosCameraDevice {
68 fn lock_for_configuration(&self) -> Result<ConfigLockGuard<'_>, Error> {
69 unsafe { self.device.lockForConfiguration() }
70 .map_err(|e| Error::Platform(PlatformError::NsError(e)))?;
71 Ok(ConfigLockGuard {
72 device: &self.device,
73 })
74 }
75
76 fn focus_modes(&self) -> impl Iterator<Item = MacosFocusMode> {
77 let device = &self.device;
78 [
79 AVCaptureFocusMode(0), AVCaptureFocusMode(1), AVCaptureFocusMode(2), ]
83 .into_iter()
84 .filter(move |mode| unsafe { device.isFocusModeSupported(*mode) })
85 }
86
87 fn set_focus_mode(&self, mode: MacosFocusMode) -> Result<(), Error> {
88 let _guard = self.lock_for_configuration()?;
89 catch_objc(AssertUnwindSafe(|| unsafe {
90 self.device.setFocusMode(mode)
91 }))
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",
98 )));
99 }
100 let _guard = self.lock_for_configuration()?;
101 catch_objc(AssertUnwindSafe(|| unsafe {
102 self.device.setFocusPointOfInterest(CGPoint { x, y });
103 }))
104 }
105
106 fn exposure_modes(&self) -> impl Iterator<Item = MacosExposureMode> {
107 let device = &self.device;
108 [
109 AVCaptureExposureMode(0), AVCaptureExposureMode(1), AVCaptureExposureMode(2), AVCaptureExposureMode(3), ]
114 .into_iter()
115 .filter(move |mode| unsafe { device.isExposureModeSupported(*mode) })
116 }
117
118 fn set_exposure_mode(&self, mode: MacosExposureMode) -> Result<(), Error> {
119 let _guard = self.lock_for_configuration()?;
120 catch_objc(AssertUnwindSafe(|| unsafe {
121 self.device.setExposureMode(mode)
122 }))
123 }
124
125 fn set_exposure_point(&self, x: f64, y: f64) -> Result<(), Error> {
126 if !unsafe { self.device.isExposurePointOfInterestSupported() } {
127 return Err(Error::Platform(PlatformError::Message(
128 "exposure point of interest not supported",
129 )));
130 }
131 let _guard = self.lock_for_configuration()?;
132 catch_objc(AssertUnwindSafe(|| unsafe {
133 self.device.setExposurePointOfInterest(CGPoint { x, y });
134 }))
135 }
136
137 fn set_exposure_target_bias(&self, bias: f32) -> Result<(), Error> {
138 let _guard = self.lock_for_configuration()?;
139 catch_objc(AssertUnwindSafe(|| unsafe {
140 self.device
141 .setExposureTargetBias_completionHandler(bias, None);
142 }))
143 }
144
145 fn set_white_balance_mode(&self, mode: MacosWhiteBalanceMode) -> Result<(), Error> {
146 if !unsafe { self.device.isWhiteBalanceModeSupported(mode) } {
147 return Err(Error::Platform(PlatformError::Message(
148 "white balance mode not supported",
149 )));
150 }
151 let _guard = self.lock_for_configuration()?;
152 catch_objc(AssertUnwindSafe(|| unsafe {
153 self.device.setWhiteBalanceMode(mode)
154 }))
155 }
156
157 fn has_torch(&self) -> bool {
158 unsafe { self.device.hasTorch() }
159 }
160
161 fn set_torch_mode(&self, mode: MacosTorchMode) -> Result<(), Error> {
162 if !unsafe { self.device.isTorchModeSupported(mode) } {
163 return Err(Error::Platform(PlatformError::Message(
164 "torch mode not supported",
165 )));
166 }
167 let _guard = self.lock_for_configuration()?;
168 catch_objc(AssertUnwindSafe(|| unsafe {
169 self.device.setTorchMode(mode)
170 }))
171 }
172
173 fn max_zoom_factor(&self) -> f64 {
174 unsafe { self.device.maxAvailableVideoZoomFactor() }
175 }
176
177 fn set_zoom_factor(&self, factor: f64) -> Result<(), Error> {
178 let _guard = self.lock_for_configuration()?;
179 catch_objc(AssertUnwindSafe(|| unsafe {
180 self.device.setVideoZoomFactor(factor)
181 }))
182 }
183
184 fn set_active_video_min_frame_duration(&self, duration: Ratio) -> Result<(), Error> {
185 let _guard = self.lock_for_configuration()?;
186 let cm_time = objc2_core_media::CMTime {
187 value: duration.numerator as i64,
188 timescale: duration.denominator as i32,
189 flags: objc2_core_media::CMTimeFlags(1),
190 epoch: 0,
191 };
192 catch_objc(AssertUnwindSafe(|| unsafe {
193 self.device.setActiveVideoMinFrameDuration(cm_time);
194 }))
195 }
196
197 fn set_active_video_max_frame_duration(&self, duration: Ratio) -> Result<(), Error> {
198 let _guard = self.lock_for_configuration()?;
199 let cm_time = objc2_core_media::CMTime {
200 value: duration.numerator as i64,
201 timescale: duration.denominator as i32,
202 flags: objc2_core_media::CMTimeFlags(1),
203 epoch: 0,
204 };
205 catch_objc(AssertUnwindSafe(|| unsafe {
206 self.device.setActiveVideoMaxFrameDuration(cm_time);
207 }))
208 }
209}
210
211pub trait MacosFrameExt {
213 fn pixel_buffer(&self) -> &CVPixelBuffer;
219}
220
221impl MacosFrameExt for MacosFrame<'_> {
222 fn pixel_buffer(&self) -> &CVPixelBuffer {
223 self.pixel_buffer_ref()
224 }
225}