diff --git a/Cargo.toml b/Cargo.toml index 269b5bf..6b7865b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,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", "nokhwa-bindings-linux", "nokhwa-core"] +members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "nokhwa-bindings-linux", "nokhwa-core", "nokhwa-decoders"] exclude = ["examples/*"] [lib] diff --git a/flake.lock b/flake.lock index e78497a..aa4bebd 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1740019556, - "narHash": "sha256-vn285HxnnlHLWnv59Og7muqECNMS33mWLM14soFIv2g=", + "lastModified": 1750215678, + "narHash": "sha256-Rc/ytpamXRf6z8UA2SGa4aaWxUXRbX2MAWIu2C8M+ok=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "dad564433178067be1fbdfcce23b546254b6d641", + "rev": "5395fb3ab3f97b9b7abca147249fa2e8ed27b192", "type": "github" }, "original": { @@ -36,11 +36,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1736320768, - "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", "type": "github" }, "original": { @@ -62,11 +62,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1740191166, - "narHash": "sha256-WqRxO1Afx8jPYG4CKwkvDFWFvDLCwCd4mxb97hFGYPg=", + "lastModified": 1750387093, + "narHash": "sha256-MgL1+yNVcSD6OlzSmKt5GS4RmAQnNCjckjgPC1hmMPg=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "74a3fb71b0cc67376ab9e7c31abcd68c813fc226", + "rev": "517e9871d182346b53bb7f23fed00810c14db396", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index d025baa..6c7f9fc 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,7 @@ }; rustbin = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override { - extensions = ["rust-src"]; + extensions = ["rust-src" "clippy" "rustfmt" "miri"]; }); in { formatter = pkgs.alejandra; @@ -46,6 +46,8 @@ libv4l pipewire rustup + ffmpeg-full + nasm ]); env.RUST_SRC_PATH = "${rustbin}/lib/rustlib/src/rust/library"; diff --git a/nokhwa-bindings-linux/src/lib.rs b/nokhwa-bindings-linux/src/lib.rs index 8b7b8b3..fa7e2e7 100644 --- a/nokhwa-bindings-linux/src/lib.rs +++ b/nokhwa-bindings-linux/src/lib.rs @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +pub mod pipewire; #[cfg(feature = "v4l2")] pub mod v4l2; -pub mod pipewire; mod v4l2r; diff --git a/nokhwa-bindings-linux/src/pipewire.rs b/nokhwa-bindings-linux/src/pipewire.rs index 425ac29..d0d0501 100644 --- a/nokhwa-bindings-linux/src/pipewire.rs +++ b/nokhwa-bindings-linux/src/pipewire.rs @@ -1 +1 @@ -// TODO: todo, probably 0.12 \ No newline at end of file +// TODO: todo, probably 0.12 diff --git a/nokhwa-bindings-linux/src/v4l2.rs b/nokhwa-bindings-linux/src/v4l2.rs index 42e5ccd..7fb41b8 100644 --- a/nokhwa-bindings-linux/src/v4l2.rs +++ b/nokhwa-bindings-linux/src/v4l2.rs @@ -1,38 +1,57 @@ -use std::borrow::Cow; +use flume::{bounded, unbounded, Sender}; use nokhwa_core::camera::{Camera, Capture, Setting}; -use nokhwa_core::control::{ControlDescription, ControlFlags, ControlId, ControlValue, ControlValueDescriptor, Controls, Orientation}; +use nokhwa_core::control::{ + ControlDescription, ControlFlags, ControlId, ControlValue, ControlValueDescriptor, Controls, + Orientation, +}; use nokhwa_core::error::{NokhwaError, NokhwaResult}; +use nokhwa_core::frame_buffer::{CompactString, FrameBuffer, Metadata}; use nokhwa_core::frame_format::FrameFormat; use nokhwa_core::platform::{Backends, PlatformTrait}; use nokhwa_core::ranges::Range; use nokhwa_core::stream::{Event, StreamBounds, StreamConfiguration, StreamHandle}; use nokhwa_core::types::{CameraFormat, CameraIndex, CameraInformation, FrameRate, Resolution}; +use std::borrow::Cow; use std::collections::hash_map::{Keys, Values}; use std::collections::{HashMap, HashSet}; use std::num::NonZeroI32; use std::sync::Arc; use std::thread::JoinHandle; -use flume::{Sender, unbounded, bounded}; use v4l::context::enum_devices; use v4l::control::{Description, Flags, MenuItem, Type, Value}; use v4l::frameinterval::FrameIntervalEnum; +use v4l::io::traits::OutputStream; +use v4l::prelude::MmapStream; use v4l::video::output::Parameters; use v4l::video::Output; use v4l::{Capabilities, Control, Device, Format, FourCC, Fraction, FrameInterval}; -use v4l2_sys_mit::{V4L2_CAMERA_ORIENTATION_BACK, V4L2_CAMERA_ORIENTATION_EXTERNAL, V4L2_CAMERA_ORIENTATION_FRONT, V4L2_CID_AUTO_EXPOSURE_BIAS, V4L2_CID_AUTO_FOCUS_RANGE, V4L2_CID_AUTO_FOCUS_STATUS, V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_CAMERA_ORIENTATION, V4L2_CID_EXPOSURE_ABSOLUTE, V4L2_CID_EXPOSURE_AUTO, V4L2_CID_EXPOSURE_METERING, V4L2_CID_FLASH_LED_MODE, V4L2_CID_FLASH_STROBE, V4L2_CID_FLASH_STROBE_STATUS, V4L2_CID_FLASH_STROBE_STOP, V4L2_CID_FOCUS_ABSOLUTE, V4L2_CID_FOCUS_AUTO, V4L2_CID_FOCUS_RELATIVE, V4L2_CID_IRIS_ABSOLUTE, V4L2_CID_IRIS_RELATIVE, V4L2_CID_ISO_SENSITIVITY, V4L2_CID_ISO_SENSITIVITY_AUTO, V4L2_CID_ZOOM_ABSOLUTE, V4L2_CID_ZOOM_CONTINUOUS, V4L2_CID_ZOOM_RELATIVE}; -use v4l::io::traits::OutputStream; -use v4l::prelude::MmapStream; -use nokhwa_core::frame_buffer::{CompactString, FrameBuffer, Metadata}; +use v4l2_sys_mit::{ + V4L2_CAMERA_ORIENTATION_BACK, V4L2_CAMERA_ORIENTATION_EXTERNAL, V4L2_CAMERA_ORIENTATION_FRONT, + V4L2_CID_AUTO_EXPOSURE_BIAS, V4L2_CID_AUTO_FOCUS_RANGE, V4L2_CID_AUTO_FOCUS_STATUS, + V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_CAMERA_ORIENTATION, + V4L2_CID_EXPOSURE_ABSOLUTE, V4L2_CID_EXPOSURE_AUTO, V4L2_CID_EXPOSURE_METERING, + V4L2_CID_FLASH_LED_MODE, V4L2_CID_FLASH_STROBE, V4L2_CID_FLASH_STROBE_STATUS, + V4L2_CID_FLASH_STROBE_STOP, V4L2_CID_FOCUS_ABSOLUTE, V4L2_CID_FOCUS_AUTO, + V4L2_CID_FOCUS_RELATIVE, V4L2_CID_IRIS_ABSOLUTE, V4L2_CID_IRIS_RELATIVE, + V4L2_CID_ISO_SENSITIVITY, V4L2_CID_ISO_SENSITIVITY_AUTO, V4L2_CID_ZOOM_ABSOLUTE, + V4L2_CID_ZOOM_CONTINUOUS, V4L2_CID_ZOOM_RELATIVE, +}; fn index_capabilities_to_camera_info(index: u32, capabilities: Capabilities) -> CameraInformation { let name = capabilities.card; let description = capabilities.driver; - let misc = format!("{} v{}.{}.{} Flags: {}", capabilities.bus, capabilities.version.0, capabilities.version.1, capabilities.version.2, capabilities.capabilities); + let misc = format!( + "{} v{}.{}.{} Flags: {}", + capabilities.bus, + capabilities.version.0, + capabilities.version.1, + capabilities.version.2, + capabilities.capabilities + ); CameraInformation::new(name, description, misc, CameraIndex::Index(index)) } - macro_rules! define_back_and_forth { ( $($frame_format:expr => $fourcc:expr ,)+ ) => { fn frame_format_to_fourcc(frame_format: FrameFormat) -> Result { @@ -51,7 +70,7 @@ macro_rules! define_back_and_forth { return Err(NokhwaError::ConversionError("Unsupported FrameFormat".to_string())) }} } - + fn fourcc_to_frame_format(four_cc: FourCC) -> FrameFormat { match &four_cc.repr { $( @@ -63,7 +82,6 @@ macro_rules! define_back_and_forth { } } - define_back_and_forth!( FrameFormat::H265 => b"HEVC", FrameFormat::H264 => b"H264", @@ -210,74 +228,71 @@ fn convert_description_to_ctrl_body(description: Description) -> Option { - ( - ControlValueDescriptor::Integer(Range::new(description.minimum, description.maximum, Some(description.step as i64))), - Some(ControlValue::Integer(description.default)) - ) - } - Type::U8 => { - ( - ControlValueDescriptor::Integer(Range::new(0, u8::MAX_VALUE as i64, Some(description.step as i64))), - Some(ControlValue::Integer(description.default)) - ) - } - Type::U16 => { - ( - ControlValueDescriptor::Integer(Range::new(0, u16::MAX_VALUE as i64, Some(description.step as i64))), - Some(ControlValue::Integer(description.default)) - ) - } - Type::U32 => { - ( - ControlValueDescriptor::Integer(Range::new(0, u32::MAX_VALUE as i64, Some(description.step as i64))), - Some(ControlValue::Integer(description.default)) - ) - } - Type::String => { - ( - ControlValueDescriptor::String, - None, - ) - } - Type::Boolean => { - ( - ControlValueDescriptor::Boolean, - Some(ControlValue::Boolean(description.default != 0)) - ) - } - Type::Bitmask => { - ( - ControlValueDescriptor::BitMask, - Some(ControlValue::BitMask(description.default as u64)) - ) - } + Type::Integer | Type::Integer64 => ( + ControlValueDescriptor::Integer(Range::new( + description.minimum, + description.maximum, + Some(description.step as i64), + )), + Some(ControlValue::Integer(description.default)), + ), + Type::U8 => ( + ControlValueDescriptor::Integer(Range::new( + 0, + u8::MAX_VALUE as i64, + Some(description.step as i64), + )), + Some(ControlValue::Integer(description.default)), + ), + Type::U16 => ( + ControlValueDescriptor::Integer(Range::new( + 0, + u16::MAX_VALUE as i64, + Some(description.step as i64), + )), + Some(ControlValue::Integer(description.default)), + ), + Type::U32 => ( + ControlValueDescriptor::Integer(Range::new( + 0, + u32::MAX_VALUE as i64, + Some(description.step as i64), + )), + Some(ControlValue::Integer(description.default)), + ), + Type::String => (ControlValueDescriptor::String, None), + Type::Boolean => ( + ControlValueDescriptor::Boolean, + Some(ControlValue::Boolean(description.default != 0)), + ), + Type::Bitmask => ( + ControlValueDescriptor::BitMask, + Some(ControlValue::BitMask(description.default as u64)), + ), Type::IntegerMenu | Type::Menu => { // our keys let descriptor = match description.items { - Some(items) => { - ControlValueDescriptor::Menu(items.into_iter().map(|(idx, menu_item)| { - (ControlValue::Integer(idx as i64), match menu_item { - MenuItem::Name(name) => ControlValue::String(name), - MenuItem::Value(v) => ControlValue::Integer(*v), + Some(items) => ControlValueDescriptor::Menu( + items + .into_iter() + .map(|(idx, menu_item)| { + ( + ControlValue::Integer(idx as i64), + match menu_item { + MenuItem::Name(name) => ControlValue::String(name), + MenuItem::Value(v) => ControlValue::Integer(*v), + }, + ) }) - }).collect::>()) - } + .collect::>(), + ), // This can probably never happen so we just immediately return if this bad thing // happens somehow None => return None, }; - ( - descriptor, - Some(ControlValue::Integer(description.default)) - ) - } - Type::Button => { - ( - ControlValueDescriptor::Null, - None, - ) + (descriptor, Some(ControlValue::Integer(description.default))) } + Type::Button => (ControlValueDescriptor::Null, None), // we simply will not support control class. // if someone needs it we can fix it later. @@ -287,17 +302,13 @@ fn convert_description_to_ctrl_body(description: Description) -> Option return None, }; - ControlDescription::new( - flags, - descriptor, - default - ) + ControlDescription::new(flags, descriptor, default) } fn conv_control_value_to_v4l_value(control: ControlValue) -> Result { let value = match control { ControlValue::Null => Value::None, - ControlValue::Integer(i) | ControlValue::BitMask(i) => Value::Integer(i), + ControlValue::Integer(i) | ControlValue::BitMask(i) => Value::Integer(i), ControlValue::String(s) => Value::String(s), ControlValue::Boolean(t) => Value::Boolean(t), ControlValue::Binary(b) => Value::CompoundU8(b), @@ -305,7 +316,9 @@ fn conv_control_value_to_v4l_value(control: ControlValue) -> Result Value::Integer(match o { @@ -315,14 +328,15 @@ fn conv_control_value_to_v4l_value(control: ControlValue) -> Result V4L2_CAMERA_ORIENTATION_EXTERNAL as i64, }), _ => { - return Err(NokhwaError::ConversionError("Conversion not supported for this data type.".to_string())) + return Err(NokhwaError::ConversionError( + "Conversion not supported for this data type.".to_string(), + )) } }; Ok(value) } - pub struct V4L2Platform {} impl PlatformTrait for V4L2Platform { @@ -338,25 +352,30 @@ impl PlatformTrait for V4L2Platform { } fn query(&mut self) -> NokhwaResult> { - Ok(enum_devices().into_iter() + Ok(enum_devices() + .into_iter() .map(|v4l_node| { let index = v4l_node.index(); // open camera for capabilities. if we dont get any, dont return the camera - Device::new(index).map(|dev| - dev.query_caps().map(|caps| { - index_capabilities_to_camera_info(index as u32, caps) - }).ok() - ).ok().flatten() - }).flatten().collect::>()) + Device::new(index) + .map(|dev| { + dev.query_caps() + .map(|caps| index_capabilities_to_camera_info(index as u32, caps)) + .ok() + }) + .ok() + .flatten() + }) + .flatten() + .collect::>()) } fn open(&mut self, index: CameraIndex) -> NokhwaResult { let device = match &index { CameraIndex::Index(i) => Device::new(*i as usize), - CameraIndex::String(path) => Device::with_path(path) - }.map_err(|why| { - NokhwaError::OpenDeviceError(index.to_string(), why.to_string()) - })?; + CameraIndex::String(path) => Device::with_path(path), + } + .map_err(|why| NokhwaError::OpenDeviceError(index.to_string(), why.to_string()))?; let mut v4l2_camera = V4L2Camera { device, @@ -384,85 +403,131 @@ impl Setting for V4L2Camera { fn enumerate_formats(&self) -> Result, NokhwaError> { let mut formats = vec![]; - for frame_format in self.device.enum_formats().map_err(|why| { - NokhwaError::GetPropertyError { property: "enum_formats".to_string(), error: why.to_string() } - })?.into_iter().map(|desc| { - fourcc_to_frame_format(desc.fourcc) - }) { + for frame_format in self + .device + .enum_formats() + .map_err(|why| NokhwaError::GetPropertyError { + property: "enum_formats".to_string(), + error: why.to_string(), + })? + .into_iter() + .map(|desc| fourcc_to_frame_format(desc.fourcc)) + { formats.extend( - self.enumerate_resolution_and_frame_rates(frame_format)?.into_iter().flat_map(|(resolution, frame_rates)| { - frame_rates.into_iter().map(|frame_rate| { - CameraFormat::new(resolution, frame_format, frame_rate) - }) - }) + self.enumerate_resolution_and_frame_rates(frame_format)? + .into_iter() + .flat_map(|(resolution, frame_rates)| { + frame_rates.into_iter().map(|frame_rate| { + CameraFormat::new(resolution, frame_format, frame_rate) + }) + }), ); } Ok(formats) } - fn enumerate_resolution_and_frame_rates(&self, frame_format: FrameFormat) -> Result>, NokhwaError> { + fn enumerate_resolution_and_frame_rates( + &self, + frame_format: FrameFormat, + ) -> Result>, NokhwaError> { let fourcc = frame_format_to_fourcc(frame_format)?; - let resolutions = self.device.enum_framesizes(fourcc).map_err(|why| { - NokhwaError::GetPropertyError { property: "enum_framesizes".to_string(), error: why.to_string() } - })?.into_iter() - .flat_map(|frame_size| { - frame_size.size.to_discrete() - }).map(|discrete| { Resolution::new(discrete.width, discrete.height) - }).collect::>(); + let resolutions = self + .device + .enum_framesizes(fourcc) + .map_err(|why| NokhwaError::GetPropertyError { + property: "enum_framesizes".to_string(), + error: why.to_string(), + })? + .into_iter() + .flat_map(|frame_size| frame_size.size.to_discrete()) + .map(|discrete| Resolution::new(discrete.width, discrete.height)) + .collect::>(); - let v4l2_frame_intervals = resolutions.iter() - .map(|resolution| (*resolution, self.device.enum_frameintervals(fourcc, resolution.width(), resolution.height()))) + let v4l2_frame_intervals = resolutions + .iter() + .map(|resolution| { + ( + *resolution, + self.device.enum_frameintervals( + fourcc, + resolution.width(), + resolution.height(), + ), + ) + }) .collect::)>, std::io::Error>>() - .map_err(|why| { - NokhwaError::GetPropertyError { property: "enum_frameintervals".to_string(), error: why.to_string() } - })?; + .map_err(|why| NokhwaError::GetPropertyError { + property: "enum_frameintervals".to_string(), + error: why.to_string(), + })?; - Ok(v4l2_frame_intervals.into_iter().flatten().flat_map(|(resolution, interval)| { - match interval.interval { - FrameIntervalEnum::Discrete(discrete) => { - NonZeroI32::new(discrete.denominator as i32).map(|denominator| { - (resolution, vec![FrameRate::new(discrete.numerator as i32, denominator)]) - }) - } - FrameIntervalEnum::Stepwise(stepwise) => { - // we have to do this ourselves - - // no logic to handle different or zero demoninator - if (stepwise.step.denominator != stepwise.max.denominator) || (stepwise.step.denominator != stepwise.min.denominator) { - return None + Ok(v4l2_frame_intervals + .into_iter() + .flatten() + .flat_map(|(resolution, interval)| { + match interval.interval { + FrameIntervalEnum::Discrete(discrete) => { + NonZeroI32::new(discrete.denominator as i32).map(|denominator| { + ( + resolution, + vec![FrameRate::new(discrete.numerator as i32, denominator)], + ) + }) } + FrameIntervalEnum::Stepwise(stepwise) => { + // we have to do this ourselves - let min = stepwise.min.numerator as i32; - let max = stepwise.max.numerator as i32; - let step = stepwise.step.numerator as i32; - let denominator = stepwise.step.denominator as i32; + // no logic to handle different or zero demoninator + if (stepwise.step.denominator != stepwise.max.denominator) + || (stepwise.step.denominator != stepwise.min.denominator) + { + return None; + } - NonZeroI32::new(denominator).map(|denominator| { - (resolution, (min..max).step_by(step as usize).map(|numerator| { - FrameRate::new(numerator, denominator) - }).collect::>()) - }) + let min = stepwise.min.numerator as i32; + let max = stepwise.max.numerator as i32; + let step = stepwise.step.numerator as i32; + let denominator = stepwise.step.denominator as i32; + + NonZeroI32::new(denominator).map(|denominator| { + ( + resolution, + (min..max) + .step_by(step as usize) + .map(|numerator| FrameRate::new(numerator, denominator)) + .collect::>(), + ) + }) + } } - } - }).flatten().collect::>>()) + }) + .flatten() + .collect::>>()) } fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError> { let fourcc = frame_format_to_fourcc(*camera_format.format())?; - self.device.set_format( - &Format::new(camera_format.width(), camera_format.height(), fourcc) - ).map_err(|why| NokhwaError::SetPropertyError { - property: "set_format".to_string(), - value: format!("format: {camera_format} fourcc: {fourcc}"), - error: why.to_string(), - })?; - self.device.set_params(&Parameters::new(Fraction::new(*camera_format.frame_rate().numerator() as u32, *camera_format.frame_rate().denominator() as u32))).map_err(|why| { - NokhwaError::SetPropertyError { + self.device + .set_format(&Format::new( + camera_format.width(), + camera_format.height(), + fourcc, + )) + .map_err(|why| NokhwaError::SetPropertyError { + property: "set_format".to_string(), + value: format!("format: {camera_format} fourcc: {fourcc}"), + error: why.to_string(), + })?; + self.device + .set_params(&Parameters::new(Fraction::new( + *camera_format.frame_rate().numerator() as u32, + *camera_format.frame_rate().denominator() as u32, + ))) + .map_err(|why| NokhwaError::SetPropertyError { property: "set_params".to_string(), value: format!("{}", camera_format.frame_rate()), error: why.to_string(), - } - })?; + })?; self.camera_format = Some(camera_format); Ok(()) } @@ -487,60 +552,91 @@ impl Setting for V4L2Camera { self.controls.description(id) } - fn set_control(&mut self, property: &ControlId, value: ControlValue) -> Result<(), NokhwaError> { + fn set_control( + &mut self, + property: &ControlId, + value: ControlValue, + ) -> Result<(), NokhwaError> { if !self.controls.validate(property, &value)? { return Err(NokhwaError::SetPropertyError { property: property.to_string(), value: value.to_string(), error: "failed to validate".to_string(), - }) + }); } let cid = control_id_to_cid(*property)?; let v4l_value = conv_control_value_to_v4l_value(value.clone())?; - self.device.set_control(Control { id: cid, value: v4l_value }).map_err(|why| { - Err(NokhwaError::SetPropertyError { - property: cid.to_string(), - value: value.to_string(), - error: why.to_string(), + self.device + .set_control(Control { + id: cid, + value: v4l_value, }) - })?; + .map_err(|why| { + Err(NokhwaError::SetPropertyError { + property: cid.to_string(), + value: value.to_string(), + error: why.to_string(), + }) + })?; self.controls.set_control_value(property, value)?; Ok(()) } fn refresh_controls(&mut self) -> Result<(), NokhwaError> { - let descriptions = self.device.query_controls().map_err(|why| { - NokhwaError::GetPropertyError { property: "query_controls".to_string(), error: why.to_string() } - })?.into_iter().map(|description| { - let id = cid_to_control_id(description.id); + let descriptions = self + .device + .query_controls() + .map_err(|why| NokhwaError::GetPropertyError { + property: "query_controls".to_string(), + error: why.to_string(), + })? + .into_iter() + .map(|description| { + let id = cid_to_control_id(description.id); - convert_description_to_ctrl_body(description).map(|body| { - (id, body) + convert_description_to_ctrl_body(description).map(|body| (id, body)) }) - }).flatten().collect::>(); + .flatten() + .collect::>(); - let values = descriptions.keys().into_iter().copied().flat_map(|k| control_id_to_cid(k).map(|cid| (k, cid))).flat_map(|(id, cid)| { - self.device.control(cid).map(|v| (id, v)) - }).map(|(id, value)| { - (id, match value.value { - Value::None => ControlValue::Null, - Value::Integer(i) => ControlValue::Integer(i), - Value::Boolean(b) => ControlValue::Boolean(b), - Value::String(s) => ControlValue::String(s), - Value::CompoundU8(bin) | Value::CompoundPtr(bin) => ControlValue::Binary(bin), - Value::CompoundU16(u) | Value::CompoundU32(u) => ControlValue::Array( - u.into_iter().map(|u| ControlValue::Integer(u as i64)).collect() - ), + let values = descriptions + .keys() + .into_iter() + .copied() + .flat_map(|k| control_id_to_cid(k).map(|cid| (k, cid))) + .flat_map(|(id, cid)| self.device.control(cid).map(|v| (id, v))) + .map(|(id, value)| { + ( + id, + match value.value { + Value::None => ControlValue::Null, + Value::Integer(i) => ControlValue::Integer(i), + Value::Boolean(b) => ControlValue::Boolean(b), + Value::String(s) => ControlValue::String(s), + Value::CompoundU8(bin) | Value::CompoundPtr(bin) => { + ControlValue::Binary(bin) + } + Value::CompoundU16(u) | Value::CompoundU32(u) => ControlValue::Array( + u.into_iter() + .map(|u| ControlValue::Integer(u as i64)) + .collect(), + ), + }, + ) }) - }).collect::>(); + .collect::>(); match Controls::new(descriptions, values) { - Some(c) => { self.controls = c; } - None => return Err(NokhwaError::SetPropertyError { - property: "control".to_string(), - value: format!("{:?} {:?}", descriptions, values), - error: "Failed to convert to control".to_string(), - }) + Some(c) => { + self.controls = c; + } + None => { + return Err(NokhwaError::SetPropertyError { + property: "control".to_string(), + value: format!("{:?} {:?}", descriptions, values), + error: "Failed to convert to control".to_string(), + }) + } } Ok(()) @@ -559,16 +655,21 @@ impl Drop for V4L2Stream { } impl Capture for V4L2Camera { - fn open_stream(&mut self, stream_configuration: Option) -> Result { + fn open_stream( + &mut self, + stream_configuration: Option, + ) -> Result { if let Some(_) = self.stream { - return Err(NokhwaError::OpenStreamError("StreamAlreadyOpen".to_string())) + return Err(NokhwaError::OpenStreamError( + "StreamAlreadyOpen".to_string(), + )); } let stream_config = stream_configuration.unwrap_or_default(); let format = match self.camera_format { Some(fmt) => fmt, - None => return Err(NokhwaError::OpenStreamError("No Format".to_string())) + None => return Err(NokhwaError::OpenStreamError("No Format".to_string())), }; let (control, ctrl_recv) = bounded(1); @@ -579,64 +680,79 @@ impl Capture for V4L2Camera { let control = Arc::new(control); - let mut mmap_stream = MmapStream::new(&self.device, v4l::buffer::Type::VideoCapture).map_err(|why| { - return NokhwaError::OpenStreamError(why.to_string()) - })?; + let mut mmap_stream = MmapStream::new(&self.device, v4l::buffer::Type::VideoCapture) + .map_err(|why| return NokhwaError::OpenStreamError(why.to_string()))?; let stream_handle = StreamHandle::new(receiver, control.clone(), stream_config, format); - let thread = std::thread::spawn(move || { + let thread = std::thread::spawn(move || loop { + if ctrl_recv.is_disconnected() || sender.is_disconnected() { + return; + } + if let Ok(_) = ctrl_recv.try_recv() { + let _ = sender.send(Event::Closed); + return; + } - loop { - if ctrl_recv.is_disconnected() || sender.is_disconnected() { - return; + match mmap_stream.next() { + Ok((data, meta)) => { + let data = Cow::Owned(data.to_owned()); + let mut metadata = Metadata::new(); + + metadata.insert( + CompactString::from("flags"), + ControlValue::BitMask(meta.flags.bits() as u64), + ); + metadata.insert( + CompactString::from("time_secs"), + ControlValue::Integer(meta.timestamp.sec), + ); + metadata.insert( + CompactString::from("time_usecs"), + ControlValue::Integer(meta.timestamp.usec), + ); + metadata.insert( + CompactString::from("size"), + ControlValue::Integer(meta.bytesused as i64), + ); + metadata.insert( + CompactString::from("sequence"), + ControlValue::Integer(meta.sequence as i64), + ); + metadata.insert( + CompactString::from("field"), + ControlValue::Integer(meta.field as i64), + ); + + let _ = sender.send(Event::NewFrame(FrameBuffer::new(data, Some(metadata)))); } - if let Ok(_) = ctrl_recv.try_recv() { - let _ = sender.send(Event::Closed); - return; - } - - match mmap_stream.next() { - Ok((data, meta)) => { - let data = Cow::Owned(data.to_owned()); - let mut metadata = Metadata::new(); - - metadata.insert(CompactString::from("flags"), ControlValue::BitMask(meta.flags.bits() as u64)); - metadata.insert(CompactString::from("time_secs"), ControlValue::Integer(meta.timestamp.sec)); - metadata.insert(CompactString::from("time_usecs"), ControlValue::Integer(meta.timestamp.usec)); - metadata.insert(CompactString::from("size"), ControlValue::Integer(meta.bytesused as i64)); - metadata.insert(CompactString::from("sequence"), ControlValue::Integer(meta.sequence as i64)); - metadata.insert(CompactString::from("field"), ControlValue::Integer(meta.field as i64)); - - let _ = sender.send(Event::NewFrame(FrameBuffer::new(data, Some(metadata)))); - } - Err(why) => { - let _ = sender.send(Event::Error(Box::new(why))); - } + Err(why) => { + let _ = sender.send(Event::Error(Box::new(why))); } } }); - self.stream = Some( - V4L2Stream { - thread, - control, - } - ); + self.stream = Some(V4L2Stream { thread, control }); Ok(stream_handle) - } fn close_stream(&mut self) -> Result<(), NokhwaError> { let mut stream = match std::mem::take(&mut self.stream) { Some(s) => s, - None => return Err(NokhwaError::StreamShutdownError("No stream to shutdown".to_string())), + None => { + return Err(NokhwaError::StreamShutdownError( + "No stream to shutdown".to_string(), + )) + } }; let _ = stream.control.send(()); - stream.thread.join().map_err(|why| NokhwaError::StreamShutdownError(format!("{why:?}")))?; + stream + .thread + .join() + .map_err(|why| NokhwaError::StreamShutdownError(format!("{why:?}")))?; Ok(()) } diff --git a/nokhwa-bindings-linux/src/v4l2r.rs b/nokhwa-bindings-linux/src/v4l2r.rs index 0ffdd02..70b786d 100644 --- a/nokhwa-bindings-linux/src/v4l2r.rs +++ b/nokhwa-bindings-linux/src/v4l2r.rs @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 6d230d6..1dc5837 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -222,11 +222,13 @@ mod internal { kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, }; use flume::{Receiver, Sender}; + use nokhwa_core::control::{ + CameraControl, ControlValue, ControlValueDescription, KnownCameraControl, + }; use nokhwa_core::{ error::NokhwaError, types::{ - ApiBackend, CameraFormat, CameraIndex, CameraInformation, - FrameFormat, + ApiBackend, CameraFormat, CameraIndex, CameraInformation, FrameFormat, KnownCameraControlFlag, Resolution, }, }; @@ -246,7 +248,6 @@ mod internal { ffi::{c_float, c_void, CStr}, sync::Arc, }; - use nokhwa_core::control::{CameraControl, ControlValueDescription, ControlValue, KnownCameraControl}; const UTF8_ENCODING: usize = 4; type CGFloat = c_float; diff --git a/nokhwa-bindings-windows/src/lib.rs b/nokhwa-bindings-windows/src/lib.rs index 51cb7b3..b7e6fec 100644 --- a/nokhwa-bindings-windows/src/lib.rs +++ b/nokhwa-bindings-windows/src/lib.rs @@ -29,10 +29,13 @@ #[cfg(all(windows, not(feature = "docs-only")))] pub mod wmf { + use nokhwa_core::control::{ + CameraControl, ControlValue, ControlValueDescription, KnownCameraControl, + }; use nokhwa_core::error::NokhwaError; use nokhwa_core::types::{ - ApiBackend, CameraFormat, CameraIndex, CameraInformation, - FrameFormat, KnownCameraControlFlag, Resolution, + ApiBackend, CameraFormat, CameraIndex, CameraInformation, FrameFormat, + KnownCameraControlFlag, Resolution, }; use once_cell::sync::Lazy; use std::ffi::c_void; @@ -46,7 +49,6 @@ pub mod wmf { Arc, }, }; - use nokhwa_core::control::{CameraControl, ControlValueDescription, ControlValue, KnownCameraControl}; use windows::Win32::Media::DirectShow::{CameraControl_Flags_Auto, CameraControl_Flags_Manual}; use windows::Win32::Media::MediaFoundation::{ MFCreateSample, MF_SOURCE_READER_FIRST_VIDEO_STREAM, @@ -66,10 +68,9 @@ pub mod wmf { KernelStreaming::GUID_NULL, MediaFoundation::{ IMFActivate, IMFAttributes, IMFMediaSource, IMFSample, IMFSourceReader, - MFCreateAttributes, MFCreateSourceReaderFromMediaSource, - MFEnumDeviceSources, MFShutdown, MFStartup, - MFSTARTUP_NOSOCKET, MF_API_VERSION, MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MFCreateAttributes, MFCreateSourceReaderFromMediaSource, MFEnumDeviceSources, + 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_MT_FRAME_RATE, MF_MT_FRAME_RATE_RANGE_MAX, MF_MT_FRAME_RATE_RANGE_MIN, MF_MT_FRAME_SIZE, @@ -268,7 +269,7 @@ pub mod wmf { // return early if we have no devices connected if count >= 0 { - return Ok(device_list) + return Ok(device_list); } unsafe { from_raw_parts(unused_mf_activate.assume_init(), count as usize) } @@ -629,7 +630,7 @@ pub mod wmf { None => { index += 1; continue; - }, + } }; for frame_rate in framerate_list { @@ -999,7 +1000,7 @@ pub mod wmf { // Otherwise, constructing IMFMediaType from scratch can sometimes fail due to not exactly matching. // Therefore, we search for the first media_type that matches and also works correctly. - let mut last_error : Option = None; + let mut last_error: Option = None; let mut index = 0; while let Ok(media_type) = unsafe { @@ -1040,7 +1041,11 @@ pub mod wmf { } }; - if (Resolution { width_x: width, height_y: height }) != format.resolution() { + if (Resolution { + width_x: width, + height_y: height, + }) != format.resolution() + { continue; } @@ -1093,7 +1098,7 @@ pub mod wmf { self.device_format = format; self.format_refreshed()?; return Ok(()); - }, + } Err(why) => { last_error = Some(NokhwaError::SetPropertyError { property: "MEDIA_FOUNDATION_FIRST_VIDEO_STREAM".to_string(), @@ -1240,12 +1245,10 @@ pub mod wmf { #[allow(clippy::needless_pass_by_value)] #[allow(clippy::must_use_candidate)] pub mod wmf { - use nokhwa_core::error::NokhwaError; - use nokhwa_core::types::{ - CameraFormat, CameraIndex, CameraInformation, - }; - use std::borrow::Cow; use nokhwa_core::control::{CameraControl, ControlValue, KnownCameraControl}; + use nokhwa_core::error::NokhwaError; + use nokhwa_core::types::{CameraFormat, CameraIndex, CameraInformation}; + use std::borrow::Cow; pub fn initialize_mf() -> Result<(), NokhwaError> { Err(NokhwaError::NotImplementedError( diff --git a/nokhwa-core/Cargo.toml b/nokhwa-core/Cargo.toml index 31a6f3e..85a072d 100644 --- a/nokhwa-core/Cargo.toml +++ b/nokhwa-core/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/l1npengtul/nokhwa" [features] default = [] serialize = ["serde"] -wgpu-types = ["wgpu"] +wgpu = ["wgpu-types"] opencv-mat = ["opencv", "opencv/clang-runtime"] docs-features = ["serialize", "wgpu-types"] async = ["async-trait", "flume/async", "futures-core"] @@ -27,6 +27,8 @@ num-traits = "0.2" ordered-float = "5" typed-builder = "0.21" compact_str = "0.9" +bytemuck = "1.23" +smallmap = "1.4" [dependencies.num-rational] version = "0.4" @@ -37,17 +39,12 @@ features = ["serde", "std"] version = "0.25" default-features = false -[dependencies.small-map] -version = "0.1.3" -default-features = false -features = ["fxhash"] - [dependencies.serde] version = "1.0" features = ["derive"] optional = true -[dependencies.wgpu] +[dependencies.wgpu-types] version = "25" optional = true diff --git a/nokhwa-core/src/camera.rs b/nokhwa-core/src/camera.rs index 8d0032f..19d3065 100644 --- a/nokhwa-core/src/camera.rs +++ b/nokhwa-core/src/camera.rs @@ -1,20 +1,26 @@ -use crate::control::{ControlDescription, ControlId, ControlValue, Controls}; +use crate::control::{ControlDescription, ControlId, ControlValue}; use crate::error::NokhwaError; use crate::frame_format::FrameFormat; use crate::stream::{StreamConfiguration, StreamHandle}; use crate::types::{CameraFormat, FrameRate, Resolution}; -use std::collections::hash_map::{Keys, Values}; use std::collections::HashMap; +use std::collections::hash_map::{Keys, Values}; use std::sync::Arc; pub trait Setting { + /// # Errors + /// Will error on fn enumerate_formats(&self) -> Result, NokhwaError>; + /// # Errors + /// Will error on fn enumerate_resolution_and_frame_rates( &self, frame_format: FrameFormat, ) -> Result>, NokhwaError>; + /// # Errors + /// Will error on fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError>; fn control_ids(&self) -> Keys; @@ -27,9 +33,13 @@ pub trait Setting { fn control_description(&self, id: &ControlId) -> Option<&ControlDescription>; + /// # Errors + /// Will error on fn set_control(&mut self, property: &ControlId, value: ControlValue) - -> Result<(), NokhwaError>; + -> Result<(), NokhwaError>; + /// # Errors + /// Will error on fn refresh_controls(&mut self) -> Result<(), NokhwaError>; } @@ -43,9 +53,7 @@ pub trait AsyncSetting { ) -> Result>, NokhwaError>; async fn set_format_async(&self, camera_format: CameraFormat) -> Result<(), NokhwaError>; - - async fn properties_async(&self) -> &Controls; - + async fn set_property_async( &mut self, property: &ControlId, @@ -55,15 +63,25 @@ pub trait AsyncSetting { pub trait Capture { /// Implementations MUST guarantee that there can only ever be one stream open at once. - fn open_stream(&mut self, stream_configuration: Option) -> Result, NokhwaError>; + /// # Errors + /// Errors are driver specific + fn open_stream( + &mut self, + stream_configuration: Option, + ) -> Result, NokhwaError>; // Implementations MUST be multi-close tolerant. + /// # Errors + /// Errors are driver specific fn close_stream(&mut self) -> Result<(), NokhwaError>; } #[cfg(feature = "async")] pub trait AsyncStream { - async fn open_stream_async(&mut self, stream_configuration: Option) -> Result; + async fn open_stream_async( + &mut self, + stream_configuration: Option, + ) -> Result; async fn close_stream_async(&mut self) -> Result<(), NokhwaError>; } diff --git a/nokhwa-core/src/codec.rs b/nokhwa-core/src/codec.rs new file mode 100644 index 0000000..f2d8956 --- /dev/null +++ b/nokhwa-core/src/codec.rs @@ -0,0 +1,69 @@ +use crate::error::NokhwaError; +use crate::frame_format::FrameFormat; +use crate::types::CameraFormat; +use std::fmt::Debug; + +pub trait Codec { + type Config: Clone + Debug; + + type Input<'a>; + + type Output; + + type WrittenMeta: Clone + Debug; + + /// # Errors + /// Errors are decoder specific. + fn allowed_formats(&self) -> Result<&[FrameFormat], NokhwaError>; + + fn config(&self) -> &Self::Config; + + /// # Errors + /// Errors are decoder specific. + fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError>; + + /// # Errors + /// Errors are decoder specific. + fn send_item(&mut self, input: Self::Input<'_>) -> Result<(), NokhwaError>; + + fn receive_decoded_item( + &mut self, + writing_output: &mut Self::Output, + ) -> Result; + + fn preferred_buffer_min_size( + &mut self, + format: &Option, + ) -> Result, NokhwaError>; + + fn deinitialize(&mut self) -> Result<(), NokhwaError>; +} + +#[cfg(feature = "async")] +pub trait CodecAsync: Codec { + + async fn allowed_formats_async(&self) -> Result<&[FrameFormat], NokhwaError> { + self.allowed_formats() + } + + async fn set_format_async(&self, format: CameraFormat) -> Result<(), NokhwaError>; + + async fn config_async(&self) -> &Self::Config { + self.config() + } + + fn set_config_async(&mut self, config: Self::Config) -> Result<(), NokhwaError> { + self.set_config(config) + } + + fn send_item_async(&mut self, input: &Self::Input) -> Result<(), NokhwaError>; + + fn receive_decoded_item_async( + &mut self, + writing_output: &mut Self::Output, + ) -> Result, NokhwaError>; + + async fn deinitialize_async(&mut self) -> Result<(), NokhwaError> { + self.deinitialize() + } +} diff --git a/nokhwa-core/src/control.rs b/nokhwa-core/src/control.rs index f80b1fd..4fa8f5d 100644 --- a/nokhwa-core/src/control.rs +++ b/nokhwa-core/src/control.rs @@ -1,5 +1,6 @@ use crate::error::{NokhwaError, NokhwaResult}; use crate::ranges::{Range, ValidatableRange}; +use compact_str::CompactString; use ordered_float::OrderedFloat; use std::collections::hash_map::{Keys, Values}; use std::collections::{HashMap, HashSet}; @@ -60,11 +61,11 @@ pub struct Controls { impl Controls { /// INVARIANTS: All `ControlId` in `device_values` MUST exist in `device_controls` - pub fn new( + #[must_use] pub fn new( device_controls: HashMap, device_values: HashMap, ) -> Option { - for (id, value) in device_values.iter() { + for (id, value) in &device_values { if let Some(description) = device_controls.get(id) { if !description.validate(value) { return None; @@ -78,11 +79,11 @@ impl Controls { }) } - pub fn empty() -> Self { + #[must_use] pub fn empty() -> Self { Self::default() } - pub fn unchecked_new( + #[must_use] pub fn unchecked_new( device_controls: HashMap, device_values: HashMap, ) -> Self { @@ -92,36 +93,39 @@ impl Controls { } } - pub fn description(&self, control_id: &ControlId) -> Option<&ControlDescription> { + #[must_use] pub fn description(&self, control_id: &ControlId) -> Option<&ControlDescription> { self.descriptions.get(control_id) } - pub fn value(&self, control_id: &ControlId) -> Option<&ControlValue> { + #[must_use] pub fn value(&self, control_id: &ControlId) -> Option<&ControlValue> { self.values.get(control_id) } - pub fn descriptions(&self) -> Values { + #[must_use] pub fn descriptions(&self) -> Values { self.descriptions.values() } - pub fn values(&self) -> Values { + #[must_use] pub fn values(&self) -> Values { self.values.values() } - pub fn ids(&self) -> Keys { + #[must_use] pub fn ids(&self) -> Keys { self.descriptions.keys() } - pub fn validate(&self, control_id: &ControlId, value: &ControlValue) -> Result { - let description = match self.descriptions.get(control_id) { - Some(desc) => desc, - None => return Err(NokhwaError::GetPropertyError { - property: control_id.to_string(), - error: "ID Not Found".to_string(), - }), - }; + pub fn validate( + &self, + control_id: &ControlId, + value: &ControlValue, + ) -> Result { + let Some(description) = self.descriptions.get(control_id) else { + return Err(NokhwaError::GetPropertyError { + property: control_id.to_string(), + error: "ID Not Found".to_string(), + }); + }; - if let None = self.values.get(control_id) { + if !self.values.contains_key(control_id) { return Err(NokhwaError::GetPropertyError { property: control_id.to_string(), error: "ID Not Found".to_string(), @@ -159,7 +163,7 @@ pub struct ControlDescription { } impl ControlDescription { - pub fn new( + #[must_use] pub fn new( control_flags: HashSet, control_value_descriptor: ControlValueDescriptor, default_value: Option, @@ -177,7 +181,7 @@ impl ControlDescription { }) } - pub fn new_unchecked( + #[must_use] pub fn new_unchecked( control_flags: HashSet, control_value_descriptor: ControlValueDescriptor, default_value: Option, @@ -189,15 +193,15 @@ impl ControlDescription { } } - pub fn flags(&self) -> &HashSet { + #[must_use] pub fn flags(&self) -> &HashSet { &self.flags } - pub fn descriptor(&self) -> &ControlValueDescriptor { + #[must_use] pub fn descriptor(&self) -> &ControlValueDescriptor { &self.descriptor } - pub fn default_value(&self) -> &Option { + #[must_use] pub fn default_value(&self) -> &Option { &self.default_value } @@ -209,7 +213,7 @@ impl ControlDescription { self.flags.remove(&flag) } - pub fn validate(&self, value: &ControlValue) -> bool { + #[must_use] pub fn validate(&self, value: &ControlValue) -> bool { self.descriptor.validate(value) } } @@ -233,11 +237,11 @@ pub enum ControlValueDescriptor { Null, Integer(Range), BitMask, - Float(Range), + Float(Range>), String, Boolean, // Array of any values of singular type - Array(ControlValueDescriptor), + Array(Box), // Menu(Enum) of valid choices // The keys are valid choices, // the values represent what the choice is (usually a string or int). @@ -257,7 +261,7 @@ pub enum ControlValueDescriptor { } impl ControlValueDescriptor { - pub fn validate(&self, value: &ControlValue) -> bool { + #[must_use] pub fn validate(&self, value: &ControlValue) -> bool { match self { ControlValueDescriptor::Null => { if let &ControlValue::Null = value { @@ -291,12 +295,12 @@ impl ControlValueDescriptor { } ControlValueDescriptor::Array(arr) => { if let &ControlValue::Array(_) = value { - return arr.is_valid_value(value); + return arr.validate(value); } } ControlValueDescriptor::Binary(size_limits) => { if let ControlValue::Binary(bin) = value { - return size_limits.validate(bin.len() as u64); + return size_limits.validate(&(bin.len() as u64)); } } ControlValueDescriptor::Menu(choices) => { @@ -328,7 +332,7 @@ pub enum ControlValue { Integer(i64), BitMask(u64), Float(OrderedFloat), - String(String), + String(CompactString), Boolean(bool), Array(Vec), Binary(Vec), @@ -338,9 +342,8 @@ pub enum ControlValue { } impl ControlValue { - pub fn is_primitive(&self) -> bool { - match self { - ControlValue::Null + #[must_use] pub fn is_primitive(&self) -> bool { + matches!(self, ControlValue::Null | ControlValue::Integer(_) | ControlValue::BitMask(_) | ControlValue::Float(_) @@ -348,9 +351,7 @@ impl ControlValue { | ControlValue::Boolean(_) | ControlValue::Binary(_) | ControlValue::Area { .. } - | ControlValue::Orientation(_) => true, - _ => false, - } + | ControlValue::Orientation(_)) } // pub fn primitive_same_type(&self, other: &ControlValuePrimitive) -> bool { @@ -369,7 +370,7 @@ impl ControlValue { // false // } - pub fn same_type(&self, other: &ControlValue) -> bool { + #[must_use] pub fn same_type(&self, other: &ControlValue) -> bool { match self { ControlValue::Null => { if let ControlValue::Null = other { @@ -426,7 +427,6 @@ impl ControlValue { return true; } } - _ => return false, } false diff --git a/nokhwa-core/src/decoder.rs b/nokhwa-core/src/decoder.rs index f59d6f7..1fbce55 100644 --- a/nokhwa-core/src/decoder.rs +++ b/nokhwa-core/src/decoder.rs @@ -1,59 +1,34 @@ -use std::borrow::Cow; -use std::fmt::Debug; use crate::error::NokhwaError; use crate::frame_buffer::FrameBuffer; -use crate::frame_format::FrameFormat; -use crate::stream::{StreamHandle}; -use crate::types::{CameraFormat, FrameRate, Resolution}; +use crate::types::CameraFormat; +use std::fmt::Debug; +pub use image::{ImageBuffer, Pixel, Primitive}; +use crate::image::{DecodedImage, NonFloatScalarWidth}; -#[derive(Debug)] -pub struct Decoder<'stream, Video> where - Video: Codec { - video: Video, - stream: &'stream mut StreamHandle +pub trait Decoder { + type Config: Clone + Debug + TryFrom; + type OutputMeta: Debug; + + fn config(&self) -> &Self::Config; + + fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError>; + + fn decode_to_buffer( + &mut self, + to_decode: FrameBuffer, + buffer: impl AsMut<[u8]>, + ) -> Result; + + fn decode_to_pixel_buffer( + &mut self, + to_decode: FrameBuffer, + buffer: impl AsMut<[P::Subpixel]>, + ) -> Result + where

::Subpixel: NonFloatScalarWidth; + + fn decode( + &mut self, + to_decode: FrameBuffer, + ) -> Result, NokhwaError> + where

::Subpixel: NonFloatScalarWidth; } - -impl<'stream, Video> Decoder<'stream, Video> where Video: Codec { - pub fn new(stream: &'stream mut StreamHandle, decoder: Video) -> Result { - let format = stream.format(); - - let mut decoder = decoder; - decoder.initialize(format)?; - Ok(Self { video: decoder, stream }) - } - - pub fn -} - -#[cfg(feature = "async")] -#[derive(Debug)] -pub struct DecoderAsync<'stream, Video> where - Video: CodecAsync { - video: Video, - stream_handle: &'stream mut StreamHandle -} - -pub trait Codec: Debug { - const ALLOWED_FORMATS: &'static [FrameFormat]; - - fn initialize(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError>; - - fn stop(&mut self) -> Result<(), NokhwaError>; - - fn frame_format(&self) -> Result; - - fn resolution(&self) -> Result; - - fn frame_rate(&self) -> Result; - - fn set_frame_format(&mut self, frame_format: FrameFormat) -> Result<(), NokhwaError>; - - fn set_resolution(&mut self, resolution: Resolution) -> Result<(), NokhwaError>; - - fn set_frame_rate(&mut self, frame_rate: FrameRate) -> Result<(), NokhwaError>; - - fn decode_frame(&mut self, buffer: &FrameBuffer) -> Result, NokhwaError>; -} - -#[cfg(feature = "async")] -pub trait CodecAsync: Codec + Debug {} diff --git a/nokhwa-core/src/error.rs b/nokhwa-core/src/error.rs index a58f5d1..f777f85 100644 --- a/nokhwa-core/src/error.rs +++ b/nokhwa-core/src/error.rs @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -use crate::{frame_format::FrameFormat}; -use std::fmt::{Debug}; -use thiserror::Error; +use crate::frame_format::FrameFormat; use crate::platform::Backends; +use std::fmt::Debug; +use thiserror::Error; pub type NokhwaResult = Result; @@ -62,6 +62,8 @@ pub enum NokhwaError { ConversionError(String), #[error("Permission denied by user.")] PermissionDenied, + #[error("Failed to decode: {0}")] + Decoder(String), } // // pub enum InitializeError {} diff --git a/nokhwa-core/src/format_request.rs b/nokhwa-core/src/format_request.rs index f24220e..0d09499 100644 --- a/nokhwa-core/src/format_request.rs +++ b/nokhwa-core/src/format_request.rs @@ -1,11 +1,10 @@ +use crate::ranges::ValidatableRange; use crate::utils::Distance; use crate::{ frame_format::FrameFormat, ranges::Range, types::{CameraFormat, FrameRate, Resolution}, }; -use crate::ranges::ValidatableRange; - /// A helper for choosing a [`CameraFormat`]. /// The use of this is completely optional - for a simpler way try [`crate::camera::Camera::enumerate_formats`]. @@ -16,7 +15,9 @@ pub enum FormatRequestType { /// Pick the closest [`CameraFormat`] to the one requested Closest { resolution: Option>, + preferred_resolution: Option, frame_rate: Option>, + preferred_frame_rate: Option, }, HighestFrameRate { frame_rate: Range, @@ -38,18 +39,21 @@ pub struct FormatRequest { } impl FormatRequest { - pub fn new(format_request_type: FormatRequestType, allowed_frame_formats: Vec) -> Self { + #[must_use] pub fn new( + format_request_type: FormatRequestType, + allowed_frame_formats: Vec, + ) -> Self { Self { request_type: format_request_type, allowed_frame_formats, } } - pub fn best<'a>(&self, camera_formats: &'a Vec) -> Option<&'a CameraFormat> { + #[must_use] pub fn best<'a>(&self, camera_formats: &'a [CameraFormat]) -> Option<&'a CameraFormat> { camera_formats.first() } - pub fn sort_foramts(&self, mut camera_formats: Vec) -> Vec { + #[must_use] pub fn sort_foramts(&self, mut camera_formats: Vec) -> Vec { if camera_formats.is_empty() { return camera_formats; } @@ -57,69 +61,61 @@ impl FormatRequest { match self.request_type { FormatRequestType::Closest { resolution, + preferred_resolution, frame_rate, - .. + preferred_frame_rate, } => { - let resolution_point = resolution.map(|x| x.preferred()); - let frame_rate_point = frame_rate.map(|x| x.preferred()); // lets calcuate distance in 3 dimensions (add both resolution and frame_rate together) - camera_formats.sort_by(|a, b| { - let a_distance = format_distance_to_point(&resolution_point, &frame_rate_point, a); - let b_distance = format_distance_to_point(&resolution_point, &frame_rate_point, b); + let a_distance = + format_distance_to_point(&preferred_resolution, &preferred_frame_rate, a); + let b_distance = + format_distance_to_point(&preferred_resolution, &preferred_frame_rate, b); a_distance.total_cmp(&b_distance) }); - camera_formats.into_iter().filter(|fmt| { - self.allowed_frame_formats.contains(fmt.format()) - }).filter(|cam_fmt| { - if let Some(res_range) = resolution { - return res_range.validate(cam_fmt.resolution()) - } + camera_formats + .into_iter() + .filter(|fmt| self.allowed_frame_formats.contains(fmt.format())) + .filter(|cam_fmt| { + if let Some(res_range) = resolution { + return res_range.validate(cam_fmt.resolution()); + } - if let Some(frame_rate_range) = frame_rate { - return frame_rate_range.validate(&cam_fmt.frame_rate()) - } - true - }).collect() + if let Some(frame_rate_range) = frame_rate { + return frame_rate_range.validate(cam_fmt.frame_rate()); + } + true + }) + .collect() } - FormatRequestType::HighestFrameRate { - frame_rate - } => { - camera_formats.sort_by(|a, b| { - a.frame_rate().cmp(b.frame_rate()) - }); + FormatRequestType::HighestFrameRate { frame_rate } => { + camera_formats.sort_by(|a, b| a.frame_rate().cmp(b.frame_rate())); - camera_formats.into_iter().filter(|fmt| { - self.allowed_frame_formats.contains(fmt.format()) - }).filter(|a| { - frame_rate.validate(a.frame_rate()) - }).collect() + camera_formats + .into_iter() + .filter(|fmt| self.allowed_frame_formats.contains(fmt.format())) + .filter(|a| frame_rate.validate(a.frame_rate())) + .collect() } - FormatRequestType::HighestResolution { - resolution - } => { - camera_formats.sort_by(|a, b| { - a.resolution().cmp(b.resolution()) - }); + FormatRequestType::HighestResolution { resolution } => { + camera_formats.sort_by(|a, b| a.resolution().cmp(b.resolution())); - camera_formats.into_iter().filter(|fmt| { - self.allowed_frame_formats.contains(fmt.format()) - }).filter(|a| { - resolution.validate(a.resolution()) - }).collect() + camera_formats + .into_iter() + .filter(|fmt| self.allowed_frame_formats.contains(fmt.format())) + .filter(|a| resolution.validate(a.resolution())) + .collect() } FormatRequestType::Exact { resolution, frame_rate, - } => { - camera_formats.into_iter().filter(|fmt| { - self.allowed_frame_formats.contains(fmt.format()) - }).filter(|a| { - resolution.eq(a.resolution()) && frame_rate.eq(a.frame_rate()) - }).collect() - } + } => camera_formats + .into_iter() + .filter(|fmt| self.allowed_frame_formats.contains(fmt.format())) + .filter(|a| resolution.eq(a.resolution()) && frame_rate.eq(a.frame_rate())) + .collect(), FormatRequestType::Any => { // return as-is camera_formats @@ -128,14 +124,23 @@ impl FormatRequest { } } -pub fn format_distance_to_point(resolution: &Option, frame_rate: &Option, format: &CameraFormat) -> f32 { +#[must_use] +#[allow(clippy::cast_precision_loss)] +pub fn format_distance_to_point( + resolution: &Option, + frame_rate: &Option, + format: &CameraFormat, +) -> f32 { let frame_rate_distance = match frame_rate { - Some(f_point) => (format.frame_rate() - f_point).approximate_float().unwrap_or(f32::INFINITY).abs(), + Some(f_point) => (format.frame_rate() - f_point) + .approximate_float() + .unwrap_or(f32::INFINITY) + .abs(), None => 0_f32, }; - + let resolution_point_distance = match resolution { - Some(res_pt) => format.resolution().distance_from(&res_pt) as f32, + Some(res_pt) => format.resolution().distance_from(res_pt) as f32, None => 0_f32, }; diff --git a/nokhwa-core/src/frame_buffer.rs b/nokhwa-core/src/frame_buffer.rs index 2d1b53d..0734cd6 100644 --- a/nokhwa-core/src/frame_buffer.rs +++ b/nokhwa-core/src/frame_buffer.rs @@ -13,53 +13,56 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +use crate::control::ControlValue; use std::borrow::Cow; use std::hash::{Hash, Hasher}; -use crate::frame_format::FrameFormat; -use small_map::{FxSmallMap, Iter}; -use crate::control::ControlValue; - +use std::ops::Deref; pub use compact_str::CompactString; +pub use smallmap::Map; pub type PlatformSpecificFlag = u32; #[derive(Clone, Debug, Default)] pub struct Metadata { - flags: FxSmallMap<8, CompactString, ControlValue>, + flags: Map, } impl Metadata { - pub fn new() -> Self { + #[must_use] pub fn new() -> Self { Self { - flags: Default::default(), + flags: Map::default(), } } - pub fn get(&self, key: CompactString) -> Option<&ControlValue> { - self.flags.get(&key) + #[must_use] pub fn get(&self, key: &str) -> Option<&ControlValue> { + self.flags.get(key) } pub fn insert(&mut self, key: CompactString, value: ControlValue) { self.flags.insert(key, value); } - - pub fn iter(&self) -> Iter<'_, 8, CompactString, ControlValue> { - self.flags.iter() - } } impl Hash for Metadata { fn hash(&self, state: &mut H) { - for (key, value) in self.flags { + for (key, value) in self.flags.iter() { state.write(key.as_bytes()); value.hash(state); } } } +impl Deref for Metadata { + type Target = Map; + + fn deref(&self) -> &Self::Target { + &self.flags + } +} + impl PartialEq for Metadata { fn eq(&self, other: &Self) -> bool { - for (this_key, this_value) in &self.flags { + for (this_key, this_value) in self.flags.iter() { if let Some(other_value) = other.flags.get(this_key) { if this_value != other_value { return false; @@ -87,10 +90,7 @@ impl FrameBuffer { #[must_use] #[inline] pub fn new(buffer: Cow<'static, [u8]>, metadata: Option) -> Self { - Self { - buffer, - metadata, - } + Self { buffer, metadata } } /// Get the data of this buffer. @@ -99,8 +99,8 @@ impl FrameBuffer { &self.buffer } - pub fn consume(self) -> (Cow<'static, [u8]>, Option) { - return (self.buffer, self.metadata) + #[must_use] pub fn consume(self) -> (Cow<'static, [u8]>, Option) { + (self.buffer, self.metadata) } #[must_use] diff --git a/nokhwa-core/src/frame_format.rs b/nokhwa-core/src/frame_format.rs index b1c36d5..03fe44e 100644 --- a/nokhwa-core/src/frame_format.rs +++ b/nokhwa-core/src/frame_format.rs @@ -15,71 +15,70 @@ */ use std::fmt::{Display, Formatter}; +use ordered_float::OrderedFloat; +// /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. +// /// Note that endianness is determined by the native machine (or the driver itself). +// #[derive(Clone, Debug, Hash, PartialOrd, PartialEq)] +// #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +// #[non_exhaustive] +// pub enum FrameFormat { +// // Compressed Formats +// +// +// // YCbCr Formats +// +// // 8 bit per pixel, 4:4:4 +// Ayuv444, +// +// // -> 4:2:2 +// Yuyv422, // AKA YUY2 +// Uyvy422, // UYUV +// Yvyu422, +// Yv12, +// +// // 4:2:0 +// Nv12, +// Nv21, +// I420, +// +// // 16:1:1 +// Yvu9, +// +// // Grayscale Formats +// Luma8, +// Luma16, +// +// // Depth +// Depth16, +// +// // RGB Formats +// Rgb332, +// Rgb888, +// RgbA8888, +// ARgb8888, +// RgbX1010102, +// RgbA1010102, +// ARgb1010102, +// +// +// Bgr888, +// BgrA8888, +// Bgr121212, +// BgrA1212121212, +// +// Bgr161616, +// Bgr16161616, +// +// +// // Bayer Formats +// Bayer8, +// Bayer16, +// +// // Custom +// Custom(CustomFrameFormat), +// } -/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[non_exhaustive] -pub enum FrameFormat { - // Compressed Formats - H265, - H264, - Avc1, - H263, - Av1, - Mpeg1, - Mpeg2, - Mpeg4, - MJpeg, - XVid, - VP8, - VP9, - - // YCbCr Formats - - // 8 bit per pixel, 4:4:4 - Ayuv444, - - // -> 4:2:2 - Yuyv422, // AKA YUY2 - Uyvy422, // UYUV - Yvyu422, - Yv12, - - // 4:2:0 - Nv12, - Nv21, - I420, - - // 16:1:1 - Yvu9, - - // Grayscale Formats - Luma8, - Luma16, - - // Depth - Depth16, - - // RGB Formats - Rgb332, - Rgb888, - - Bgr888, - BgrA8888, - - RgbA8888, - ARgb8888, - - // Bayer Formats - Bayer8, - Bayer16, - - // Custom - Custom([u8; 8]), -} - -macro_rules! define_frame_format_groups { +macro_rules! define_frame_format_with_groups { ( $( $group_name:ident => [ @@ -87,50 +86,275 @@ macro_rules! define_frame_format_groups { ] ),* $(,)? ) => { + /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. + /// Note that endianness is determined by the native machine (or the driver itself), unless otherwise + /// specified. + /// + /// Note that compatibility is driver dependant, while some drivers (such as Web) may not respect the setting + /// at all. + #[derive(Copy, Clone, Debug, Hash, PartialOrd, PartialEq)] + #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] + #[non_exhaustive] + #[allow(non_camel_case_types)] + pub enum FrameFormat { + $( + $($format,)* + )* + Custom(CustomFrameFormat), + } + impl FrameFormat { $( pub const $group_name: &'static [FrameFormat] = &[ $(FrameFormat::$format),* ]; )* + pub const ALL: &'static [FrameFormat] = &[ + $( + $(FrameFormat::$format,)* + )* + ]; } }; } -define_frame_format_groups! { - ALL => [ - H263, H264, H265, Av1, Avc1, Mpeg1, Mpeg2, Mpeg4, MJpeg, XVid, - VP8, VP9, Yuyv422, Uyvy422, Nv12, Nv21, Yv12, Luma8, Luma16, - Rgb332, RgbA8888 - ], +define_frame_format_with_groups! { COMPRESSED => [ - H263, H264, H265, Av1, Avc1, Mpeg1, Mpeg2, Mpeg4, MJpeg, XVid, - VP8, VP9 + H265, + HEVC, + H264, + AVC1, + H263, + AV1, + MPEG_1, + MPEG_2, + MPEG_4, + MJPEG, + XviD, + VP8, + VP9, ], - CHROMA => [ - Yuyv422, Uyvy422, Nv12, Nv21, Yv12 + + YCBCR_PACKED_444 => [ + Ayuv_32, ], + YCBCR_PLANAR_444 => [], + YCBCR_SEMI_PLANAR_444 => [ + NV24, + NV42, + ], + YCBCR_PACKED_422 => [ + Yuyv_4_2_2, + Uyvy_4_2_2, + Vyuy_4_2_2, + Yvyu_4_2_2, + Y210, + Y216, + ], + YCBCR_PLANAR_422 => [], + YCBCR_SEMI_PLANAR_422 => [ + NV16, + NV61, + ], + YCBCR_PACKED_420 => [], + YCBCR_PLANAR_420 => [ + Yuv_4_2_0, + Yvu_4_2_0, + ], + YCBCR_SEMI_PLANAR_420 => [ + NV12, + NV21, + P010, + P012, + + ], + YCBCR_PACKED_411 => [ + Y41Packed + ], + YCBCR_PLANAR_411 => [ + Y411Planar + ], + YCBCR_SEMI_PLANAR_411 => [ + NV11, + ], + LUMA => [ - Luma8, Luma16 + Luma_8, + Luma_10, + Luma_12, + Luma_14, + Luma_16, + Depth_16, ], - RGB => [ - Rgb332, RgbA8888 + + RAW_RGB => [ + Rgb_3_3_2, + Rgb_5_6_5, + Rgb_5_5_5, + Rgb_8_8_8, + Argb_8_8_8_8, + Rgba_8_8_8_8, ], - COLOR_FORMATS => [ - H265, H264, H263, Av1, Avc1, Mpeg1, Mpeg2, Mpeg4, MJpeg, XVid, - VP8, VP9, Yuyv422, Uyvy422, Nv12, Nv21, Yv12, Rgb332, RgbA8888 - ], - GRAYSCALE => [ - Luma8, Luma16 + + RAW_BGR => [ + Bgr_3_3_2, + Bgr_5_6_5, + Bgr_5_5_5, + Bgr_8_8_8, + Abgr_8_8_8_8, + Bgra_8_8_8_8, ] } +// define_frame_format_groups! { +// ALL => [ +// // Compressed Formats +// H265, +// H264, +// Avc1, +// H263, +// Av1, +// Mpeg1, +// Mpeg2, +// Mpeg4, +// MJpeg, +// XVid, +// VP8, +// VP9, +// +// // YCbCr Formats +// +// // 8 bit per pixel, 4:4:4 +// Ayuv444, +// +// // -> 4:2:2 +// Yuyv422, // AKA YUY2 +// Uyvy422, // UYUV +// Yvyu422, +// Yv12, +// +// // 4:2:0 +// Nv12, +// Nv21, +// I420, +// +// // 16:1:1 +// Yvu9, +// +// // Grayscale Formats +// Luma8, +// Luma16, +// +// // Depth +// Depth16, +// +// // RGB Formats +// Rgb332, +// Rgb888, +// +// Bgr888, +// BgrA8888, +// +// RgbA8888, +// ARgb8888, +// +// // Bayer Formats +// Bayer8, +// Bayer16, +// ], +// COMPRESSED => [ +// H265, +// H264, +// Avc1, +// H263, +// Av1, +// Mpeg1, +// Mpeg2, +// Mpeg4, +// MJpeg, +// XVid, +// VP8, +// VP9, +// ], +// YCBCR => [ +// Ayuv444, +// +// // -> 4:2:2 +// Yuyv422, // AKA YUY2 +// Uyvy422, // UYUV +// Yvyu422, +// Yv12, +// +// // 4:2:0 +// Nv12, +// Nv21, +// I420, +// +// // 16:1:1 +// Yvu9, +// ], +// YCBCR_PACKED => [ +// Ayuv444, +// +// // -> 4:2:2 +// Yuyv422, // AKA YUY2 +// Uyvy422, // UYUV +// Yvyu422, +// +// // 4:2:0 +// Nv12, +// Nv21, +// I420, +// +// ], +// YCBCR_PLANAR => [ Yvu9, +// Yv12, +// ], +// LUMA => [ +// Luma8, Luma16 +// ], +// RGB => [ +// Rgb332, RgbA8888 +// ], +// COLOR_FORMATS => [ +// H265, H264, H263, Av1, Avc1, Mpeg1, Mpeg2, Mpeg4, MJpeg, XVid, +// VP8, VP9, Yuyv422, Uyvy422, Nv12, Nv21, Yv12, Rgb332, RgbA8888 +// ], +// GRAYSCALE => [ +// Luma8, Luma16, Depth16, +// ], +// RAW => [ +// // RGB Formats +// Rgb332, +// Rgb888, +// +// Bgr888, +// BgrA8888, +// +// RgbA8888, +// ARgb8888,], +// BAYER => [ +// // Bayer Formats +// Bayer8, +// Bayer16,], +// } + impl Display for FrameFormat { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{self:?}") } } +#[derive(Copy, Clone, Debug, Hash, PartialOrd, PartialEq)] +pub enum CustomFrameFormat { + UUID(u128), + FourCC([char; 4]), + U32(u32), + U64(u64), + F32(OrderedFloat), + F64(OrderedFloat), +} + #[macro_export] macro_rules! define_back_and_fourth_frame_format { ($fourcc_type:ty, { $( $frame_format:expr => $value:literal, )* }, $func_u8_8_to_fcc:expr, $func_fcc_to_u8_8:expr, $value_to_fcc_type:expr) => { diff --git a/nokhwa-core/src/image.rs b/nokhwa-core/src/image.rs new file mode 100644 index 0000000..c737bdf --- /dev/null +++ b/nokhwa-core/src/image.rs @@ -0,0 +1,78 @@ +use image::{ImageBuffer, Pixel, Primitive}; +use std::fmt::Debug; +use std::ops::{Deref, DerefMut}; +use bytemuck::Pod; +use num_traits::{NumCast, PrimInt}; + +#[derive(Debug)] +pub struct DecodedImage +where + Px: Pixel, + ::Subpixel: NonFloatScalarWidth, + Meta: Debug, +{ + pub buffer: ImageBuffer>, + pub metadata: Meta, +} + +impl DecodedImage where + Px: Pixel, + ::Subpixel: NonFloatScalarWidth, + Meta: Debug { + pub fn new(buffer: ImageBuffer>, + metadata: Meta) -> Self { + Self { + buffer, + metadata, + } + } +} + +impl Deref for DecodedImage +where + Px: Pixel, + ::Subpixel: NonFloatScalarWidth, + Meta: Debug +{ + type Target = ImageBuffer>; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl DerefMut for DecodedImage +where + Px: Pixel, + ::Subpixel: NonFloatScalarWidth,Meta: Debug +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} + +// not for safe work ;p +// TODO: add more custom integer sizes, or break our dependence on image entirely and +// create our own imagebuffer +pub trait NonFloatScalarWidth: Debug + Primitive + PrimInt + NumCast + Pod { + const WIDTH: u32; +} + +macro_rules! impl_nfsw { + ( $( [ ( $( $things:ty ),+ ) : $size:literal ] ),* $(,)? ) => { + $( + $( + impl NonFloatScalarWidth for $things { + const WIDTH: u32 = $size; + } + )+ + )* + } +} + +impl_nfsw! { + [ (u8, i8) : 8 ], + [ (u16, i16) : 16 ], + [ (u32, i32) : 32 ], + [ (u64, i64) : 64 ], +} diff --git a/nokhwa-core/src/lib.rs b/nokhwa-core/src/lib.rs index d8ff647..415622d 100644 --- a/nokhwa-core/src/lib.rs +++ b/nokhwa-core/src/lib.rs @@ -1,9 +1,10 @@ #![deny(clippy::pedantic)] #![warn(clippy::all)] -#![cfg_attr(feature = "test-fail-warning", deny(warnings))] +#![allow(clippy::missing_errors_doc)] +#![cfg_attr(feature = "test-fail-warnings", deny(warnings))] #![cfg_attr(feature = "docs-features", feature(doc_cfg))] /* - * Copyright 2022 l1npengtul / The Nokhwa Contributors + * Copyright 2025 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. @@ -20,15 +21,17 @@ //! Core type definitions for `nokhwa` pub mod camera; +pub mod codec; +pub mod control; pub mod decoder; pub mod error; pub mod format_request; pub mod frame_buffer; pub mod frame_format; -pub mod control; +pub mod image; +pub mod platform; pub mod ranges; +pub mod stream; pub mod traits; pub mod types; pub mod utils; -pub mod stream; -pub mod platform; diff --git a/nokhwa-core/src/platform.rs b/nokhwa-core/src/platform.rs index ed2a66f..d379967 100644 --- a/nokhwa-core/src/platform.rs +++ b/nokhwa-core/src/platform.rs @@ -1,4 +1,4 @@ -use crate::camera::{AsyncCamera, Camera}; +use crate::camera::Camera; use crate::error::NokhwaResult; use crate::types::{CameraIndex, CameraInformation}; use std::fmt::{Display, Formatter}; @@ -15,7 +15,7 @@ pub enum Backends { impl Display for Backends { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } @@ -31,15 +31,15 @@ pub trait PlatformTrait { fn open(&mut self, index: CameraIndex) -> NokhwaResult; - fn open_dynamic(&mut self, index: CameraIndex) -> NokhwaResult> { - self.open(index).map(|cam| Box::new(cam)) + fn open_dynamic(&mut self, index: CameraIndex) -> NokhwaResult> where ::Camera: 'static { + self.open(index).map(|cam| Box::new(cam) as Box) } } #[cfg(feature = "async")] -pub trait AsyncPlatformTrait { +pub trait AsyncPlatformTrait: PlatformTrait { const PLATFORM: Backends; - type AsyncCamera: AsyncCamera; + type AsyncCamera: crate::camera::AsyncCamera; async fn await_permission(&mut self) -> NokhwaResult<()>; @@ -47,7 +47,7 @@ pub trait AsyncPlatformTrait { async fn open_async(&mut self, index: &CameraIndex) -> NokhwaResult; - async fn open_dynamic_async(&mut self, index: &CameraIndex) -> NokhwaResult> { - self.open_async(index).await.map(|cam| Box::new(cam)) + async fn open_dynamic_async(&mut self, index: &CameraIndex) -> NokhwaResult> where ::AsyncCamera: 'static { + self.open_async(index).await.map(|cam| Box::new(cam) as Box) } } diff --git a/nokhwa-core/src/ranges.rs b/nokhwa-core/src/ranges.rs index 3250a59..8c46700 100644 --- a/nokhwa-core/src/ranges.rs +++ b/nokhwa-core/src/ranges.rs @@ -1,7 +1,7 @@ use core::fmt::{Debug, Display, Formatter}; +use ordered_float::OrderedFloat; use std::hash::Hash; use std::ops::{Div, Rem, Sub}; -use ordered_float::OrderedFloat; /// A range type that can be validated. pub trait ValidatableRange { @@ -16,7 +16,9 @@ pub trait ValidatableRange { /// /// Inclusive by default. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct Range where T: RangeItem +pub struct Range +where + T: RangeItem, { minimum: T, lower_inclusive: bool, @@ -25,7 +27,10 @@ pub struct Range where T: RangeItem step: Option, } -impl Range where T: Copy { +impl Range +where + T: Copy + RangeItem, +{ /// Create an upper and lower inclusive [`Range`] pub fn new(min: T, max: T, step: Option) -> Self { Self { @@ -42,7 +47,7 @@ impl Range where T: Copy { lower_inclusive: bool, max: T, upper_inclusive: bool, - step: Option + step: Option, ) -> Self { Self { minimum: min, @@ -53,13 +58,13 @@ impl Range where T: Copy { } } - pub fn set_minimum(&mut self, minimum: Option) { + pub fn set_minimum(&mut self, minimum: T) { self.minimum = minimum; } pub fn set_lower_inclusive(&mut self, lower_inclusive: bool) { self.lower_inclusive = lower_inclusive; } - pub fn set_maximum(&mut self, maximum: Option) { + pub fn set_maximum(&mut self, maximum: T) { self.maximum = maximum; } pub fn set_upper_inclusive(&mut self, upper_inclusive: bool) { @@ -92,33 +97,27 @@ where type Validation = T; fn validate(&self, value: &T) -> bool { - let l_comparison_fn = match self.lower_inclusive { - true => T::ge, - false => T::gt, - }; - let u_comparison_fn = match self.upper_inclusive { - true => T::le, - false => T::lt, - }; + let l_comparison_fn = if self.lower_inclusive { T::ge } else { T::gt }; + let u_comparison_fn = if self.upper_inclusive { T::le } else { T::lt }; if !(l_comparison_fn(&self.minimum, value) && u_comparison_fn(&self.maximum, value)) { - return false + return false; } // check step if let Some(step) = self.step { let step_chk_value = *value - self.minimum; - return step_chk_value % step == 0; + return step_chk_value % step == T::ZERO; } - return true + true } } impl Default for Range where - T: Default, + T: Default + RangeItem, { fn default() -> Self { Range { @@ -133,7 +132,7 @@ where impl Display for Range where - T: Debug, + T: Debug + RangeItem, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let lower_inclusive_char = bool_to_inclusive_char(self.lower_inclusive, false); @@ -148,44 +147,46 @@ where } fn bool_to_inclusive_char(inclusive: bool, upper: bool) -> char { - match inclusive { - true => { - if upper { - ']' - } else { - '[' - } - } - false => { - if upper { - ')' - } else { - '(' - } + if inclusive { + if upper { + ']' + } else { + '[' } + } else if upper { + ')' + } else { + '(' + } } -fn default_to_string(default: &Option) -> String -where - T: Debug, +pub trait RangeItem: + Copy + + Clone + + Debug + + Div + + Sub + + Rem + + Hash + + Ord + + PartialOrd + + Eq + + PartialEq { - match default { - Some(v) => { - format!("{v:?}") - } - None => String::from("None"), - } -} - -pub trait RangeItem: Copy + Clone + Debug + Div + Sub + Rem + Hash + Ord + PartialOrd + Eq + PartialEq { const ZERO: Self; + const MIN: Self; + const MAX: Self; } macro_rules! impl_num { ($($n:ty)*) => ($( impl RangeItem for $n { const ZERO: $n = 0; + + const MIN: $n = <$n>::MIN; + + const MAX: $n = <$n>::MAX; } )*) } @@ -194,8 +195,13 @@ impl_num! { i8 u8 i16 u16 i32 u32 i64 u64 i128 u128 } impl RangeItem for OrderedFloat { const ZERO: Self = OrderedFloat(0_f32); + const MIN: Self = OrderedFloat(f32::MIN); + const MAX: Self = OrderedFloat(f32::MAX); } impl RangeItem for OrderedFloat { const ZERO: Self = OrderedFloat(0_f64); -} \ No newline at end of file + + const MIN: Self = OrderedFloat(f64::MIN); + const MAX: Self = OrderedFloat(f64::MAX); +} diff --git a/nokhwa-core/src/stream.rs b/nokhwa-core/src/stream.rs index 303f9dc..ddf6d53 100644 --- a/nokhwa-core/src/stream.rs +++ b/nokhwa-core/src/stream.rs @@ -1,11 +1,11 @@ -use std::cell::Cell; -use std::sync::Arc; -use std::time::Duration; -use flume::{Receiver, Sender, TryRecvError}; -use typed_builder::TypedBuilder; use crate::error::NokhwaError; use crate::frame_buffer::FrameBuffer; use crate::types::CameraFormat; +use flume::{Receiver, Sender, TryRecvError}; +use std::cell::Cell; +use std::sync::Arc; +use std::time::Duration; +use typed_builder::TypedBuilder; /// What receiving behaviour the stream should observe. /// @@ -79,23 +79,23 @@ pub enum Event { /// The stream is closed. Closed, /// An error from the driver - Error(Box), + Error(String), /// Some other message sent by the driver. This can be ignored, although logging this is preferable. - Other(String) + Other(String), } /// Represents a handle to a currently open stream. -/// +/// /// Streams are only valid as long as the camera is live. Any Stream that is living past a camera -/// is invalid to use. (This doesn't cause UB, it will just kindly tell you that the stream has +/// is invalid to use. (This doesn't cause UB, it will just kindly tell you that the stream has /// already closed.) -/// -/// Streams may unexpectedly close due to unforeseen consequences e.g. webcam undergoes spontaneous +/// +/// Streams may unexpectedly close due to unforeseen consequences e.g. webcam undergoes spontaneous /// deconstruction. -/// +/// /// The async methods [`StreamHandle::poll_event`] and [`StreamHandle::poll_frame`] **do not** respect the [`StreamReceiverBehaviour`] setting. -/// -/// You may also close the stream from the handle side using +/// +/// You may also close the stream from the handle side using #[derive(Debug)] pub struct StreamHandle { frame: Receiver, @@ -106,7 +106,12 @@ pub struct StreamHandle { impl StreamHandle { /// You shouldn't be here. - pub fn new(recv: Receiver, control: Arc>, configuration: StreamConfiguration, format: CameraFormat) -> Self { + #[must_use] pub fn new( + recv: Receiver, + control: Arc>, + configuration: StreamConfiguration, + format: CameraFormat, + ) -> Self { Self { frame: recv, control, @@ -114,38 +119,35 @@ impl StreamHandle { format: Cell::new(format), } } - + pub fn configuration(&self) -> &StreamConfiguration { &self.configuration } - + pub fn format(&self) -> CameraFormat { self.format.get() } - + pub fn next_event(&self) -> Result { let event = match self.configuration.receiver { StreamReceiverBehaviour::Blocking => { - self.frame.recv().map_or_else(|_| { Event::Closed }, |e| { e }) - } - StreamReceiverBehaviour::Timeout(time) => { - self.frame.recv_timeout(time).map_or_else(|_| { Event::NotReady }, |e| { e }) - } - StreamReceiverBehaviour::Try => { - self.frame.try_recv().map_or_else(|why| { - match why { - TryRecvError::Empty => Event::NotReady, - TryRecvError::Disconnected => Event::Closed, - } - }, |e| { e }) + self.frame.recv().unwrap_or_else(|_| Event::Closed) } + StreamReceiverBehaviour::Timeout(time) => self + .frame + .recv_timeout(time).unwrap_or_else(|_| Event::NotReady), + StreamReceiverBehaviour::Try => self.frame.try_recv().unwrap_or_else( + |why| match why { + TryRecvError::Empty => Event::NotReady, + TryRecvError::Disconnected => Event::Closed, + }), }; - + if let Event::FormatChange(fmt) = event { self.format.set(fmt); } - - return Ok(event) + + Ok(event) } pub fn next_frame(&self) -> Result { @@ -153,30 +155,30 @@ impl StreamHandle { let event = self.next_event()?; match event { Event::NewFrame(f) => return Ok(f), - Event::FormatChange(_) | Event::NotReady => continue, Event::Terminating | Event::Closed => { let _ = self.control.try_send(()); - return Err(NokhwaError::ReadFrameError("Stream Closed.".to_string())) - } - Event::Other(why) => { - match self.configuration.on_other { - ControlFlowOnOther::Continue => continue, - ControlFlowOnOther::Break => return Err(NokhwaError::ReadFrameError(why)) - } + return Err(NokhwaError::ReadFrameError("Stream Closed.".to_string())); } + Event::Other(why) => if self.configuration.on_other == ControlFlowOnOther::Break { return Err(NokhwaError::ReadFrameError(why)) }, + Event::Error(e) => return Err(NokhwaError::ReadFrameError(e.to_string())), + _ => {} } } } #[cfg(feature = "async")] pub async fn poll_event(&self) -> Result { - Ok(self.frame.recv_async().await.map_or_else(|_| { Event::Closed }, |e| { if let Event::FormatChange(fmt) = e { - self.format.set(fmt); - } - e - })) + Ok(self.frame.recv_async().await.map_or_else( + |_| Event::Closed, + |e| { + if let Event::FormatChange(fmt) = e { + self.format.set(fmt); + } + e + }, + )) } - + // TODO: a smarter implementation? maybe? #[cfg(feature = "async")] pub async fn poll_next_frame(&self) -> Result { @@ -184,17 +186,15 @@ impl StreamHandle { let event = self.poll_event().await?; match event { Event::NewFrame(f) => return Ok(f), - Event::FormatChange(_) | Event::NotReady => continue, Event::Terminating | Event::Closed => { let _ = self.control.try_send(()); - return Err(NokhwaError::ReadFrameError("Stream Closed.".to_string())) - } - Event::Other(why) => { - match self.configuration.on_other { - ControlFlowOnOther::Continue => continue, - ControlFlowOnOther::Break => return Err(NokhwaError::ReadFrameError(why)) - } + return Err(NokhwaError::ReadFrameError("Stream Closed.".to_string())); } + Event::Other(why) => match self.configuration.on_other { + ControlFlowOnOther::Continue => continue, + ControlFlowOnOther::Break => return Err(NokhwaError::ReadFrameError(why)), + }, + _ => {} } } } @@ -205,4 +205,3 @@ impl Drop for StreamHandle { let _ = self.control.try_send(()); } } - diff --git a/nokhwa-core/src/types.rs b/nokhwa-core/src/types.rs index 21db24d..0d8b698 100644 --- a/nokhwa-core/src/types.rs +++ b/nokhwa-core/src/types.rs @@ -1,23 +1,23 @@ +use crate::ranges::RangeItem; use crate::utils::Distance; use crate::{error::NokhwaError, frame_format::FrameFormat}; +use num_rational::Rational32; +use num_traits::FromPrimitive; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; +use std::num::NonZeroI32; +use std::ops::{Div, Rem}; use std::{ borrow::Borrow, cmp::Ordering, fmt::{Debug, Display, Formatter}, - hash::{Hash}, - ops::{Sub}, + hash::Hash, + ops::Sub, }; -use std::num::NonZeroI32; -use std::ops::{Div, Rem}; -use num_rational::{Ratio, Rational32}; -use crate::ranges::{RangeItem}; -use num_traits::FromPrimitive; /// Describes the index of the camera. /// - Index: A numbered index -/// - String: A string, used for `IPCameras` or on the Browser as DeviceIDs. +/// - String: A string, used for `IPCameras` or on the Browser as `DeviceIDs`. #[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] pub enum CameraIndex { @@ -214,6 +214,8 @@ impl Rem for Resolution { impl RangeItem for Resolution { const ZERO: Self = Resolution::new(0, 0); + const MIN: Self = Resolution::new(u32::MIN, u32::MIN); + const MAX: Self = Resolution::new(u32::MAX, u32::MAX); } /// Framerate of a camera, backed by a num-rational Ratio type. @@ -230,34 +232,34 @@ pub struct FrameRate { } impl FrameRate { - pub const fn new(numerator: i32, denominator: NonZeroI32) -> Self { + #[must_use] pub const fn new(numerator: i32, denominator: NonZeroI32) -> Self { Self { rational: Rational32::new_raw(numerator, denominator.get()), } } - pub const fn frame_rate(fps: i32) -> Self { + #[must_use] pub const fn from_fps(fps: i32) -> Self { Self { rational: Rational32::new_raw(fps, 1), } } - pub fn numerator(&self) -> &i32 { - self.rational.numer() + #[must_use] pub fn numerator(&self) -> i32 { + *self.rational.numer() } - pub fn denominator(&self) -> &i32 { - self.rational.denom() + #[must_use] pub fn denominator(&self) -> i32 { + *self.rational.denom() } - pub fn as_raw(&self) -> &Rational32 { + #[must_use] pub fn as_raw(&self) -> &Rational32 { &self.rational } - pub fn approximate_float(&self) -> Option { - let numerator_float = f32::from_i32(*self.numerator())?; - let denominator_float = f32::from_i32(*self.denominator())?; - + #[must_use] pub fn approximate_float(&self) -> Option { + let numerator_float = f32::from_i32(self.numerator())?; + let denominator_float = f32::from_i32(self.denominator())?; + Some(numerator_float / denominator_float) } } @@ -307,20 +309,20 @@ impl Rem for FrameRate { } impl RangeItem for FrameRate { - const ZERO: Self = FrameRate::frame_rate(0); + const ZERO: Self = FrameRate::from_fps(0); + const MIN: Self = FrameRate::from_fps(0); + const MAX: Self = FrameRate::from_fps(i32::MAX); } impl From for FrameRate { fn from(value: Rational32) -> Self { - FrameRate { - rational: value, - } + FrameRate { rational: value } } } /// This is a convenience struct that holds all information about the format of a webcam stream. /// It consists of a [`Resolution`], [`FrameFormat`], and a [`FrameRate`]. -#[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd, Eq, Ord)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] pub struct CameraFormat { resolution: Resolution, @@ -402,7 +404,7 @@ impl Default for CameraFormat { fn default() -> Self { CameraFormat { resolution: Resolution::new(640, 480), - format: FrameFormat::MJpeg, + format: FrameFormat::MJPEG, frame_rate: FrameRate::default(), } } @@ -432,8 +434,6 @@ pub struct CameraInformation { impl CameraInformation { /// Create a new [`CameraInformation`]. - /// # JS-WASM - /// This is exported as a constructor for [`CameraInformation`]. #[must_use] // 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! @@ -448,8 +448,6 @@ impl CameraInformation { } /// Get a reference to the device info's human readable name. - /// # JS-WASM - /// This is exported as a `get_HumanReadableName`. #[must_use] // yes, i know, unnecessary alloc this, unnecessary alloc that // but wasm bindgen diff --git a/nokhwa-decoders/Cargo.toml b/nokhwa-decoders/Cargo.toml new file mode 100644 index 0000000..f1551a1 --- /dev/null +++ b/nokhwa-decoders/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "nokhwa-decoders" +version = "0.1.0" +edition = "2024" + +[features] +ffmpeg = ["ffmpeg-the-third"] +av1 = ["rav1e"] +yuyv = ["dcv-color-primitives", "yuv"] +mjpeg = ["zune-jpeg"] +wasm = [] +mega = ["ffmpeg", "av1", "yuyv", "mjpeg"] +static = ["ffmpeg-the-third/static"] +async = [] + +[dependencies] +bytemuck = "1.23" + +[dependencies.nokhwa-core] +version = "0.2" +path = "../nokhwa-core" + +[dependencies.ffmpeg-the-third] +version = "3.0" +optional = true + +[dependencies.rav1e] +version = "0.8" +optional = true + +[dependencies.yuv] +version = "0.8" +optional = true + +[dependencies.dcv-color-primitives] +version = "0.7" +optional = true + +[dependencies.zune-jpeg] +version = "0.5.0-rc2" +optional = true diff --git a/nokhwa-decoders/src/ffmpeg.rs b/nokhwa-decoders/src/ffmpeg.rs new file mode 100644 index 0000000..de46dc5 --- /dev/null +++ b/nokhwa-decoders/src/ffmpeg.rs @@ -0,0 +1,670 @@ +use core::mem::transmute; +use bytemuck::try_cast_slice_mut; +use ffmpeg_the_third::codec::{Context, Id, Parameters}; +use ffmpeg_the_third::decoder::Video; +use ffmpeg_the_third::ffi::{av_frame_alloc, av_frame_move_ref, av_image_copy_to_buffer, av_image_fill_arrays, av_image_get_buffer_size, avcodec_free_context, avcodec_parameters_alloc, avcodec_parameters_free, sws_freeContext, sws_getContext, sws_scale_frame, AVChromaLocation, AVCodecID, AVCodecParameters, AVColorPrimaries, AVColorRange, AVColorSpace, AVColorTransferCharacteristic, AVFieldOrder, AVMediaType, AVPacket, AVPacketSideData, AVPixelFormat, AVRational, SwsContext}; +use ffmpeg_the_third::{decoder, packet::Packet, Frame}; +use ffmpeg_the_third::packet::{Borrow, Ref}; +use nokhwa_core::codec::Codec; +use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel, Primitive}; +use nokhwa_core::error::NokhwaError; +use nokhwa_core::frame_buffer::FrameBuffer; +use nokhwa_core::frame_format::{CustomFrameFormat, FrameFormat}; +use nokhwa_core::image::{DecodedImage, NonFloatScalarWidth}; +use nokhwa_core::types::{CameraFormat, FrameRate, Resolution}; + +pub struct FfmpegDecoder { + codec: FfmpegCodec, + sws: Option, +} + +impl FfmpegDecoder { + pub fn new(config: ::Config) -> Result { + let codec = FfmpegCodec::new(config)?; + Ok( + Self { + codec, + sws: None, + } + ) + } + + pub fn with_format(format: &CameraFormat) -> Result { + let config = FfmpegDecoderConfig::with_camera_format(format); + let codec = FfmpegCodec::new(config)?; + Ok( + Self { + codec, + sws: None, + } + ) + } + + pub fn receive_decoded_frame(&mut self, to_decode: FrameBuffer) -> Result<(Frame, ::OutputMeta) , NokhwaError> { + let new_pkt = Packet::borrow(to_decode.buffer()); + self.codec.send_item(new_pkt.into())?; + let mut new_frame = unsafe { Frame::empty() }; + let metadata = self.codec.receive_decoded_item(&mut new_frame).map_err(|why| NokhwaError::Decoder(why.to_string()))?; + Ok((new_frame, metadata)) + } + +} + +impl Decoder for FfmpegDecoder { + type Config = ::Config; + type OutputMeta = ::WrittenMeta; + + fn config(&self) -> &Self::Config { + self.codec.config() + } + + fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError> { + self.codec.set_config(config)?; + Ok(()) + } + + fn decode_to_buffer(&mut self, to_decode: FrameBuffer, mut buffer: impl AsMut<[u8]>) -> Result { + // TODO: add an extra zippy happy path for rgb/bgr/luma + let (frame, metadata) = self.receive_decoded_frame(to_decode)?; + let av_frame_data = unsafe { frame.as_ref().ok_or(NokhwaError::Decoder("No Frame Data from Decoder".to_string()))? }; + let buffer = buffer.as_mut(); + let result = unsafe { + av_image_copy_to_buffer(buffer.as_mut().as_mut_ptr(), buffer.as_mut().len() as i32, av_frame_data.data.as_ptr() as *const *const u8, av_frame_data.linesize.as_ptr(), metadata.pixel_format, self.config().resolution.width() as i32, self.config().resolution.height() as i32, 1) + }; + if result.is_negative() { + return Err(NokhwaError::Decoder(format!("Error Code {}", result))) + } + Ok(metadata) + } + + fn decode_to_pixel_buffer(&mut self, to_decode: FrameBuffer, mut buffer: impl AsMut<[P::Subpixel]>) -> Result where

::Subpixel: NonFloatScalarWidth { + let destination_format = pixel_to_destination_px_fmt::

(::WIDTH).ok_or(NokhwaError::Decoder("Unsupported Pixel Type".to_string()))?; + + let buffer = buffer.as_mut(); + let estimated_size = self.codec.preferred_buffer_min_size(&None)?.ok_or(NokhwaError::Decoder("failed to estimate decoder buffer.length".to_string()))?; + if buffer.len() < estimated_size { + return Err(NokhwaError::Decoder("buffer too small!".to_string())) + } + + + let (mut frame, decoded_meta) = self.receive_decoded_frame(to_decode)?; + let source_format = decoded_meta.pixel_format; + + let cast_slice = try_cast_slice_mut::(buffer).map_err(|why| NokhwaError::Decoder(why.to_string()))?; + let receiving_buffer = unsafe { + let av_frame = av_frame_alloc(); + let result = av_image_fill_arrays((*av_frame).data.as_mut_ptr(), (*av_frame).linesize.as_mut_ptr(), cast_slice.as_ptr(), destination_format, self.codec.config.resolution.width() as i32, self.codec.config.resolution.height() as i32, 1); + if result < 0 { + return Err(NokhwaError::Decoder("Failed to fill avimage".to_string())); + } + av_frame + }; + + if source_format != destination_format { + let sws_scaler = match &mut self.sws { + None => { + let context = create_sws_context(source_format, destination_format, self.codec.config.resolution)?; + self.sws = Some(Sws { + sws: context, + source_pixel_format: source_format, + dest_pixel_format: destination_format, + }); + self.sws.as_mut().unwrap() + } + Some(v) => { + if v.source_pixel_format != source_format || v.dest_pixel_format != destination_format { + v.source_pixel_format = source_format; + v.dest_pixel_format = destination_format; + v.sws = create_sws_context(source_format, destination_format, self.codec.config.resolution)?; + } + v + } + }; + + let scaled = unsafe {sws_scale_frame(sws_scaler.sws, receiving_buffer, frame.as_mut_ptr())}; + if scaled < 0 { + Err(NokhwaError::Decoder("Failed to scale sws".to_string())) + } else { + Ok(decoded_meta) + } + + } else { + unsafe { + av_frame_move_ref(receiving_buffer, frame.as_mut_ptr()) + }; + Ok(decoded_meta) + } + + } + + fn decode(&mut self, to_decode: FrameBuffer) -> Result, NokhwaError> where

::Subpixel: NonFloatScalarWidth { + let min_size = P::CHANNEL_COUNT as usize * (::WIDTH as usize / 8_usize) * self.codec.config.resolution.height() as usize * self.codec.config.resolution.width() as usize; + let mut buffer: Vec = vec![::DEFAULT_MIN_VALUE; min_size]; + let meta = self.decode_to_buffer(to_decode, try_cast_slice_mut(&mut buffer).map_err(|why| NokhwaError::Decoder(why.to_string()))?)?; + Ok(DecodedImage::new(ImageBuffer::from_vec(self.codec.config.resolution.width(), self.codec.config.resolution.height(), buffer).ok_or(NokhwaError::Decoder("Failed to create Image Buffer".to_string()))?, meta)) + } + } + +fn create_sws_context(src_format: AVPixelFormat, dest: AVPixelFormat, resolution: Resolution) -> Result<*mut SwsContext, NokhwaError> { + let param = 0_f64; + let new_sws = unsafe { + sws_getContext(resolution.width() as i32, resolution.height() as i32, src_format, resolution.width() as i32, resolution.height() as i32, dest, 0, core::ptr::null_mut(), core::ptr::null_mut(), ¶m) + }; + Ok(new_sws) +} + +fn pixel_to_destination_px_fmt(width: u32) -> Option where

::Subpixel: NonFloatScalarWidth { + match P::COLOR_MODEL { + "RGB" => match width { + 8 => Some(AVPixelFormat::AV_PIX_FMT_RGB24), + _ => None, + } + + "RGBA" => match width { + 8 => Some(AVPixelFormat::AV_PIX_FMT_RGBA), + 16 => Some(switch_endian(AVPixelFormat::AV_PIX_FMT_RGBA64LE, AVPixelFormat::AV_PIX_FMT_RGBA64BE)), + _ => None, + } + "BGR" => match width { + 8 => Some(AVPixelFormat::AV_PIX_FMT_BGR24), + _ => None, + } + + "BGRA" => match width { + 8 => Some(AVPixelFormat::AV_PIX_FMT_BGRA), + 16 => Some(switch_endian(AVPixelFormat::AV_PIX_FMT_BGRA64LE, AVPixelFormat::AV_PIX_FMT_BGRA64BE)), + _ => None, + } + "Y" => match width { + 8 => Some(AVPixelFormat::AV_PIX_FMT_GRAY8), + 16 => Some(switch_endian(AVPixelFormat::AV_PIX_FMT_GRAY16LE, AVPixelFormat::AV_PIX_FMT_GRAY16BE)), + _ => None, + } + "YA" => match width { + 8 => Some(AVPixelFormat::AV_PIX_FMT_GRAY8A), + _ => None, + } + _ => None, + } +} + +pub struct Sws { + pub sws: *mut SwsContext, + pub source_pixel_format: AVPixelFormat, + pub dest_pixel_format: AVPixelFormat, + // pub filter_a: SwsFilter, + // pub filter_b: SwsFilter, +} + +impl Drop for Sws { + fn drop(&mut self) { + unsafe { + sws_freeContext(self.sws); + // sws_freeFilter(&mut self.filter_a); + // sws_freeFilter(&mut self.filter_b); + } + } +} + + +#[derive(Clone, Debug)] +pub struct FfmpegFrameMetadata { + pub color_range: AVColorRange, + pub pixel_format: AVPixelFormat, + pub resolution: Resolution, +} + +pub enum PacketOrRef<'a> { + Packet(Packet), + Ref(Borrow<'a>) +} + +impl Ref for PacketOrRef<'_> { + fn as_ptr(&self) -> *const AVPacket { + match self { + PacketOrRef::Packet(p) => p.as_ptr(), + PacketOrRef::Ref(r) => r.as_ptr(), + } + } +} + +impl<'a> From> for PacketOrRef<'a> { + fn from(value: Borrow<'a>) -> Self { + Self::Ref(value) + } +} + +impl From for PacketOrRef<'_> { + fn from(value: Packet) -> Self { + Self::Packet(value) + } +} + +pub struct FfmpegCodec { + decoder: Video, + config: FfmpegDecoderConfig, + temp_cfg: Option<*mut AVCodecParameters>, + deinitialized: bool, +} + +impl FfmpegCodec { + fn new(config: ::Config) -> Result { + let id = convert_format_to_codec_id(&config.frame_format).ok_or(NokhwaError::Decoder("Could not find Format".to_string()))?; + + let codec = decoder::find(id).ok_or(NokhwaError::Decoder("Failed to find codec".to_string()))?; + + let context = unsafe { + let ptr = ffmpeg_the_third::ffi::avcodec_alloc_context3(codec.as_ptr()); + if ptr.is_null() { + return Err(NokhwaError::Decoder("ffmpeg returned a null context".to_string())) + } + Context::wrap(ptr, None) + }; + + let mut video = context.decoder().video().map_err(|why| NokhwaError::Decoder(why.to_string()))?; + video.set_parameters(unsafe { Parameters::from_raw(config.as_ptr()?).ok_or(NokhwaError::Decoder("Failed to convert parameters".to_string()))? }).map_err(|why| NokhwaError::Decoder(why.to_string()))?; + + Ok(Self { + decoder: video, + config, + temp_cfg: None, + deinitialized: false, + }) + } +} + +impl Codec for FfmpegCodec { + + type Config = FfmpegDecoderConfig; + type Input<'a> = PacketOrRef<'a>; + type Output = Frame; + type WrittenMeta = FfmpegFrameMetadata; + + fn allowed_formats(&self) -> Result<&[FrameFormat], NokhwaError> { + if self.deinitialized { + return Err(NokhwaError::Decoder("This decoder is deinitialized - it is no longer usable.".to_string())) + } + + Ok(FrameFormat::ALL) + } + + + fn config(&self) -> &Self::Config { + &self.config + } + + fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError> { + if self.deinitialized { + return Err(NokhwaError::Decoder("This decoder is deinitialized - it is no longer usable.".to_string())) + } + dealloc_av_params(&mut self.temp_cfg); + let mut temp_config = config.as_avcodec_params()?; + self.decoder.set_parameters(unsafe { Parameters::from_raw(&mut temp_config).ok_or(NokhwaError::Decoder("Failed to convert parameters".to_string()))? }).map_err(|why| NokhwaError::Decoder(why.to_string()))?; + self.config = config; + self.temp_cfg = Some(&mut temp_config); + Ok(()) + } + + fn send_item(&mut self, input: Self::Input<'_>) -> Result<(), NokhwaError> { + if self.deinitialized { + return Err(NokhwaError::Decoder("This decoder is deinitialized - it is no longer usable.".to_string())) + } + self.decoder.send_packet(&input).map_err(|why| NokhwaError::Decoder(why.to_string())) + } + + fn receive_decoded_item(&mut self, writing_output: &mut Self::Output) -> Result { + if self.deinitialized { + return Err(NokhwaError::Decoder("This decoder is deinitialized - it is no longer usable.".to_string())) + } + self.decoder.receive_frame(writing_output).map_err(|why| NokhwaError::Decoder(why.to_string()))?; + let avframe = match unsafe { writing_output.as_ref() } { + Some(r) => r, + None => return Err(NokhwaError::Decoder("failed to get refrenece to decoded frame".to_string())) + }; + let meta = FfmpegFrameMetadata { + color_range: avframe.color_range, + pixel_format: unsafe { transmute::(avframe.format) }, + resolution: Resolution::new(avframe.height as u32, avframe.width as u32), + }; + Ok(meta) + } + + fn preferred_buffer_min_size(&mut self, camera_format: &Option) -> Result, NokhwaError> { + let (width, height, pixel_format) = match camera_format { + Some(fmt) => {( + fmt.width(), + fmt.height(), + convert_frame_format_to_pixfmt(fmt.format())) + } + None => { +( self.config().resolution.width(), self.config.resolution.height(), convert_frame_format_to_pixfmt(&self.config().frame_format) +) } + }; + + let size = unsafe { av_image_get_buffer_size(pixel_format, width as i32, height as i32, 1) }; + Ok(Some(size as usize)) + } + + fn deinitialize(&mut self) -> Result<(), NokhwaError> { + dealloc_av_params(&mut self.temp_cfg); + if self.deinitialized { + return Ok(()) + } + + self.deinitialized = true; + Ok(()) + } +} + +fn convert_format_to_codec_id(frame_format: &FrameFormat) -> Option { + match frame_format { + FrameFormat::H265 => Some(Id::H265), + FrameFormat::H264 => Some(Id::H264), + FrameFormat::AVC1 => Some(Id::H264), + FrameFormat::H263 => Some(Id::H263), + FrameFormat::AV1 => Some(Id::AV1), + FrameFormat::MPEG_1 => Some(Id::MPEG1VIDEO), + FrameFormat::MPEG_2 => Some(Id::MPEG2VIDEO), + FrameFormat::MPEG_4 => Some(Id::MPEG4), + FrameFormat::MJPEG => Some(Id::MJPEG), + FrameFormat::XviD => Some(Id::MPEG4), + FrameFormat::VP8 => Some(Id::VP8), + FrameFormat::VP9 => Some(Id::VP9), + FrameFormat::Ayuv_32 => Some(Id::RAWVIDEO), + FrameFormat::Yuyv_4_2_2 => Some(Id::RAWVIDEO), + FrameFormat::Yvyu_4_2_2 => Some(Id::RAWVIDEO), + FrameFormat::Uyvy_4_2_2 => Some(Id::RAWVIDEO), + FrameFormat::NV12 => Some(Id::RAWVIDEO), + FrameFormat::NV21 => Some(Id::RAWVIDEO), + FrameFormat::Luma_8 => Some(Id::RAWVIDEO), + FrameFormat::Luma_10 => Some(Id::RAWVIDEO), + FrameFormat::Luma_12 => Some(Id::RAWVIDEO), + FrameFormat::Luma_14 => Some(Id::RAWVIDEO), + FrameFormat::Luma_16 => Some(Id::RAWVIDEO), + FrameFormat::Rgb_3_3_2 => Some(Id::RAWVIDEO), + FrameFormat::Rgb_5_5_5 => Some(Id::RAWVIDEO), + FrameFormat::Rgb_5_6_5 => Some(Id::RAWVIDEO), + FrameFormat::Rgb_8_8_8 => Some(Id::RAWVIDEO), + FrameFormat::Argb_8_8_8_8 => Some(Id::RAWVIDEO), + FrameFormat::Rgba_8_8_8_8 => Some(Id::RAWVIDEO), + FrameFormat::Bgr_3_3_2 => Some(Id::RAWVIDEO), + FrameFormat::Bgr_5_5_5 => Some(Id::RAWVIDEO), + FrameFormat::Bgr_5_6_5 => Some(Id::RAWVIDEO), + FrameFormat::Bgr_8_8_8 => Some(Id::RAWVIDEO), + FrameFormat::Abgr_8_8_8_8 => Some(Id::RAWVIDEO), + FrameFormat::Bgra_8_8_8_8 => Some(Id::RAWVIDEO), + FrameFormat::Custom(c) => { + if let CustomFrameFormat::U32(c_id) = c { + // SAFETY: /shrug + let av_codec_id: AVCodecID = unsafe { + transmute(*c_id) + }; + Some(av_codec_id.into()) + } else { + None + } + } + _ => None, + } +} + +fn convert_frame_format_to_pixfmt(frame_format: &FrameFormat) -> AVPixelFormat { + match frame_format { + // does FFMPEG not support 32bpp 4:4:4 packed AYUV? + FrameFormat::NV24 => AVPixelFormat::AV_PIX_FMT_NV24, + FrameFormat::NV42 => AVPixelFormat::AV_PIX_FMT_NV42, + FrameFormat::Yuyv_4_2_2 => AVPixelFormat::AV_PIX_FMT_YUYV422, + FrameFormat::Uyvy_4_2_2 => AVPixelFormat::AV_PIX_FMT_UYVY422, + FrameFormat::Yvyu_4_2_2 => AVPixelFormat::AV_PIX_FMT_YVYU422, + FrameFormat::Y210 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_Y210LE } else { + AVPixelFormat::AV_PIX_FMT_Y210BE } + }, + FrameFormat::Y216 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_YUV422P16LE } else { + AVPixelFormat::AV_PIX_FMT_YUV422P16BE } + } + FrameFormat::NV16 => AVPixelFormat::AV_PIX_FMT_NV16, + FrameFormat::Yuv_4_2_0 => AVPixelFormat::AV_PIX_FMT_YUV420P, + FrameFormat::NV12 => AVPixelFormat::AV_PIX_FMT_NV12, + FrameFormat::NV21 => AVPixelFormat::AV_PIX_FMT_NV21, + FrameFormat::P010 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_P010LE } else { + AVPixelFormat::AV_PIX_FMT_P010BE } + }, + FrameFormat::P012 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_P012LE } else { + AVPixelFormat::AV_PIX_FMT_P012BE } + }, + FrameFormat::Y411Planar => AVPixelFormat::AV_PIX_FMT_YUV411P, + FrameFormat::Luma_8 => AVPixelFormat::AV_PIX_FMT_GRAY8, + FrameFormat::Luma_10 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_GRAY10LE } else { + AVPixelFormat::AV_PIX_FMT_GRAY10BE } + }, + FrameFormat::Luma_12 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_GRAY12LE } else { + AVPixelFormat::AV_PIX_FMT_GRAY12BE } + }, + FrameFormat::Luma_14 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_GRAY14LE } else { + AVPixelFormat::AV_PIX_FMT_GRAY14BE } + }, + FrameFormat::Luma_16 | FrameFormat::Depth_16 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_GRAY16LE } else { + AVPixelFormat::AV_PIX_FMT_GRAY16BE } + }, + FrameFormat::Rgb_3_3_2 => AVPixelFormat::AV_PIX_FMT_RGB8, + FrameFormat::Rgb_5_6_5 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_RGB565LE } else { + AVPixelFormat::AV_PIX_FMT_RGB565BE } + } + FrameFormat::Rgb_5_5_5 => if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_RGB555LE } else { + AVPixelFormat::AV_PIX_FMT_RGB565BE } + FrameFormat::Rgb_8_8_8 => AVPixelFormat::AV_PIX_FMT_RGB24, + FrameFormat::Argb_8_8_8_8 => AVPixelFormat::AV_PIX_FMT_ARGB, + FrameFormat::Rgba_8_8_8_8 => AVPixelFormat::AV_PIX_FMT_RGBA, + FrameFormat::Bgr_3_3_2 => AVPixelFormat::AV_PIX_FMT_BGR8, + FrameFormat::Bgr_5_6_5 => { + if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_BGR565LE } else { + AVPixelFormat::AV_PIX_FMT_BGR565BE } + } + FrameFormat::Bgr_5_5_5 => if is_little_endian() { + AVPixelFormat::AV_PIX_FMT_BGR555LE } else { + AVPixelFormat::AV_PIX_FMT_BGR555BE } + FrameFormat::Bgr_8_8_8 => AVPixelFormat::AV_PIX_FMT_BGR24, + FrameFormat::Abgr_8_8_8_8 => AVPixelFormat::AV_PIX_FMT_ABGR, + FrameFormat::Bgra_8_8_8_8 => AVPixelFormat::AV_PIX_FMT_BGRA, + _ => AVPixelFormat::AV_PIX_FMT_RGB24, + } +} + + +#[derive(Clone, Debug)] +pub struct FfmpegDecoderConfig { + pub frame_format: FrameFormat, + pub codec_tag: u32, + #[doc = " Extra binary data needed for initializing the decoder, codec-dependent.\n\n Must be allocated with av_malloc() and will be freed by\n avcodec_parameters_free(). The allocated size of extradata must be at\n least extradata_size + AV_INPUT_BUFFER_PADDING_SIZE, with the padding\n bytes zeroed."] + pub extra_data: Option<*mut u8>, + #[doc = " Size of the extradata content in bytes."] + pub extra_data_size: usize, + #[doc = " Additional data associated with the entire stream.\n\n Should be allocated with av_packet_side_data_new() or\n av_packet_side_data_add(), and will be freed by avcodec_parameters_free()."] + pub coded_side_data: Option<*mut AVPacketSideData>, + #[doc = " Amount of entries in @ref coded_side_data."] + pub coded_side_data_size: usize, + #[doc = " - video: the pixel format, the value corresponds to enum AVPixelFormat.\n - audio: the sample format, the value corresponds to enum AVSampleFormat."] + pub pix_fmt: Option, + #[doc = " Codec-specific bitstream restrictions that the stream conforms to."] + pub profile: i32, + pub level: i32, + pub resolution: Resolution, + #[doc = " Video only. The aspect ratio (width / height) which a single pixel\n should have when displayed.\n\n When the aspect ratio is unknown / undefined, the numerator should be\n set to 0 (the denominator may have any value)."] + pub sample_aspect_ratio: AVRational, + #[doc = " Video only. Number of frames per second, for streams with constant frame\n durations. Should be set to { 0, 1 } when some frames have differing\n durations or if the value is not known.\n\n @note This field correponds to values that are stored in codec-level\n headers and is typically overridden by container/transport-layer\n timestamps, when available. It should thus be used only as a last resort,\n when no higher-level timing information is available."] + pub frame_rate: FrameRate, + #[doc = " Video only. The order of the fields in interlaced video."] + pub field_order: AVFieldOrder, + #[doc = " Video only. Additional colorspace characteristics."] + pub color_range: AVColorRange, + pub color_primaries: AVColorPrimaries, + pub color_trc: AVColorTransferCharacteristic, + pub color_space: AVColorSpace, + pub chroma_location: AVChromaLocation, + #[doc = " Video only. Number of delayed frames."] + pub video_delay: i32, +} + +impl FfmpegDecoderConfig { + pub fn with_camera_format(camera_format: &CameraFormat) -> Self { + FfmpegDecoderConfig::from(*camera_format) + } + + pub fn as_avcodec_params(&self) -> Result { + let mut av_codec_params = unsafe { avcodec_parameters_alloc().read() }; + av_codec_params.codec_type = AVMediaType::AVMEDIA_TYPE_VIDEO; + if let Some(extra_data) = self.extra_data { + if extra_data.is_null() { + return Err(NokhwaError::Decoder("extra data is nullptr!".to_string())) + } + av_codec_params.extradata = extra_data; + av_codec_params.extradata_size = self.extra_data_size as i32; + } + if let Some(side_data) = self.coded_side_data { + if side_data.is_null() { + return Err(NokhwaError::Decoder("side data is nullptr!".to_string())) + } + av_codec_params.coded_side_data = side_data; + av_codec_params.nb_coded_side_data = self.coded_side_data_size as i32; + } + + if let Some(id) = convert_format_to_codec_id(&self.frame_format) { + av_codec_params.codec_id = id.into(); + + if let Some(pixfmt) = self.pix_fmt { + av_codec_params.format = pixfmt as i32; + } else { + let pixel_format = convert_frame_format_to_pixfmt(&self.frame_format); + av_codec_params.format = pixel_format as i32; + } + } else { + return Err(NokhwaError::Decoder("Failed to convert frameformat to id".to_string())) + } + + av_codec_params.profile = self.profile; + av_codec_params.level = self.level; + + av_codec_params.width = self.resolution.width() as i32; + av_codec_params.height = self.resolution.height() as i32; + + av_codec_params.sample_aspect_ratio = self.sample_aspect_ratio; + + av_codec_params.framerate = AVRational { num: self.frame_rate.numerator(), den: self.frame_rate.numerator() }; + + av_codec_params.field_order = self.field_order; + + av_codec_params.color_range = self.color_range; + + av_codec_params.color_primaries = self.color_primaries; + + av_codec_params.color_trc = self.color_trc; + + av_codec_params.color_space = self.color_space; + + av_codec_params.chroma_location = self.chroma_location; + + av_codec_params.video_delay = self.video_delay; + + Ok(av_codec_params) + } + + /// SAFETY: the user is responsible for freeing this return value + pub fn as_ptr(&self) -> Result<*mut AVCodecParameters, NokhwaError> { + Ok(&mut self.as_avcodec_params()?) + } +} + +impl From for FfmpegDecoderConfig { + fn from(value: CameraFormat) -> Self { + Self { + frame_format: *value.format(), + codec_tag: 0, + extra_data: None, + extra_data_size: 0, + coded_side_data: None, + coded_side_data_size: 0, + pix_fmt: None, + profile: 0, + level: 0, + resolution: *value.resolution(), + sample_aspect_ratio: AVRational { num: 0, den: 1 }, + frame_rate: *value.frame_rate(), + field_order: AVFieldOrder::AV_FIELD_UNKNOWN, + color_range: AVColorRange::AVCOL_RANGE_UNSPECIFIED, + color_primaries: AVColorPrimaries::AVCOL_PRI_RESERVED0, + color_trc: AVColorTransferCharacteristic::AVCOL_TRC_RESERVED0, + color_space: AVColorSpace::AVCOL_SPC_RGB, + chroma_location: AVChromaLocation::AVCHROMA_LOC_UNSPECIFIED, + video_delay: 0, + } + } +} + +unsafe impl Send for FfmpegCodec {} + +unsafe impl Sync for FfmpegCodec {} + +fn dealloc_av_params(avcodec_parameters: &mut Option<*mut AVCodecParameters>) { + if let Some(param) = avcodec_parameters { + if !param.is_null() { + unsafe { + let mut_param = param as *mut *mut AVCodecParameters; + avcodec_parameters_free(mut_param) + } + } + *avcodec_parameters = None; + } +} + +impl Drop for FfmpegCodec { + fn drop(&mut self) { + let _ = self.deinitialize(); + dealloc_av_params(&mut self.temp_cfg); + unsafe { + let mut ctx = self.decoder.as_mut_ptr(); + if !ctx.is_null() { + avcodec_free_context(&mut ctx) + } + + // if let Some(mut sws) = self.sws_context { + // sws_freeContext(&mut sws) + // } + } + } +} + +fn switch_endian(little: T, not: T) -> T { + match is_little_endian() { + true => little, + false => not, + } +} + +#[cfg(target_endian = "little")] +const fn is_little_endian() -> bool { + true +} +#[cfg(not(target_endian = "little"))] +const fn is_little_endian() -> bool { + false +} diff --git a/nokhwa-decoders/src/lib.rs b/nokhwa-decoders/src/lib.rs new file mode 100644 index 0000000..11b92af --- /dev/null +++ b/nokhwa-decoders/src/lib.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "ffmpeg")] +pub mod ffmpeg; \ No newline at end of file