untested ffmpeg decoder, mostly finished core

This commit is contained in:
l1npengtul
2025-06-20 17:32:06 +09:00
parent 361e26e453
commit a2f458c331
27 changed files with 1880 additions and 671 deletions
+1 -1
View File
@@ -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
View File
@@ -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": {
+3 -1
View File
@@ -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";
+1 -1
View File
@@ -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;
+276 -160
View File
@@ -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> {
@@ -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 => {
(
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 => {
(
Some(ControlValue::Boolean(description.default != 0)),
),
Type::Bitmask => (
ControlValueDescriptor::BitMask,
Some(ControlValue::BitMask(description.default as u64))
)
}
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 {
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,11 +302,7 @@ 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> {
@@ -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,52 +403,85 @@ 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)| {
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)| {
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)])
(
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
if (stepwise.step.denominator != stepwise.max.denominator)
|| (stepwise.step.denominator != stepwise.min.denominator)
{
return None;
}
let min = stepwise.min.numerator as i32;
@@ -438,30 +490,43 @@ impl Setting for V4L2Camera {
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>>())
(
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 {
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_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,17 +552,26 @@ 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| {
self.device
.set_control(Control {
id: cid,
value: v4l_value,
})
.map_err(|why| {
Err(NokhwaError::SetPropertyError {
property: cid.to_string(),
value: value.to_string(),
@@ -509,39 +583,61 @@ impl Setting for V4L2Camera {
}
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 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 {
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::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()
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 {
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,15 +680,12 @@ 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 || {
loop {
let thread = std::thread::spawn(move || loop {
if ctrl_recv.is_disconnected() || sender.is_disconnected() {
return;
}
@@ -601,12 +699,30 @@ impl Capture for V4L2Camera {
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));
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))));
}
@@ -614,29 +730,29 @@ impl Capture for V4L2Camera {
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(())
}
+4 -3
View File
@@ -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;
+19 -16
View File
@@ -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 {
@@ -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(
+4 -7
View File
@@ -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
+24 -6
View File
@@ -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>;
/// # Errors
/// Will error on
fn refresh_controls(&mut self) -> Result<(), NokhwaError>;
}
@@ -44,8 +54,6 @@ pub trait AsyncSetting {
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>;
}
+69
View File
@@ -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()
}
}
+35 -35
View File
@@ -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 {
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
View File
@@ -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 {}
+5 -3
View File
@@ -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 {}
+54 -49
View File
@@ -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| {
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())
return res_range.validate(cam_fmt.resolution());
}
if let Some(frame_rate_range) = frame_rate {
return frame_rate_range.validate(&cam_fmt.frame_rate())
return frame_rate_range.validate(cam_fmt.frame_rate());
}
true
}).collect()
})
.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,
};
+21 -21
View File
@@ -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
View File
@@ -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) => {
+78
View File
@@ -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 ],
}
+8 -5
View File
@@ -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;
+8 -8
View File
@@ -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>)
}
}
+44 -38
View File
@@ -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 inclusive {
if upper {
']'
} else {
'['
}
}
false => {
if upper {
} 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);
}
+36 -37
View File
@@ -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,9 +79,9 @@ 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.
@@ -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,
@@ -126,26 +131,23 @@ impl StreamHandle {
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 })
self.frame.recv().unwrap_or_else(|_| Event::Closed)
}
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 {
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,
}
}, |e| { e })
}
}),
};
if let Event::FormatChange(fmt) = event {
self.format.set(fmt);
}
return Ok(event)
Ok(event)
}
pub fn next_frame(&self) -> Result<FrameBuffer, NokhwaError> {
@@ -153,28 +155,28 @@ 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 {
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?
@@ -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()))
return Err(NokhwaError::ReadFrameError("Stream Closed.".to_string()));
}
Event::Other(why) => {
match self.configuration.on_other {
Event::Other(why) => match self.configuration.on_other {
ControlFlowOnOther::Continue => continue,
ControlFlowOnOther::Break => return Err(NokhwaError::ReadFrameError(why))
}
}
ControlFlowOnOther::Break => return Err(NokhwaError::ReadFrameError(why)),
},
_ => {}
}
}
}
@@ -205,4 +205,3 @@ impl Drop for StreamHandle {
let _ = self.control.try_send(());
}
}
+26 -28
View File
@@ -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,33 +232,33 @@ 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
+41
View File
@@ -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
+670
View File
@@ -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(), &param)
};
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
}
+2
View File
@@ -0,0 +1,2 @@
#[cfg(feature = "ffmpeg")]
pub mod ffmpeg;