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