mirror of
https://github.com/l1npengtul/nokhwa.git
synced 2026-07-04 02:27:26 +00:00
untested ffmpeg decoder, mostly finished core
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@ repository = "https://github.com/l1npengtul/nokhwa"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[workspace]
|
[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/*"]
|
exclude = ["examples/*"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
Generated
+9
-9
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1740019556,
|
"lastModified": 1750215678,
|
||||||
"narHash": "sha256-vn285HxnnlHLWnv59Og7muqECNMS33mWLM14soFIv2g=",
|
"narHash": "sha256-Rc/ytpamXRf6z8UA2SGa4aaWxUXRbX2MAWIu2C8M+ok=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "dad564433178067be1fbdfcce23b546254b6d641",
|
"rev": "5395fb3ab3f97b9b7abca147249fa2e8ed27b192",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -36,11 +36,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736320768,
|
"lastModified": 1744536153,
|
||||||
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -62,11 +62,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1740191166,
|
"lastModified": 1750387093,
|
||||||
"narHash": "sha256-WqRxO1Afx8jPYG4CKwkvDFWFvDLCwCd4mxb97hFGYPg=",
|
"narHash": "sha256-MgL1+yNVcSD6OlzSmKt5GS4RmAQnNCjckjgPC1hmMPg=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "74a3fb71b0cc67376ab9e7c31abcd68c813fc226",
|
"rev": "517e9871d182346b53bb7f23fed00810c14db396",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
};
|
};
|
||||||
rustbin = pkgs.rust-bin.selectLatestNightlyWith (toolchain:
|
rustbin = pkgs.rust-bin.selectLatestNightlyWith (toolchain:
|
||||||
toolchain.default.override {
|
toolchain.default.override {
|
||||||
extensions = ["rust-src"];
|
extensions = ["rust-src" "clippy" "rustfmt" "miri"];
|
||||||
});
|
});
|
||||||
in {
|
in {
|
||||||
formatter = pkgs.alejandra;
|
formatter = pkgs.alejandra;
|
||||||
@@ -46,6 +46,8 @@
|
|||||||
libv4l
|
libv4l
|
||||||
pipewire
|
pipewire
|
||||||
rustup
|
rustup
|
||||||
|
ffmpeg-full
|
||||||
|
nasm
|
||||||
]);
|
]);
|
||||||
|
|
||||||
env.RUST_SRC_PATH = "${rustbin}/lib/rustlib/src/rust/library";
|
env.RUST_SRC_PATH = "${rustbin}/lib/rustlib/src/rust/library";
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
pub mod pipewire;
|
||||||
#[cfg(feature = "v4l2")]
|
#[cfg(feature = "v4l2")]
|
||||||
pub mod v4l2;
|
pub mod v4l2;
|
||||||
pub mod pipewire;
|
|
||||||
mod v4l2r;
|
mod v4l2r;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
// TODO: todo, probably 0.12
|
// TODO: todo, probably 0.12
|
||||||
|
|||||||
+339
-223
@@ -1,38 +1,57 @@
|
|||||||
use std::borrow::Cow;
|
use flume::{bounded, unbounded, Sender};
|
||||||
use nokhwa_core::camera::{Camera, Capture, Setting};
|
use nokhwa_core::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::error::{NokhwaError, NokhwaResult};
|
||||||
|
use nokhwa_core::frame_buffer::{CompactString, FrameBuffer, Metadata};
|
||||||
use nokhwa_core::frame_format::FrameFormat;
|
use nokhwa_core::frame_format::FrameFormat;
|
||||||
use nokhwa_core::platform::{Backends, PlatformTrait};
|
use nokhwa_core::platform::{Backends, PlatformTrait};
|
||||||
use nokhwa_core::ranges::Range;
|
use nokhwa_core::ranges::Range;
|
||||||
use nokhwa_core::stream::{Event, StreamBounds, StreamConfiguration, StreamHandle};
|
use nokhwa_core::stream::{Event, StreamBounds, StreamConfiguration, StreamHandle};
|
||||||
use nokhwa_core::types::{CameraFormat, CameraIndex, CameraInformation, FrameRate, Resolution};
|
use nokhwa_core::types::{CameraFormat, CameraIndex, CameraInformation, FrameRate, Resolution};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::hash_map::{Keys, Values};
|
use std::collections::hash_map::{Keys, Values};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::num::NonZeroI32;
|
use std::num::NonZeroI32;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use flume::{Sender, unbounded, bounded};
|
|
||||||
use v4l::context::enum_devices;
|
use v4l::context::enum_devices;
|
||||||
use v4l::control::{Description, Flags, MenuItem, Type, Value};
|
use v4l::control::{Description, Flags, MenuItem, Type, Value};
|
||||||
use v4l::frameinterval::FrameIntervalEnum;
|
use v4l::frameinterval::FrameIntervalEnum;
|
||||||
|
use v4l::io::traits::OutputStream;
|
||||||
|
use v4l::prelude::MmapStream;
|
||||||
use v4l::video::output::Parameters;
|
use v4l::video::output::Parameters;
|
||||||
use v4l::video::Output;
|
use v4l::video::Output;
|
||||||
use v4l::{Capabilities, Control, Device, Format, FourCC, Fraction, FrameInterval};
|
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 v4l2_sys_mit::{
|
||||||
use v4l::io::traits::OutputStream;
|
V4L2_CAMERA_ORIENTATION_BACK, V4L2_CAMERA_ORIENTATION_EXTERNAL, V4L2_CAMERA_ORIENTATION_FRONT,
|
||||||
use v4l::prelude::MmapStream;
|
V4L2_CID_AUTO_EXPOSURE_BIAS, V4L2_CID_AUTO_FOCUS_RANGE, V4L2_CID_AUTO_FOCUS_STATUS,
|
||||||
use nokhwa_core::frame_buffer::{CompactString, FrameBuffer, Metadata};
|
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 {
|
fn index_capabilities_to_camera_info(index: u32, capabilities: Capabilities) -> CameraInformation {
|
||||||
let name = capabilities.card;
|
let name = capabilities.card;
|
||||||
let description = capabilities.driver;
|
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))
|
CameraInformation::new(name, description, misc, CameraIndex::Index(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
macro_rules! define_back_and_forth {
|
macro_rules! define_back_and_forth {
|
||||||
( $($frame_format:expr => $fourcc:expr ,)+ ) => {
|
( $($frame_format:expr => $fourcc:expr ,)+ ) => {
|
||||||
fn frame_format_to_fourcc(frame_format: FrameFormat) -> Result<FourCC, NokhwaError> {
|
fn frame_format_to_fourcc(frame_format: FrameFormat) -> Result<FourCC, NokhwaError> {
|
||||||
@@ -51,7 +70,7 @@ macro_rules! define_back_and_forth {
|
|||||||
return Err(NokhwaError::ConversionError("Unsupported FrameFormat".to_string()))
|
return Err(NokhwaError::ConversionError("Unsupported FrameFormat".to_string()))
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fourcc_to_frame_format(four_cc: FourCC) -> FrameFormat {
|
fn fourcc_to_frame_format(four_cc: FourCC) -> FrameFormat {
|
||||||
match &four_cc.repr {
|
match &four_cc.repr {
|
||||||
$(
|
$(
|
||||||
@@ -63,7 +82,6 @@ macro_rules! define_back_and_forth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
define_back_and_forth!(
|
define_back_and_forth!(
|
||||||
FrameFormat::H265 => b"HEVC",
|
FrameFormat::H265 => b"HEVC",
|
||||||
FrameFormat::H264 => b"H264",
|
FrameFormat::H264 => b"H264",
|
||||||
@@ -210,74 +228,71 @@ fn convert_description_to_ctrl_body(description: Description) -> Option<ControlD
|
|||||||
let flags = flags(description.flags);
|
let flags = flags(description.flags);
|
||||||
|
|
||||||
let (descriptor, default) = match description.typ {
|
let (descriptor, default) = match description.typ {
|
||||||
Type::Integer | Type::Integer64 => {
|
Type::Integer | Type::Integer64 => (
|
||||||
(
|
ControlValueDescriptor::Integer(Range::new(
|
||||||
ControlValueDescriptor::Integer(Range::new(description.minimum, description.maximum, Some(description.step as i64))),
|
description.minimum,
|
||||||
Some(ControlValue::Integer(description.default))
|
description.maximum,
|
||||||
)
|
Some(description.step as i64),
|
||||||
}
|
)),
|
||||||
Type::U8 => {
|
Some(ControlValue::Integer(description.default)),
|
||||||
(
|
),
|
||||||
ControlValueDescriptor::Integer(Range::new(0, u8::MAX_VALUE as i64, Some(description.step as i64))),
|
Type::U8 => (
|
||||||
Some(ControlValue::Integer(description.default))
|
ControlValueDescriptor::Integer(Range::new(
|
||||||
)
|
0,
|
||||||
}
|
u8::MAX_VALUE as i64,
|
||||||
Type::U16 => {
|
Some(description.step as i64),
|
||||||
(
|
)),
|
||||||
ControlValueDescriptor::Integer(Range::new(0, u16::MAX_VALUE as i64, Some(description.step as i64))),
|
Some(ControlValue::Integer(description.default)),
|
||||||
Some(ControlValue::Integer(description.default))
|
),
|
||||||
)
|
Type::U16 => (
|
||||||
}
|
ControlValueDescriptor::Integer(Range::new(
|
||||||
Type::U32 => {
|
0,
|
||||||
(
|
u16::MAX_VALUE as i64,
|
||||||
ControlValueDescriptor::Integer(Range::new(0, u32::MAX_VALUE as i64, Some(description.step as i64))),
|
Some(description.step as i64),
|
||||||
Some(ControlValue::Integer(description.default))
|
)),
|
||||||
)
|
Some(ControlValue::Integer(description.default)),
|
||||||
}
|
),
|
||||||
Type::String => {
|
Type::U32 => (
|
||||||
(
|
ControlValueDescriptor::Integer(Range::new(
|
||||||
ControlValueDescriptor::String,
|
0,
|
||||||
None,
|
u32::MAX_VALUE as i64,
|
||||||
)
|
Some(description.step as i64),
|
||||||
}
|
)),
|
||||||
Type::Boolean => {
|
Some(ControlValue::Integer(description.default)),
|
||||||
(
|
),
|
||||||
ControlValueDescriptor::Boolean,
|
Type::String => (ControlValueDescriptor::String, None),
|
||||||
Some(ControlValue::Boolean(description.default != 0))
|
Type::Boolean => (
|
||||||
)
|
ControlValueDescriptor::Boolean,
|
||||||
}
|
Some(ControlValue::Boolean(description.default != 0)),
|
||||||
Type::Bitmask => {
|
),
|
||||||
(
|
Type::Bitmask => (
|
||||||
ControlValueDescriptor::BitMask,
|
ControlValueDescriptor::BitMask,
|
||||||
Some(ControlValue::BitMask(description.default as u64))
|
Some(ControlValue::BitMask(description.default as u64)),
|
||||||
)
|
),
|
||||||
}
|
|
||||||
Type::IntegerMenu | Type::Menu => {
|
Type::IntegerMenu | Type::Menu => {
|
||||||
// our keys
|
// our keys
|
||||||
let descriptor = match description.items {
|
let descriptor = match description.items {
|
||||||
Some(items) => {
|
Some(items) => ControlValueDescriptor::Menu(
|
||||||
ControlValueDescriptor::Menu(items.into_iter().map(|(idx, menu_item)| {
|
items
|
||||||
(ControlValue::Integer(idx as i64), match menu_item {
|
.into_iter()
|
||||||
MenuItem::Name(name) => ControlValue::String(name),
|
.map(|(idx, menu_item)| {
|
||||||
MenuItem::Value(v) => ControlValue::Integer(*v),
|
(
|
||||||
|
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
|
// This can probably never happen so we just immediately return if this bad thing
|
||||||
// happens somehow
|
// happens somehow
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
(
|
(descriptor, Some(ControlValue::Integer(description.default)))
|
||||||
descriptor,
|
|
||||||
Some(ControlValue::Integer(description.default))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Type::Button => {
|
|
||||||
(
|
|
||||||
ControlValueDescriptor::Null,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
Type::Button => (ControlValueDescriptor::Null, None),
|
||||||
|
|
||||||
// we simply will not support control class.
|
// we simply will not support control class.
|
||||||
// if someone needs it we can fix it later.
|
// if someone needs it we can fix it later.
|
||||||
@@ -287,17 +302,13 @@ fn convert_description_to_ctrl_body(description: Description) -> Option<ControlD
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
ControlDescription::new(
|
ControlDescription::new(flags, descriptor, default)
|
||||||
flags,
|
|
||||||
descriptor,
|
|
||||||
default
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn conv_control_value_to_v4l_value(control: ControlValue) -> Result<Value, NokhwaError> {
|
fn conv_control_value_to_v4l_value(control: ControlValue) -> Result<Value, NokhwaError> {
|
||||||
let value = match control {
|
let value = match control {
|
||||||
ControlValue::Null => Value::None,
|
ControlValue::Null => Value::None,
|
||||||
ControlValue::Integer(i) | ControlValue::BitMask(i) => Value::Integer(i),
|
ControlValue::Integer(i) | ControlValue::BitMask(i) => Value::Integer(i),
|
||||||
ControlValue::String(s) => Value::String(s),
|
ControlValue::String(s) => Value::String(s),
|
||||||
ControlValue::Boolean(t) => Value::Boolean(t),
|
ControlValue::Boolean(t) => Value::Boolean(t),
|
||||||
ControlValue::Binary(b) => Value::CompoundU8(b),
|
ControlValue::Binary(b) => Value::CompoundU8(b),
|
||||||
@@ -305,7 +316,9 @@ fn conv_control_value_to_v4l_value(control: ControlValue) -> Result<Value, Nokhw
|
|||||||
if let ControlValue::Integer(i) = &e {
|
if let ControlValue::Integer(i) = &e {
|
||||||
Value::Integer(*i)
|
Value::Integer(*i)
|
||||||
} else {
|
} 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 {
|
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,
|
_ => 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)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct V4L2Platform {}
|
pub struct V4L2Platform {}
|
||||||
|
|
||||||
impl PlatformTrait for V4L2Platform {
|
impl PlatformTrait for V4L2Platform {
|
||||||
@@ -338,25 +352,30 @@ impl PlatformTrait for V4L2Platform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn query(&mut self) -> NokhwaResult<Vec<CameraInformation>> {
|
fn query(&mut self) -> NokhwaResult<Vec<CameraInformation>> {
|
||||||
Ok(enum_devices().into_iter()
|
Ok(enum_devices()
|
||||||
|
.into_iter()
|
||||||
.map(|v4l_node| {
|
.map(|v4l_node| {
|
||||||
let index = v4l_node.index();
|
let index = v4l_node.index();
|
||||||
// open camera for capabilities. if we dont get any, dont return the camera
|
// open camera for capabilities. if we dont get any, dont return the camera
|
||||||
Device::new(index).map(|dev|
|
Device::new(index)
|
||||||
dev.query_caps().map(|caps| {
|
.map(|dev| {
|
||||||
index_capabilities_to_camera_info(index as u32, caps)
|
dev.query_caps()
|
||||||
}).ok()
|
.map(|caps| index_capabilities_to_camera_info(index as u32, caps))
|
||||||
).ok().flatten()
|
.ok()
|
||||||
}).flatten().collect::<Vec<_>>())
|
})
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&mut self, index: CameraIndex) -> NokhwaResult<Self::Camera> {
|
fn open(&mut self, index: CameraIndex) -> NokhwaResult<Self::Camera> {
|
||||||
let device = match &index {
|
let device = match &index {
|
||||||
CameraIndex::Index(i) => Device::new(*i as usize),
|
CameraIndex::Index(i) => Device::new(*i as usize),
|
||||||
CameraIndex::String(path) => Device::with_path(path)
|
CameraIndex::String(path) => Device::with_path(path),
|
||||||
}.map_err(|why| {
|
}
|
||||||
NokhwaError::OpenDeviceError(index.to_string(), why.to_string())
|
.map_err(|why| NokhwaError::OpenDeviceError(index.to_string(), why.to_string()))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut v4l2_camera = V4L2Camera {
|
let mut v4l2_camera = V4L2Camera {
|
||||||
device,
|
device,
|
||||||
@@ -384,85 +403,131 @@ impl Setting for V4L2Camera {
|
|||||||
fn enumerate_formats(&self) -> Result<Vec<CameraFormat>, NokhwaError> {
|
fn enumerate_formats(&self) -> Result<Vec<CameraFormat>, NokhwaError> {
|
||||||
let mut formats = vec![];
|
let mut formats = vec![];
|
||||||
|
|
||||||
for frame_format in self.device.enum_formats().map_err(|why| {
|
for frame_format in self
|
||||||
NokhwaError::GetPropertyError { property: "enum_formats".to_string(), error: why.to_string() }
|
.device
|
||||||
})?.into_iter().map(|desc| {
|
.enum_formats()
|
||||||
fourcc_to_frame_format(desc.fourcc)
|
.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(
|
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)?
|
||||||
frame_rates.into_iter().map(|frame_rate| {
|
.into_iter()
|
||||||
CameraFormat::new(resolution, frame_format, frame_rate)
|
.flat_map(|(resolution, frame_rates)| {
|
||||||
})
|
frame_rates.into_iter().map(|frame_rate| {
|
||||||
})
|
CameraFormat::new(resolution, frame_format, frame_rate)
|
||||||
|
})
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(formats)
|
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 fourcc = frame_format_to_fourcc(frame_format)?;
|
||||||
let resolutions = self.device.enum_framesizes(fourcc).map_err(|why| {
|
let resolutions = self
|
||||||
NokhwaError::GetPropertyError { property: "enum_framesizes".to_string(), error: why.to_string() }
|
.device
|
||||||
})?.into_iter()
|
.enum_framesizes(fourcc)
|
||||||
.flat_map(|frame_size| {
|
.map_err(|why| NokhwaError::GetPropertyError {
|
||||||
frame_size.size.to_discrete()
|
property: "enum_framesizes".to_string(),
|
||||||
}).map(|discrete| { Resolution::new(discrete.width, discrete.height)
|
error: why.to_string(),
|
||||||
}).collect::<Vec<Resolution>>();
|
})?
|
||||||
|
.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()
|
let v4l2_frame_intervals = resolutions
|
||||||
.map(|resolution| (*resolution, self.device.enum_frameintervals(fourcc, resolution.width(), resolution.height())))
|
.iter()
|
||||||
|
.map(|resolution| {
|
||||||
|
(
|
||||||
|
*resolution,
|
||||||
|
self.device.enum_frameintervals(
|
||||||
|
fourcc,
|
||||||
|
resolution.width(),
|
||||||
|
resolution.height(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<Result<Vec<(Resolution, Vec<FrameInterval>)>, std::io::Error>>()
|
.collect::<Result<Vec<(Resolution, Vec<FrameInterval>)>, std::io::Error>>()
|
||||||
.map_err(|why| {
|
.map_err(|why| NokhwaError::GetPropertyError {
|
||||||
NokhwaError::GetPropertyError { property: "enum_frameintervals".to_string(), error: why.to_string() }
|
property: "enum_frameintervals".to_string(),
|
||||||
})?;
|
error: why.to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(v4l2_frame_intervals.into_iter().flatten().flat_map(|(resolution, interval)| {
|
Ok(v4l2_frame_intervals
|
||||||
match interval.interval {
|
.into_iter()
|
||||||
FrameIntervalEnum::Discrete(discrete) => {
|
.flatten()
|
||||||
NonZeroI32::new(discrete.denominator as i32).map(|denominator| {
|
.flat_map(|(resolution, interval)| {
|
||||||
(resolution, vec![FrameRate::new(discrete.numerator as i32, denominator)])
|
match interval.interval {
|
||||||
})
|
FrameIntervalEnum::Discrete(discrete) => {
|
||||||
}
|
NonZeroI32::new(discrete.denominator as i32).map(|denominator| {
|
||||||
FrameIntervalEnum::Stepwise(stepwise) => {
|
(
|
||||||
// we have to do this ourselves
|
resolution,
|
||||||
|
vec![FrameRate::new(discrete.numerator as i32, denominator)],
|
||||||
// no logic to handle different or zero demoninator
|
)
|
||||||
if (stepwise.step.denominator != stepwise.max.denominator) || (stepwise.step.denominator != stepwise.min.denominator) {
|
})
|
||||||
return None
|
|
||||||
}
|
}
|
||||||
|
FrameIntervalEnum::Stepwise(stepwise) => {
|
||||||
|
// we have to do this ourselves
|
||||||
|
|
||||||
let min = stepwise.min.numerator as i32;
|
// no logic to handle different or zero demoninator
|
||||||
let max = stepwise.max.numerator as i32;
|
if (stepwise.step.denominator != stepwise.max.denominator)
|
||||||
let step = stepwise.step.numerator as i32;
|
|| (stepwise.step.denominator != stepwise.min.denominator)
|
||||||
let denominator = stepwise.step.denominator as i32;
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
NonZeroI32::new(denominator).map(|denominator| {
|
let min = stepwise.min.numerator as i32;
|
||||||
(resolution, (min..max).step_by(step as usize).map(|numerator| {
|
let max = stepwise.max.numerator as i32;
|
||||||
FrameRate::new(numerator, denominator)
|
let step = stepwise.step.numerator as i32;
|
||||||
}).collect::<Vec<FrameRate>>())
|
let denominator = stepwise.step.denominator as i32;
|
||||||
})
|
|
||||||
|
NonZeroI32::new(denominator).map(|denominator| {
|
||||||
|
(
|
||||||
|
resolution,
|
||||||
|
(min..max)
|
||||||
|
.step_by(step as usize)
|
||||||
|
.map(|numerator| FrameRate::new(numerator, denominator))
|
||||||
|
.collect::<Vec<FrameRate>>(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}).flatten().collect::<HashMap<Resolution, Vec<FrameRate>>>())
|
.flatten()
|
||||||
|
.collect::<HashMap<Resolution, Vec<FrameRate>>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError> {
|
fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError> {
|
||||||
let fourcc = frame_format_to_fourcc(*camera_format.format())?;
|
let fourcc = frame_format_to_fourcc(*camera_format.format())?;
|
||||||
self.device.set_format(
|
self.device
|
||||||
&Format::new(camera_format.width(), camera_format.height(), fourcc)
|
.set_format(&Format::new(
|
||||||
).map_err(|why| NokhwaError::SetPropertyError {
|
camera_format.width(),
|
||||||
property: "set_format".to_string(),
|
camera_format.height(),
|
||||||
value: format!("format: {camera_format} fourcc: {fourcc}"),
|
fourcc,
|
||||||
error: why.to_string(),
|
))
|
||||||
})?;
|
.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| {
|
property: "set_format".to_string(),
|
||||||
NokhwaError::SetPropertyError {
|
value: format!("format: {camera_format} fourcc: {fourcc}"),
|
||||||
|
error: why.to_string(),
|
||||||
|
})?;
|
||||||
|
self.device
|
||||||
|
.set_params(&Parameters::new(Fraction::new(
|
||||||
|
*camera_format.frame_rate().numerator() as u32,
|
||||||
|
*camera_format.frame_rate().denominator() as u32,
|
||||||
|
)))
|
||||||
|
.map_err(|why| NokhwaError::SetPropertyError {
|
||||||
property: "set_params".to_string(),
|
property: "set_params".to_string(),
|
||||||
value: format!("{}", camera_format.frame_rate()),
|
value: format!("{}", camera_format.frame_rate()),
|
||||||
error: why.to_string(),
|
error: why.to_string(),
|
||||||
}
|
})?;
|
||||||
})?;
|
|
||||||
self.camera_format = Some(camera_format);
|
self.camera_format = Some(camera_format);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -487,60 +552,91 @@ impl Setting for V4L2Camera {
|
|||||||
self.controls.description(id)
|
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)? {
|
if !self.controls.validate(property, &value)? {
|
||||||
return Err(NokhwaError::SetPropertyError {
|
return Err(NokhwaError::SetPropertyError {
|
||||||
property: property.to_string(),
|
property: property.to_string(),
|
||||||
value: value.to_string(),
|
value: value.to_string(),
|
||||||
error: "failed to validate".to_string(),
|
error: "failed to validate".to_string(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
let cid = control_id_to_cid(*property)?;
|
let cid = control_id_to_cid(*property)?;
|
||||||
let v4l_value = conv_control_value_to_v4l_value(value.clone())?;
|
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
|
||||||
Err(NokhwaError::SetPropertyError {
|
.set_control(Control {
|
||||||
property: cid.to_string(),
|
id: cid,
|
||||||
value: value.to_string(),
|
value: v4l_value,
|
||||||
error: why.to_string(),
|
|
||||||
})
|
})
|
||||||
})?;
|
.map_err(|why| {
|
||||||
|
Err(NokhwaError::SetPropertyError {
|
||||||
|
property: cid.to_string(),
|
||||||
|
value: value.to_string(),
|
||||||
|
error: why.to_string(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
self.controls.set_control_value(property, value)?;
|
self.controls.set_control_value(property, value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_controls(&mut self) -> Result<(), NokhwaError> {
|
fn refresh_controls(&mut self) -> Result<(), NokhwaError> {
|
||||||
let descriptions = self.device.query_controls().map_err(|why| {
|
let descriptions = self
|
||||||
NokhwaError::GetPropertyError { property: "query_controls".to_string(), error: why.to_string() }
|
.device
|
||||||
})?.into_iter().map(|description| {
|
.query_controls()
|
||||||
let id = cid_to_control_id(description.id);
|
.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| {
|
convert_description_to_ctrl_body(description).map(|body| (id, 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)| {
|
let values = descriptions
|
||||||
self.device.control(cid).map(|v| (id, v))
|
.keys()
|
||||||
}).map(|(id, value)| {
|
.into_iter()
|
||||||
(id, match value.value {
|
.copied()
|
||||||
Value::None => ControlValue::Null,
|
.flat_map(|k| control_id_to_cid(k).map(|cid| (k, cid)))
|
||||||
Value::Integer(i) => ControlValue::Integer(i),
|
.flat_map(|(id, cid)| self.device.control(cid).map(|v| (id, v)))
|
||||||
Value::Boolean(b) => ControlValue::Boolean(b),
|
.map(|(id, value)| {
|
||||||
Value::String(s) => ControlValue::String(s),
|
(
|
||||||
Value::CompoundU8(bin) | Value::CompoundPtr(bin) => ControlValue::Binary(bin),
|
id,
|
||||||
Value::CompoundU16(u) | Value::CompoundU32(u) => ControlValue::Array(
|
match value.value {
|
||||||
u.into_iter().map(|u| ControlValue::Integer(u as i64)).collect()
|
Value::None => ControlValue::Null,
|
||||||
),
|
Value::Integer(i) => ControlValue::Integer(i),
|
||||||
|
Value::Boolean(b) => ControlValue::Boolean(b),
|
||||||
|
Value::String(s) => ControlValue::String(s),
|
||||||
|
Value::CompoundU8(bin) | Value::CompoundPtr(bin) => {
|
||||||
|
ControlValue::Binary(bin)
|
||||||
|
}
|
||||||
|
Value::CompoundU16(u) | Value::CompoundU32(u) => ControlValue::Array(
|
||||||
|
u.into_iter()
|
||||||
|
.map(|u| ControlValue::Integer(u as i64))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}).collect::<HashMap<ControlId, ControlValue>>();
|
.collect::<HashMap<ControlId, ControlValue>>();
|
||||||
|
|
||||||
match Controls::new(descriptions, values) {
|
match Controls::new(descriptions, values) {
|
||||||
Some(c) => { self.controls = c; }
|
Some(c) => {
|
||||||
None => return Err(NokhwaError::SetPropertyError {
|
self.controls = c;
|
||||||
property: "control".to_string(),
|
}
|
||||||
value: format!("{:?} {:?}", descriptions, values),
|
None => {
|
||||||
error: "Failed to convert to control".to_string(),
|
return Err(NokhwaError::SetPropertyError {
|
||||||
})
|
property: "control".to_string(),
|
||||||
|
value: format!("{:?} {:?}", descriptions, values),
|
||||||
|
error: "Failed to convert to control".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -559,16 +655,21 @@ impl Drop for V4L2Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Capture for V4L2Camera {
|
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 {
|
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 stream_config = stream_configuration.unwrap_or_default();
|
||||||
|
|
||||||
let format = match self.camera_format {
|
let format = match self.camera_format {
|
||||||
Some(fmt) => fmt,
|
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);
|
let (control, ctrl_recv) = bounded(1);
|
||||||
@@ -579,64 +680,79 @@ impl Capture for V4L2Camera {
|
|||||||
|
|
||||||
let control = Arc::new(control);
|
let control = Arc::new(control);
|
||||||
|
|
||||||
let mut mmap_stream = MmapStream::new(&self.device, v4l::buffer::Type::VideoCapture).map_err(|why| {
|
let mut mmap_stream = MmapStream::new(&self.device, v4l::buffer::Type::VideoCapture)
|
||||||
return NokhwaError::OpenStreamError(why.to_string())
|
.map_err(|why| return NokhwaError::OpenStreamError(why.to_string()))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let stream_handle = StreamHandle::new(receiver, control.clone(), stream_config, format);
|
let stream_handle = StreamHandle::new(receiver, control.clone(), stream_config, format);
|
||||||
|
|
||||||
let thread = std::thread::spawn(move || {
|
let thread = std::thread::spawn(move || loop {
|
||||||
|
if ctrl_recv.is_disconnected() || sender.is_disconnected() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Ok(_) = ctrl_recv.try_recv() {
|
||||||
|
let _ = sender.send(Event::Closed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
match mmap_stream.next() {
|
||||||
if ctrl_recv.is_disconnected() || sender.is_disconnected() {
|
Ok((data, meta)) => {
|
||||||
return;
|
let data = Cow::Owned(data.to_owned());
|
||||||
|
let mut metadata = Metadata::new();
|
||||||
|
|
||||||
|
metadata.insert(
|
||||||
|
CompactString::from("flags"),
|
||||||
|
ControlValue::BitMask(meta.flags.bits() as u64),
|
||||||
|
);
|
||||||
|
metadata.insert(
|
||||||
|
CompactString::from("time_secs"),
|
||||||
|
ControlValue::Integer(meta.timestamp.sec),
|
||||||
|
);
|
||||||
|
metadata.insert(
|
||||||
|
CompactString::from("time_usecs"),
|
||||||
|
ControlValue::Integer(meta.timestamp.usec),
|
||||||
|
);
|
||||||
|
metadata.insert(
|
||||||
|
CompactString::from("size"),
|
||||||
|
ControlValue::Integer(meta.bytesused as i64),
|
||||||
|
);
|
||||||
|
metadata.insert(
|
||||||
|
CompactString::from("sequence"),
|
||||||
|
ControlValue::Integer(meta.sequence as i64),
|
||||||
|
);
|
||||||
|
metadata.insert(
|
||||||
|
CompactString::from("field"),
|
||||||
|
ControlValue::Integer(meta.field as i64),
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = sender.send(Event::NewFrame(FrameBuffer::new(data, Some(metadata))));
|
||||||
}
|
}
|
||||||
if let Ok(_) = ctrl_recv.try_recv() {
|
Err(why) => {
|
||||||
let _ = sender.send(Event::Closed);
|
let _ = sender.send(Event::Error(Box::new(why)));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match mmap_stream.next() {
|
|
||||||
Ok((data, meta)) => {
|
|
||||||
let data = Cow::Owned(data.to_owned());
|
|
||||||
let mut metadata = Metadata::new();
|
|
||||||
|
|
||||||
metadata.insert(CompactString::from("flags"), ControlValue::BitMask(meta.flags.bits() as u64));
|
|
||||||
metadata.insert(CompactString::from("time_secs"), ControlValue::Integer(meta.timestamp.sec));
|
|
||||||
metadata.insert(CompactString::from("time_usecs"), ControlValue::Integer(meta.timestamp.usec));
|
|
||||||
metadata.insert(CompactString::from("size"), ControlValue::Integer(meta.bytesused as i64));
|
|
||||||
metadata.insert(CompactString::from("sequence"), ControlValue::Integer(meta.sequence as i64));
|
|
||||||
metadata.insert(CompactString::from("field"), ControlValue::Integer(meta.field as i64));
|
|
||||||
|
|
||||||
let _ = sender.send(Event::NewFrame(FrameBuffer::new(data, Some(metadata))));
|
|
||||||
}
|
|
||||||
Err(why) => {
|
|
||||||
let _ = sender.send(Event::Error(Box::new(why)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.stream = Some(
|
self.stream = Some(V4L2Stream { thread, control });
|
||||||
V4L2Stream {
|
|
||||||
thread,
|
|
||||||
control,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(stream_handle)
|
Ok(stream_handle)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_stream(&mut self) -> Result<(), NokhwaError> {
|
fn close_stream(&mut self) -> Result<(), NokhwaError> {
|
||||||
let mut stream = match std::mem::take(&mut self.stream) {
|
let mut stream = match std::mem::take(&mut self.stream) {
|
||||||
Some(s) => s,
|
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(());
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
// TODO
|
// TODO
|
||||||
|
|||||||
@@ -222,11 +222,13 @@ mod internal {
|
|||||||
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
||||||
};
|
};
|
||||||
use flume::{Receiver, Sender};
|
use flume::{Receiver, Sender};
|
||||||
|
use nokhwa_core::control::{
|
||||||
|
CameraControl, ControlValue, ControlValueDescription, KnownCameraControl,
|
||||||
|
};
|
||||||
use nokhwa_core::{
|
use nokhwa_core::{
|
||||||
error::NokhwaError,
|
error::NokhwaError,
|
||||||
types::{
|
types::{
|
||||||
ApiBackend, CameraFormat, CameraIndex, CameraInformation,
|
ApiBackend, CameraFormat, CameraIndex, CameraInformation, FrameFormat,
|
||||||
FrameFormat,
|
|
||||||
KnownCameraControlFlag, Resolution,
|
KnownCameraControlFlag, Resolution,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -246,7 +248,6 @@ mod internal {
|
|||||||
ffi::{c_float, c_void, CStr},
|
ffi::{c_float, c_void, CStr},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use nokhwa_core::control::{CameraControl, ControlValueDescription, ControlValue, KnownCameraControl};
|
|
||||||
|
|
||||||
const UTF8_ENCODING: usize = 4;
|
const UTF8_ENCODING: usize = 4;
|
||||||
type CGFloat = c_float;
|
type CGFloat = c_float;
|
||||||
|
|||||||
@@ -29,10 +29,13 @@
|
|||||||
|
|
||||||
#[cfg(all(windows, not(feature = "docs-only")))]
|
#[cfg(all(windows, not(feature = "docs-only")))]
|
||||||
pub mod wmf {
|
pub mod wmf {
|
||||||
|
use nokhwa_core::control::{
|
||||||
|
CameraControl, ControlValue, ControlValueDescription, KnownCameraControl,
|
||||||
|
};
|
||||||
use nokhwa_core::error::NokhwaError;
|
use nokhwa_core::error::NokhwaError;
|
||||||
use nokhwa_core::types::{
|
use nokhwa_core::types::{
|
||||||
ApiBackend, CameraFormat, CameraIndex, CameraInformation,
|
ApiBackend, CameraFormat, CameraIndex, CameraInformation, FrameFormat,
|
||||||
FrameFormat, KnownCameraControlFlag, Resolution,
|
KnownCameraControlFlag, Resolution,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
@@ -46,7 +49,6 @@ pub mod wmf {
|
|||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use nokhwa_core::control::{CameraControl, ControlValueDescription, ControlValue, KnownCameraControl};
|
|
||||||
use windows::Win32::Media::DirectShow::{CameraControl_Flags_Auto, CameraControl_Flags_Manual};
|
use windows::Win32::Media::DirectShow::{CameraControl_Flags_Auto, CameraControl_Flags_Manual};
|
||||||
use windows::Win32::Media::MediaFoundation::{
|
use windows::Win32::Media::MediaFoundation::{
|
||||||
MFCreateSample, MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
MFCreateSample, MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
||||||
@@ -66,10 +68,9 @@ pub mod wmf {
|
|||||||
KernelStreaming::GUID_NULL,
|
KernelStreaming::GUID_NULL,
|
||||||
MediaFoundation::{
|
MediaFoundation::{
|
||||||
IMFActivate, IMFAttributes, IMFMediaSource, IMFSample, IMFSourceReader,
|
IMFActivate, IMFAttributes, IMFMediaSource, IMFSample, IMFSourceReader,
|
||||||
MFCreateAttributes, MFCreateSourceReaderFromMediaSource,
|
MFCreateAttributes, MFCreateSourceReaderFromMediaSource, MFEnumDeviceSources,
|
||||||
MFEnumDeviceSources, MFShutdown, MFStartup,
|
MFShutdown, MFStartup, MFSTARTUP_NOSOCKET, MF_API_VERSION,
|
||||||
MFSTARTUP_NOSOCKET, MF_API_VERSION, MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
|
MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
|
||||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID,
|
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID,
|
||||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, MF_MT_FRAME_RATE,
|
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,
|
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
|
// return early if we have no devices connected
|
||||||
if count >= 0 {
|
if count >= 0 {
|
||||||
return Ok(device_list)
|
return Ok(device_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe { from_raw_parts(unused_mf_activate.assume_init(), count as usize) }
|
unsafe { from_raw_parts(unused_mf_activate.assume_init(), count as usize) }
|
||||||
@@ -629,7 +630,7 @@ pub mod wmf {
|
|||||||
None => {
|
None => {
|
||||||
index += 1;
|
index += 1;
|
||||||
continue;
|
continue;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for frame_rate in framerate_list {
|
for frame_rate in framerate_list {
|
||||||
@@ -999,7 +1000,7 @@ pub mod wmf {
|
|||||||
// Otherwise, constructing IMFMediaType from scratch can sometimes fail due to not exactly matching.
|
// Otherwise, constructing IMFMediaType from scratch can sometimes fail due to not exactly matching.
|
||||||
// Therefore, we search for the first media_type that matches and also works correctly.
|
// Therefore, we search for the first media_type that matches and also works correctly.
|
||||||
|
|
||||||
let mut last_error : Option<NokhwaError> = None;
|
let mut last_error: Option<NokhwaError> = None;
|
||||||
|
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
while let Ok(media_type) = unsafe {
|
while let Ok(media_type) = unsafe {
|
||||||
@@ -1040,7 +1041,11 @@ pub mod wmf {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Resolution { width_x: width, height_y: height }) != format.resolution() {
|
if (Resolution {
|
||||||
|
width_x: width,
|
||||||
|
height_y: height,
|
||||||
|
}) != format.resolution()
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1093,7 +1098,7 @@ pub mod wmf {
|
|||||||
self.device_format = format;
|
self.device_format = format;
|
||||||
self.format_refreshed()?;
|
self.format_refreshed()?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
}
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
last_error = Some(NokhwaError::SetPropertyError {
|
last_error = Some(NokhwaError::SetPropertyError {
|
||||||
property: "MEDIA_FOUNDATION_FIRST_VIDEO_STREAM".to_string(),
|
property: "MEDIA_FOUNDATION_FIRST_VIDEO_STREAM".to_string(),
|
||||||
@@ -1240,12 +1245,10 @@ pub mod wmf {
|
|||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[allow(clippy::must_use_candidate)]
|
#[allow(clippy::must_use_candidate)]
|
||||||
pub mod wmf {
|
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::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> {
|
pub fn initialize_mf() -> Result<(), NokhwaError> {
|
||||||
Err(NokhwaError::NotImplementedError(
|
Err(NokhwaError::NotImplementedError(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ repository = "https://github.com/l1npengtul/nokhwa"
|
|||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
serialize = ["serde"]
|
serialize = ["serde"]
|
||||||
wgpu-types = ["wgpu"]
|
wgpu = ["wgpu-types"]
|
||||||
opencv-mat = ["opencv", "opencv/clang-runtime"]
|
opencv-mat = ["opencv", "opencv/clang-runtime"]
|
||||||
docs-features = ["serialize", "wgpu-types"]
|
docs-features = ["serialize", "wgpu-types"]
|
||||||
async = ["async-trait", "flume/async", "futures-core"]
|
async = ["async-trait", "flume/async", "futures-core"]
|
||||||
@@ -27,6 +27,8 @@ num-traits = "0.2"
|
|||||||
ordered-float = "5"
|
ordered-float = "5"
|
||||||
typed-builder = "0.21"
|
typed-builder = "0.21"
|
||||||
compact_str = "0.9"
|
compact_str = "0.9"
|
||||||
|
bytemuck = "1.23"
|
||||||
|
smallmap = "1.4"
|
||||||
|
|
||||||
[dependencies.num-rational]
|
[dependencies.num-rational]
|
||||||
version = "0.4"
|
version = "0.4"
|
||||||
@@ -37,17 +39,12 @@ features = ["serde", "std"]
|
|||||||
version = "0.25"
|
version = "0.25"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.small-map]
|
|
||||||
version = "0.1.3"
|
|
||||||
default-features = false
|
|
||||||
features = ["fxhash"]
|
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.wgpu]
|
[dependencies.wgpu-types]
|
||||||
version = "25"
|
version = "25"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
use crate::control::{ControlDescription, ControlId, ControlValue, Controls};
|
use crate::control::{ControlDescription, ControlId, ControlValue};
|
||||||
use crate::error::NokhwaError;
|
use crate::error::NokhwaError;
|
||||||
use crate::frame_format::FrameFormat;
|
use crate::frame_format::FrameFormat;
|
||||||
use crate::stream::{StreamConfiguration, StreamHandle};
|
use crate::stream::{StreamConfiguration, StreamHandle};
|
||||||
use crate::types::{CameraFormat, FrameRate, Resolution};
|
use crate::types::{CameraFormat, FrameRate, Resolution};
|
||||||
use std::collections::hash_map::{Keys, Values};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hash_map::{Keys, Values};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub trait Setting {
|
pub trait Setting {
|
||||||
|
/// # Errors
|
||||||
|
/// Will error on
|
||||||
fn enumerate_formats(&self) -> Result<Vec<CameraFormat>, NokhwaError>;
|
fn enumerate_formats(&self) -> Result<Vec<CameraFormat>, NokhwaError>;
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
/// Will error on
|
||||||
fn enumerate_resolution_and_frame_rates(
|
fn enumerate_resolution_and_frame_rates(
|
||||||
&self,
|
&self,
|
||||||
frame_format: FrameFormat,
|
frame_format: FrameFormat,
|
||||||
) -> Result<HashMap<Resolution, Vec<FrameRate>>, NokhwaError>;
|
) -> Result<HashMap<Resolution, Vec<FrameRate>>, NokhwaError>;
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
/// Will error on
|
||||||
fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError>;
|
fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError>;
|
||||||
|
|
||||||
fn control_ids(&self) -> Keys<ControlId, ControlDescription>;
|
fn control_ids(&self) -> Keys<ControlId, ControlDescription>;
|
||||||
@@ -27,9 +33,13 @@ pub trait Setting {
|
|||||||
|
|
||||||
fn control_description(&self, id: &ControlId) -> Option<&ControlDescription>;
|
fn control_description(&self, id: &ControlId) -> Option<&ControlDescription>;
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
/// Will error on
|
||||||
fn set_control(&mut self, property: &ControlId, value: ControlValue)
|
fn set_control(&mut self, property: &ControlId, value: ControlValue)
|
||||||
-> Result<(), NokhwaError>;
|
-> Result<(), NokhwaError>;
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
/// Will error on
|
||||||
fn refresh_controls(&mut self) -> Result<(), NokhwaError>;
|
fn refresh_controls(&mut self) -> Result<(), NokhwaError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,9 +53,7 @@ pub trait AsyncSetting {
|
|||||||
) -> Result<HashMap<Resolution, Vec<FrameRate>>, NokhwaError>;
|
) -> Result<HashMap<Resolution, Vec<FrameRate>>, NokhwaError>;
|
||||||
|
|
||||||
async fn set_format_async(&self, camera_format: CameraFormat) -> Result<(), NokhwaError>;
|
async fn set_format_async(&self, camera_format: CameraFormat) -> Result<(), NokhwaError>;
|
||||||
|
|
||||||
async fn properties_async(&self) -> &Controls;
|
|
||||||
|
|
||||||
async fn set_property_async(
|
async fn set_property_async(
|
||||||
&mut self,
|
&mut self,
|
||||||
property: &ControlId,
|
property: &ControlId,
|
||||||
@@ -55,15 +63,25 @@ pub trait AsyncSetting {
|
|||||||
|
|
||||||
pub trait Capture {
|
pub trait Capture {
|
||||||
/// Implementations MUST guarantee that there can only ever be one stream open at once.
|
/// 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.
|
// Implementations MUST be multi-close tolerant.
|
||||||
|
/// # Errors
|
||||||
|
/// Errors are driver specific
|
||||||
fn close_stream(&mut self) -> Result<(), NokhwaError>;
|
fn close_stream(&mut self) -> Result<(), NokhwaError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
pub trait AsyncStream {
|
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>;
|
async fn close_stream_async(&mut self) -> Result<(), NokhwaError>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use crate::error::NokhwaError;
|
||||||
|
use crate::frame_format::FrameFormat;
|
||||||
|
use crate::types::CameraFormat;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
pub trait Codec {
|
||||||
|
type Config: Clone + Debug;
|
||||||
|
|
||||||
|
type Input<'a>;
|
||||||
|
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
type WrittenMeta: Clone + Debug;
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
/// Errors are decoder specific.
|
||||||
|
fn allowed_formats(&self) -> Result<&[FrameFormat], NokhwaError>;
|
||||||
|
|
||||||
|
fn config(&self) -> &Self::Config;
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
/// Errors are decoder specific.
|
||||||
|
fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError>;
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
/// Errors are decoder specific.
|
||||||
|
fn send_item(&mut self, input: Self::Input<'_>) -> Result<(), NokhwaError>;
|
||||||
|
|
||||||
|
fn receive_decoded_item(
|
||||||
|
&mut self,
|
||||||
|
writing_output: &mut Self::Output,
|
||||||
|
) -> Result<Self::WrittenMeta, NokhwaError>;
|
||||||
|
|
||||||
|
fn preferred_buffer_min_size(
|
||||||
|
&mut self,
|
||||||
|
format: &Option<CameraFormat>,
|
||||||
|
) -> Result<Option<usize>, NokhwaError>;
|
||||||
|
|
||||||
|
fn deinitialize(&mut self) -> Result<(), NokhwaError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub trait CodecAsync: Codec {
|
||||||
|
|
||||||
|
async fn allowed_formats_async(&self) -> Result<&[FrameFormat], NokhwaError> {
|
||||||
|
self.allowed_formats()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_format_async(&self, format: CameraFormat) -> Result<(), NokhwaError>;
|
||||||
|
|
||||||
|
async fn config_async(&self) -> &Self::Config {
|
||||||
|
self.config()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_config_async(&mut self, config: Self::Config) -> Result<(), NokhwaError> {
|
||||||
|
self.set_config(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_item_async(&mut self, input: &Self::Input) -> Result<(), NokhwaError>;
|
||||||
|
|
||||||
|
fn receive_decoded_item_async(
|
||||||
|
&mut self,
|
||||||
|
writing_output: &mut Self::Output,
|
||||||
|
) -> Result<Option<usize>, NokhwaError>;
|
||||||
|
|
||||||
|
async fn deinitialize_async(&mut self) -> Result<(), NokhwaError> {
|
||||||
|
self.deinitialize()
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
-38
@@ -1,5 +1,6 @@
|
|||||||
use crate::error::{NokhwaError, NokhwaResult};
|
use crate::error::{NokhwaError, NokhwaResult};
|
||||||
use crate::ranges::{Range, ValidatableRange};
|
use crate::ranges::{Range, ValidatableRange};
|
||||||
|
use compact_str::CompactString;
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use std::collections::hash_map::{Keys, Values};
|
use std::collections::hash_map::{Keys, Values};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
@@ -60,11 +61,11 @@ pub struct Controls {
|
|||||||
|
|
||||||
impl Controls {
|
impl Controls {
|
||||||
/// INVARIANTS: All `ControlId` in `device_values` MUST exist in `device_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_controls: HashMap<ControlId, ControlDescription>,
|
||||||
device_values: HashMap<ControlId, ControlValue>,
|
device_values: HashMap<ControlId, ControlValue>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
for (id, value) in device_values.iter() {
|
for (id, value) in &device_values {
|
||||||
if let Some(description) = device_controls.get(id) {
|
if let Some(description) = device_controls.get(id) {
|
||||||
if !description.validate(value) {
|
if !description.validate(value) {
|
||||||
return None;
|
return None;
|
||||||
@@ -78,11 +79,11 @@ impl Controls {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty() -> Self {
|
#[must_use] pub fn empty() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unchecked_new(
|
#[must_use] pub fn unchecked_new(
|
||||||
device_controls: HashMap<ControlId, ControlDescription>,
|
device_controls: HashMap<ControlId, ControlDescription>,
|
||||||
device_values: HashMap<ControlId, ControlValue>,
|
device_values: HashMap<ControlId, ControlValue>,
|
||||||
) -> Self {
|
) -> 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)
|
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)
|
self.values.get(control_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn descriptions(&self) -> Values<ControlId, ControlDescription> {
|
#[must_use] pub fn descriptions(&self) -> Values<ControlId, ControlDescription> {
|
||||||
self.descriptions.values()
|
self.descriptions.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn values(&self) -> Values<ControlId, ControlValue> {
|
#[must_use] pub fn values(&self) -> Values<ControlId, ControlValue> {
|
||||||
self.values.values()
|
self.values.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ids(&self) -> Keys<ControlId, ControlDescription> {
|
#[must_use] pub fn ids(&self) -> Keys<ControlId, ControlDescription> {
|
||||||
self.descriptions.keys()
|
self.descriptions.keys()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self, control_id: &ControlId, value: &ControlValue) -> Result<bool, NokhwaError> {
|
pub fn validate(
|
||||||
let description = match self.descriptions.get(control_id) {
|
&self,
|
||||||
Some(desc) => desc,
|
control_id: &ControlId,
|
||||||
None => return Err(NokhwaError::GetPropertyError {
|
value: &ControlValue,
|
||||||
property: control_id.to_string(),
|
) -> Result<bool, NokhwaError> {
|
||||||
error: "ID Not Found".to_string(),
|
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 {
|
return Err(NokhwaError::GetPropertyError {
|
||||||
property: control_id.to_string(),
|
property: control_id.to_string(),
|
||||||
error: "ID Not Found".to_string(),
|
error: "ID Not Found".to_string(),
|
||||||
@@ -159,7 +163,7 @@ pub struct ControlDescription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ControlDescription {
|
impl ControlDescription {
|
||||||
pub fn new(
|
#[must_use] pub fn new(
|
||||||
control_flags: HashSet<ControlFlags>,
|
control_flags: HashSet<ControlFlags>,
|
||||||
control_value_descriptor: ControlValueDescriptor,
|
control_value_descriptor: ControlValueDescriptor,
|
||||||
default_value: Option<ControlValue>,
|
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_flags: HashSet<ControlFlags>,
|
||||||
control_value_descriptor: ControlValueDescriptor,
|
control_value_descriptor: ControlValueDescriptor,
|
||||||
default_value: Option<ControlValue>,
|
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
|
&self.flags
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn descriptor(&self) -> &ControlValueDescriptor {
|
#[must_use] pub fn descriptor(&self) -> &ControlValueDescriptor {
|
||||||
&self.descriptor
|
&self.descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_value(&self) -> &Option<ControlValue> {
|
#[must_use] pub fn default_value(&self) -> &Option<ControlValue> {
|
||||||
&self.default_value
|
&self.default_value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +213,7 @@ impl ControlDescription {
|
|||||||
self.flags.remove(&flag)
|
self.flags.remove(&flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self, value: &ControlValue) -> bool {
|
#[must_use] pub fn validate(&self, value: &ControlValue) -> bool {
|
||||||
self.descriptor.validate(value)
|
self.descriptor.validate(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,11 +237,11 @@ pub enum ControlValueDescriptor {
|
|||||||
Null,
|
Null,
|
||||||
Integer(Range<i64>),
|
Integer(Range<i64>),
|
||||||
BitMask,
|
BitMask,
|
||||||
Float(Range<f64>),
|
Float(Range<OrderedFloat<f64>>),
|
||||||
String,
|
String,
|
||||||
Boolean,
|
Boolean,
|
||||||
// Array of any values of singular type
|
// Array of any values of singular type
|
||||||
Array(ControlValueDescriptor),
|
Array(Box<ControlValueDescriptor>),
|
||||||
// Menu(Enum) of valid choices
|
// Menu(Enum) of valid choices
|
||||||
// The keys are valid choices,
|
// The keys are valid choices,
|
||||||
// the values represent what the choice is (usually a string or int).
|
// the values represent what the choice is (usually a string or int).
|
||||||
@@ -257,7 +261,7 @@ pub enum ControlValueDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ControlValueDescriptor {
|
impl ControlValueDescriptor {
|
||||||
pub fn validate(&self, value: &ControlValue) -> bool {
|
#[must_use] pub fn validate(&self, value: &ControlValue) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ControlValueDescriptor::Null => {
|
ControlValueDescriptor::Null => {
|
||||||
if let &ControlValue::Null = value {
|
if let &ControlValue::Null = value {
|
||||||
@@ -291,12 +295,12 @@ impl ControlValueDescriptor {
|
|||||||
}
|
}
|
||||||
ControlValueDescriptor::Array(arr) => {
|
ControlValueDescriptor::Array(arr) => {
|
||||||
if let &ControlValue::Array(_) = value {
|
if let &ControlValue::Array(_) = value {
|
||||||
return arr.is_valid_value(value);
|
return arr.validate(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlValueDescriptor::Binary(size_limits) => {
|
ControlValueDescriptor::Binary(size_limits) => {
|
||||||
if let ControlValue::Binary(bin) = value {
|
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) => {
|
ControlValueDescriptor::Menu(choices) => {
|
||||||
@@ -328,7 +332,7 @@ pub enum ControlValue {
|
|||||||
Integer(i64),
|
Integer(i64),
|
||||||
BitMask(u64),
|
BitMask(u64),
|
||||||
Float(OrderedFloat<f64>),
|
Float(OrderedFloat<f64>),
|
||||||
String(String),
|
String(CompactString),
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
Array(Vec<ControlValue>),
|
Array(Vec<ControlValue>),
|
||||||
Binary(Vec<u8>),
|
Binary(Vec<u8>),
|
||||||
@@ -338,9 +342,8 @@ pub enum ControlValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ControlValue {
|
impl ControlValue {
|
||||||
pub fn is_primitive(&self) -> bool {
|
#[must_use] pub fn is_primitive(&self) -> bool {
|
||||||
match self {
|
matches!(self, ControlValue::Null
|
||||||
ControlValue::Null
|
|
||||||
| ControlValue::Integer(_)
|
| ControlValue::Integer(_)
|
||||||
| ControlValue::BitMask(_)
|
| ControlValue::BitMask(_)
|
||||||
| ControlValue::Float(_)
|
| ControlValue::Float(_)
|
||||||
@@ -348,9 +351,7 @@ impl ControlValue {
|
|||||||
| ControlValue::Boolean(_)
|
| ControlValue::Boolean(_)
|
||||||
| ControlValue::Binary(_)
|
| ControlValue::Binary(_)
|
||||||
| ControlValue::Area { .. }
|
| ControlValue::Area { .. }
|
||||||
| ControlValue::Orientation(_) => true,
|
| ControlValue::Orientation(_))
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn primitive_same_type(&self, other: &ControlValuePrimitive) -> bool {
|
// pub fn primitive_same_type(&self, other: &ControlValuePrimitive) -> bool {
|
||||||
@@ -369,7 +370,7 @@ impl ControlValue {
|
|||||||
// false
|
// false
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn same_type(&self, other: &ControlValue) -> bool {
|
#[must_use] pub fn same_type(&self, other: &ControlValue) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ControlValue::Null => {
|
ControlValue::Null => {
|
||||||
if let ControlValue::Null = other {
|
if let ControlValue::Null = other {
|
||||||
@@ -426,7 +427,6 @@ impl ControlValue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
|
|||||||
+30
-55
@@ -1,59 +1,34 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use crate::error::NokhwaError;
|
use crate::error::NokhwaError;
|
||||||
use crate::frame_buffer::FrameBuffer;
|
use crate::frame_buffer::FrameBuffer;
|
||||||
use crate::frame_format::FrameFormat;
|
use crate::types::CameraFormat;
|
||||||
use crate::stream::{StreamHandle};
|
use std::fmt::Debug;
|
||||||
use crate::types::{CameraFormat, FrameRate, Resolution};
|
pub use image::{ImageBuffer, Pixel, Primitive};
|
||||||
|
use crate::image::{DecodedImage, NonFloatScalarWidth};
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub trait Decoder {
|
||||||
pub struct Decoder<'stream, Video> where
|
type Config: Clone + Debug + TryFrom<CameraFormat>;
|
||||||
Video: Codec {
|
type OutputMeta: Debug;
|
||||||
video: Video,
|
|
||||||
stream: &'stream mut StreamHandle
|
fn config(&self) -> &Self::Config;
|
||||||
|
|
||||||
|
fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError>;
|
||||||
|
|
||||||
|
fn decode_to_buffer(
|
||||||
|
&mut self,
|
||||||
|
to_decode: FrameBuffer,
|
||||||
|
buffer: impl AsMut<[u8]>,
|
||||||
|
) -> Result<Self::OutputMeta, NokhwaError>;
|
||||||
|
|
||||||
|
fn decode_to_pixel_buffer<P: Pixel>(
|
||||||
|
&mut self,
|
||||||
|
to_decode: FrameBuffer,
|
||||||
|
buffer: impl AsMut<[P::Subpixel]>,
|
||||||
|
) -> Result<Self::OutputMeta, NokhwaError>
|
||||||
|
where <P as Pixel>::Subpixel: NonFloatScalarWidth;
|
||||||
|
|
||||||
|
fn decode<P: Pixel>(
|
||||||
|
&mut self,
|
||||||
|
to_decode: FrameBuffer,
|
||||||
|
) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError>
|
||||||
|
where <P as Pixel>::Subpixel: NonFloatScalarWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stream, Video> Decoder<'stream, Video> where Video: Codec {
|
|
||||||
pub fn new(stream: &'stream mut StreamHandle, decoder: Video) -> Result<Self, NokhwaError> {
|
|
||||||
let format = stream.format();
|
|
||||||
|
|
||||||
let mut decoder = decoder;
|
|
||||||
decoder.initialize(format)?;
|
|
||||||
Ok(Self { video: decoder, stream })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DecoderAsync<'stream, Video> where
|
|
||||||
Video: CodecAsync {
|
|
||||||
video: Video,
|
|
||||||
stream_handle: &'stream mut StreamHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Codec: Debug {
|
|
||||||
const ALLOWED_FORMATS: &'static [FrameFormat];
|
|
||||||
|
|
||||||
fn initialize(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError>;
|
|
||||||
|
|
||||||
fn stop(&mut self) -> Result<(), NokhwaError>;
|
|
||||||
|
|
||||||
fn frame_format(&self) -> Result<FrameFormat, NokhwaError>;
|
|
||||||
|
|
||||||
fn resolution(&self) -> Result<Resolution, NokhwaError>;
|
|
||||||
|
|
||||||
fn frame_rate(&self) -> Result<FrameRate, NokhwaError>;
|
|
||||||
|
|
||||||
fn set_frame_format(&mut self, frame_format: FrameFormat) -> Result<(), NokhwaError>;
|
|
||||||
|
|
||||||
fn set_resolution(&mut self, resolution: Resolution) -> Result<(), NokhwaError>;
|
|
||||||
|
|
||||||
fn set_frame_rate(&mut self, frame_rate: FrameRate) -> Result<(), NokhwaError>;
|
|
||||||
|
|
||||||
fn decode_frame(&mut self, buffer: &FrameBuffer) -> Result<Cow<'_, [u8]>, NokhwaError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
pub trait CodecAsync: Codec + Debug {}
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
use crate::{frame_format::FrameFormat};
|
use crate::frame_format::FrameFormat;
|
||||||
use std::fmt::{Debug};
|
|
||||||
use thiserror::Error;
|
|
||||||
use crate::platform::Backends;
|
use crate::platform::Backends;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub type NokhwaResult<T> = Result<T, NokhwaError>;
|
pub type NokhwaResult<T> = Result<T, NokhwaError>;
|
||||||
|
|
||||||
@@ -62,6 +62,8 @@ pub enum NokhwaError {
|
|||||||
ConversionError(String),
|
ConversionError(String),
|
||||||
#[error("Permission denied by user.")]
|
#[error("Permission denied by user.")]
|
||||||
PermissionDenied,
|
PermissionDenied,
|
||||||
|
#[error("Failed to decode: {0}")]
|
||||||
|
Decoder(String),
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// pub enum InitializeError {}
|
// pub enum InitializeError {}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
use crate::ranges::ValidatableRange;
|
||||||
use crate::utils::Distance;
|
use crate::utils::Distance;
|
||||||
use crate::{
|
use crate::{
|
||||||
frame_format::FrameFormat,
|
frame_format::FrameFormat,
|
||||||
ranges::Range,
|
ranges::Range,
|
||||||
types::{CameraFormat, FrameRate, Resolution},
|
types::{CameraFormat, FrameRate, Resolution},
|
||||||
};
|
};
|
||||||
use crate::ranges::ValidatableRange;
|
|
||||||
|
|
||||||
|
|
||||||
/// A helper for choosing a [`CameraFormat`].
|
/// A helper for choosing a [`CameraFormat`].
|
||||||
/// The use of this is completely optional - for a simpler way try [`crate::camera::Camera::enumerate_formats`].
|
/// 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
|
/// Pick the closest [`CameraFormat`] to the one requested
|
||||||
Closest {
|
Closest {
|
||||||
resolution: Option<Range<Resolution>>,
|
resolution: Option<Range<Resolution>>,
|
||||||
|
preferred_resolution: Option<Resolution>,
|
||||||
frame_rate: Option<Range<FrameRate>>,
|
frame_rate: Option<Range<FrameRate>>,
|
||||||
|
preferred_frame_rate: Option<FrameRate>,
|
||||||
},
|
},
|
||||||
HighestFrameRate {
|
HighestFrameRate {
|
||||||
frame_rate: Range<FrameRate>,
|
frame_rate: Range<FrameRate>,
|
||||||
@@ -38,18 +39,21 @@ pub struct FormatRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
Self {
|
||||||
request_type: format_request_type,
|
request_type: format_request_type,
|
||||||
allowed_frame_formats,
|
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()
|
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() {
|
if camera_formats.is_empty() {
|
||||||
return camera_formats;
|
return camera_formats;
|
||||||
}
|
}
|
||||||
@@ -57,69 +61,61 @@ impl FormatRequest {
|
|||||||
match self.request_type {
|
match self.request_type {
|
||||||
FormatRequestType::Closest {
|
FormatRequestType::Closest {
|
||||||
resolution,
|
resolution,
|
||||||
|
preferred_resolution,
|
||||||
frame_rate,
|
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)
|
// lets calcuate distance in 3 dimensions (add both resolution and frame_rate together)
|
||||||
|
|
||||||
camera_formats.sort_by(|a, b| {
|
camera_formats.sort_by(|a, b| {
|
||||||
let a_distance = format_distance_to_point(&resolution_point, &frame_rate_point, a);
|
let a_distance =
|
||||||
let b_distance = format_distance_to_point(&resolution_point, &frame_rate_point, b);
|
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)
|
a_distance.total_cmp(&b_distance)
|
||||||
});
|
});
|
||||||
|
|
||||||
camera_formats.into_iter().filter(|fmt| {
|
camera_formats
|
||||||
self.allowed_frame_formats.contains(fmt.format())
|
.into_iter()
|
||||||
}).filter(|cam_fmt| {
|
.filter(|fmt| self.allowed_frame_formats.contains(fmt.format()))
|
||||||
if let Some(res_range) = resolution {
|
.filter(|cam_fmt| {
|
||||||
return res_range.validate(cam_fmt.resolution())
|
if let Some(res_range) = resolution {
|
||||||
}
|
return res_range.validate(cam_fmt.resolution());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(frame_rate_range) = frame_rate {
|
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
|
true
|
||||||
}).collect()
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
FormatRequestType::HighestFrameRate {
|
FormatRequestType::HighestFrameRate { frame_rate } => {
|
||||||
frame_rate
|
camera_formats.sort_by(|a, b| a.frame_rate().cmp(b.frame_rate()));
|
||||||
} => {
|
|
||||||
camera_formats.sort_by(|a, b| {
|
|
||||||
a.frame_rate().cmp(b.frame_rate())
|
|
||||||
});
|
|
||||||
|
|
||||||
camera_formats.into_iter().filter(|fmt| {
|
camera_formats
|
||||||
self.allowed_frame_formats.contains(fmt.format())
|
.into_iter()
|
||||||
}).filter(|a| {
|
.filter(|fmt| self.allowed_frame_formats.contains(fmt.format()))
|
||||||
frame_rate.validate(a.frame_rate())
|
.filter(|a| frame_rate.validate(a.frame_rate()))
|
||||||
}).collect()
|
.collect()
|
||||||
}
|
}
|
||||||
FormatRequestType::HighestResolution {
|
FormatRequestType::HighestResolution { resolution } => {
|
||||||
resolution
|
camera_formats.sort_by(|a, b| a.resolution().cmp(b.resolution()));
|
||||||
} => {
|
|
||||||
camera_formats.sort_by(|a, b| {
|
|
||||||
a.resolution().cmp(b.resolution())
|
|
||||||
});
|
|
||||||
|
|
||||||
camera_formats.into_iter().filter(|fmt| {
|
camera_formats
|
||||||
self.allowed_frame_formats.contains(fmt.format())
|
.into_iter()
|
||||||
}).filter(|a| {
|
.filter(|fmt| self.allowed_frame_formats.contains(fmt.format()))
|
||||||
resolution.validate(a.resolution())
|
.filter(|a| resolution.validate(a.resolution()))
|
||||||
}).collect()
|
.collect()
|
||||||
}
|
}
|
||||||
FormatRequestType::Exact {
|
FormatRequestType::Exact {
|
||||||
resolution,
|
resolution,
|
||||||
frame_rate,
|
frame_rate,
|
||||||
} => {
|
} => camera_formats
|
||||||
camera_formats.into_iter().filter(|fmt| {
|
.into_iter()
|
||||||
self.allowed_frame_formats.contains(fmt.format())
|
.filter(|fmt| self.allowed_frame_formats.contains(fmt.format()))
|
||||||
}).filter(|a| {
|
.filter(|a| resolution.eq(a.resolution()) && frame_rate.eq(a.frame_rate()))
|
||||||
resolution.eq(a.resolution()) && frame_rate.eq(a.frame_rate())
|
.collect(),
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
FormatRequestType::Any => {
|
FormatRequestType::Any => {
|
||||||
// return as-is
|
// return as-is
|
||||||
camera_formats
|
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 {
|
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,
|
None => 0_f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolution_point_distance = match resolution {
|
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,
|
None => 0_f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,53 +13,56 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
use crate::control::ControlValue;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use crate::frame_format::FrameFormat;
|
use std::ops::Deref;
|
||||||
use small_map::{FxSmallMap, Iter};
|
|
||||||
use crate::control::ControlValue;
|
|
||||||
|
|
||||||
pub use compact_str::CompactString;
|
pub use compact_str::CompactString;
|
||||||
|
pub use smallmap::Map;
|
||||||
|
|
||||||
pub type PlatformSpecificFlag = u32;
|
pub type PlatformSpecificFlag = u32;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
flags: FxSmallMap<8, CompactString, ControlValue>,
|
flags: Map<CompactString, ControlValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Metadata {
|
impl Metadata {
|
||||||
pub fn new() -> Self {
|
#[must_use] pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
flags: Default::default(),
|
flags: Map::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, key: CompactString) -> Option<&ControlValue> {
|
#[must_use] pub fn get(&self, key: &str) -> Option<&ControlValue> {
|
||||||
self.flags.get(&key)
|
self.flags.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, key: CompactString, value: ControlValue) {
|
pub fn insert(&mut self, key: CompactString, value: ControlValue) {
|
||||||
self.flags.insert(key, value);
|
self.flags.insert(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> Iter<'_, 8, CompactString, ControlValue> {
|
|
||||||
self.flags.iter()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Metadata {
|
impl Hash for Metadata {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
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());
|
state.write(key.as_bytes());
|
||||||
value.hash(state);
|
value.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for Metadata {
|
||||||
|
type Target = Map<CompactString, ControlValue>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq for Metadata {
|
impl PartialEq for Metadata {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
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 let Some(other_value) = other.flags.get(this_key) {
|
||||||
if this_value != other_value {
|
if this_value != other_value {
|
||||||
return false;
|
return false;
|
||||||
@@ -87,10 +90,7 @@ impl FrameBuffer {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(buffer: Cow<'static, [u8]>, metadata: Option<Metadata>) -> Self {
|
pub fn new(buffer: Cow<'static, [u8]>, metadata: Option<Metadata>) -> Self {
|
||||||
Self {
|
Self { buffer, metadata }
|
||||||
buffer,
|
|
||||||
metadata,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the data of this buffer.
|
/// Get the data of this buffer.
|
||||||
@@ -99,8 +99,8 @@ impl FrameBuffer {
|
|||||||
&self.buffer
|
&self.buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn consume(self) -> (Cow<'static, [u8]>, Option<Metadata>) {
|
#[must_use] pub fn consume(self) -> (Cow<'static, [u8]>, Option<Metadata>) {
|
||||||
return (self.buffer, self.metadata)
|
(self.buffer, self.metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
+307
-83
@@ -15,71 +15,70 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter};
|
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`.
|
macro_rules! define_frame_format_with_groups {
|
||||||
#[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 {
|
|
||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
$group_name:ident => [
|
$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 {
|
impl FrameFormat {
|
||||||
$(
|
$(
|
||||||
pub const $group_name: &'static [FrameFormat] = &[
|
pub const $group_name: &'static [FrameFormat] = &[
|
||||||
$(FrameFormat::$format),*
|
$(FrameFormat::$format),*
|
||||||
];
|
];
|
||||||
)*
|
)*
|
||||||
|
pub const ALL: &'static [FrameFormat] = &[
|
||||||
|
$(
|
||||||
|
$(FrameFormat::$format,)*
|
||||||
|
)*
|
||||||
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
define_frame_format_groups! {
|
define_frame_format_with_groups! {
|
||||||
ALL => [
|
|
||||||
H263, H264, H265, Av1, Avc1, Mpeg1, Mpeg2, Mpeg4, MJpeg, XVid,
|
|
||||||
VP8, VP9, Yuyv422, Uyvy422, Nv12, Nv21, Yv12, Luma8, Luma16,
|
|
||||||
Rgb332, RgbA8888
|
|
||||||
],
|
|
||||||
COMPRESSED => [
|
COMPRESSED => [
|
||||||
H263, H264, H265, Av1, Avc1, Mpeg1, Mpeg2, Mpeg4, MJpeg, XVid,
|
H265,
|
||||||
VP8, VP9
|
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 => [
|
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,
|
RAW_BGR => [
|
||||||
VP8, VP9, Yuyv422, Uyvy422, Nv12, Nv21, Yv12, Rgb332, RgbA8888
|
Bgr_3_3_2,
|
||||||
],
|
Bgr_5_6_5,
|
||||||
GRAYSCALE => [
|
Bgr_5_5_5,
|
||||||
Luma8, Luma16
|
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 {
|
impl Display for FrameFormat {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{self:?}")
|
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_export]
|
||||||
macro_rules! define_back_and_fourth_frame_format {
|
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) => {
|
($fourcc_type:ty, { $( $frame_format:expr => $value:literal, )* }, $func_u8_8_to_fcc:expr, $func_fcc_to_u8_8:expr, $value_to_fcc_type:expr) => {
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
use image::{ImageBuffer, Pixel, Primitive};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use bytemuck::Pod;
|
||||||
|
use num_traits::{NumCast, PrimInt};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DecodedImage<Px, Meta>
|
||||||
|
where
|
||||||
|
Px: Pixel,
|
||||||
|
<Px as Pixel>::Subpixel: NonFloatScalarWidth,
|
||||||
|
Meta: Debug,
|
||||||
|
{
|
||||||
|
pub buffer: ImageBuffer<Px, Vec<Px::Subpixel>>,
|
||||||
|
pub metadata: Meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Px, Meta> DecodedImage<Px, Meta> where
|
||||||
|
Px: Pixel,
|
||||||
|
<Px as Pixel>::Subpixel: NonFloatScalarWidth,
|
||||||
|
Meta: Debug {
|
||||||
|
pub fn new(buffer: ImageBuffer<Px, Vec<Px::Subpixel>>,
|
||||||
|
metadata: Meta) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer,
|
||||||
|
metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Px, Meta> Deref for DecodedImage<Px, Meta>
|
||||||
|
where
|
||||||
|
Px: Pixel,
|
||||||
|
<Px as Pixel>::Subpixel: NonFloatScalarWidth,
|
||||||
|
Meta: Debug
|
||||||
|
{
|
||||||
|
type Target = ImageBuffer<Px, Vec<Px::Subpixel>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Px, Meta> DerefMut for DecodedImage<Px, Meta>
|
||||||
|
where
|
||||||
|
Px: Pixel,
|
||||||
|
<Px as Pixel>::Subpixel: NonFloatScalarWidth,Meta: Debug
|
||||||
|
{
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not for safe work ;p
|
||||||
|
// TODO: add more custom integer sizes, or break our dependence on image entirely and
|
||||||
|
// create our own imagebuffer
|
||||||
|
pub trait NonFloatScalarWidth: Debug + Primitive + PrimInt + NumCast + Pod {
|
||||||
|
const WIDTH: u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_nfsw {
|
||||||
|
( $( [ ( $( $things:ty ),+ ) : $size:literal ] ),* $(,)? ) => {
|
||||||
|
$(
|
||||||
|
$(
|
||||||
|
impl NonFloatScalarWidth for $things {
|
||||||
|
const WIDTH: u32 = $size;
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_nfsw! {
|
||||||
|
[ (u8, i8) : 8 ],
|
||||||
|
[ (u16, i16) : 16 ],
|
||||||
|
[ (u32, i32) : 32 ],
|
||||||
|
[ (u64, i64) : 64 ],
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
#![deny(clippy::pedantic)]
|
#![deny(clippy::pedantic)]
|
||||||
#![warn(clippy::all)]
|
#![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))]
|
#![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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -20,15 +21,17 @@
|
|||||||
|
|
||||||
//! Core type definitions for `nokhwa`
|
//! Core type definitions for `nokhwa`
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
|
pub mod codec;
|
||||||
|
pub mod control;
|
||||||
pub mod decoder;
|
pub mod decoder;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod format_request;
|
pub mod format_request;
|
||||||
pub mod frame_buffer;
|
pub mod frame_buffer;
|
||||||
pub mod frame_format;
|
pub mod frame_format;
|
||||||
pub mod control;
|
pub mod image;
|
||||||
|
pub mod platform;
|
||||||
pub mod ranges;
|
pub mod ranges;
|
||||||
|
pub mod stream;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod stream;
|
|
||||||
pub mod platform;
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::camera::{AsyncCamera, Camera};
|
use crate::camera::Camera;
|
||||||
use crate::error::NokhwaResult;
|
use crate::error::NokhwaResult;
|
||||||
use crate::types::{CameraIndex, CameraInformation};
|
use crate::types::{CameraIndex, CameraInformation};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
@@ -15,7 +15,7 @@ pub enum Backends {
|
|||||||
|
|
||||||
impl Display for Backends {
|
impl Display for Backends {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
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(&mut self, index: CameraIndex) -> NokhwaResult<Self::Camera>;
|
||||||
|
|
||||||
fn open_dynamic(&mut self, index: CameraIndex) -> NokhwaResult<Box<dyn Camera>> {
|
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))
|
self.open(index).map(|cam| Box::new(cam) as Box<dyn Camera>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
pub trait AsyncPlatformTrait {
|
pub trait AsyncPlatformTrait: PlatformTrait {
|
||||||
const PLATFORM: Backends;
|
const PLATFORM: Backends;
|
||||||
type AsyncCamera: AsyncCamera;
|
type AsyncCamera: crate::camera::AsyncCamera;
|
||||||
|
|
||||||
async fn await_permission(&mut self) -> NokhwaResult<()>;
|
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_async(&mut self, index: &CameraIndex) -> NokhwaResult<Self::AsyncCamera>;
|
||||||
|
|
||||||
async fn open_dynamic_async(&mut self, index: &CameraIndex) -> NokhwaResult<Box<dyn Camera>> {
|
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))
|
self.open_async(index).await.map(|cam| Box::new(cam) as Box<dyn Camera>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-46
@@ -1,7 +1,7 @@
|
|||||||
use core::fmt::{Debug, Display, Formatter};
|
use core::fmt::{Debug, Display, Formatter};
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::ops::{Div, Rem, Sub};
|
use std::ops::{Div, Rem, Sub};
|
||||||
use ordered_float::OrderedFloat;
|
|
||||||
|
|
||||||
/// A range type that can be validated.
|
/// A range type that can be validated.
|
||||||
pub trait ValidatableRange {
|
pub trait ValidatableRange {
|
||||||
@@ -16,7 +16,9 @@ pub trait ValidatableRange {
|
|||||||
///
|
///
|
||||||
/// Inclusive by default.
|
/// Inclusive by default.
|
||||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
#[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,
|
minimum: T,
|
||||||
lower_inclusive: bool,
|
lower_inclusive: bool,
|
||||||
@@ -25,7 +27,10 @@ pub struct Range<T> where T: RangeItem
|
|||||||
step: Option<T>,
|
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`]
|
/// Create an upper and lower inclusive [`Range`]
|
||||||
pub fn new(min: T, max: T, step: Option<T>) -> Self {
|
pub fn new(min: T, max: T, step: Option<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -42,7 +47,7 @@ impl<T> Range<T> where T: Copy {
|
|||||||
lower_inclusive: bool,
|
lower_inclusive: bool,
|
||||||
max: T,
|
max: T,
|
||||||
upper_inclusive: bool,
|
upper_inclusive: bool,
|
||||||
step: Option<T>
|
step: Option<T>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
minimum: min,
|
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;
|
self.minimum = minimum;
|
||||||
}
|
}
|
||||||
pub fn set_lower_inclusive(&mut self, lower_inclusive: bool) {
|
pub fn set_lower_inclusive(&mut self, lower_inclusive: bool) {
|
||||||
self.lower_inclusive = lower_inclusive;
|
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;
|
self.maximum = maximum;
|
||||||
}
|
}
|
||||||
pub fn set_upper_inclusive(&mut self, upper_inclusive: bool) {
|
pub fn set_upper_inclusive(&mut self, upper_inclusive: bool) {
|
||||||
@@ -92,33 +97,27 @@ where
|
|||||||
type Validation = T;
|
type Validation = T;
|
||||||
|
|
||||||
fn validate(&self, value: &T) -> bool {
|
fn validate(&self, value: &T) -> bool {
|
||||||
let l_comparison_fn = match self.lower_inclusive {
|
let l_comparison_fn = if self.lower_inclusive { T::ge } else { T::gt };
|
||||||
true => T::ge,
|
let u_comparison_fn = if self.upper_inclusive { T::le } else { T::lt };
|
||||||
false => T::gt,
|
|
||||||
};
|
|
||||||
let u_comparison_fn = match self.upper_inclusive {
|
|
||||||
true => T::le,
|
|
||||||
false => T::lt,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !(l_comparison_fn(&self.minimum, value) && u_comparison_fn(&self.maximum, value)) {
|
if !(l_comparison_fn(&self.minimum, value) && u_comparison_fn(&self.maximum, value)) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check step
|
// check step
|
||||||
|
|
||||||
if let Some(step) = self.step {
|
if let Some(step) = self.step {
|
||||||
let step_chk_value = *value - self.minimum;
|
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>
|
impl<T> Default for Range<T>
|
||||||
where
|
where
|
||||||
T: Default,
|
T: Default + RangeItem,
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Range {
|
Range {
|
||||||
@@ -133,7 +132,7 @@ where
|
|||||||
|
|
||||||
impl<T> Display for Range<T>
|
impl<T> Display for Range<T>
|
||||||
where
|
where
|
||||||
T: Debug,
|
T: Debug + RangeItem,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
let lower_inclusive_char = bool_to_inclusive_char(self.lower_inclusive, false);
|
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 {
|
fn bool_to_inclusive_char(inclusive: bool, upper: bool) -> char {
|
||||||
match inclusive {
|
if inclusive {
|
||||||
true => {
|
if upper {
|
||||||
if upper {
|
']'
|
||||||
']'
|
} else {
|
||||||
} else {
|
'['
|
||||||
'['
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false => {
|
|
||||||
if upper {
|
|
||||||
')'
|
|
||||||
} else {
|
|
||||||
'('
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if upper {
|
||||||
|
')'
|
||||||
|
} else {
|
||||||
|
'('
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_to_string<T>(default: &Option<T>) -> String
|
pub trait RangeItem:
|
||||||
where
|
Copy
|
||||||
T: Debug,
|
+ 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 ZERO: Self;
|
||||||
|
const MIN: Self;
|
||||||
|
const MAX: Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_num {
|
macro_rules! impl_num {
|
||||||
($($n:ty)*) => ($(
|
($($n:ty)*) => ($(
|
||||||
impl RangeItem for $n {
|
impl RangeItem for $n {
|
||||||
const ZERO: $n = 0;
|
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> {
|
impl RangeItem for OrderedFloat<f32> {
|
||||||
const ZERO: Self = OrderedFloat(0_f32);
|
const ZERO: Self = OrderedFloat(0_f32);
|
||||||
|
const MIN: Self = OrderedFloat(f32::MIN);
|
||||||
|
const MAX: Self = OrderedFloat(f32::MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RangeItem for OrderedFloat<f64> {
|
impl RangeItem for OrderedFloat<f64> {
|
||||||
const ZERO: Self = OrderedFloat(0_f64);
|
const ZERO: Self = OrderedFloat(0_f64);
|
||||||
}
|
|
||||||
|
const MIN: Self = OrderedFloat(f64::MIN);
|
||||||
|
const MAX: Self = OrderedFloat(f64::MAX);
|
||||||
|
}
|
||||||
|
|||||||
+55
-56
@@ -1,11 +1,11 @@
|
|||||||
use std::cell::Cell;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use flume::{Receiver, Sender, TryRecvError};
|
|
||||||
use typed_builder::TypedBuilder;
|
|
||||||
use crate::error::NokhwaError;
|
use crate::error::NokhwaError;
|
||||||
use crate::frame_buffer::FrameBuffer;
|
use crate::frame_buffer::FrameBuffer;
|
||||||
use crate::types::CameraFormat;
|
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.
|
/// What receiving behaviour the stream should observe.
|
||||||
///
|
///
|
||||||
@@ -79,23 +79,23 @@ pub enum Event {
|
|||||||
/// The stream is closed.
|
/// The stream is closed.
|
||||||
Closed,
|
Closed,
|
||||||
/// An error from the driver
|
/// 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.
|
/// 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.
|
/// Represents a handle to a currently open stream.
|
||||||
///
|
///
|
||||||
/// Streams are only valid as long as the camera is live. Any Stream that is living past a camera
|
/// Streams are only valid as long as the camera is live. Any Stream that is living past a camera
|
||||||
/// is invalid to use. (This doesn't cause UB, it will just kindly tell you that the stream has
|
/// is invalid to use. (This doesn't cause UB, it will just kindly tell you that the stream has
|
||||||
/// already closed.)
|
/// already closed.)
|
||||||
///
|
///
|
||||||
/// Streams may unexpectedly close due to unforeseen consequences e.g. webcam undergoes spontaneous
|
/// Streams may unexpectedly close due to unforeseen consequences e.g. webcam undergoes spontaneous
|
||||||
/// deconstruction.
|
/// deconstruction.
|
||||||
///
|
///
|
||||||
/// The async methods [`StreamHandle::poll_event`] and [`StreamHandle::poll_frame`] **do not** respect the [`StreamReceiverBehaviour`] setting.
|
/// The async methods [`StreamHandle::poll_event`] and [`StreamHandle::poll_frame`] **do not** respect the [`StreamReceiverBehaviour`] setting.
|
||||||
///
|
///
|
||||||
/// You may also close the stream from the handle side using
|
/// You may also close the stream from the handle side using
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StreamHandle {
|
pub struct StreamHandle {
|
||||||
frame: Receiver<Event>,
|
frame: Receiver<Event>,
|
||||||
@@ -106,7 +106,12 @@ pub struct StreamHandle {
|
|||||||
|
|
||||||
impl StreamHandle {
|
impl StreamHandle {
|
||||||
/// You shouldn't be here.
|
/// 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 {
|
Self {
|
||||||
frame: recv,
|
frame: recv,
|
||||||
control,
|
control,
|
||||||
@@ -114,38 +119,35 @@ impl StreamHandle {
|
|||||||
format: Cell::new(format),
|
format: Cell::new(format),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configuration(&self) -> &StreamConfiguration {
|
pub fn configuration(&self) -> &StreamConfiguration {
|
||||||
&self.configuration
|
&self.configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(&self) -> CameraFormat {
|
pub fn format(&self) -> CameraFormat {
|
||||||
self.format.get()
|
self.format.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_event(&self) -> Result<Event, NokhwaError> {
|
pub fn next_event(&self) -> Result<Event, NokhwaError> {
|
||||||
let event = match self.configuration.receiver {
|
let event = match self.configuration.receiver {
|
||||||
StreamReceiverBehaviour::Blocking => {
|
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 {
|
|
||||||
TryRecvError::Empty => Event::NotReady,
|
|
||||||
TryRecvError::Disconnected => Event::Closed,
|
|
||||||
}
|
|
||||||
}, |e| { e })
|
|
||||||
}
|
}
|
||||||
|
StreamReceiverBehaviour::Timeout(time) => self
|
||||||
|
.frame
|
||||||
|
.recv_timeout(time).unwrap_or_else(|_| Event::NotReady),
|
||||||
|
StreamReceiverBehaviour::Try => self.frame.try_recv().unwrap_or_else(
|
||||||
|
|why| match why {
|
||||||
|
TryRecvError::Empty => Event::NotReady,
|
||||||
|
TryRecvError::Disconnected => Event::Closed,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Event::FormatChange(fmt) = event {
|
if let Event::FormatChange(fmt) = event {
|
||||||
self.format.set(fmt);
|
self.format.set(fmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(event)
|
Ok(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_frame(&self) -> Result<FrameBuffer, NokhwaError> {
|
pub fn next_frame(&self) -> Result<FrameBuffer, NokhwaError> {
|
||||||
@@ -153,30 +155,30 @@ impl StreamHandle {
|
|||||||
let event = self.next_event()?;
|
let event = self.next_event()?;
|
||||||
match event {
|
match event {
|
||||||
Event::NewFrame(f) => return Ok(f),
|
Event::NewFrame(f) => return Ok(f),
|
||||||
Event::FormatChange(_) | Event::NotReady => continue,
|
|
||||||
Event::Terminating | Event::Closed => {
|
Event::Terminating | Event::Closed => {
|
||||||
let _ = self.control.try_send(());
|
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 {
|
|
||||||
ControlFlowOnOther::Continue => continue,
|
|
||||||
ControlFlowOnOther::Break => return Err(NokhwaError::ReadFrameError(why))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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")]
|
#[cfg(feature = "async")]
|
||||||
pub async fn poll_event(&self) -> Result<Event, NokhwaError> {
|
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(
|
||||||
self.format.set(fmt);
|
|_| Event::Closed,
|
||||||
}
|
|e| {
|
||||||
e
|
if let Event::FormatChange(fmt) = e {
|
||||||
}))
|
self.format.set(fmt);
|
||||||
|
}
|
||||||
|
e
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: a smarter implementation? maybe?
|
// TODO: a smarter implementation? maybe?
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
pub async fn poll_next_frame(&self) -> Result<FrameBuffer, NokhwaError> {
|
pub async fn poll_next_frame(&self) -> Result<FrameBuffer, NokhwaError> {
|
||||||
@@ -184,17 +186,15 @@ impl StreamHandle {
|
|||||||
let event = self.poll_event().await?;
|
let event = self.poll_event().await?;
|
||||||
match event {
|
match event {
|
||||||
Event::NewFrame(f) => return Ok(f),
|
Event::NewFrame(f) => return Ok(f),
|
||||||
Event::FormatChange(_) | Event::NotReady => continue,
|
|
||||||
Event::Terminating | Event::Closed => {
|
Event::Terminating | Event::Closed => {
|
||||||
let _ = self.control.try_send(());
|
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 {
|
|
||||||
ControlFlowOnOther::Continue => continue,
|
|
||||||
ControlFlowOnOther::Break => return Err(NokhwaError::ReadFrameError(why))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Event::Other(why) => match self.configuration.on_other {
|
||||||
|
ControlFlowOnOther::Continue => continue,
|
||||||
|
ControlFlowOnOther::Break => return Err(NokhwaError::ReadFrameError(why)),
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,4 +205,3 @@ impl Drop for StreamHandle {
|
|||||||
let _ = self.control.try_send(());
|
let _ = self.control.try_send(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-29
@@ -1,23 +1,23 @@
|
|||||||
|
use crate::ranges::RangeItem;
|
||||||
use crate::utils::Distance;
|
use crate::utils::Distance;
|
||||||
use crate::{error::NokhwaError, frame_format::FrameFormat};
|
use crate::{error::NokhwaError, frame_format::FrameFormat};
|
||||||
|
use num_rational::Rational32;
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::num::NonZeroI32;
|
||||||
|
use std::ops::{Div, Rem};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
fmt::{Debug, Display, Formatter},
|
fmt::{Debug, Display, Formatter},
|
||||||
hash::{Hash},
|
hash::Hash,
|
||||||
ops::{Sub},
|
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.
|
/// Describes the index of the camera.
|
||||||
/// - Index: A numbered index
|
/// - 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)]
|
#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||||
pub enum CameraIndex {
|
pub enum CameraIndex {
|
||||||
@@ -214,6 +214,8 @@ impl Rem for Resolution {
|
|||||||
|
|
||||||
impl RangeItem for Resolution {
|
impl RangeItem for Resolution {
|
||||||
const ZERO: Self = Resolution::new(0, 0);
|
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.
|
/// Framerate of a camera, backed by a num-rational Ratio type.
|
||||||
@@ -230,34 +232,34 @@ pub struct FrameRate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FrameRate {
|
impl FrameRate {
|
||||||
pub const fn new(numerator: i32, denominator: NonZeroI32) -> Self {
|
#[must_use] pub const fn new(numerator: i32, denominator: NonZeroI32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rational: Rational32::new_raw(numerator, denominator.get()),
|
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 {
|
Self {
|
||||||
rational: Rational32::new_raw(fps, 1),
|
rational: Rational32::new_raw(fps, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn numerator(&self) -> &i32 {
|
#[must_use] pub fn numerator(&self) -> i32 {
|
||||||
self.rational.numer()
|
*self.rational.numer()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn denominator(&self) -> &i32 {
|
#[must_use] pub fn denominator(&self) -> i32 {
|
||||||
self.rational.denom()
|
*self.rational.denom()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_raw(&self) -> &Rational32 {
|
#[must_use] pub fn as_raw(&self) -> &Rational32 {
|
||||||
&self.rational
|
&self.rational
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn approximate_float(&self) -> Option<f32> {
|
#[must_use] pub fn approximate_float(&self) -> Option<f32> {
|
||||||
let numerator_float = f32::from_i32(*self.numerator())?;
|
let numerator_float = f32::from_i32(self.numerator())?;
|
||||||
let denominator_float = f32::from_i32(*self.denominator())?;
|
let denominator_float = f32::from_i32(self.denominator())?;
|
||||||
|
|
||||||
Some(numerator_float / denominator_float)
|
Some(numerator_float / denominator_float)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,20 +309,20 @@ impl Rem for FrameRate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RangeItem 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 {
|
impl From<Rational32> for FrameRate {
|
||||||
fn from(value: Rational32) -> Self {
|
fn from(value: Rational32) -> Self {
|
||||||
FrameRate {
|
FrameRate { rational: value }
|
||||||
rational: value,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is a convenience struct that holds all information about the format of a webcam stream.
|
/// 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`].
|
/// 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))]
|
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||||
pub struct CameraFormat {
|
pub struct CameraFormat {
|
||||||
resolution: Resolution,
|
resolution: Resolution,
|
||||||
@@ -402,7 +404,7 @@ impl Default for CameraFormat {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
CameraFormat {
|
CameraFormat {
|
||||||
resolution: Resolution::new(640, 480),
|
resolution: Resolution::new(640, 480),
|
||||||
format: FrameFormat::MJpeg,
|
format: FrameFormat::MJPEG,
|
||||||
frame_rate: FrameRate::default(),
|
frame_rate: FrameRate::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,8 +434,6 @@ pub struct CameraInformation {
|
|||||||
|
|
||||||
impl CameraInformation {
|
impl CameraInformation {
|
||||||
/// Create a new [`CameraInformation`].
|
/// Create a new [`CameraInformation`].
|
||||||
/// # JS-WASM
|
|
||||||
/// This is exported as a constructor for [`CameraInformation`].
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
// OK, i just checkeed back on this code. WTF was I on when I wrote `&(impl AsRef<str> + ?Sized)` ????
|
// 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!
|
// 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.
|
/// Get a reference to the device info's human readable name.
|
||||||
/// # JS-WASM
|
|
||||||
/// This is exported as a `get_HumanReadableName`.
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
// yes, i know, unnecessary alloc this, unnecessary alloc that
|
// yes, i know, unnecessary alloc this, unnecessary alloc that
|
||||||
// but wasm bindgen
|
// but wasm bindgen
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
[package]
|
||||||
|
name = "nokhwa-decoders"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
ffmpeg = ["ffmpeg-the-third"]
|
||||||
|
av1 = ["rav1e"]
|
||||||
|
yuyv = ["dcv-color-primitives", "yuv"]
|
||||||
|
mjpeg = ["zune-jpeg"]
|
||||||
|
wasm = []
|
||||||
|
mega = ["ffmpeg", "av1", "yuyv", "mjpeg"]
|
||||||
|
static = ["ffmpeg-the-third/static"]
|
||||||
|
async = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytemuck = "1.23"
|
||||||
|
|
||||||
|
[dependencies.nokhwa-core]
|
||||||
|
version = "0.2"
|
||||||
|
path = "../nokhwa-core"
|
||||||
|
|
||||||
|
[dependencies.ffmpeg-the-third]
|
||||||
|
version = "3.0"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.rav1e]
|
||||||
|
version = "0.8"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.yuv]
|
||||||
|
version = "0.8"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.dcv-color-primitives]
|
||||||
|
version = "0.7"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.zune-jpeg]
|
||||||
|
version = "0.5.0-rc2"
|
||||||
|
optional = true
|
||||||
@@ -0,0 +1,670 @@
|
|||||||
|
use core::mem::transmute;
|
||||||
|
use bytemuck::try_cast_slice_mut;
|
||||||
|
use ffmpeg_the_third::codec::{Context, Id, Parameters};
|
||||||
|
use ffmpeg_the_third::decoder::Video;
|
||||||
|
use ffmpeg_the_third::ffi::{av_frame_alloc, av_frame_move_ref, av_image_copy_to_buffer, av_image_fill_arrays, av_image_get_buffer_size, avcodec_free_context, avcodec_parameters_alloc, avcodec_parameters_free, sws_freeContext, sws_getContext, sws_scale_frame, AVChromaLocation, AVCodecID, AVCodecParameters, AVColorPrimaries, AVColorRange, AVColorSpace, AVColorTransferCharacteristic, AVFieldOrder, AVMediaType, AVPacket, AVPacketSideData, AVPixelFormat, AVRational, SwsContext};
|
||||||
|
use ffmpeg_the_third::{decoder, packet::Packet, Frame};
|
||||||
|
use ffmpeg_the_third::packet::{Borrow, Ref};
|
||||||
|
use nokhwa_core::codec::Codec;
|
||||||
|
use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel, Primitive};
|
||||||
|
use nokhwa_core::error::NokhwaError;
|
||||||
|
use nokhwa_core::frame_buffer::FrameBuffer;
|
||||||
|
use nokhwa_core::frame_format::{CustomFrameFormat, FrameFormat};
|
||||||
|
use nokhwa_core::image::{DecodedImage, NonFloatScalarWidth};
|
||||||
|
use nokhwa_core::types::{CameraFormat, FrameRate, Resolution};
|
||||||
|
|
||||||
|
pub struct FfmpegDecoder {
|
||||||
|
codec: FfmpegCodec,
|
||||||
|
sws: Option<Sws>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FfmpegDecoder {
|
||||||
|
pub fn new(config: <FfmpegDecoder as nokhwa_core::decoder::Decoder>::Config) -> Result<Self, NokhwaError> {
|
||||||
|
let codec = FfmpegCodec::new(config)?;
|
||||||
|
Ok(
|
||||||
|
Self {
|
||||||
|
codec,
|
||||||
|
sws: None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_format(format: &CameraFormat) -> Result<Self, NokhwaError> {
|
||||||
|
let config = FfmpegDecoderConfig::with_camera_format(format);
|
||||||
|
let codec = FfmpegCodec::new(config)?;
|
||||||
|
Ok(
|
||||||
|
Self {
|
||||||
|
codec,
|
||||||
|
sws: None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive_decoded_frame(&mut self, to_decode: FrameBuffer) -> Result<(Frame, <FfmpegDecoder as Decoder>::OutputMeta) , NokhwaError> {
|
||||||
|
let new_pkt = Packet::borrow(to_decode.buffer());
|
||||||
|
self.codec.send_item(new_pkt.into())?;
|
||||||
|
let mut new_frame = unsafe { Frame::empty() };
|
||||||
|
let metadata = self.codec.receive_decoded_item(&mut new_frame).map_err(|why| NokhwaError::Decoder(why.to_string()))?;
|
||||||
|
Ok((new_frame, metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder for FfmpegDecoder {
|
||||||
|
type Config = <FfmpegCodec as Codec>::Config;
|
||||||
|
type OutputMeta = <FfmpegCodec as Codec>::WrittenMeta;
|
||||||
|
|
||||||
|
fn config(&self) -> &Self::Config {
|
||||||
|
self.codec.config()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError> {
|
||||||
|
self.codec.set_config(config)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_to_buffer(&mut self, to_decode: FrameBuffer, mut buffer: impl AsMut<[u8]>) -> Result<Self::OutputMeta, NokhwaError> {
|
||||||
|
// TODO: add an extra zippy happy path for rgb/bgr/luma
|
||||||
|
let (frame, metadata) = self.receive_decoded_frame(to_decode)?;
|
||||||
|
let av_frame_data = unsafe { frame.as_ref().ok_or(NokhwaError::Decoder("No Frame Data from Decoder".to_string()))? };
|
||||||
|
let buffer = buffer.as_mut();
|
||||||
|
let result = unsafe {
|
||||||
|
av_image_copy_to_buffer(buffer.as_mut().as_mut_ptr(), buffer.as_mut().len() as i32, av_frame_data.data.as_ptr() as *const *const u8, av_frame_data.linesize.as_ptr(), metadata.pixel_format, self.config().resolution.width() as i32, self.config().resolution.height() as i32, 1)
|
||||||
|
};
|
||||||
|
if result.is_negative() {
|
||||||
|
return Err(NokhwaError::Decoder(format!("Error Code {}", result)))
|
||||||
|
}
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_to_pixel_buffer<P: Pixel>(&mut self, to_decode: FrameBuffer, mut buffer: impl AsMut<[P::Subpixel]>) -> Result<Self::OutputMeta, NokhwaError> where <P as Pixel>::Subpixel: NonFloatScalarWidth {
|
||||||
|
let destination_format = pixel_to_destination_px_fmt::<P>(<P::Subpixel as NonFloatScalarWidth>::WIDTH).ok_or(NokhwaError::Decoder("Unsupported Pixel Type".to_string()))?;
|
||||||
|
|
||||||
|
let buffer = buffer.as_mut();
|
||||||
|
let estimated_size = self.codec.preferred_buffer_min_size(&None)?.ok_or(NokhwaError::Decoder("failed to estimate decoder buffer.length".to_string()))?;
|
||||||
|
if buffer.len() < estimated_size {
|
||||||
|
return Err(NokhwaError::Decoder("buffer too small!".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let (mut frame, decoded_meta) = self.receive_decoded_frame(to_decode)?;
|
||||||
|
let source_format = decoded_meta.pixel_format;
|
||||||
|
|
||||||
|
let cast_slice = try_cast_slice_mut::<P::Subpixel, u8>(buffer).map_err(|why| NokhwaError::Decoder(why.to_string()))?;
|
||||||
|
let receiving_buffer = unsafe {
|
||||||
|
let av_frame = av_frame_alloc();
|
||||||
|
let result = av_image_fill_arrays((*av_frame).data.as_mut_ptr(), (*av_frame).linesize.as_mut_ptr(), cast_slice.as_ptr(), destination_format, self.codec.config.resolution.width() as i32, self.codec.config.resolution.height() as i32, 1);
|
||||||
|
if result < 0 {
|
||||||
|
return Err(NokhwaError::Decoder("Failed to fill avimage".to_string()));
|
||||||
|
}
|
||||||
|
av_frame
|
||||||
|
};
|
||||||
|
|
||||||
|
if source_format != destination_format {
|
||||||
|
let sws_scaler = match &mut self.sws {
|
||||||
|
None => {
|
||||||
|
let context = create_sws_context(source_format, destination_format, self.codec.config.resolution)?;
|
||||||
|
self.sws = Some(Sws {
|
||||||
|
sws: context,
|
||||||
|
source_pixel_format: source_format,
|
||||||
|
dest_pixel_format: destination_format,
|
||||||
|
});
|
||||||
|
self.sws.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
Some(v) => {
|
||||||
|
if v.source_pixel_format != source_format || v.dest_pixel_format != destination_format {
|
||||||
|
v.source_pixel_format = source_format;
|
||||||
|
v.dest_pixel_format = destination_format;
|
||||||
|
v.sws = create_sws_context(source_format, destination_format, self.codec.config.resolution)?;
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let scaled = unsafe {sws_scale_frame(sws_scaler.sws, receiving_buffer, frame.as_mut_ptr())};
|
||||||
|
if scaled < 0 {
|
||||||
|
Err(NokhwaError::Decoder("Failed to scale sws".to_string()))
|
||||||
|
} else {
|
||||||
|
Ok(decoded_meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
av_frame_move_ref(receiving_buffer, frame.as_mut_ptr())
|
||||||
|
};
|
||||||
|
Ok(decoded_meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode<P: Pixel>(&mut self, to_decode: FrameBuffer) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError> where <P as Pixel>::Subpixel: NonFloatScalarWidth {
|
||||||
|
let min_size = P::CHANNEL_COUNT as usize * (<P::Subpixel as NonFloatScalarWidth>::WIDTH as usize / 8_usize) * self.codec.config.resolution.height() as usize * self.codec.config.resolution.width() as usize;
|
||||||
|
let mut buffer: Vec<P::Subpixel> = vec![<P::Subpixel>::DEFAULT_MIN_VALUE; min_size];
|
||||||
|
let meta = self.decode_to_buffer(to_decode, try_cast_slice_mut(&mut buffer).map_err(|why| NokhwaError::Decoder(why.to_string()))?)?;
|
||||||
|
Ok(DecodedImage::new(ImageBuffer::from_vec(self.codec.config.resolution.width(), self.codec.config.resolution.height(), buffer).ok_or(NokhwaError::Decoder("Failed to create Image Buffer".to_string()))?, meta))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_sws_context(src_format: AVPixelFormat, dest: AVPixelFormat, resolution: Resolution) -> Result<*mut SwsContext, NokhwaError> {
|
||||||
|
let param = 0_f64;
|
||||||
|
let new_sws = unsafe {
|
||||||
|
sws_getContext(resolution.width() as i32, resolution.height() as i32, src_format, resolution.width() as i32, resolution.height() as i32, dest, 0, core::ptr::null_mut(), core::ptr::null_mut(), ¶m)
|
||||||
|
};
|
||||||
|
Ok(new_sws)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixel_to_destination_px_fmt<P: Pixel>(width: u32) -> Option<AVPixelFormat> where <P as Pixel>::Subpixel: NonFloatScalarWidth {
|
||||||
|
match P::COLOR_MODEL {
|
||||||
|
"RGB" => match width {
|
||||||
|
8 => Some(AVPixelFormat::AV_PIX_FMT_RGB24),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
|
||||||
|
"RGBA" => match width {
|
||||||
|
8 => Some(AVPixelFormat::AV_PIX_FMT_RGBA),
|
||||||
|
16 => Some(switch_endian(AVPixelFormat::AV_PIX_FMT_RGBA64LE, AVPixelFormat::AV_PIX_FMT_RGBA64BE)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
"BGR" => match width {
|
||||||
|
8 => Some(AVPixelFormat::AV_PIX_FMT_BGR24),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
|
||||||
|
"BGRA" => match width {
|
||||||
|
8 => Some(AVPixelFormat::AV_PIX_FMT_BGRA),
|
||||||
|
16 => Some(switch_endian(AVPixelFormat::AV_PIX_FMT_BGRA64LE, AVPixelFormat::AV_PIX_FMT_BGRA64BE)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
"Y" => match width {
|
||||||
|
8 => Some(AVPixelFormat::AV_PIX_FMT_GRAY8),
|
||||||
|
16 => Some(switch_endian(AVPixelFormat::AV_PIX_FMT_GRAY16LE, AVPixelFormat::AV_PIX_FMT_GRAY16BE)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
"YA" => match width {
|
||||||
|
8 => Some(AVPixelFormat::AV_PIX_FMT_GRAY8A),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sws {
|
||||||
|
pub sws: *mut SwsContext,
|
||||||
|
pub source_pixel_format: AVPixelFormat,
|
||||||
|
pub dest_pixel_format: AVPixelFormat,
|
||||||
|
// pub filter_a: SwsFilter,
|
||||||
|
// pub filter_b: SwsFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Sws {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
sws_freeContext(self.sws);
|
||||||
|
// sws_freeFilter(&mut self.filter_a);
|
||||||
|
// sws_freeFilter(&mut self.filter_b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FfmpegFrameMetadata {
|
||||||
|
pub color_range: AVColorRange,
|
||||||
|
pub pixel_format: AVPixelFormat,
|
||||||
|
pub resolution: Resolution,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PacketOrRef<'a> {
|
||||||
|
Packet(Packet),
|
||||||
|
Ref(Borrow<'a>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ref for PacketOrRef<'_> {
|
||||||
|
fn as_ptr(&self) -> *const AVPacket {
|
||||||
|
match self {
|
||||||
|
PacketOrRef::Packet(p) => p.as_ptr(),
|
||||||
|
PacketOrRef::Ref(r) => r.as_ptr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Borrow<'a>> for PacketOrRef<'a> {
|
||||||
|
fn from(value: Borrow<'a>) -> Self {
|
||||||
|
Self::Ref(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Packet> for PacketOrRef<'_> {
|
||||||
|
fn from(value: Packet) -> Self {
|
||||||
|
Self::Packet(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FfmpegCodec {
|
||||||
|
decoder: Video,
|
||||||
|
config: FfmpegDecoderConfig,
|
||||||
|
temp_cfg: Option<*mut AVCodecParameters>,
|
||||||
|
deinitialized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FfmpegCodec {
|
||||||
|
fn new(config: <FfmpegCodec as Codec>::Config) -> Result<Self, NokhwaError> {
|
||||||
|
let id = convert_format_to_codec_id(&config.frame_format).ok_or(NokhwaError::Decoder("Could not find Format".to_string()))?;
|
||||||
|
|
||||||
|
let codec = decoder::find(id).ok_or(NokhwaError::Decoder("Failed to find codec".to_string()))?;
|
||||||
|
|
||||||
|
let context = unsafe {
|
||||||
|
let ptr = ffmpeg_the_third::ffi::avcodec_alloc_context3(codec.as_ptr());
|
||||||
|
if ptr.is_null() {
|
||||||
|
return Err(NokhwaError::Decoder("ffmpeg returned a null context".to_string()))
|
||||||
|
}
|
||||||
|
Context::wrap(ptr, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut video = context.decoder().video().map_err(|why| NokhwaError::Decoder(why.to_string()))?;
|
||||||
|
video.set_parameters(unsafe { Parameters::from_raw(config.as_ptr()?).ok_or(NokhwaError::Decoder("Failed to convert parameters".to_string()))? }).map_err(|why| NokhwaError::Decoder(why.to_string()))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
decoder: video,
|
||||||
|
config,
|
||||||
|
temp_cfg: None,
|
||||||
|
deinitialized: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Codec for FfmpegCodec {
|
||||||
|
|
||||||
|
type Config = FfmpegDecoderConfig;
|
||||||
|
type Input<'a> = PacketOrRef<'a>;
|
||||||
|
type Output = Frame;
|
||||||
|
type WrittenMeta = FfmpegFrameMetadata;
|
||||||
|
|
||||||
|
fn allowed_formats(&self) -> Result<&[FrameFormat], NokhwaError> {
|
||||||
|
if self.deinitialized {
|
||||||
|
return Err(NokhwaError::Decoder("This decoder is deinitialized - it is no longer usable.".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FrameFormat::ALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn config(&self) -> &Self::Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError> {
|
||||||
|
if self.deinitialized {
|
||||||
|
return Err(NokhwaError::Decoder("This decoder is deinitialized - it is no longer usable.".to_string()))
|
||||||
|
}
|
||||||
|
dealloc_av_params(&mut self.temp_cfg);
|
||||||
|
let mut temp_config = config.as_avcodec_params()?;
|
||||||
|
self.decoder.set_parameters(unsafe { Parameters::from_raw(&mut temp_config).ok_or(NokhwaError::Decoder("Failed to convert parameters".to_string()))? }).map_err(|why| NokhwaError::Decoder(why.to_string()))?;
|
||||||
|
self.config = config;
|
||||||
|
self.temp_cfg = Some(&mut temp_config);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_item(&mut self, input: Self::Input<'_>) -> Result<(), NokhwaError> {
|
||||||
|
if self.deinitialized {
|
||||||
|
return Err(NokhwaError::Decoder("This decoder is deinitialized - it is no longer usable.".to_string()))
|
||||||
|
}
|
||||||
|
self.decoder.send_packet(&input).map_err(|why| NokhwaError::Decoder(why.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_decoded_item(&mut self, writing_output: &mut Self::Output) -> Result<FfmpegFrameMetadata, NokhwaError> {
|
||||||
|
if self.deinitialized {
|
||||||
|
return Err(NokhwaError::Decoder("This decoder is deinitialized - it is no longer usable.".to_string()))
|
||||||
|
}
|
||||||
|
self.decoder.receive_frame(writing_output).map_err(|why| NokhwaError::Decoder(why.to_string()))?;
|
||||||
|
let avframe = match unsafe { writing_output.as_ref() } {
|
||||||
|
Some(r) => r,
|
||||||
|
None => return Err(NokhwaError::Decoder("failed to get refrenece to decoded frame".to_string()))
|
||||||
|
};
|
||||||
|
let meta = FfmpegFrameMetadata {
|
||||||
|
color_range: avframe.color_range,
|
||||||
|
pixel_format: unsafe { transmute::<i32, AVPixelFormat>(avframe.format) },
|
||||||
|
resolution: Resolution::new(avframe.height as u32, avframe.width as u32),
|
||||||
|
};
|
||||||
|
Ok(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preferred_buffer_min_size(&mut self, camera_format: &Option<CameraFormat>) -> Result<Option<usize>, NokhwaError> {
|
||||||
|
let (width, height, pixel_format) = match camera_format {
|
||||||
|
Some(fmt) => {(
|
||||||
|
fmt.width(),
|
||||||
|
fmt.height(),
|
||||||
|
convert_frame_format_to_pixfmt(fmt.format()))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
( self.config().resolution.width(), self.config.resolution.height(), convert_frame_format_to_pixfmt(&self.config().frame_format)
|
||||||
|
) }
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = unsafe { av_image_get_buffer_size(pixel_format, width as i32, height as i32, 1) };
|
||||||
|
Ok(Some(size as usize))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinitialize(&mut self) -> Result<(), NokhwaError> {
|
||||||
|
dealloc_av_params(&mut self.temp_cfg);
|
||||||
|
if self.deinitialized {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
self.deinitialized = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_format_to_codec_id(frame_format: &FrameFormat) -> Option<Id> {
|
||||||
|
match frame_format {
|
||||||
|
FrameFormat::H265 => Some(Id::H265),
|
||||||
|
FrameFormat::H264 => Some(Id::H264),
|
||||||
|
FrameFormat::AVC1 => Some(Id::H264),
|
||||||
|
FrameFormat::H263 => Some(Id::H263),
|
||||||
|
FrameFormat::AV1 => Some(Id::AV1),
|
||||||
|
FrameFormat::MPEG_1 => Some(Id::MPEG1VIDEO),
|
||||||
|
FrameFormat::MPEG_2 => Some(Id::MPEG2VIDEO),
|
||||||
|
FrameFormat::MPEG_4 => Some(Id::MPEG4),
|
||||||
|
FrameFormat::MJPEG => Some(Id::MJPEG),
|
||||||
|
FrameFormat::XviD => Some(Id::MPEG4),
|
||||||
|
FrameFormat::VP8 => Some(Id::VP8),
|
||||||
|
FrameFormat::VP9 => Some(Id::VP9),
|
||||||
|
FrameFormat::Ayuv_32 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Yuyv_4_2_2 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Yvyu_4_2_2 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Uyvy_4_2_2 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::NV12 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::NV21 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Luma_8 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Luma_10 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Luma_12 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Luma_14 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Luma_16 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Rgb_3_3_2 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Rgb_5_5_5 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Rgb_5_6_5 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Rgb_8_8_8 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Argb_8_8_8_8 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Rgba_8_8_8_8 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Bgr_3_3_2 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Bgr_5_5_5 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Bgr_5_6_5 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Bgr_8_8_8 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Abgr_8_8_8_8 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Bgra_8_8_8_8 => Some(Id::RAWVIDEO),
|
||||||
|
FrameFormat::Custom(c) => {
|
||||||
|
if let CustomFrameFormat::U32(c_id) = c {
|
||||||
|
// SAFETY: /shrug
|
||||||
|
let av_codec_id: AVCodecID = unsafe {
|
||||||
|
transmute(*c_id)
|
||||||
|
};
|
||||||
|
Some(av_codec_id.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_frame_format_to_pixfmt(frame_format: &FrameFormat) -> AVPixelFormat {
|
||||||
|
match frame_format {
|
||||||
|
// does FFMPEG not support 32bpp 4:4:4 packed AYUV?
|
||||||
|
FrameFormat::NV24 => AVPixelFormat::AV_PIX_FMT_NV24,
|
||||||
|
FrameFormat::NV42 => AVPixelFormat::AV_PIX_FMT_NV42,
|
||||||
|
FrameFormat::Yuyv_4_2_2 => AVPixelFormat::AV_PIX_FMT_YUYV422,
|
||||||
|
FrameFormat::Uyvy_4_2_2 => AVPixelFormat::AV_PIX_FMT_UYVY422,
|
||||||
|
FrameFormat::Yvyu_4_2_2 => AVPixelFormat::AV_PIX_FMT_YVYU422,
|
||||||
|
FrameFormat::Y210 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_Y210LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_Y210BE }
|
||||||
|
},
|
||||||
|
FrameFormat::Y216 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_YUV422P16LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_YUV422P16BE }
|
||||||
|
}
|
||||||
|
FrameFormat::NV16 => AVPixelFormat::AV_PIX_FMT_NV16,
|
||||||
|
FrameFormat::Yuv_4_2_0 => AVPixelFormat::AV_PIX_FMT_YUV420P,
|
||||||
|
FrameFormat::NV12 => AVPixelFormat::AV_PIX_FMT_NV12,
|
||||||
|
FrameFormat::NV21 => AVPixelFormat::AV_PIX_FMT_NV21,
|
||||||
|
FrameFormat::P010 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_P010LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_P010BE }
|
||||||
|
},
|
||||||
|
FrameFormat::P012 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_P012LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_P012BE }
|
||||||
|
},
|
||||||
|
FrameFormat::Y411Planar => AVPixelFormat::AV_PIX_FMT_YUV411P,
|
||||||
|
FrameFormat::Luma_8 => AVPixelFormat::AV_PIX_FMT_GRAY8,
|
||||||
|
FrameFormat::Luma_10 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_GRAY10LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_GRAY10BE }
|
||||||
|
},
|
||||||
|
FrameFormat::Luma_12 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_GRAY12LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_GRAY12BE }
|
||||||
|
},
|
||||||
|
FrameFormat::Luma_14 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_GRAY14LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_GRAY14BE }
|
||||||
|
},
|
||||||
|
FrameFormat::Luma_16 | FrameFormat::Depth_16 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_GRAY16LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_GRAY16BE }
|
||||||
|
},
|
||||||
|
FrameFormat::Rgb_3_3_2 => AVPixelFormat::AV_PIX_FMT_RGB8,
|
||||||
|
FrameFormat::Rgb_5_6_5 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_RGB565LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_RGB565BE }
|
||||||
|
}
|
||||||
|
FrameFormat::Rgb_5_5_5 => if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_RGB555LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_RGB565BE }
|
||||||
|
FrameFormat::Rgb_8_8_8 => AVPixelFormat::AV_PIX_FMT_RGB24,
|
||||||
|
FrameFormat::Argb_8_8_8_8 => AVPixelFormat::AV_PIX_FMT_ARGB,
|
||||||
|
FrameFormat::Rgba_8_8_8_8 => AVPixelFormat::AV_PIX_FMT_RGBA,
|
||||||
|
FrameFormat::Bgr_3_3_2 => AVPixelFormat::AV_PIX_FMT_BGR8,
|
||||||
|
FrameFormat::Bgr_5_6_5 => {
|
||||||
|
if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_BGR565LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_BGR565BE }
|
||||||
|
}
|
||||||
|
FrameFormat::Bgr_5_5_5 => if is_little_endian() {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_BGR555LE } else {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_BGR555BE }
|
||||||
|
FrameFormat::Bgr_8_8_8 => AVPixelFormat::AV_PIX_FMT_BGR24,
|
||||||
|
FrameFormat::Abgr_8_8_8_8 => AVPixelFormat::AV_PIX_FMT_ABGR,
|
||||||
|
FrameFormat::Bgra_8_8_8_8 => AVPixelFormat::AV_PIX_FMT_BGRA,
|
||||||
|
_ => AVPixelFormat::AV_PIX_FMT_RGB24,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FfmpegDecoderConfig {
|
||||||
|
pub frame_format: FrameFormat,
|
||||||
|
pub codec_tag: u32,
|
||||||
|
#[doc = " Extra binary data needed for initializing the decoder, codec-dependent.\n\n Must be allocated with av_malloc() and will be freed by\n avcodec_parameters_free(). The allocated size of extradata must be at\n least extradata_size + AV_INPUT_BUFFER_PADDING_SIZE, with the padding\n bytes zeroed."]
|
||||||
|
pub extra_data: Option<*mut u8>,
|
||||||
|
#[doc = " Size of the extradata content in bytes."]
|
||||||
|
pub extra_data_size: usize,
|
||||||
|
#[doc = " Additional data associated with the entire stream.\n\n Should be allocated with av_packet_side_data_new() or\n av_packet_side_data_add(), and will be freed by avcodec_parameters_free()."]
|
||||||
|
pub coded_side_data: Option<*mut AVPacketSideData>,
|
||||||
|
#[doc = " Amount of entries in @ref coded_side_data."]
|
||||||
|
pub coded_side_data_size: usize,
|
||||||
|
#[doc = " - video: the pixel format, the value corresponds to enum AVPixelFormat.\n - audio: the sample format, the value corresponds to enum AVSampleFormat."]
|
||||||
|
pub pix_fmt: Option<AVPixelFormat>,
|
||||||
|
#[doc = " Codec-specific bitstream restrictions that the stream conforms to."]
|
||||||
|
pub profile: i32,
|
||||||
|
pub level: i32,
|
||||||
|
pub resolution: Resolution,
|
||||||
|
#[doc = " Video only. The aspect ratio (width / height) which a single pixel\n should have when displayed.\n\n When the aspect ratio is unknown / undefined, the numerator should be\n set to 0 (the denominator may have any value)."]
|
||||||
|
pub sample_aspect_ratio: AVRational,
|
||||||
|
#[doc = " Video only. Number of frames per second, for streams with constant frame\n durations. Should be set to { 0, 1 } when some frames have differing\n durations or if the value is not known.\n\n @note This field correponds to values that are stored in codec-level\n headers and is typically overridden by container/transport-layer\n timestamps, when available. It should thus be used only as a last resort,\n when no higher-level timing information is available."]
|
||||||
|
pub frame_rate: FrameRate,
|
||||||
|
#[doc = " Video only. The order of the fields in interlaced video."]
|
||||||
|
pub field_order: AVFieldOrder,
|
||||||
|
#[doc = " Video only. Additional colorspace characteristics."]
|
||||||
|
pub color_range: AVColorRange,
|
||||||
|
pub color_primaries: AVColorPrimaries,
|
||||||
|
pub color_trc: AVColorTransferCharacteristic,
|
||||||
|
pub color_space: AVColorSpace,
|
||||||
|
pub chroma_location: AVChromaLocation,
|
||||||
|
#[doc = " Video only. Number of delayed frames."]
|
||||||
|
pub video_delay: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FfmpegDecoderConfig {
|
||||||
|
pub fn with_camera_format(camera_format: &CameraFormat) -> Self {
|
||||||
|
FfmpegDecoderConfig::from(*camera_format)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_avcodec_params(&self) -> Result<AVCodecParameters, NokhwaError> {
|
||||||
|
let mut av_codec_params = unsafe { avcodec_parameters_alloc().read() };
|
||||||
|
av_codec_params.codec_type = AVMediaType::AVMEDIA_TYPE_VIDEO;
|
||||||
|
if let Some(extra_data) = self.extra_data {
|
||||||
|
if extra_data.is_null() {
|
||||||
|
return Err(NokhwaError::Decoder("extra data is nullptr!".to_string()))
|
||||||
|
}
|
||||||
|
av_codec_params.extradata = extra_data;
|
||||||
|
av_codec_params.extradata_size = self.extra_data_size as i32;
|
||||||
|
}
|
||||||
|
if let Some(side_data) = self.coded_side_data {
|
||||||
|
if side_data.is_null() {
|
||||||
|
return Err(NokhwaError::Decoder("side data is nullptr!".to_string()))
|
||||||
|
}
|
||||||
|
av_codec_params.coded_side_data = side_data;
|
||||||
|
av_codec_params.nb_coded_side_data = self.coded_side_data_size as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(id) = convert_format_to_codec_id(&self.frame_format) {
|
||||||
|
av_codec_params.codec_id = id.into();
|
||||||
|
|
||||||
|
if let Some(pixfmt) = self.pix_fmt {
|
||||||
|
av_codec_params.format = pixfmt as i32;
|
||||||
|
} else {
|
||||||
|
let pixel_format = convert_frame_format_to_pixfmt(&self.frame_format);
|
||||||
|
av_codec_params.format = pixel_format as i32;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(NokhwaError::Decoder("Failed to convert frameformat to id".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
av_codec_params.profile = self.profile;
|
||||||
|
av_codec_params.level = self.level;
|
||||||
|
|
||||||
|
av_codec_params.width = self.resolution.width() as i32;
|
||||||
|
av_codec_params.height = self.resolution.height() as i32;
|
||||||
|
|
||||||
|
av_codec_params.sample_aspect_ratio = self.sample_aspect_ratio;
|
||||||
|
|
||||||
|
av_codec_params.framerate = AVRational { num: self.frame_rate.numerator(), den: self.frame_rate.numerator() };
|
||||||
|
|
||||||
|
av_codec_params.field_order = self.field_order;
|
||||||
|
|
||||||
|
av_codec_params.color_range = self.color_range;
|
||||||
|
|
||||||
|
av_codec_params.color_primaries = self.color_primaries;
|
||||||
|
|
||||||
|
av_codec_params.color_trc = self.color_trc;
|
||||||
|
|
||||||
|
av_codec_params.color_space = self.color_space;
|
||||||
|
|
||||||
|
av_codec_params.chroma_location = self.chroma_location;
|
||||||
|
|
||||||
|
av_codec_params.video_delay = self.video_delay;
|
||||||
|
|
||||||
|
Ok(av_codec_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SAFETY: the user is responsible for freeing this return value
|
||||||
|
pub fn as_ptr(&self) -> Result<*mut AVCodecParameters, NokhwaError> {
|
||||||
|
Ok(&mut self.as_avcodec_params()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CameraFormat> for FfmpegDecoderConfig {
|
||||||
|
fn from(value: CameraFormat) -> Self {
|
||||||
|
Self {
|
||||||
|
frame_format: *value.format(),
|
||||||
|
codec_tag: 0,
|
||||||
|
extra_data: None,
|
||||||
|
extra_data_size: 0,
|
||||||
|
coded_side_data: None,
|
||||||
|
coded_side_data_size: 0,
|
||||||
|
pix_fmt: None,
|
||||||
|
profile: 0,
|
||||||
|
level: 0,
|
||||||
|
resolution: *value.resolution(),
|
||||||
|
sample_aspect_ratio: AVRational { num: 0, den: 1 },
|
||||||
|
frame_rate: *value.frame_rate(),
|
||||||
|
field_order: AVFieldOrder::AV_FIELD_UNKNOWN,
|
||||||
|
color_range: AVColorRange::AVCOL_RANGE_UNSPECIFIED,
|
||||||
|
color_primaries: AVColorPrimaries::AVCOL_PRI_RESERVED0,
|
||||||
|
color_trc: AVColorTransferCharacteristic::AVCOL_TRC_RESERVED0,
|
||||||
|
color_space: AVColorSpace::AVCOL_SPC_RGB,
|
||||||
|
chroma_location: AVChromaLocation::AVCHROMA_LOC_UNSPECIFIED,
|
||||||
|
video_delay: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for FfmpegCodec {}
|
||||||
|
|
||||||
|
unsafe impl Sync for FfmpegCodec {}
|
||||||
|
|
||||||
|
fn dealloc_av_params(avcodec_parameters: &mut Option<*mut AVCodecParameters>) {
|
||||||
|
if let Some(param) = avcodec_parameters {
|
||||||
|
if !param.is_null() {
|
||||||
|
unsafe {
|
||||||
|
let mut_param = param as *mut *mut AVCodecParameters;
|
||||||
|
avcodec_parameters_free(mut_param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*avcodec_parameters = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FfmpegCodec {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.deinitialize();
|
||||||
|
dealloc_av_params(&mut self.temp_cfg);
|
||||||
|
unsafe {
|
||||||
|
let mut ctx = self.decoder.as_mut_ptr();
|
||||||
|
if !ctx.is_null() {
|
||||||
|
avcodec_free_context(&mut ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if let Some(mut sws) = self.sws_context {
|
||||||
|
// sws_freeContext(&mut sws)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_endian<T>(little: T, not: T) -> T {
|
||||||
|
match is_little_endian() {
|
||||||
|
true => little,
|
||||||
|
false => not,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_endian = "little")]
|
||||||
|
const fn is_little_endian() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
#[cfg(not(target_endian = "little"))]
|
||||||
|
const fn is_little_endian() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(feature = "ffmpeg")]
|
||||||
|
pub mod ffmpeg;
|
||||||
Reference in New Issue
Block a user