mirror of
https://github.com/l1npengtul/nokhwa.git
synced 2026-07-04 02:27:26 +00:00
untested ffmpeg decoder, mostly finished core
This commit is contained in:
+1
-1
@@ -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]
|
||||
|
||||
Generated
+9
-9
@@ -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": {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1 +1 @@
|
||||
// TODO: todo, probably 0.12
|
||||
// TODO: todo, probably 0.12
|
||||
|
||||
+339
-223
@@ -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<FourCC, NokhwaError> {
|
||||
@@ -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<ControlD
|
||||
let flags = flags(description.flags);
|
||||
|
||||
let (descriptor, default) = match description.typ {
|
||||
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::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::<HashMap<ControlValue, ControlValue>>())
|
||||
}
|
||||
.collect::<HashMap<ControlValue, ControlValue>>(),
|
||||
),
|
||||
// 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<ControlD
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
ControlDescription::new(
|
||||
flags,
|
||||
descriptor,
|
||||
default
|
||||
)
|
||||
ControlDescription::new(flags, descriptor, default)
|
||||
}
|
||||
|
||||
fn conv_control_value_to_v4l_value(control: ControlValue) -> Result<Value, NokhwaError> {
|
||||
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, Nokhw
|
||||
if let ControlValue::Integer(i) = &e {
|
||||
Value::Integer(*i)
|
||||
} else {
|
||||
return Err(NokhwaError::ConversionError("could not convert non integer enum pick".to_string()))
|
||||
return Err(NokhwaError::ConversionError(
|
||||
"could not convert non integer enum pick".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
ControlValue::Orientation(o) => Value::Integer(match o {
|
||||
@@ -315,14 +328,15 @@ fn conv_control_value_to_v4l_value(control: ControlValue) -> Result<Value, Nokhw
|
||||
_ => 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<Vec<CameraInformation>> {
|
||||
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::<Vec<_>>())
|
||||
Device::new(index)
|
||||
.map(|dev| {
|
||||
dev.query_caps()
|
||||
.map(|caps| index_capabilities_to_camera_info(index as u32, caps))
|
||||
.ok()
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn open(&mut self, index: CameraIndex) -> NokhwaResult<Self::Camera> {
|
||||
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<Vec<CameraFormat>, 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<HashMap<Resolution, Vec<FrameRate>>, NokhwaError> {
|
||||
fn enumerate_resolution_and_frame_rates(
|
||||
&self,
|
||||
frame_format: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<FrameRate>>, 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::<Vec<Resolution>>();
|
||||
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::<Vec<Resolution>>();
|
||||
|
||||
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::<Result<Vec<(Resolution, Vec<FrameInterval>)>, 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::<Vec<FrameRate>>())
|
||||
})
|
||||
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::<Vec<FrameRate>>(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}).flatten().collect::<HashMap<Resolution, Vec<FrameRate>>>())
|
||||
})
|
||||
.flatten()
|
||||
.collect::<HashMap<Resolution, Vec<FrameRate>>>())
|
||||
}
|
||||
|
||||
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::<HashMap<ControlId, ControlDescription>>();
|
||||
.flatten()
|
||||
.collect::<HashMap<ControlId, ControlDescription>>();
|
||||
|
||||
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::<HashMap<ControlId, ControlValue>>();
|
||||
.collect::<HashMap<ControlId, ControlValue>>();
|
||||
|
||||
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<StreamConfiguration>) -> Result<StreamHandle, NokhwaError> {
|
||||
fn open_stream(
|
||||
&mut self,
|
||||
stream_configuration: Option<StreamConfiguration>,
|
||||
) -> Result<StreamHandle, NokhwaError> {
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
// TODO
|
||||
// TODO
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<NokhwaError> = None;
|
||||
let mut last_error: Option<NokhwaError> = 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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<Vec<CameraFormat>, NokhwaError>;
|
||||
|
||||
/// # Errors
|
||||
/// Will error on
|
||||
fn enumerate_resolution_and_frame_rates(
|
||||
&self,
|
||||
frame_format: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<FrameRate>>, NokhwaError>;
|
||||
|
||||
/// # Errors
|
||||
/// Will error on
|
||||
fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError>;
|
||||
|
||||
fn control_ids(&self) -> Keys<ControlId, ControlDescription>;
|
||||
@@ -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<HashMap<Resolution, Vec<FrameRate>>, 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<StreamConfiguration>) -> Result<Arc<StreamHandle>, NokhwaError>;
|
||||
/// # Errors
|
||||
/// Errors are driver specific
|
||||
fn open_stream(
|
||||
&mut self,
|
||||
stream_configuration: Option<StreamConfiguration>,
|
||||
) -> Result<Arc<StreamHandle>, 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<StreamConfiguration>) -> Result<StreamHandle, NokhwaError>;
|
||||
async fn open_stream_async(
|
||||
&mut self,
|
||||
stream_configuration: Option<StreamConfiguration>,
|
||||
) -> Result<StreamHandle, NokhwaError>;
|
||||
|
||||
async fn close_stream_async(&mut self) -> Result<(), NokhwaError>;
|
||||
}
|
||||
|
||||
@@ -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<Self::WrittenMeta, NokhwaError>;
|
||||
|
||||
fn preferred_buffer_min_size(
|
||||
&mut self,
|
||||
format: &Option<CameraFormat>,
|
||||
) -> Result<Option<usize>, 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<Option<usize>, NokhwaError>;
|
||||
|
||||
async fn deinitialize_async(&mut self) -> Result<(), NokhwaError> {
|
||||
self.deinitialize()
|
||||
}
|
||||
}
|
||||
+38
-38
@@ -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<ControlId, ControlDescription>,
|
||||
device_values: HashMap<ControlId, ControlValue>,
|
||||
) -> Option<Self> {
|
||||
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<ControlId, ControlDescription>,
|
||||
device_values: HashMap<ControlId, ControlValue>,
|
||||
) -> 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<ControlId, ControlDescription> {
|
||||
#[must_use] pub fn descriptions(&self) -> Values<ControlId, ControlDescription> {
|
||||
self.descriptions.values()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> Values<ControlId, ControlValue> {
|
||||
#[must_use] pub fn values(&self) -> Values<ControlId, ControlValue> {
|
||||
self.values.values()
|
||||
}
|
||||
|
||||
pub fn ids(&self) -> Keys<ControlId, ControlDescription> {
|
||||
#[must_use] pub fn ids(&self) -> Keys<ControlId, ControlDescription> {
|
||||
self.descriptions.keys()
|
||||
}
|
||||
|
||||
pub fn validate(&self, control_id: &ControlId, value: &ControlValue) -> Result<bool, NokhwaError> {
|
||||
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<bool, NokhwaError> {
|
||||
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<ControlFlags>,
|
||||
control_value_descriptor: ControlValueDescriptor,
|
||||
default_value: Option<ControlValue>,
|
||||
@@ -177,7 +181,7 @@ impl ControlDescription {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_unchecked(
|
||||
#[must_use] pub fn new_unchecked(
|
||||
control_flags: HashSet<ControlFlags>,
|
||||
control_value_descriptor: ControlValueDescriptor,
|
||||
default_value: Option<ControlValue>,
|
||||
@@ -189,15 +193,15 @@ impl ControlDescription {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> &HashSet<ControlFlags> {
|
||||
#[must_use] pub fn flags(&self) -> &HashSet<ControlFlags> {
|
||||
&self.flags
|
||||
}
|
||||
|
||||
pub fn descriptor(&self) -> &ControlValueDescriptor {
|
||||
#[must_use] pub fn descriptor(&self) -> &ControlValueDescriptor {
|
||||
&self.descriptor
|
||||
}
|
||||
|
||||
pub fn default_value(&self) -> &Option<ControlValue> {
|
||||
#[must_use] pub fn default_value(&self) -> &Option<ControlValue> {
|
||||
&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<i64>),
|
||||
BitMask,
|
||||
Float(Range<f64>),
|
||||
Float(Range<OrderedFloat<f64>>),
|
||||
String,
|
||||
Boolean,
|
||||
// Array of any values of singular type
|
||||
Array(ControlValueDescriptor),
|
||||
Array(Box<ControlValueDescriptor>),
|
||||
// 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<f64>),
|
||||
String(String),
|
||||
String(CompactString),
|
||||
Boolean(bool),
|
||||
Array(Vec<ControlValue>),
|
||||
Binary(Vec<u8>),
|
||||
@@ -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
|
||||
|
||||
+30
-55
@@ -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<CameraFormat>;
|
||||
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<Self::OutputMeta, NokhwaError>;
|
||||
|
||||
fn decode_to_pixel_buffer<P: Pixel>(
|
||||
&mut self,
|
||||
to_decode: FrameBuffer,
|
||||
buffer: impl AsMut<[P::Subpixel]>,
|
||||
) -> Result<Self::OutputMeta, NokhwaError>
|
||||
where <P as Pixel>::Subpixel: NonFloatScalarWidth;
|
||||
|
||||
fn decode<P: Pixel>(
|
||||
&mut self,
|
||||
to_decode: FrameBuffer,
|
||||
) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError>
|
||||
where <P as Pixel>::Subpixel: NonFloatScalarWidth;
|
||||
}
|
||||
|
||||
impl<'stream, Video> Decoder<'stream, Video> where Video: Codec {
|
||||
pub fn new(stream: &'stream mut StreamHandle, decoder: Video) -> Result<Self, NokhwaError> {
|
||||
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<FrameFormat, NokhwaError>;
|
||||
|
||||
fn resolution(&self) -> Result<Resolution, NokhwaError>;
|
||||
|
||||
fn frame_rate(&self) -> Result<FrameRate, NokhwaError>;
|
||||
|
||||
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<Cow<'_, [u8]>, NokhwaError>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub trait CodecAsync: Codec + Debug {}
|
||||
|
||||
@@ -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<T> = Result<T, NokhwaError>;
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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<Range<Resolution>>,
|
||||
preferred_resolution: Option<Resolution>,
|
||||
frame_rate: Option<Range<FrameRate>>,
|
||||
preferred_frame_rate: Option<FrameRate>,
|
||||
},
|
||||
HighestFrameRate {
|
||||
frame_rate: Range<FrameRate>,
|
||||
@@ -38,18 +39,21 @@ pub struct FormatRequest {
|
||||
}
|
||||
|
||||
impl FormatRequest {
|
||||
pub fn new(format_request_type: FormatRequestType, allowed_frame_formats: Vec<FrameFormat>) -> Self {
|
||||
#[must_use] pub fn new(
|
||||
format_request_type: FormatRequestType,
|
||||
allowed_frame_formats: Vec<FrameFormat>,
|
||||
) -> Self {
|
||||
Self {
|
||||
request_type: format_request_type,
|
||||
allowed_frame_formats,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn best<'a>(&self, camera_formats: &'a Vec<CameraFormat>) -> 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<CameraFormat>) -> Vec<CameraFormat> {
|
||||
#[must_use] pub fn sort_foramts(&self, mut camera_formats: Vec<CameraFormat>) -> Vec<CameraFormat> {
|
||||
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<Resolution>, frame_rate: &Option<FrameRate>, format: &CameraFormat) -> f32 {
|
||||
#[must_use]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn format_distance_to_point(
|
||||
resolution: &Option<Resolution>,
|
||||
frame_rate: &Option<FrameRate>,
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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<CompactString, ControlValue>,
|
||||
}
|
||||
|
||||
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<H: Hasher>(&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<CompactString, ControlValue>;
|
||||
|
||||
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<Metadata>) -> 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<Metadata>) {
|
||||
return (self.buffer, self.metadata)
|
||||
#[must_use] pub fn consume(self) -> (Cow<'static, [u8]>, Option<Metadata>) {
|
||||
(self.buffer, self.metadata)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
||||
+307
-83
@@ -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<f32>),
|
||||
F64(OrderedFloat<f64>),
|
||||
}
|
||||
|
||||
#[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) => {
|
||||
|
||||
@@ -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<Px, Meta>
|
||||
where
|
||||
Px: Pixel,
|
||||
<Px as Pixel>::Subpixel: NonFloatScalarWidth,
|
||||
Meta: Debug,
|
||||
{
|
||||
pub buffer: ImageBuffer<Px, Vec<Px::Subpixel>>,
|
||||
pub metadata: Meta,
|
||||
}
|
||||
|
||||
impl<Px, Meta> DecodedImage<Px, Meta> where
|
||||
Px: Pixel,
|
||||
<Px as Pixel>::Subpixel: NonFloatScalarWidth,
|
||||
Meta: Debug {
|
||||
pub fn new(buffer: ImageBuffer<Px, Vec<Px::Subpixel>>,
|
||||
metadata: Meta) -> Self {
|
||||
Self {
|
||||
buffer,
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Px, Meta> Deref for DecodedImage<Px, Meta>
|
||||
where
|
||||
Px: Pixel,
|
||||
<Px as Pixel>::Subpixel: NonFloatScalarWidth,
|
||||
Meta: Debug
|
||||
{
|
||||
type Target = ImageBuffer<Px, Vec<Px::Subpixel>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl<Px, Meta> DerefMut for DecodedImage<Px, Meta>
|
||||
where
|
||||
Px: Pixel,
|
||||
<Px as 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 ],
|
||||
}
|
||||
@@ -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 <l1npengtul@protonmail.com> / The Nokhwa Contributors
|
||||
* Copyright 2025 l1npengtul <l1npengtul@protonmail.com> / 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;
|
||||
|
||||
@@ -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<Self::Camera>;
|
||||
|
||||
fn open_dynamic(&mut self, index: CameraIndex) -> NokhwaResult<Box<dyn Camera>> {
|
||||
self.open(index).map(|cam| Box::new(cam))
|
||||
fn open_dynamic(&mut self, index: CameraIndex) -> NokhwaResult<Box<dyn Camera>> where <Self as PlatformTrait>::Camera: 'static {
|
||||
self.open(index).map(|cam| Box::new(cam) as Box<dyn Camera>)
|
||||
}
|
||||
}
|
||||
|
||||
#[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<Self::AsyncCamera>;
|
||||
|
||||
async fn open_dynamic_async(&mut self, index: &CameraIndex) -> NokhwaResult<Box<dyn Camera>> {
|
||||
self.open_async(index).await.map(|cam| Box::new(cam))
|
||||
async fn open_dynamic_async(&mut self, index: &CameraIndex) -> NokhwaResult<Box<dyn Camera>> where <Self as AsyncPlatformTrait>::AsyncCamera: 'static {
|
||||
self.open_async(index).await.map(|cam| Box::new(cam) as Box<dyn Camera>)
|
||||
}
|
||||
}
|
||||
|
||||
+52
-46
@@ -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<T> where T: RangeItem
|
||||
pub struct Range<T>
|
||||
where
|
||||
T: RangeItem,
|
||||
{
|
||||
minimum: T,
|
||||
lower_inclusive: bool,
|
||||
@@ -25,7 +27,10 @@ pub struct Range<T> where T: RangeItem
|
||||
step: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Range<T> where T: Copy {
|
||||
impl<T> Range<T>
|
||||
where
|
||||
T: Copy + RangeItem,
|
||||
{
|
||||
/// Create an upper and lower inclusive [`Range`]
|
||||
pub fn new(min: T, max: T, step: Option<T>) -> Self {
|
||||
Self {
|
||||
@@ -42,7 +47,7 @@ impl<T> Range<T> where T: Copy {
|
||||
lower_inclusive: bool,
|
||||
max: T,
|
||||
upper_inclusive: bool,
|
||||
step: Option<T>
|
||||
step: Option<T>,
|
||||
) -> Self {
|
||||
Self {
|
||||
minimum: min,
|
||||
@@ -53,13 +58,13 @@ impl<T> Range<T> where T: Copy {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_minimum(&mut self, minimum: Option<T>) {
|
||||
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<T>) {
|
||||
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<T> Default for Range<T>
|
||||
where
|
||||
T: Default,
|
||||
T: Default + RangeItem,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Range {
|
||||
@@ -133,7 +132,7 @@ where
|
||||
|
||||
impl<T> Display for Range<T>
|
||||
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<T>(default: &Option<T>) -> String
|
||||
where
|
||||
T: Debug,
|
||||
pub trait RangeItem:
|
||||
Copy
|
||||
+ Clone
|
||||
+ Debug
|
||||
+ Div<Output = Self>
|
||||
+ Sub<Output = Self>
|
||||
+ Rem<Output = Self>
|
||||
+ Hash
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Eq
|
||||
+ PartialEq
|
||||
{
|
||||
match default {
|
||||
Some(v) => {
|
||||
format!("{v:?}")
|
||||
}
|
||||
None => String::from("None"),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RangeItem: Copy + Clone + Debug + Div<Output = Self> + Sub<Output = Self> + Rem<Output = Self> + 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<f32> {
|
||||
const ZERO: Self = OrderedFloat(0_f32);
|
||||
const MIN: Self = OrderedFloat(f32::MIN);
|
||||
const MAX: Self = OrderedFloat(f32::MAX);
|
||||
}
|
||||
|
||||
impl RangeItem for OrderedFloat<f64> {
|
||||
const ZERO: Self = OrderedFloat(0_f64);
|
||||
}
|
||||
|
||||
const MIN: Self = OrderedFloat(f64::MIN);
|
||||
const MAX: Self = OrderedFloat(f64::MAX);
|
||||
}
|
||||
|
||||
+55
-56
@@ -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<dyn std::error::Error>),
|
||||
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<Event>,
|
||||
@@ -106,7 +106,12 @@ pub struct StreamHandle {
|
||||
|
||||
impl StreamHandle {
|
||||
/// You shouldn't be here.
|
||||
pub fn new(recv: Receiver<Event>, control: Arc<Sender<()>>, configuration: StreamConfiguration, format: CameraFormat) -> Self {
|
||||
#[must_use] pub fn new(
|
||||
recv: Receiver<Event>,
|
||||
control: Arc<Sender<()>>,
|
||||
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<Event, NokhwaError> {
|
||||
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<FrameBuffer, NokhwaError> {
|
||||
@@ -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<Event, NokhwaError> {
|
||||
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<FrameBuffer, NokhwaError> {
|
||||
@@ -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(());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-29
@@ -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<f32> {
|
||||
let numerator_float = f32::from_i32(*self.numerator())?;
|
||||
let denominator_float = f32::from_i32(*self.denominator())?;
|
||||
|
||||
#[must_use] pub fn approximate_float(&self) -> Option<f32> {
|
||||
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<Rational32> 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<str> + ?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
|
||||
|
||||
@@ -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
|
||||
@@ -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<Sws>,
|
||||
}
|
||||
|
||||
impl FfmpegDecoder {
|
||||
pub fn new(config: <FfmpegDecoder as nokhwa_core::decoder::Decoder>::Config) -> Result<Self, NokhwaError> {
|
||||
let codec = FfmpegCodec::new(config)?;
|
||||
Ok(
|
||||
Self {
|
||||
codec,
|
||||
sws: None,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_format(format: &CameraFormat) -> Result<Self, NokhwaError> {
|
||||
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, <FfmpegDecoder as Decoder>::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 = <FfmpegCodec as Codec>::Config;
|
||||
type OutputMeta = <FfmpegCodec as Codec>::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<Self::OutputMeta, NokhwaError> {
|
||||
// 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<P: Pixel>(&mut self, to_decode: FrameBuffer, mut buffer: impl AsMut<[P::Subpixel]>) -> Result<Self::OutputMeta, NokhwaError> where <P as Pixel>::Subpixel: NonFloatScalarWidth {
|
||||
let destination_format = pixel_to_destination_px_fmt::<P>(<P::Subpixel as NonFloatScalarWidth>::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::<P::Subpixel, u8>(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<P: Pixel>(&mut self, to_decode: FrameBuffer) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError> where <P as Pixel>::Subpixel: NonFloatScalarWidth {
|
||||
let min_size = P::CHANNEL_COUNT as usize * (<P::Subpixel as NonFloatScalarWidth>::WIDTH as usize / 8_usize) * self.codec.config.resolution.height() as usize * self.codec.config.resolution.width() as usize;
|
||||
let mut buffer: Vec<P::Subpixel> = vec![<P::Subpixel>::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<P: Pixel>(width: u32) -> Option<AVPixelFormat> where <P as Pixel>::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<Borrow<'a>> for PacketOrRef<'a> {
|
||||
fn from(value: Borrow<'a>) -> Self {
|
||||
Self::Ref(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Packet> 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: <FfmpegCodec as Codec>::Config) -> Result<Self, NokhwaError> {
|
||||
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<FfmpegFrameMetadata, NokhwaError> {
|
||||
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::<i32, AVPixelFormat>(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<CameraFormat>) -> Result<Option<usize>, 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<Id> {
|
||||
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<AVPixelFormat>,
|
||||
#[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<AVCodecParameters, NokhwaError> {
|
||||
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<CameraFormat> 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<T>(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
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "ffmpeg")]
|
||||
pub mod ffmpeg;
|
||||
Reference in New Issue
Block a user