camera_stream/platform/macos/
device.rs1use objc2::rc::Retained;
2use objc2_av_foundation::{AVCaptureDevice, AVCaptureDeviceFormat, AVMediaTypeVideo};
3use objc2_core_media::CMVideoFormatDescriptionGetDimensions;
4
5use crate::device::{CameraDevice, CameraManager};
6use crate::error::{Error, PlatformError};
7use crate::platform::macos::stream::MacosCameraStream;
8use crate::types::*;
9
10#[derive(Default)]
12pub struct MacosCameraManager;
13
14impl CameraManager for MacosCameraManager {
15 type Device = MacosCameraDevice;
16 type Error = Error;
17
18 fn discover_devices(&self) -> Result<impl Iterator<Item = Self::Device>, Self::Error> {
19 let media_type = unsafe { AVMediaTypeVideo }.ok_or_else(|| {
20 Error::Platform(PlatformError::Message("AVMediaTypeVideo not available"))
21 })?;
22
23 #[allow(deprecated)]
24 let devices: Vec<_> = unsafe { AVCaptureDevice::devicesWithMediaType(media_type) }
25 .iter()
26 .map(|d| MacosCameraDevice::new(d.clone()))
27 .collect();
28
29 Ok(devices.into_iter())
30 }
31
32 fn default_device(&self) -> Result<Option<Self::Device>, Self::Error> {
33 let media_type = unsafe { AVMediaTypeVideo }.ok_or_else(|| {
34 Error::Platform(PlatformError::Message("AVMediaTypeVideo not available"))
35 })?;
36
37 let device = unsafe { AVCaptureDevice::defaultDeviceWithMediaType(media_type) };
38 Ok(device.map(MacosCameraDevice::new))
39 }
40}
41
42pub struct MacosCameraDevice {
44 pub(crate) device: Retained<AVCaptureDevice>,
45 id_cache: String,
46 name_cache: String,
47}
48
49impl MacosCameraDevice {
50 pub(crate) fn new(device: Retained<AVCaptureDevice>) -> Self {
51 let id_cache = unsafe { device.uniqueID() }.to_string();
52 let name_cache = unsafe { device.localizedName() }.to_string();
53 MacosCameraDevice {
54 device,
55 id_cache,
56 name_cache,
57 }
58 }
59
60 pub fn av_device(&self) -> &AVCaptureDevice {
62 &self.device
63 }
64}
65
66pub(crate) fn format_to_descriptors(
67 format: &AVCaptureDeviceFormat,
68) -> impl Iterator<Item = FormatDescriptor> + use<> {
69 let desc = unsafe { format.formatDescription() };
70 let media_sub_type = unsafe { desc.media_sub_type() };
71 let pixel_format = fourcc_to_pixel_format(media_sub_type);
72
73 let dims = unsafe { CMVideoFormatDescriptionGetDimensions(&desc) };
74 let size = Size {
75 width: dims.width as u32,
76 height: dims.height as u32,
77 };
78
79 let ranges = unsafe { format.videoSupportedFrameRateRanges() };
80 let frame_rate_ranges: Vec<_> = ranges
81 .iter()
82 .map(|r| {
83 let min_rate = unsafe { r.minFrameRate() };
84 let max_rate = unsafe { r.maxFrameRate() };
85 FrameRateRange {
86 min: f64_to_ratio(min_rate),
87 max: f64_to_ratio(max_rate),
88 }
89 })
90 .collect();
91
92 let descriptors: Vec<_> = pixel_format
93 .into_iter()
94 .flat_map(move |pf| FormatDescriptor::from_ranges(pf, size, frame_rate_ranges.clone()))
95 .collect();
96
97 descriptors.into_iter()
98}
99
100pub(crate) fn fourcc_to_pixel_format(fourcc: u32) -> Option<PixelFormat> {
101 #[allow(clippy::mistyped_literal_suffixes)]
103 match fourcc {
104 0x34_32_30_76 => Some(PixelFormat::Nv12), 0x34_32_30_66 => Some(PixelFormat::Nv12), 0x79_75_76_32 => Some(PixelFormat::Yuyv), 0x32_76_75_79 => Some(PixelFormat::Uyvy), 0x42_47_52_41 => Some(PixelFormat::Bgra32), 0x6A_70_65_67 => Some(PixelFormat::Jpeg), _ => None,
111 }
112}
113
114pub(crate) fn pixel_format_to_fourcc(pf: &PixelFormat) -> u32 {
115 #[allow(clippy::mistyped_literal_suffixes)]
116 match pf {
117 PixelFormat::Nv12 => 0x34_32_30_76, PixelFormat::Yuyv => 0x79_75_76_32, PixelFormat::Uyvy => 0x32_76_75_79, PixelFormat::Bgra32 => 0x42_47_52_41, PixelFormat::Jpeg => 0x6A_70_65_67, }
123}
124
125fn f64_to_ratio(fps: f64) -> Ratio {
126 let denominator = 1000u32;
129 let numerator = (fps * denominator as f64).round() as u32;
130 Ratio {
131 numerator,
132 denominator,
133 }
134}
135
136impl CameraDevice for MacosCameraDevice {
137 type Stream = MacosCameraStream;
138 type Error = Error;
139
140 fn id(&self) -> &str {
141 &self.id_cache
142 }
143
144 fn name(&self) -> &str {
145 &self.name_cache
146 }
147
148 fn supported_formats(&self) -> Result<impl Iterator<Item = FormatDescriptor>, Self::Error> {
149 let formats: Vec<_> = unsafe { self.device.formats() }
150 .iter()
151 .flat_map(|f| format_to_descriptors(&f))
152 .collect();
153 Ok(formats.into_iter())
154 }
155
156 fn open(self, config: &StreamConfig) -> Result<Self::Stream, Self::Error> {
157 MacosCameraStream::new(self.device, config)
158 }
159}