diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 97cadb7..1a1445c 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -197,6 +197,7 @@ use objc::{ declare::ClassDecl, runtime::{Class, Object, Protocol, Sel, BOOL, YES}, }; +use once_cell::sync::Lazy; use std::collections::HashSet; use std::{ borrow::Cow, @@ -329,104 +330,111 @@ fn default_callback(_: bool) {} pub type CompressionData<'a> = (Cow<'a, [u8]>, FrameFormat); pub type DataPipe<'a> = (Sender>, Receiver>); -static CALLBACK_CLASS: &Class = { - let mut decl = ClassDecl::new("MyCaptureCallback", class!(NSObject)).unwrap(); +static CALLBACK_CLASS: Lazy<&'static Class> = Lazy::new(|| { + { + let mut decl = ClassDecl::new("MyCaptureCallback", class!(NSObject)).unwrap(); - // frame stack - decl.add_ivar::<*mut c_void>("_fnptr"); // oooh scary provenannce-breaking BULLSHIT AAAAAA I LOVE TYPE ERASURE + // frame stack + decl.add_ivar::<*mut c_void>("_fnptr"); // oooh scary provenannce-breaking BULLSHIT AAAAAA I LOVE TYPE ERASURE - extern "C" fn my_callback_get_fnptr(this: &Object, _: Sel) -> *mut c_void { - unsafe { *this.get_ivar("_fnptr") } - } - extern "C" fn my_callback_set_fnptr(this: &mut Object, _: Sel, new_fnptr: *mut c_void) { - unsafe { - this.set_ivar("_fnptr", new_fnptr); + extern "C" fn my_callback_get_fnptr(this: &Object, _: Sel) -> *mut c_void { + unsafe { *this.get_ivar("_fnptr") } } - } - - // Delegate compliance method - // SAFETY: This assumes that the buffer byte size is a u8. Any other size will cause unsafety. - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - extern "C" fn capture_out_callback( - this: &mut Object, - _: Sel, - _: *mut Object, - didOutputSampleBuffer: CMSampleBufferRef, - _: *mut Object, - ) { - let image_buffer: CVImageBufferRef = - unsafe { CMSampleBufferGetImageBuffer(didOutputSampleBuffer) }; - unsafe { - CVPixelBufferLockBaseAddress(image_buffer, 0); - }; - - let buffer_codec = unsafe { CVPixelBufferGetPixelFormatType(image_buffer) }; - - let fourcc = match buffer_codec { - kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => FrameFormat::YUYV, - kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => FrameFormat::MJPEG, - kCMPixelFormat_8IndexedGray_WhiteIsZero => FrameFormat::GRAY, - _ => { - return; + extern "C" fn my_callback_set_fnptr(this: &mut Object, _: Sel, new_fnptr: *mut c_void) { + unsafe { + this.set_ivar("_fnptr", new_fnptr); } - }; + } - let buffer_length = unsafe { CVPixelBufferGetDataSize(image_buffer) }; - let buffer_ptr = unsafe { CVPixelBufferGetBaseAddress(image_buffer) }; - let buffer_as_vec = unsafe { - std::slice::from_raw_parts_mut(buffer_ptr as *mut u8, buffer_length as usize).to_vec() - }; + // Delegate compliance method + // SAFETY: This assumes that the buffer byte size is a u8. Any other size will cause unsafety. + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + extern "C" fn capture_out_callback( + this: &mut Object, + _: Sel, + _: *mut Object, + didOutputSampleBuffer: CMSampleBufferRef, + _: *mut Object, + ) { + let image_buffer: CVImageBufferRef = + unsafe { CMSampleBufferGetImageBuffer(didOutputSampleBuffer) }; + unsafe { + CVPixelBufferLockBaseAddress(image_buffer, 0); + }; - unsafe { CVPixelBufferUnlockBaseAddress(image_buffer, 0) }; - // oooooh scarey unsafe - let function_cvoid: *mut c_void = unsafe { msg_send![this, fnptr] }; - let boxed_fn = unsafe { - // AAAAAAAAAAAAAAAAAAAAAAAAA - // https://c.tenor.com/0e_zWtFLOzQAAAAC/needy-streamer-overload-needy-girl-overdose.gif - std::mem::transmute_copy::< - *mut c_void, - Box, FrameFormat) + Send + Sync + 'static>, - >(&function_cvoid) - }; - boxed_fn(buffer_as_vec, fourcc); + let buffer_codec = unsafe { CVPixelBufferGetPixelFormatType(image_buffer) }; + + let fourcc = match buffer_codec { + kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => FrameFormat::YUYV, + kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => FrameFormat::MJPEG, + kCMPixelFormat_8IndexedGray_WhiteIsZero => FrameFormat::GRAY, + _ => { + return; + } + }; + + let buffer_length = unsafe { CVPixelBufferGetDataSize(image_buffer) }; + let buffer_ptr = unsafe { CVPixelBufferGetBaseAddress(image_buffer) }; + let buffer_as_vec = unsafe { + std::slice::from_raw_parts_mut(buffer_ptr as *mut u8, buffer_length as usize) + .to_vec() + }; + + unsafe { CVPixelBufferUnlockBaseAddress(image_buffer, 0) }; + // oooooh scarey unsafe + // FIXME: FnMut is not boundary safe. + // Revery back fo `fn()` + let function_cvoid: *mut c_void = unsafe { msg_send![this, fnptr] }; + let mut boxed_fn = unsafe { + // AAAAAAAAAAAAAAAAAAAAAAAAA + // https://c.tenor.com/0e_zWtFLOzQAAAAC/needy-streamer-overload-needy-girl-overdose.gif + std::mem::transmute_copy::< + *mut c_void, + Box, FrameFormat) + Send + Sync + 'static>, + >(&function_cvoid) + }; + boxed_fn(buffer_as_vec, fourcc); + } + + #[allow(non_snake_case)] + extern "C" fn capture_drop_callback( + _: &mut Object, + _: Sel, + _: *mut Object, + _: *mut Object, + _: *mut Object, + ) { + } + + unsafe { + decl.add_method( + sel!(fnptr), + my_callback_get_fnptr as extern "C" fn(&Object, Sel) -> *mut c_void, + ); + decl.add_method( + sel!(setFnPtr:), + my_callback_set_fnptr as extern "C" fn(&mut Object, Sel, *mut c_void), + ); + decl.add_method( + sel!(captureOutput:didOutputSampleBuffer:fromConnection:), + capture_out_callback + as extern "C" fn(&mut Object, Sel, *mut Object, CMSampleBufferRef, *mut Object), + ); + decl.add_method( + sel!(captureOutput:didDropSampleBuffer:fromConnection:), + capture_drop_callback + as extern "C" fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object), + ); + + decl.add_protocol( + Protocol::get("AVCaptureVideoDataOutputSampleBufferDelegate").unwrap(), + ); + } + + decl.register() } - - #[allow(non_snake_case)] - extern "C" fn capture_drop_callback( - _: &mut Object, - _: Sel, - _: *mut Object, - _: *mut Object, - _: *mut Object, - ) { - } - - unsafe { - decl.add_method( - sel!(fnptr), - my_callback_get_fnptr as extern "C" fn(&Object, Sel) -> *mut c_void, - ); - decl.add_method( - sel!(setFnPtr:), - my_callback_set_fnptr as extern "C" fn(&mut Object, Sel, *mut c_void), - ); - decl.add_method( - sel!(captureOutput:didOutputSampleBuffer:fromConnection:), - capture_out_callback - as extern "C" fn(&mut Object, Sel, *mut Object, CMSampleBufferRef, *mut Object), - ); - decl.add_method( - sel!(captureOutput:didDropSampleBuffer:fromConnection:), - capture_drop_callback - as extern "C" fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object), - ); - - decl.add_protocol(Protocol::get("AVCaptureVideoDataOutputSampleBufferDelegate").unwrap()); - } - - decl.register() -}; +}); pub fn request_permission_with_callback(callback: Box) { let cls = class!(AVCaptureDevice); @@ -492,6 +500,22 @@ pub fn query_avfoundation() -> Result, NokhwaError> { Ok(device_set.into_iter().collect()) } +pub fn get_raw_device_info(index: CameraIndex, device: *mut Object) -> CameraInfo { + let name = nsstr_to_str(unsafe { msg_send![device, localizedName] }); + let manufacturer = nsstr_to_str(unsafe { msg_send![device, manufacturer] }); + let position: AVCaptureDevicePosition = unsafe { msg_send![device, position] }; + let lens_aperture: f64 = unsafe { msg_send![device, lensAperture] }; + let device_type = nsstr_to_str(unsafe { msg_send![device, deviceType] }); + let model_id = nsstr_to_str(unsafe { msg_send![device, modelID] }); + let description = format!( + "{}: {} - {}, {:?} f{}", + manufacturer, model_id, device_type, position, lens_aperture + ); + let misc = nsstr_to_str(unsafe { msg_send![device, uniqueID] }); + + CameraInfo::new(name.as_ref(), &description, misc.as_ref(), index) +} + #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] pub enum AVCaptureDeviceType { Dual, @@ -644,7 +668,7 @@ impl AVCaptureVideoCallback { let fnptr_pinned = Box::leak(callback); unsafe { - let _ = msg_send![delegate, setFnPtr: fnptr_pinned]; + let _: () = msg_send![delegate, setFnPtr: fnptr_pinned]; } let queue = @@ -786,26 +810,12 @@ impl AVCaptureDeviceDiscoverySession { pub fn devices(&self) -> Vec { let device_ns_array: *mut Object = unsafe { msg_send![self.inner, devices] }; let objects_len: NSUInteger = unsafe { NSArray::count(device_ns_array) }; - let mut devices = vec![AVCaptureDeviceDescriptor::default(); objects_len as usize]; + let mut devices = Vec::with_capacity(objects_len as usize); for index in 0..objects_len { let device = unsafe { device_ns_array.objectAtIndex(index) }; - let name = nsstr_to_str(unsafe { msg_send![device, localizedName] }); - let manufacturer = nsstr_to_str(unsafe { msg_send![device, manufacturer] }); - let position: AVCaptureDevicePosition = unsafe { msg_send![device, position] }; - let lens_aperture: f64 = unsafe { msg_send![device, lensAperture] }; - let device_type = nsstr_to_str(unsafe { msg_send![device, deviceType] }); - let model_id = nsstr_to_str(unsafe { msg_send![device, modelID] }); - let description = format!( - "{}: {} - {}, {:?} f{}", - manufacturer, model_id, device_type, position, lens_aperture - ); - let misc = nsstr_to_str(unsafe { msg_send![device, uniqueID] }); - - devices.push(CameraInfo::new( - name.into(), - &description, - misc.into(), + devices.push(get_raw_device_info( CameraIndex::Index(index as u32), + device, )); } let _: *mut c_void = unsafe { msg_send![device_ns_array, release] }; @@ -831,8 +841,8 @@ impl AVCaptureDevice { impl AVCaptureDevice { pub fn new(index: CameraIndex) -> Result { - match index { - CameraIndex::Index(index) => { + match &index { + CameraIndex::Index(idx) => { let devices = AVCaptureDeviceDiscoverySession::new( vec![ AVCaptureDeviceType::UltraWide, @@ -844,19 +854,19 @@ impl AVCaptureDevice { )? .devices(); - match devices.get(index as usize) { - Some(device) => Ok(AVCaptureDevice::from_id(&device.misc())?), + match devices.get(*idx as usize) { + Some(device) => Ok(AVCaptureDevice::from_id(&device.misc(), Some(index))?), None => Err(NokhwaError::OpenDeviceError( - index.to_string(), + idx.to_string(), "Not Found".to_string(), )), } } - CameraIndex::String(id) => Ok(AVCaptureDevice::from_id(&id)?), + CameraIndex::String(id) => Ok(AVCaptureDevice::from_id(id, None)?), } } - pub fn from_id(id: &str) -> Result { + pub fn from_id(id: &str, index_hint: Option) -> Result { let nsstr_id = str_to_nsstr(id); let avfoundation_capture_cls = class!(AVCaptureDevice); let capture: *mut Object = @@ -867,7 +877,18 @@ impl AVCaptureDevice { "Device is null".to_string(), )); } - Ok(AVCaptureDevice { inner: capture }) + let camera_info = get_raw_device_info( + index_hint.unwrap_or(CameraIndex::String(id.to_string())), + capture, + ); + Ok(AVCaptureDevice { + inner: capture, + device: camera_info, + }) + } + + pub fn info(&self) -> &CameraInfo { + &self.device } pub fn supported_formats_raw(&self) -> Result, NokhwaError> { @@ -879,19 +900,18 @@ impl AVCaptureDevice { pub fn supported_formats(&self) -> Result, NokhwaError> { Ok(self .supported_formats_raw()? - .into_iter() + .iter() .flat_map(|av_fmt| { - av_fmt.fps_list.into_iter().map(|fps_f64| { + let resolution = av_fmt.resolution; + av_fmt.fps_list.iter().map(move |fps_f64| { let fps = if fps_f64.fract() != 0.0 { 0 } else { - fps_f64 as u32 + *fps_f64 as u32 }; - let resolution = Resolution::new( - av_fmt.resolution.height as u32, - av_fmt.resolution.width as u32, - ); + let resolution = + Resolution::new(resolution.height as u32, resolution.width as u32); CameraFormat::new(resolution, av_fmt.fourcc, fps) }) @@ -946,8 +966,10 @@ impl AVCaptureDevice { } // thank you ffmpeg - pub fn set_all(&mut self, descriptor: CameraFormat) -> Result<(), AVFError> { - let format_list = self.supported_formats()?; + pub fn set_all(&mut self, descriptor: CameraFormat) -> Result<(), NokhwaError> { + let format_list = try_ns_arr_to_vec::(unsafe { + msg_send![self.inner, formats] + })?; let format_description_sel = sel!(formatDescription); let mut selected_format: *mut Object = std::ptr::null_mut(); @@ -958,8 +980,8 @@ impl AVCaptureDevice { unsafe { msg_send![format.internal, performSelector: format_description_sel] }; let dimensions = unsafe { CMVideoFormatDescriptionGetDimensions(format_desc_ref) }; - if dimensions.height == descriptor.resolution().height - && dimensions.width == descriptor.resolution().width + if dimensions.height == descriptor.resolution().height() as i32 + && dimensions.width == descriptor.resolution().width() as i32 { selected_format = format.internal; @@ -969,7 +991,7 @@ impl AVCaptureDevice { let max_fps: f64 = unsafe { msg_send![range.inner, valueForKey:"maxFrameRate"] }; - if (f64::from(descriptor.fps) - max_fps).abs() < 0.01 { + if (f64::from(descriptor.frame_rate()) - max_fps).abs() < 0.01 { selected_range = range.inner; break; } @@ -1010,7 +1032,7 @@ impl Drop for AVCaptureDevice { } impl AVCaptureDeviceInput { - pub fn new(capture_device: &AVCaptureDevice) -> Result { + pub fn new(capture_device: &AVCaptureDevice) -> Result { let cls = class!(AVCaptureDeviceInput); let err_ptr: *mut c_void = std::ptr::null_mut(); let capture_input: *mut Object = unsafe { @@ -1058,7 +1080,7 @@ impl AVCaptureVideoDataOutput { let _: () = msg_send![ self.inner, setSampleBufferDelegate: delegate.delegate - queue: delegate.queue, + queue: avf_queue_str ]; }; Ok(()) diff --git a/nokhwa-core/src/traits.rs b/nokhwa-core/src/traits.rs index 898f704..7a10faf 100644 --- a/nokhwa-core/src/traits.rs +++ b/nokhwa-core/src/traits.rs @@ -229,7 +229,7 @@ pub trait CaptureBackendTrait { origin: wgpu::Origin3d::ZERO, aspect: TextureAspect::All, }, - &frame.to_vec(), + &frame, ImageDataLayout { offset: 0, bytes_per_row: width_nonzero, diff --git a/src/camera.rs b/src/camera.rs index ddd0406..9c60de3 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -85,16 +85,12 @@ impl Camera { /// Sets the current Camera's index. Note that this re-initializes the camera. /// # Errors /// The Backend may fail to initialize. - pub fn set_index(&mut self, new_idx: CameraIndex) -> Result<(), NokhwaError> { + pub fn set_index(&mut self, new_idx: &CameraIndex) -> Result<(), NokhwaError> { { self.device.stop_stream()?; } let new_camera_format = self.device.camera_format(); - let new_camera = init_camera( - &new_idx, - RequestedFormat::Exact(new_camera_format), - self.api, - )?; + let new_camera = init_camera(new_idx, RequestedFormat::Exact(new_camera_format), self.api)?; self.device = new_camera; Ok(()) } diff --git a/src/init.rs b/src/init.rs index 5be7a51..99375a0 100644 --- a/src/init.rs +++ b/src/init.rs @@ -19,7 +19,7 @@ any(target_os = "macos", target_os = "ios") )))] fn init_avfoundation(callback: impl Fn(bool) + Send + 'static) { - callback.call(true); + callback(true); } #[cfg(all( @@ -60,7 +60,7 @@ fn status_avfoundation() -> bool { /// /// The `on_complete` is called after initialization (a.k.a User granted permission). The callback's argument /// is weather the initialization was successful or not -pub fn nokhwa_initialize(on_complete: impl FnMut(bool) + Send + 'static) { +pub fn nokhwa_initialize(on_complete: impl Fn(bool) + Send + 'static) { init_avfoundation(on_complete); } diff --git a/src/lib.rs b/src/lib.rs index 47d1ce7..1042a08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![deny(clippy::pedantic)] #![warn(clippy::all)] +#![allow(clippy::module_name_repetitions)] /* * Copyright 2022 l1npengtul / The Nokhwa Contributors *