diff --git a/Cargo.toml b/Cargo.toml index 8894e99..14dc55a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/l1npengtul/nokhwa" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] -members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "examples/*"] +members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "nokhwa-core", "examples/*"] exclude = ["examples/jscam"] [lib] @@ -42,8 +42,6 @@ test-fail-warning = [] [dependencies] thiserror = "1.0" paste = "1.0" -anymap = "1.0.0-beta.2" -enum_dispatch = "0.3" [dependencies.serde] version = "1.0" diff --git a/nokhwa-bindings-windows/Cargo.toml b/nokhwa-bindings-windows/Cargo.toml index aef8fa4..c327f35 100644 --- a/nokhwa-bindings-windows/Cargo.toml +++ b/nokhwa-bindings-windows/Cargo.toml @@ -13,10 +13,9 @@ keywords = ["media-foundation", "windows", "capture", "webcam"] [dependencies] thiserror = "^1.0" -[target.'cfg(all(target_os = "windows", windows))'.dependencies] +[target.'cfg(target_os = "windows")'.dependencies] lazy_static = "1.4.0" -[target.'cfg(all(target_os = "windows", windows))'.dependencies.windows] -version = "0.37.0" -features = ["alloc", "Win32_Media_MediaFoundation", "Win32_System_Com", "Win32_Foundation", "Win32_Media_DirectShow"] - +[target.'cfg(target_os = "windows")'.dependencies.windows] +version = "0.39.0" +features = ["Win32_Media_MediaFoundation", "Win32_System_Com", "Win32_Foundation", "Win32_Media_DirectShow", "Win32_Media", "Win32"] diff --git a/nokhwa-bindings-windows/src/lib.rs b/nokhwa-bindings-windows/src/lib.rs index 9522401..ebd0ab4 100644 --- a/nokhwa-bindings-windows/src/lib.rs +++ b/nokhwa-bindings-windows/src/lib.rs @@ -14,1883 +14,5 @@ * limitations under the License. */ -#![deny(clippy::pedantic)] -#![warn(clippy::all)] -#![allow(clippy::missing_errors_doc)] -#![allow(clippy::cast_possible_truncation)] -#![allow(clippy::too_many_lines)] - -//! # nokhwa-bindings-windows -//! This crate is the `MediaFoundation` bindings for the `nokhwa` crate. -//! -//! It is not meant for general consumption. If you are looking for a Windows camera capture crate, consider using `nokhwa` with feature `input-msmf`. -//! -//! No support or API stability will be given. Subject to change at any time. - -#[cfg(all(target_os = "windows", windows))] -#[macro_use] -extern crate lazy_static; - -use std::{ - borrow::{Borrow, Cow}, - cmp::Ordering, - slice::from_raw_parts, -}; -use thiserror::Error; - -#[allow(clippy::module_name_repetitions)] -#[derive(Error, Debug, Clone)] -pub enum BindingError { - #[error("Failed to initialize Media Foundation: {0}")] - InitializeError(String), - #[error("Failed to de-initialize Media Foundation: {0}")] - DeInitializeError(String), - #[error("Failed to set GUID {0} to {1}: {2}")] - GUIDSetError(String, String, String), - #[error("Failed to Read GUID {0}: {1}")] - GUIDReadError(String, String), - #[error("Attribute Error: {0}")] - AttributeError(String), - #[error("Failed to enumerate: {0}")] - EnumerateError(String), - #[error("Failed to open device {0}: {1}")] - DeviceOpenFailError(String, String), - #[error("Failed to read frame: {0}")] - ReadFrameError(String), - #[error("Not Implemented!")] - NotImplementedError, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct MFResolution { - pub width_x: u32, - pub height_y: u32, -} - -#[derive(Copy, Clone, Debug, PartialEq, Hash, PartialOrd, Ord, Eq)] -pub enum MFFrameFormat { - MJPEG, - YUYV, -} - -#[derive(Copy, Clone, Debug, Hash, PartialEq)] -pub struct MFCameraFormat { - resolution: MFResolution, - format: MFFrameFormat, - frame_rate: u32, -} - -impl Default for MFCameraFormat { - fn default() -> Self { - MFCameraFormat { - resolution: MFResolution { - width_x: 640, - height_y: 480, - }, - format: MFFrameFormat::MJPEG, - frame_rate: 15, - } - } -} - -impl MFCameraFormat { - #[must_use] - pub fn new(resolution: MFResolution, format: MFFrameFormat, frame_rate: u32) -> Self { - MFCameraFormat { - resolution, - format, - frame_rate, - } - } - - #[must_use] - pub fn new_from(res_x: u32, res_y: u32, format: MFFrameFormat, fps: u32) -> Self { - MFCameraFormat { - resolution: MFResolution { - width_x: res_x, - height_y: res_y, - }, - format, - frame_rate: fps, - } - } - - #[must_use] - pub fn resolution(&self) -> MFResolution { - self.resolution - } - - #[must_use] - pub fn width(&self) -> u32 { - self.resolution.width_x - } - - #[must_use] - pub fn height(&self) -> u32 { - self.resolution.height_y - } - - pub fn set_resolution(&mut self, resolution: MFResolution) { - self.resolution = resolution; - } - - #[must_use] - pub fn frame_rate(&self) -> u32 { - self.frame_rate - } - - pub fn set_frame_rate(&mut self, frame_rate: u32) { - self.frame_rate = frame_rate; - } - - #[must_use] - pub fn format(&self) -> MFFrameFormat { - self.format - } - - pub fn set_format(&mut self, format: MFFrameFormat) { - self.format = format; - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MediaFoundationDeviceDescriptor<'a> { - index: usize, - name: Cow<'a, [u16]>, - symlink: Cow<'a, [u16]>, -} - -impl<'a> MediaFoundationDeviceDescriptor<'a> { - /// # Errors - /// If name or symlink is a nullptr, this will error. - /// # Safety - /// name and symlink must not be null - pub unsafe fn new( - index: usize, - name: *mut u16, - symlink: *mut u16, - name_len: u32, - symlink_len: u32, - ) -> Result { - let name = if name.is_null() { - return Err(BindingError::AttributeError("name nullptr".to_string())); - } else { - Cow::from(from_raw_parts(name, name_len as usize)) - }; - - let symlink = if symlink.is_null() { - return Err(BindingError::AttributeError("symlink nullptr".to_string())); - } else { - Cow::from(from_raw_parts(symlink, symlink_len as usize)) - }; - - Ok(MediaFoundationDeviceDescriptor { - index, - name, - symlink, - }) - } - - #[must_use] - pub fn index(&self) -> usize { - self.index - } - - #[must_use] - pub fn name(&self) -> &Cow<[u16]> { - &self.name - } - - #[must_use] - pub fn symlink(&self) -> &Cow<[u16]> { - &self.symlink - } - - #[must_use] - pub fn name_as_string(&self) -> String { - String::from_utf16_lossy(self.name.borrow()) - } - - #[must_use] - pub fn link_as_string(&self) -> String { - String::from_utf16_lossy(self.symlink.borrow()) - } -} - -impl<'a> PartialOrd for MediaFoundationDeviceDescriptor<'a> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl<'a> Ord for MediaFoundationDeviceDescriptor<'a> { - fn cmp(&self, other: &Self) -> Ordering { - self.index.cmp(&other.index) - } -} - -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub enum MediaFoundationControls { - Brightness, - Contrast, - Hue, - Saturation, - Sharpness, - Gamma, - ColorEnable, - WhiteBalance, - BacklightComp, - Gain, - Pan, - Tilt, - Roll, - Zoom, - Exposure, - Iris, - Focus, -} - -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct MFControl { - control: MediaFoundationControls, - min: i32, - max: i32, - step: i32, - current: i32, - default: i32, - manual: bool, - active: bool, -} - -impl MFControl { - #[allow(clippy::too_many_arguments)] - #[must_use] - pub fn new( - control: MediaFoundationControls, - min: i32, - max: i32, - step: i32, - current: i32, - default: i32, - manual: bool, - active: bool, - ) -> Self { - MFControl { - control, - min, - max, - step, - current, - default, - manual, - active, - } - } - - #[must_use] - pub fn control(&self) -> MediaFoundationControls { - self.control - } - - pub fn set_control(&mut self, control: MediaFoundationControls) { - self.control = control; - } - - #[must_use] - pub fn min(&self) -> i32 { - self.min - } - - pub fn set_min(&mut self, min: i32) { - self.min = min; - } - - #[must_use] - pub fn max(&self) -> i32 { - self.max - } - - pub fn set_max(&mut self, max: i32) { - self.max = max; - } - - #[must_use] - pub fn step(&self) -> i32 { - self.step - } - - pub fn set_step(&mut self, step: i32) { - self.step = step; - } - - #[must_use] - pub fn current(&self) -> i32 { - self.current - } - - pub fn set_current(&mut self, current: i32) { - self.current = current; - } - #[must_use] - pub fn default(&self) -> i32 { - self.default - } - - pub fn set_default(&mut self, default: i32) { - self.default = default; - } - - #[must_use] - pub fn manual(&self) -> bool { - self.manual - } - - pub fn set_manual(&mut self, manual: bool) { - self.manual = manual; - } - - #[must_use] - pub fn active(&self) -> bool { - self.active - } - - pub fn set_active(&mut self, active: bool) { - self.active = active; - } -} - -#[cfg(all(windows, not(feature = "docs-only")))] -pub mod wmf { - #![windows_subsystem = "windows"] - use crate::{ - BindingError, MFCameraFormat, MFControl, MFFrameFormat, MFResolution, - MediaFoundationControls, MediaFoundationDeviceDescriptor, - }; - use std::{ - borrow::Cow, - cell::Cell, - mem::MaybeUninit, - slice::from_raw_parts, - sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, - Arc, - }, - }; - use windows::{ - core::{Interface, GUID}, - Win32::{ - Foundation::PWSTR, - Media::{ - DirectShow::{ - CameraControl_Exposure, CameraControl_Focus, CameraControl_Iris, - CameraControl_Pan, CameraControl_Roll, CameraControl_Tilt, CameraControl_Zoom, - IAMCameraControl, IAMVideoProcAmp, VideoProcAmp_BacklightCompensation, - VideoProcAmp_Brightness, VideoProcAmp_ColorEnable, VideoProcAmp_Contrast, - VideoProcAmp_Gain, VideoProcAmp_Gamma, VideoProcAmp_Hue, - VideoProcAmp_Saturation, VideoProcAmp_Sharpness, VideoProcAmp_WhiteBalance, - }, - MediaFoundation::{ - IMFActivate, IMFAttributes, IMFMediaSource, IMFSample, IMFSourceReader, - MFCreateAttributes, MFCreateMediaType, MFCreateSourceReaderFromMediaSource, - MFEnumDeviceSources, MFMediaType_Video, MFShutdown, MFStartup, - MFSTARTUP_NOSOCKET, MF_API_VERSION, MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, - MF_MEDIASOURCE_SERVICE, MF_MT_FRAME_RATE, MF_MT_FRAME_RATE_RANGE_MAX, - MF_MT_FRAME_RATE_RANGE_MIN, MF_MT_FRAME_SIZE, MF_MT_MAJOR_TYPE, MF_MT_SUBTYPE, - MF_READWRITE_DISABLE_CONVERTERS, - }, - }, - System::Com::{CoInitializeEx, CoUninitialize, COINIT}, - }, - }; - - lazy_static! { - static ref INITIALIZED: Arc = Arc::new(AtomicBool::new(false)); - static ref CAMERA_REFCNT: Arc = Arc::new(AtomicUsize::new(0)); - } - - // See: https://stackoverflow.com/questions/80160/what-does-coinit-speed-over-memory-do - const CO_INIT_APARTMENT_THREADED: COINIT = COINIT(0x2); - const CO_INIT_DISABLE_OLE1DDE: COINIT = COINIT(0x4); - - // See: https://gix.github.io/media-types/#major-types - const MF_VIDEO_FORMAT_YUY2: GUID = GUID::from_values( - 0x3259_5559, - 0x0000, - 0x0010, - [0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71], - ); - const MF_VIDEO_FORMAT_MJPEG: GUID = GUID::from_values( - 0x4750_4A4D, - 0x0000, - 0x0010, - [0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71], - ); - - const MEDIA_FOUNDATION_FIRST_VIDEO_STREAM: u32 = 0xFFFF_FFFC; - - const CAM_CTRL_AUTO: i32 = 0x0001; - const CAM_CTRL_MANUAL: i32 = 0x0002; - - pub fn initialize_mf() -> Result<(), BindingError> { - if !(INITIALIZED.load(Ordering::SeqCst)) { - let a = std::ptr::null_mut(); - if let Err(why) = - unsafe { CoInitializeEx(a, CO_INIT_APARTMENT_THREADED | CO_INIT_DISABLE_OLE1DDE) } - { - return Err(BindingError::InitializeError(why.to_string())); - } - - if let Err(why) = unsafe { MFStartup(MF_API_VERSION, MFSTARTUP_NOSOCKET) } { - unsafe { - CoUninitialize(); - } - return Err(BindingError::InitializeError(why.to_string())); - } - INITIALIZED.store(true, Ordering::SeqCst); - } - Ok(()) - } - - pub fn de_initialize_mf() -> Result<(), BindingError> { - if INITIALIZED.load(Ordering::SeqCst) { - unsafe { - if let Err(why) = MFShutdown() { - return Err(BindingError::DeInitializeError(why.to_string())); - } - CoUninitialize(); - INITIALIZED.store(false, Ordering::SeqCst); - } - } - Ok(()) - } - - fn query_activate_pointers() -> Result, BindingError> { - initialize_mf()?; - - let mut attributes: Option = None; - if let Err(why) = unsafe { MFCreateAttributes(&mut attributes, 1) } { - return Err(BindingError::AttributeError(why.to_string())); - } - - let attributes = match attributes { - Some(attr) => { - if let Err(why) = unsafe { - attr.SetGUID( - &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, - &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID, - ) - } { - return Err(BindingError::AttributeError(why.to_string())); - } - attr - } - None => { - return Err(BindingError::AttributeError( - "Attributes is null!".to_string(), - )); - } - }; - - let mut count: u32 = 0; - let mut unused_mf_activate: MaybeUninit<*mut Option> = MaybeUninit::uninit(); // WTF? - - if let Err(why) = - unsafe { MFEnumDeviceSources(attributes, unused_mf_activate.as_mut_ptr(), &mut count) } - { - return Err(BindingError::EnumerateError(why.to_string())); - } - - let mut device_list = vec![]; - - unsafe { from_raw_parts(unused_mf_activate.assume_init(), count as usize) } - .iter() - .for_each(|pointer| { - if let Some(imf_activate) = pointer { - device_list.push(imf_activate.clone()); - } - }); - - Ok(device_list) - } - - fn activate_to_descriptors( - index: usize, - imf_activate: &IMFActivate, - ) -> Result, BindingError> { - let mut name: PWSTR = PWSTR(&mut 0_u16); - let mut len_name = 0; - let mut symlink: PWSTR = PWSTR(&mut 0_u16); - let mut len_symlink = 0; - - if let Err(why) = unsafe { - imf_activate.GetAllocatedString( - &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, - &mut name, - &mut len_name, - ) - } { - return Err(BindingError::GUIDReadError( - "MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - imf_activate.GetAllocatedString( - &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, - &mut symlink, - &mut len_symlink, - ) - } { - return Err(BindingError::GUIDReadError( - "MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK".to_string(), - why.to_string(), - )); - } - - unsafe { - MediaFoundationDeviceDescriptor::new(index, name.0, symlink.0, len_name, len_symlink) - } - } - - pub fn query_media_foundation_descriptors( - ) -> Result>, BindingError> { - let mut device_list = vec![]; - - for (index, activate_ptr) in query_activate_pointers()?.into_iter().enumerate() { - device_list.push(activate_to_descriptors(index, &activate_ptr)?); - } - Ok(device_list) - } - - pub struct MediaFoundationDevice<'a> { - is_open: Cell, - device_specifier: MediaFoundationDeviceDescriptor<'a>, - device_format: MFCameraFormat, - source_reader: IMFSourceReader, - } - - impl<'a> MediaFoundationDevice<'a> { - pub fn new(index: usize) -> Result { - let (media_source, device_descriptor) = match query_activate_pointers()? - .into_iter() - .nth(index) - { - Some(activate) => match unsafe { activate.ActivateObject::() } { - Ok(media_source) => (media_source, activate_to_descriptors(index, &activate)?), - Err(why) => { - return Err(BindingError::DeviceOpenFailError( - index.to_string(), - why.to_string(), - )) - } - }, - None => { - return Err(BindingError::DeviceOpenFailError( - index.to_string(), - "Not Found".to_string(), - )) - } - }; - - let source_reader_attr: Option = { - let attr = match { - let mut attr: Option = None; - - if let Err(why) = unsafe { MFCreateAttributes(&mut attr, 3) } { - return Err(BindingError::AttributeError(why.to_string())); - } - - attr - } { - Some(imf_attr) => imf_attr, - None => { - return Err(BindingError::AttributeError( - "Attribute Alloc Fail".to_string(), - )) - } - }; - - if let Err(why) = - unsafe { attr.SetUINT32(&MF_READWRITE_DISABLE_CONVERTERS, true as u32) } - { - return Err(BindingError::AttributeError(why.to_string())); - } - - Some(attr) - }; - - let source_reader = match unsafe { - MFCreateSourceReaderFromMediaSource(&media_source, source_reader_attr) - } { - Ok(sr) => sr, - Err(why) => { - return Err(BindingError::DeviceOpenFailError( - index.to_string(), - why.to_string(), - )) - } - }; - - // increment refcnt - CAMERA_REFCNT.store(CAMERA_REFCNT.load(Ordering::SeqCst) + 1, Ordering::SeqCst); - - Ok(MediaFoundationDevice { - is_open: Cell::new(false), - device_specifier: device_descriptor, - device_format: MFCameraFormat::default(), - source_reader, - }) - } - - pub fn with_string(unique_id: &[u16]) -> Result { - let devicelist = query_media_foundation_descriptors()?; - let mut id_eq = None; - - for mfdev in devicelist { - if (mfdev.symlink() as &[u16]) == unique_id { - id_eq = Some(mfdev.index); - break; - } - } - - match id_eq { - Some(index) => Self::new(index), - None => { - return Err(BindingError::DeviceOpenFailError( - std::str::from_utf8( - &unique_id.iter().map(|x| *x as u8).collect::>(), - ) - .unwrap_or("") - .to_string(), - "Not Found".to_string(), - )) - } - } - } - - pub fn index(&self) -> usize { - self.device_specifier.index - } - - pub fn name(&self) -> String { - self.device_specifier.name_as_string() - } - - pub fn symlink(&self) -> String { - self.device_specifier.link_as_string() - } - - pub fn compatible_format_list(&mut self) -> Result, BindingError> { - let mut camera_format_list = vec![]; - let mut index = 0; - - while let Ok(media_type) = unsafe { - self.source_reader - .GetNativeMediaType(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, index) - } { - let fourcc = match unsafe { media_type.GetGUID(&MF_MT_SUBTYPE) } { - Ok(fcc) => fcc, - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_SUBTYPE".to_string(), - why.to_string(), - )) - } - }; - - let (width, height) = match unsafe { media_type.GetUINT64(&MF_MT_FRAME_SIZE) } { - Ok(res_u64) => { - let width = (res_u64 >> 32) as u32; - let height = res_u64 as u32; // the cast will truncate the upper bits - (width, height) - } - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_FRAME_SIZE".to_string(), - why.to_string(), - )) - } - }; - - // MFRatio is represented as 2 u32s in memory. This means we cann convert it to 2 - let frame_rate_max = - match unsafe { media_type.GetUINT64(&MF_MT_FRAME_RATE_RANGE_MAX) } { - Ok(fraction_u64) => { - let mut numerator = (fraction_u64 >> 32) as u32; - let denominator = fraction_u64 as u32; - if denominator != 1 { - numerator = 0; - } - numerator - } - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_FRAME_RATE_RANGE_MAX".to_string(), - why.to_string(), - )) - } - }; - - let frame_rate = match unsafe { media_type.GetUINT64(&MF_MT_FRAME_RATE) } { - Ok(fraction_u64) => { - let mut numerator = (fraction_u64 >> 32) as u32; - let denominator = fraction_u64 as u32; - if denominator != 1 { - numerator = 0; - } - numerator - } - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_FRAME_RATE".to_string(), - why.to_string(), - )) - } - }; - - let frame_rate_min = - match unsafe { media_type.GetUINT64(&MF_MT_FRAME_RATE_RANGE_MIN) } { - Ok(fraction_u64) => { - let mut numerator = (fraction_u64 >> 32) as u32; - let denominator = fraction_u64 as u32; - if denominator != 1 { - numerator = 0; - } - numerator - } - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_FRAME_RATE_RANGE_MIN".to_string(), - why.to_string(), - )) - } - }; - - if fourcc == MF_VIDEO_FORMAT_MJPEG { - if frame_rate_min != 0 { - camera_format_list.push(MFCameraFormat { - resolution: MFResolution { - width_x: width, - height_y: height, - }, - format: MFFrameFormat::MJPEG, - frame_rate: frame_rate_min, - }); - } - - if frame_rate != 0 && frame_rate_min != frame_rate { - camera_format_list.push(MFCameraFormat { - resolution: MFResolution { - width_x: width, - height_y: height, - }, - format: MFFrameFormat::MJPEG, - frame_rate, - }); - } - - if frame_rate_max != 0 && frame_rate != frame_rate_max { - camera_format_list.push(MFCameraFormat { - resolution: MFResolution { - width_x: width, - height_y: height, - }, - format: MFFrameFormat::MJPEG, - frame_rate: frame_rate_max, - }); - } - } else if fourcc == MF_VIDEO_FORMAT_YUY2 { - if frame_rate_min != 0 { - camera_format_list.push(MFCameraFormat { - resolution: MFResolution { - width_x: width, - height_y: height, - }, - format: MFFrameFormat::YUYV, - frame_rate: frame_rate_min, - }); - } - - if frame_rate != 0 && frame_rate_min != frame_rate { - camera_format_list.push(MFCameraFormat { - resolution: MFResolution { - width_x: width, - height_y: height, - }, - format: MFFrameFormat::YUYV, - frame_rate, - }); - } - - if frame_rate_max != 0 && frame_rate != frame_rate_max { - camera_format_list.push(MFCameraFormat { - resolution: MFResolution { - width_x: width, - height_y: height, - }, - format: MFFrameFormat::YUYV, - frame_rate: frame_rate_max, - }); - } - } - - index += 1; - } - Ok(camera_format_list) - } - - pub fn control(&self, control: MediaFoundationControls) -> Result { - let media_source = unsafe { - let mut receiver: MaybeUninit = MaybeUninit::uninit(); - let mut ptr_receiver = receiver.as_mut_ptr(); - if let Err(why) = self.source_reader.GetServiceForStream( - MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, - &MF_MEDIASOURCE_SERVICE, - &IMFMediaSource::IID, - (&mut ptr_receiver as *mut *mut IMFMediaSource).cast::<*mut std::ffi::c_void>(), - ) { - return Err(BindingError::GUIDSetError( - "MEDIA_FOUNDATION_FIRST_VIDEO_STREAM".to_string(), - "MF_MEDIASOURCE_SERVICE".to_string(), - why.to_string(), - )); - } - receiver.assume_init() - }; - - let camera_control = match media_source.cast::() { - Ok(cc) => cc, - Err(why) => { - return Err(BindingError::GUIDReadError( - "IAMCameraControl".to_string(), - why.to_string(), - )) - } - }; - - let video_proc_amp = match media_source.cast::() { - Ok(vpa) => vpa, - Err(why) => { - return Err(BindingError::GUIDReadError( - "IAMVideoProcAmp".to_string(), - why.to_string(), - )) - } - }; - - let mut min = 0; - let mut max = 0; - let mut step = 0; - let mut default = 0; - let mut value = 0; - let mut flag = 0; - - match control { - MediaFoundationControls::Brightness => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_Brightness.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Brightness-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - video_proc_amp.Get(VideoProcAmp_Brightness.0, &mut value, &mut flag) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Brightness-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Contrast => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_Contrast.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Contrast-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - video_proc_amp.Get(VideoProcAmp_Contrast.0, &mut value, &mut flag) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Contrast-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Hue => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_Hue.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Hue-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { video_proc_amp.Get(VideoProcAmp_Hue.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Hue-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Saturation => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_Saturation.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Saturation-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - video_proc_amp.Get(VideoProcAmp_Saturation.0, &mut value, &mut flag) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Saturation-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Sharpness => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_Sharpness.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Sharpness-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - video_proc_amp.Get(VideoProcAmp_Sharpness.0, &mut value, &mut flag) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Sharpness-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Gamma => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_Gamma.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Gamma-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { video_proc_amp.Get(VideoProcAmp_Gamma.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Gamma-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::ColorEnable => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_ColorEnable.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_ColorEnable-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - video_proc_amp.Get(VideoProcAmp_ColorEnable.0, &mut value, &mut flag) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_ColorEnable-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::WhiteBalance => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_WhiteBalance.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_WhiteBalance-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - video_proc_amp.Get(VideoProcAmp_WhiteBalance.0, &mut value, &mut flag) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_WhiteBalance-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::BacklightComp => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_BacklightCompensation.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_BacklightCompensation-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - video_proc_amp.Get( - VideoProcAmp_BacklightCompensation.0, - &mut value, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_BacklightCompensation-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Gain => { - if let Err(why) = unsafe { - video_proc_amp.GetRange( - VideoProcAmp_Gain.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Gain-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { video_proc_amp.Get(VideoProcAmp_Gain.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "VideoProcAmp_Gain-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Pan => { - if let Err(why) = unsafe { - camera_control.GetRange( - CameraControl_Pan.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "CameraControl_Pan-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { camera_control.Get(CameraControl_Pan.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "CameraControl_Pan-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Tilt => { - if let Err(why) = unsafe { - camera_control.GetRange( - CameraControl_Tilt.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "CameraControl_Tilt-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { camera_control.Get(CameraControl_Tilt.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "CameraControl_Tilt-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Roll => { - if let Err(why) = unsafe { - camera_control.GetRange( - CameraControl_Roll.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "CameraControl_Roll-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { camera_control.Get(CameraControl_Roll.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "CameraControl_Roll-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Zoom => { - if let Err(why) = unsafe { - camera_control.GetRange( - CameraControl_Zoom.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "CameraControl_Zoom-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { camera_control.Get(CameraControl_Zoom.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "CameraControl_Zoom-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Exposure => { - if let Err(why) = unsafe { - camera_control.GetRange( - CameraControl_Exposure.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "CameraControl_Exposure-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = unsafe { - camera_control.Get(CameraControl_Exposure.0, &mut value, &mut flag) - } { - return Err(BindingError::GUIDReadError( - "CameraControl_Exposure-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Iris => { - if let Err(why) = unsafe { - camera_control.GetRange( - CameraControl_Iris.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "CameraControl_Iris-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { camera_control.Get(CameraControl_Iris.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "CameraControl_Iris-Value".to_string(), - why.to_string(), - )); - } - } - MediaFoundationControls::Focus => { - if let Err(why) = unsafe { - camera_control.GetRange( - CameraControl_Focus.0, - &mut min, - &mut max, - &mut step, - &mut default, - &mut flag, - ) - } { - return Err(BindingError::GUIDReadError( - "CameraControl_Focus-Range".to_string(), - why.to_string(), - )); - } - - if let Err(why) = - unsafe { camera_control.Get(CameraControl_Focus.0, &mut value, &mut flag) } - { - return Err(BindingError::GUIDReadError( - "CameraControl_Focus-Value".to_string(), - why.to_string(), - )); - } - } - } - - let is_manual = matches!(flag, CAM_CTRL_MANUAL); - - Ok(MFControl::new( - control, min, max, step, value, default, is_manual, true, - )) - } - - pub fn set_control(&mut self, control: MFControl) -> Result<(), BindingError> { - let media_source = unsafe { - let mut receiver: MaybeUninit = MaybeUninit::uninit(); - let mut ptr_receiver = receiver.as_mut_ptr(); - if let Err(why) = self.source_reader.GetServiceForStream( - MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, - &MF_MEDIASOURCE_SERVICE, - &IMFMediaSource::IID, - (&mut ptr_receiver as *mut *mut IMFMediaSource).cast::<*mut std::ffi::c_void>(), - ) { - return Err(BindingError::GUIDSetError( - "MEDIA_FOUNDATION_FIRST_VIDEO_STREAM".to_string(), - "MF_MEDIASOURCE_SERVICE".to_string(), - why.to_string(), - )); - } - receiver.assume_init() - }; - - let camera_control = match media_source.cast::() { - Ok(cc) => cc, - Err(why) => { - return Err(BindingError::GUIDReadError( - "IAMCameraControl".to_string(), - why.to_string(), - )) - } - }; - - let video_proc_amp = match media_source.cast::() { - Ok(vpa) => vpa, - Err(why) => { - return Err(BindingError::GUIDReadError( - "IAMVideoProcAmp".to_string(), - why.to_string(), - )) - } - }; - - let value = control.current; - let flags = if control.manual { - CAM_CTRL_MANUAL - } else { - CAM_CTRL_AUTO - }; - let flag_str = if control.manual { - "CAM_CTRL_MANUAL" - } else { - "CAM_CTRL_AUTO" - }; - - match control.control { - MediaFoundationControls::Brightness => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_Brightness.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_Brightness".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Contrast => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_Contrast.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_Contrast".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Hue => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_Hue.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_Hue".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Saturation => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_Saturation.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_Saturation".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Sharpness => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_Sharpness.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_Sharpness".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Gamma => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_Gamma.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_Gamma".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::ColorEnable => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_ColorEnable.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_ColorEnable".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::WhiteBalance => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_WhiteBalance.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_WhiteBalance".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::BacklightComp => { - if let Err(why) = unsafe { - video_proc_amp.Set(VideoProcAmp_BacklightCompensation.0, value, flags) - } { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_BacklightCompensation".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Gain => { - if let Err(why) = - unsafe { video_proc_amp.Set(VideoProcAmp_Gain.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "VideoProcAmp_Gain".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Pan => { - if let Err(why) = - unsafe { camera_control.Set(CameraControl_Pan.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "CameraControl_Pan".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Tilt => { - if let Err(why) = - unsafe { camera_control.Set(CameraControl_Tilt.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "CameraControl_Tilt".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Roll => { - if let Err(why) = - unsafe { camera_control.Set(CameraControl_Roll.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "CameraControl_Roll".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Zoom => { - if let Err(why) = - unsafe { camera_control.Set(CameraControl_Zoom.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "CameraControl_Zoom".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Exposure => { - if let Err(why) = - unsafe { camera_control.Set(CameraControl_Exposure.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "CameraControl_Exposure".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Iris => { - if let Err(why) = - unsafe { camera_control.Set(CameraControl_Iris.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "CameraControl_Iris".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - MediaFoundationControls::Focus => { - if let Err(why) = - unsafe { camera_control.Set(CameraControl_Focus.0, value, flags) } - { - return Err(BindingError::GUIDSetError( - "CameraControl_Focus".to_string(), - format!("{} {}", value, flag_str), - why.to_string(), - )); - } - } - } - - Ok(()) - } - - pub fn format(&self) -> MFCameraFormat { - self.device_format - } - - pub fn set_format(&mut self, format: MFCameraFormat) -> Result<(), BindingError> { - // convert to media_type - let media_type = match unsafe { MFCreateMediaType() } { - Ok(mt) => mt, - Err(why) => return Err(BindingError::AttributeError(why.to_string())), - }; - - // set relevant things - let resolution = (u64::from(format.resolution.width_x) << 32_u64) - + u64::from(format.resolution.height_y); - let fps = { - let frame_rate_u64 = 0_u64; - let mut bytes: [u8; 8] = frame_rate_u64.to_le_bytes(); - bytes[7] = format.frame_rate as u8; - bytes[3] = 0x01; - println!("{:?}", bytes); - u64::from_le_bytes(bytes) - }; - let fourcc = match format.format { - MFFrameFormat::MJPEG => MF_VIDEO_FORMAT_MJPEG, - MFFrameFormat::YUYV => MF_VIDEO_FORMAT_YUY2, - }; - // setting to the new media_type - if let Err(why) = unsafe { media_type.SetGUID(&MF_MT_MAJOR_TYPE, &MFMediaType_Video) } { - return Err(BindingError::GUIDSetError( - "MF_MT_MAJOR_TYPE".to_string(), - "MFMediaType_Video".to_string(), - why.to_string(), - )); - } - if let Err(why) = unsafe { media_type.SetGUID(&MF_MT_SUBTYPE, &fourcc) } { - return Err(BindingError::GUIDSetError( - "MF_MT_SUBTYPE".to_string(), - format!("{:?}", fourcc), - why.to_string(), - )); - } - if let Err(why) = unsafe { media_type.SetUINT64(&MF_MT_FRAME_SIZE, resolution) } { - return Err(BindingError::GUIDSetError( - "MF_MT_FRAME_SIZE".to_string(), - resolution.to_string(), - why.to_string(), - )); - } - if let Err(why) = unsafe { media_type.SetUINT64(&MF_MT_FRAME_RATE, fps) } { - return Err(BindingError::GUIDSetError( - "MF_MT_FRAME_RATE".to_string(), - fps.to_string(), - why.to_string(), - )); - } - if let Err(why) = unsafe { media_type.SetUINT64(&MF_MT_FRAME_RATE_RANGE_MIN, fps) } { - return Err(BindingError::GUIDSetError( - "MF_MT_FRAME_RATE_RANGE_MIN".to_string(), - fps.to_string(), - why.to_string(), - )); - } - if let Err(why) = unsafe { media_type.SetUINT64(&MF_MT_FRAME_RATE_RANGE_MAX, fps) } { - return Err(BindingError::GUIDSetError( - "MF_MT_FRAME_RATE_RANGE_MAX".to_string(), - fps.to_string(), - why.to_string(), - )); - } - - let reserved = std::ptr::null_mut(); - if let Err(why) = unsafe { - self.source_reader.SetCurrentMediaType( - MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, - reserved, - media_type.clone(), - ) - } { - return Err(BindingError::GUIDSetError( - "MF_SOURCE_READER_FIRST_VIDEO_STREAM".to_string(), - format!("{:?}", media_type), - why.to_string(), - )); - } - self.device_format = format; - Ok(()) - } - - pub fn is_stream_open(&self) -> bool { - self.is_open.get() - } - - pub fn start_stream(&mut self) -> Result<(), BindingError> { - if let Err(why) = unsafe { - self.source_reader - .SetStreamSelection(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, true) - } { - println!("a"); - return Err(BindingError::ReadFrameError(why.to_string())); - } - - self.is_open.set(true); - Ok(()) - } - - pub fn raw_bytes(&mut self) -> Result, BindingError> { - let mut flags: u32 = 0; - let mut imf_sample: Option = None; - - { - loop { - if let Err(why) = unsafe { - self.source_reader.ReadSample( - MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, - 0, - std::ptr::null_mut(), - &mut flags, - std::ptr::null_mut(), - &mut imf_sample, - ) - } { - return Err(BindingError::ReadFrameError(why.to_string())); - } - - if imf_sample.is_some() { - break; - } - } - } - - let imf_sample = match imf_sample { - Some(sample) => sample, - None => { - // shouldn't happen - return Err(BindingError::ReadFrameError("why".to_string())); - } - }; - - let buffer = match unsafe { imf_sample.ConvertToContiguousBuffer() } { - Ok(buf) => buf, - Err(why) => return Err(BindingError::ReadFrameError(why.to_string())), - }; - - let mut buffer_valid_length = 0; - let mut buffer_start_ptr = std::ptr::null_mut::(); - - if let Err(why) = unsafe { - buffer.Lock( - &mut buffer_start_ptr, - std::ptr::null_mut(), - &mut buffer_valid_length, - ) - } { - return Err(BindingError::ReadFrameError(why.to_string())); - } - - if buffer_start_ptr.is_null() { - return Err(BindingError::ReadFrameError( - "Buffer Pointer Null".to_string(), - )); - } - - if buffer_valid_length == 0 { - return Err(BindingError::ReadFrameError("No Data Size".to_string())); - } - - let mut data_slice = Vec::with_capacity(buffer_valid_length as usize); - - unsafe { - // Copy pointer because we're bout to drop IMFSample - data_slice.extend_from_slice(std::slice::from_raw_parts_mut( - buffer_start_ptr, - buffer_valid_length as usize, - ) as &[u8]); - // swallow errors - if buffer - .Lock( - &mut buffer_start_ptr, - std::ptr::null_mut(), - &mut buffer_valid_length, - ) - .is_ok() - {} - } - - Ok(Cow::from(data_slice)) - } - - pub fn stop_stream(&mut self) { - self.is_open.set(false); - } - } - - impl<'a> Drop for MediaFoundationDevice<'a> { - fn drop(&mut self) { - // swallow errors - unsafe { - if self - .source_reader - .Flush(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM) - .is_ok() - {} - - // decrement refcnt - if CAMERA_REFCNT.load(Ordering::SeqCst) > 0 { - CAMERA_REFCNT.store(CAMERA_REFCNT.load(Ordering::SeqCst) - 1, Ordering::SeqCst); - } - if CAMERA_REFCNT.load(Ordering::SeqCst) == 0 { - #[allow(clippy::let_underscore_drop)] - let _ = de_initialize_mf(); - } - } - } - } -} - -#[cfg(any(not(windows), feature = "docs-only"))] -#[allow(clippy::missing_errors_doc)] -#[allow(clippy::unused_self)] -pub mod wmf { - use crate::{ - BindingError, MFCameraFormat, MFControl, MediaFoundationControls, - MediaFoundationDeviceDescriptor, - }; - use std::borrow::Cow; - - pub fn initialize_mf() -> Result<(), BindingError> { - Err(BindingError::NotImplementedError) - } - - pub fn de_initialize_mf() -> Result<(), BindingError> { - Err(BindingError::NotImplementedError) - } - - pub fn query_msmf() -> Result>, BindingError> { - Err(BindingError::NotImplementedError) - } - - struct Empty(); - - pub struct MediaFoundationDevice<'a> { - phantom: &'a Empty, - } - - impl<'a> MediaFoundationDevice<'a> { - pub fn new(_: usize, _: MFCameraFormat) -> Result { - Ok(MediaFoundationDevice { phantom: &Empty() }) - } - - pub fn index(&self) -> usize { - usize::MAX - } - - pub fn name(&self) -> String { - "".to_string() - } - - pub fn symlink(&self) -> String { - "".to_string() - } - - pub fn compatible_format_list(&mut self) -> Result, BindingError> { - Err(BindingError::NotImplementedError) - } - - pub fn control( - &self, - _control: MediaFoundationControls, - ) -> Result { - Err(BindingError::NotImplementedError) - } - - pub fn set_control(&mut self, _control: MFControl) -> Result<(), BindingError> { - Err(BindingError::NotImplementedError) - } - - pub fn format(&self) -> MFCameraFormat { - MFCameraFormat::default() - } - - pub fn set_format(&mut self, _format: MFCameraFormat) -> Result<(), BindingError> { - Err(BindingError::NotImplementedError) - } - - pub fn is_stream_open(&self) -> bool { - false - } - - pub fn start_stream(&mut self) -> Result<(), BindingError> { - Err(BindingError::NotImplementedError) - } - - pub fn raw_bytes(&mut self) -> Result, BindingError> { - Err(BindingError::NotImplementedError) - } - - pub fn stop_stream(&mut self) { - self.phantom = &Empty(); - } - } - - impl<'a> Drop for MediaFoundationDevice<'a> { - fn drop(&mut self) {} - } -} +pub mod traits; +pub mod types; diff --git a/nokhwa-bindings-windows/src/traits.rs b/nokhwa-bindings-windows/src/traits.rs new file mode 100644 index 0000000..7c566f9 --- /dev/null +++ b/nokhwa-bindings-windows/src/traits.rs @@ -0,0 +1,247 @@ +/* + * Copyright 2022 l1npengtul / The Nokhwa Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ + error::NokhwaError, + utils::{CameraFormat, CameraInfo, FrameFormat, Resolution}, + Buffer, CameraControl, CameraIndex, ControlValueSetter, KnownCameraControl, RequestedFormat, +}; +use std::{borrow::Cow, collections::HashMap}; +#[cfg(feature = "output-wgpu")] +use wgpu::{ + Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue, + Texture as WgpuTexture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, + TextureUsages, +}; + +/// This trait is for any backend that allows you to grab and take frames from a camera. +/// Many of the backends are **blocking**, if the camera is occupied the library will block while it waits for it to become available. +/// +/// **Note**: +/// - Backends, if not provided with a camera format, will be spawned with 640x480@15 FPS, MJPEG [`CameraFormat`]. +/// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::Camera) struct abstracts most of this away, if you plan to use the raw backend structs please read the `Quirks` section of each backend. +/// - If you call [`stop_stream()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera. +pub trait CaptureBackendTrait { + /// Returns the current backend used. + fn backend(&self) -> crate::ApiBackend; + + /// Gets the camera information such as Name and Index as a [`CameraInfo`]. + fn camera_info(&self) -> &CameraInfo; + + /// Forcefully refreshes the stored camera format, bringing it into sync with "reality" (current camera state) + /// # Errors + /// If the camera can not get its most recent [`CameraFormat`]. this will error. + fn refresh_camera_format(&mut self) -> Result<(), NokhwaError>; + + /// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed. + fn camera_format(&self) -> CameraFormat; + + /// Will set the current [`CameraFormat`] + /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. + /// # Errors + /// If you started the stream and the camera rejects the new camera format, this will return an error. + fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError>; + + /// A hashmap of [`Resolution`]s mapped to framerates. Not sorted! + /// # Errors + /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError)). + fn compatible_list_by_resolution( + &mut self, + fourcc: FrameFormat, + ) -> Result>, NokhwaError>; + + /// Gets the compatible [`CameraFormat`] of the camera + /// # Errors + /// If it fails to get, this will error. + fn compatible_camera_formats(&mut self) -> Result, NokhwaError> { + let mut compatible_formats = vec![]; + for fourcc in self.compatible_fourcc()? { + for (resolution, fps_list) in self.compatible_list_by_resolution(fourcc)? { + for fps in fps_list { + compatible_formats.push(CameraFormat::new(resolution, fourcc, fps)); + } + } + } + + Ok(compatible_formats) + } + + /// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most. + /// # Errors + /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError)). + fn compatible_fourcc(&mut self) -> Result, NokhwaError>; + + /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. + fn resolution(&self) -> Resolution; + + /// Will set the current [`Resolution`] + /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. + /// # Errors + /// If you started the stream and the camera rejects the new resolution, this will return an error. + fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>; + + /// Gets the current camera framerate (See: [`CameraFormat`]). This will force refresh to the current latest if it has changed. + fn frame_rate(&self) -> u32; + + /// Will set the current framerate + /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. + /// # Errors + /// If you started the stream and the camera rejects the new framerate, this will return an error. + fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; + + /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. + fn frame_format(&self) -> FrameFormat; + + /// Will set the current [`FrameFormat`] + /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. + /// # Errors + /// If you started the stream and the camera rejects the new frame format, this will return an error. + fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>; + + /// Gets the value of [`KnownCameraControl`]. + /// # Errors + /// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, etc) + /// this will error. + fn camera_control(&self, control: KnownCameraControl) -> Result; + + /// Gets the current supported list of [`KnownCameraControl`] + /// # Errors + /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". + fn camera_controls(&self) -> Result, NokhwaError>; + + /// Sets the control to `control` in the camera. + /// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way + /// then calling [`set_value()`](CameraControl::set_value()) + /// # Errors + /// If the `control` is not supported, the value is invalid (less than min, greater than max, not in step), or there was an error setting the control, + /// this will error. + fn set_camera_control( + &mut self, + id: KnownCameraControl, + value: ControlValueSetter, + ) -> Result<(), NokhwaError>; + + /// Will open the camera stream with set parameters. This will be called internally if you try and call [`frame()`](CaptureBackendTrait::frame()) before you call [`open_stream()`](CaptureBackendTrait::open_stream()). + /// # Errors + /// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. + fn open_stream(&mut self) -> Result<(), NokhwaError>; + + /// Checks if stream if open. If it is, it will return true. + fn is_stream_open(&self) -> bool; + + /// Will get a frame from the camera as a [`Buffer`]. Depending on the backend, if you have not called [`open_stream()`](CaptureBackendTrait::open_stream()) before you called this, + /// it will either return an error. + /// # Errors + /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, + /// this will error. + fn frame<'a>(&mut self) -> Result, NokhwaError>; + + /// Will get a frame from the camera **without** any processing applied, meaning you will usually get a frame you need to decode yourself. + /// # Errors + /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. + fn frame_raw(&mut self) -> Result, NokhwaError>; + + /// The minimum buffer size needed to write the current frame. If `alpha` is true, it will instead return the minimum size of the buffer with an alpha channel as well. + /// This assumes that you are decoding to RGB/RGBA for [`FrameFormat::MJPEG`] or [`FrameFormat::YUYV`] and Luma8/LumaA8 for [`FrameFormat::GRAY`] + #[must_use] + fn decoded_buffer_size(&self, alpha: bool) -> usize { + let cfmt = self.camera_format(); + let resolution = cfmt.resolution(); + let pxwidth = match cfmt.format() { + FrameFormat::MJPEG | FrameFormat::YUYV => 3, + FrameFormat::GRAY => 1, + }; + if alpha { + return (resolution.width() * resolution.height() * (pxwidth + 1)) as usize; + } + (resolution.width() * resolution.height() * pxwidth) as usize + } + + #[cfg(feature = "output-wgpu")] + #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-wgpu")))] + /// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame. + /// # Errors + /// If the frame cannot be captured or the resolution is 0 on any axis, this will error. + fn frame_texture<'a>( + &mut self, + device: &WgpuDevice, + queue: &WgpuQueue, + label: Option<&'a str>, + ) -> Result { + use crate::pixel_format::RgbAFormat; + use std::num::NonZeroU32; + let frame = self.frame()?.decode_image::()?; + + let texture_size = Extent3d { + width: frame.width(), + height: frame.height(), + depth_or_array_layers: 1, + }; + + let texture = device.create_texture(&TextureDescriptor { + label, + size: texture_size, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8UnormSrgb, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }); + + let width_nonzero = match NonZeroU32::try_from(4 * frame.width()) { + Ok(w) => Some(w), + Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), + }; + + let height_nonzero = match NonZeroU32::try_from(frame.height()) { + Ok(h) => Some(h), + Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), + }; + + queue.write_texture( + ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: TextureAspect::All, + }, + &frame.to_vec(), + ImageDataLayout { + offset: 0, + bytes_per_row: width_nonzero, + rows_per_image: height_nonzero, + }, + texture_size, + ); + + Ok(texture) + } + + /// Will drop the stream. + /// # Errors + /// Please check the `Quirks` section of each backend. + fn stop_stream(&mut self) -> Result<(), NokhwaError>; +} + +pub trait VirtualBackendTrait {} diff --git a/nokhwa-bindings-windows/src/types.rs b/nokhwa-bindings-windows/src/types.rs new file mode 100644 index 0000000..1f6b3b0 --- /dev/null +++ b/nokhwa-bindings-windows/src/types.rs @@ -0,0 +1,1692 @@ +use crate::NokhwaError; +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +use nokhwa_bindings_macos::avfoundation::{ + AVCaptureDeviceDescriptor, AVFourCC, AVVideoResolution, CaptureDeviceFormatDescriptor, +}; +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +use nokhwa_bindings_windows::{ + MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls, + MediaFoundationDeviceDescriptor, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::{ + borrow::{Borrow, Cow}, + cmp::Ordering, + fmt::{Display, Formatter}, +}; +#[cfg(feature = "input-uvc")] +use uvc::StreamFormat; +#[cfg(all(feature = "input-v4l", target_os = "linux"))] +use v4l::{control::Description, Format, FourCC}; +#[cfg(feature = "output-wasm")] +use wasm_bindgen::prelude::wasm_bindgen; + +/// Tells the init function what camera format to pick. +/// - `HighestResolution(Option)`: Pick the highest [`Resolution`] for the given framerate (the `Option`). If its `None`, it will pick the highest possible [`Resolution`] +/// - `HighestFrameRate(Option)`: Pick the highest frame rate for the given [`Resolution`] (the `Option`). If it is `None`, it will pick the highest possinle framerate. +/// - `Exact`: Pick the exact [`CameraFormat`] provided. +/// - `Closest`: Pick the closest [`CameraFormat`] provided in order of [`FrameFormat`], [`Resolution`], and FPS. Note that if the [`FrameFormat`] does not exist, this will fail to resolve. +/// - `None`: Pick a random [`CameraFormat`] +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum RequestedFormat { + HighestResolution, + HighestFrameRate, + Exact(CameraFormat), + Closest(CameraFormat), + None, +} + +impl RequestedFormat { + /// Fufill the requested using a list of all availible formats. + pub fn fufill(&self, all_formats: &[CameraFormat]) -> Option { + match request { + RequestedFormat::HighestResolution => { + let mut formats = all_formats.to_vec(); + formats.sort_by(|a, b| a.resolution().cmp(&b.resolution())); + let resolution = formats.iter().last()?; + let mut format_resolutions = formats + .into_iter() + .filter(|fmt| fmt.resolution() == resolution.resolution()) + .collect::>(); + format_resolutions.sort_by(|a, b| a.frame_rate().cmp(&b.frame_rate())); + format_resolutions.last().map(|x| *x) + } + RequestedFormat::HighestFrameRate => { + let mut formats = all_formats.to_vec(); + formats.sort_by(|a, b| a.frame_rate().cmp(&b.frame_rate())); + let frame_rate = formats.iter().last()?; + let mut format_framerates = formats + .into_iter() + .filter(|fmt| fmt.frame_rate() == frame_rate.frame_rate()) + .collect::>(); + format_framerates.sort_by(|a, b| a.resolution().cmp(&b.resolution())); + format_framerates.last().map(|x| *x) + } + RequestedFormat::Exact(fmt) => fmt, + RequestedFormat::Closest(c) => { + let mut same_fmt_formats = all_formats + .iter() + .filter(|x| x.format() == c.format()) + .collect::>(); + let mut resolution_map = same_fmt_formats + .iter() + .map(|x| { + let res = x.resolution(); + let x_diff = res.x() as i32 - c.resolution().x() as i32; + let y_diff = res.y() as i32 - c.resolution().y() as i32; + let dist_no_sqrt = (x_diff.abs()).pow(2) + (y_diff.abs()).pow(2); + (dist_no_sqrt, res) + }) + .collect::>(); + resolution_map.sort_by(|a, b| a.0.cmp(*b.0.cmp())); + resolution_map.dedup_by(|a, b| a.0.eq(&b.0)); + let resolution = resolution_map.first()?.1; + + let frame_rates = all_formats + .iter() + .filter_map(|cfmt| { + if cfmt.format() == c.format() && cfmt.resolution() == c.resolution() { + return Some(cfmt.frame_rate()); + } + None + }) + .collect::>(); + // sort FPSes + let mut framerate_map = frame_rates + .iter() + .map(|x| { + let abs = x as i32 - c.frame_rate() as i32; + (abs.abs(), *x) + }) + .collect::>(); + framerate_map.sort(); + let frame_rate = framerate_map.first()?.1; + Some(CameraFormat::new(resolution, c.format(), frame_rate)) + } + RequestedFormat::None => all_formats.first().map(|x| *x), + } + } +} + +/// Describes the index of the camera. +/// - Index: A numbered index +/// - String: A string, used for IPCameras. +#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum CameraIndex { + Index(u32), + String(String), +} + +impl CameraIndex { + /// Turns this value into a number. If it is a string, it will attempt to parse it as a `u32`. + /// # Errors + /// Fails if the value is not a number. + pub fn as_index(&self) -> Result { + match self { + CameraIndex::Index(i) => Ok(*i), + CameraIndex::String(s) => s + .parse::() + .map_err(|why| NokhwaError::GeneralError(why.to_string())), + } + } + + /// Turns this value into a `String`. If it is a number, it will be automatically converted. + #[must_use] + pub fn as_string(&self) -> String { + match self { + CameraIndex::Index(i) => i.to_string(), + CameraIndex::String(s) => s.to_string(), + } + } + + /// Returns true if this [`CameraIndex`] contains an [`CameraIndex::Index`] + #[must_use] + pub fn is_index(&self) -> bool { + match self { + CameraIndex::Index(_) => true, + CameraIndex::String(_) => false, + } + } + + /// Returns true if this [`CameraIndex`] contains an [`CameraIndex::String`] + #[must_use] + pub fn is_string(&self) -> bool { + !self.is_index() + } +} + +impl Display for CameraIndex { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_string()) + } +} + +impl AsRef for CameraIndex { + fn as_ref(&self) -> &str { + self.to_string().as_str() + } +} + +impl TryFrom for u32 { + type Error = NokhwaError; + + fn try_from(value: CameraIndex) -> Result { + value.as_index() + } +} + +impl TryFrom for usize { + type Error = NokhwaError; + + fn try_from(value: CameraIndex) -> Result { + value.as_index().map(|i| i as usize) + } +} + +/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. +/// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) +/// - MJPEG is a motion-jpeg compressed frame, it allows for high frame rates. +/// - GRAY is a grayscale image format, usually for specialized cameras such as IR Cameras. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum FrameFormat { + MJPEG, + YUYV, + GRAY, +} + +impl Display for FrameFormat { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FrameFormat::MJPEG => { + write!(f, "MJPG") + } + FrameFormat::YUYV => { + write!(f, "YUYV") + } + FrameFormat::GRAY => { + write!(f, "GRAY") + } + } + } +} + +#[cfg(feature = "input-uvc")] +impl From for uvc::FrameFormat { + fn from(ff: FrameFormat) -> Self { + match ff { + FrameFormat::MJPEG => uvc::FrameFormat::MJPEG, + FrameFormat::YUYV => uvc::FrameFormat::YUYV, + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for FrameFormat { + fn from(mf_ff: MFFrameFormat) -> Self { + match mf_ff { + MFFrameFormat::MJPEG => FrameFormat::MJPEG, + MFFrameFormat::YUYV => FrameFormat::YUYV, + MFFrameFormat::GRAY => FrameFormat::GRAY, + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for MFFrameFormat { + fn from(ff: FrameFormat) -> Self { + match ff { + FrameFormat::MJPEG => MFFrameFormat::MJPEG, + FrameFormat::YUYV => MFFrameFormat::YUYV, + FrameFormat::GRAY => MFFrameFormat::GRAY8, //FIXME + } + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +impl From for FrameFormat { + fn from(av_fcc: AVFourCC) -> Self { + match av_fcc { + AVFourCC::YUV2 => FrameFormat::YUYV, + AVFourCC::MJPEG => FrameFormat::MJPEG, + AVFourCC::GRAY8 => FrameFormat::GRAY, + } + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +impl From for AVFourCC { + fn from(ff: FrameFormat) -> Self { + match ff { + FrameFormat::MJPEG => AVFourCC::MJPEG, + FrameFormat::YUYV => AVFourCC::YUV2, + FrameFormat::GRAY => AVFourCC::GRAY8, + } + } +} + +#[must_use] +pub const fn frame_formats() -> [FrameFormat; 3] { + [FrameFormat::MJPEG, FrameFormat::YUYV, FrameFormat::GRAY] +} + +/// Describes a Resolution. +/// This struct consists of a Width and a Height value (x,y).
+/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest. +/// # JS-WASM +/// This is exported as `JSResolution` +#[cfg_attr(feature = "output-wasm", wasm_bindgen)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +pub struct Resolution { + pub width_x: u32, + pub height_y: u32, +} + +#[cfg_attr(feature = "output-wasm", wasm_bindgen)] +impl Resolution { + /// Create a new resolution from 2 image size coordinates. + /// # JS-WASM + /// This is exported as a constructor for [`Resolution`]. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))] + pub fn new(x: u32, y: u32) -> Self { + Resolution { + width_x: x, + height_y: y, + } + } + + /// Get the width of Resolution + /// # JS-WASM + /// This is exported as `get_Width`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Width))] + pub fn width(self) -> u32 { + self.width_x + } + + /// Get the height of Resolution + /// # JS-WASM + /// This is exported as `get_Height`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Height))] + pub fn height(self) -> u32 { + self.height_y + } + + /// Get the x (width) of Resolution + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))] + pub fn x(self) -> u32 { + self.width_x + } + + /// Get the y (height) of Resolution + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))] + pub fn y(self) -> u32 { + self.height_y + } +} + +impl Display for Resolution { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}x{}", self.x(), self.y()) + } +} + +impl PartialOrd for Resolution { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Resolution { + fn cmp(&self, other: &Self) -> Ordering { + match self.x().cmp(&other.x()) { + Ordering::Less => Ordering::Less, + Ordering::Equal => self.y().cmp(&other.y()), + Ordering::Greater => Ordering::Greater, + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for Resolution { + fn from(mf_res: MFResolution) -> Self { + Resolution { + width_x: mf_res.width_x, + height_y: mf_res.height_y, + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for MFResolution { + fn from(res: Resolution) -> Self { + MFResolution { + width_x: res.width(), + height_y: res.height(), + } + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +#[allow(clippy::cast_sign_loss)] +impl From for Resolution { + fn from(res: AVVideoResolution) -> Self { + Resolution { + width_x: res.width as u32, + height_y: res.height as u32, + } + } +} + +/// This is a convenience struct that holds all information about the format of a webcam stream. +/// It consists of a [`Resolution`], [`FrameFormat`], and a frame rate(u8). +#[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CameraFormat { + resolution: Resolution, + format: FrameFormat, + frame_rate: u32, +} + +impl CameraFormat { + /// Construct a new [`CameraFormat`] + #[must_use] + pub fn new(resolution: Resolution, format: FrameFormat, frame_rate: u32) -> Self { + CameraFormat { + resolution, + format, + frame_rate, + } + } + + /// [`CameraFormat::new()`], but raw. + #[must_use] + pub fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: u32) -> Self { + CameraFormat { + resolution: Resolution { + width_x: res_x, + height_y: res_y, + }, + format, + frame_rate: fps, + } + } + + /// Get the resolution of the current [`CameraFormat`] + #[must_use] + pub fn resolution(&self) -> Resolution { + self.resolution + } + + /// Get the width of the resolution of the current [`CameraFormat`] + #[must_use] + pub fn width(&self) -> u32 { + self.resolution.width() + } + + /// Get the height of the resolution of the current [`CameraFormat`] + #[must_use] + pub fn height(&self) -> u32 { + self.resolution.height() + } + + /// Set the [`CameraFormat`]'s resolution. + pub fn set_resolution(&mut self, resolution: Resolution) { + self.resolution = resolution; + } + + /// Get the frame rate of the current [`CameraFormat`] + #[must_use] + pub fn frame_rate(&self) -> u32 { + self.frame_rate + } + + /// Set the [`CameraFormat`]'s frame rate. + pub fn set_frame_rate(&mut self, frame_rate: u32) { + self.frame_rate = frame_rate; + } + + /// Get the [`CameraFormat`]'s format. + #[must_use] + pub fn format(&self) -> FrameFormat { + self.format + } + + /// Set the [`CameraFormat`]'s format. + pub fn set_format(&mut self, format: FrameFormat) { + self.format = format; + } +} + +#[cfg(feature = "input-uvc")] +impl From for StreamFormat { + fn from(cf: CameraFormat) -> Self { + StreamFormat { + width: cf.width(), + height: cf.height(), + fps: cf.frame_rate(), + format: cf.format().into(), + } + } +} + +impl Default for CameraFormat { + fn default() -> Self { + CameraFormat { + resolution: Resolution::new(640, 480), + format: FrameFormat::MJPEG, + frame_rate: 30, + } + } +} + +impl Display for CameraFormat { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}@{}FPS, {} Format", + self.resolution, self.frame_rate, self.format + ) + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for CameraFormat { + fn from(mf_cam_fmt: MFCameraFormat) -> Self { + CameraFormat { + resolution: mf_cam_fmt.resolution().into(), + format: mf_cam_fmt.format().into(), + frame_rate: mf_cam_fmt.frame_rate(), + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for MFCameraFormat { + fn from(cf: CameraFormat) -> Self { + MFCameraFormat::new(cf.resolution.into(), cf.format.into(), cf.frame_rate) + } +} + +#[cfg(all(feature = "input-v4l", target_os = "linux"))] +impl From for Format { + fn from(cam_fmt: CameraFormat) -> Self { + let pxfmt = match cam_fmt.format() { + FrameFormat::MJPEG => FourCC::new(b"MJPG"), + FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY => FourCC::new(b"GREY"), + }; + + Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt) + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +#[allow(clippy::cast_possible_wrap)] +#[allow(clippy::cast_lossless)] +impl From for CaptureDeviceFormatDescriptor { + fn from(cf: CameraFormat) -> Self { + CaptureDeviceFormatDescriptor { + resolution: AVVideoResolution { + width: cf.width() as i32, + height: cf.height() as i32, + }, + fps: cf.frame_rate(), + fourcc: cf.format().into(), + } + } +} + +/// Information about a Camera e.g. its name. +/// `description` amd `misc` may contain information that may differ from backend to backend. Refer to each backend for details. +/// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system. +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] +#[cfg_attr(feature = "output-wasm", wasm_bindgen)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CameraInfo { + human_name: String, + description: String, + misc: String, + index: CameraIndex, +} + +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = CameraInfo))] +impl CameraInfo { + /// Create a new [`CameraInfo`]. + /// # JS-WASM + /// This is exported as a constructor for [`CameraInfo`]. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))] + // OK, i just checkeed back on this code. WTF was I on when I wrote `&(impl AsRef + ?Sized)` ???? + // I need to get on the same shit that my previous self was on, because holy shit that stuff is strong as FUCK! + // Finally fixed this insanity. Hopefully I didnt torment anyone by actually putting this in a stable release. + pub fn new(human_name: &str, description: &str, misc: &str, index: CameraIndex) -> Self { + CameraInfo { + human_name: human_name.to_string(), + description: description.to_string(), + misc: misc.to_string(), + index, + } + } + + /// Get a reference to the device info's human readable name. + /// # JS-WASM + /// This is exported as a `get_HumanReadableName`. + #[must_use] + #[cfg_attr( + feature = "output-wasm", + wasm_bindgen(getter = HumanReadableName) + )] + // yes, i know, unnecessary alloc this, unnecessary alloc that + // but wasm bindgen + pub fn human_name(&self) -> String { + self.human_name.clone() + } + + /// Set the device info's human name. + /// # JS-WASM + /// This is exported as a `set_HumanReadableName`. + #[cfg_attr( + feature = "output-wasm", + wasm_bindgen(setter = HumanReadableName) + )] + pub fn set_human_name(&mut self, human_name: &str) { + self.human_name = human_name.to_string(); + } + + /// Get a reference to the device info's description. + /// # JS-WASM + /// This is exported as a `get_Description`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Description))] + pub fn description(&self) -> &str { + self.description.borrow() + } + + /// Set the device info's description. + /// # JS-WASM + /// This is exported as a `set_Description`. + #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Description))] + pub fn set_description(&mut self, description: &str) { + self.description = description.to_string(); + } + + /// Get a reference to the device info's misc. + /// # JS-WASM + /// This is exported as a `get_MiscString`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = MiscString))] + pub fn misc(&self) -> String { + self.misc.clone() + } + + /// Set the device info's misc. + /// # JS-WASM + /// This is exported as a `set_MiscString`. + #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = MiscString))] + pub fn set_misc(&mut self, misc: &str) { + self.misc = misc.to_string(); + } + + /// Get a reference to the device info's index. + /// # JS-WASM + /// This is exported as a `get_Index`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index))] + pub fn index(&self) -> &CameraIndex { + &self.index + } + + /// Set the device info's index. + /// # JS-WASM + /// This is exported as a `set_Index`. + #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Index))] + pub fn set_index(&mut self, index: CameraIndex) { + self.index = index; + } + + // /// Gets the device info's index as an `u32`. + // /// # Errors + // /// If the index is not parsable as a `u32`, this will error. + // /// # JS-WASM + // /// This is exported as `get_Index_Int` + // #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] + // pub fn index_num(&self) -> Result { + // match &self.index { + // CameraIndex::Index(i) => Ok(*i), + // CameraIndex::String(s) => match s.parse::() { + // Ok(p) => Ok(p), + // Err(why) => Err(NokhwaError::GetPropertyError { + // property: "index-int".to_string(), + // error: why.to_string(), + // }), + // }, + // } + // } +} + +impl Display for CameraInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Name: {}, Description: {}, Extra: {}, Index: {}", + self.human_name, self.description, self.misc, self.index + ) + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From> for CameraInfo { + fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { + CameraInfo { + human_name: dev_desc.name_as_string(), + description: "Media Foundation Device".to_string(), + misc: dev_desc.link_as_string(), + index: dev_desc.index() as u32, + } + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +#[allow(clippy::cast_possible_truncation)] +impl From for CameraInfo { + fn from(descriptor: AVCaptureDeviceDescriptor) -> Self { + CameraInfo { + human_name: descriptor.name, + description: descriptor.description, + misc: descriptor.misc, + index: descriptor.index as u32, + } + } +} + +/// The list of known camera controls to the library.
+/// These can control the picture brightness, etc.
+/// Note that not all backends/devices support all these. Run [`supported_camera_controls()`](crate::CaptureBackendTrait::camera_controls) to see which ones can be set. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KnownCameraControl { + Brightness, + Contrast, + Hue, + Saturation, + Sharpness, + Gamma, + WhiteBalance, + BacklightComp, + Gain, + Pan, + Tilt, + Zoom, + Exposure, + Iris, + Focus, + /// Other camera control. Listed is the ID. + /// Wasteful, however is needed for a unified API across Windows, Linux, and MacOSX due to Microsoft's usage of GUIDs. + /// + /// THIS SHOULD ONLY BE USED WHEN YOU KNOW THE PLATFORM THAT YOU ARE RUNNING ON. + Other(u128), +} + +/// All camera controls in an array. +#[must_use] +pub const fn all_known_camera_controls() -> [KnownCameraControl; 15] { + [ + KnownCameraControl::Brightness, + KnownCameraControl::Contrast, + KnownCameraControl::Hue, + KnownCameraControl::Saturation, + KnownCameraControl::Sharpness, + KnownCameraControl::Gamma, + KnownCameraControl::WhiteBalance, + KnownCameraControl::BacklightComp, + KnownCameraControl::Gain, + KnownCameraControl::Pan, + KnownCameraControl::Tilt, + KnownCameraControl::Zoom, + KnownCameraControl::Exposure, + KnownCameraControl::Iris, + KnownCameraControl::Focus, + ] +} + +impl Display for KnownCameraControl { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &self) + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for KnownCameraControl { + fn from(mf_c: MediaFoundationControls) -> Self { + match mf_c { + MediaFoundationControls::Brightness => KnownCameraControl::Brightness, + MediaFoundationControls::Contrast => KnownCameraControl::Contrast, + MediaFoundationControls::Hue => KnownCameraControl::Hue, + MediaFoundationControls::Saturation => KnownCameraControl::Saturation, + MediaFoundationControls::Sharpness => KnownCameraControl::Sharpness, + MediaFoundationControls::Gamma => KnownCameraControl::Gamma, + MediaFoundationControls::WhiteBalance => KnownCameraControl::WhiteBalance, + MediaFoundationControls::BacklightComp => KnownCameraControl::BacklightComp, + MediaFoundationControls::Gain => KnownCameraControl::Gain, + MediaFoundationControls::Pan => KnownCameraControl::Pan, + MediaFoundationControls::Tilt => KnownCameraControl::Tilt, + MediaFoundationControls::Zoom => KnownCameraControl::Zoom, + MediaFoundationControls::Exposure => KnownCameraControl::Exposure, + MediaFoundationControls::Iris => KnownCameraControl::Iris, + MediaFoundationControls::Focus => KnownCameraControl::Focus, + MediaFoundationControls::ColorEnable => KnownCameraControl::Other(0), + MediaFoundationControls::Roll => KnownCameraControl::Other(1), + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for KnownCameraControl { + fn from(mf_cc: MFControl) -> Self { + mf_cc.control().into() + } +} + +#[cfg(all(feature = "input-v4l", target_os = "linux"))] +impl From for KnownCameraControl { + fn from(value: Description) -> KnownCameraControl { + match value.id { + 9_963_776 => KnownCameraControl::Brightness, + 9_963_777 => KnownCameraControl::Contrast, + 9_963_779 => KnownCameraControl::Hue, + 9_963_778 => KnownCameraControl::Saturation, + 9_963_803 => KnownCameraControl::Sharpness, + 9_963_792 => KnownCameraControl::Gamma, + 9_963_802 => KnownCameraControl::WhiteBalance, + 9_963_804 => KnownCameraControl::BacklightComp, + 9_963_795 => KnownCameraControl::Gain, + 10_094_852 => KnownCameraControl::Pan, + 10_094_853 => KnownCameraControl::Tilt, + 10_094_862 => KnownCameraControl::Zoom, + 10_094_850 => KnownCameraControl::Exposure, + 10_094_866 => KnownCameraControl::Iris, + 10_094_859 => KnownCameraControl::Focus, + id => KnownCameraControl::Other(id as u128), + } + } +} + +#[cfg(all(feature = "input-opencv"))] +impl From for KnownCameraControl { + fn from(id: i32) -> Self { + use opencv::videoio::*; + match id { + CAP_PROP_BRIGHTNESS => KnownCameraControl::Brightness, + CAP_PROP_CONTRAST => KnownCameraControl::Contrast, + CAP_PROP_HUE => KnownCameraControl::Hue, + CAP_PROP_SATURATION => KnownCameraControl::Saturation, + CAP_PROP_SHARPNESS => KnownCameraControl::Sharpness, + CAP_PROP_GAMMA => KnownCameraControl::Gamma, + CAP_PROP_WB_TEMPERATURE => KnownCameraControl::WhiteBalance, + CAP_PROP_BACKLIGHT => KnownCameraControl::BacklightComp, + CAP_PROP_GAIN => KnownCameraControl::Gain, + CAP_PROP_PAN => KnownCameraControl::Pan, + CAP_PROP_TILT => KnownCameraControl::Tilt, + CAP_PROP_ZOOM => KnownCameraControl::Zoom, + CAP_PROP_EXPOSURE => KnownCameraControl::Exposure, + CAP_PROP_IRIS => KnownCameraControl::Iris, + CAP_PROP_FOCUS => KnownCameraControl::Focus, + id => KnownCameraControl::Other(id as u128), + } + } +} + +#[cfg(all(feature = "input-opencv"))] +impl From for i32 { + fn from(kcc: KnownCameraControl) -> Self { + use opencv::videoio::*; + + match kcc { + KnownCameraControl::Brightness => CAP_PROP_BRIGHTNESS, + KnownCameraControl::Contrast => CAP_PROP_CONTRAST, + KnownCameraControl::Hue => CAP_PROP_HUE, + KnownCameraControl::Saturation => CAP_PROP_SATURATION, + KnownCameraControl::Sharpness => CAP_PROP_SHARPNESS, + KnownCameraControl::Gamma => CAP_PROP_GAMMA, + KnownCameraControl::WhiteBalance => CAP_PROP_WB_TEMPERATURE, + KnownCameraControl::BacklightComp => CAP_PROP_BACKLIGHT, + KnownCameraControl::Gain => CAP_PROP_GAIN, + KnownCameraControl::Pan => CAP_PROP_PAN, + KnownCameraControl::Tilt => CAP_PROP_TILT, + KnownCameraControl::Zoom => CAP_PROP_ZOOM, + KnownCameraControl::Exposure => CAP_PROP_EXPOSURE, + KnownCameraControl::Iris => CAP_PROP_IRIS, + KnownCameraControl::Focus => CAP_PROP_FOCUS, + KnownCameraControl::Other(id) => id as i32, + } + } +} + +/// This tells you weather a [`KnownCameraControl`] is automatically managed by the OS/Driver +/// or manually managed by you, the programmer. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub enum KnownCameraControlFlag { + Automatic, + Manual, + ReadOnly, + WriteOnly, + Volatile, + Disabled, + Inactive, +} + +impl Display for KnownCameraControlFlag { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +// TODO: use in CameraControl +/// The values for a [`CameraControl`]. +/// +/// This provides a wide range of values that can be used to control a camera. +pub enum ControlValueDescription { + None, + Integer { + value: i64, + default: i64, + step: i64, + }, + IntegerRange { + min: i64, + max: i64, + value: i64, + step: i64, + default: i64, + }, + Float { + value: f64, + default: f64, + step: f64, + }, + FloatRange { + min: f64, + max: f64, + value: f64, + step: f64, + default: f64, + }, + Boolean { + value: bool, + default: bool, + }, + String { + value: String, + default: Option, + }, + Bytes { + value: Vec, + default: Vec, + }, +} + +impl ControlValueDescription { + /// Get the value of this [`ControlValueDescription`] + #[must_use] + pub fn value(&self) -> ControlValueSetter { + match self { + ControlValueDescription::None => ControlValueSetter::None, + ControlValueDescription::Integer { value, .. } + | ControlValueDescription::IntegerRange { value, .. } => { + ControlValueSetter::Integer(*value) + } + ControlValueDescription::Float { value, .. } + | ControlValueDescription::FloatRange { value, .. } => { + ControlValueSetter::Float(*value) + } + ControlValueDescription::Boolean { value, .. } => ControlValueSetter::Boolean(*value), + ControlValueDescription::String { value, .. } => { + ControlValueSetter::String(value.clone()) + } + ControlValueDescription::Bytes { value, .. } => { + ControlValueSetter::Bytes(value.clone()) + } + } + } + + /// Verifies if the [setter](crate::utils::ControlValueSetter) is valid for the provided [`ControlValueDescription`]. + /// - `true` => Is valid. + /// - `false` => Is not valid. + #[must_use] + pub fn verify_setter(&self, setter: &ControlValueSetter) -> bool { + match setter { + ControlValueSetter::None => { + matches!(self, ControlValueDescription::None) + } + ControlValueSetter::Integer(i) => match self { + ControlValueDescription::Integer { + value, + default, + step, + } => (i - default).abs() % step == 0 || (i - value) % step == 0, + ControlValueDescription::IntegerRange { + min, + max, + value, + step, + default, + } => { + if value > max || value < min { + return false; + } + + (i - default) % step == 0 || (i - value) % step == 0 + } + _ => false, + }, + ControlValueSetter::Float(f) => match self { + ControlValueDescription::Float { + value, + default, + step, + } => (f - default).abs() % step == 0_f64 || (f - value) % step == 0_f64, + ControlValueDescription::FloatRange { + min, + max, + value, + step, + default, + } => { + if value > max || value < min { + return false; + } + + (f - default) % step == 0_f64 || (f - value) % step == 0_f64 + } + _ => false, + }, + ControlValueSetter::Boolean(_) => { + matches!(self, ControlValueDescription::Boolean { .. }) + } + ControlValueSetter::String(_) => { + matches!(self, ControlValueDescription::String { .. }) + } + ControlValueSetter::Bytes(_) => { + matches!(self, ControlValueDescription::Bytes { .. }) + } + } + } +} + +impl Display for ControlValueDescription { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ControlValueDescription::None => { + write!(f, "(None)") + } + ControlValueDescription::Integer { + value, + default, + step, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {})", + value, default, step + ) + } + ControlValueDescription::IntegerRange { + min, + max, + value, + step, + default, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {}, Range: ({}, {}))", + value, default, step, min, max + ) + } + ControlValueDescription::Float { + value, + default, + step, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {})", + value, default, step + ) + } + ControlValueDescription::FloatRange { + min, + max, + value, + step, + default, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {}, Range: ({}, {}))", + value, default, step, min, max + ) + } + ControlValueDescription::Boolean { value, default } => { + write!(f, "(Current: {}, Default: {})", value, default) + } + ControlValueDescription::String { value, default } => { + write!(f, "(Current: {}, Default: {:?})", value, default) + } + ControlValueDescription::Bytes { value, default } => { + write!(f, "(Current: {:x?}, Default: {:x?})", value, default) + } + } + } +} + +// fn step_chk(val: i64, default: i64, step: i64) -> Result<(), NokhwaError> { +// if (val - default) % step != 0 { +// return Err(NokhwaError::StructureError { +// structure: "Value".to_string(), +// error: "Doesnt fit step".to_string(), +// }); +// } +// Ok(()) +// } + +/// This struct tells you everything about a particular [`KnownCameraControl`]. +/// +/// However, you should never need to instantiate this struct, since its usually generated for you by `nokhwa`. +/// The only time you should be modifying this struct is when you need to set a value and pass it back to the camera. +/// NOTE: Assume the values for `min` and `max` as **non-inclusive**!. +/// E.g. if the [`CameraControl`] says `min` is 100, the minimum is actually 101. +#[derive(Clone, Debug, PartialOrd, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CameraControl { + control: KnownCameraControl, + name: String, + description: ControlValueDescription, + value: ControlValueSetter, + flag: Vec, + active: bool, +} + +impl CameraControl { + /// Creates a new [`CameraControl`] + #[must_use] + pub fn new( + control: KnownCameraControl, + name: String, + description: ControlValueDescription, + flag: Vec, + active: bool, + ) -> Self { + let value = description.value(); + CameraControl { + control, + name, + description, + value, + flag, + active, + } + } + + /// Gets the [`KnownCameraControl`] of this [`CameraControl`] + #[must_use] + pub fn control(&self) -> KnownCameraControl { + self.control + } + + /// Gets the current value description of this [`CameraControl`] + #[must_use] + pub fn value(&self) -> &ControlValueDescription { + &self.description + } + + /// Sets the value of this [`CameraControl`] + /// # Errors + /// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error + pub fn set_value(&mut self, value: ControlValueSetter) -> Result<(), NokhwaError> { + if !self.description.verify_setter(&value) { + return Err(NokhwaError::SetPropertyError { + property: format!("ControlValueDescription: {}", self.description), + value: format!("ControlValueSetter: {}", self.value), + error: "Invalid Value.".to_string(), + }); + } + + self.value = value; + Ok(()) + } + + /// Gets the [`KnownCameraControlFlag`] of this [`CameraControl`], + /// telling you weather this control is automatically set or manually set. + #[must_use] + pub fn flag(&self) -> &[KnownCameraControlFlag] { + &self.flag + } + + /// Gets `active` of this [`CameraControl`], + /// telling you weather this control is currently active(in-use). + #[must_use] + pub fn active(&self) -> bool { + self.active + } + + /// Gets `active` of this [`CameraControl`], + /// telling you weather this control is currently active(in-use). + pub fn set_active(&mut self, active: bool) { + self.active = active; + } +} + +impl Display for CameraControl { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Control: {}, Name: {}, Value: {}, Flag: {:?}, Active: {}", + self.control, self.name, self.description, self.flag, self.active + ) + } +} + +/// The setter for a control value +#[derive(Clone, Debug, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ControlValueSetter { + None, + Integer(i64), + Float(f64), + Boolean(bool), + String(String), + Bytes(Vec), +} + +impl Display for ControlValueSetter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ControlValueSetter::None => { + write!(f, "Value: None") + } + ControlValueSetter::Integer(i) => { + write!(f, "Value: {}", i) + } + ControlValueSetter::Float(d) => { + write!(f, "Value: {}", d) + } + ControlValueSetter::Boolean(b) => { + write!(f, "Value: {}", b) + } + ControlValueSetter::String(s) => { + write!(f, "Value: {}", s) + } + ControlValueSetter::Bytes(b) => { + write!(f, "Value: {:x?}", b) + } + } + } +} + +/// The list of known capture backends to the library.
+/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform. +/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` +/// - `Video4Linux` - `Video4Linux2`, a linux specific backend. +/// - `UniversalVideoClass` - ***DEPRECATED*** Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use. +/// - `MediaFoundation` - Microsoft Media Foundation, Windows only, +/// - `OpenCv` - Uses `OpenCV` to capture. Platform agnostic. +/// - `GStreamer` - ***DEPRECATED*** Uses `GStreamer` RTP to capture. Platform agnostic. +/// - `Network` - Uses `OpenCV` to capture from an IP. +/// - `Browser` - Uses browser APIs to capture from a webcam. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ApiBackend { + Auto, + AVFoundation, + Video4Linux, + #[deprecated] + UniversalVideoClass, + MediaFoundation, + OpenCv, + #[deprecated] + GStreamer, + Network, + Browser, +} + +impl Display for ApiBackend { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let self_str = format!("{:?}", self); + write!(f, "{}", self_str) + } +} + +// /// A webcam index that supports both strings and integers. Most backends take an int, but `IPCamera`s take a URL (string). +// #[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] +// pub enum CameraIndex { +// Index(u32), +// String(String), +// } + +// impl CameraIndex { +// /// Gets the device info's index as an `u32`. +// /// # Errors +// /// If the index is not parsable as a `u32`, this will error. +// pub fn as_index(&self) -> Result { +// match self { +// CameraIndex::Index(i) => Ok(*i), +// CameraIndex::String(s) => match s.parse::() { +// Ok(p) => Ok(p), +// Err(why) => Err(NokhwaError::GetPropertyError { +// property: "index-int".to_string(), +// error: why.to_string(), +// }), +// }, +// } +// } +// } + +// impl Display for CameraIndex { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// match self { +// CameraIndex::Index(idx) => { +// write!(f, "{}", idx) +// } +// CameraIndex::String(ip) => { +// write!(f, "{}", ip) +// } +// } +// } +// } + +// impl From for CameraIndex { +// fn from(v: u32) -> Self { +// CameraIndex::Index(v) +// } +// } + +// /// Trait for strings that can be converted to [`CameraIndex`]es. +// pub trait ValidString: AsRef {} +// +// impl ValidString for String {} +// impl<'a> ValidString for &'a String {} +// impl<'a> ValidString for &'a mut String {} +// impl<'a> ValidString for Cow<'a, str> {} +// impl<'a> ValidString for &'a Cow<'a, str> {} +// impl<'a> ValidString for &'a mut Cow<'a, str> {} +// impl<'a> ValidString for &'a str {} +// impl<'a> ValidString for &'a mut str {} + +// impl From for CameraIndex +// where +// T: ValidString, +// { +// fn from(v: T) -> Self { +// CameraIndex::String(v.as_ref().to_string()) +// } +// } + +/// Converts a MJPEG stream of [u8] into a Vec of RGB888. (R,G,B,R,G,B,...) +/// # Errors +/// If `mozjpeg` fails to read scanlines or setup the decompressor, this will error. +/// # Safety +/// This function uses `unsafe`. The caller must ensure that: +/// - The input data is of the right size, does not exceed bounds, and/or the final size matches with the initial size. +#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] +#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + use mozjpeg::Decompress; + + let mut jpeg_decompress = match Decompress::new_mem(data) { + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + } + } + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + }; + + let scanlines_res: Option> = jpeg_decompress.read_scanlines_flat(); + // assert!(jpeg_decompress.finish_decompress()); + if !jpeg_decompress.finish_decompress() { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "JPEG Decompressor did not finish.".to_string(), + }); + } + + match scanlines_res { + Some(pixels) => Ok(pixels), + None => Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "Failed to get read readlines into RGB888 pixels!".to_string(), + }), + } +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) +} + +/// Equivalent to [`mjpeg_to_rgb`] except with a destination buffer. +/// # Errors +/// If the decoding fails (e.g. invalid MJPEG stream), the buffer is not large enough, or you are doing this on `WebAssembly`, this will error. +#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] +#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + use mozjpeg::Decompress; + + let mut jpeg_decompress = match Decompress::new_mem(data) { + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + } + } + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + }; + + // assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size()); + if dest.len() != jpeg_decompress.min_flat_buffer_size() { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "Bad decoded buffer size".to_string(), + }); + } + + jpeg_decompress.read_scanlines_flat_into(dest); + // assert!(jpeg_decompress.finish_decompress()); + if !jpeg_decompress.finish_decompress() { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "JPEG Decompressor did not finish.".to_string(), + }); + } + Ok(()) +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) +} + +// For those maintaining this, I recommend you read: https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#yuy2 +// https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB +// and this too: https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte +// The YUY2(YUYV) format is a 16 bit format. We read 4 bytes at a time to get 6 bytes of RGB888. +// First, the YUY2 is converted to YCbCr 4:4:4 (4:2:2 -> 4:4:4) +// then it is converted to 6 bytes (2 pixels) of RGB888 +/// Converts a YUYV 4:2:2 datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) +/// # Errors +/// This may error when the data stream size is not divisible by 4, a i32 -> u8 conversion fails, or it fails to read from a certain index. +pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" + .to_string(), + }); + } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + let mut dest = vec![0; rgb_buf_size]; + buf_yuyv422_to_rgb(data, &mut dest, rgba)?; + + Ok(dest) +} + +/// Same as [`yuyv422_to_rgb`] but with a destination buffer instead of a return `Vec` +/// # Errors +/// If the stream is invalid YUYV, or the destination buffer is not large enough, this will error. +pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" + .to_string(), + }); + } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + if dest.len() != rgb_buf_size { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()), + }); + } + + let iter = data.chunks_exact(4); + + if rgba { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = i32::from(yuyv[0]); + let u = i32::from(yuyv[1]); + let y2 = i32::from(yuyv[2]); + let v = i32::from(yuyv[3]); + let pixel1 = yuyv444_to_rgba(y1, u, v); + let pixel2 = yuyv444_to_rgba(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + for i in dest.iter_mut().take(rgb_buf_size) { + *i = match iter.next() { + Some(v) => v, + None => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGBA8888".to_string(), + error: "Ran out of RGBA YUYV values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string() + }) + } + } + } + } else { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = i32::from(yuyv[0]); + let u = i32::from(yuyv[1]); + let y2 = i32::from(yuyv[2]); + let v = i32::from(yuyv[3]); + let pixel1 = yuyv444_to_rgb(y1, u, v); + let pixel2 = yuyv444_to_rgb(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + + for i in dest.iter_mut().take(rgb_buf_size) { + *i = match iter.next() { + Some(v) => v, + None => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Ran out of RGB YUYV values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string() + }) + } + } + } + } + + Ok(()) +} + +// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB +/// Convert `YCbCr` 4:4:4 to a RGB888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) +#[allow(clippy::many_single_char_names)] +#[allow(clippy::cast_possible_truncation)] +#[allow(clippy::cast_sign_loss)] +#[must_use] +#[inline] +pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { + let c298 = (y - 16) * 298; + let d = u - 128; + let e = v - 128; + let r = ((c298 + 409 * e + 128) >> 8) as u8; + let g = ((c298 - 100 * d - 208 * e + 128) >> 8) as u8; + let b = ((c298 + 516 * d + 128) >> 8) as u8; + [r, g, b] +} + +// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB +/// Convert `YCbCr` 4:4:4 to a RGBA8888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) +/// +/// Equivalent to [`yuyv444_to_rgb`] but with an alpha channel attached. +#[allow(clippy::many_single_char_names)] +#[must_use] +#[inline] +pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] { + let [r, g, b] = yuyv444_to_rgb(y, u, v); + [r, g, b, 255] +} diff --git a/nokhwa-core/Cargo.toml b/nokhwa-core/Cargo.toml new file mode 100644 index 0000000..511b778 --- /dev/null +++ b/nokhwa-core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nokhwa-core" +version = "0.1.0" +authors = ["l1npengtul "] +edition = "2021" +description = "Core type definitions for nokhwa" +keywords = ["camera", "webcam", "capture", "cross-platform"] +license = "Apache-2.0" +repository = "https://github.com/l1npengtul/nokhwa" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/nokhwa-core/src/lib.rs b/nokhwa-core/src/lib.rs new file mode 100644 index 0000000..52bbe08 --- /dev/null +++ b/nokhwa-core/src/lib.rs @@ -0,0 +1,1708 @@ +/* + * Copyright 2022 l1npengtul / The Nokhwa Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::NokhwaError; +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +use nokhwa_bindings_macos::avfoundation::{ + AVCaptureDeviceDescriptor, AVFourCC, AVVideoResolution, CaptureDeviceFormatDescriptor, +}; +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +use nokhwa_bindings_windows::{ + MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls, + MediaFoundationDeviceDescriptor, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::{ + borrow::{Borrow, Cow}, + cmp::Ordering, + fmt::{Display, Formatter}, +}; +#[cfg(feature = "input-uvc")] +use uvc::StreamFormat; +#[cfg(all(feature = "input-v4l", target_os = "linux"))] +use v4l::{control::Description, Format, FourCC}; +#[cfg(feature = "output-wasm")] +use wasm_bindgen::prelude::wasm_bindgen; + +/// Tells the init function what camera format to pick. +/// - `HighestResolution(Option)`: Pick the highest [`Resolution`] for the given framerate (the `Option`). If its `None`, it will pick the highest possible [`Resolution`] +/// - `HighestFrameRate(Option)`: Pick the highest frame rate for the given [`Resolution`] (the `Option`). If it is `None`, it will pick the highest possinle framerate. +/// - `Exact`: Pick the exact [`CameraFormat`] provided. +/// - `Closest`: Pick the closest [`CameraFormat`] provided in order of [`FrameFormat`], [`Resolution`], and FPS. Note that if the [`FrameFormat`] does not exist, this will fail to resolve. +/// - `None`: Pick a random [`CameraFormat`] +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum RequestedFormat { + HighestResolution, + HighestFrameRate, + Exact(CameraFormat), + Closest(CameraFormat), + None, +} + +impl RequestedFormat { + /// Fufill the requested using a list of all availible formats. + pub fn fufill(&self, all_formats: &[CameraFormat]) -> Option { + match request { + RequestedFormat::HighestResolution => { + let mut formats = all_formats.to_vec(); + formats.sort_by(|a, b| a.resolution().cmp(&b.resolution())); + let resolution = formats.iter().last()?; + let mut format_resolutions = formats + .into_iter() + .filter(|fmt| fmt.resolution() == resolution.resolution()) + .collect::>(); + format_resolutions.sort_by(|a, b| a.frame_rate().cmp(&b.frame_rate())); + format_resolutions.last().map(|x| *x) + } + RequestedFormat::HighestFrameRate => { + let mut formats = all_formats.to_vec(); + formats.sort_by(|a, b| a.frame_rate().cmp(&b.frame_rate())); + let frame_rate = formats.iter().last()?; + let mut format_framerates = formats + .into_iter() + .filter(|fmt| fmt.frame_rate() == frame_rate.frame_rate()) + .collect::>(); + format_framerates.sort_by(|a, b| a.resolution().cmp(&b.resolution())); + format_framerates.last().map(|x| *x) + } + RequestedFormat::Exact(fmt) => fmt, + RequestedFormat::Closest(c) => { + let mut same_fmt_formats = all_formats + .iter() + .filter(|x| x.format() == c.format()) + .collect::>(); + let mut resolution_map = same_fmt_formats + .iter() + .map(|x| { + let res = x.resolution(); + let x_diff = res.x() as i32 - c.resolution().x() as i32; + let y_diff = res.y() as i32 - c.resolution().y() as i32; + let dist_no_sqrt = (x_diff.abs()).pow(2) + (y_diff.abs()).pow(2); + (dist_no_sqrt, res) + }) + .collect::>(); + resolution_map.sort_by(|a, b| a.0.cmp(*b.0.cmp())); + resolution_map.dedup_by(|a, b| a.0.eq(&b.0)); + let resolution = resolution_map.first()?.1; + + let frame_rates = all_formats + .iter() + .filter_map(|cfmt| { + if cfmt.format() == c.format() && cfmt.resolution() == c.resolution() { + return Some(cfmt.frame_rate()); + } + None + }) + .collect::>(); + // sort FPSes + let mut framerate_map = frame_rates + .iter() + .map(|x| { + let abs = x as i32 - c.frame_rate() as i32; + (abs.abs(), *x) + }) + .collect::>(); + framerate_map.sort(); + let frame_rate = framerate_map.first()?.1; + Some(CameraFormat::new(resolution, c.format(), frame_rate)) + } + RequestedFormat::None => all_formats.first().map(|x| *x), + } + } +} + +/// Describes the index of the camera. +/// - Index: A numbered index +/// - String: A string, used for IPCameras. +#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum CameraIndex { + Index(u32), + String(String), +} + +impl CameraIndex { + /// Turns this value into a number. If it is a string, it will attempt to parse it as a `u32`. + /// # Errors + /// Fails if the value is not a number. + pub fn as_index(&self) -> Result { + match self { + CameraIndex::Index(i) => Ok(*i), + CameraIndex::String(s) => s + .parse::() + .map_err(|why| NokhwaError::GeneralError(why.to_string())), + } + } + + /// Turns this value into a `String`. If it is a number, it will be automatically converted. + #[must_use] + pub fn as_string(&self) -> String { + match self { + CameraIndex::Index(i) => i.to_string(), + CameraIndex::String(s) => s.to_string(), + } + } + + /// Returns true if this [`CameraIndex`] contains an [`CameraIndex::Index`] + #[must_use] + pub fn is_index(&self) -> bool { + match self { + CameraIndex::Index(_) => true, + CameraIndex::String(_) => false, + } + } + + /// Returns true if this [`CameraIndex`] contains an [`CameraIndex::String`] + #[must_use] + pub fn is_string(&self) -> bool { + !self.is_index() + } +} + +impl Display for CameraIndex { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_string()) + } +} + +impl AsRef for CameraIndex { + fn as_ref(&self) -> &str { + self.to_string().as_str() + } +} + +impl TryFrom for u32 { + type Error = NokhwaError; + + fn try_from(value: CameraIndex) -> Result { + value.as_index() + } +} + +impl TryFrom for usize { + type Error = NokhwaError; + + fn try_from(value: CameraIndex) -> Result { + value.as_index().map(|i| i as usize) + } +} + +/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. +/// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) +/// - MJPEG is a motion-jpeg compressed frame, it allows for high frame rates. +/// - GRAY is a grayscale image format, usually for specialized cameras such as IR Cameras. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum FrameFormat { + MJPEG, + YUYV, + GRAY, +} + +impl Display for FrameFormat { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FrameFormat::MJPEG => { + write!(f, "MJPG") + } + FrameFormat::YUYV => { + write!(f, "YUYV") + } + FrameFormat::GRAY => { + write!(f, "GRAY") + } + } + } +} + +#[cfg(feature = "input-uvc")] +impl From for uvc::FrameFormat { + fn from(ff: FrameFormat) -> Self { + match ff { + FrameFormat::MJPEG => uvc::FrameFormat::MJPEG, + FrameFormat::YUYV => uvc::FrameFormat::YUYV, + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for FrameFormat { + fn from(mf_ff: MFFrameFormat) -> Self { + match mf_ff { + MFFrameFormat::MJPEG => FrameFormat::MJPEG, + MFFrameFormat::YUYV => FrameFormat::YUYV, + MFFrameFormat::GRAY => FrameFormat::GRAY, + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for MFFrameFormat { + fn from(ff: FrameFormat) -> Self { + match ff { + FrameFormat::MJPEG => MFFrameFormat::MJPEG, + FrameFormat::YUYV => MFFrameFormat::YUYV, + FrameFormat::GRAY => MFFrameFormat::GRAY8, //FIXME + } + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +impl From for FrameFormat { + fn from(av_fcc: AVFourCC) -> Self { + match av_fcc { + AVFourCC::YUV2 => FrameFormat::YUYV, + AVFourCC::MJPEG => FrameFormat::MJPEG, + AVFourCC::GRAY8 => FrameFormat::GRAY, + } + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +impl From for AVFourCC { + fn from(ff: FrameFormat) -> Self { + match ff { + FrameFormat::MJPEG => AVFourCC::MJPEG, + FrameFormat::YUYV => AVFourCC::YUV2, + FrameFormat::GRAY => AVFourCC::GRAY8, + } + } +} + +#[must_use] +pub const fn frame_formats() -> [FrameFormat; 3] { + [FrameFormat::MJPEG, FrameFormat::YUYV, FrameFormat::GRAY] +} + +/// Describes a Resolution. +/// This struct consists of a Width and a Height value (x,y).
+/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest. +/// # JS-WASM +/// This is exported as `JSResolution` +#[cfg_attr(feature = "output-wasm", wasm_bindgen)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +pub struct Resolution { + pub width_x: u32, + pub height_y: u32, +} + +#[cfg_attr(feature = "output-wasm", wasm_bindgen)] +impl Resolution { + /// Create a new resolution from 2 image size coordinates. + /// # JS-WASM + /// This is exported as a constructor for [`Resolution`]. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))] + pub fn new(x: u32, y: u32) -> Self { + Resolution { + width_x: x, + height_y: y, + } + } + + /// Get the width of Resolution + /// # JS-WASM + /// This is exported as `get_Width`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Width))] + pub fn width(self) -> u32 { + self.width_x + } + + /// Get the height of Resolution + /// # JS-WASM + /// This is exported as `get_Height`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Height))] + pub fn height(self) -> u32 { + self.height_y + } + + /// Get the x (width) of Resolution + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))] + pub fn x(self) -> u32 { + self.width_x + } + + /// Get the y (height) of Resolution + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))] + pub fn y(self) -> u32 { + self.height_y + } +} + +impl Display for Resolution { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}x{}", self.x(), self.y()) + } +} + +impl PartialOrd for Resolution { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Resolution { + fn cmp(&self, other: &Self) -> Ordering { + match self.x().cmp(&other.x()) { + Ordering::Less => Ordering::Less, + Ordering::Equal => self.y().cmp(&other.y()), + Ordering::Greater => Ordering::Greater, + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for Resolution { + fn from(mf_res: MFResolution) -> Self { + Resolution { + width_x: mf_res.width_x, + height_y: mf_res.height_y, + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for MFResolution { + fn from(res: Resolution) -> Self { + MFResolution { + width_x: res.width(), + height_y: res.height(), + } + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +#[allow(clippy::cast_sign_loss)] +impl From for Resolution { + fn from(res: AVVideoResolution) -> Self { + Resolution { + width_x: res.width as u32, + height_y: res.height as u32, + } + } +} + +/// This is a convenience struct that holds all information about the format of a webcam stream. +/// It consists of a [`Resolution`], [`FrameFormat`], and a frame rate(u8). +#[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CameraFormat { + resolution: Resolution, + format: FrameFormat, + frame_rate: u32, +} + +impl CameraFormat { + /// Construct a new [`CameraFormat`] + #[must_use] + pub fn new(resolution: Resolution, format: FrameFormat, frame_rate: u32) -> Self { + CameraFormat { + resolution, + format, + frame_rate, + } + } + + /// [`CameraFormat::new()`], but raw. + #[must_use] + pub fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: u32) -> Self { + CameraFormat { + resolution: Resolution { + width_x: res_x, + height_y: res_y, + }, + format, + frame_rate: fps, + } + } + + /// Get the resolution of the current [`CameraFormat`] + #[must_use] + pub fn resolution(&self) -> Resolution { + self.resolution + } + + /// Get the width of the resolution of the current [`CameraFormat`] + #[must_use] + pub fn width(&self) -> u32 { + self.resolution.width() + } + + /// Get the height of the resolution of the current [`CameraFormat`] + #[must_use] + pub fn height(&self) -> u32 { + self.resolution.height() + } + + /// Set the [`CameraFormat`]'s resolution. + pub fn set_resolution(&mut self, resolution: Resolution) { + self.resolution = resolution; + } + + /// Get the frame rate of the current [`CameraFormat`] + #[must_use] + pub fn frame_rate(&self) -> u32 { + self.frame_rate + } + + /// Set the [`CameraFormat`]'s frame rate. + pub fn set_frame_rate(&mut self, frame_rate: u32) { + self.frame_rate = frame_rate; + } + + /// Get the [`CameraFormat`]'s format. + #[must_use] + pub fn format(&self) -> FrameFormat { + self.format + } + + /// Set the [`CameraFormat`]'s format. + pub fn set_format(&mut self, format: FrameFormat) { + self.format = format; + } +} + +#[cfg(feature = "input-uvc")] +impl From for StreamFormat { + fn from(cf: CameraFormat) -> Self { + StreamFormat { + width: cf.width(), + height: cf.height(), + fps: cf.frame_rate(), + format: cf.format().into(), + } + } +} + +impl Default for CameraFormat { + fn default() -> Self { + CameraFormat { + resolution: Resolution::new(640, 480), + format: FrameFormat::MJPEG, + frame_rate: 30, + } + } +} + +impl Display for CameraFormat { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}@{}FPS, {} Format", + self.resolution, self.frame_rate, self.format + ) + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for CameraFormat { + fn from(mf_cam_fmt: MFCameraFormat) -> Self { + CameraFormat { + resolution: mf_cam_fmt.resolution().into(), + format: mf_cam_fmt.format().into(), + frame_rate: mf_cam_fmt.frame_rate(), + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for MFCameraFormat { + fn from(cf: CameraFormat) -> Self { + MFCameraFormat::new(cf.resolution.into(), cf.format.into(), cf.frame_rate) + } +} + +#[cfg(all(feature = "input-v4l", target_os = "linux"))] +impl From for Format { + fn from(cam_fmt: CameraFormat) -> Self { + let pxfmt = match cam_fmt.format() { + FrameFormat::MJPEG => FourCC::new(b"MJPG"), + FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY => FourCC::new(b"GREY"), + }; + + Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt) + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +#[allow(clippy::cast_possible_wrap)] +#[allow(clippy::cast_lossless)] +impl From for CaptureDeviceFormatDescriptor { + fn from(cf: CameraFormat) -> Self { + CaptureDeviceFormatDescriptor { + resolution: AVVideoResolution { + width: cf.width() as i32, + height: cf.height() as i32, + }, + fps: cf.frame_rate(), + fourcc: cf.format().into(), + } + } +} + +/// Information about a Camera e.g. its name. +/// `description` amd `misc` may contain information that may differ from backend to backend. Refer to each backend for details. +/// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system. +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] +#[cfg_attr(feature = "output-wasm", wasm_bindgen)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CameraInfo { + human_name: String, + description: String, + misc: String, + index: CameraIndex, +} + +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = CameraInfo))] +impl CameraInfo { + /// Create a new [`CameraInfo`]. + /// # JS-WASM + /// This is exported as a constructor for [`CameraInfo`]. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))] + // OK, i just checkeed back on this code. WTF was I on when I wrote `&(impl AsRef + ?Sized)` ???? + // I need to get on the same shit that my previous self was on, because holy shit that stuff is strong as FUCK! + // Finally fixed this insanity. Hopefully I didnt torment anyone by actually putting this in a stable release. + pub fn new(human_name: &str, description: &str, misc: &str, index: CameraIndex) -> Self { + CameraInfo { + human_name: human_name.to_string(), + description: description.to_string(), + misc: misc.to_string(), + index, + } + } + + /// Get a reference to the device info's human readable name. + /// # JS-WASM + /// This is exported as a `get_HumanReadableName`. + #[must_use] + #[cfg_attr( + feature = "output-wasm", + wasm_bindgen(getter = HumanReadableName) + )] + // yes, i know, unnecessary alloc this, unnecessary alloc that + // but wasm bindgen + pub fn human_name(&self) -> String { + self.human_name.clone() + } + + /// Set the device info's human name. + /// # JS-WASM + /// This is exported as a `set_HumanReadableName`. + #[cfg_attr( + feature = "output-wasm", + wasm_bindgen(setter = HumanReadableName) + )] + pub fn set_human_name(&mut self, human_name: &str) { + self.human_name = human_name.to_string(); + } + + /// Get a reference to the device info's description. + /// # JS-WASM + /// This is exported as a `get_Description`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Description))] + pub fn description(&self) -> &str { + self.description.borrow() + } + + /// Set the device info's description. + /// # JS-WASM + /// This is exported as a `set_Description`. + #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Description))] + pub fn set_description(&mut self, description: &str) { + self.description = description.to_string(); + } + + /// Get a reference to the device info's misc. + /// # JS-WASM + /// This is exported as a `get_MiscString`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = MiscString))] + pub fn misc(&self) -> String { + self.misc.clone() + } + + /// Set the device info's misc. + /// # JS-WASM + /// This is exported as a `set_MiscString`. + #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = MiscString))] + pub fn set_misc(&mut self, misc: &str) { + self.misc = misc.to_string(); + } + + /// Get a reference to the device info's index. + /// # JS-WASM + /// This is exported as a `get_Index`. + #[must_use] + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index))] + pub fn index(&self) -> &CameraIndex { + &self.index + } + + /// Set the device info's index. + /// # JS-WASM + /// This is exported as a `set_Index`. + #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Index))] + pub fn set_index(&mut self, index: CameraIndex) { + self.index = index; + } + + // /// Gets the device info's index as an `u32`. + // /// # Errors + // /// If the index is not parsable as a `u32`, this will error. + // /// # JS-WASM + // /// This is exported as `get_Index_Int` + // #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] + // pub fn index_num(&self) -> Result { + // match &self.index { + // CameraIndex::Index(i) => Ok(*i), + // CameraIndex::String(s) => match s.parse::() { + // Ok(p) => Ok(p), + // Err(why) => Err(NokhwaError::GetPropertyError { + // property: "index-int".to_string(), + // error: why.to_string(), + // }), + // }, + // } + // } +} + +impl Display for CameraInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Name: {}, Description: {}, Extra: {}, Index: {}", + self.human_name, self.description, self.misc, self.index + ) + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From> for CameraInfo { + fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { + CameraInfo { + human_name: dev_desc.name_as_string(), + description: "Media Foundation Device".to_string(), + misc: dev_desc.link_as_string(), + index: dev_desc.index() as u32, + } + } +} + +#[cfg(any( + all( + feature = "input-avfoundation", + any(target_os = "macos", target_os = "ios") + ), + all( + feature = "docs-only", + feature = "docs-nolink", + feature = "input-avfoundation" + ) +))] +#[allow(clippy::cast_possible_truncation)] +impl From for CameraInfo { + fn from(descriptor: AVCaptureDeviceDescriptor) -> Self { + CameraInfo { + human_name: descriptor.name, + description: descriptor.description, + misc: descriptor.misc, + index: descriptor.index as u32, + } + } +} + +/// The list of known camera controls to the library.
+/// These can control the picture brightness, etc.
+/// Note that not all backends/devices support all these. Run [`supported_camera_controls()`](crate::CaptureBackendTrait::camera_controls) to see which ones can be set. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KnownCameraControl { + Brightness, + Contrast, + Hue, + Saturation, + Sharpness, + Gamma, + WhiteBalance, + BacklightComp, + Gain, + Pan, + Tilt, + Zoom, + Exposure, + Iris, + Focus, + /// Other camera control. Listed is the ID. + /// Wasteful, however is needed for a unified API across Windows, Linux, and MacOSX due to Microsoft's usage of GUIDs. + /// + /// THIS SHOULD ONLY BE USED WHEN YOU KNOW THE PLATFORM THAT YOU ARE RUNNING ON. + Other(u128), +} + +/// All camera controls in an array. +#[must_use] +pub const fn all_known_camera_controls() -> [KnownCameraControl; 15] { + [ + KnownCameraControl::Brightness, + KnownCameraControl::Contrast, + KnownCameraControl::Hue, + KnownCameraControl::Saturation, + KnownCameraControl::Sharpness, + KnownCameraControl::Gamma, + KnownCameraControl::WhiteBalance, + KnownCameraControl::BacklightComp, + KnownCameraControl::Gain, + KnownCameraControl::Pan, + KnownCameraControl::Tilt, + KnownCameraControl::Zoom, + KnownCameraControl::Exposure, + KnownCameraControl::Iris, + KnownCameraControl::Focus, + ] +} + +impl Display for KnownCameraControl { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &self) + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for KnownCameraControl { + fn from(mf_c: MediaFoundationControls) -> Self { + match mf_c { + MediaFoundationControls::Brightness => KnownCameraControl::Brightness, + MediaFoundationControls::Contrast => KnownCameraControl::Contrast, + MediaFoundationControls::Hue => KnownCameraControl::Hue, + MediaFoundationControls::Saturation => KnownCameraControl::Saturation, + MediaFoundationControls::Sharpness => KnownCameraControl::Sharpness, + MediaFoundationControls::Gamma => KnownCameraControl::Gamma, + MediaFoundationControls::WhiteBalance => KnownCameraControl::WhiteBalance, + MediaFoundationControls::BacklightComp => KnownCameraControl::BacklightComp, + MediaFoundationControls::Gain => KnownCameraControl::Gain, + MediaFoundationControls::Pan => KnownCameraControl::Pan, + MediaFoundationControls::Tilt => KnownCameraControl::Tilt, + MediaFoundationControls::Zoom => KnownCameraControl::Zoom, + MediaFoundationControls::Exposure => KnownCameraControl::Exposure, + MediaFoundationControls::Iris => KnownCameraControl::Iris, + MediaFoundationControls::Focus => KnownCameraControl::Focus, + MediaFoundationControls::ColorEnable => KnownCameraControl::Other(0), + MediaFoundationControls::Roll => KnownCameraControl::Other(1), + } + } +} + +#[cfg(any( + all(feature = "input-msmf", target_os = "windows"), + all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") +))] +impl From for KnownCameraControl { + fn from(mf_cc: MFControl) -> Self { + mf_cc.control().into() + } +} + +#[cfg(all(feature = "input-v4l", target_os = "linux"))] +impl From for KnownCameraControl { + fn from(value: Description) -> KnownCameraControl { + match value.id { + 9_963_776 => KnownCameraControl::Brightness, + 9_963_777 => KnownCameraControl::Contrast, + 9_963_779 => KnownCameraControl::Hue, + 9_963_778 => KnownCameraControl::Saturation, + 9_963_803 => KnownCameraControl::Sharpness, + 9_963_792 => KnownCameraControl::Gamma, + 9_963_802 => KnownCameraControl::WhiteBalance, + 9_963_804 => KnownCameraControl::BacklightComp, + 9_963_795 => KnownCameraControl::Gain, + 10_094_852 => KnownCameraControl::Pan, + 10_094_853 => KnownCameraControl::Tilt, + 10_094_862 => KnownCameraControl::Zoom, + 10_094_850 => KnownCameraControl::Exposure, + 10_094_866 => KnownCameraControl::Iris, + 10_094_859 => KnownCameraControl::Focus, + id => KnownCameraControl::Other(id as u128), + } + } +} + +#[cfg(all(feature = "input-opencv"))] +impl From for KnownCameraControl { + fn from(id: i32) -> Self { + use opencv::videoio::*; + match id { + CAP_PROP_BRIGHTNESS => KnownCameraControl::Brightness, + CAP_PROP_CONTRAST => KnownCameraControl::Contrast, + CAP_PROP_HUE => KnownCameraControl::Hue, + CAP_PROP_SATURATION => KnownCameraControl::Saturation, + CAP_PROP_SHARPNESS => KnownCameraControl::Sharpness, + CAP_PROP_GAMMA => KnownCameraControl::Gamma, + CAP_PROP_WB_TEMPERATURE => KnownCameraControl::WhiteBalance, + CAP_PROP_BACKLIGHT => KnownCameraControl::BacklightComp, + CAP_PROP_GAIN => KnownCameraControl::Gain, + CAP_PROP_PAN => KnownCameraControl::Pan, + CAP_PROP_TILT => KnownCameraControl::Tilt, + CAP_PROP_ZOOM => KnownCameraControl::Zoom, + CAP_PROP_EXPOSURE => KnownCameraControl::Exposure, + CAP_PROP_IRIS => KnownCameraControl::Iris, + CAP_PROP_FOCUS => KnownCameraControl::Focus, + id => KnownCameraControl::Other(id as u128), + } + } +} + +#[cfg(all(feature = "input-opencv"))] +impl From for i32 { + fn from(kcc: KnownCameraControl) -> Self { + use opencv::videoio::*; + + match kcc { + KnownCameraControl::Brightness => CAP_PROP_BRIGHTNESS, + KnownCameraControl::Contrast => CAP_PROP_CONTRAST, + KnownCameraControl::Hue => CAP_PROP_HUE, + KnownCameraControl::Saturation => CAP_PROP_SATURATION, + KnownCameraControl::Sharpness => CAP_PROP_SHARPNESS, + KnownCameraControl::Gamma => CAP_PROP_GAMMA, + KnownCameraControl::WhiteBalance => CAP_PROP_WB_TEMPERATURE, + KnownCameraControl::BacklightComp => CAP_PROP_BACKLIGHT, + KnownCameraControl::Gain => CAP_PROP_GAIN, + KnownCameraControl::Pan => CAP_PROP_PAN, + KnownCameraControl::Tilt => CAP_PROP_TILT, + KnownCameraControl::Zoom => CAP_PROP_ZOOM, + KnownCameraControl::Exposure => CAP_PROP_EXPOSURE, + KnownCameraControl::Iris => CAP_PROP_IRIS, + KnownCameraControl::Focus => CAP_PROP_FOCUS, + KnownCameraControl::Other(id) => id as i32, + } + } +} + +/// This tells you weather a [`KnownCameraControl`] is automatically managed by the OS/Driver +/// or manually managed by you, the programmer. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub enum KnownCameraControlFlag { + Automatic, + Manual, + ReadOnly, + WriteOnly, + Volatile, + Disabled, + Inactive, +} + +impl Display for KnownCameraControlFlag { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +// TODO: use in CameraControl +/// The values for a [`CameraControl`]. +/// +/// This provides a wide range of values that can be used to control a camera. +pub enum ControlValueDescription { + None, + Integer { + value: i64, + default: i64, + step: i64, + }, + IntegerRange { + min: i64, + max: i64, + value: i64, + step: i64, + default: i64, + }, + Float { + value: f64, + default: f64, + step: f64, + }, + FloatRange { + min: f64, + max: f64, + value: f64, + step: f64, + default: f64, + }, + Boolean { + value: bool, + default: bool, + }, + String { + value: String, + default: Option, + }, + Bytes { + value: Vec, + default: Vec, + }, +} + +impl ControlValueDescription { + /// Get the value of this [`ControlValueDescription`] + #[must_use] + pub fn value(&self) -> ControlValueSetter { + match self { + ControlValueDescription::None => ControlValueSetter::None, + ControlValueDescription::Integer { value, .. } + | ControlValueDescription::IntegerRange { value, .. } => { + ControlValueSetter::Integer(*value) + } + ControlValueDescription::Float { value, .. } + | ControlValueDescription::FloatRange { value, .. } => { + ControlValueSetter::Float(*value) + } + ControlValueDescription::Boolean { value, .. } => ControlValueSetter::Boolean(*value), + ControlValueDescription::String { value, .. } => { + ControlValueSetter::String(value.clone()) + } + ControlValueDescription::Bytes { value, .. } => { + ControlValueSetter::Bytes(value.clone()) + } + } + } + + /// Verifies if the [setter](crate::utils::ControlValueSetter) is valid for the provided [`ControlValueDescription`]. + /// - `true` => Is valid. + /// - `false` => Is not valid. + #[must_use] + pub fn verify_setter(&self, setter: &ControlValueSetter) -> bool { + match setter { + ControlValueSetter::None => { + matches!(self, ControlValueDescription::None) + } + ControlValueSetter::Integer(i) => match self { + ControlValueDescription::Integer { + value, + default, + step, + } => (i - default).abs() % step == 0 || (i - value) % step == 0, + ControlValueDescription::IntegerRange { + min, + max, + value, + step, + default, + } => { + if value > max || value < min { + return false; + } + + (i - default) % step == 0 || (i - value) % step == 0 + } + _ => false, + }, + ControlValueSetter::Float(f) => match self { + ControlValueDescription::Float { + value, + default, + step, + } => (f - default).abs() % step == 0_f64 || (f - value) % step == 0_f64, + ControlValueDescription::FloatRange { + min, + max, + value, + step, + default, + } => { + if value > max || value < min { + return false; + } + + (f - default) % step == 0_f64 || (f - value) % step == 0_f64 + } + _ => false, + }, + ControlValueSetter::Boolean(_) => { + matches!(self, ControlValueDescription::Boolean { .. }) + } + ControlValueSetter::String(_) => { + matches!(self, ControlValueDescription::String { .. }) + } + ControlValueSetter::Bytes(_) => { + matches!(self, ControlValueDescription::Bytes { .. }) + } + } + } +} + +impl Display for ControlValueDescription { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ControlValueDescription::None => { + write!(f, "(None)") + } + ControlValueDescription::Integer { + value, + default, + step, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {})", + value, default, step + ) + } + ControlValueDescription::IntegerRange { + min, + max, + value, + step, + default, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {}, Range: ({}, {}))", + value, default, step, min, max + ) + } + ControlValueDescription::Float { + value, + default, + step, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {})", + value, default, step + ) + } + ControlValueDescription::FloatRange { + min, + max, + value, + step, + default, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {}, Range: ({}, {}))", + value, default, step, min, max + ) + } + ControlValueDescription::Boolean { value, default } => { + write!(f, "(Current: {}, Default: {})", value, default) + } + ControlValueDescription::String { value, default } => { + write!(f, "(Current: {}, Default: {:?})", value, default) + } + ControlValueDescription::Bytes { value, default } => { + write!(f, "(Current: {:x?}, Default: {:x?})", value, default) + } + } + } +} + +// fn step_chk(val: i64, default: i64, step: i64) -> Result<(), NokhwaError> { +// if (val - default) % step != 0 { +// return Err(NokhwaError::StructureError { +// structure: "Value".to_string(), +// error: "Doesnt fit step".to_string(), +// }); +// } +// Ok(()) +// } + +/// This struct tells you everything about a particular [`KnownCameraControl`]. +/// +/// However, you should never need to instantiate this struct, since its usually generated for you by `nokhwa`. +/// The only time you should be modifying this struct is when you need to set a value and pass it back to the camera. +/// NOTE: Assume the values for `min` and `max` as **non-inclusive**!. +/// E.g. if the [`CameraControl`] says `min` is 100, the minimum is actually 101. +#[derive(Clone, Debug, PartialOrd, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CameraControl { + control: KnownCameraControl, + name: String, + description: ControlValueDescription, + value: ControlValueSetter, + flag: Vec, + active: bool, +} + +impl CameraControl { + /// Creates a new [`CameraControl`] + #[must_use] + pub fn new( + control: KnownCameraControl, + name: String, + description: ControlValueDescription, + flag: Vec, + active: bool, + ) -> Self { + let value = description.value(); + CameraControl { + control, + name, + description, + value, + flag, + active, + } + } + + /// Gets the [`KnownCameraControl`] of this [`CameraControl`] + #[must_use] + pub fn control(&self) -> KnownCameraControl { + self.control + } + + /// Gets the current value description of this [`CameraControl`] + #[must_use] + pub fn value(&self) -> &ControlValueDescription { + &self.description + } + + /// Sets the value of this [`CameraControl`] + /// # Errors + /// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error + pub fn set_value(&mut self, value: ControlValueSetter) -> Result<(), NokhwaError> { + if !self.description.verify_setter(&value) { + return Err(NokhwaError::SetPropertyError { + property: format!("ControlValueDescription: {}", self.description), + value: format!("ControlValueSetter: {}", self.value), + error: "Invalid Value.".to_string(), + }); + } + + self.value = value; + Ok(()) + } + + /// Gets the [`KnownCameraControlFlag`] of this [`CameraControl`], + /// telling you weather this control is automatically set or manually set. + #[must_use] + pub fn flag(&self) -> &[KnownCameraControlFlag] { + &self.flag + } + + /// Gets `active` of this [`CameraControl`], + /// telling you weather this control is currently active(in-use). + #[must_use] + pub fn active(&self) -> bool { + self.active + } + + /// Gets `active` of this [`CameraControl`], + /// telling you weather this control is currently active(in-use). + pub fn set_active(&mut self, active: bool) { + self.active = active; + } +} + +impl Display for CameraControl { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Control: {}, Name: {}, Value: {}, Flag: {:?}, Active: {}", + self.control, self.name, self.description, self.flag, self.active + ) + } +} + +/// The setter for a control value +#[derive(Clone, Debug, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ControlValueSetter { + None, + Integer(i64), + Float(f64), + Boolean(bool), + String(String), + Bytes(Vec), +} + +impl Display for ControlValueSetter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ControlValueSetter::None => { + write!(f, "Value: None") + } + ControlValueSetter::Integer(i) => { + write!(f, "Value: {}", i) + } + ControlValueSetter::Float(d) => { + write!(f, "Value: {}", d) + } + ControlValueSetter::Boolean(b) => { + write!(f, "Value: {}", b) + } + ControlValueSetter::String(s) => { + write!(f, "Value: {}", s) + } + ControlValueSetter::Bytes(b) => { + write!(f, "Value: {:x?}", b) + } + } + } +} + +/// The list of known capture backends to the library.
+/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform. +/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` +/// - `Video4Linux` - `Video4Linux2`, a linux specific backend. +/// - `UniversalVideoClass` - ***DEPRECATED*** Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use. +/// - `MediaFoundation` - Microsoft Media Foundation, Windows only, +/// - `OpenCv` - Uses `OpenCV` to capture. Platform agnostic. +/// - `GStreamer` - ***DEPRECATED*** Uses `GStreamer` RTP to capture. Platform agnostic. +/// - `Network` - Uses `OpenCV` to capture from an IP. +/// - `Browser` - Uses browser APIs to capture from a webcam. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ApiBackend { + Auto, + AVFoundation, + Video4Linux, + #[deprecated] + UniversalVideoClass, + MediaFoundation, + OpenCv, + #[deprecated] + GStreamer, + Network, + Browser, +} + +impl Display for ApiBackend { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let self_str = format!("{:?}", self); + write!(f, "{}", self_str) + } +} + +// /// A webcam index that supports both strings and integers. Most backends take an int, but `IPCamera`s take a URL (string). +// #[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] +// pub enum CameraIndex { +// Index(u32), +// String(String), +// } + +// impl CameraIndex { +// /// Gets the device info's index as an `u32`. +// /// # Errors +// /// If the index is not parsable as a `u32`, this will error. +// pub fn as_index(&self) -> Result { +// match self { +// CameraIndex::Index(i) => Ok(*i), +// CameraIndex::String(s) => match s.parse::() { +// Ok(p) => Ok(p), +// Err(why) => Err(NokhwaError::GetPropertyError { +// property: "index-int".to_string(), +// error: why.to_string(), +// }), +// }, +// } +// } +// } + +// impl Display for CameraIndex { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// match self { +// CameraIndex::Index(idx) => { +// write!(f, "{}", idx) +// } +// CameraIndex::String(ip) => { +// write!(f, "{}", ip) +// } +// } +// } +// } + +// impl From for CameraIndex { +// fn from(v: u32) -> Self { +// CameraIndex::Index(v) +// } +// } + +// /// Trait for strings that can be converted to [`CameraIndex`]es. +// pub trait ValidString: AsRef {} +// +// impl ValidString for String {} +// impl<'a> ValidString for &'a String {} +// impl<'a> ValidString for &'a mut String {} +// impl<'a> ValidString for Cow<'a, str> {} +// impl<'a> ValidString for &'a Cow<'a, str> {} +// impl<'a> ValidString for &'a mut Cow<'a, str> {} +// impl<'a> ValidString for &'a str {} +// impl<'a> ValidString for &'a mut str {} + +// impl From for CameraIndex +// where +// T: ValidString, +// { +// fn from(v: T) -> Self { +// CameraIndex::String(v.as_ref().to_string()) +// } +// } + +/// Converts a MJPEG stream of [u8] into a Vec of RGB888. (R,G,B,R,G,B,...) +/// # Errors +/// If `mozjpeg` fails to read scanlines or setup the decompressor, this will error. +/// # Safety +/// This function uses `unsafe`. The caller must ensure that: +/// - The input data is of the right size, does not exceed bounds, and/or the final size matches with the initial size. +#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] +#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + use mozjpeg::Decompress; + + let mut jpeg_decompress = match Decompress::new_mem(data) { + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + } + } + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + }; + + let scanlines_res: Option> = jpeg_decompress.read_scanlines_flat(); + // assert!(jpeg_decompress.finish_decompress()); + if !jpeg_decompress.finish_decompress() { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "JPEG Decompressor did not finish.".to_string(), + }); + } + + match scanlines_res { + Some(pixels) => Ok(pixels), + None => Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "Failed to get read readlines into RGB888 pixels!".to_string(), + }), + } +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) +} + +/// Equivalent to [`mjpeg_to_rgb`] except with a destination buffer. +/// # Errors +/// If the decoding fails (e.g. invalid MJPEG stream), the buffer is not large enough, or you are doing this on `WebAssembly`, this will error. +#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] +#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + use mozjpeg::Decompress; + + let mut jpeg_decompress = match Decompress::new_mem(data) { + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + } + } + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + }; + + // assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size()); + if dest.len() != jpeg_decompress.min_flat_buffer_size() { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "Bad decoded buffer size".to_string(), + }); + } + + jpeg_decompress.read_scanlines_flat_into(dest); + // assert!(jpeg_decompress.finish_decompress()); + if !jpeg_decompress.finish_decompress() { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "JPEG Decompressor did not finish.".to_string(), + }); + } + Ok(()) +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) +} + +// For those maintaining this, I recommend you read: https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#yuy2 +// https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB +// and this too: https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte +// The YUY2(YUYV) format is a 16 bit format. We read 4 bytes at a time to get 6 bytes of RGB888. +// First, the YUY2 is converted to YCbCr 4:4:4 (4:2:2 -> 4:4:4) +// then it is converted to 6 bytes (2 pixels) of RGB888 +/// Converts a YUYV 4:2:2 datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) +/// # Errors +/// This may error when the data stream size is not divisible by 4, a i32 -> u8 conversion fails, or it fails to read from a certain index. +pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" + .to_string(), + }); + } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + let mut dest = vec![0; rgb_buf_size]; + buf_yuyv422_to_rgb(data, &mut dest, rgba)?; + + Ok(dest) +} + +/// Same as [`yuyv422_to_rgb`] but with a destination buffer instead of a return `Vec` +/// # Errors +/// If the stream is invalid YUYV, or the destination buffer is not large enough, this will error. +pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" + .to_string(), + }); + } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + if dest.len() != rgb_buf_size { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()), + }); + } + + let iter = data.chunks_exact(4); + + if rgba { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = i32::from(yuyv[0]); + let u = i32::from(yuyv[1]); + let y2 = i32::from(yuyv[2]); + let v = i32::from(yuyv[3]); + let pixel1 = yuyv444_to_rgba(y1, u, v); + let pixel2 = yuyv444_to_rgba(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + for i in dest.iter_mut().take(rgb_buf_size) { + *i = match iter.next() { + Some(v) => v, + None => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGBA8888".to_string(), + error: "Ran out of RGBA YUYV values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string() + }) + } + } + } + } else { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = i32::from(yuyv[0]); + let u = i32::from(yuyv[1]); + let y2 = i32::from(yuyv[2]); + let v = i32::from(yuyv[3]); + let pixel1 = yuyv444_to_rgb(y1, u, v); + let pixel2 = yuyv444_to_rgb(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + + for i in dest.iter_mut().take(rgb_buf_size) { + *i = match iter.next() { + Some(v) => v, + None => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Ran out of RGB YUYV values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string() + }) + } + } + } + } + + Ok(()) +} + +// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB +/// Convert `YCbCr` 4:4:4 to a RGB888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) +#[allow(clippy::many_single_char_names)] +#[allow(clippy::cast_possible_truncation)] +#[allow(clippy::cast_sign_loss)] +#[must_use] +#[inline] +pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { + let c298 = (y - 16) * 298; + let d = u - 128; + let e = v - 128; + let r = ((c298 + 409 * e + 128) >> 8) as u8; + let g = ((c298 - 100 * d - 208 * e + 128) >> 8) as u8; + let b = ((c298 + 516 * d + 128) >> 8) as u8; + [r, g, b] +} + +// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB +/// Convert `YCbCr` 4:4:4 to a RGBA8888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) +/// +/// Equivalent to [`yuyv444_to_rgb`] but with an alpha channel attached. +#[allow(clippy::many_single_char_names)] +#[must_use] +#[inline] +pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] { + let [r, g, b] = yuyv444_to_rgb(y, u, v); + [r, g, b, 255] +} diff --git a/src/backends/capture/msmf_backend.rs b/src/backends/capture/msmf_backend.rs index f0f1702..7caff17 100644 --- a/src/backends/capture/msmf_backend.rs +++ b/src/backends/capture/msmf_backend.rs @@ -16,11 +16,14 @@ use crate::{ all_known_camera_controls, mjpeg_to_rgb, yuyv422_to_rgb, ApiBackend, CameraControl, - CameraFormat, CameraInfo, CaptureBackendTrait, FrameFormat, KnownCameraControl, - KnownCameraControlFlag, NokhwaError, Resolution, + CameraFormat, CameraIndex, CameraInfo, CaptureBackendTrait, FrameFormat, KnownCameraControl, + KnownCameraControlFlag, NokhwaError, RequestedFormat, Resolution, }; use image::{ImageBuffer, Rgb}; -use nokhwa_bindings_windows::{wmf::MediaFoundationDevice, MFControl, MediaFoundationControls}; +use nokhwa_bindings_windows::{ + wmf::MediaFoundationDevice, MFCameraFormat, MFControl, MFFrameFormat, MFResolution, + MediaFoundationControls, +}; use std::{any::Any, borrow::Cow, collections::HashMap}; /// The backend that deals with Media Foundation on Windows. @@ -42,21 +45,36 @@ pub struct MediaFoundationCaptureDevice<'a> { impl<'a> MediaFoundationCaptureDevice<'a> { /// Creates a new capture device using the Media Foundation backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery. - /// - /// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default. /// # Errors /// This function will error if Media Foundation fails to get the device. - pub fn new(index: usize, camera_fmt: Option) -> Result { - let format = camera_fmt.unwrap_or_default(); - let mf_device = MediaFoundationDevice::new(index, format.into())?; + pub fn new(index: CameraIndex, camera_fmt: RequestedFormat) -> Result { + let mut mf_device = MediaFoundationDevice::new(index.as_index()? as usize)?; let info = CameraInfo::new( - mf_device.name(), - "MediaFoundation Camera Device".to_string(), - mf_device.symlink(), - mf_device.index(), + &mf_device.name(), + &"MediaFoundation Camera Device".to_string(), + &mf_device.symlink(), + index, ); + let availible = mf_device + .compatible_format_list()? + .into_iter() + .map(|x| { + let cf: CameraFormat = x.into(); + cf + }) + .collect::>(); + + let desired = camera_fmt + .fufill(&availible) + .ok_or(NokhwaError::InitializeError { + backend: ApiBackend::MediaFoundation, + error: "Failed to fulfill requested format".to_string(), + })?; + + mf_device.set_format(desired.into())?; + Ok(MediaFoundationCaptureDevice { inner: mf_device, info, @@ -66,16 +84,56 @@ impl<'a> MediaFoundationCaptureDevice<'a> { /// Create a new Media Foundation Device with desired settings. /// # Errors /// This function will error if Media Foundation fails to get the device. + #[deprecated(since = "0.10", note = "please use `new` instead.")] pub fn new_with( - index: usize, + index: CameraIndex, width: u32, height: u32, fps: u32, fourcc: FrameFormat, ) -> Result { - let camera_format = Some(CameraFormat::new_from(width, height, fourcc, fps)); + let camera_format = + RequestedFormat::Exact(CameraFormat::new_from(width, height, fourcc, fps)); MediaFoundationCaptureDevice::new(index, camera_format) } + + /// Gets the list of supported [`KnownCameraControl`]s + /// # Errors + /// May error if there is an error from `MediaFoundation`. + fn supported_camera_controls(&self) -> Result, NokhwaError> { + let mut supported_camera_controls: Vec = vec![]; + + for camera_control in all_known_camera_controls() { + let msmf_camera_control: MediaFoundationControls = match camera_control { + KnownCameraControl::Brightness => MediaFoundationControls::Brightness, + KnownCameraControl::Contrast => MediaFoundationControls::Contrast, + KnownCameraControl::Hue => MediaFoundationControls::Hue, + KnownCameraControl::Saturation => MediaFoundationControls::Saturation, + KnownCameraControl::Sharpness => MediaFoundationControls::Sharpness, + KnownCameraControl::Gamma => MediaFoundationControls::Gamma, + KnownCameraControl::WhiteBalance => MediaFoundationControls::WhiteBalance, + KnownCameraControl::BacklightComp => MediaFoundationControls::BacklightComp, + KnownCameraControl::Gain => MediaFoundationControls::Gain, + KnownCameraControl::Pan => MediaFoundationControls::Pan, + KnownCameraControl::Tilt => MediaFoundationControls::Tilt, + KnownCameraControl::Zoom => MediaFoundationControls::Zoom, + KnownCameraControl::Exposure => MediaFoundationControls::Exposure, + KnownCameraControl::Iris => MediaFoundationControls::Iris, + KnownCameraControl::Focus => MediaFoundationControls::Focus, + KnownCameraControl::Other(id) => match id { + 0 => MediaFoundationControls::ColorEnable, + 1 => MediaFoundationControls::Roll, + _ => continue, + }, + }; + + if let Ok(supported) = self.inner.control(msmf_camera_control) { + supported_camera_controls.push(supported.control().into()); + } + } + + Ok(supported_camera_controls) + } } impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { @@ -87,6 +145,11 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { &self.info } + fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> { + let _ = self.inner.format_refreshed()?; + Ok(()) + } + fn camera_format(&self) -> CameraFormat { self.inner.format().into() } @@ -179,38 +242,6 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { self.set_camera_format(new_format) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { - let mut supported_camera_controls: Vec = vec![]; - - for camera_control in all_known_camera_controls() { - let msmf_camera_control: MediaFoundationControls = match camera_control { - KnownCameraControl::Brightness => MediaFoundationControls::Brightness, - KnownCameraControl::Contrast => MediaFoundationControls::Contrast, - KnownCameraControl::Hue => MediaFoundationControls::Hue, - KnownCameraControl::Saturation => MediaFoundationControls::Saturation, - KnownCameraControl::Sharpness => MediaFoundationControls::Sharpness, - KnownCameraControl::Gamma => MediaFoundationControls::Gamma, - KnownCameraControl::ColorEnable => MediaFoundationControls::ColorEnable, - KnownCameraControl::WhiteBalance => MediaFoundationControls::WhiteBalance, - KnownCameraControl::BacklightComp => MediaFoundationControls::BacklightComp, - KnownCameraControl::Gain => MediaFoundationControls::Gain, - KnownCameraControl::Pan => MediaFoundationControls::Pan, - KnownCameraControl::Tilt => MediaFoundationControls::Tilt, - KnownCameraControl::Roll => MediaFoundationControls::Roll, - KnownCameraControl::Zoom => MediaFoundationControls::Zoom, - KnownCameraControl::Exposure => MediaFoundationControls::Exposure, - KnownCameraControl::Iris => MediaFoundationControls::Iris, - KnownCameraControl::Focus => MediaFoundationControls::Focus, - }; - - if let Ok(supported) = self.inner.control(msmf_camera_control) { - supported_camera_controls.push(supported.control().into()); - } - } - - Ok(supported_camera_controls) - } - fn camera_control(&self, control: KnownCameraControl) -> Result { let msmf_camera_control: MediaFoundationControls = match control { KnownCameraControl::Brightness => MediaFoundationControls::Brightness, @@ -219,13 +250,11 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { KnownCameraControl::Saturation => MediaFoundationControls::Saturation, KnownCameraControl::Sharpness => MediaFoundationControls::Sharpness, KnownCameraControl::Gamma => MediaFoundationControls::Gamma, - KnownCameraControl::ColorEnable => MediaFoundationControls::ColorEnable, KnownCameraControl::WhiteBalance => MediaFoundationControls::WhiteBalance, KnownCameraControl::BacklightComp => MediaFoundationControls::BacklightComp, KnownCameraControl::Gain => MediaFoundationControls::Gain, KnownCameraControl::Pan => MediaFoundationControls::Pan, KnownCameraControl::Tilt => MediaFoundationControls::Tilt, - KnownCameraControl::Roll => MediaFoundationControls::Roll, KnownCameraControl::Zoom => MediaFoundationControls::Zoom, KnownCameraControl::Exposure => MediaFoundationControls::Exposure, KnownCameraControl::Iris => MediaFoundationControls::Iris, @@ -258,6 +287,10 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { ) } + fn camera_controls(&self) -> Result, NokhwaError> { + todo!() + } + fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError> { let ctrl = match control.control() { KnownCameraControl::Brightness => MediaFoundationControls::Brightness, @@ -301,28 +334,6 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { Ok(()) } - fn raw_supported_camera_controls(&self) -> Result>, NokhwaError> { - Err(NokhwaError::UnsupportedOperationError( - ApiBackend::MediaFoundation, - )) - } - - fn raw_camera_control(&self, _control: &dyn Any) -> Result, NokhwaError> { - Err(NokhwaError::UnsupportedOperationError( - ApiBackend::MediaFoundation, - )) - } - - fn set_raw_camera_control( - &mut self, - _control: &dyn Any, - _value: &dyn Any, - ) -> Result<(), NokhwaError> { - Err(NokhwaError::UnsupportedOperationError( - ApiBackend::MediaFoundation, - )) - } - fn open_stream(&mut self) -> Result<(), NokhwaError> { if let Err(why) = self.inner.start_stream() { return Err(why.into()); diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 7c566f9..8b13789 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -1,247 +1 @@ -/* - * Copyright 2022 l1npengtul / The Nokhwa Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -use crate::{ - error::NokhwaError, - utils::{CameraFormat, CameraInfo, FrameFormat, Resolution}, - Buffer, CameraControl, CameraIndex, ControlValueSetter, KnownCameraControl, RequestedFormat, -}; -use std::{borrow::Cow, collections::HashMap}; -#[cfg(feature = "output-wgpu")] -use wgpu::{ - Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue, - Texture as WgpuTexture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, - TextureUsages, -}; - -/// This trait is for any backend that allows you to grab and take frames from a camera. -/// Many of the backends are **blocking**, if the camera is occupied the library will block while it waits for it to become available. -/// -/// **Note**: -/// - Backends, if not provided with a camera format, will be spawned with 640x480@15 FPS, MJPEG [`CameraFormat`]. -/// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::Camera) struct abstracts most of this away, if you plan to use the raw backend structs please read the `Quirks` section of each backend. -/// - If you call [`stop_stream()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera. -pub trait CaptureBackendTrait { - /// Returns the current backend used. - fn backend(&self) -> crate::ApiBackend; - - /// Gets the camera information such as Name and Index as a [`CameraInfo`]. - fn camera_info(&self) -> &CameraInfo; - - /// Forcefully refreshes the stored camera format, bringing it into sync with "reality" (current camera state) - /// # Errors - /// If the camera can not get its most recent [`CameraFormat`]. this will error. - fn refresh_camera_format(&mut self) -> Result<(), NokhwaError>; - - /// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed. - fn camera_format(&self) -> CameraFormat; - - /// Will set the current [`CameraFormat`] - /// This will reset the current stream if used while stream is opened. - /// - /// This will also update the cache. - /// # Errors - /// If you started the stream and the camera rejects the new camera format, this will return an error. - fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError>; - - /// A hashmap of [`Resolution`]s mapped to framerates. Not sorted! - /// # Errors - /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError)). - fn compatible_list_by_resolution( - &mut self, - fourcc: FrameFormat, - ) -> Result>, NokhwaError>; - - /// Gets the compatible [`CameraFormat`] of the camera - /// # Errors - /// If it fails to get, this will error. - fn compatible_camera_formats(&mut self) -> Result, NokhwaError> { - let mut compatible_formats = vec![]; - for fourcc in self.compatible_fourcc()? { - for (resolution, fps_list) in self.compatible_list_by_resolution(fourcc)? { - for fps in fps_list { - compatible_formats.push(CameraFormat::new(resolution, fourcc, fps)); - } - } - } - - Ok(compatible_formats) - } - - /// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most. - /// # Errors - /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError)). - fn compatible_fourcc(&mut self) -> Result, NokhwaError>; - - /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. - fn resolution(&self) -> Resolution; - - /// Will set the current [`Resolution`] - /// This will reset the current stream if used while stream is opened. - /// - /// This will also update the cache. - /// # Errors - /// If you started the stream and the camera rejects the new resolution, this will return an error. - fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>; - - /// Gets the current camera framerate (See: [`CameraFormat`]). This will force refresh to the current latest if it has changed. - fn frame_rate(&self) -> u32; - - /// Will set the current framerate - /// This will reset the current stream if used while stream is opened. - /// - /// This will also update the cache. - /// # Errors - /// If you started the stream and the camera rejects the new framerate, this will return an error. - fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; - - /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. - fn frame_format(&self) -> FrameFormat; - - /// Will set the current [`FrameFormat`] - /// This will reset the current stream if used while stream is opened. - /// - /// This will also update the cache. - /// # Errors - /// If you started the stream and the camera rejects the new frame format, this will return an error. - fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>; - - /// Gets the value of [`KnownCameraControl`]. - /// # Errors - /// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, etc) - /// this will error. - fn camera_control(&self, control: KnownCameraControl) -> Result; - - /// Gets the current supported list of [`KnownCameraControl`] - /// # Errors - /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". - fn camera_controls(&self) -> Result, NokhwaError>; - - /// Sets the control to `control` in the camera. - /// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way - /// then calling [`set_value()`](CameraControl::set_value()) - /// # Errors - /// If the `control` is not supported, the value is invalid (less than min, greater than max, not in step), or there was an error setting the control, - /// this will error. - fn set_camera_control( - &mut self, - id: KnownCameraControl, - value: ControlValueSetter, - ) -> Result<(), NokhwaError>; - - /// Will open the camera stream with set parameters. This will be called internally if you try and call [`frame()`](CaptureBackendTrait::frame()) before you call [`open_stream()`](CaptureBackendTrait::open_stream()). - /// # Errors - /// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. - fn open_stream(&mut self) -> Result<(), NokhwaError>; - - /// Checks if stream if open. If it is, it will return true. - fn is_stream_open(&self) -> bool; - - /// Will get a frame from the camera as a [`Buffer`]. Depending on the backend, if you have not called [`open_stream()`](CaptureBackendTrait::open_stream()) before you called this, - /// it will either return an error. - /// # Errors - /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, - /// this will error. - fn frame<'a>(&mut self) -> Result, NokhwaError>; - - /// Will get a frame from the camera **without** any processing applied, meaning you will usually get a frame you need to decode yourself. - /// # Errors - /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. - fn frame_raw(&mut self) -> Result, NokhwaError>; - - /// The minimum buffer size needed to write the current frame. If `alpha` is true, it will instead return the minimum size of the buffer with an alpha channel as well. - /// This assumes that you are decoding to RGB/RGBA for [`FrameFormat::MJPEG`] or [`FrameFormat::YUYV`] and Luma8/LumaA8 for [`FrameFormat::GRAY`] - #[must_use] - fn decoded_buffer_size(&self, alpha: bool) -> usize { - let cfmt = self.camera_format(); - let resolution = cfmt.resolution(); - let pxwidth = match cfmt.format() { - FrameFormat::MJPEG | FrameFormat::YUYV => 3, - FrameFormat::GRAY => 1, - }; - if alpha { - return (resolution.width() * resolution.height() * (pxwidth + 1)) as usize; - } - (resolution.width() * resolution.height() * pxwidth) as usize - } - - #[cfg(feature = "output-wgpu")] - #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-wgpu")))] - /// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame. - /// # Errors - /// If the frame cannot be captured or the resolution is 0 on any axis, this will error. - fn frame_texture<'a>( - &mut self, - device: &WgpuDevice, - queue: &WgpuQueue, - label: Option<&'a str>, - ) -> Result { - use crate::pixel_format::RgbAFormat; - use std::num::NonZeroU32; - let frame = self.frame()?.decode_image::()?; - - let texture_size = Extent3d { - width: frame.width(), - height: frame.height(), - depth_or_array_layers: 1, - }; - - let texture = device.create_texture(&TextureDescriptor { - label, - size: texture_size, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: TextureFormat::Rgba8UnormSrgb, - usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, - }); - - let width_nonzero = match NonZeroU32::try_from(4 * frame.width()) { - Ok(w) => Some(w), - Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), - }; - - let height_nonzero = match NonZeroU32::try_from(frame.height()) { - Ok(h) => Some(h), - Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), - }; - - queue.write_texture( - ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: TextureAspect::All, - }, - &frame.to_vec(), - ImageDataLayout { - offset: 0, - bytes_per_row: width_nonzero, - rows_per_image: height_nonzero, - }, - texture_size, - ); - - Ok(texture) - } - - /// Will drop the stream. - /// # Errors - /// Please check the `Quirks` section of each backend. - fn stop_stream(&mut self) -> Result<(), NokhwaError>; -} - -pub trait VirtualBackendTrait {} diff --git a/src/utils.rs b/src/utils.rs index 994d206..9c5635a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,1708 +1 @@ -/* - * Copyright 2022 l1npengtul / The Nokhwa Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use crate::NokhwaError; -#[cfg(any( - all( - feature = "input-avfoundation", - any(target_os = "macos", target_os = "ios") - ), - all( - feature = "docs-only", - feature = "docs-nolink", - feature = "input-avfoundation" - ) -))] -use nokhwa_bindings_macos::avfoundation::{ - AVCaptureDeviceDescriptor, AVFourCC, AVVideoResolution, CaptureDeviceFormatDescriptor, -}; -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -use nokhwa_bindings_windows::{ - MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls, - MediaFoundationDeviceDescriptor, -}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::{ - borrow::{Borrow, Cow}, - cmp::Ordering, - fmt::{Display, Formatter}, -}; -#[cfg(feature = "input-uvc")] -use uvc::StreamFormat; -#[cfg(all(feature = "input-v4l", target_os = "linux"))] -use v4l::{control::Description, Format, FourCC}; -#[cfg(feature = "output-wasm")] -use wasm_bindgen::prelude::wasm_bindgen; - -/// Tells the init function what camera format to pick. -/// - `HighestResolution(Option)`: Pick the highest [`Resolution`] for the given framerate (the `Option`). If its `None`, it will pick the highest possible [`Resolution`] -/// - `HighestFrameRate(Option)`: Pick the highest frame rate for the given [`Resolution`] (the `Option`). If it is `None`, it will pick the highest possinle framerate. -/// - `Exact`: Pick the exact [`CameraFormat`] provided. -/// - `Closest`: Pick the closest [`CameraFormat`] provided in order of [`FrameFormat`], [`Resolution`], and FPS. Note that if the [`FrameFormat`] does not exist, this will fail to resolve. -/// - `None`: Pick a random [`CameraFormat`] -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum RequestedFormat { - HighestResolution, - HighestFrameRate, - Exact(CameraFormat), - Closest(CameraFormat), - None, -} - -impl RequestedFormat { - /// Fufill the requested using a list of all availible formats. - pub fn fufill(&self, all_formats: &[CameraFormat]) -> Option { - match request { - RequestedFormat::HighestResolution => { - let mut formats = all_formats.to_vec(); - formats.sort_by(|a, b| a.resolution().cmp(&b.resolution())); - let resolution = formats.iter().last()?; - let mut format_resolutions = formats - .into_iter() - .filter(|fmt| fmt.resolution() == resolution.resolution()) - .collect::>(); - format_resolutions.sort_by(|a, b| a.frame_rate().cmp(&b.frame_rate())); - format_resolutions.last().map(|x| *x) - } - RequestedFormat::HighestFrameRate => { - let mut formats = all_formats.to_vec(); - formats.sort_by(|a, b| a.frame_rate().cmp(&b.frame_rate())); - let frame_rate = formats.iter().last()?; - let mut format_framerates = formats - .into_iter() - .filter(|fmt| fmt.frame_rate() == frame_rate.frame_rate()) - .collect::>(); - format_framerates.sort_by(|a, b| a.resolution().cmp(&b.resolution())); - format_framerates.last().map(|x| *x) - } - RequestedFormat::Exact(fmt) => fmt, - RequestedFormat::Closest(c) => { - let mut same_fmt_formats = all_formats - .iter() - .filter(|x| x.format() == c.format()) - .collect::>(); - let mut resolution_map = same_fmt_formats - .iter() - .map(|x| { - let res = x.resolution(); - let x_diff = res.x() as i32 - c.resolution().x() as i32; - let y_diff = res.y() as i32 - c.resolution().y() as i32; - let dist_no_sqrt = (x_diff.abs()).pow(2) + (y_diff.abs()).pow(2); - (dist_no_sqrt, res) - }) - .collect::>(); - resolution_map.sort_by(|a, b| a.0.cmp(*b.0.cmp())); - resolution_map.dedup_by(|a, b| a.0.eq(&b.0)); - let resolution = resolution_map.first()?.1; - - let frame_rates = all_formats - .iter() - .filter_map(|cfmt| { - if cfmt.format() == c.format() && cfmt.resolution() == c.resolution() { - return Some(cfmt.frame_rate()); - } - None - }) - .collect::>(); - // sort FPSes - let mut framerate_map = frame_rates - .iter() - .map(|x| { - let abs = x as i32 - c.frame_rate() as i32; - (abs.abs(), *x) - }) - .collect::>(); - framerate_map.sort(); - let frame_rate = framerate_map.first()?.1; - Some(CameraFormat::new(resolution, c.format(), frame_rate)) - } - RequestedFormat::None => all_formats.first().map(|x| *x), - } - } -} - -/// Describes the index of the camera. -/// - Index: A numbered index -/// - String: A string, used for IPCameras. -#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum CameraIndex { - Index(u32), - String(String), -} - -impl CameraIndex { - /// Turns this value into a number. If it is a string, it will attempt to parse it as a `u32`. - /// # Errors - /// Fails if the value is not a number. - pub fn as_index(&self) -> Result { - match self { - CameraIndex::Index(i) => Ok(*i), - CameraIndex::String(s) => s - .parse::() - .map_err(|why| NokhwaError::GeneralError(why.to_string())), - } - } - - /// Turns this value into a `String`. If it is a number, it will be automatically converted. - #[must_use] - pub fn as_string(&self) -> String { - match self { - CameraIndex::Index(i) => i.to_string(), - CameraIndex::String(s) => s.to_string(), - } - } - - /// Returns true if this [`CameraIndex`] contains an [`CameraIndex::Index`] - #[must_use] - pub fn is_index(&self) -> bool { - match self { - CameraIndex::Index(_) => true, - CameraIndex::String(_) => false, - } - } - - /// Returns true if this [`CameraIndex`] contains an [`CameraIndex::String`] - #[must_use] - pub fn is_string(&self) -> bool { - !self.is_index() - } -} - -impl Display for CameraIndex { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_string()) - } -} - -impl AsRef for CameraIndex { - fn as_ref(&self) -> &str { - self.to_string().as_str() - } -} - -impl TryFrom for u32 { - type Error = NokhwaError; - - fn try_from(value: CameraIndex) -> Result { - value.as_index() - } -} - -impl TryFrom for usize { - type Error = NokhwaError; - - fn try_from(value: CameraIndex) -> Result { - value.as_index().map(|i| i as usize) - } -} - -/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. -/// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) -/// - MJPEG is a motion-jpeg compressed frame, it allows for high frame rates. -/// - GRAY is a grayscale image format, usually for specialized cameras such as IR Cameras. -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum FrameFormat { - MJPEG, - YUYV, - GRAY, -} - -impl Display for FrameFormat { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - FrameFormat::MJPEG => { - write!(f, "MJPG") - } - FrameFormat::YUYV => { - write!(f, "YUYV") - } - FrameFormat::GRAY => { - write!(f, "GRAY") - } - } - } -} - -#[cfg(feature = "input-uvc")] -impl From for uvc::FrameFormat { - fn from(ff: FrameFormat) -> Self { - match ff { - FrameFormat::MJPEG => uvc::FrameFormat::MJPEG, - FrameFormat::YUYV => uvc::FrameFormat::YUYV, - } - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From for FrameFormat { - fn from(mf_ff: MFFrameFormat) -> Self { - match mf_ff { - MFFrameFormat::MJPEG => FrameFormat::MJPEG, - MFFrameFormat::YUYV => FrameFormat::YUYV, - MFFrameFormat::GRAY8 => FrameFormat::GRAY, - } - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From for MFFrameFormat { - fn from(ff: FrameFormat) -> Self { - match ff { - FrameFormat::MJPEG => MFFrameFormat::MJPEG, - FrameFormat::YUYV => MFFrameFormat::YUYV, - FrameFormat::GRAY => MFFrameFormat::GRAY8, //FIXME - } - } -} - -#[cfg(any( - all( - feature = "input-avfoundation", - any(target_os = "macos", target_os = "ios") - ), - all( - feature = "docs-only", - feature = "docs-nolink", - feature = "input-avfoundation" - ) -))] -impl From for FrameFormat { - fn from(av_fcc: AVFourCC) -> Self { - match av_fcc { - AVFourCC::YUV2 => FrameFormat::YUYV, - AVFourCC::MJPEG => FrameFormat::MJPEG, - AVFourCC::GRAY8 => FrameFormat::GRAY, - } - } -} - -#[cfg(any( - all( - feature = "input-avfoundation", - any(target_os = "macos", target_os = "ios") - ), - all( - feature = "docs-only", - feature = "docs-nolink", - feature = "input-avfoundation" - ) -))] -impl From for AVFourCC { - fn from(ff: FrameFormat) -> Self { - match ff { - FrameFormat::MJPEG => AVFourCC::MJPEG, - FrameFormat::YUYV => AVFourCC::YUV2, - FrameFormat::GRAY => AVFourCC::GRAY8, - } - } -} - -#[must_use] -pub const fn frame_formats() -> [FrameFormat; 3] { - [FrameFormat::MJPEG, FrameFormat::YUYV, FrameFormat::GRAY] -} - -/// Describes a Resolution. -/// This struct consists of a Width and a Height value (x,y).
-/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest. -/// # JS-WASM -/// This is exported as `JSResolution` -#[cfg_attr(feature = "output-wasm", wasm_bindgen)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -pub struct Resolution { - pub width_x: u32, - pub height_y: u32, -} - -#[cfg_attr(feature = "output-wasm", wasm_bindgen)] -impl Resolution { - /// Create a new resolution from 2 image size coordinates. - /// # JS-WASM - /// This is exported as a constructor for [`Resolution`]. - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))] - pub fn new(x: u32, y: u32) -> Self { - Resolution { - width_x: x, - height_y: y, - } - } - - /// Get the width of Resolution - /// # JS-WASM - /// This is exported as `get_Width`. - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Width))] - pub fn width(self) -> u32 { - self.width_x - } - - /// Get the height of Resolution - /// # JS-WASM - /// This is exported as `get_Height`. - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Height))] - pub fn height(self) -> u32 { - self.height_y - } - - /// Get the x (width) of Resolution - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))] - pub fn x(self) -> u32 { - self.width_x - } - - /// Get the y (height) of Resolution - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))] - pub fn y(self) -> u32 { - self.height_y - } -} - -impl Display for Resolution { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}x{}", self.x(), self.y()) - } -} - -impl PartialOrd for Resolution { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Resolution { - fn cmp(&self, other: &Self) -> Ordering { - match self.x().cmp(&other.x()) { - Ordering::Less => Ordering::Less, - Ordering::Equal => self.y().cmp(&other.y()), - Ordering::Greater => Ordering::Greater, - } - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From for Resolution { - fn from(mf_res: MFResolution) -> Self { - Resolution { - width_x: mf_res.width_x, - height_y: mf_res.height_y, - } - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From for MFResolution { - fn from(res: Resolution) -> Self { - MFResolution { - width_x: res.width(), - height_y: res.height(), - } - } -} - -#[cfg(any( - all( - feature = "input-avfoundation", - any(target_os = "macos", target_os = "ios") - ), - all( - feature = "docs-only", - feature = "docs-nolink", - feature = "input-avfoundation" - ) -))] -#[allow(clippy::cast_sign_loss)] -impl From for Resolution { - fn from(res: AVVideoResolution) -> Self { - Resolution { - width_x: res.width as u32, - height_y: res.height as u32, - } - } -} - -/// This is a convenience struct that holds all information about the format of a webcam stream. -/// It consists of a [`Resolution`], [`FrameFormat`], and a frame rate(u8). -#[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct CameraFormat { - resolution: Resolution, - format: FrameFormat, - frame_rate: u32, -} - -impl CameraFormat { - /// Construct a new [`CameraFormat`] - #[must_use] - pub fn new(resolution: Resolution, format: FrameFormat, frame_rate: u32) -> Self { - CameraFormat { - resolution, - format, - frame_rate, - } - } - - /// [`CameraFormat::new()`], but raw. - #[must_use] - pub fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: u32) -> Self { - CameraFormat { - resolution: Resolution { - width_x: res_x, - height_y: res_y, - }, - format, - frame_rate: fps, - } - } - - /// Get the resolution of the current [`CameraFormat`] - #[must_use] - pub fn resolution(&self) -> Resolution { - self.resolution - } - - /// Get the width of the resolution of the current [`CameraFormat`] - #[must_use] - pub fn width(&self) -> u32 { - self.resolution.width() - } - - /// Get the height of the resolution of the current [`CameraFormat`] - #[must_use] - pub fn height(&self) -> u32 { - self.resolution.height() - } - - /// Set the [`CameraFormat`]'s resolution. - pub fn set_resolution(&mut self, resolution: Resolution) { - self.resolution = resolution; - } - - /// Get the frame rate of the current [`CameraFormat`] - #[must_use] - pub fn frame_rate(&self) -> u32 { - self.frame_rate - } - - /// Set the [`CameraFormat`]'s frame rate. - pub fn set_frame_rate(&mut self, frame_rate: u32) { - self.frame_rate = frame_rate; - } - - /// Get the [`CameraFormat`]'s format. - #[must_use] - pub fn format(&self) -> FrameFormat { - self.format - } - - /// Set the [`CameraFormat`]'s format. - pub fn set_format(&mut self, format: FrameFormat) { - self.format = format; - } -} - -#[cfg(feature = "input-uvc")] -impl From for StreamFormat { - fn from(cf: CameraFormat) -> Self { - StreamFormat { - width: cf.width(), - height: cf.height(), - fps: cf.frame_rate(), - format: cf.format().into(), - } - } -} - -impl Default for CameraFormat { - fn default() -> Self { - CameraFormat { - resolution: Resolution::new(640, 480), - format: FrameFormat::MJPEG, - frame_rate: 30, - } - } -} - -impl Display for CameraFormat { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}@{}FPS, {} Format", - self.resolution, self.frame_rate, self.format - ) - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From for CameraFormat { - fn from(mf_cam_fmt: MFCameraFormat) -> Self { - CameraFormat { - resolution: mf_cam_fmt.resolution().into(), - format: mf_cam_fmt.format().into(), - frame_rate: mf_cam_fmt.frame_rate(), - } - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From for MFCameraFormat { - fn from(cf: CameraFormat) -> Self { - MFCameraFormat::new(cf.resolution.into(), cf.format.into(), cf.frame_rate) - } -} - -#[cfg(all(feature = "input-v4l", target_os = "linux"))] -impl From for Format { - fn from(cam_fmt: CameraFormat) -> Self { - let pxfmt = match cam_fmt.format() { - FrameFormat::MJPEG => FourCC::new(b"MJPG"), - FrameFormat::YUYV => FourCC::new(b"YUYV"), - FrameFormat::GRAY => FourCC::new(b"GREY"), - }; - - Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt) - } -} - -#[cfg(any( - all( - feature = "input-avfoundation", - any(target_os = "macos", target_os = "ios") - ), - all( - feature = "docs-only", - feature = "docs-nolink", - feature = "input-avfoundation" - ) -))] -#[allow(clippy::cast_possible_wrap)] -#[allow(clippy::cast_lossless)] -impl From for CaptureDeviceFormatDescriptor { - fn from(cf: CameraFormat) -> Self { - CaptureDeviceFormatDescriptor { - resolution: AVVideoResolution { - width: cf.width() as i32, - height: cf.height() as i32, - }, - fps: cf.frame_rate(), - fourcc: cf.format().into(), - } - } -} - -/// Information about a Camera e.g. its name. -/// `description` amd `misc` may contain information that may differ from backend to backend. Refer to each backend for details. -/// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system. -#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] -#[cfg_attr(feature = "output-wasm", wasm_bindgen)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct CameraInfo { - human_name: String, - description: String, - misc: String, - index: CameraIndex, -} - -#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = CameraInfo))] -impl CameraInfo { - /// Create a new [`CameraInfo`]. - /// # JS-WASM - /// This is exported as a constructor for [`CameraInfo`]. - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))] - // OK, i just checkeed back on this code. WTF was I on when I wrote `&(impl AsRef + ?Sized)` ???? - // I need to get on the same shit that my previous self was on, because holy shit that stuff is strong as FUCK! - // Finally fixed this insanity. Hopefully I didnt torment anyone by actually putting this in a stable release. - pub fn new(human_name: &str, description: &str, misc: &str, index: CameraIndex) -> Self { - CameraInfo { - human_name: human_name.to_string(), - description: description.to_string(), - misc: misc.to_string(), - index, - } - } - - /// Get a reference to the device info's human readable name. - /// # JS-WASM - /// This is exported as a `get_HumanReadableName`. - #[must_use] - #[cfg_attr( - feature = "output-wasm", - wasm_bindgen(getter = HumanReadableName) - )] - // yes, i know, unnecessary alloc this, unnecessary alloc that - // but wasm bindgen - pub fn human_name(&self) -> String { - self.human_name.clone() - } - - /// Set the device info's human name. - /// # JS-WASM - /// This is exported as a `set_HumanReadableName`. - #[cfg_attr( - feature = "output-wasm", - wasm_bindgen(setter = HumanReadableName) - )] - pub fn set_human_name(&mut self, human_name: &str) { - self.human_name = human_name.to_string(); - } - - /// Get a reference to the device info's description. - /// # JS-WASM - /// This is exported as a `get_Description`. - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Description))] - pub fn description(&self) -> &str { - self.description.borrow() - } - - /// Set the device info's description. - /// # JS-WASM - /// This is exported as a `set_Description`. - #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Description))] - pub fn set_description(&mut self, description: &str) { - self.description = description.to_string(); - } - - /// Get a reference to the device info's misc. - /// # JS-WASM - /// This is exported as a `get_MiscString`. - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = MiscString))] - pub fn misc(&self) -> String { - self.misc.clone() - } - - /// Set the device info's misc. - /// # JS-WASM - /// This is exported as a `set_MiscString`. - #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = MiscString))] - pub fn set_misc(&mut self, misc: &str) { - self.misc = misc.to_string(); - } - - /// Get a reference to the device info's index. - /// # JS-WASM - /// This is exported as a `get_Index`. - #[must_use] - #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index))] - pub fn index(&self) -> &CameraIndex { - &self.index - } - - /// Set the device info's index. - /// # JS-WASM - /// This is exported as a `set_Index`. - #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Index))] - pub fn set_index(&mut self, index: CameraIndex) { - self.index = index; - } - - // /// Gets the device info's index as an `u32`. - // /// # Errors - // /// If the index is not parsable as a `u32`, this will error. - // /// # JS-WASM - // /// This is exported as `get_Index_Int` - // #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] - // pub fn index_num(&self) -> Result { - // match &self.index { - // CameraIndex::Index(i) => Ok(*i), - // CameraIndex::String(s) => match s.parse::() { - // Ok(p) => Ok(p), - // Err(why) => Err(NokhwaError::GetPropertyError { - // property: "index-int".to_string(), - // error: why.to_string(), - // }), - // }, - // } - // } -} - -impl Display for CameraInfo { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Name: {}, Description: {}, Extra: {}, Index: {}", - self.human_name, self.description, self.misc, self.index - ) - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From> for CameraInfo { - fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { - CameraInfo { - human_name: dev_desc.name_as_string(), - description: "Media Foundation Device".to_string(), - misc: dev_desc.link_as_string(), - index: dev_desc.index() as u32, - } - } -} - -#[cfg(any( - all( - feature = "input-avfoundation", - any(target_os = "macos", target_os = "ios") - ), - all( - feature = "docs-only", - feature = "docs-nolink", - feature = "input-avfoundation" - ) -))] -#[allow(clippy::cast_possible_truncation)] -impl From for CameraInfo { - fn from(descriptor: AVCaptureDeviceDescriptor) -> Self { - CameraInfo { - human_name: descriptor.name, - description: descriptor.description, - misc: descriptor.misc, - index: descriptor.index as u32, - } - } -} - -/// The list of known camera controls to the library.
-/// These can control the picture brightness, etc.
-/// Note that not all backends/devices support all these. Run [`supported_camera_controls()`](crate::CaptureBackendTrait::camera_controls) to see which ones can be set. -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum KnownCameraControl { - Brightness, - Contrast, - Hue, - Saturation, - Sharpness, - Gamma, - WhiteBalance, - BacklightComp, - Gain, - Pan, - Tilt, - Zoom, - Exposure, - Iris, - Focus, - /// Other camera control. Listed is the ID. - /// Wasteful, however is needed for a unified API across Windows, Linux, and MacOSX due to Microsoft's usage of GUIDs. - /// - /// THIS SHOULD ONLY BE USED WHEN YOU KNOW THE PLATFORM THAT YOU ARE RUNNING ON. - Other(u128), -} - -/// All camera controls in an array. -#[must_use] -pub const fn all_known_camera_controls() -> [KnownCameraControl; 15] { - [ - KnownCameraControl::Brightness, - KnownCameraControl::Contrast, - KnownCameraControl::Hue, - KnownCameraControl::Saturation, - KnownCameraControl::Sharpness, - KnownCameraControl::Gamma, - KnownCameraControl::WhiteBalance, - KnownCameraControl::BacklightComp, - KnownCameraControl::Gain, - KnownCameraControl::Pan, - KnownCameraControl::Tilt, - KnownCameraControl::Zoom, - KnownCameraControl::Exposure, - KnownCameraControl::Iris, - KnownCameraControl::Focus, - ] -} - -impl Display for KnownCameraControl { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", &self) - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From for KnownCameraControl { - fn from(mf_c: MediaFoundationControls) -> Self { - match mf_c { - MediaFoundationControls::Brightness => KnownCameraControl::Brightness, - MediaFoundationControls::Contrast => KnownCameraControl::Contrast, - MediaFoundationControls::Hue => KnownCameraControl::Hue, - MediaFoundationControls::Saturation => KnownCameraControl::Saturation, - MediaFoundationControls::Sharpness => KnownCameraControl::Sharpness, - MediaFoundationControls::Gamma => KnownCameraControl::Gamma, - MediaFoundationControls::WhiteBalance => KnownCameraControl::WhiteBalance, - MediaFoundationControls::BacklightComp => KnownCameraControl::BacklightComp, - MediaFoundationControls::Gain => KnownCameraControl::Gain, - MediaFoundationControls::Pan => KnownCameraControl::Pan, - MediaFoundationControls::Tilt => KnownCameraControl::Tilt, - MediaFoundationControls::Zoom => KnownCameraControl::Zoom, - MediaFoundationControls::Exposure => KnownCameraControl::Exposure, - MediaFoundationControls::Iris => KnownCameraControl::Iris, - MediaFoundationControls::Focus => KnownCameraControl::Focus, - MediaFoundationControls::ColorEnable => KnownCameraControl::Other(0), - MediaFoundationControls::Roll => KnownCameraControl::Other(1), - } - } -} - -#[cfg(any( - all(feature = "input-msmf", target_os = "windows"), - all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") -))] -impl From for KnownCameraControl { - fn from(mf_cc: MFControl) -> Self { - mf_cc.control().into() - } -} - -#[cfg(all(feature = "input-v4l", target_os = "linux"))] -impl From for KnownCameraControl { - fn from(value: Description) -> KnownCameraControl { - match value.id { - 9_963_776 => KnownCameraControl::Brightness, - 9_963_777 => KnownCameraControl::Contrast, - 9_963_779 => KnownCameraControl::Hue, - 9_963_778 => KnownCameraControl::Saturation, - 9_963_803 => KnownCameraControl::Sharpness, - 9_963_792 => KnownCameraControl::Gamma, - 9_963_802 => KnownCameraControl::WhiteBalance, - 9_963_804 => KnownCameraControl::BacklightComp, - 9_963_795 => KnownCameraControl::Gain, - 10_094_852 => KnownCameraControl::Pan, - 10_094_853 => KnownCameraControl::Tilt, - 10_094_862 => KnownCameraControl::Zoom, - 10_094_850 => KnownCameraControl::Exposure, - 10_094_866 => KnownCameraControl::Iris, - 10_094_859 => KnownCameraControl::Focus, - id => KnownCameraControl::Other(id as u128), - } - } -} - -#[cfg(all(feature = "input-opencv"))] -impl From for KnownCameraControl { - fn from(id: i32) -> Self { - use opencv::videoio::*; - match id { - CAP_PROP_BRIGHTNESS => KnownCameraControl::Brightness, - CAP_PROP_CONTRAST => KnownCameraControl::Contrast, - CAP_PROP_HUE => KnownCameraControl::Hue, - CAP_PROP_SATURATION => KnownCameraControl::Saturation, - CAP_PROP_SHARPNESS => KnownCameraControl::Sharpness, - CAP_PROP_GAMMA => KnownCameraControl::Gamma, - CAP_PROP_WB_TEMPERATURE => KnownCameraControl::WhiteBalance, - CAP_PROP_BACKLIGHT => KnownCameraControl::BacklightComp, - CAP_PROP_GAIN => KnownCameraControl::Gain, - CAP_PROP_PAN => KnownCameraControl::Pan, - CAP_PROP_TILT => KnownCameraControl::Tilt, - CAP_PROP_ZOOM => KnownCameraControl::Zoom, - CAP_PROP_EXPOSURE => KnownCameraControl::Exposure, - CAP_PROP_IRIS => KnownCameraControl::Iris, - CAP_PROP_FOCUS => KnownCameraControl::Focus, - id => KnownCameraControl::Other(id as u128), - } - } -} - -#[cfg(all(feature = "input-opencv"))] -impl From for i32 { - fn from(kcc: KnownCameraControl) -> Self { - use opencv::videoio::*; - - match kcc { - KnownCameraControl::Brightness => CAP_PROP_BRIGHTNESS, - KnownCameraControl::Contrast => CAP_PROP_CONTRAST, - KnownCameraControl::Hue => CAP_PROP_HUE, - KnownCameraControl::Saturation => CAP_PROP_SATURATION, - KnownCameraControl::Sharpness => CAP_PROP_SHARPNESS, - KnownCameraControl::Gamma => CAP_PROP_GAMMA, - KnownCameraControl::WhiteBalance => CAP_PROP_WB_TEMPERATURE, - KnownCameraControl::BacklightComp => CAP_PROP_BACKLIGHT, - KnownCameraControl::Gain => CAP_PROP_GAIN, - KnownCameraControl::Pan => CAP_PROP_PAN, - KnownCameraControl::Tilt => CAP_PROP_TILT, - KnownCameraControl::Zoom => CAP_PROP_ZOOM, - KnownCameraControl::Exposure => CAP_PROP_EXPOSURE, - KnownCameraControl::Iris => CAP_PROP_IRIS, - KnownCameraControl::Focus => CAP_PROP_FOCUS, - KnownCameraControl::Other(id) => id as i32, - } - } -} - -/// This tells you weather a [`KnownCameraControl`] is automatically managed by the OS/Driver -/// or manually managed by you, the programmer. -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub enum KnownCameraControlFlag { - Automatic, - Manual, - ReadOnly, - WriteOnly, - Volatile, - Disabled, - Inactive, -} - -impl Display for KnownCameraControlFlag { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -#[derive(Clone, Debug, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -// TODO: use in CameraControl -/// The values for a [`CameraControl`]. -/// -/// This provides a wide range of values that can be used to control a camera. -pub enum ControlValueDescription { - None, - Integer { - value: i64, - default: i64, - step: i64, - }, - IntegerRange { - min: i64, - max: i64, - value: i64, - step: i64, - default: i64, - }, - Float { - value: f64, - default: f64, - step: f64, - }, - FloatRange { - min: f64, - max: f64, - value: f64, - step: f64, - default: f64, - }, - Boolean { - value: bool, - default: bool, - }, - String { - value: String, - default: Option, - }, - Bytes { - value: Vec, - default: Vec, - }, -} - -impl ControlValueDescription { - /// Get the value of this [`ControlValueDescription`] - #[must_use] - pub fn value(&self) -> ControlValueSetter { - match self { - ControlValueDescription::None => ControlValueSetter::None, - ControlValueDescription::Integer { value, .. } - | ControlValueDescription::IntegerRange { value, .. } => { - ControlValueSetter::Integer(*value) - } - ControlValueDescription::Float { value, .. } - | ControlValueDescription::FloatRange { value, .. } => { - ControlValueSetter::Float(*value) - } - ControlValueDescription::Boolean { value, .. } => ControlValueSetter::Boolean(*value), - ControlValueDescription::String { value, .. } => { - ControlValueSetter::String(value.clone()) - } - ControlValueDescription::Bytes { value, .. } => { - ControlValueSetter::Bytes(value.clone()) - } - } - } - - /// Verifies if the [setter](crate::utils::ControlValueSetter) is valid for the provided [`ControlValueDescription`]. - /// - `true` => Is valid. - /// - `false` => Is not valid. - #[must_use] - pub fn verify_setter(&self, setter: &ControlValueSetter) -> bool { - match setter { - ControlValueSetter::None => { - matches!(self, ControlValueDescription::None) - } - ControlValueSetter::Integer(i) => match self { - ControlValueDescription::Integer { - value, - default, - step, - } => (i - default).abs() % step == 0 || (i - value) % step == 0, - ControlValueDescription::IntegerRange { - min, - max, - value, - step, - default, - } => { - if value > max || value < min { - return false; - } - - (i - default) % step == 0 || (i - value) % step == 0 - } - _ => false, - }, - ControlValueSetter::Float(f) => match self { - ControlValueDescription::Float { - value, - default, - step, - } => (f - default).abs() % step == 0_f64 || (f - value) % step == 0_f64, - ControlValueDescription::FloatRange { - min, - max, - value, - step, - default, - } => { - if value > max || value < min { - return false; - } - - (f - default) % step == 0_f64 || (f - value) % step == 0_f64 - } - _ => false, - }, - ControlValueSetter::Boolean(_) => { - matches!(self, ControlValueDescription::Boolean { .. }) - } - ControlValueSetter::String(_) => { - matches!(self, ControlValueDescription::String { .. }) - } - ControlValueSetter::Bytes(_) => { - matches!(self, ControlValueDescription::Bytes { .. }) - } - } - } -} - -impl Display for ControlValueDescription { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ControlValueDescription::None => { - write!(f, "(None)") - } - ControlValueDescription::Integer { - value, - default, - step, - } => { - write!( - f, - "(Current: {}, Default: {}, Step: {})", - value, default, step - ) - } - ControlValueDescription::IntegerRange { - min, - max, - value, - step, - default, - } => { - write!( - f, - "(Current: {}, Default: {}, Step: {}, Range: ({}, {}))", - value, default, step, min, max - ) - } - ControlValueDescription::Float { - value, - default, - step, - } => { - write!( - f, - "(Current: {}, Default: {}, Step: {})", - value, default, step - ) - } - ControlValueDescription::FloatRange { - min, - max, - value, - step, - default, - } => { - write!( - f, - "(Current: {}, Default: {}, Step: {}, Range: ({}, {}))", - value, default, step, min, max - ) - } - ControlValueDescription::Boolean { value, default } => { - write!(f, "(Current: {}, Default: {})", value, default) - } - ControlValueDescription::String { value, default } => { - write!(f, "(Current: {}, Default: {:?})", value, default) - } - ControlValueDescription::Bytes { value, default } => { - write!(f, "(Current: {:x?}, Default: {:x?})", value, default) - } - } - } -} - -// fn step_chk(val: i64, default: i64, step: i64) -> Result<(), NokhwaError> { -// if (val - default) % step != 0 { -// return Err(NokhwaError::StructureError { -// structure: "Value".to_string(), -// error: "Doesnt fit step".to_string(), -// }); -// } -// Ok(()) -// } - -/// This struct tells you everything about a particular [`KnownCameraControl`]. -/// -/// However, you should never need to instantiate this struct, since its usually generated for you by `nokhwa`. -/// The only time you should be modifying this struct is when you need to set a value and pass it back to the camera. -/// NOTE: Assume the values for `min` and `max` as **non-inclusive**!. -/// E.g. if the [`CameraControl`] says `min` is 100, the minimum is actually 101. -#[derive(Clone, Debug, PartialOrd, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct CameraControl { - control: KnownCameraControl, - name: String, - description: ControlValueDescription, - value: ControlValueSetter, - flag: Vec, - active: bool, -} - -impl CameraControl { - /// Creates a new [`CameraControl`] - #[must_use] - pub fn new( - control: KnownCameraControl, - name: String, - description: ControlValueDescription, - flag: Vec, - active: bool, - ) -> Self { - let value = description.value(); - CameraControl { - control, - name, - description, - value, - flag, - active, - } - } - - /// Gets the [`KnownCameraControl`] of this [`CameraControl`] - #[must_use] - pub fn control(&self) -> KnownCameraControl { - self.control - } - - /// Gets the current value description of this [`CameraControl`] - #[must_use] - pub fn value(&self) -> &ControlValueDescription { - &self.description - } - - /// Sets the value of this [`CameraControl`] - /// # Errors - /// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error - pub fn set_value(&mut self, value: ControlValueSetter) -> Result<(), NokhwaError> { - if !self.description.verify_setter(&value) { - return Err(NokhwaError::SetPropertyError { - property: format!("ControlValueDescription: {}", self.description), - value: format!("ControlValueSetter: {}", self.value), - error: "Invalid Value.".to_string(), - }); - } - - self.value = value; - Ok(()) - } - - /// Gets the [`KnownCameraControlFlag`] of this [`CameraControl`], - /// telling you weather this control is automatically set or manually set. - #[must_use] - pub fn flag(&self) -> &[KnownCameraControlFlag] { - &self.flag - } - - /// Gets `active` of this [`CameraControl`], - /// telling you weather this control is currently active(in-use). - #[must_use] - pub fn active(&self) -> bool { - self.active - } - - /// Gets `active` of this [`CameraControl`], - /// telling you weather this control is currently active(in-use). - pub fn set_active(&mut self, active: bool) { - self.active = active; - } -} - -impl Display for CameraControl { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Control: {}, Name: {}, Value: {}, Flag: {:?}, Active: {}", - self.control, self.name, self.description, self.flag, self.active - ) - } -} - -/// The setter for a control value -#[derive(Clone, Debug, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum ControlValueSetter { - None, - Integer(i64), - Float(f64), - Boolean(bool), - String(String), - Bytes(Vec), -} - -impl Display for ControlValueSetter { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ControlValueSetter::None => { - write!(f, "Value: None") - } - ControlValueSetter::Integer(i) => { - write!(f, "Value: {}", i) - } - ControlValueSetter::Float(d) => { - write!(f, "Value: {}", d) - } - ControlValueSetter::Boolean(b) => { - write!(f, "Value: {}", b) - } - ControlValueSetter::String(s) => { - write!(f, "Value: {}", s) - } - ControlValueSetter::Bytes(b) => { - write!(f, "Value: {:x?}", b) - } - } - } -} - -/// The list of known capture backends to the library.
-/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform. -/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` -/// - `Video4Linux` - `Video4Linux2`, a linux specific backend. -/// - `UniversalVideoClass` - ***DEPRECATED*** Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use. -/// - `MediaFoundation` - Microsoft Media Foundation, Windows only, -/// - `OpenCv` - Uses `OpenCV` to capture. Platform agnostic. -/// - `GStreamer` - ***DEPRECATED*** Uses `GStreamer` RTP to capture. Platform agnostic. -/// - `Network` - Uses `OpenCV` to capture from an IP. -/// - `Browser` - Uses browser APIs to capture from a webcam. -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum ApiBackend { - Auto, - AVFoundation, - Video4Linux, - #[deprecated] - UniversalVideoClass, - MediaFoundation, - OpenCv, - #[deprecated] - GStreamer, - Network, - Browser, -} - -impl Display for ApiBackend { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let self_str = format!("{:?}", self); - write!(f, "{}", self_str) - } -} - -// /// A webcam index that supports both strings and integers. Most backends take an int, but `IPCamera`s take a URL (string). -// #[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] -// pub enum CameraIndex { -// Index(u32), -// String(String), -// } - -// impl CameraIndex { -// /// Gets the device info's index as an `u32`. -// /// # Errors -// /// If the index is not parsable as a `u32`, this will error. -// pub fn as_index(&self) -> Result { -// match self { -// CameraIndex::Index(i) => Ok(*i), -// CameraIndex::String(s) => match s.parse::() { -// Ok(p) => Ok(p), -// Err(why) => Err(NokhwaError::GetPropertyError { -// property: "index-int".to_string(), -// error: why.to_string(), -// }), -// }, -// } -// } -// } - -// impl Display for CameraIndex { -// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { -// match self { -// CameraIndex::Index(idx) => { -// write!(f, "{}", idx) -// } -// CameraIndex::String(ip) => { -// write!(f, "{}", ip) -// } -// } -// } -// } - -// impl From for CameraIndex { -// fn from(v: u32) -> Self { -// CameraIndex::Index(v) -// } -// } - -// /// Trait for strings that can be converted to [`CameraIndex`]es. -// pub trait ValidString: AsRef {} -// -// impl ValidString for String {} -// impl<'a> ValidString for &'a String {} -// impl<'a> ValidString for &'a mut String {} -// impl<'a> ValidString for Cow<'a, str> {} -// impl<'a> ValidString for &'a Cow<'a, str> {} -// impl<'a> ValidString for &'a mut Cow<'a, str> {} -// impl<'a> ValidString for &'a str {} -// impl<'a> ValidString for &'a mut str {} - -// impl From for CameraIndex -// where -// T: ValidString, -// { -// fn from(v: T) -> Self { -// CameraIndex::String(v.as_ref().to_string()) -// } -// } - -/// Converts a MJPEG stream of [u8] into a Vec of RGB888. (R,G,B,R,G,B,...) -/// # Errors -/// If `mozjpeg` fails to read scanlines or setup the decompressor, this will error. -/// # Safety -/// This function uses `unsafe`. The caller must ensure that: -/// - The input data is of the right size, does not exceed bounds, and/or the final size matches with the initial size. -#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] -#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] -pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { - use mozjpeg::Decompress; - - let mut jpeg_decompress = match Decompress::new_mem(data) { - Ok(decompress) => { - let decompressor_res = if rgba { - decompress.rgba() - } else { - decompress.rgb() - }; - match decompressor_res { - Ok(decompressor) => decompressor, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) - } - } - } - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) - } - }; - - let scanlines_res: Option> = jpeg_decompress.read_scanlines_flat(); - // assert!(jpeg_decompress.finish_decompress()); - if !jpeg_decompress.finish_decompress() { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: "JPEG Decompressor did not finish.".to_string(), - }); - } - - match scanlines_res { - Some(pixels) => Ok(pixels), - None => Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: "Failed to get read readlines into RGB888 pixels!".to_string(), - }), - } -} - -#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] -pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { - Err(NokhwaError::NotImplementedError( - "Not available on WASM".to_string(), - )) -} - -/// Equivalent to [`mjpeg_to_rgb`] except with a destination buffer. -/// # Errors -/// If the decoding fails (e.g. invalid MJPEG stream), the buffer is not large enough, or you are doing this on `WebAssembly`, this will error. -#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] -#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] -pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { - use mozjpeg::Decompress; - - let mut jpeg_decompress = match Decompress::new_mem(data) { - Ok(decompress) => { - let decompressor_res = if rgba { - decompress.rgba() - } else { - decompress.rgb() - }; - match decompressor_res { - Ok(decompressor) => decompressor, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) - } - } - } - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) - } - }; - - // assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size()); - if dest.len() != jpeg_decompress.min_flat_buffer_size() { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: "Bad decoded buffer size".to_string(), - }); - } - - jpeg_decompress.read_scanlines_flat_into(dest); - // assert!(jpeg_decompress.finish_decompress()); - if !jpeg_decompress.finish_decompress() { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: "JPEG Decompressor did not finish.".to_string(), - }); - } - Ok(()) -} - -#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] -pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { - Err(NokhwaError::NotImplementedError( - "Not available on WASM".to_string(), - )) -} - -// For those maintaining this, I recommend you read: https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#yuy2 -// https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB -// and this too: https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte -// The YUY2(YUYV) format is a 16 bit format. We read 4 bytes at a time to get 6 bytes of RGB888. -// First, the YUY2 is converted to YCbCr 4:4:4 (4:2:2 -> 4:4:4) -// then it is converted to 6 bytes (2 pixels) of RGB888 -/// Converts a YUYV 4:2:2 datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) -/// # Errors -/// This may error when the data stream size is not divisible by 4, a i32 -> u8 conversion fails, or it fails to read from a certain index. -pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { - if data.len() % 4 != 0 { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" - .to_string(), - }); - } - - let pixel_size = if rgba { 4 } else { 3 }; - // yuyv yields 2 3-byte pixels per yuyv chunk - let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); - - let mut dest = vec![0; rgb_buf_size]; - buf_yuyv422_to_rgb(data, &mut dest, rgba)?; - - Ok(dest) -} - -/// Same as [`yuyv422_to_rgb`] but with a destination buffer instead of a return `Vec` -/// # Errors -/// If the stream is invalid YUYV, or the destination buffer is not large enough, this will error. -pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { - if data.len() % 4 != 0 { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" - .to_string(), - }); - } - - let pixel_size = if rgba { 4 } else { 3 }; - // yuyv yields 2 3-byte pixels per yuyv chunk - let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); - - if dest.len() != rgb_buf_size { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()), - }); - } - - let iter = data.chunks_exact(4); - - if rgba { - let mut iter = iter - .flat_map(|yuyv| { - let y1 = i32::from(yuyv[0]); - let u = i32::from(yuyv[1]); - let y2 = i32::from(yuyv[2]); - let v = i32::from(yuyv[3]); - let pixel1 = yuyv444_to_rgba(y1, u, v); - let pixel2 = yuyv444_to_rgba(y2, u, v); - [pixel1, pixel2] - }) - .flatten(); - for i in dest.iter_mut().take(rgb_buf_size) { - *i = match iter.next() { - Some(v) => v, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGBA8888".to_string(), - error: "Ran out of RGBA YUYV values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string() - }) - } - } - } - } else { - let mut iter = iter - .flat_map(|yuyv| { - let y1 = i32::from(yuyv[0]); - let u = i32::from(yuyv[1]); - let y2 = i32::from(yuyv[2]); - let v = i32::from(yuyv[3]); - let pixel1 = yuyv444_to_rgb(y1, u, v); - let pixel2 = yuyv444_to_rgb(y2, u, v); - [pixel1, pixel2] - }) - .flatten(); - - for i in dest.iter_mut().take(rgb_buf_size) { - *i = match iter.next() { - Some(v) => v, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: "Ran out of RGB YUYV values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string() - }) - } - } - } - } - - Ok(()) -} - -// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB -/// Convert `YCbCr` 4:4:4 to a RGB888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) -#[allow(clippy::many_single_char_names)] -#[allow(clippy::cast_possible_truncation)] -#[allow(clippy::cast_sign_loss)] -#[must_use] -#[inline] -pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { - let c298 = (y - 16) * 298; - let d = u - 128; - let e = v - 128; - let r = ((c298 + 409 * e + 128) >> 8) as u8; - let g = ((c298 - 100 * d - 208 * e + 128) >> 8) as u8; - let b = ((c298 + 516 * d + 128) >> 8) as u8; - [r, g, b] -} - -// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB -/// Convert `YCbCr` 4:4:4 to a RGBA8888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) -/// -/// Equivalent to [`yuyv444_to_rgb`] but with an alpha channel attached. -#[allow(clippy::many_single_char_names)] -#[must_use] -#[inline] -pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] { - let [r, g, b] = yuyv444_to_rgb(y, u, v); - [r, g, b, 255] -} +pub use