#![allow(clippy::not_unsafe_ptr_arg_deref)]
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod internal {
#[allow(non_snake_case)]
pub mod core_media {
use crate::internal::CGFloat;
use core_media_sys::{
CMBlockBufferRef, CMFormatDescriptionRef, CMSampleBufferRef, CMTime, CMVideoDimensions,
FourCharCode,
};
use objc::{runtime::Object, Message};
use std::ops::Deref;
pub type Id = *mut Object;
#[repr(transparent)]
#[derive(Clone)]
pub struct NSObject(pub Id);
impl Deref for NSObject {
type Target = Object;
fn deref(&self) -> &Self::Target {
unsafe { &*self.0 }
}
}
unsafe impl Message for NSObject {}
impl NSObject {
pub fn alloc() -> Self {
Self(unsafe { msg_send!(objc::class!(NSObject), alloc) })
}
}
#[repr(transparent)]
#[derive(Clone)]
pub struct NSString(pub Id);
impl Deref for NSString {
type Target = Object;
fn deref(&self) -> &Self::Target {
unsafe { &*self.0 }
}
}
unsafe impl Message for NSString {}
impl NSString {
pub fn alloc() -> Self {
Self(unsafe { msg_send!(objc::class!(NSString), alloc) })
}
}
pub type AVMediaType = NSString;
#[allow(non_snake_case)]
#[link(name = "CoreMedia", kind = "framework")]
extern "C" {
pub fn CMVideoFormatDescriptionGetDimensions(
videoDesc: CMFormatDescriptionRef,
) -> CMVideoDimensions;
pub fn CMTimeMake(value: i64, scale: i32) -> CMTime;
pub fn CMBlockBufferGetDataLength(theBuffer: CMBlockBufferRef) -> std::os::raw::c_int;
pub fn CMBlockBufferCopyDataBytes(
theSourceBuffer: CMBlockBufferRef,
offsetToData: usize,
dataLength: usize,
destination: *mut std::os::raw::c_void,
) -> std::os::raw::c_int;
pub fn CMSampleBufferGetDataBuffer(sbuf: CMSampleBufferRef) -> CMBlockBufferRef;
pub fn dispatch_queue_create(
label: *const std::os::raw::c_char,
attr: NSObject,
) -> NSObject;
pub fn dispatch_release(object: NSObject);
pub fn CMSampleBufferGetImageBuffer(sbuf: CMSampleBufferRef) -> CVImageBufferRef;
pub fn CVPixelBufferLockBaseAddress(
pixelBuffer: CVPixelBufferRef,
lockFlags: CVPixelBufferLockFlags,
) -> CVReturn;
pub fn CVPixelBufferUnlockBaseAddress(
pixelBuffer: CVPixelBufferRef,
unlockFlags: CVPixelBufferLockFlags,
) -> CVReturn;
pub fn CVPixelBufferGetDataSize(pixelBuffer: CVPixelBufferRef)
-> std::os::raw::c_ulong;
pub fn CVPixelBufferGetBaseAddress(
pixelBuffer: CVPixelBufferRef,
) -> *mut std::os::raw::c_void;
pub fn CVPixelBufferGetPixelFormatType(pixelBuffer: CVPixelBufferRef) -> OSType;
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct __CVBuffer {
_unused: [u8; 0],
}
#[allow(non_snake_case)]
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
#[repr(C)]
pub struct AVCaptureWhiteBalanceGains {
pub blueGain: f32,
pub greenGain: f32,
pub redGain: f32,
}
pub type CVBufferRef = *mut __CVBuffer;
pub type CVImageBufferRef = CVBufferRef;
pub type CVPixelBufferRef = CVImageBufferRef;
pub type CVPixelBufferLockFlags = u64;
pub type CVReturn = i32;
pub type OSType = FourCharCode;
pub type AVVideoCodecType = NSString;
#[link(name = "AVFoundation", kind = "framework")]
extern "C" {
pub static AVVideoCodecKey: NSString;
pub static AVVideoCodecTypeHEVC: AVVideoCodecType;
pub static AVVideoCodecTypeH264: AVVideoCodecType;
pub static AVVideoCodecTypeJPEG: AVVideoCodecType;
pub static AVVideoCodecTypeAppleProRes4444: AVVideoCodecType;
pub static AVVideoCodecTypeAppleProRes422: AVVideoCodecType;
pub static AVVideoCodecTypeAppleProRes422HQ: AVVideoCodecType;
pub static AVVideoCodecTypeAppleProRes422LT: AVVideoCodecType;
pub static AVVideoCodecTypeAppleProRes422Proxy: AVVideoCodecType;
pub static AVVideoCodecTypeHEVCWithAlpha: AVVideoCodecType;
pub static AVVideoCodecHEVC: NSString;
pub static AVVideoCodecH264: NSString;
pub static AVVideoCodecJPEG: NSString;
pub static AVVideoCodecAppleProRes4444: NSString;
pub static AVVideoCodecAppleProRes422: NSString;
pub static AVVideoWidthKey: NSString;
pub static AVVideoHeightKey: NSString;
pub static AVVideoExpectedSourceFrameRateKey: NSString;
pub static AVMediaTypeVideo: AVMediaType;
pub static AVMediaTypeAudio: AVMediaType;
pub static AVMediaTypeText: AVMediaType;
pub static AVMediaTypeClosedCaption: AVMediaType;
pub static AVMediaTypeSubtitle: AVMediaType;
pub static AVMediaTypeTimecode: AVMediaType;
pub static AVMediaTypeMetadata: AVMediaType;
pub static AVMediaTypeMuxed: AVMediaType;
pub static AVMediaTypeMetadataObject: AVMediaType;
pub static AVMediaTypeDepthData: AVMediaType;
pub static AVCaptureLensPositionCurrent: f32;
pub static AVCaptureExposureTargetBiasCurrent: f32;
pub static AVCaptureExposureDurationCurrent: CMTime;
pub static AVCaptureISOCurrent: f32;
}
}
use crate::core_media::{
dispatch_queue_create, AVCaptureExposureDurationCurrent,
AVCaptureExposureTargetBiasCurrent, AVCaptureISOCurrent, AVCaptureWhiteBalanceGains,
AVMediaTypeAudio, AVMediaTypeClosedCaption, AVMediaTypeDepthData, AVMediaTypeMetadata,
AVMediaTypeMetadataObject, AVMediaTypeMuxed, AVMediaTypeSubtitle, AVMediaTypeText,
AVMediaTypeTimecode, AVMediaTypeVideo, CGPoint, CMSampleBufferGetImageBuffer,
CMVideoFormatDescriptionGetDimensions, CVImageBufferRef, CVPixelBufferGetBaseAddress,
CVPixelBufferGetDataSize, CVPixelBufferLockBaseAddress, CVPixelBufferUnlockBaseAddress,
NSObject, OSType,
};
use block::ConcreteBlock;
use cocoa_foundation::{
base::Nil,
foundation::{NSArray, NSInteger, NSString, NSUInteger},
};
use core_media_sys::{
kCMPixelFormat_24RGB, kCMPixelFormat_422YpCbCr8_yuvs,
kCMPixelFormat_8IndexedGray_WhiteIsZero, kCMVideoCodecType_422YpCbCr8,
kCMVideoCodecType_JPEG, kCMVideoCodecType_JPEG_OpenDML, CMFormatDescriptionGetMediaSubType,
CMFormatDescriptionRef, CMSampleBufferRef, CMTime, CMVideoDimensions,
};
use core_video_sys::{
kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange,
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
};
use flume::{Receiver, Sender};
use nokhwa_core::{
error::NokhwaError,
types::{
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo,
ControlValueDescription, ControlValueSetter, FrameFormat, KnownCameraControl,
KnownCameraControlFlag, Resolution,
},
};
use objc::runtime::objc_getClass;
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
};
use once_cell::sync::Lazy;
use std::ffi::CString;
use std::{
borrow::Cow,
cmp::Ordering,
collections::BTreeMap,
convert::TryFrom,
error::Error,
ffi::{c_float, c_void, CStr},
sync::Arc,
};
const UTF8_ENCODING: usize = 4;
type CGFloat = c_float;
macro_rules! create_boilerplate_impl {
{
$( [$class_vis:vis $class_name:ident : $( {$field_vis:vis $field_name:ident : $field_type:ty} ),*] ),+
} => {
$(
$class_vis struct $class_name {
inner: *mut Object,
$(
$field_vis $field_name : $field_type
)*
}
impl $class_name {
pub fn inner(&self) -> *mut Object {
self.inner
}
}
)+
};
{
$( [$class_vis:vis $class_name:ident ] ),+
} => {
$(
$class_vis struct $class_name {
inner: *mut Object,
}
impl $class_name {
pub fn inner(&self) -> *mut Object {
self.inner
}
}
impl From<*mut Object> for $class_name {
fn from(obj: *mut Object) -> Self {
$class_name {
inner: obj,
}
}
}
)+
};
}
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
];
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<T: Into<*mut Object>>(data: Vec<T>) -> *mut Object {
let cstr = CString::new("NSMutableArray").unwrap();
let ns_arr_cls = unsafe { objc_getClass(cstr.as_ptr()) };
let mutable_array: *mut Object = unsafe { msg_send![ns_arr_cls, array] };
data.into_iter().for_each(|item| {
let item_obj: *mut Object = item.into();
let _: () = unsafe { msg_send![mutable_array, addObject: item_obj] };
});
mutable_array
}
fn ns_arr_to_vec<T: From<*mut Object>>(data: *mut Object) -> Vec<T> {
let length = unsafe { NSArray::count(data) };
let mut out_vec: Vec<T> = Vec::with_capacity(length as usize);
for index in 0..length {
let item = unsafe { NSArray::objectAtIndex(data, index) };
out_vec.push(T::from(item));
}
out_vec
}
fn try_ns_arr_to_vec<T, TE>(data: *mut Object) -> Result<Vec<T>, TE>
where
TE: Error,
T: TryFrom<*mut Object, Error = TE>,
{
let length = unsafe { NSArray::count(data) };
let mut out_vec: Vec<T> = 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)?);
}
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
}
}
#[allow(non_upper_case_globals)]
fn raw_fcc_to_frameformat(raw: OSType) -> Option<FrameFormat> {
match raw {
kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => {
Some(FrameFormat::YUYV)
}
kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => Some(FrameFormat::MJPEG),
kCMPixelFormat_8IndexedGray_WhiteIsZero => Some(FrameFormat::GRAY),
kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange
| kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
| 875704438 => Some(FrameFormat::NV12),
kCMPixelFormat_24RGB => Some(FrameFormat::RAWRGB),
_ => None,
}
}
pub type CompressionData<'a> = (Cow<'a, [u8]>, FrameFormat);
pub type DataPipe<'a> = (Sender<CompressionData<'a>>, Receiver<CompressionData<'a>>);
static CALLBACK_CLASS: Lazy<&'static Class> = Lazy::new(|| {
{
let mut decl = ClassDecl::new("MyCaptureCallback", class!(NSObject)).unwrap();
decl.add_ivar::<*const c_void>("_arcmutptr");
extern "C" fn my_callback_get_arcmutptr(this: &Object, _: Sel) -> *const c_void {
unsafe { *this.get_ivar("_arcmutptr") }
}
extern "C" fn my_callback_set_arcmutptr(
this: &mut Object,
_: Sel,
new_arcmutptr: *const c_void,
) {
unsafe {
this.set_ivar("_arcmutptr", new_arcmutptr);
}
}
#[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_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 bufferlck_cv: *const c_void = unsafe { msg_send![this, bufferPtr] };
let buffer_sndr = unsafe {
let ptr = bufferlck_cv.cast::<Sender<(Vec<u8>, FrameFormat)>>();
Arc::from_raw(ptr)
};
if let Err(_) = buffer_sndr.send((buffer_as_vec, FrameFormat::GRAY)) {
return;
}
std::mem::forget(buffer_sndr);
}
#[allow(non_snake_case)]
extern "C" fn capture_drop_callback(
_: &mut Object,
_: Sel,
_: *mut Object,
_: *mut Object,
_: *mut Object,
) {
}
unsafe {
decl.add_method(
sel!(bufferPtr),
my_callback_get_arcmutptr as extern "C" fn(&Object, Sel) -> *const c_void,
);
decl.add_method(
sel!(SetBufferPtr:),
my_callback_set_arcmutptr as extern "C" fn(&mut Object, Sel, *const 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: impl Fn(bool) + Send + Sync + 'static) {
let cls = class!(AVCaptureDevice);
let wrapper = move |bool: BOOL| {
callback(bool == YES);
};
let objc_fn_block: ConcreteBlock<(BOOL,), (), _> = ConcreteBlock::new(wrapper);
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
}
pub fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
Ok(AVCaptureDeviceDiscoverySession::new(vec![
AVCaptureDeviceType::UltraWide,
AVCaptureDeviceType::WideAngle,
AVCaptureDeviceType::Telephoto,
AVCaptureDeviceType::TrueDepth,
AVCaptureDeviceType::ExternalUnknown,
])?
.devices())
}
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,
DualWide,
Triple,
WideAngle,
UltraWide,
Telephoto,
TrueDepth,
ExternalUnknown,
}
impl From<AVCaptureDeviceType> 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)
}
}
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum AVMediaType {
Audio,
ClosedCaption,
DepthData,
Metadata,
MetadataObject,
Muxed,
Subtitle,
Text,
Timecode,
Video,
}
impl From<AVMediaType> 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 },
}
}
}
impl TryFrom<*mut Object> for AVMediaType {
type Error = NokhwaError;
fn try_from(value: *mut Object) -> Result<Self, Self::Error> {
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 AVMediaType {
pub fn into_ns_str(self) -> *mut Object {
<*mut Object>::from(self)
}
}
#[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 {
delegate: *mut Object,
queue: NSObject,
}
impl AVCaptureVideoCallback {
pub fn new(
device_spec: &CStr,
buffer: &Arc<Sender<(Vec<u8>, FrameFormat)>>,
) -> Result<Self, NokhwaError> {
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 buffer_as_ptr = {
let arc_raw = Arc::as_ptr(buffer);
arc_raw.cast::<c_void>()
};
unsafe {
let _: () = msg_send![delegate, SetBufferPtr: buffer_as_ptr];
}
let queue = unsafe {
dispatch_queue_create(device_spec.as_ptr(), NSObject(std::ptr::null_mut()))
};
Ok(AVCaptureVideoCallback { delegate, queue })
}
pub fn data_len(&self) -> usize {
unsafe { msg_send![self.delegate, dataLength] }
}
pub fn inner(&self) -> *mut Object {
self.delegate
}
pub fn queue(&self) -> &NSObject {
&self.queue
}
}
create_boilerplate_impl! {
[pub AVFrameRateRange],
[pub AVCaptureDeviceDiscoverySession],
[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<f64>,
pub fourcc: FrameFormat,
}
impl TryFrom<*mut Object> for AVCaptureDeviceFormat {
type Error = NokhwaError;
fn try_from(value: *mut Object) -> Result<Self, Self::Error> {
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::<AVFrameRateRange>(unsafe {
msg_send![value, videoSupportedFrameRateRanges]
})
.into_iter()
.flat_map(|v| {
if v.min() != 0_f64 && v.min() != 1_f64 {
vec![v.min(), v.max()]
} else {
vec![v.max()] }
})
.collect::<Vec<f64>>();
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 raw_fcc_to_frameformat(fcc_raw) {
Some(fcc) => fcc,
None => {
return Err(NokhwaError::StructureError {
structure: "FourCharCode".to_string(),
error: format!("Unknown FourCharCode {fcc_raw:?}"),
})
}
};
Ok(AVCaptureDeviceFormat {
internal: value,
resolution,
fps_list,
fourcc,
})
}
}
impl AVCaptureDeviceDiscoverySession {
pub fn new(device_types: Vec<AVCaptureDeviceType>) -> Result<Self, NokhwaError> {
let device_types = vec_to_ns_arr(device_types);
let position = 0 as NSInteger;
let media_type_video = unsafe { AVMediaTypeVideo.clone() }.0;
let discovery_session_cls = class!(AVCaptureDeviceDiscoverySession);
let discovery_session: *mut Object = unsafe {
msg_send![discovery_session_cls, discoverySessionWithDeviceTypes:device_types mediaType:media_type_video position:position]
};
Ok(AVCaptureDeviceDiscoverySession {
inner: discovery_session,
})
}
pub fn default() -> Result<Self, NokhwaError> {
AVCaptureDeviceDiscoverySession::new(vec![
AVCaptureDeviceType::UltraWide,
AVCaptureDeviceType::Telephoto,
AVCaptureDeviceType::ExternalUnknown,
AVCaptureDeviceType::Dual,
AVCaptureDeviceType::DualWide,
AVCaptureDeviceType::Triple,
])
}
pub fn devices(&self) -> Vec<CameraInfo> {
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::with_capacity(objects_len as usize);
for index in 0..objects_len {
let device = unsafe { device_ns_array.objectAtIndex(index) };
devices.push(get_raw_device_info(
CameraIndex::Index(index as u32),
device,
));
}
devices
}
}
pub struct AVCaptureDevice {
inner: *mut Object,
device: CameraInfo,
locked: bool,
}
impl AVCaptureDevice {
pub fn inner(&self) -> *mut Object {
self.inner
}
}
impl AVCaptureDevice {
pub fn new(index: &CameraIndex) -> Result<Self, NokhwaError> {
match &index {
CameraIndex::Index(idx) => {
let devices = query_avfoundation()?;
match devices.get(*idx as usize) {
Some(device) => Ok(AVCaptureDevice::from_id(
&device.misc(),
Some(index.clone()),
)?),
None => Err(NokhwaError::OpenDeviceError(
idx.to_string(),
"Not Found".to_string(),
)),
}
}
CameraIndex::String(id) => Ok(AVCaptureDevice::from_id(id, None)?),
}
}
pub fn from_id(id: &str, index_hint: Option<CameraIndex>) -> Result<Self, NokhwaError> {
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(),
));
}
let camera_info = get_raw_device_info(
index_hint.unwrap_or_else(|| CameraIndex::String(id.to_string())),
capture,
);
Ok(AVCaptureDevice {
inner: capture,
device: camera_info,
locked: false,
})
}
pub fn info(&self) -> &CameraInfo {
&self.device
}
pub fn supported_formats_raw(&self) -> Result<Vec<AVCaptureDeviceFormat>, NokhwaError> {
try_ns_arr_to_vec::<AVCaptureDeviceFormat, NokhwaError>(unsafe {
msg_send![self.inner, formats]
})
}
pub fn supported_formats(&self) -> Result<Vec<CameraFormat>, NokhwaError> {
Ok(self
.supported_formats_raw()?
.iter()
.flat_map(|av_fmt| {
let resolution = av_fmt.resolution;
av_fmt.fps_list.iter().map(move |fps_f64| {
let fps = *fps_f64 as u32;
let resolution =
Resolution::new(resolution.width as u32, resolution.height as u32); CameraFormat::new(resolution, av_fmt.fourcc, fps)
})
})
.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 is_suspended(&self) -> bool {
unsafe {
let result: BOOL = msg_send![self.inner, isSuspended];
result == YES
}
}
pub fn lock(&self) -> Result<(), NokhwaError> {
if self.locked {
return Ok(());
}
if self.already_in_use() {
return Err(NokhwaError::InitializeError {
backend: ApiBackend::AVFoundation,
error: "Already 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(NokhwaError::SetPropertyError {
property: "lockForConfiguration".to_string(),
value: "Locked".to_string(),
error: "Cannot lock for configuration".to_string(),
});
}
if !accepted == YES {
return Err(NokhwaError::SetPropertyError {
property: "lockForConfiguration".to_string(),
value: "Locked".to_string(),
error: "Lock Rejected".to_string(),
});
}
Ok(())
}
pub fn unlock(&mut self) {
if self.locked {
self.locked = false;
unsafe { msg_send![self.inner, unlockForConfiguration] }
}
}
pub fn set_all(&mut self, descriptor: CameraFormat) -> Result<(), NokhwaError> {
self.lock()?;
let format_list = try_ns_arr_to_vec::<AVCaptureDeviceFormat, NokhwaError>(unsafe {
msg_send![self.inner, formats]
})?;
let format_description_sel = sel!(formatDescription);
let mut selected_format: *mut Object = std::ptr::null_mut();
let mut selected_range: *mut Object = std::ptr::null_mut();
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) };
if dimensions.height == descriptor.resolution().height() as i32
&& dimensions.width == descriptor.resolution().width() as i32
{
selected_format = format.internal;
for range in ns_arr_to_vec::<AVFrameRateRange>(unsafe {
msg_send![format.internal, videoSupportedFrameRateRanges]
}) {
let max_fps: f64 = unsafe { msg_send![range.inner, maxFrameRate] };
if (f64::from(descriptor.frame_rate()) - max_fps).abs() < 0.01 {
selected_range = range.inner;
break;
}
}
}
}
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(),
});
}
let activefmtkey = str_to_nsstr("activeFormat");
let min_frame_duration = str_to_nsstr("minFrameDuration");
let active_video_min_frame_duration = str_to_nsstr("activeVideoMinFrameDuration");
let active_video_max_frame_duration = str_to_nsstr("activeVideoMaxFrameDuration");
let _: () =
unsafe { msg_send![self.inner, setValue:selected_format forKey:activefmtkey] };
let min_frame_duration: *mut Object =
unsafe { msg_send![selected_range, valueForKey: min_frame_duration] };
let _: () = unsafe {
msg_send![self.inner, setValue:min_frame_duration forKey:active_video_min_frame_duration]
};
let _: () = unsafe {
msg_send![self.inner, setValue:min_frame_duration forKey:active_video_max_frame_duration]
};
self.unlock();
Ok(())
}
pub fn get_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
let active_format: *mut Object = unsafe { msg_send![self.inner, activeFormat] };
let mut controls = vec![];
let focus_current: NSInteger = unsafe { msg_send![self.inner, focusMode] };
let focus_locked: BOOL =
unsafe { msg_send![self.inner, isFocusModeSupported:NSInteger::from(0)] };
let focus_auto: BOOL =
unsafe { msg_send![self.inner, isFocusModeSupported:NSInteger::from(1)] };
let focus_continuous: BOOL =
unsafe { msg_send![self.inner, isFocusModeSupported:NSInteger::from(2)] };
{
let mut supported_focus_values = vec![];
if focus_locked == YES {
supported_focus_values.push(0)
}
if focus_auto == YES {
supported_focus_values.push(1)
}
if focus_continuous == YES {
supported_focus_values.push(2)
}
controls.push(CameraControl::new(
KnownCameraControl::Focus,
"FocusMode".to_string(),
ControlValueDescription::Enum {
value: focus_current,
possible: supported_focus_values,
default: focus_current,
},
vec![],
true,
));
}
let focus_poi_supported: BOOL =
unsafe { msg_send![self.inner, isFocusPointOfInterestSupported] };
let focus_poi: CGPoint = unsafe { msg_send![self.inner, focusPointOfInterest] };
controls.push(CameraControl::new(
KnownCameraControl::Other(0),
"FocusPointOfInterest".to_string(),
ControlValueDescription::Point {
value: (focus_poi.x as f64, focus_poi.y as f64),
default: (0.5, 0.5),
},
if focus_poi_supported == NO {
vec![
KnownCameraControlFlag::Disabled,
KnownCameraControlFlag::ReadOnly,
]
} else {
vec![]
},
focus_auto == YES || focus_continuous == YES,
));
let focus_manual: BOOL =
unsafe { msg_send![self.inner, isLockingFocusWithCustomLensPositionSupported] };
let focus_lenspos: f32 = unsafe { msg_send![self.inner, lensPosition] };
controls.push(CameraControl::new(
KnownCameraControl::Other(1),
"FocusManualLensPosition".to_string(),
ControlValueDescription::FloatRange {
min: 0.0,
max: 1.0,
value: focus_lenspos as f64,
step: f64::MIN_POSITIVE,
default: 1.0,
},
if focus_manual == YES {
vec![]
} else {
vec![
KnownCameraControlFlag::Disabled,
KnownCameraControlFlag::ReadOnly,
]
},
focus_manual == YES,
));
let exposure_current: NSInteger = unsafe { msg_send![self.inner, exposureMode] };
let exposure_locked: BOOL =
unsafe { msg_send![self.inner, isExposureModeSupported:NSInteger::from(0)] };
let exposure_auto: BOOL =
unsafe { msg_send![self.inner, isExposureModeSupported:NSInteger::from(1)] };
let exposure_continuous: BOOL =
unsafe { msg_send![self.inner, isExposureModeSupported:NSInteger::from(2)] };
let exposure_custom: BOOL =
unsafe { msg_send![self.inner, isExposureModeSupported:NSInteger::from(3)] };
{
let mut supported_exposure_values = vec![];
if exposure_locked == YES {
supported_exposure_values.push(0);
}
if exposure_auto == YES {
supported_exposure_values.push(1);
}
if exposure_continuous == YES {
supported_exposure_values.push(2);
}
if exposure_custom == YES {
supported_exposure_values.push(3);
}
controls.push(CameraControl::new(
KnownCameraControl::Exposure,
"ExposureMode".to_string(),
ControlValueDescription::Enum {
value: exposure_current,
possible: supported_exposure_values,
default: exposure_current,
},
vec![],
true,
));
}
let exposure_poi_supported: BOOL =
unsafe { msg_send![self.inner, isExposurePointOfInterestSupported] };
let exposure_poi: CGPoint = unsafe { msg_send![self.inner, exposurePointOfInterest] };
controls.push(CameraControl::new(
KnownCameraControl::Other(2),
"ExposurePointOfInterest".to_string(),
ControlValueDescription::Point {
value: (exposure_poi.x as f64, exposure_poi.y as f64),
default: (0.5, 0.5),
},
if exposure_poi_supported == NO {
vec![
KnownCameraControlFlag::Disabled,
KnownCameraControlFlag::ReadOnly,
]
} else {
vec![]
},
focus_auto == YES || focus_continuous == YES,
));
let expposure_face_driven_supported: BOOL =
unsafe { msg_send![self.inner, isFaceDrivenAutoExposureEnabled] };
let exposure_face_driven: BOOL = unsafe {
msg_send![
self.inner,
automaticallyAdjustsFaceDrivenAutoExposureEnabled
]
};
controls.push(CameraControl::new(
KnownCameraControl::Other(3),
"ExposureFaceDriven".to_string(),
ControlValueDescription::Boolean {
value: exposure_face_driven == YES,
default: false,
},
if expposure_face_driven_supported == NO {
vec![
KnownCameraControlFlag::Disabled,
KnownCameraControlFlag::ReadOnly,
]
} else {
vec![]
},
exposure_poi_supported == YES,
));
let exposure_bias: f32 = unsafe { msg_send![self.inner, exposureTargetBias] };
let exposure_bias_min: f32 = unsafe { msg_send![self.inner, minExposureTargetBias] };
let exposure_bias_max: f32 = unsafe { msg_send![self.inner, maxExposureTargetBias] };
controls.push(CameraControl::new(
KnownCameraControl::Other(4),
"ExposureBiasTarget".to_string(),
ControlValueDescription::FloatRange {
min: exposure_bias_min as f64,
max: exposure_bias_max as f64,
value: exposure_bias as f64,
step: f32::MIN_POSITIVE as f64,
default: unsafe { AVCaptureExposureTargetBiasCurrent } as f64,
},
vec![],
true,
));
let exposure_duration: CMTime = unsafe { msg_send![self.inner, exposureDuration] };
let exposure_duration_min: CMTime =
unsafe { msg_send![active_format, minExposureDuration] };
let exposure_duration_max: CMTime =
unsafe { msg_send![active_format, maxExposureDuration] };
controls.push(CameraControl::new(
KnownCameraControl::Gamma,
"ExposureDuration".to_string(),
ControlValueDescription::IntegerRange {
min: exposure_duration_min.value,
max: exposure_duration_max.value,
value: exposure_duration.value,
step: 1,
default: unsafe { AVCaptureExposureDurationCurrent.value },
},
if exposure_custom == YES {
vec![
KnownCameraControlFlag::ReadOnly,
KnownCameraControlFlag::Volatile,
]
} else {
vec![KnownCameraControlFlag::Volatile]
},
exposure_custom == YES,
));
let exposure_iso: f32 = unsafe { msg_send![self.inner, ISO] };
let exposure_iso_min: f32 = unsafe { msg_send![active_format, minISO] };
let exposure_iso_max: f32 = unsafe { msg_send![active_format, maxISO] };
controls.push(CameraControl::new(
KnownCameraControl::Brightness,
"ExposureISO".to_string(),
ControlValueDescription::FloatRange {
min: exposure_iso_min as f64,
max: exposure_iso_max as f64,
value: exposure_iso as f64,
step: f32::MIN_POSITIVE as f64,
default: unsafe { AVCaptureISOCurrent } as f64,
},
if exposure_custom == YES {
vec![
KnownCameraControlFlag::ReadOnly,
KnownCameraControlFlag::Volatile,
]
} else {
vec![KnownCameraControlFlag::Volatile]
},
exposure_custom == YES,
));
let lens_aperture: f32 = unsafe { msg_send![self.inner, lensAperture] };
controls.push(CameraControl::new(
KnownCameraControl::Iris,
"LensAperture".to_string(),
ControlValueDescription::Float {
value: lens_aperture as f64,
default: lens_aperture as f64,
step: lens_aperture as f64,
},
vec![KnownCameraControlFlag::ReadOnly],
false,
));
let white_balance_current: NSInteger =
unsafe { msg_send![self.inner, whiteBalanceMode] };
let white_balance_manual: BOOL =
unsafe { msg_send![self.inner, isWhiteBalanceModeSupported:NSInteger::from(0)] };
let white_balance_auto: BOOL =
unsafe { msg_send![self.inner, isWhiteBalanceModeSupported:NSInteger::from(1)] };
let white_balance_continuous: BOOL =
unsafe { msg_send![self.inner, isWhiteBalanceModeSupported:NSInteger::from(2)] };
{
let mut possible = vec![];
if white_balance_manual == YES {
possible.push(0);
}
if white_balance_auto == YES {
possible.push(1);
}
if white_balance_continuous == YES {
possible.push(2);
}
controls.push(CameraControl::new(
KnownCameraControl::WhiteBalance,
"WhiteBalanceMode".to_string(),
ControlValueDescription::Enum {
value: white_balance_current as i64,
possible,
default: 0,
},
vec![],
true,
));
}
let white_balance_gains: AVCaptureWhiteBalanceGains =
unsafe { msg_send![self.inner, deviceWhiteBalanceGains] };
let white_balance_default: AVCaptureWhiteBalanceGains =
unsafe { msg_send![self.inner, grayWorldDeviceWhiteBalanceGains] };
let white_balancne_max: AVCaptureWhiteBalanceGains =
unsafe { msg_send![self.inner, maxWhiteBalanceGain] };
let white_balance_gain_supported: BOOL = unsafe {
msg_send![
self.inner,
isLockingWhiteBalanceWithCustomDeviceGainsSupported
]
};
controls.push(CameraControl::new(
KnownCameraControl::Gain,
"WhiteBalanceGain".to_string(),
ControlValueDescription::RGB {
value: (
white_balance_gains.redGain as f64,
white_balance_gains.greenGain as f64,
white_balance_gains.blueGain as f64,
),
max: (
white_balancne_max.redGain as f64,
white_balancne_max.greenGain as f64,
white_balancne_max.blueGain as f64,
),
default: (
white_balance_default.redGain as f64,
white_balance_default.greenGain as f64,
white_balance_default.blueGain as f64,
),
},
if white_balance_gain_supported == YES {
vec![
KnownCameraControlFlag::Disabled,
KnownCameraControlFlag::ReadOnly,
]
} else {
vec![]
},
white_balance_gain_supported == YES,
));
let has_torch: BOOL = unsafe { msg_send![self.inner, isTorchAvailable] };
let torch_active: BOOL = unsafe { msg_send![self.inner, isTorchActive] };
let torch_off: BOOL =
unsafe { msg_send![self.inner, isTorchModeSupported:NSInteger::from(0)] };
let torch_on: BOOL =
unsafe { msg_send![self.inner, isTorchModeSupported:NSInteger::from(1)] };
let torch_auto: BOOL =
unsafe { msg_send![self.inner, isTorchModeSupported:NSInteger::from(2)] };
{
let mut possible = vec![];
if torch_off == YES {
possible.push(0);
}
if torch_on == YES {
possible.push(1);
}
if torch_auto == YES {
possible.push(2);
}
controls.push(CameraControl::new(
KnownCameraControl::Other(5),
"TorchMode".to_string(),
ControlValueDescription::Enum {
value: (torch_active == YES) as i64,
possible,
default: 0,
},
if has_torch == YES {
vec![
KnownCameraControlFlag::Disabled,
KnownCameraControlFlag::ReadOnly,
]
} else {
vec![]
},
has_torch == YES,
));
}
let has_llb: BOOL = unsafe { msg_send![self.inner, isLowLightBoostSupported] };
let llb_enabled: BOOL = unsafe { msg_send![self.inner, isLowLightBoostEnabled] };
{
controls.push(CameraControl::new(
KnownCameraControl::BacklightComp,
"LowLightCompensation".to_string(),
ControlValueDescription::Boolean {
value: llb_enabled == YES,
default: false,
},
if has_llb == NO {
vec![
KnownCameraControlFlag::Disabled,
KnownCameraControlFlag::ReadOnly,
]
} else {
vec![]
},
has_llb == YES,
));
}
let zoom_current: CGFloat = unsafe { msg_send![self.inner, videoZoomFactor] };
let zoom_min: CGFloat = unsafe { msg_send![self.inner, minAvailableVideoZoomFactor] };
let zoom_max: CGFloat = unsafe { msg_send![self.inner, maxAvailableVideoZoomFactor] };
controls.push(CameraControl::new(
KnownCameraControl::Zoom,
"Zoom".to_string(),
ControlValueDescription::FloatRange {
min: zoom_min as f64,
max: zoom_max as f64,
value: zoom_current as f64,
step: f32::MIN_POSITIVE as f64,
default: 1.0,
},
vec![],
true,
));
let distortion_correction_supported: BOOL =
unsafe { msg_send![self.inner, isGeometricDistortionCorrectionSupported] };
let distortion_correction_current_value: BOOL =
unsafe { msg_send![self.inner, isGeometricDistortionCorrectionEnabled] };
controls.push(CameraControl::new(
KnownCameraControl::Other(6),
"DistortionCorrection".to_string(),
ControlValueDescription::Boolean {
value: distortion_correction_current_value == YES,
default: false,
},
if distortion_correction_supported == YES {
vec![
KnownCameraControlFlag::ReadOnly,
KnownCameraControlFlag::Disabled,
]
} else {
vec![]
},
distortion_correction_supported == YES,
));
Ok(controls)
}
pub fn set_control(
&mut self,
id: KnownCameraControl,
value: ControlValueSetter,
) -> Result<(), NokhwaError> {
let rc = self.get_controls()?;
let controls = rc
.iter()
.map(|cc| (cc.control(), cc))
.collect::<BTreeMap<_, _>>();
match id {
KnownCameraControl::Brightness => {
let isoctrl = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if isoctrl.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error:
"Exposure is in improper state to set ISO (Please set to `custom`!)"
.to_string(),
});
}
if isoctrl.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let current_duration = unsafe { AVCaptureExposureDurationCurrent };
let new_iso = *value.as_float().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected float".to_string(),
})? as f32;
if !isoctrl.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe {
msg_send![self.inner, setExposureModeCustomWithDuration:current_duration ISO:new_iso completionHandler:Nil]
};
Ok(())
}
KnownCameraControl::Gamma => {
let duration_ctrl = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if duration_ctrl
.flag()
.contains(&KnownCameraControlFlag::ReadOnly)
{
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Exposure is in improper state to set Duration (Please set to `custom`!)"
.to_string(),
});
}
if duration_ctrl
.flag()
.contains(&KnownCameraControlFlag::Disabled)
{
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let current_duration: CMTime =
unsafe { msg_send![self.inner, exposureDuration] };
let current_iso = unsafe { AVCaptureISOCurrent };
let new_duration = CMTime {
value: *value.as_integer().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected i64".to_string(),
})?,
timescale: current_duration.timescale,
flags: current_duration.flags,
epoch: current_duration.epoch,
};
if !duration_ctrl.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe {
msg_send![self.inner, setExposureModeCustomWithDuration:new_duration ISO:current_iso completionHandler:Nil]
};
Ok(())
}
KnownCameraControl::WhiteBalance => {
let wb_enum_value = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if wb_enum_value
.flag()
.contains(&KnownCameraControlFlag::ReadOnly)
{
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if wb_enum_value
.flag()
.contains(&KnownCameraControlFlag::Disabled)
{
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter =
NSInteger::from(*value.as_enum().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Enum".to_string(),
})? as i32);
if !wb_enum_value.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe { msg_send![self.inner, whiteBalanceMode: setter] };
Ok(())
}
KnownCameraControl::BacklightComp => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter =
NSInteger::from(*value.as_enum().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Enum".to_string(),
})? as i32);
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe { msg_send![self.inner, whiteBalanceMode: setter] };
Ok(())
}
KnownCameraControl::Gain => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter = NSInteger::from(*value.as_boolean().ok_or(
NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Boolean".to_string(),
},
)? as i32);
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe { msg_send![self.inner, whiteBalanceMode: setter] };
Ok(())
}
KnownCameraControl::Zoom => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter = *value.as_float().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected float".to_string(),
})? as c_float;
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe {
msg_send![self.inner, rampToVideoZoomFactor: setter withRate: 1.0_f32]
};
Ok(())
}
KnownCameraControl::Exposure => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter =
NSInteger::from(*value.as_enum().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Enum".to_string(),
})? as i32);
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe { msg_send![self.inner, exposureMode: setter] };
Ok(())
}
KnownCameraControl::Iris => Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
}),
KnownCameraControl::Focus => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter =
NSInteger::from(*value.as_enum().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Enum".to_string(),
})? as i32);
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe { msg_send![self.inner, focusMode: setter] };
Ok(())
}
KnownCameraControl::Other(i) => match i {
0 => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter = value
.as_point()
.ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Point".to_string(),
})
.map(|(x, y)| CGPoint {
x: *x as f32,
y: *y as f32,
})?;
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe { msg_send![self.inner, focusPointOfInterest: setter] };
Ok(())
}
1 => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter = *value.as_float().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected float".to_string(),
})? as c_float;
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe {
msg_send![self.inner, setFocusModeLockedWithLensPosition: setter handler: Nil]
};
Ok(())
}
2 => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter = value
.as_point()
.ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Point".to_string(),
})
.map(|(x, y)| CGPoint {
x: *x as f32,
y: *y as f32,
})?;
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () =
unsafe { msg_send![self.inner, exposurePointOfInterest: setter] };
Ok(())
}
3 => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter =
if *value.as_boolean().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Boolean".to_string(),
})? {
YES
} else {
NO
};
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe {
msg_send![
self.inner,
automaticallyAdjustsFaceDrivenAutoExposureEnabled: setter
]
};
Ok(())
}
4 => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter = *value.as_float().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Float".to_string(),
})? as f32;
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe {
msg_send![self.inner, setExposureTargetBias: setter handler: Nil]
};
Ok(())
}
5 => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter = NSInteger::from(*value.as_enum().ok_or(
NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Enum".to_string(),
},
)? as i32);
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe { msg_send![self.inner, torchMode: setter] };
Ok(())
}
6 => {
let ctrlvalue = controls.get(&id).ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Control does not exist".to_string(),
})?;
if ctrlvalue.flag().contains(&KnownCameraControlFlag::ReadOnly) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Read Only".to_string(),
});
}
if ctrlvalue.flag().contains(&KnownCameraControlFlag::Disabled) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Disabled".to_string(),
});
}
let setter =
if *value.as_boolean().ok_or(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Expected Boolean".to_string(),
})? {
YES
} else {
NO
};
if !ctrlvalue.description().verify_setter(&value) {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Failed to verify value".to_string(),
});
}
let _: () = unsafe {
msg_send![self.inner, geometricDistortionCorrectionEnabled: setter]
};
Ok(())
}
_ => Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Unknown Control".to_string(),
}),
},
_ => Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Unknown Control".to_string(),
}),
}
}
pub fn active_format(&self) -> Result<CameraFormat, NokhwaError> {
let af: *mut Object = unsafe { msg_send![self.inner, activeFormat] };
let avf_format = AVCaptureDeviceFormat::try_from(af)?;
let resolution = avf_format.resolution;
let fourcc = avf_format.fourcc;
let mut a = avf_format
.fps_list
.into_iter()
.map(move |fps_f64| {
let fps = fps_f64 as u32;
let resolution =
Resolution::new(resolution.width as u32, resolution.height as u32); CameraFormat::new(resolution, fourcc, fps)
})
.collect::<Vec<_>>();
a.sort_by(|a, b| a.frame_rate().cmp(&b.frame_rate()));
if a.len() != 0 {
Ok(a[a.len() - 1])
} else {
Err(NokhwaError::GetPropertyError {
property: "activeFormat".to_string(),
error: "None??".to_string(),
})
}
}
}
impl AVCaptureDeviceInput {
pub fn new(capture_device: &AVCaptureDevice) -> Result<Self, NokhwaError> {
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(),
});
}
Ok(AVCaptureDeviceInput {
inner: capture_input,
})
}
}
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 _: () = msg_send![
self.inner,
setSampleBufferDelegate: delegate.delegate
queue: delegate.queue().0
];
};
Ok(())
}
}
impl Default for AVCaptureVideoDataOutput {
fn default() -> Self {
let cls = class!(AVCaptureVideoDataOutput);
let inner: *mut Object = unsafe { msg_send![cls, new] };
AVCaptureVideoDataOutput { inner }
}
}
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 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 }
}
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub use crate::internal::*;