From c090601599a312006b2f7f145b39e5f7c35c3d8a Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Tue, 13 Sep 2022 08:41:03 -0700 Subject: [PATCH] in progress avfoundation update --- Cargo.toml | 4 +- nokhwa-bindings-macos/Cargo.toml | 20 +- nokhwa-bindings-macos/build.rs | 1 + nokhwa-bindings-macos/src/lib.rs | 2074 ++++++++++---------------- nokhwa-bindings-windows/Cargo.toml | 2 +- src/backends/capture/avfoundation.rs | 89 +- 6 files changed, 874 insertions(+), 1316 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index db2a0b7..0d9c2c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,12 +84,12 @@ version = "0.8" optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3.4" +version = "0.4" path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] -version = "0.1.1" +version = "0.2" path = "nokhwa-bindings-macos" optional = true diff --git a/nokhwa-bindings-macos/Cargo.toml b/nokhwa-bindings-macos/Cargo.toml index de67d62..9fbb26a 100644 --- a/nokhwa-bindings-macos/Cargo.toml +++ b/nokhwa-bindings-macos/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa-bindings-macos" -version = "0.1.2" +version = "0.2.0" edition = "2018" authors = ["l1npengtul"] license = "Apache-2.0" @@ -10,13 +10,13 @@ keywords = ["avfoundation", "macos", "capture", "webcam"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -thiserror = "1.0.26" -flume = "0.10.9" +flume = "0.10" +core-media-sys = "0.1" +cocoa-foundation = "0.1" +objc = { version = "0.2", features = ["exception"] } +block = "0.1" +once_cell = "1.14" -[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -core-media-sys = "0.1.2" -cocoa-foundation = "0.1.0" -objc = { version = "0.2.7", features = ["exception"] } -block = "0.1.6" -dashmap = "5.3.4" -lazy_static = "1.4.0" \ No newline at end of file +[dependencies.nokhwa-core] +version = "0.1" +path = "../nokhwa-core" diff --git a/nokhwa-bindings-macos/build.rs b/nokhwa-bindings-macos/build.rs index 59f445b..d01268b 100644 --- a/nokhwa-bindings-macos/build.rs +++ b/nokhwa-bindings-macos/build.rs @@ -18,6 +18,7 @@ fn main() { println!("cargo:rustc-link-lib=framework=CoreMedia"); println!("cargo:rustc-link-lib=framework=AVFoundation"); + println!("cargo:rustc-link-lib=framework=CoreVideo"); } #[cfg(not(any(target_os = "macos", target_os = "ios")))] diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 1d2a408..b8a34fb 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -23,37 +23,6 @@ #[cfg(any(target_os = "macos", target_os = "ios"))] #[macro_use] extern crate objc; -#[cfg(any(target_os = "macos", target_os = "ios"))] -#[macro_use] -extern crate lazy_static; - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum AVFError { - #[error("Invalid: Expected {expected} Found {found}")] - InvalidType { expected: String, found: String }, - #[error("Invalid Value: {found}")] - InvalidValue { found: String }, - #[error("Already Busy: {0}")] - AlreadyBusy(String), - #[error("Failed to open device {index}: {why}")] - FailedToOpenDevice { index: usize, why: String }, - #[error("Config Not Accepted")] - ConfigNotAccepted, - #[error("General Error: {0}")] - General(String), - #[error("Cannot add input to session: Rejected")] - RejectedInput, - #[error("Cannot add output to session: Rejected")] - RejectedOutput, - #[error("Failed to open stream: {0}")] - StreamOpen(String), - #[error("Failed to read frame: {0}")] - ReadFrame(String), - #[error("Unsupported")] - NotSupported, -} #[cfg(any(target_os = "macos", target_os = "ios"))] #[allow(non_snake_case)] @@ -200,51 +169,46 @@ pub mod core_media { } } -#[cfg(any(target_os = "macos", target_os = "ios"))] -pub mod avfoundation { - use crate::core_media::{ - AVMediaTypeAudio, AVMediaTypeClosedCaption, AVMediaTypeDepthData, AVMediaTypeMetadata, - AVMediaTypeMetadataObject, AVMediaTypeMuxed, AVMediaTypeSubtitle, AVMediaTypeText, - AVMediaTypeTimecode, CVPixelBufferGetPixelFormatType, - }; - use crate::{ - core_media::{ - dispatch_queue_create, AVMediaTypeVideo, CMSampleBufferGetImageBuffer, - CMVideoFormatDescriptionGetDimensions, CVImageBufferRef, CVPixelBufferGetBaseAddress, - CVPixelBufferGetDataSize, CVPixelBufferLockBaseAddress, CVPixelBufferUnlockBaseAddress, - NSObject, - }, - AVFError, - }; - use block::ConcreteBlock; - use cocoa_foundation::foundation::{NSArray, NSInteger, NSString, NSUInteger}; - use core_media_sys::{ - kCMPixelFormat_422YpCbCr8_yuvs, kCMPixelFormat_8IndexedGray_WhiteIsZero, - kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, kCMVideoCodecType_JPEG_OpenDML, - CMFormatDescriptionGetMediaSubType, CMFormatDescriptionRef, CMSampleBufferRef, - CMVideoDimensions, - }; - use dashmap::DashMap; - use flume::{Receiver, Sender}; - use objc::{ - declare::ClassDecl, - runtime::{Class, Object, Protocol, Sel, BOOL, YES}, - }; - use std::{ - borrow::Cow, - cmp::Ordering, - convert::TryFrom, - error::Error, - ffi::{c_void, CStr, CString}, - sync::{ - atomic::{AtomicBool, Ordering as MemOrdering}, - Arc, Mutex, TryLockError, - }, - }; +use crate::core_media::{ + dispatch_queue_create, AVMediaTypeVideo, CMSampleBufferGetImageBuffer, + CMVideoFormatDescriptionGetDimensions, CVImageBufferRef, CVPixelBufferGetBaseAddress, + CVPixelBufferGetDataSize, CVPixelBufferLockBaseAddress, CVPixelBufferUnlockBaseAddress, + NSObject, +}; +use crate::core_media::{ + AVMediaTypeAudio, AVMediaTypeClosedCaption, AVMediaTypeDepthData, AVMediaTypeMetadata, + AVMediaTypeMetadataObject, AVMediaTypeMuxed, AVMediaTypeSubtitle, AVMediaTypeText, + AVMediaTypeTimecode, CVPixelBufferGetPixelFormatType, +}; +use block::ConcreteBlock; +use cocoa_foundation::foundation::{NSArray, NSInteger, NSString, NSUInteger}; +use core_media_sys::{ + kCMPixelFormat_422YpCbCr8_yuvs, kCMPixelFormat_8IndexedGray_WhiteIsZero, + kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, kCMVideoCodecType_JPEG_OpenDML, + CMFormatDescriptionGetMediaSubType, CMFormatDescriptionRef, CMSampleBufferRef, + CMVideoDimensions, +}; +use flume::{Receiver, Sender}; +use nokhwa_core::types::Resolution; +use nokhwa_core::{ + error::NokhwaError, + types::{ApiBackend, CameraFormat, CameraIndex, CameraInfo, FrameFormat}, +}; +use objc::{ + declare::ClassDecl, + runtime::{Class, Object, Protocol, Sel, BOOL, YES}, +}; +use std::{ + borrow::Cow, + cmp::Ordering, + convert::TryFrom, + error::Error, + ffi::{c_void, CStr, CString}, +}; - const UTF8_ENCODING: usize = 4; +const UTF8_ENCODING: usize = 4; - macro_rules! create_boilerplate_impl { +macro_rules! create_boilerplate_impl { { $( [$class_vis:vis $class_name:ident : $( {$field_vis:vis $field_name:ident : $field_type:ty} ),*] ),+ } => { @@ -289,405 +253,493 @@ pub mod avfoundation { }; } - fn str_to_nsstr(string: &str) -> *mut Object { - let cls = class!(NSString); - let bytes = string.as_ptr() as *const c_void; +fn str_to_nsstr(string: &str) -> *mut Object { + let cls = class!(NSString); + let bytes = string.as_ptr() as *const c_void; + unsafe { + let obj: *mut Object = msg_send![cls, alloc]; + let obj: *mut Object = msg_send![ + obj, + initWithBytes:bytes + length:string.len() + encoding:UTF8_ENCODING + ]; + let _: *mut c_void = msg_send![obj, autorelease]; + obj + } +} + +fn nsstr_to_str<'a>(nsstr: *mut Object) -> Cow<'a, str> { + let data = unsafe { CStr::from_ptr(nsstr.UTF8String()) }; + data.to_string_lossy() +} + +fn vec_to_ns_arr>(data: Vec) -> *mut Object { + let ns_mut_array_cls = class!(NSMutableArray); + let ns_array_cls = class!(NSArray); + let mutable_array: *mut Object = unsafe { msg_send![ns_mut_array_cls, array] }; + data.into_iter().for_each(|item| { + let item_obj: *mut Object = item.into(); + let _: *mut c_void = unsafe { msg_send![mutable_array, addObject: item_obj] }; + }); + let immutable_array: *mut Object = + unsafe { msg_send![ns_array_cls, arrayWithArray: mutable_array] }; + let _: *mut c_void = unsafe { msg_send![mutable_array, autorelease] }; + let _: *mut c_void = unsafe { msg_send![immutable_array, autorelease] }; + immutable_array +} + +fn ns_arr_to_vec>(data: *mut Object) -> Vec { + let length = unsafe { NSArray::count(data) }; + + let mut out_vec: Vec = Vec::with_capacity(length as usize); + for index in 0..length { + let item = unsafe { NSArray::objectAtIndex(data, index) }; + out_vec.push(T::from(item)); + } + let _: *mut c_void = unsafe { msg_send![data, autorelease] }; + out_vec +} + +fn try_ns_arr_to_vec(data: *mut Object) -> Result, TE> +where + TE: Error, + T: TryFrom<*mut Object, Error = TE>, +{ + let length = unsafe { NSArray::count(data) }; + + let mut out_vec: Vec = Vec::with_capacity(length as usize); + for index in 0..length { + let item = unsafe { NSArray::objectAtIndex(data, index) }; + out_vec.push(T::try_from(item)?); + } + let _: *mut c_void = unsafe { msg_send![data, autorelease] }; + Ok(out_vec) +} + +fn compare_ns_string(this: *mut Object, other: core_media::NSString) -> bool { + unsafe { + let equal: BOOL = msg_send![this, isEqualToString: other]; + equal == YES + } +} + +fn default_callback(_: bool) {} + +pub type CompressionData<'a> = (Cow<'a, [u8]>, FrameFormat); +pub type DataPipe<'a> = (Sender>, Receiver>); + +static CALLBACK_CLASS: &'static Class = { + let mut decl = ClassDecl::new("MyCaptureCallback", class!(NSObject)).unwrap(); + + // frame stack + decl.add_ivar::("_index"); + + extern "C" fn my_callback_get_index(this: &Object, _: Sel) -> usize { + unsafe { *this.get_ivar("_index") } + } + + extern "C" fn my_callback_set_index(this: &mut Object, _: Sel, new_index: usize) { unsafe { - let obj: *mut Object = msg_send![cls, alloc]; - let obj: *mut Object = msg_send![ - obj, - initWithBytes:bytes - length:string.len() - encoding:UTF8_ENCODING - ]; - let _: *mut c_void = msg_send![obj, autorelease]; - obj + this.set_ivar("_index", new_index); } } - fn nsstr_to_str<'a>(nsstr: *mut Object) -> Cow<'a, str> { - let data = unsafe { CStr::from_ptr(nsstr.UTF8String()) }; - data.to_string_lossy() - } - - fn vec_to_ns_arr>(data: Vec) -> *mut Object { - let ns_mut_array_cls = class!(NSMutableArray); - let ns_array_cls = class!(NSArray); - let mutable_array: *mut Object = unsafe { msg_send![ns_mut_array_cls, array] }; - data.into_iter().for_each(|item| { - let item_obj: *mut Object = item.into(); - let _: *mut c_void = unsafe { msg_send![mutable_array, addObject: item_obj] }; - }); - let immutable_array: *mut Object = - unsafe { msg_send![ns_array_cls, arrayWithArray: mutable_array] }; - let _: *mut c_void = unsafe { msg_send![mutable_array, autorelease] }; - let _: *mut c_void = unsafe { msg_send![immutable_array, autorelease] }; - immutable_array - } - - fn ns_arr_to_vec>(data: *mut Object) -> Vec { - let length = unsafe { NSArray::count(data) }; - - let mut out_vec: Vec = Vec::with_capacity(length as usize); - for index in 0..length { - let item = unsafe { NSArray::objectAtIndex(data, index) }; - out_vec.push(T::from(item)); - } - let _: *mut c_void = unsafe { msg_send![data, autorelease] }; - out_vec - } - - fn try_ns_arr_to_vec(data: *mut Object) -> Result, TE> - where - TE: Error, - T: TryFrom<*mut Object, Error = TE>, - { - let length = unsafe { NSArray::count(data) }; - - let mut out_vec: Vec = Vec::with_capacity(length as usize); - for index in 0..length { - let item = unsafe { NSArray::objectAtIndex(data, index) }; - out_vec.push(T::try_from(item)?); - } - let _: *mut c_void = unsafe { msg_send![data, autorelease] }; - Ok(out_vec) - } - - fn compare_ns_string(this: *mut Object, other: crate::core_media::NSString) -> bool { + // 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 { - let equal: BOOL = msg_send![this, isEqualToString: other]; - equal == YES - } - } - - fn default_callback(_: bool) {} - - pub type CompressionData<'a> = (Cow<'a, [u8]>, AVFourCC); - pub type DataPipe<'a> = (Sender>, Receiver>); - - lazy_static! { - static ref CAMERA_AUTHORIZED: Arc = Arc::new(AtomicBool::new(false)); - static ref USER_CALLBACK_FN: Arc> = Arc::new(Mutex::new(default_callback)); - static ref PIPE_MAP: Arc>> = Arc::new(DashMap::new()); - static ref CALLBACK_CLASS: &'static Class = { - let mut decl = ClassDecl::new("MyCaptureCallback", class!(NSObject)).unwrap(); - - // frame stack - decl.add_ivar::("_index"); - - extern "C" fn my_callback_get_index(this: &Object, _: Sel) -> usize { - unsafe { - *this.get_ivar("_index") - } - } - - extern "C" fn my_callback_set_index(this: &mut Object, _: Sel, new_index: usize) { - unsafe { - this.set_ivar("_index", new_index); - } - } - - // 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 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 => AVFourCC::YUV2, - kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => AVFourCC::MJPEG, - _ => { - 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) }; - let index: usize = unsafe { msg_send![this, index] }; - let pipes = &PIPE_MAP.get(&index); - if let Some(pipe) = pipes { - let _ = pipe.value().0.send((Cow::from(buffer_as_vec), fourcc)); - } - } - - #[allow(non_snake_case)] - extern fn capture_drop_callback(_: &mut Object, _: Sel, _: *mut Object, _: *mut Object, _: *mut Object) { - } - - unsafe { - decl.add_method( - sel!(index), my_callback_get_index as extern "C" fn(&Object, Sel) -> usize - ); - decl.add_method( - sel!(setIndex:), my_callback_set_index as extern "C" fn(&mut Object, Sel, usize) - ); - 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() - }; - } - - fn objc_authorization_event_callback_fn(result: BOOL) { - let result = if result == YES { - CAMERA_AUTHORIZED.store(true, MemOrdering::SeqCst); - true - } else { - CAMERA_AUTHORIZED.store(false, MemOrdering::SeqCst); - false + CVPixelBufferLockBaseAddress(image_buffer, 0); }; - loop { - match USER_CALLBACK_FN.try_lock() { - Ok(callback) => { - callback(result); - break; - } - Err(why) => match why { - TryLockError::Poisoned(_) => { - break; - } - TryLockError::WouldBlock => { - continue; - } - }, + let buffer_codec = unsafe { CVPixelBufferGetPixelFormatType(image_buffer) }; + + let fourcc = match buffer_codec { + kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => AVFourCC::YUV2, + kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => AVFourCC::MJPEG, + _ => { + return; } - } - } - - pub fn request_permission_with_callback(callback: fn(bool)) { - let cls = class!(AVCaptureDevice); - loop { - match USER_CALLBACK_FN.try_lock() { - Ok(mut cb) => { - *cb = callback; - break; - } - Err(why) => match why { - TryLockError::Poisoned(_) => { - break; - } - TryLockError::WouldBlock => { - continue; - } - }, - } - } - - let objc_fn_block: ConcreteBlock<(BOOL,), (), fn(BOOL)> = - ConcreteBlock::new(objc_authorization_event_callback_fn); - let objc_fn_pass = objc_fn_block.copy(); - - unsafe { - let _: () = msg_send![cls, requestAccessForMediaType:(AVMediaTypeVideo.clone()) completionHandler:objc_fn_pass]; - } - } - - pub fn current_authorization_status() -> AVAuthorizationStatus { - let cls = class!(AVCaptureDevice); - let status: AVAuthorizationStatus = unsafe { - msg_send![cls, authorizationStatusForMediaType:AVMediaType::Video.into_ns_str()] }; - match status { - AVAuthorizationStatus::Authorized => CAMERA_AUTHORIZED.store(true, MemOrdering::SeqCst), - _ => CAMERA_AUTHORIZED.store(false, MemOrdering::SeqCst), + + 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) }; + let index: usize = unsafe { msg_send![this, index] }; + let pipes = &PIPE_MAP.get(&index); + if let Some(pipe) = pipes { + let _ = pipe.value().0.send((Cow::from(buffer_as_vec), fourcc)); } - status } - // fuck it, use deprecated APIs - pub fn query_avfoundation() -> Result, AVFError> { - Ok(AVCaptureDevice::devices_with_type(AVMediaType::Video) - .into_iter() - .enumerate() - .map(|(idx, dev)| AVCaptureDeviceDescriptor::from_capture_device(dev, idx)) - .collect::>()) + #[allow(non_snake_case)] + extern "C" fn capture_drop_callback( + _: &mut Object, + _: Sel, + _: *mut Object, + _: *mut Object, + _: *mut Object, + ) { } - pub enum AVFCameraControls { - Focus, - Exposure, - WhiteBalance, - Lighting, - Color, - Zoom, + unsafe { + decl.add_method( + sel!(index), + my_callback_get_index as extern "C" fn(&Object, Sel) -> usize, + ); + decl.add_method( + sel!(setIndex:), + my_callback_set_index as extern "C" fn(&mut Object, Sel, usize), + ); + 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()); } - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - pub enum AVCaptureDeviceType { - Dual, - DualWide, - Triple, - WideAngle, - UltraWide, - Telephoto, - TrueDepth, - ExternalUnknown, - } + decl.register() +}; - impl From for *mut Object { - fn from(device_type: AVCaptureDeviceType) -> Self { - match device_type { - AVCaptureDeviceType::Dual => str_to_nsstr("AVCaptureDeviceTypeBuiltInDualCamera"), - AVCaptureDeviceType::DualWide => { - str_to_nsstr("AVCaptureDeviceTypeBuiltInDualWideCamera") - } - AVCaptureDeviceType::Triple => { - str_to_nsstr("AVCaptureDeviceTypeBuiltInTripleCamera") - } - AVCaptureDeviceType::WideAngle => { - str_to_nsstr("AVCaptureDeviceTypeBuiltInWideAngleCamera") - } - AVCaptureDeviceType::UltraWide => { - str_to_nsstr("AVCaptureDeviceTypeBuiltInUltraWideCamera") - } - AVCaptureDeviceType::Telephoto => { - str_to_nsstr("AVCaptureDeviceTypeBuiltInTelephotoCamera") - } - AVCaptureDeviceType::TrueDepth => { - str_to_nsstr("AVCaptureDeviceTypeBuiltInTrueDepthCamera") - } - AVCaptureDeviceType::ExternalUnknown => { - str_to_nsstr("AVCaptureDeviceTypeBuiltInExternalUnknown") - } +pub fn request_permission_with_callback(callback: Box) { + let cls = class!(AVCaptureDevice); + let objc_fn_block = ConcreteBlock::new(callback); + let objc_fn_pass = objc_fn_block.copy(); + + unsafe { + let _: () = msg_send![cls, requestAccessForMediaType:(AVMediaTypeVideo.clone()) completionHandler:objc_fn_pass]; + } +} + +pub fn current_authorization_status() -> AVAuthorizationStatus { + let cls = class!(AVCaptureDevice); + let status: AVAuthorizationStatus = + unsafe { msg_send![cls, authorizationStatusForMediaType:AVMediaType::Video.into_ns_str()] }; + status +} + +// fuck it, use deprecated APIs +pub fn query_avfoundation() -> Result, AVFError> { + Ok(AVCaptureDevice::devices_with_type(AVMediaType::Video) + .into_iter() + .enumerate() + .map(|(idx, dev)| AVCaptureDeviceDescriptor::from_capture_device(dev, idx)) + .collect::>()) +} + +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub enum AVCaptureDeviceType { + Dual, + DualWide, + Triple, + WideAngle, + UltraWide, + Telephoto, + TrueDepth, + ExternalUnknown, +} + +impl From for *mut Object { + fn from(device_type: AVCaptureDeviceType) -> Self { + match device_type { + AVCaptureDeviceType::Dual => str_to_nsstr("AVCaptureDeviceTypeBuiltInDualCamera"), + AVCaptureDeviceType::DualWide => { + str_to_nsstr("AVCaptureDeviceTypeBuiltInDualWideCamera") + } + AVCaptureDeviceType::Triple => str_to_nsstr("AVCaptureDeviceTypeBuiltInTripleCamera"), + AVCaptureDeviceType::WideAngle => { + str_to_nsstr("AVCaptureDeviceTypeBuiltInWideAngleCamera") + } + AVCaptureDeviceType::UltraWide => { + str_to_nsstr("AVCaptureDeviceTypeBuiltInUltraWideCamera") + } + AVCaptureDeviceType::Telephoto => { + str_to_nsstr("AVCaptureDeviceTypeBuiltInTelephotoCamera") + } + AVCaptureDeviceType::TrueDepth => { + str_to_nsstr("AVCaptureDeviceTypeBuiltInTrueDepthCamera") + } + AVCaptureDeviceType::ExternalUnknown => { + str_to_nsstr("AVCaptureDeviceTypeBuiltInExternalUnknown") } } } +} - impl AVCaptureDeviceType { - pub fn into_ns_str(self) -> *mut Object { - <*mut Object>::from(self) +impl AVCaptureDeviceType { + pub fn into_ns_str(self) -> *mut Object { + <*mut Object>::from(self) + } +} + +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub enum AVMediaType { + Audio, + ClosedCaption, + DepthData, + Metadata, + MetadataObject, + Muxed, + Subtitle, + Text, + Timecode, + Video, +} + +impl From for *mut Object { + fn from(media_type: AVMediaType) -> Self { + match media_type { + AVMediaType::Audio => unsafe { AVMediaTypeAudio.0 }, + AVMediaType::ClosedCaption => unsafe { AVMediaTypeClosedCaption.0 }, + AVMediaType::DepthData => unsafe { AVMediaTypeDepthData.0 }, + AVMediaType::Metadata => unsafe { AVMediaTypeMetadata.0 }, + AVMediaType::MetadataObject => unsafe { AVMediaTypeMetadataObject.0 }, + AVMediaType::Muxed => unsafe { AVMediaTypeMuxed.0 }, + AVMediaType::Subtitle => unsafe { AVMediaTypeSubtitle.0 }, + AVMediaType::Text => unsafe { AVMediaTypeText.0 }, + AVMediaType::Timecode => unsafe { AVMediaTypeTimecode.0 }, + AVMediaType::Video => unsafe { AVMediaTypeVideo.0 }, } } +} - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - pub enum AVMediaType { - Audio, - ClosedCaption, - DepthData, - Metadata, - MetadataObject, - Muxed, - Subtitle, - Text, - Timecode, - Video, - } +impl TryFrom<*mut Object> for AVMediaType { + type Error = NokhwaError; - impl From for *mut Object { - fn from(media_type: AVMediaType) -> Self { - match media_type { - AVMediaType::Audio => unsafe { AVMediaTypeAudio.0 }, - AVMediaType::ClosedCaption => unsafe { AVMediaTypeClosedCaption.0 }, - AVMediaType::DepthData => unsafe { AVMediaTypeDepthData.0 }, - AVMediaType::Metadata => unsafe { AVMediaTypeMetadata.0 }, - AVMediaType::MetadataObject => unsafe { AVMediaTypeMetadataObject.0 }, - AVMediaType::Muxed => unsafe { AVMediaTypeMuxed.0 }, - AVMediaType::Subtitle => unsafe { AVMediaTypeSubtitle.0 }, - AVMediaType::Text => unsafe { AVMediaTypeText.0 }, - AVMediaType::Timecode => unsafe { AVMediaTypeTimecode.0 }, - AVMediaType::Video => unsafe { AVMediaTypeVideo.0 }, + fn try_from(value: *mut Object) -> Result { + unsafe { + if compare_ns_string(value, (AVMediaTypeAudio).clone()) { + Ok(AVMediaType::Audio) + } else if compare_ns_string(value, (AVMediaTypeClosedCaption).clone()) { + Ok(AVMediaType::ClosedCaption) + } else if compare_ns_string(value, (AVMediaTypeDepthData).clone()) { + Ok(AVMediaType::DepthData) + } else if compare_ns_string(value, (AVMediaTypeMetadata).clone()) { + Ok(AVMediaType::Metadata) + } else if compare_ns_string(value, (AVMediaTypeMetadataObject).clone()) { + Ok(AVMediaType::MetadataObject) + } else if compare_ns_string(value, (AVMediaTypeMuxed).clone()) { + Ok(AVMediaType::Muxed) + } else if compare_ns_string(value, (AVMediaTypeSubtitle).clone()) { + Ok(AVMediaType::Subtitle) + } else if compare_ns_string(value, (AVMediaTypeText).clone()) { + Ok(AVMediaType::Text) + } else if compare_ns_string(value, (AVMediaTypeTimecode).clone()) { + Ok(AVMediaType::Timecode) + } else if compare_ns_string(value, (AVMediaTypeVideo).clone()) { + Ok(AVMediaType::Video) + } else { + let name = nsstr_to_str(value); + Err(NokhwaError::GetPropertyError { + property: "AVMediaType".to_string(), + error: format!("Invalid AVMediaType {name}"), + }) } } } +} - impl TryFrom<*mut Object> for AVMediaType { - type Error = AVFError; +impl AVMediaType { + pub fn into_ns_str(self) -> *mut Object { + <*mut Object>::from(self) + } +} - fn try_from(value: *mut Object) -> Result { - unsafe { - if compare_ns_string(value, (AVMediaTypeAudio).clone()) { - Ok(AVMediaType::Audio) - } else if compare_ns_string(value, (AVMediaTypeClosedCaption).clone()) { - Ok(AVMediaType::ClosedCaption) - } else if compare_ns_string(value, (AVMediaTypeDepthData).clone()) { - Ok(AVMediaType::DepthData) - } else if compare_ns_string(value, (AVMediaTypeMetadata).clone()) { - Ok(AVMediaType::Metadata) - } else if compare_ns_string(value, (AVMediaTypeMetadataObject).clone()) { - Ok(AVMediaType::MetadataObject) - } else if compare_ns_string(value, (AVMediaTypeMuxed).clone()) { - Ok(AVMediaType::Muxed) - } else if compare_ns_string(value, (AVMediaTypeSubtitle).clone()) { - Ok(AVMediaType::Subtitle) - } else if compare_ns_string(value, (AVMediaTypeText).clone()) { - Ok(AVMediaType::Text) - } else if compare_ns_string(value, (AVMediaTypeTimecode).clone()) { - Ok(AVMediaType::Timecode) - } else if compare_ns_string(value, (AVMediaTypeVideo).clone()) { - Ok(AVMediaType::Video) - } else { - let name = nsstr_to_str(value); - Err(AVFError::InvalidValue { - found: name.to_string(), - }) - } +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[repr(isize)] +pub enum AVCaptureDevicePosition { + Unspecified = 0, + Back = 1, + Front = 2, +} + +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[repr(isize)] +pub enum AVAuthorizationStatus { + NotDetermined = 0, + Restricted = 1, + Denied = 2, + Authorized = 3, +} + +pub struct AVCaptureVideoCallback { + index: usize, + delegate: *mut Object, +} + +impl AVCaptureVideoCallback { + pub fn new(callback: Box) -> Self { + let cls = &CALLBACK_CLASS as &Class; + let delegate: *mut Object = unsafe { msg_send![cls, alloc] }; + let delegate: *mut Object = unsafe { msg_send![delegate, init] }; + + let data_pipe: DataPipe = flume::unbounded(); + let _ = &PIPE_MAP.insert(index, data_pipe); + + AVCaptureVideoCallback { index, delegate } + } + + pub fn index(&self) -> usize { + self.index + } + + pub fn data_len(&self) -> usize { + unsafe { msg_send![self.delegate, dataLength] } + } + + pub fn inner(&self) -> *mut Object { + self.delegate + } +} + +impl Drop for AVCaptureVideoCallback { + fn drop(&mut self) { + unsafe { + let _: () = msg_send![self.delegate, autorelease]; + } + } +} + +create_boilerplate_impl! { + [pub AVFrameRateRange], + [pub AVCaptureDeviceDiscoverySession], + [pub AVCaptureDevice], + [pub AVCaptureDeviceInput], + [pub AVCaptureSession] +} + +impl AVFrameRateRange { + pub fn max(&self) -> f64 { + unsafe { msg_send![self.inner, maxFrameRate] } + } + + pub fn min(&self) -> f64 { + unsafe { msg_send![self.inner, minFrameRate] } + } +} + +#[derive(Debug)] +pub struct AVCaptureDeviceFormat { + pub(crate) internal: *mut Object, + pub resolution: CMVideoDimensions, + pub fps_list: Vec, + pub fourcc: FrameFormat, +} + +impl TryFrom<*mut Object> for AVCaptureDeviceFormat { + type Error = NokhwaError; + + fn try_from(value: *mut Object) -> Result { + let media_type_raw: *mut Object = unsafe { msg_send![value, mediaType] }; + let media_type = AVMediaType::try_from(media_type_raw)?; + if media_type != AVMediaType::Video { + return Err(NokhwaError::StructureError { + structure: "AVMediaType".to_string(), + error: "Not Video".to_string(), + }); + } + let mut fps_list = ns_arr_to_vec::(unsafe { + msg_send![value, videoSupportedFrameRateRanges] + }) + .into_iter() + .flat_map(|v| [v.min(), v.max()]) + .collect::>(); + fps_list.sort_by(|n, m| n.partial_cmp(m).unwrap_or(Ordering::Equal)); + fps_list.dedup(); + let description_obj: *mut Object = unsafe { msg_send![value, formatDescription] }; + let resolution = + unsafe { CMVideoFormatDescriptionGetDimensions(description_obj as *mut c_void) }; + let fcc_raw = unsafe { CMFormatDescriptionGetMediaSubType(description_obj as *mut c_void) }; + #[allow(non_upper_case_globals)] + let fourcc = match fcc_raw { + kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => FrameFormat::YUYV, + kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => FrameFormat::MJPEG, + kCMPixelFormat_8IndexedGray_WhiteIsZero => FrameFormat::GRAY, + fcc => { + return Err(NokhwaError::StructureError { + structure: "FourCharCode".to_string(), + error: format!("Unknown FourCharCode {fcc:?}"), + }) } - } + }; + let _: *mut c_void = unsafe { msg_send![description_obj, autorelease] }; + Ok(AVCaptureDeviceFormat { + internal: value, + resolution, + fps_list, + fourcc, + }) + } +} + +impl Drop for AVCaptureDeviceFormat { + fn drop(&mut self) { + unsafe { msg_send![self.internal, autorelease] } + } +} + +impl AVCaptureDeviceDiscoverySession { + pub fn new( + device_types: Vec, + media_type: AVMediaType, + position: AVCaptureDevicePosition, + ) -> Result { + let device_types = vec_to_ns_arr(device_types); + let media_type: *mut Object = media_type.into(); + let position = position as NSInteger; + + let discovery_session_cls = class!(AVCaptureDeviceDiscoverySession); + let discovery_session: *mut Object = unsafe { + msg_send![discovery_session_cls, discoverySessionWithDeviceTypes:device_types mediaType:media_type position:position] + }; + Ok(AVCaptureDeviceDiscoverySession { + inner: discovery_session, + }) } - impl AVMediaType { - pub fn into_ns_str(self) -> *mut Object { - <*mut Object>::from(self) - } + pub fn default() -> Result { + AVCaptureDeviceDiscoverySession::new( + vec![ + AVCaptureDeviceType::UltraWide, + AVCaptureDeviceType::Telephoto, + AVCaptureDeviceType::ExternalUnknown, + AVCaptureDeviceType::Dual, + AVCaptureDeviceType::DualWide, + AVCaptureDeviceType::Triple, + ], + AVMediaType::Video, + AVCaptureDevicePosition::Unspecified, + ) } - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - #[repr(isize)] - pub enum AVCaptureDevicePosition { - Unspecified = 0, - Back = 1, - Front = 2, - } - - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - #[repr(isize)] - pub enum AVAuthorizationStatus { - NotDetermined = 0, - Restricted = 1, - Denied = 2, - Authorized = 3, - } - - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - #[repr(u32)] - pub enum AVFourCC { - YUV2, - MJPEG, - GRAY8, - } - - // Localized Name - // - #[derive(Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)] - pub struct AVCaptureDeviceDescriptor { - pub name: String, - pub description: String, - pub misc: String, - pub index: u64, - } - - impl AVCaptureDeviceDescriptor { - pub fn from_capture_device( - device: AVCaptureDevice, - index: usize, - ) -> AVCaptureDeviceDescriptor { - let device = device.inner(); - let name = nsstr_to_str(unsafe { msg_send![device, localizedName] }).to_string(); + 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]; + 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] }; @@ -697,859 +749,381 @@ pub mod avfoundation { "{}: {} - {}, {:?} f{}", manufacturer, model_id, device_type, position, lens_aperture ); - let misc = nsstr_to_str(unsafe { msg_send![device, uniqueID] }).to_string(); - AVCaptureDeviceDescriptor { - name, - description, - misc, - index: index as u64, - } + let misc = nsstr_to_str(unsafe { msg_send![device, uniqueID] }); + + devices.push(CameraInfo::new( + name.into(), + &description, + misc.into(), + CameraIndex::Index(index as u32), + )); } + let _: *mut c_void = unsafe { msg_send![device_ns_array, release] }; + devices + } +} + +impl Drop for AVCaptureDeviceDiscoverySession { + fn drop(&mut self) { + unsafe { msg_send![self.inner, autorelease] } + } +} + +impl AVCaptureDevice { + pub fn devices_with_type(video_type: AVMediaType) -> Vec { + let cls = class!(AVCaptureDevice); + let devices: *mut Object = unsafe { msg_send![cls, devicesWithMediaType: video_type] }; + ns_arr_to_vec(devices) } - pub struct AVCaptureVideoCallback { - index: usize, - delegate: *mut Object, - } + pub fn new(index: CameraIndex) -> Result { + match index { + CameraIndex::Index(index) => { + let devices = AVCaptureDeviceDiscoverySession::new( + vec![ + AVCaptureDeviceType::UltraWide, + AVCaptureDeviceType::Telephoto, + AVCaptureDeviceType::ExternalUnknown, + ], + AVMediaType::Video, + AVCaptureDevicePosition::Unspecified, + )? + .devices(); - impl AVCaptureVideoCallback { - pub fn new(index: usize) -> Self { - let cls = &CALLBACK_CLASS as &Class; - let delegate: *mut Object = unsafe { msg_send![cls, alloc] }; - let delegate: *mut Object = unsafe { msg_send![delegate, init] }; - - let data_pipe: DataPipe = flume::unbounded(); - let _ = &PIPE_MAP.insert(index, data_pipe); - - AVCaptureVideoCallback { index, delegate } - } - - pub fn index(&self) -> usize { - self.index - } - - pub fn data_len(&self) -> usize { - unsafe { msg_send![self.delegate, dataLength] } - } - - pub fn frame_to_slice<'a>(&self) -> Result, AVFError> { - let pipe_map = &PIPE_MAP.get(&self.index); // why rust - let pipe_recv = match pipe_map { - Some(pipe) => &pipe.value().1, - None => return Err(AVFError::ReadFrame("Data Pipe None".to_string())), - }; - let data = match pipe_recv.drain().last() { - Some(frame) => frame, - None => match pipe_recv.recv() { - Ok(f) => f, - Err(why) => { - return Err(AVFError::ReadFrame(format!( - "Failed to read frame from pipe: {}", - why - ))) - } - }, - }; - Ok(data) - } - - pub fn frame_to_slice_no_block<'a>(&self) -> Result, AVFError> { - let pipe_map = &PIPE_MAP.get(&self.index); // why rust - let pipe_recv = match pipe_map { - Some(pipe) => &pipe.value().1, - None => return Err(AVFError::ReadFrame("Data Pipe None".to_string())), - }; - let data = match pipe_recv.drain().last() { - Some(frame) => frame, - None => { - return Err(AVFError::ReadFrame( - "Failed to read frame from pipe: None".to_string(), - )) - } - }; - Ok(data) - } - - pub fn inner(&self) -> *mut Object { - self.delegate - } - } - - impl Drop for AVCaptureVideoCallback { - fn drop(&mut self) { - unsafe { - let _: () = msg_send![self.delegate, autorelease]; - } - } - } - - create_boilerplate_impl! { - [pub AVFrameRateRange], - [pub AVCaptureDeviceDiscoverySession], - [pub AVCaptureDevice], - [pub AVCaptureDeviceInput], - [pub AVCaptureSession] - } - - impl AVFrameRateRange { - pub fn max(&self) -> f64 { - unsafe { msg_send![self.inner, maxFrameRate] } - } - - pub fn min(&self) -> f64 { - unsafe { msg_send![self.inner, minFrameRate] } - } - } - - pub type AVVideoResolution = CMVideoDimensions; - - #[derive(Copy, Clone, Debug)] - pub struct CaptureDeviceFormatDescriptor { - pub resolution: AVVideoResolution, - pub fps: u32, - pub fourcc: AVFourCC, - } - - impl CaptureDeviceFormatDescriptor { - pub fn compatible_with_capture_format(&self, other: &AVCaptureDeviceFormat) -> bool { - for fps in &other.fps_list { - if self.resolution.height == other.resolution.height - && self.resolution.width == other.resolution.width - && self.fourcc == other.fourcc - && (*fps as u32) == self.fps - { - return true; + match devices.get(index as usize) { + Some(device) => Ok(AVCaptureDevice::from_id(&device.misc())?), + None => Err(NokhwaError::OpenDeviceError( + index.to_string(), + "Not Found".to_string(), + )), } } - false + CameraIndex::String(id) => Ok(AVCaptureDevice::from_id(&id)?), } } - #[derive(Debug)] - pub struct AVCaptureDeviceFormat { - pub(crate) internal: *mut Object, - pub resolution: CMVideoDimensions, - pub fps_list: Vec, - pub fourcc: AVFourCC, + pub fn from_id(id: &str) -> Result { + let nsstr_id = str_to_nsstr(id); + let avfoundation_capture_cls = class!(AVCaptureDevice); + let capture: *mut Object = + unsafe { msg_send![avfoundation_capture_cls, deviceWithUniqueID: nsstr_id] }; + if capture.is_null() { + return Err(NokhwaError::OpenDeviceError( + id.to_string(), + "Device is null".to_string(), + )); + } + Ok(AVCaptureDevice { inner: capture }) } - impl TryFrom<*mut Object> for AVCaptureDeviceFormat { - type Error = AVFError; + pub fn supported_formats_raw(&self) -> Result, NokhwaError> { + try_ns_arr_to_vec::(unsafe { + msg_send![self.inner, formats] + }) + } - fn try_from(value: *mut Object) -> Result { - let media_type_raw: *mut Object = unsafe { msg_send![value, mediaType] }; - let media_type = AVMediaType::try_from(media_type_raw)?; - if media_type != AVMediaType::Video { - return Err(AVFError::InvalidType { - expected: "AVMediaTypeVideo".to_string(), - found: format!("{:?}", media_type), - }); - } - let mut fps_list = ns_arr_to_vec::(unsafe { - msg_send![value, videoSupportedFrameRateRanges] - }) + pub fn supported_formats(&self) -> Result, NokhwaError> { + Ok(self + .supported_formats_raw()? .into_iter() - .flat_map(|v| [v.min(), v.max()]) - .collect::>(); - fps_list.sort_by(|n, m| n.partial_cmp(m).unwrap_or(Ordering::Equal)); - fps_list.dedup(); - let description_obj: *mut Object = unsafe { msg_send![value, formatDescription] }; - let resolution = - unsafe { CMVideoFormatDescriptionGetDimensions(description_obj as *mut c_void) }; - let fcc_raw = - unsafe { CMFormatDescriptionGetMediaSubType(description_obj as *mut c_void) }; - #[allow(non_upper_case_globals)] - let fourcc = match fcc_raw { - kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => AVFourCC::YUV2, - kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => AVFourCC::MJPEG, - kCMPixelFormat_8IndexedGray_WhiteIsZero => AVFourCC::GRAY8, - _ => { - return Err(AVFError::InvalidValue { - found: fcc_raw.to_string(), - }) - } - }; - let _: *mut c_void = unsafe { msg_send![description_obj, autorelease] }; - Ok(AVCaptureDeviceFormat { - internal: value, - resolution, - fps_list, - fourcc, - }) - } - } + .flat_map(|av_fmt| { + av_fmt.fps_list.into_iter().map(|fps_f64| { + let fps = if fps_f64.fract() != 0.0 { + 0 + } else { + fps_f64 as u32 + }; - impl Drop for AVCaptureDeviceFormat { - fn drop(&mut self) { - unsafe { msg_send![self.internal, autorelease] } - } - } + let resolution = Resolution::new( + av_fmt.resolution.height as u32, + av_fmt.resolution.width as u32, + ); - impl AVCaptureDeviceDiscoverySession { - pub fn new( - device_types: Vec, - media_type: AVMediaType, - position: AVCaptureDevicePosition, - ) -> Result { - let device_types = vec_to_ns_arr(device_types); - let media_type: *mut Object = media_type.into(); - let position = position as NSInteger; - - let discovery_session_cls = class!(AVCaptureDeviceDiscoverySession); - let discovery_session: *mut Object = unsafe { - msg_send![discovery_session_cls, discoverySessionWithDeviceTypes:device_types mediaType:media_type position:position] - }; - Ok(AVCaptureDeviceDiscoverySession { - inner: discovery_session, - }) - } - - pub fn default() -> Result { - AVCaptureDeviceDiscoverySession::new( - vec![ - AVCaptureDeviceType::UltraWide, - AVCaptureDeviceType::Telephoto, - AVCaptureDeviceType::ExternalUnknown, - AVCaptureDeviceType::Dual, - AVCaptureDeviceType::DualWide, - AVCaptureDeviceType::Triple, - ], - AVMediaType::Video, - AVCaptureDevicePosition::Unspecified, - ) - } - - 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]; - for index in 0..objects_len { - let device = unsafe { device_ns_array.objectAtIndex(index) }; - let name = nsstr_to_str(unsafe { msg_send![device, localizedName] }).to_string(); - 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] }).to_string(); - - devices.push(AVCaptureDeviceDescriptor { - name, - description, - misc, - index, + CameraFormat::new(resolution, av_fmt.fourcc, fps) }) - } - let _: *mut c_void = unsafe { msg_send![device_ns_array, release] }; - devices - } - } - - impl Drop for AVCaptureDeviceDiscoverySession { - fn drop(&mut self) { - unsafe { msg_send![self.inner, autorelease] } - } - } - - impl AVCaptureDevice { - pub fn devices_with_type(_: AVMediaType) -> Vec { - let cls = class!(AVCaptureDevice); - let video_type = unsafe { AVMediaTypeVideo.clone() }; - let devices: *mut Object = unsafe { msg_send![cls, devicesWithMediaType: video_type] }; - ns_arr_to_vec(devices) - } - - pub fn new(index: usize) -> Result { - let devices = AVCaptureDeviceDiscoverySession::new( - vec![ - AVCaptureDeviceType::UltraWide, - AVCaptureDeviceType::Telephoto, - AVCaptureDeviceType::ExternalUnknown, - ], - AVMediaType::Video, - AVCaptureDevicePosition::Unspecified, - )? - .devices(); - match devices.get(index) { - Some(device) => Ok(AVCaptureDevice::from_id(&device.misc)?), - None => Err(AVFError::FailedToOpenDevice { - index, - why: "No device at index".to_string(), - }), - } - } - - pub fn from_id(id: &str) -> Result { - let nsstr_id = str_to_nsstr(id); - let avfoundation_capture_cls = class!(AVCaptureDevice); - let capture: *mut Object = - unsafe { msg_send![avfoundation_capture_cls, deviceWithUniqueID: nsstr_id] }; - Ok(AVCaptureDevice { inner: capture }) - } - - pub fn supported_formats(&self) -> Result, AVFError> { - try_ns_arr_to_vec::(unsafe { - msg_send![self.inner, formats] }) + .filter(|x| x.frame_rate() != 0) + .collect()) + } + + pub fn already_in_use(&self) -> bool { + unsafe { + let result: BOOL = msg_send![self.inner(), isInUseByAnotherApplication]; + result == YES } + } - pub fn already_in_use(&self) -> bool { - unsafe { - let result: BOOL = msg_send![self.inner(), isInUseByAnotherApplication]; - result == YES - } + pub fn is_suspended(&self) -> bool { + unsafe { + let result: BOOL = msg_send![self.inner, isSuspended]; + result == YES } + } - pub fn is_suspended(&self) -> bool { - unsafe { msg_send![self.inner, isSuspended] } + pub fn lock(&self) -> Result<(), NokhwaError> { + if self.already_in_use() { + return Err(NokhwaError::InitializeError { + backend: ApiBackend::AVFoundation, + error: "Already in use".to_string(), + }); } - - pub fn lock(&self) -> Result<(), AVFError> { - if self.already_in_use() { - return Err(AVFError::AlreadyBusy("In Use".to_string())); - } - let err_ptr: *mut c_void = std::ptr::null_mut(); - let accepted: BOOL = unsafe { msg_send![self.inner, lockForConfiguration: err_ptr] }; - if !err_ptr.is_null() { - return Err(AVFError::ConfigNotAccepted); - } - // Space these out for debug purposes - if !accepted == YES { - return Err(AVFError::ConfigNotAccepted); - } - Ok(()) + let err_ptr: *mut c_void = std::ptr::null_mut(); + let accepted: BOOL = unsafe { msg_send![self.inner, lockForConfiguration: err_ptr] }; + if !err_ptr.is_null() { + return Err(NokhwaError::SetPropertyError { + property: "lockForConfiguration".to_string(), + value: "Locked".to_string(), + error: "Cannot lock for configuration".to_string(), + }); } - - pub fn unlock(&self) { - unsafe { msg_send![self.inner, unlockForConfiguration] } + // Space these out for debug purposes + if !accepted == YES { + return Err(NokhwaError::SetPropertyError { + property: "lockForConfiguration".to_string(), + value: "Locked".to_string(), + error: "Lock Rejected".to_string(), + }); } + Ok(()) + } - // thank you ffmpeg - pub fn set_all( - &mut self, - descriptor: CaptureDeviceFormatDescriptor, - ) -> Result<(), AVFError> { - let format_list = self.supported_formats()?; - let format_description_sel = sel!(formatDescription); + pub fn unlock(&self) { + unsafe { msg_send![self.inner, unlockForConfiguration] } + } - let mut selected_format: *mut Object = std::ptr::null_mut(); - let mut selected_range: *mut Object = std::ptr::null_mut(); + // thank you ffmpeg + pub fn set_all(&mut self, descriptor: CameraFormat) -> Result<(), AVFError> { + let format_list = self.supported_formats()?; + let format_description_sel = sel!(formatDescription); - for format in format_list { - let format_desc_ref: CMFormatDescriptionRef = - unsafe { msg_send![format.internal, performSelector: format_description_sel] }; - let dimensions = unsafe { CMVideoFormatDescriptionGetDimensions(format_desc_ref) }; + let mut selected_format: *mut Object = std::ptr::null_mut(); + let mut selected_range: *mut Object = std::ptr::null_mut(); - if dimensions.height == descriptor.resolution.height - && dimensions.width == descriptor.resolution.width - { - selected_format = format.internal; + for format in format_list { + let format_desc_ref: CMFormatDescriptionRef = + unsafe { msg_send![format.internal, performSelector: format_description_sel] }; + let dimensions = unsafe { CMVideoFormatDescriptionGetDimensions(format_desc_ref) }; - for range in ns_arr_to_vec::(unsafe { - msg_send![format.internal, valueForKey:"videoSupportedFrameRateRanges"] - }) { - let max_fps: f64 = - unsafe { msg_send![range.inner, valueForKey:"maxFrameRate"] }; + if dimensions.height == descriptor.resolution().height + && dimensions.width == descriptor.resolution().width + { + selected_format = format.internal; - if (f64::from(descriptor.fps) - max_fps).abs() < 0.01 { - selected_range = range.inner; - break; - } + for range in ns_arr_to_vec::(unsafe { + msg_send![format.internal, valueForKey:"videoSupportedFrameRateRanges"] + }) { + let max_fps: f64 = + unsafe { msg_send![range.inner, valueForKey:"maxFrameRate"] }; + + if (f64::from(descriptor.fps) - max_fps).abs() < 0.01 { + selected_range = range.inner; + break; } } } - - if selected_range.is_null() || selected_format.is_null() { - return Err(AVFError::ConfigNotAccepted); - } - - self.lock()?; - let _: () = - unsafe { msg_send![self.inner, setValue:selected_format forKey:"activeFormat"] }; - let min_frame_duration: *mut Object = - unsafe { msg_send![selected_range, valueForKey:"minFrameDuration"] }; - let _: () = unsafe { - msg_send![self.inner, setValue:min_frame_duration forKey:"activeVideoMinFrameDuration"] - }; - let _: () = unsafe { - msg_send![self.inner, setValue:min_frame_duration forKey:"activeVideoMaxFrameDuration"] - }; - self.unlock(); - Ok(()) } + + if selected_range.is_null() || selected_format.is_null() { + return Err(NokhwaError::SetPropertyError { + property: "CameraFormat".to_string(), + value: descriptor.to_string(), + error: "Not Found/Rejected/Unsupported".to_string(), + }); + } + + self.lock()?; + let _: () = + unsafe { msg_send![self.inner, setValue:selected_format forKey:"activeFormat"] }; + let min_frame_duration: *mut Object = + unsafe { msg_send![selected_range, valueForKey:"minFrameDuration"] }; + let _: () = unsafe { + msg_send![self.inner, setValue:min_frame_duration forKey:"activeVideoMinFrameDuration"] + }; + let _: () = unsafe { + msg_send![self.inner, setValue:min_frame_duration forKey:"activeVideoMaxFrameDuration"] + }; + self.unlock(); + Ok(()) } +} - impl Drop for AVCaptureDevice { - fn drop(&mut self) { - unsafe { - let _: () = msg_send![self.inner, release]; - } - } - } - - impl AVCaptureDeviceInput { - 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 { - let allocated: *mut Object = msg_send![cls, alloc]; - msg_send![allocated, initWithDevice:capture_device.inner() error:err_ptr] - }; - if !err_ptr.is_null() { - return Err(AVFError::General("Failed to create input".to_string())); - } - - Ok(AVCaptureDeviceInput { - inner: capture_input, - }) - } - } - - impl Drop for AVCaptureDeviceInput { - fn drop(&mut self) { - unsafe { msg_send![self.inner, autorelease] } - } - } - - pub struct AVCaptureVideoDataOutput { - inner: *mut Object, - } - - impl AVCaptureVideoDataOutput { - pub fn new() -> Self { - AVCaptureVideoDataOutput::default() - } - - pub fn add_delegate(&self, delegate: &AVCaptureVideoCallback) -> Result<(), AVFError> { - unsafe { - let avf_queue_str = match CString::new("avf_queue") { - Ok(avf) => avf.into_raw(), - Err(_) => { - // should not happen - return Err(AVFError::StreamOpen("String contains null? This is a bug, please report it: https://github.com/l1npengtul/nokhwa".to_string())); - } - }; - let queue = dispatch_queue_create(avf_queue_str, NSObject(std::ptr::null_mut())); - - let _: () = msg_send![ - self.inner, - setSampleBufferDelegate: delegate.delegate - queue: queue - ]; - }; - Ok(()) - } - } - - impl Default for AVCaptureVideoDataOutput { - fn default() -> Self { - let cls = class!(AVCaptureVideoDataOutput); - let inner: *mut Object = unsafe { msg_send![cls, new] }; - - AVCaptureVideoDataOutput { inner } - } - } - - impl Drop for AVCaptureVideoDataOutput { - fn drop(&mut self) { - unsafe { msg_send![self.inner, autorelease] } - } - } - - impl AVCaptureSession { - pub fn new() -> Self { - AVCaptureSession::default() - } - - pub fn begin_configuration(&self) { - unsafe { msg_send![self.inner, beginConfiguration] } - } - - pub fn commit_configuration(&self) { - unsafe { msg_send![self.inner, commitConfiguration] } - } - - pub fn can_add_input(&self, input: &AVCaptureDeviceInput) -> bool { - let result: BOOL = unsafe { msg_send![self.inner, canAddInput:input.inner] }; - result == YES - } - - pub fn add_input(&self, input: &AVCaptureDeviceInput) -> Result<(), AVFError> { - if self.can_add_input(input) { - let _: () = unsafe { msg_send![self.inner, addInput:input.inner] }; - return Ok(()); - } - Err(AVFError::RejectedInput) - } - - pub fn remove_input(&self, input: &AVCaptureDeviceInput) { - unsafe { msg_send![self.inner, removeInput:input.inner] } - } - - pub fn can_add_output(&self, output: &AVCaptureVideoDataOutput) -> bool { - let result: BOOL = unsafe { msg_send![self.inner, canAddOutput:output.inner] }; - result == YES - } - - pub fn add_output(&self, output: &AVCaptureVideoDataOutput) -> Result<(), AVFError> { - if self.can_add_output(output) { - let _: () = unsafe { msg_send![self.inner, addOutput:output.inner] }; - return Ok(()); - } - Err(AVFError::RejectedInput) - } - - pub fn remove_output(&self, output: &AVCaptureVideoDataOutput) { - unsafe { msg_send![self.inner, removeOutput:output.inner] } - } - - pub fn is_running(&self) -> bool { - let running: BOOL = unsafe { msg_send![self.inner, isRunning] }; - running == YES - } - - pub fn start(&self) -> Result<(), AVFError> { - let start_stream_fn = || { - let _: () = unsafe { msg_send![self.inner, startRunning] }; - }; - - if std::panic::catch_unwind(start_stream_fn).is_err() { - return Err(AVFError::StreamOpen( - "Cannot run AVCaptureSession".to_string(), - )); - } - Ok(()) - } - - pub fn stop(&self) { - unsafe { msg_send![self.inner, stopRunning] } - } - - pub fn is_interrupted(&self) -> bool { - let interrupted: BOOL = unsafe { msg_send![self.inner, isInterrupted] }; - interrupted == YES - } - } - - impl Drop for AVCaptureSession { - fn drop(&mut self) { - self.stop(); - unsafe { msg_send![self.inner, autorelease] } - } - } - - impl Default for AVCaptureSession { - fn default() -> Self { - let cls = class!(AVCaptureSession); - let session: *mut Object = { - let alloc: *mut Object = unsafe { msg_send![cls, alloc] }; - unsafe { msg_send![alloc, init] } - }; - AVCaptureSession { inner: session } +impl Drop for AVCaptureDevice { + fn drop(&mut self) { + unsafe { + let _: () = msg_send![self.inner, release]; } } } -#[cfg(not(any(target_os = "macos", target_os = "ios")))] -pub mod avfoundation { - use crate::AVFError; - use flume::{Receiver, Sender}; - use std::borrow::Cow; - - pub type CompressionData<'a> = (Cow<'a, [u8]>, AVFourCC); - pub type DataPipe<'a> = (Sender>, Receiver>); - - pub fn request_permission_with_callback(_: fn(bool)) {} - - pub fn current_authorization_status() -> AVAuthorizationStatus { - AVAuthorizationStatus::NotDetermined - } - - // fuck it, use deprecated APIs - pub fn query_avfoundation() -> Result, AVFError> { - Err(AVFError::NotSupported) - } - - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - pub enum AVCaptureDeviceType { - Dual, - DualWide, - Triple, - WideAngle, - UltraWide, - Telephoto, - TrueDepth, - ExternalUnknown, - } - - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - pub enum AVMediaType { - Audio, - ClosedCaption, - DepthData, - Metadata, - MetadataObject, - Muxed, - Subtitle, - Text, - Timecode, - Video, - } - - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - #[repr(isize)] - pub enum AVCaptureDevicePosition { - Unspecified = 0, - Back = 1, - Front = 2, - } - - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - #[repr(isize)] - pub enum AVAuthorizationStatus { - NotDetermined = 0, - Restricted = 1, - Denied = 2, - Authorized = 3, - } - - #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] - #[repr(u32)] - pub enum AVFourCC { - YUV2, - MJPEG, - GRAY8, - } - - // Localized Name - // - #[derive(Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)] - pub struct AVCaptureDeviceDescriptor { - pub name: String, - pub description: String, - pub misc: String, - pub index: u64, - } - - pub struct AVCaptureVideoCallback {} - - impl AVCaptureVideoCallback { - pub fn new(_: usize) -> Self { - AVCaptureVideoCallback {} +impl AVCaptureDeviceInput { + 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 { + let allocated: *mut Object = msg_send![cls, alloc]; + msg_send![allocated, initWithDevice:capture_device.inner() error:err_ptr] + }; + if !err_ptr.is_null() { + return Err(NokhwaError::InitializeError { + backend: ApiBackend::AVFoundation, + error: "Failed to create input".to_string(), + }); } - pub fn index(&self) -> usize { - 0 - } - - pub fn data_len(&self) -> usize { - 0 - } - - pub fn frame_to_slice<'a>(&self) -> Result, AVFError> { - Err(AVFError::NotSupported) - } - - pub fn frame_to_slice_no_block<'a>(&self) -> Result, AVFError> { - Err(AVFError::NotSupported) - } - } - - pub struct AVFrameRateRange {} - pub struct AVCaptureDeviceDiscoverySession {} - pub struct AVCaptureDevice {} - pub struct AVCaptureDeviceInput {} - pub struct AVCaptureSession {} - - impl AVFrameRateRange { - pub fn max(&self) -> f64 { - 0_f64 - } - - pub fn min(&self) -> f64 { - 0_f64 - } - } - - #[derive(Copy, Clone, Debug)] - pub struct CMVideoDimensions { - pub width: i32, - pub height: i32, - } - - pub type AVVideoResolution = CMVideoDimensions; - - #[derive(Copy, Clone, Debug)] - pub struct CaptureDeviceFormatDescriptor { - pub resolution: AVVideoResolution, - pub fps: u32, - pub fourcc: AVFourCC, - } - - impl CaptureDeviceFormatDescriptor { - pub fn compatible_with_capture_format(&self, other: &AVCaptureDeviceFormat) -> bool { - for fps in &other.fps_list { - if self.resolution.height == other.resolution.height - && self.resolution.width == other.resolution.width - && self.fourcc == other.fourcc - && (*fps as u32) == self.fps - { - return true; - } - } - false - } - } - - #[derive(Debug)] - pub struct AVCaptureDeviceFormat { - pub resolution: CMVideoDimensions, - pub fps_list: Vec, - pub fourcc: AVFourCC, - } - - impl AVCaptureDeviceDiscoverySession { - pub fn new( - _: Vec, - _: AVMediaType, - _: AVCaptureDevicePosition, - ) -> Result { - Err(AVFError::NotSupported) - } - - pub fn default() -> Result { - AVCaptureDeviceDiscoverySession::new( - vec![ - AVCaptureDeviceType::UltraWide, - AVCaptureDeviceType::Telephoto, - AVCaptureDeviceType::ExternalUnknown, - AVCaptureDeviceType::Dual, - AVCaptureDeviceType::DualWide, - AVCaptureDeviceType::Triple, - ], - AVMediaType::Video, - AVCaptureDevicePosition::Unspecified, - ) - } - - pub fn devices(&self) -> Vec { - vec![] - } - } - - impl AVCaptureDevice { - pub fn devices_with_type(_: AVMediaType) -> Vec { - vec![] - } - - pub fn new(_: usize) -> Result { - Err(AVFError::NotSupported) - } - - pub fn from_id(_: &str) -> Result { - Err(AVFError::NotSupported) - } - - pub fn supported_formats(&self) -> Result, AVFError> { - Err(AVFError::NotSupported) - } - - pub fn already_in_use(&self) -> bool { - false - } - - pub fn is_suspended(&self) -> bool { - false - } - - pub fn lock(&self) -> Result<(), AVFError> { - Err(AVFError::NotSupported) - } - - pub fn unlock(&self) {} - - pub fn set_frame_rate(&mut self, _: u32) {} - - pub fn set_all(&mut self, _: CaptureDeviceFormatDescriptor) -> Result<(), AVFError> { - Err(AVFError::NotSupported) - } - } - - impl AVCaptureDeviceInput { - pub fn new(_: &AVCaptureDevice) -> Result { - Err(AVFError::NotSupported) - } - } - - pub struct AVCaptureVideoDataOutput {} - - impl AVCaptureVideoDataOutput { - pub fn new() -> Self { - AVCaptureVideoDataOutput::default() - } - - pub fn add_delegate(&self, _: &AVCaptureVideoCallback) -> Result<(), AVFError> { - Err(AVFError::NotSupported) - } - } - - impl Default for AVCaptureVideoDataOutput { - fn default() -> Self { - AVCaptureVideoDataOutput {} - } - } - - impl AVCaptureSession { - pub fn new() -> Self { - AVCaptureSession::default() - } - - pub fn begin_configuration(&self) {} - - pub fn commit_configuration(&self) {} - - pub fn can_add_input(&self, _: &AVCaptureDeviceInput) -> bool { - false - } - - pub fn add_input(&self, _: &AVCaptureDeviceInput) -> Result<(), AVFError> { - Err(AVFError::NotSupported) - } - - pub fn remove_input(&self, _: &AVCaptureDeviceInput) {} - - pub fn can_add_output(&self, _: &AVCaptureVideoDataOutput) -> bool { - false - } - - pub fn add_output(&self, _: &AVCaptureVideoDataOutput) -> Result<(), AVFError> { - Err(AVFError::NotSupported) - } - - pub fn remove_output(&self, _: &AVCaptureVideoDataOutput) {} - - pub fn is_running(&self) -> bool { - false - } - - pub fn start(&self) -> Result<(), AVFError> { - Err(AVFError::NotSupported) - } - - pub fn stop(&self) {} - - pub fn is_interrupted(&self) -> bool { - false - } - } - - impl Default for AVCaptureSession { - fn default() -> Self { - AVCaptureSession {} - } + Ok(AVCaptureDeviceInput { + inner: capture_input, + }) + } +} + +impl Drop for AVCaptureDeviceInput { + fn drop(&mut self) { + unsafe { msg_send![self.inner, autorelease] } + } +} + +pub struct AVCaptureVideoDataOutput { + inner: *mut Object, +} + +impl AVCaptureVideoDataOutput { + pub fn new() -> Self { + AVCaptureVideoDataOutput::default() + } + + pub fn add_delegate(&self, delegate: &AVCaptureVideoCallback) -> Result<(), NokhwaError> { + unsafe { + let avf_queue_str = match CString::new("avf_queue") { + Ok(avf) => avf.into_raw(), + Err(_) => { + // should not happen + return Err(NokhwaError::GeneralError("String contains null? This is a bug, please report it: https://github.com/l1npengtul/nokhwa".to_string())); + } + }; + let queue = dispatch_queue_create(avf_queue_str, NSObject(std::ptr::null_mut())); + + let _: () = msg_send![ + self.inner, + setSampleBufferDelegate: delegate.delegate + queue: queue + ]; + }; + Ok(()) + } +} + +impl Default for AVCaptureVideoDataOutput { + fn default() -> Self { + let cls = class!(AVCaptureVideoDataOutput); + let inner: *mut Object = unsafe { msg_send![cls, new] }; + + AVCaptureVideoDataOutput { inner } + } +} + +impl Drop for AVCaptureVideoDataOutput { + fn drop(&mut self) { + unsafe { msg_send![self.inner, autorelease] } + } +} + +impl AVCaptureSession { + pub fn new() -> Self { + AVCaptureSession::default() + } + + pub fn begin_configuration(&self) { + unsafe { msg_send![self.inner, beginConfiguration] } + } + + pub fn commit_configuration(&self) { + unsafe { msg_send![self.inner, commitConfiguration] } + } + + pub fn can_add_input(&self, input: &AVCaptureDeviceInput) -> bool { + let result: BOOL = unsafe { msg_send![self.inner, canAddInput:input.inner] }; + result == YES + } + + pub fn add_input(&self, input: &AVCaptureDeviceInput) -> Result<(), NokhwaError> { + if self.can_add_input(input) { + let _: () = unsafe { msg_send![self.inner, addInput:input.inner] }; + return Ok(()); + } + Err(NokhwaError::SetPropertyError { + property: "AVCaptureDeviceInput".to_string(), + value: "add new input".to_string(), + error: "Rejected".to_string(), + }) + } + + pub fn remove_input(&self, input: &AVCaptureDeviceInput) { + unsafe { msg_send![self.inner, removeInput:input.inner] } + } + + pub fn can_add_output(&self, output: &AVCaptureVideoDataOutput) -> bool { + let result: BOOL = unsafe { msg_send![self.inner, canAddOutput:output.inner] }; + result == YES + } + + pub fn add_output(&self, output: &AVCaptureVideoDataOutput) -> Result<(), NokhwaError> { + if self.can_add_output(output) { + let _: () = unsafe { msg_send![self.inner, addOutput:output.inner] }; + return Ok(()); + } + Err(NokhwaError::SetPropertyError { + property: "AVCaptureVideoDataOutput".to_string(), + value: "add new output".to_string(), + error: "Rejected".to_string(), + }) + } + + pub fn remove_output(&self, output: &AVCaptureVideoDataOutput) { + unsafe { msg_send![self.inner, removeOutput:output.inner] } + } + + pub fn is_running(&self) -> bool { + let running: BOOL = unsafe { msg_send![self.inner, isRunning] }; + running == YES + } + + pub fn start(&self) -> Result<(), NokhwaError> { + let start_stream_fn = || { + let _: () = unsafe { msg_send![self.inner, startRunning] }; + }; + + if std::panic::catch_unwind(start_stream_fn).is_err() { + return Err(NokhwaError::OpenStreamError( + "Cannot run AVCaptureSession".to_string(), + )); + } + Ok(()) + } + + pub fn stop(&self) { + unsafe { msg_send![self.inner, stopRunning] } + } + + pub fn is_interrupted(&self) -> bool { + let interrupted: BOOL = unsafe { msg_send![self.inner, isInterrupted] }; + interrupted == YES + } +} + +impl Drop for AVCaptureSession { + fn drop(&mut self) { + self.stop(); + unsafe { msg_send![self.inner, autorelease] } + } +} + +impl Default for AVCaptureSession { + fn default() -> Self { + let cls = class!(AVCaptureSession); + let session: *mut Object = { + let alloc: *mut Object = unsafe { msg_send![cls, alloc] }; + unsafe { msg_send![alloc, init] } + }; + AVCaptureSession { inner: session } } } diff --git a/nokhwa-bindings-windows/Cargo.toml b/nokhwa-bindings-windows/Cargo.toml index afbfb02..07505ac 100644 --- a/nokhwa-bindings-windows/Cargo.toml +++ b/nokhwa-bindings-windows/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa-bindings-windows" -version = "0.3.4" +version = "0.4.0" authors = ["l1npengtul"] edition = "2021" license = "Apache-2.0" diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index 15bcf03..97b4542 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -14,23 +14,23 @@ * limitations under the License. */ -use crate::nokhwa_check; use image::{ImageBuffer, Rgb}; -use nokhwa_bindings_macos::avfoundation::{ - query_avfoundation, AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, - AVCaptureVideoCallback, AVCaptureVideoDataOutput, AVFourCC, -}; -use nokhwa_core::pixel_format::FormatDecoder; -use nokhwa_core::traits::CaptureBackendTrait; -use nokhwa_core::types::{ - mjpeg_to_rgb, yuyv422_to_rgb, ApiBackend, CameraControl, ControlValueSetter, FrameFormat, - KnownCameraControl, Resolution, +use nokhwa_bindings_macos::{ + AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, AVCaptureVideoCallback, + AVCaptureVideoDataOutput, }; use nokhwa_core::{ error::NokhwaError, - types::{CameraFormat, CameraInfo}, + pixel_format::FormatDecoder, + traits::CaptureBackendTrait, + types::{ + mjpeg_to_rgb, yuyv422_to_rgb, ApiBackend, CameraControl, CameraFormat, CameraIndex, + CameraInfo, ControlValueSetter, FrameFormat, KnownCameraControl, RequestedFormat, + Resolution, + }, }; -use std::{any::Any, borrow::Borrow, borrow::Cow, collections::HashMap, ops::Deref}; +use std::{borrow::Cow, collections::HashMap}; +use v4l::frameinterval::FrameIntervalEnum; /// The backend struct that interfaces with V4L2. /// To see what this does, please see [`CaptureBackendTrait`]. @@ -57,25 +57,12 @@ impl AVFoundationCaptureDevice { /// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default. /// # Errors /// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user. - pub fn new(index: usize, camera_format: Option) -> Result { - let camera_format = match camera_format { - Some(fmt) => fmt, - None => CameraFormat::default(), - }; - - let device_descriptor: CameraInfo = match query_avfoundation()?.into_iter().nth(index) { - Some(descriptor) => descriptor.into(), - None => { - return Err(NokhwaError::OpenDeviceError( - index.to_string(), - "No Device".to_string(), - )) - } - }; - - let mut device = AVCaptureDevice::from_id(&device_descriptor.misc())?; + pub fn new(index: CameraIndex, req_fmt: RequestedFormat) -> Result { + let mut device = AVCaptureDevice::new(index)?; device.lock()?; - device.set_all(camera_format.into())?; + let formats = device.supported_formats()?; + let camera_fmt = req_fmt.fulfill(&formats)?; + device.set_all(camera_fmt)?; Ok(AVFoundationCaptureDevice { device, @@ -92,6 +79,7 @@ impl AVFoundationCaptureDevice { /// /// # Errors /// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user. + #[deprecated(since = "0.10.0", note = "please use `new` instead.")] pub fn new_with( index: usize, width: u32, @@ -99,8 +87,11 @@ impl AVFoundationCaptureDevice { fps: u32, fourcc: FrameFormat, ) -> Result { - let camera_format = Some(CameraFormat::new_from(width, height, fourcc, fps)); - AVFoundationCaptureDevice::new(index, camera_format) + let camera_format = CameraFormat::new_from(width, height, fourcc, fps); + AVFoundationCaptureDevice::new( + CameraIndex::Index(index as u32), + RequestedFormat::Exact(camera_format), + ) } } @@ -114,7 +105,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { } fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> { - todo!() + Ok(()) } fn camera_format(&self) -> CameraFormat { @@ -133,23 +124,21 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { &mut self, fourcc: FrameFormat, ) -> Result>, NokhwaError> { - Ok(self + let supported_cfmt = self .device .supported_formats()? .into_iter() - .map(|fmt| { - ( - FrameFormat::from(fmt.fourcc), - Resolution::from(fmt.resolution), - (&fmt.fps_list) - .iter() - .map(|f| *f as u32) - .collect::>(), - ) - }) - .filter(|x| (*x).0 == fourcc) - .map(|fmt| (fmt.1, fmt.2)) - .collect::>>()) + .filter(|x| x.format() != fourcc); + let mut res_list = HashMap::new(); + for format in supported_cfmt { + match res_list.get_mut(&format.resolution()) { + Some(fpses) => fpses.push(format.frame_rate()), + None => { + vec![format.frame_rate()] + } + } + } + Ok(res_list) } fn compatible_fourcc(&mut self) -> Result, NokhwaError> { @@ -266,12 +255,6 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { Ok(image_buf) } - fn frame_typed( - &mut self, - ) -> Result>, NokhwaError> { - todo!() - } - fn frame_raw(&mut self) -> Result, NokhwaError> { match &self.session { Some(session) => {