add test images, add tests for yuv decoder

This commit is contained in:
l1npengtul
2025-09-09 23:19:15 +09:00
parent aabdaeb062
commit 97048c2d49
16 changed files with 810 additions and 656 deletions
+52 -40
View File
@@ -13,16 +13,16 @@ repository = "https://github.com/l1npengtul/nokhwa"
[workspace] [workspace]
members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "nokhwa-bindings-linux", "nokhwa-core", "nokhwa-decoders"] members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "nokhwa-bindings-linux", "nokhwa-core", "nokhwa-decoders"]
exclude = ["examples/*"]
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["rlib"]
[features] [features]
default = ["decoding-yuv","decoding-mozjpeg"] default = ["decoding-yuv","decoding-mjpeg"]
serialize = ["serde", "nokhwa-core/serialize"] serialize = ["serde", "nokhwa-core/serialize"]
decoding-yuv = ["mozjpeg"] decoding-yuv = ["nokhwa-decoders/yuv"]
decoding-mozjpeg = ["mozjpeg"] decoding-mjpeg = ["nokhwa-decoders/mjpeg"]
decoding-ffmpeg = ["nokhwa-decoders/ffmpeg"]
input-avfoundation = ["nokhwa-bindings-macos", "flume"] input-avfoundation = ["nokhwa-bindings-macos", "flume"]
input-msmf = ["nokhwa-bindings-windows"] input-msmf = ["nokhwa-bindings-windows"]
input-v4l = ["nokhwa-bindings-linux"] input-v4l = ["nokhwa-bindings-linux"]
@@ -31,7 +31,7 @@ input-native = ["input-avfoundation", "input-v4l", "input-msmf"]
# input-uvc = ["uvc", "uvc/vendor", "usb_enumeration", "lazy_static"] # input-uvc = ["uvc", "uvc/vendor", "usb_enumeration", "lazy_static"]
input-opencv = ["opencv", "opencv/rgb", "rgb", "nokhwa-core/opencv-mat"] input-opencv = ["opencv", "opencv/rgb", "rgb", "nokhwa-core/opencv-mat"]
input-jscam = [ "wasm-bindgen-futures", "wasm-rs-async-executor", "output-async", "js-sys", "web-sys", "serde-wasm-bindgen", "serde"] input-jscam = [ "wasm-bindgen-futures", "wasm-rs-async-executor", "output-async", "js-sys", "web-sys", "serde-wasm-bindgen", "serde"]
output-wgpu = ["wgpu", "nokhwa-core/wgpu-types"] output-wgpu = ["wgpu-types", "nokhwa-core/wgpu"]
#output-wasm = ["input-jscam"] #output-wasm = ["input-jscam"]
output-threaded = [] output-threaded = []
output-async = ["nokhwa-core/async", "async-trait"] output-async = ["nokhwa-core/async", "async-trait"]
@@ -43,44 +43,10 @@ test-fail-warning = []
[dependencies] [dependencies]
paste = "1.0" paste = "1.0"
[dependencies.mozjpeg]
version = "0.10"
optional = true
[dependencies.nokhwa-core] [dependencies.nokhwa-core]
version = "0.2" version = "0.2"
path = "nokhwa-core" path = "nokhwa-core"
[dependencies.serde]
version = "1.0"
optional = true
[dependencies.flume]
version = "0.11"
optional = true
[dependencies.image]
version = "0.25"
default-features = false
[dependencies.usb_enumeration]
version = "0.2"
optional = true
[dependencies.wgpu]
version = "25"
optional = true
[dependencies.opencv]
version = "0.94"
default-features = false
features = ["videoio"]
optional = true
[dependencies.rgb]
version = "0.8"
optional = true
[dependencies.nokhwa-bindings-windows] [dependencies.nokhwa-bindings-windows]
version = "0.4" version = "0.4"
path = "nokhwa-bindings-windows" path = "nokhwa-bindings-windows"
@@ -96,6 +62,37 @@ version = "0.2"
path = "nokhwa-bindings-linux" path = "nokhwa-bindings-linux"
optional = true optional = true
[dependencies.nokhwa-decoders]
path = "nokhwa-decoders"
optional = true
[dependencies.serde]
workspace = true
optional = true
[dependencies.flume]
version = "0.11"
optional = true
[dependencies.image]
workspace = true
[dependencies.usb_enumeration]
version = "0.2"
optional = true
[dependencies.wgpu-types]
workspace = true
optional = true
[dependencies.opencv]
workspace = true
optional = true
[dependencies.rgb]
version = "0.8"
optional = true
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3" version = "0.3"
features = [ features = [
@@ -144,3 +141,18 @@ optional = true
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["docs-only", "docs-nolink", "docs-features"] features = ["docs-only", "docs-nolink", "docs-features"]
[workspace.dependencies.image]
version = "0.25"
default-features = false
[workspace.dependencies.serde]
version = "1.0"
features = ["derive"]
[workspace.dependencies.wgpu-types]
version = "26"
[workspace.dependencies.opencv]
version = "0.95"
default-features = false
Generated
+6 -6
View File
@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1751498133, "lastModified": 1755020227,
"narHash": "sha256-QWJ+NQbMU+NcU2xiyo7SNox1fAuwksGlQhpzBl76g1I=", "narHash": "sha256-gGmm+h0t6rY88RPTaIm3su95QvQIVjAJx558YUG4Id8=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d55716bb59b91ae9d1ced4b1ccdea7a442ecbfdb", "rev": "695d5db1b8b20b73292501683a524e0bd79074fb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -62,11 +62,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1751510438, "lastModified": 1755052812,
"narHash": "sha256-m8PjOoyyCR4nhqtHEBP1tB/jF+gJYYguSZmUmVTEAQE=", "narHash": "sha256-Tjw2YP7Hz8+ibE8wJ+Ps65vh1lzAe5ozmoo9sdQ7rGg=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "7f415261f298656f8164bd636c0dc05af4e95b6b", "rev": "433023cba5f4fa66b8b0fdbb8f91d420c9cc2527",
"type": "github" "type": "github"
}, },
"original": { "original": {
+77 -37
View File
@@ -5,57 +5,97 @@
rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.url = "github:oxalica/rust-overlay";
}; };
outputs = { outputs =
self, {
nixpkgs, self,
rust-overlay, nixpkgs,
flake-utils, rust-overlay,
... flake-utils,
}: ...
}:
flake-utils.lib.eachDefaultSystem ( flake-utils.lib.eachDefaultSystem (
system: let system:
let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
overlays = [rust-overlay.overlays.default]; overlays = [ rust-overlay.overlays.default ];
config.allowUnfree = true;
}; };
rustbin = pkgs.rust-bin.selectLatestNightlyWith (toolchain: rustshell = pkgs.mkShell.override {
stdenv = pkgs.gccStdenv;
};
rustbin = pkgs.rust-bin.selectLatestNightlyWith (
toolchain:
toolchain.default.override { toolchain.default.override {
extensions = ["rust-src" "clippy" "rustfmt" "miri"]; extensions = [
}); "rust-src"
in { "clippy"
"rustfmt"
"miri"
"rust-analyzer"
];
}
);
in
{
formatter = pkgs.alejandra; formatter = pkgs.alejandra;
devShells.default = pkgs.mkShell { devShells.default = rustshell {
packages = [ packages = [
rustbin rustbin
] ++ (with pkgs; [ ]
llvmPackages.libclang.lib ++ (with pkgs; [
llvmPackages.clang llvmPackages_21.clangWithLibcAndBasicRtAndLibcxx
pkg-config pkg-config
cmake cmake
vcpkg vcpkg
rustPlatform.bindgenHook lldb
xmlstarlet rustPlatform.bindgenHook
opencv xmlstarlet
alsa-lib opencv
systemdLibs alsa-lib
cmake systemdLibs
fontconfig cmake
linuxHeaders fontconfig
v4l-utils linuxHeaders
libv4l v4l-utils
pipewire libv4l
rustup pipewire
ffmpeg-full rustup
nasm gcc
ffmpeg-full
nasm
libGL
flite
quirc
lcevcdec
xz
celt
opencore-amr
snappy
codec2
gsm
ilbc
lame
libtheora
libogg
twolame
vo-amrwbenc
vvenc
xavs
xvidcore
soxr
libvdpau
jetbrains.rust-rover
]); ]);
env.RUST_SRC_PATH = "${rustbin}/lib/rustlib/src/rust/library"; env.RUST_SRC_PATH = "${rustbin}/lib/rustlib/src/rust/library";
env.LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; env.LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
shellHook = let shellHook =
pathToRustProject = "/project/component[@name='RustProjectSettings']"; let
in pathToRustProject = "/project/component[@name='RustProjectSettings']";
in
'' ''
echo "WONDERHOOOOOY!!!!" echo "WONDERHOOOOOY!!!!"
xmlstarlet edit --inplace --update "${pathToRustProject}/option[@name='explicitPathToStdlib']/@value" --value "${rustbin}/lib/rustlib/src/rust/library" .idea/workspace.xml xmlstarlet edit --inplace --update "${pathToRustProject}/option[@name='explicitPathToStdlib']/@value" --value "${rustbin}/lib/rustlib/src/rust/library" .idea/workspace.xml
+4 -8
View File
@@ -29,7 +29,6 @@ typed-builder = "0.21"
compact_str = "0.9" compact_str = "0.9"
bytemuck = "1.23" bytemuck = "1.23"
smallmap = "1.4" smallmap = "1.4"
constcat = "0.6"
paste = "1.0" paste = "1.0"
[dependencies.num-rational] [dependencies.num-rational]
@@ -38,21 +37,18 @@ default-features = false
features = ["serde", "std"] features = ["serde", "std"]
[dependencies.image] [dependencies.image]
version = "0.25" workspace = true
default-features = false
[dependencies.serde] [dependencies.serde]
version = "1.0" workspace = true
features = ["derive"]
optional = true optional = true
[dependencies.wgpu-types] [dependencies.wgpu-types]
version = "25" workspace = true
optional = true optional = true
[dependencies.opencv] [dependencies.opencv]
version = "0.94" workspace = true
default-features = false
optional = true optional = true
[dependencies.async-trait] [dependencies.async-trait]
+1 -1
View File
@@ -1,7 +1,7 @@
use crate::error::NokhwaError; use crate::error::NokhwaError;
use crate::frame_buffer::FrameBuffer; use crate::frame_buffer::FrameBuffer;
use crate::image::{DecodedImage, NonFloatScalarWidth}; use crate::image::{DecodedImage, NonFloatScalarWidth};
use crate::types::{CameraFormat, Resolution}; use crate::types::{Resolution};
pub use image::{ImageBuffer, Pixel, Primitive}; pub use image::{ImageBuffer, Pixel, Primitive};
use std::fmt::Debug; use std::fmt::Debug;
+1 -1
View File
@@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
use crate::frame_format::FrameFormat; use crate::frame_format::FrameFormat;
use crate::platform::Backends;
use std::fmt::Debug; use std::fmt::Debug;
use thiserror::Error; use thiserror::Error;
use crate::types::Backends;
pub type NokhwaResult<T> = Result<T, NokhwaError>; pub type NokhwaResult<T> = Result<T, NokhwaError>;
+1 -1
View File
@@ -1,5 +1,5 @@
use bytemuck::Pod; use bytemuck::Pod;
use image::{ImageBuffer, Pixel, Primitive}; pub use image::{ImageBuffer, Pixel, Primitive};
use num_traits::{NumCast, PrimInt}; use num_traits::{NumCast, PrimInt};
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
+3 -18
View File
@@ -1,23 +1,8 @@
use crate::camera::Camera; use crate::camera::Camera;
use crate::error::NokhwaResult; use crate::error::NokhwaResult;
use crate::types::{CameraIndex, CameraInformation}; use crate::types::{Backends, CameraIndex, CameraInformation, QueriedCamera};
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum Backends {
Video4Linux2,
WebWASM,
AVFoundation,
MicrosoftMediaFoundation,
OpenCV,
Custom(&'static str),
}
impl Display for Backends {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
pub trait PlatformTrait { pub trait PlatformTrait {
const PLATFORM: Backends; const PLATFORM: Backends;
@@ -27,7 +12,7 @@ pub trait PlatformTrait {
fn check_permission_given(&mut self) -> bool; fn check_permission_given(&mut self) -> bool;
fn query(&mut self) -> NokhwaResult<Vec<CameraInformation>>; fn query(&mut self) -> NokhwaResult<Vec<QueriedCamera>>;
fn open(&mut self, index: CameraIndex) -> NokhwaResult<Self::Camera>; fn open(&mut self, index: CameraIndex) -> NokhwaResult<Self::Camera>;
@@ -47,7 +32,7 @@ pub trait AsyncPlatformTrait: PlatformTrait {
async fn await_permission(&mut self) -> NokhwaResult<()>; async fn await_permission(&mut self) -> NokhwaResult<()>;
async fn query_async(&mut self) -> NokhwaResult<Vec<CameraInformation>>; async fn query_async(&mut self) -> NokhwaResult<Vec<QueriedCamera>>;
async fn open_async(&mut self, index: &CameraIndex) -> NokhwaResult<Self::AsyncCamera>; async fn open_async(&mut self, index: &CameraIndex) -> NokhwaResult<Self::AsyncCamera>;
+51 -141
View File
@@ -44,9 +44,8 @@ impl CameraIndex {
pub fn as_string(&self) -> String { pub fn as_string(&self) -> String {
match self { match self {
CameraIndex::Index(i) => i.to_string(), CameraIndex::Index(i) => i.to_string(),
CameraIndex::String(s) => s.to_string(), CameraIndex::String(s) | CameraIndex::Stable(s) => s.to_string(),
CameraIndex::Stable(s) => s.to_string(), }
}
} }
/// Returns true if this [`CameraIndex`] contains an [`CameraIndex::Index`] /// Returns true if this [`CameraIndex`] contains an [`CameraIndex::Index`]
@@ -133,6 +132,7 @@ impl Resolution {
/// Get the x (width) of Resolution /// Get the x (width) of Resolution
#[must_use] #[must_use]
#[inline] #[inline]
#[deprecated]
pub fn x(self) -> u32 { pub fn x(self) -> u32 {
self.width self.width
} }
@@ -140,6 +140,7 @@ impl Resolution {
/// Get the y (height) of Resolution /// Get the y (height) of Resolution
#[must_use] #[must_use]
#[inline] #[inline]
#[deprecated]
pub fn y(self) -> u32 { pub fn y(self) -> u32 {
self.height self.height
} }
@@ -152,7 +153,7 @@ impl Resolution {
impl Display for Resolution { impl Display for Resolution {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}x{}", self.x(), self.y()) write!(f, "{}x{}", self.width(), self.height())
} }
} }
@@ -164,9 +165,9 @@ impl PartialOrd for Resolution {
impl Ord for Resolution { impl Ord for Resolution {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
match self.x().cmp(&other.x()) { match self.width().cmp(&other.width()) {
Ordering::Less => Ordering::Less, Ordering::Less => Ordering::Less,
Ordering::Equal => self.y().cmp(&other.y()), Ordering::Equal => self.height().cmp(&other.height()),
Ordering::Greater => Ordering::Greater, Ordering::Greater => Ordering::Greater,
} }
} }
@@ -174,11 +175,11 @@ impl Ord for Resolution {
impl Distance<u32> for Resolution { impl Distance<u32> for Resolution {
fn distance_from(&self, other: &Self) -> u32 { fn distance_from(&self, other: &Self) -> u32 {
let x1 = self.x(); let x1 = self.width();
let x2 = other.x(); let x2 = other.width();
let y1 = self.y(); let y1 = self.height();
let y2 = other.y(); let y2 = other.height();
(x2 - x1).pow(2) + (y2 - y1).pow(2) (x2 - x1).pow(2) + (y2 - y1).pow(2)
} }
@@ -188,8 +189,8 @@ impl Div for Resolution {
type Output = Resolution; type Output = Resolution;
fn div(self, rhs: Self) -> Self::Output { fn div(self, rhs: Self) -> Self::Output {
let x_div = self.x().div(rhs.x()); let x_div = self.width().div(rhs.width());
let y_div = self.y().div(rhs.y()); let y_div = self.height().div(rhs.height());
Resolution::new(x_div, y_div) Resolution::new(x_div, y_div)
} }
} }
@@ -198,8 +199,8 @@ impl Sub for Resolution {
type Output = Resolution; type Output = Resolution;
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
let x_sub = self.x().sub(rhs.x()); let x_sub = self.width().sub(rhs.width());
let y_sub = self.y().sub(rhs.y()); let y_sub = self.height().sub(rhs.height());
Resolution::new(x_sub, y_sub) Resolution::new(x_sub, y_sub)
} }
} }
@@ -208,8 +209,8 @@ impl Rem for Resolution {
type Output = Resolution; type Output = Resolution;
fn rem(self, rhs: Self) -> Self::Output { fn rem(self, rhs: Self) -> Self::Output {
let x_rem = self.x().rem(rhs.x()); let x_rem = self.width().rem(rhs.width());
let y_rem = self.y().rem(rhs.y()); let y_rem = self.height().rem(rhs.height());
Resolution::new(x_rem, y_rem) Resolution::new(x_rem, y_rem)
} }
} }
@@ -429,15 +430,15 @@ impl Display for CameraFormat {
} }
/// Information about a Camera e.g. its name. /// Information about a Camera e.g. its name.
/// `description` amd `misc` may contain information that may differ from backend to backend. Refer to each backend for details. /// `description` and `misc` may contain information that may differ from backend to backend. Refer to each backend for details.
/// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system. /// `stable_id` contains the stable ID that may be used to reopen the same device.
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd)] #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct CameraInformation { pub struct CameraInformation {
human_name: String, human_name: String,
description: String, description: String,
misc: String, misc: String,
index: CameraIndex, stable_id: Option<String>,
} }
impl CameraInformation { impl CameraInformation {
@@ -446,12 +447,12 @@ impl CameraInformation {
// 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!
// Finally fixed this insanity. Hopefully I didnt torment anyone by actually putting this in a stable release. // Finally fixed this insanity. Hopefully I didnt torment anyone by actually putting this in a stable release.
pub fn new(human_name: String, description: String, misc: String, index: CameraIndex) -> Self { pub fn new(human_name: String, description: String, misc: String, stable_id: Option<String>) -> Self {
CameraInformation { CameraInformation {
human_name, human_name,
description, description,
misc, misc,
index, stable_id,
} }
} }
@@ -459,146 +460,55 @@ impl CameraInformation {
#[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
pub fn human_name(&self) -> String { pub fn human_name(&self) -> &str {
self.human_name.clone() &self.human_name
}
/// Set the device info's human name.
/// # JS-WASM
/// This is exported as a `set_HumanReadableName`.
pub fn set_human_name(&mut self, human_name: &str) {
self.human_name = human_name.to_string();
} }
/// Get a reference to the device info's description. /// Get a reference to the device info's description.
/// # JS-WASM
/// This is exported as a `get_Description`.
#[must_use] #[must_use]
pub fn description(&self) -> &str { pub fn description(&self) -> &str {
self.description.borrow() &self.description
}
/// Set the device info's description.
/// # JS-WASM
/// This is exported as a `set_Description`.
pub fn set_description(&mut self, description: &str) {
self.description = description.to_string();
} }
/// Get a reference to the device info's misc. /// Get a reference to the device info's misc.
/// # JS-WASM
/// This is exported as a `get_MiscString`.
#[must_use] #[must_use]
pub fn misc(&self) -> String { pub fn misc(&self) -> &str {
self.misc.clone() &self.misc
} }
/// Set the device info's misc. #[must_use] pub fn stable_id(&self) -> Option<&str> {
/// # JS-WASM self.stable_id.as_deref()
/// This is exported as a `set_MiscString`.
pub fn set_misc(&mut self, misc: &str) {
self.misc = misc.to_string();
} }
/// Get a reference to the device info's index.
/// # JS-WASM
/// This is exported as a `get_Index`.
#[must_use]
pub fn index(&self) -> &CameraIndex {
&self.index
}
/// Set the device info's index.
/// # JS-WASM
/// This is exported as a `set_Index`.
pub fn set_index(&mut self, index: CameraIndex) {
self.index = index;
}
// /// Gets the device info's index as an `u32`.
// /// # Errors
// /// If the index is not parsable as a `u32`, this will error.
// /// # JS-WASM
// /// This is exported as `get_Index_Int`
// #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))]
// pub fn index_num(&self) -> Result<u32, NokhwaError> {
// match &self.index {
// CameraIndex::Index(i) => Ok(*i),
// CameraIndex::String(s) => match s.parse::<u32>() {
// Ok(p) => Ok(p),
// Err(why) => Err(NokhwaError::GetPropertyError {
// property: "index-int".to_string(),
// error: why.to_string(),
// }),
// },
// }
// }
} }
impl Display for CameraInformation { impl Display for CameraInformation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"Name: {}, Description: {}, Extra: {}, Index: {}", "Name: {}, Description: {}, Extra: {}, Stable Index: {:?}",
self.human_name, self.description, self.misc, self.index self.human_name, self.description, self.misc, self.stable_id
) )
} }
} }
// fn step_chk(val: i64, default: i64, step: i64) -> Result<(), NokhwaError> { #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
// if (val - default) % step != 0 { pub enum Backends {
// return Err(NokhwaError::StructureError { Video4Linux2,
// structure: "Value".to_string(), WebWASM,
// error: "Doesnt fit step".to_string(), AVFoundation,
// }); MicrosoftMediaFoundation,
// } OpenCV,
// Ok(()) Custom(&'static str),
// } }
// /// The list of known capture backends to the library. <br> impl Display for Backends {
// /// - `Auto` - Use automatic selection. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// /// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` write!(f, "{self:?}")
// /// - `Video4Linux` - `Video4Linux2`, a linux specific backend. }
// /// - `UniversalVideoClass` - ***DEPRECATED*** Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use. }
// /// - `MediaFoundation` - Microsoft Media Foundation, Windows only,
// /// - `OpenCv` - Uses `OpenCV` to capture. Platform agnostic. #[derive(Clone, Debug, PartialOrd, PartialEq)]
// /// - `GStreamer` - ***DEPRECATED*** Uses `GStreamer` RTP to capture. Platform agnostic. pub struct QueriedCamera {
// /// - `Browser` - Uses browser APIs to capture from a webcam. pub index: CameraIndex,
// pub enum SelectableBackend { pub information: CameraInformation
// Auto, }
// Custom(&'static str),
// AVFoundation,
// Video4Linux,
// UniversalVideoClass,
// MediaFoundation,
// OpenCv,
// GStreamer,
// Browser,
// }
//
// /// The list of known capture backends to the library. <br>
// /// - `AVFoundation` - Uses `AVFoundation` on `MacOSX`
// /// - `Video4Linux` - `Video4Linux2`, a linux specific backend.
// /// - `UniversalVideoClass` - ***DEPRECATED*** Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use.
// /// - `MediaFoundation` - Microsoft Media Foundation, Windows only,
// /// - `OpenCv` - Uses `OpenCV` to capture. Platform agnostic.
// /// - `GStreamer` - ***DEPRECATED*** Uses `GStreamer` RTP to capture. Platform agnostic.
// /// - `Browser` - Uses browser APIs to capture from a webcam.
// #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
// #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
// pub enum ApiBackend {
// Custom(&'static str),
// AVFoundation,
// Video4Linux,
// UniversalVideoClass,
// MediaFoundation,
// OpenCv,
// GStreamer,
// Browser,
// }
//
// impl Display for ApiBackend {
// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// write!(f, "{self:?}")
// }
// }
+5 -6
View File
@@ -5,10 +5,9 @@ edition = "2024"
[features] [features]
ffmpeg = ["ffmpeg-the-third"] ffmpeg = ["ffmpeg-the-third"]
av1 = ["rav1e"]
yuyv = ["dcv-color-primitives", "yuv"] yuyv = ["dcv-color-primitives", "yuv"]
mjpeg = ["zune-jpeg", "zune-core"] mjpeg = ["zune-jpeg", "zune-core"]
static = ["ffmpeg-the-third/static"] #static = ["ffmpeg-the-third/static"]
async = [] async = []
[dependencies] [dependencies]
@@ -22,10 +21,6 @@ path = "../nokhwa-core"
version = "3.0" version = "3.0"
optional = true optional = true
[dependencies.rav1e]
version = "0.8"
optional = true
[dependencies.yuv] [dependencies.yuv]
version = "0.8" version = "0.8"
optional = true optional = true
@@ -41,3 +36,7 @@ optional = true
[dependencies.zune-core] [dependencies.zune-core]
version = "0.5.0-rc2" version = "0.5.0-rc2"
optional = true optional = true
[dev-dependencies.image]
workspace = true
features = ["png", "jpeg", "avif"]
+51 -30
View File
@@ -4,11 +4,10 @@ use ffmpeg_the_third::codec::{Context, Id, Parameters};
use ffmpeg_the_third::decoder::Video; use ffmpeg_the_third::decoder::Video;
use ffmpeg_the_third::ffi::{ use ffmpeg_the_third::ffi::{
AVChromaLocation, AVCodecID, AVCodecParameters, AVColorPrimaries, AVColorRange, AVColorSpace, AVChromaLocation, AVCodecID, AVCodecParameters, AVColorPrimaries, AVColorRange, AVColorSpace,
AVColorTransferCharacteristic, AVFieldOrder, AVMediaType, AVPacket, AVColorTransferCharacteristic, AVFieldOrder, AVMediaType, AVPacket, AVPixelFormat, AVRational,
AVPixelFormat, AVRational, SwsContext, av_frame_alloc, av_frame_move_ref, SwsContext, av_frame_alloc, av_frame_move_ref, av_image_copy_to_buffer, av_image_fill_arrays,
av_image_copy_to_buffer, av_image_fill_arrays, av_image_get_buffer_size, avcodec_free_context, av_image_get_buffer_size, avcodec_free_context, avcodec_parameters_alloc,
avcodec_parameters_alloc, avcodec_parameters_free, sws_freeContext, sws_getContext, avcodec_parameters_free, sws_freeContext, sws_getContext, sws_scale_frame,
sws_scale_frame,
}; };
use ffmpeg_the_third::packet::{Borrow, Ref}; use ffmpeg_the_third::packet::{Borrow, Ref};
use ffmpeg_the_third::{Frame, decoder, packet::Packet}; use ffmpeg_the_third::{Frame, decoder, packet::Packet};
@@ -72,7 +71,7 @@ impl Decoder for FfmpegDecoder {
&mut self, &mut self,
to_decode: FrameBuffer, to_decode: FrameBuffer,
mut buffer: impl AsMut<[u8]>, mut buffer: impl AsMut<[u8]>,
_destination_format: Option<Self::DestinationFormatHint> _destination_format: Option<Self::DestinationFormatHint>,
) -> Result<Self::OutputMeta, NokhwaError> { ) -> Result<Self::OutputMeta, NokhwaError> {
// TODO: add an extra zippy happy path for rgb/bgr/luma // TODO: add an extra zippy happy path for rgb/bgr/luma
let (frame, metadata) = self.receive_decoded_frame(to_decode)?; let (frame, metadata) = self.receive_decoded_frame(to_decode)?;
@@ -108,19 +107,20 @@ impl Decoder for FfmpegDecoder {
where where
<P as Pixel>::Subpixel: NonFloatScalarWidth, <P as Pixel>::Subpixel: NonFloatScalarWidth,
{ {
let destination_format = let destination_format = pixel_to_destination_px_fmt::<P>().ok_or(
pixel_to_destination_px_fmt::<P>() NokhwaError::DecoderInvalidBuffer("Unsupported Pixel Type".to_string()),
.ok_or(NokhwaError::DecoderInvalidBuffer("Unsupported Pixel Type".to_string()))?; )?;
let buffer = buffer.as_mut(); let buffer = buffer.as_mut();
let estimated_size = let estimated_size = self.codec.preferred_buffer_min_size(&None)?.ok_or(
self.codec NokhwaError::DecoderInvalidBuffer(
.preferred_buffer_min_size(&None)? "failed to estimate decoder buffer.length".to_string(),
.ok_or(NokhwaError::DecoderInvalidBuffer( ),
"failed to estimate decoder buffer.length".to_string(), )?;
))?;
if buffer.len() < estimated_size { if buffer.len() < estimated_size {
return Err(NokhwaError::DecoderInvalidBuffer("buffer too small!".to_string())); return Err(NokhwaError::DecoderInvalidBuffer(
"buffer too small!".to_string(),
));
} }
let (mut frame, decoded_meta) = self.receive_decoded_frame(to_decode)?; let (mut frame, decoded_meta) = self.receive_decoded_frame(to_decode)?;
@@ -198,7 +198,12 @@ impl Decoder for FfmpegDecoder {
{ {
let min_size = self.output_decoder_min_size_pixel::<P>(self.config().resolution); let min_size = self.output_decoder_min_size_pixel::<P>(self.config().resolution);
let mut buffer: Vec<P::Subpixel> = vec![<P::Subpixel>::DEFAULT_MIN_VALUE; min_size]; 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::DecoderInvalidBuffer(why.to_string()))?, None)?; let meta = self.decode_to_buffer(
to_decode,
try_cast_slice_mut(&mut buffer)
.map_err(|why| NokhwaError::DecoderInvalidBuffer(why.to_string()))?,
None,
)?;
Ok(DecodedImage::new( Ok(DecodedImage::new(
ImageBuffer::from_vec( ImageBuffer::from_vec(
self.codec.config.resolution.width(), self.codec.config.resolution.width(),
@@ -212,9 +217,19 @@ impl Decoder for FfmpegDecoder {
)) ))
} }
fn output_decoder_min_size(&self, resolution: Resolution, destination_format: Self::DestinationFormatHint) -> usize { fn output_decoder_min_size(
let size = &self,
unsafe { av_image_get_buffer_size(destination_format, resolution.width() as i32, resolution.height() as i32, 1) }; resolution: Resolution,
destination_format: Self::DestinationFormatHint,
) -> usize {
let size = unsafe {
av_image_get_buffer_size(
destination_format,
resolution.width() as i32,
resolution.height() as i32,
1,
)
};
size as usize size as usize
} }
@@ -353,11 +368,13 @@ pub struct FfmpegCodec {
impl FfmpegCodec { impl FfmpegCodec {
fn new(config: <FfmpegCodec as Codec>::Config) -> Result<Self, NokhwaError> { fn new(config: <FfmpegCodec as Codec>::Config) -> Result<Self, NokhwaError> {
let id = convert_format_to_codec_id(&config.frame_format) let id = convert_format_to_codec_id(&config.frame_format).ok_or(
.ok_or(NokhwaError::DecoderUnsupportedFrameFormat(config.frame_format))?; NokhwaError::DecoderUnsupportedFrameFormat(config.frame_format),
)?;
let codec = let codec = decoder::find(id).ok_or(NokhwaError::DecoderInitializationError(
decoder::find(id).ok_or(NokhwaError::DecoderInitializationError("Failed to find codec".to_string()))?; "Failed to find codec".to_string(),
))?;
let context = unsafe { let context = unsafe {
let ptr = ffmpeg_the_third::ffi::avcodec_alloc_context3(codec.as_ptr()); let ptr = ffmpeg_the_third::ffi::avcodec_alloc_context3(codec.as_ptr());
@@ -375,9 +392,11 @@ impl FfmpegCodec {
.map_err(|why| NokhwaError::Decoder(why.to_string()))?; .map_err(|why| NokhwaError::Decoder(why.to_string()))?;
video video
.set_parameters(unsafe { .set_parameters(unsafe {
Parameters::from_raw(config.as_ptr()?).ok_or(NokhwaError::DecoderInitializationError( Parameters::from_raw(config.as_ptr()?).ok_or(
"Failed to convert parameters".to_string(), NokhwaError::DecoderInitializationError(
))? "Failed to convert parameters".to_string(),
),
)?
}) })
.map_err(|why| NokhwaError::Decoder(why.to_string()))?; .map_err(|why| NokhwaError::Decoder(why.to_string()))?;
@@ -416,9 +435,11 @@ impl Codec for FfmpegCodec {
let mut temp_config = config.as_avcodec_params()?; let mut temp_config = config.as_avcodec_params()?;
self.decoder self.decoder
.set_parameters(unsafe { .set_parameters(unsafe {
Parameters::from_raw(&mut temp_config).ok_or(NokhwaError::DecoderInvalidConfiguration( Parameters::from_raw(&mut temp_config).ok_or(
"Failed to convert parameters".to_string(), NokhwaError::DecoderInvalidConfiguration(
))? "Failed to convert parameters".to_string(),
),
)?
}) })
.map_err(|why| NokhwaError::DecoderInvalidConfiguration(why.to_string()))?; .map_err(|why| NokhwaError::DecoderInvalidConfiguration(why.to_string()))?;
self.config = config; self.config = config;
+2
View File
@@ -1,3 +1,5 @@
extern crate core;
#[cfg(feature = "ffmpeg")] #[cfg(feature = "ffmpeg")]
pub mod ffmpeg; pub mod ffmpeg;
#[cfg(feature = "mjpeg")] #[cfg(feature = "mjpeg")]
+74 -41
View File
@@ -1,15 +1,16 @@
use zune_core::bytestream::ZCursor; use bytemuck::cast_slice_mut;
use zune_core::colorspace::ColorSpace;
pub use zune_core::options::DecoderOptions;
use zune_jpeg::errors::DecodeErrors;
pub use zune_jpeg::ImageInfo;
use zune_jpeg::JpegDecoder;
use nokhwa_core::codec::Codec;
use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel}; use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel};
use nokhwa_core::error::NokhwaError; use nokhwa_core::error::NokhwaError;
use nokhwa_core::frame_buffer::FrameBuffer; use nokhwa_core::frame_buffer::FrameBuffer;
use nokhwa_core::image::Primitive;
use nokhwa_core::image::{DecodedImage, NonFloatScalarWidth}; use nokhwa_core::image::{DecodedImage, NonFloatScalarWidth};
use nokhwa_core::types::Resolution; use nokhwa_core::types::Resolution;
use zune_core::bytestream::ZCursor;
use zune_core::colorspace::ColorSpace;
pub use zune_core::options::DecoderOptions;
pub use zune_jpeg::ImageInfo;
use zune_jpeg::JpegDecoder;
use zune_jpeg::errors::DecodeErrors;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MJpegDecoder { pub struct MJpegDecoder {
@@ -30,7 +31,12 @@ impl Decoder for MJpegDecoder {
Ok(()) Ok(())
} }
fn decode_to_buffer(&mut self, to_decode: FrameBuffer, mut buffer: impl AsMut<[u8]>, destination_format_hint: Option<Self::DestinationFormatHint>) -> Result<Self::OutputMeta, NokhwaError> { fn decode_to_buffer(
&mut self,
to_decode: FrameBuffer,
mut buffer: impl AsMut<[u8]>,
destination_format_hint: Option<Self::DestinationFormatHint>,
) -> Result<Self::OutputMeta, NokhwaError> {
let buffer = buffer.as_mut(); let buffer = buffer.as_mut();
let cursor = ZCursor::new(to_decode.as_ref()); let cursor = ZCursor::new(to_decode.as_ref());
@@ -38,7 +44,10 @@ impl Decoder for MJpegDecoder {
let mut config = self.config.decoder_options; let mut config = self.config.decoder_options;
if let Some(dest_hint) = destination_format_hint { if let Some(dest_hint) = destination_format_hint {
config = self.config.decoder_options.jpeg_set_out_colorspace(dest_hint.into()); config = self
.config
.decoder_options
.jpeg_set_out_colorspace(dest_hint.into());
} }
decoder.set_options(config); decoder.set_options(config);
@@ -47,39 +56,64 @@ impl Decoder for MJpegDecoder {
let info = match decoder.info() { let info = match decoder.info() {
Some(i) => i, Some(i) => i,
None => return Err(NokhwaError::Decoder("??????? how did we get here".to_string())) None => {
return Err(NokhwaError::Decoder(
"??????? how did we get here".to_string(),
));
}
}; };
Ok(info.into()) Ok(info.into())
} }
fn decode_to_pixel_buffer<P: Pixel>(&mut self, to_decode: FrameBuffer, buffer: impl AsMut<[P::Subpixel]>) -> Result<Self::OutputMeta, NokhwaError> fn decode_to_pixel_buffer<P: Pixel>(
&mut self,
to_decode: FrameBuffer,
mut buffer: impl AsMut<[P::Subpixel]>,
) -> Result<Self::OutputMeta, NokhwaError>
where where
<P as Pixel>::Subpixel: NonFloatScalarWidth <P as Pixel>::Subpixel: NonFloatScalarWidth,
{ {
let hint = match pixel_to_colorspace::<P>() { let hint = match pixel_to_colorspace::<P>() {
Some(cs) => cs, Some(cs) => cs,
None => return Err(NokhwaError::DecoderUnsupportedDestinationPixelFormat(P::COLOR_MODEL, <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES)) None => {
return Err(NokhwaError::DecoderUnsupportedDestinationPixelFormat(
P::COLOR_MODEL,
<<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES,
));
}
}; };
let meta = self.decode_to_buffer(to_decode, buffer, Some(hint))?; let meta = self.decode_to_buffer(to_decode, cast_slice_mut(buffer.as_mut()), Some(hint))?;
Ok(meta) Ok(meta)
} }
fn decode<P: Pixel>(&mut self, to_decode: FrameBuffer) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError> fn decode<P: Pixel>(
&mut self,
to_decode: FrameBuffer,
) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError>
where where
<P as Pixel>::Subpixel: NonFloatScalarWidth <P as Pixel>::Subpixel: NonFloatScalarWidth,
{ {
let min_size = self.output_decoder_min_size_pixel::<P>(self.config.resolution); let min_size = self.output_decoder_min_size_pixel::<P>(self.config.resolution);
let mut buffer = vec![0_u8; min_size]; let mut out_buffer: Vec<P::Subpixel> = vec![P::Subpixel::DEFAULT_MAX_VALUE; min_size];
let output_metadata = self.decode_to_pixel_buffer(to_decode, buffer.as_mut_slice())?; let output_metadata =
let image_buffer = ImageBuffer::from_raw(output_metadata.resolution.width(), output_metadata.resolution.height(), buffer).ok_or(NokhwaError::Decoder("Failed to make imagebuffer".to_string()))?; self.decode_to_pixel_buffer::<P>(to_decode, out_buffer.as_mut_slice())?;
Ok(DecodedImage::new( let image_buffer = ImageBuffer::from_raw(
image_buffer, output_metadata.resolution.width(),
output_metadata output_metadata.resolution.height(),
)) out_buffer,
)
.ok_or(NokhwaError::Decoder(
"Failed to make imagebuffer".to_string(),
))?;
Ok(DecodedImage::new(image_buffer, output_metadata))
} }
fn output_decoder_min_size(&self, resolution: Resolution, destination_format: Self::DestinationFormatHint) -> usize { fn output_decoder_min_size(
&self,
resolution: Resolution,
destination_format: Self::DestinationFormatHint,
) -> usize {
let stride = match destination_format { let stride = match destination_format {
OutputColor::Rgb => 3, OutputColor::Rgb => 3,
OutputColor::RgbA => 4, OutputColor::RgbA => 4,
@@ -122,7 +156,7 @@ impl From<ImageInfo> for ImageMeta {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct MJpegOptions { pub struct MJpegOptions {
pub resolution: Resolution, pub resolution: Resolution,
pub decoder_options: DecoderOptions pub decoder_options: DecoderOptions,
} }
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] #[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
@@ -135,9 +169,9 @@ pub enum OutputColor {
LumaA, LumaA,
} }
impl Into<ColorSpace> for OutputColor { impl From<OutputColor> for ColorSpace {
fn into(self) -> ColorSpace { fn from(val: OutputColor) -> Self {
match self { match val {
OutputColor::Rgb => ColorSpace::RGB, OutputColor::Rgb => ColorSpace::RGB,
OutputColor::RgbA => ColorSpace::RGBA, OutputColor::RgbA => ColorSpace::RGBA,
OutputColor::Bgr => ColorSpace::BGR, OutputColor::Bgr => ColorSpace::BGR,
@@ -148,7 +182,11 @@ impl Into<ColorSpace> for OutputColor {
} }
} }
fn pixel_to_colorspace<P>() -> Option<OutputColor> where P: Pixel, <P as Pixel>::Subpixel: NonFloatScalarWidth { fn pixel_to_colorspace<P>() -> Option<OutputColor>
where
P: Pixel,
<P as Pixel>::Subpixel: NonFloatScalarWidth,
{
match P::COLOR_MODEL { match P::COLOR_MODEL {
"RGBA" => Some(OutputColor::RgbA), "RGBA" => Some(OutputColor::RgbA),
"RGB" => Some(OutputColor::Rgb), "RGB" => Some(OutputColor::Rgb),
@@ -162,12 +200,8 @@ fn pixel_to_colorspace<P>() -> Option<OutputColor> where P: Pixel, <P as Pixel>:
fn err_to_err(decode_errors: DecodeErrors) -> NokhwaError { fn err_to_err(decode_errors: DecodeErrors) -> NokhwaError {
match decode_errors { match decode_errors {
DecodeErrors::Format(fmt) => { DecodeErrors::Format(fmt) => NokhwaError::Decoder(fmt),
NokhwaError::Decoder(fmt) DecodeErrors::FormatStatic(fmt) => NokhwaError::Decoder(fmt.to_string()),
}
DecodeErrors::FormatStatic(fmt) => {
NokhwaError::Decoder(fmt.to_string())
}
DecodeErrors::IllegalMagicBytes(b) => { DecodeErrors::IllegalMagicBytes(b) => {
NokhwaError::DecoderInvalidFrameData(format!("bad magic bytes: {b}")) NokhwaError::DecoderInvalidFrameData(format!("bad magic bytes: {b}"))
} }
@@ -177,9 +211,10 @@ fn err_to_err(decode_errors: DecodeErrors) -> NokhwaError {
DecodeErrors::ZeroError => { DecodeErrors::ZeroError => {
NokhwaError::DecoderInvalidBuffer("image has zero width.".to_string()) NokhwaError::DecoderInvalidBuffer("image has zero width.".to_string())
} }
DecodeErrors::DqtError(e) | DecodeErrors::MCUError(e) | DecodeErrors::SosError(e) | DecodeErrors::SofError(e)=> { DecodeErrors::DqtError(e)
NokhwaError::Decoder(format!("error decoding: {e}")) | DecodeErrors::MCUError(e)
} | DecodeErrors::SosError(e)
| DecodeErrors::SofError(e) => NokhwaError::Decoder(format!("error decoding: {e}")),
DecodeErrors::Unsupported(why) => { DecodeErrors::Unsupported(why) => {
NokhwaError::DecoderInvalidConfiguration(format!("not supported: {why:?}")) NokhwaError::DecoderInvalidConfiguration(format!("not supported: {why:?}"))
} }
@@ -192,8 +227,6 @@ fn err_to_err(decode_errors: DecodeErrors) -> NokhwaError {
DecodeErrors::TooSmallOutput(a, b) => { DecodeErrors::TooSmallOutput(a, b) => {
NokhwaError::DecoderInvalidBuffer(format!("too small: {a},{b}")) NokhwaError::DecoderInvalidBuffer(format!("too small: {a},{b}"))
} }
DecodeErrors::IoErrors(io) => { DecodeErrors::IoErrors(io) => NokhwaError::Decoder(format!("io error: {io:?}")),
NokhwaError::Decoder(format!("io error: {io:?}"))
}
} }
} }
+475 -168
View File
@@ -1,15 +1,35 @@
use bytemuck::{cast_slice, cast_slice_mut, try_cast_slice_mut}; use bytemuck::{cast_slice, cast_slice_mut, try_cast_slice_mut};
use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel, Primitive};
use nokhwa_core::error::NokhwaError; use nokhwa_core::error::NokhwaError;
use nokhwa_core::frame_buffer::FrameBuffer; use nokhwa_core::frame_buffer::FrameBuffer;
use nokhwa_core::frame_format::FrameFormat; use nokhwa_core::frame_format::FrameFormat;
use nokhwa_core::types::{CameraFormat, Resolution};
use yuv::{ayuv_to_rgb, ayuv_to_rgba, p010_to_bgr, p010_to_bgra, p010_to_rgb, p010_to_rgb10, p010_to_rgba, p010_to_rgba10, p012_to_rgb12, p012_to_rgba12, uyvy422_to_bgr, uyvy422_to_bgra, uyvy422_to_rgb, uyvy422_to_rgb_p16, uyvy422_to_rgba, uyvy422_to_rgba_p16, vyuy422_to_bgr, vyuy422_to_bgra, vyuy422_to_rgb, vyuy422_to_rgb_p16, vyuy422_to_rgba, vyuy422_to_rgba_p16, yuv420_to_bgr, yuv420_to_bgra, yuv420_to_rgb, yuv420_to_rgba, yuv_nv12_to_bgr, yuv_nv12_to_bgra, yuv_nv12_to_rgb, yuv_nv12_to_rgba, yuv_nv16_to_bgr, yuv_nv16_to_bgra, yuv_nv16_to_rgb, yuv_nv16_to_rgba, yuv_nv21_to_bgr, yuv_nv21_to_bgra, yuv_nv21_to_rgb, yuv_nv21_to_rgba, yuv_nv24_to_bgr, yuv_nv24_to_bgra, yuv_nv24_to_rgb, yuv_nv24_to_rgba, yuv_nv42_to_bgr, yuv_nv42_to_bgra, yuv_nv42_to_rgb, yuv_nv42_to_rgba, yuv_nv61_to_bgr, yuv_nv61_to_bgra, yuv_nv61_to_rgb, yuv_nv61_to_rgba, yuyv422_to_bgr, yuyv422_to_bgra, yuyv422_to_rgb, yuyv422_to_rgb_p16, yuyv422_to_rgba, yuyv422_to_rgba_p16, yvyu422_to_bgr, yvyu422_to_bgra, yvyu422_to_rgb, yvyu422_to_rgb_p16, yvyu422_to_rgba, yvyu422_to_rgba_p16, YuvBiPlanarImage, YuvConversionMode, YuvPackedImage, YuvPlanarImage, YuvRange, YuvStandardMatrix};
use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel, Primitive};
use nokhwa_core::image::{DecodedImage, NonFloatScalarWidth}; use nokhwa_core::image::{DecodedImage, NonFloatScalarWidth};
use nokhwa_core::types::{CameraFormat, Resolution};
use yuv::{
YuvBiPlanarImage, YuvConversionMode, YuvPackedImage, YuvPlanarImage, YuvRange,
YuvStandardMatrix, ayuv_to_rgb, ayuv_to_rgba, p010_to_bgr, p010_to_bgra, p010_to_rgb,
p010_to_rgb10, p010_to_rgba, p010_to_rgba10, p012_to_rgb12, p012_to_rgba12, uyvy422_to_bgr,
uyvy422_to_bgra, uyvy422_to_rgb, uyvy422_to_rgb_p16, uyvy422_to_rgba, uyvy422_to_rgba_p16,
vyuy422_to_bgr, vyuy422_to_bgra, vyuy422_to_rgb, vyuy422_to_rgb_p16, vyuy422_to_rgba,
vyuy422_to_rgba_p16, yuv_nv12_to_bgr, yuv_nv12_to_bgra, yuv_nv12_to_rgb, yuv_nv12_to_rgba,
yuv_nv16_to_bgr, yuv_nv16_to_bgra, yuv_nv16_to_rgb, yuv_nv16_to_rgba, yuv_nv21_to_bgr,
yuv_nv21_to_bgra, yuv_nv21_to_rgb, yuv_nv21_to_rgba, yuv_nv24_to_bgr, yuv_nv24_to_bgra,
yuv_nv24_to_rgb, yuv_nv24_to_rgba, yuv_nv42_to_bgr, yuv_nv42_to_bgra, yuv_nv42_to_rgb,
yuv_nv42_to_rgba, yuv_nv61_to_bgr, yuv_nv61_to_bgra, yuv_nv61_to_rgb, yuv_nv61_to_rgba,
yuv420_to_bgr, yuv420_to_bgra, yuv420_to_rgb, yuv420_to_rgba, yuyv422_to_bgr, yuyv422_to_bgra,
yuyv422_to_rgb, yuyv422_to_rgb_p16, yuyv422_to_rgba, yuyv422_to_rgba_p16, yvyu422_to_bgr,
yvyu422_to_bgra, yvyu422_to_rgb, yvyu422_to_rgb_p16, yvyu422_to_rgba, yvyu422_to_rgba_p16,
};
pub struct YUVDecoder { pub struct YUVDecoder {
config: YUVConfig, config: YUVConfig,
stride_cache: Option<CachedStride>, }
impl YUVDecoder {
pub fn new(config: <Self as Decoder>::Config) -> Self {
Self { config }
}
} }
impl Decoder for YUVDecoder { impl Decoder for YUVDecoder {
@@ -23,85 +43,88 @@ impl Decoder for YUVDecoder {
fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError> { fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError> {
if !FrameFormat::YCBCR.contains(&config.yuv_type) { if !FrameFormat::YCBCR.contains(&config.yuv_type) {
return Err(NokhwaError::DecoderUnsupportedFrameFormat(config.yuv_type)) return Err(NokhwaError::DecoderUnsupportedFrameFormat(config.yuv_type));
} }
self.config = config; self.config = config;
self.stride_cache = None;
Ok(()) Ok(())
} }
fn decode_to_buffer(&mut self, to_decode: FrameBuffer<'_>, mut buffer: impl AsMut<[u8]>, destination_format: Option<Self::DestinationFormatHint>) -> Result<Self::OutputMeta, NokhwaError> { fn decode_to_buffer(
&mut self,
to_decode: FrameBuffer<'_>,
mut buffer: impl AsMut<[u8]>,
destination_format: Option<Self::DestinationFormatHint>,
) -> Result<Self::OutputMeta, NokhwaError> {
let destination_format = match destination_format { let destination_format = match destination_format {
Some(df) => df, Some(df) => df,
None => return Err(NokhwaError::DecoderDestinationHintRequired) None => return Err(NokhwaError::DecoderDestinationHintRequired),
}; };
let buffer = buffer.as_mut(); let buffer = buffer.as_mut();
if buffer.len() < self.output_decoder_min_size(self.config.resolution, destination_format) { if buffer.len() < self.output_decoder_min_size(self.config.resolution, destination_format) {
return Err(NokhwaError::DecoderInvalidBuffer("Too small!".to_string())) return Err(NokhwaError::DecoderInvalidBuffer("Too small!".to_string()));
} }
let stride = match self.stride_cache { let stride = figure_out_stride(self.config.yuv_type).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))?;
Some(c) => c, let byte_width = figure_out_byte_width(self.config.yuv_type).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))?;
None => {
self.stride_cache = figure_out_stride(self.config.yuv_type);
match self.stride_cache { let stride_3px = 3 *
Some(s) => s, self.config.resolution.width();
None => return Err(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type)), let stride_4px = 4 * self.config.resolution.width();
} let stride_3px_2w = 6 * self.config.resolution.width();
} let stride_4px_2w = 8 * self.config.resolution.width();
};
// todo: clean up ts into a macro </3 // todo: clean up ts into a macro </3
let decode_status = match stride { let decode_status = match stride {
CachedStride::Packed(stride) => { Stride::Packed(stride) => {
let image = prepare_to_packed_image(&to_decode, self.config.resolution, stride); let image = prepare_to_packed_image(&to_decode, self.config.resolution, byte_width, stride);
match self.config.yuv_type { match self.config.yuv_type {
FrameFormat::Ayuv_32 => { FrameFormat::Ayuv_32 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(ayuv_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix, self.config.premultiply_alpha)), YUVDestination::Rgb8 => Some(ayuv_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.premultiply_alpha)),
YUVDestination::Rgba8 => Some(ayuv_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix, self.config.premultiply_alpha)), YUVDestination::Rgba8 => Some(ayuv_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.premultiply_alpha)),
_ => None, _ => None,
} }
} }
FrameFormat::Yuyv_4_2_2 => { FrameFormat::Yuyv_4_2_2 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yuyv422_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Rgb8 => Some(yuyv422_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Rgba8 => Some(yuyv422_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Rgba8 => Some(yuyv422_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
YUVDestination::Rgb16 => Some(yuyv422_to_rgb_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), 6, 16, self.config.range, self.config.matrix)), YUVDestination::Rgb16 => Some(yuyv422_to_rgb_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, 16, self.config.range, self.config.matrix)),
YUVDestination::Rgba16 => Some(yuyv422_to_rgba_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), 6, 16, self.config.range, self.config.matrix)), YUVDestination::Rgba16 => Some(yuyv422_to_rgba_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, 16, self.config.range, self.config.matrix)),
YUVDestination::Bgr8 => Some(yuyv422_to_bgr(&image, buffer, 8, self.config.range, self.config.matrix)), YUVDestination::Bgr8 => Some(yuyv422_to_bgr(&image, buffer, stride_4px_2w, self.config.range, self.config.matrix)),
YUVDestination::Bgra8 => Some(yuyv422_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Bgra8 => Some(yuyv422_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
} }
} }
FrameFormat::Uyvy_4_2_2 => { FrameFormat::Uyvy_4_2_2 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(uyvy422_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Rgb8 => Some(uyvy422_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Rgba8 => Some(uyvy422_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Rgba8 => Some(uyvy422_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
YUVDestination::Rgb16 => Some(uyvy422_to_rgb_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), 6, 16, self.config.range, self.config.matrix)), YUVDestination::Rgb16 => Some(uyvy422_to_rgb_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, 16, self.config.range, self.config.matrix)),
YUVDestination::Rgba16 => Some(uyvy422_to_rgba_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), 6, 16, self.config.range, self.config.matrix)), YUVDestination::Rgba16 => Some(uyvy422_to_rgba_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, 16, self.config.range, self.config.matrix)),
YUVDestination::Bgr8 => Some(uyvy422_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Bgr8 => Some(uyvy422_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Bgra8 => Some(uyvy422_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Bgra8 => Some(uyvy422_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
} }
} }
FrameFormat::Vyuy_4_2_2 => { FrameFormat::Vyuy_4_2_2 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(vyuy422_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Rgb8 => Some(vyuy422_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Rgba8 => Some(vyuy422_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Rgba8 => Some(vyuy422_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
YUVDestination::Rgb16 => Some(vyuy422_to_rgb_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), 6, 16, self.config.range, self.config.matrix)), YUVDestination::Rgb16 => Some(vyuy422_to_rgb_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, 16, self.config.range, self.config.matrix)),
YUVDestination::Rgba16 => Some(vyuy422_to_rgba_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), 6, 16, self.config.range, self.config.matrix)), YUVDestination::Rgba16 => Some(vyuy422_to_rgba_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, 16, self.config.range, self.config.matrix)),
YUVDestination::Bgr8 => Some(vyuy422_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Bgr8 => Some(vyuy422_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Bgra8 => Some(vyuy422_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Bgra8 => Some(vyuy422_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
} }
} }
FrameFormat::Yvyu_4_2_2 => { FrameFormat::Yvyu_4_2_2 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yvyu422_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Rgb8 => Some(yvyu422_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Rgba8 => Some(yvyu422_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Rgba8 => Some(yvyu422_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
YUVDestination::Rgb16 => Some(yvyu422_to_rgb_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), 6, 16, self.config.range, self.config.matrix)), YUVDestination::Rgb16 => Some(yvyu422_to_rgb_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, 16, self.config.range, self.config.matrix)),
YUVDestination::Rgba16 => Some(yvyu422_to_rgba_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), 6, 16, self.config.range, self.config.matrix)), YUVDestination::Rgba16 => Some(yvyu422_to_rgba_p16(&convert_packed_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, 16, self.config.range, self.config.matrix)),
YUVDestination::Bgr8 => Some(yvyu422_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Bgr8 => Some(yvyu422_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Bgra8 => Some(yvyu422_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Bgra8 => Some(yvyu422_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
} }
} }
_ => { _ => {
@@ -113,78 +136,79 @@ impl Decoder for YUVDecoder {
} }
} }
} }
CachedStride::SemiPlanar(y_stride, uv_stride) => { Stride::Semi(y_stride, uv_stride) => {
let image = prepare_to_semi_planar_image(&to_decode, self.config.resolution, y_stride, uv_stride); let image = prepare_to_semi_planar_image(&to_decode, self.config.resolution, byte_width, y_stride, uv_stride);
match self.config.yuv_type { match self.config.yuv_type {
FrameFormat::NV24 => { FrameFormat::NV24 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yuv_nv24_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgb8 => Some(yuv_nv24_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Rgba8 => Some(yuv_nv24_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgba8 => Some(yuv_nv24_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgr8 => Some(yuv_nv24_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgr8 => Some(yuv_nv24_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgra8 => Some(yuv_nv24_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgra8 => Some(yuv_nv24_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
_ => None, _ => None,
} }
} }
FrameFormat::NV42 => { FrameFormat::NV42 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yuv_nv42_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgb8 => Some(yuv_nv42_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Rgba8 => Some(yuv_nv42_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgba8 => Some(yuv_nv42_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgr8 => Some(yuv_nv42_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgr8 => Some(yuv_nv42_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgra8 => Some(yuv_nv42_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgra8 => Some(yuv_nv42_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
_ => None, _ => None,
} }
} }
FrameFormat::NV16 => { FrameFormat::NV16 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yuv_nv16_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgb8 => Some(yuv_nv16_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Rgba8 => Some(yuv_nv16_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgba8 => Some(yuv_nv16_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgr8 => Some(yuv_nv16_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgr8 => Some(yuv_nv16_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgra8 => Some(yuv_nv16_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgra8 => Some(yuv_nv16_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
_ => None, _ => None,
} }
} }
FrameFormat::NV61 => { FrameFormat::NV61 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yuv_nv61_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgb8 => Some(yuv_nv61_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Rgba8 => Some(yuv_nv61_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgba8 => Some(yuv_nv61_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgr8 => Some(yuv_nv61_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgr8 => Some(yuv_nv61_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgra8 => Some(yuv_nv61_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgra8 => Some(yuv_nv61_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
_ => None, _ => None,
} }
} }
FrameFormat::NV12 => { FrameFormat::NV12 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yuv_nv12_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgb8 => Some(yuv_nv12_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Rgba8 => Some(yuv_nv12_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgba8 => Some(yuv_nv12_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgr8 => Some(yuv_nv12_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgr8 => Some(yuv_nv12_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgra8 => Some(yuv_nv12_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgra8 => Some(yuv_nv12_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
_ => None, _ => None,
} }
} }
FrameFormat::NV21 => { FrameFormat::NV21 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yuv_nv21_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgb8 => Some(yuv_nv21_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Rgba8 => Some(yuv_nv21_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgba8 => Some(yuv_nv21_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgr8 => Some(yuv_nv21_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgr8 => Some(yuv_nv21_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgra8 => Some(yuv_nv21_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgra8 => Some(yuv_nv21_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
_ => None, _ => None,
} }
} }
FrameFormat::P010 => { FrameFormat::P010 => {
let a = convert_bi_planar_image_to_u16(image);
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(p010_to_rgb(&convert_bi_planar_image_to_u16(image), buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgb8 => Some(p010_to_rgb(&a, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Rgba8 => Some(p010_to_rgba(&convert_bi_planar_image_to_u16(image), buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Rgba8 => Some(p010_to_rgba(&a, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgr8 => Some(p010_to_bgr(&convert_bi_planar_image_to_u16(image), buffer, 3, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgr8 => Some(p010_to_bgr(&a, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Bgra8 => Some(p010_to_bgra(&convert_bi_planar_image_to_u16(image), buffer, 4, self.config.range, self.config.matrix, self.config.mode)), YUVDestination::Bgra8 => Some(p010_to_bgra(&a, buffer, stride_4px, self.config.range, self.config.matrix, self.config.mode)),
YUVDestination::Rgb16 => Some(p010_to_rgb10(&convert_bi_planar_image_to_u16(image), cast_slice_mut(buffer), 6, self.config.range, self.config.matrix)), YUVDestination::Rgb16 => Some(p010_to_rgb10(&a, cast_slice_mut(buffer), stride_3px_2w, self.config.range, self.config.matrix)),
YUVDestination::Rgba16 => Some(p010_to_rgba10(&convert_bi_planar_image_to_u16(image), cast_slice_mut(buffer), 8, self.config.range, self.config.matrix)), YUVDestination::Rgba16 => Some(p010_to_rgba10(&a, cast_slice_mut(buffer), stride_4px, self.config.range, self.config.matrix)),
// _ => None, // _ => None,
} }
} }
FrameFormat::P012 => { FrameFormat::P012 => {
match destination_format { match destination_format {
YUVDestination::Rgb16 => Some(p012_to_rgb12(&convert_bi_planar_image_to_u16(image), cast_slice_mut(buffer), 6, self.config.range, self.config.matrix)), YUVDestination::Rgb16 => Some(p012_to_rgb12(&convert_bi_planar_image_to_u16(image), cast_slice_mut(buffer), stride_3px_2w, self.config.range, self.config.matrix)),
YUVDestination::Rgba16 => Some(p012_to_rgba12(&convert_bi_planar_image_to_u16(image), cast_slice_mut(buffer), 8, self.config.range, self.config.matrix)), YUVDestination::Rgba16 => Some(p012_to_rgba12(&convert_bi_planar_image_to_u16(image), cast_slice_mut(buffer), stride_4px_2w, self.config.range, self.config.matrix)),
_ => None, _ => None,
} }
} }
@@ -197,15 +221,15 @@ impl Decoder for YUVDecoder {
} }
} }
} }
CachedStride::Planar(y_stride, u_stride, v_stride) => { Stride::Planar(y_stride, u_stride, v_stride, line_ratio) => {
let image = prepare_to_planar_image(&to_decode, self.config.resolution, y_stride, u_stride, v_stride); let image = prepare_to_planar_image(&to_decode, self.config.resolution, byte_width, y_stride, u_stride, v_stride, line_ratio);
match self.config.yuv_type { match self.config.yuv_type {
FrameFormat::Yuv_4_2_0 => { FrameFormat::Yuv_4_2_0 => {
match destination_format { match destination_format {
YUVDestination::Rgb8 => Some(yuv420_to_rgb(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Rgb8 => Some(yuv420_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Rgba8 => Some(yuv420_to_rgba(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Rgba8 => Some(yuv420_to_rgba(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
YUVDestination::Bgr8 => Some(yuv420_to_bgr(&image, buffer, 3, self.config.range, self.config.matrix)), YUVDestination::Bgr8 => Some(yuv420_to_bgr(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
YUVDestination::Bgra8 => Some(yuv420_to_bgra(&image, buffer, 4, self.config.range, self.config.matrix)), YUVDestination::Bgra8 => Some(yuv420_to_bgra(&image, buffer, stride_4px, self.config.range, self.config.matrix)),
_ => None, _ => None,
} }
} }
@@ -222,46 +246,68 @@ impl Decoder for YUVDecoder {
match decode_status { match decode_status {
Some(Ok(_)) => Ok(()), Some(Ok(_)) => Ok(()),
Some(Err(why)) => Err(NokhwaError::Decoder(why.to_string())), Some(Err(why)) => Err(NokhwaError::Decoder(why.to_string())),
None => Err(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type)), None => Err(NokhwaError::DecoderUnsupportedFrameFormat(
self.config.yuv_type,
)),
} }
} }
fn decode_to_pixel_buffer<P: Pixel>(&mut self, to_decode: FrameBuffer<'_>, mut buffer: impl AsMut<[P::Subpixel]>) -> Result<Self::OutputMeta, NokhwaError> fn decode_to_pixel_buffer<P: Pixel>(
&mut self,
to_decode: FrameBuffer<'_>,
mut buffer: impl AsMut<[P::Subpixel]>,
) -> Result<Self::OutputMeta, NokhwaError>
where where
<P as Pixel>::Subpixel: NonFloatScalarWidth <P as Pixel>::Subpixel: NonFloatScalarWidth,
{ {
let destination = match YUVDestination::get_by_pixel::<P>() { let destination = match YUVDestination::get_by_pixel::<P>() {
None => return Err(NokhwaError::DecoderUnsupportedDestinationPixelFormat(P::COLOR_MODEL, <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES)), None => {
return Err(NokhwaError::DecoderUnsupportedDestinationPixelFormat(
P::COLOR_MODEL,
<<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES,
));
}
Some(d) => d, Some(d) => d,
}; };
let buffer = buffer.as_mut(); let buffer = buffer.as_mut();
let cast_slice = try_cast_slice_mut::<P::Subpixel, u8>(buffer) let cast_slice = try_cast_slice_mut::<P::Subpixel, u8>(buffer)
.map_err(|why| NokhwaError::DecoderInvalidBuffer(why.to_string()))?; .map_err(|why| NokhwaError::DecoderInvalidBuffer(why.to_string()))?;
self.decode_to_buffer(to_decode, cast_slice, Some(destination)) self.decode_to_buffer(to_decode, cast_slice, Some(destination))
} }
fn decode<P: Pixel>(&mut self, to_decode: FrameBuffer<'_>) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError> fn decode<P: Pixel>(
&mut self,
to_decode: FrameBuffer<'_>,
) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError>
where where
<P as Pixel>::Subpixel: NonFloatScalarWidth <P as Pixel>::Subpixel: NonFloatScalarWidth,
{ {
let min_size_alloc = self.output_decoder_min_size_pixel::<P>(self.config.resolution); let min_size_alloc = self.output_decoder_min_size_pixel::<P>(self.config.resolution);
let mut out_buffer: Vec<P::Subpixel> = vec![P::Subpixel::DEFAULT_MIN_VALUE; min_size_alloc]; let mut out_buffer: Vec<P::Subpixel> = vec![P::Subpixel::DEFAULT_MIN_VALUE; min_size_alloc];
self.decode_to_pixel_buffer::<P>(to_decode, &mut out_buffer)?; self.decode_to_pixel_buffer::<P>(to_decode, &mut out_buffer)?;
Ok( Ok(DecodedImage::new(
DecodedImage::new( ImageBuffer::from_vec(
ImageBuffer::from_vec(self.config.resolution.width(), self.config.resolution.height(), out_buffer) self.config.resolution.width(),
.ok_or(NokhwaError::Decoder("failed to convert into an image buffer".to_string()))?, self.config.resolution.height(),
() out_buffer,
) )
) .ok_or(NokhwaError::Decoder(
"failed to convert into an image buffer".to_string(),
))?,
(),
))
} }
fn output_decoder_min_size(&self, resolution: Resolution, destination_format: Self::DestinationFormatHint) -> usize { fn output_decoder_min_size(
&self,
resolution: Resolution,
destination_format: Self::DestinationFormatHint,
) -> usize {
let px_size = match destination_format { let px_size = match destination_format {
YUVDestination::Rgb8 | YUVDestination::Bgr8 => 3, YUVDestination::Rgb8 | YUVDestination::Bgr8 => 3,
YUVDestination::Rgba8 | YUVDestination::Bgra8 => 4, YUVDestination::Rgba8 | YUVDestination::Bgra8 => 4,
YUVDestination::Rgb16 => 3 * 2, YUVDestination::Rgb16 => 3 * 2,
YUVDestination::Rgba16 => 4 * 2, YUVDestination::Rgba16 => 4 * 2,
}; };
@@ -285,30 +331,32 @@ pub enum YUVDestination {
} }
impl YUVDestination { impl YUVDestination {
pub fn get_by_pixel<P>() -> Option<Self> where P: Pixel, <P as Pixel>::Subpixel: NonFloatScalarWidth { pub fn get_by_pixel<P>() -> Option<Self>
where
P: Pixel,
<P as Pixel>::Subpixel: NonFloatScalarWidth,
{
match P::COLOR_MODEL { match P::COLOR_MODEL {
"RGB" => { "RGB" => match <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES {
match <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES { 1 => Some(YUVDestination::Rgb8),
1 => Some(YUVDestination::Rgb8), 2 => Some(YUVDestination::Rgb16),
2 => Some(YUVDestination::Rgb16), _ => None,
_ => None, },
} "RGBA" => match <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES {
}
"RGBA" => match <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES {
1 => Some(YUVDestination::Rgba8), 1 => Some(YUVDestination::Rgba8),
2 => Some(YUVDestination::Rgba16), 2 => Some(YUVDestination::Rgba16),
_ => None, _ => None,
} },
"BGR" =>match <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES { "BGR" => match <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES {
1 => Some(YUVDestination::Bgr8), 1 => Some(YUVDestination::Bgr8),
// 2 => Some(YUVDestination::Bgr16), // 2 => Some(YUVDestination::Bgr16),
_ => None, _ => None,
} },
"BGRA" => match <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES { "BGRA" => match <<P as Pixel>::Subpixel as NonFloatScalarWidth>::WIDTH_BYTES {
1 => Some(YUVDestination::Bgra8), 1 => Some(YUVDestination::Bgra8),
// 2 => Some(YUVDestination::Bgra16), // 2 => Some(YUVDestination::Bgra16),
_ => None, _ => None,
} },
_ => None, _ => None,
} }
} }
@@ -329,7 +377,7 @@ impl TryFrom<CameraFormat> for YUVConfig {
fn try_from(value: CameraFormat) -> Result<Self, Self::Error> { fn try_from(value: CameraFormat) -> Result<Self, Self::Error> {
if !FrameFormat::YCBCR.contains(&value.format()) { if !FrameFormat::YCBCR.contains(&value.format()) {
return Err(NokhwaError::DecoderUnsupportedFrameFormat(value.format())) return Err(NokhwaError::DecoderUnsupportedFrameFormat(value.format()));
} }
Ok(YUVConfig { Ok(YUVConfig {
resolution: value.resolution(), resolution: value.resolution(),
@@ -342,33 +390,69 @@ impl TryFrom<CameraFormat> for YUVConfig {
} }
} }
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] #[derive(Copy, Clone, Debug)]
enum CachedStride { enum Stride {
Packed(u32), Packed(u32),
SemiPlanar(u32, u32), Semi(u32, u32),
Planar(u32, u32, u32), Planar(u32, u32, u32, u32),
} }
fn figure_out_stride(frame_format: FrameFormat) -> Option<CachedStride> { fn figure_out_stride(frame_format: FrameFormat) -> Option<Stride> {
if let Some(s) = packed_stride_component_per_4px(frame_format) { if let Some(yuy) = packed_stride_component(frame_format) {
return Some(CachedStride::Packed(s)); return Some(Stride::Packed(yuy));
} }
if let Some((s1, s2)) = semiplanar_stride_per_4px(frame_format) { if let Some((y, uv)) = semiplanar_stride(frame_format) {
return Some(CachedStride::SemiPlanar(s1, s2)); return Some(Stride::Semi(y, uv));
} }
if let Some((s1, s2, s3)) = planar_stride_per_4px(frame_format) { // l here means the ratio of luma lines to chroma lines
return Some(CachedStride::Planar(s1, s2, s3)); // u_r and v_r are defined as _ratios_ to the luma stride, i.e. how many luma components
// per chroma component, u_r = 2 means 2 luma per 1 u chroma
if let Some((y, u_r, v_r, l)) = planar_stride(frame_format) {
return Some(Stride::Planar(y, u_r, v_r, l));
} }
None None
} }
fn packed_stride_component_per_4px(format: FrameFormat) -> Option<u32> { fn figure_out_byte_width(format: FrameFormat) -> Option<u32> {
match format { match format {
FrameFormat::Ayuv_32 => Some(64), FrameFormat::Ayuv_32 | FrameFormat::Yuyv_4_2_2
| FrameFormat::Uyvy_4_2_2
| FrameFormat::Vyuy_4_2_2
| FrameFormat::Yvyu_4_2_2 => Some(1),
FrameFormat::NV24 | FrameFormat::NV42 | FrameFormat::NV16 | FrameFormat::NV61 | FrameFormat::NV12 | FrameFormat::NV21 => Some(1),
FrameFormat::P010 | FrameFormat::P012 => Some(2),
FrameFormat::Yuv_4_2_0 => Some(1),
_ => None,
}
}
fn packed_stride_component(format: FrameFormat) -> Option<u32> {
match format {
FrameFormat::Ayuv_32 => Some(4),
FrameFormat::Yuyv_4_2_2 FrameFormat::Yuyv_4_2_2
| FrameFormat::Uyvy_4_2_2 | FrameFormat::Uyvy_4_2_2
| FrameFormat::Vyuy_4_2_2 | FrameFormat::Vyuy_4_2_2
| FrameFormat::Yvyu_4_2_2 => Some(16), | FrameFormat::Yvyu_4_2_2 => Some(2),
_ => None,
}
}
fn semiplanar_stride(format: FrameFormat) -> Option<(u32, u32)> {
match format {
FrameFormat::NV24 | FrameFormat::NV42 => Some((1, 2)),
FrameFormat::NV16 | FrameFormat::NV61 => Some((1, 1)),
FrameFormat::P010 | FrameFormat::P012 => Some((1, 1)),
FrameFormat::NV12 | FrameFormat::NV21 => Some((1, 1)),
_ => None,
}
}
fn planar_stride(format: FrameFormat) -> Option<(u32, u32, u32, u32)> {
match format {
// if you are here wondering if I will ever add another planar format
// the answer is no.
// do not bother opening an issue or a pr i will never merge it use a sane format like nv12
FrameFormat::Yuv_4_2_0 => Some((1, 2, 2, 2)),
_ => None, _ => None,
} }
} }
@@ -376,77 +460,79 @@ fn packed_stride_component_per_4px(format: FrameFormat) -> Option<u32> {
fn prepare_to_packed_image<'a>( fn prepare_to_packed_image<'a>(
frame_buffer: &'a FrameBuffer<'a>, frame_buffer: &'a FrameBuffer<'a>,
resolution: Resolution, resolution: Resolution,
byte_width: u32,
yuy_stride: u32, yuy_stride: u32,
) -> YuvPackedImage<'a, u8> { ) -> YuvPackedImage<'a, u8> {
YuvPackedImage { YuvPackedImage {
yuy: frame_buffer.buffer(), yuy: frame_buffer.buffer(),
yuy_stride, yuy_stride: yuy_stride * resolution.width(),
width: resolution.width(), width: resolution.width(),
height: resolution.height(), height: resolution.height(),
} }
} }
fn semiplanar_stride_per_4px(format: FrameFormat) -> Option<(u32, u32)> {
match format {
FrameFormat::NV24 | FrameFormat::NV42 => Some((4, 8)),
FrameFormat::NV16 | FrameFormat::NV61 => Some((4, 4)),
FrameFormat::NV12 | FrameFormat::NV21 | FrameFormat::P010 | FrameFormat::P012 => {
Some((4, 4))
}
_ => None,
}
}
fn prepare_to_semi_planar_image<'a>( fn prepare_to_semi_planar_image<'a>(
frame_buffer: &'a FrameBuffer<'a>, frame_buffer: &'a FrameBuffer<'a>,
resolution: Resolution, resolution: Resolution,
byte_width: u32,
y_stride: u32, y_stride: u32,
uv_stride: u32, uv_stride: u32,
) -> YuvBiPlanarImage<'a, u8> { ) -> YuvBiPlanarImage<'a, u8> {
let uv_start = (resolution.width() * resolution.height()) as usize; let uv_start = (resolution.width() * resolution.height() * byte_width) as usize;
YuvBiPlanarImage { YuvBiPlanarImage {
y_plane: &frame_buffer.buffer()[0..uv_start], y_plane: &frame_buffer.buffer()[0..uv_start],
y_stride, y_stride: y_stride * resolution.width(),
uv_plane: &frame_buffer.buffer()[uv_start..frame_buffer.len()], uv_plane: &frame_buffer.buffer()[uv_start..frame_buffer.len()],
uv_stride, uv_stride: uv_stride * resolution.width(),
width: resolution.width(), width: resolution.width(),
height: resolution.height(), height: resolution.height(),
} }
} }
fn planar_stride_per_4px(format: FrameFormat) -> Option<(u32, u32, u32)> { // fn planar_stride_per_4px(format: FrameFormat) -> Option<(u32, u32, u32)> {
match format { // match format {
FrameFormat::Yuv_4_2_0 => Some((4, 1, 1)), // FrameFormat::Yuv_4_2_0 => Some((stride_ 1, 1)),
_ => None, // _ => None,
} // }
} // }
// does this work? idk
fn prepare_to_planar_image<'a>( fn prepare_to_planar_image<'a>(
frame_buffer: &'a FrameBuffer<'a>, frame_buffer: &'a FrameBuffer<'a>,
resolution: Resolution, resolution: Resolution,
y_stride: u32, byte_width: u32,
u_stride: u32, y_stride_base: u32,
v_stride: u32, u_stride_ratio: u32,
v_stride_ratio: u32,
line_ratio: u32,
) -> YuvPlanarImage<'a, u8> { ) -> YuvPlanarImage<'a, u8> {
let u_start = (resolution.width() * resolution.height()) as usize;
let v_start = let y_stride = resolution.width() * y_stride_base;
u_start + ((resolution.width() / 4) * y_stride * (resolution.height() * u_stride)) as usize; let u_stride = y_stride / u_stride_ratio;
let v_stride = y_stride / v_stride_ratio;
let chroma_lines = resolution.height() / line_ratio;
// size of y area
let u_start = resolution.height() * resolution.width() * byte_width;
let v_start = u_start + u_stride * chroma_lines * byte_width;
let us = u_start as usize;
let vs = v_start as usize;
YuvPlanarImage { YuvPlanarImage {
y_plane: &frame_buffer.buffer()[0..u_start], y_plane: &frame_buffer.buffer()[0..us],
y_stride, y_stride,
u_plane: &frame_buffer.buffer()[u_start..v_start], u_plane: &frame_buffer.buffer()[us..vs],
u_stride, u_stride,
v_plane: &frame_buffer.buffer()[v_start..frame_buffer.len()], v_plane: &frame_buffer.buffer()[vs..frame_buffer.len()],
v_stride, v_stride,
width: resolution.width(), width: resolution.width(),
height: resolution.height(), height: resolution.height(),
} }
} }
fn convert_packed_image_to_u16( fn convert_packed_image_to_u16(yuv_packed_image: YuvPackedImage<u8>) -> YuvPackedImage<u16> {
yuv_packed_image: YuvPackedImage<u8>, let buf = cast_slice(yuv_packed_image.yuy);
) -> YuvPackedImage<u16> {
let buf= cast_slice(yuv_packed_image.yuy);
YuvPackedImage { YuvPackedImage {
yuy: buf, yuy: buf,
yuy_stride: yuv_packed_image.yuy_stride, yuy_stride: yuv_packed_image.yuy_stride,
@@ -458,8 +544,8 @@ fn convert_packed_image_to_u16(
fn convert_bi_planar_image_to_u16( fn convert_bi_planar_image_to_u16(
yuv_bi_planar_image: YuvBiPlanarImage<u8>, yuv_bi_planar_image: YuvBiPlanarImage<u8>,
) -> YuvBiPlanarImage<u16> { ) -> YuvBiPlanarImage<u16> {
let buf_y= cast_slice(yuv_bi_planar_image.y_plane); let buf_y = cast_slice(yuv_bi_planar_image.y_plane);
let buf_uv= cast_slice(yuv_bi_planar_image.uv_plane); let buf_uv = cast_slice(yuv_bi_planar_image.uv_plane);
YuvBiPlanarImage { YuvBiPlanarImage {
y_plane: buf_y, y_plane: buf_y,
y_stride: yuv_bi_planar_image.y_stride, y_stride: yuv_bi_planar_image.y_stride,
@@ -469,3 +555,224 @@ fn convert_bi_planar_image_to_u16(
height: yuv_bi_planar_image.height, height: yuv_bi_planar_image.height,
} }
} }
#[cfg(test)]
mod test {
use crate::yuv::{YUVConfig, YUVDecoder};
use image::{DynamicImage, EncodableLayout, ImageBuffer, ImageFormat, ImageReader, Pixel, PixelWithColorType, Rgb, Rgba};
use nokhwa_core::decoder::Decoder;
use nokhwa_core::frame_buffer::FrameBuffer;
use nokhwa_core::frame_format::FrameFormat;
use nokhwa_core::image::NonFloatScalarWidth;
use nokhwa_core::types::Resolution;
use std::borrow::Cow;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read};
use yuv::{YuvConversionMode, YuvRange, YuvStandardMatrix};
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum PixelFormat {
Rgb8,
RgbA8,
Rgb16,
RgbA16,
}
fn base_test<P: Pixel>(filename: String, resolution: Resolution, format: FrameFormat) -> ImageBuffer<P, Vec<P::Subpixel>>
where
<P as Pixel>::Subpixel: NonFloatScalarWidth {
let mut file = File::open(filename).unwrap();
let mut nv12_data = Vec::new();
file.read_to_end(&mut nv12_data).unwrap();
let mut decoder = YUVDecoder::new(
YUVConfig {
resolution,
yuv_type: format,
range: YuvRange::Full,
matrix: YuvStandardMatrix::Bt601,
mode: YuvConversionMode::Balanced,
premultiply_alpha: false,
}
);
let frame_buffer = FrameBuffer::new(Cow::Owned(nv12_data), None);
decoder.decode::<P>(frame_buffer).unwrap().buffer
}
fn write_image<P: Pixel + PixelWithColorType>(image: ImageBuffer<P, Vec<P::Subpixel>>, filename: String) where
[<P as Pixel>::Subpixel]: EncodableLayout
{
image.save_with_format(filename, ImageFormat::Png).unwrap();
}
fn load_image(filename: String, format: ImageFormat) -> DynamicImage {
let file = File::open(filename).unwrap();
let image = image::load(BufReader::new(file), format).unwrap();
image
}
#[test]
fn test_nv12() {
let base_filename = "test_images/yuv/nv12/crimeandsekai";
let resolution = Resolution::new(1024, 1520);
let format = FrameFormat::NV12;
let img_format = ImageFormat::Png;
// test nv12 rgb8
{
let image_rgb8 = load_image(format!("{base_filename}.rgb8.png"), img_format).to_rgb8();
let out = base_test::<Rgb<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(out.as_raw(), image_rgb8.as_raw());
}
// test nv12 rgba8
{
let image_rgba8 = load_image(format!("{base_filename}.rgba8.png"), img_format).to_rgba8();
let out = base_test::<Rgba<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(out.as_raw(), image_rgba8.as_raw());
}
}
// FIXME: Can anyone make a 32bit AYUV image? I can't. Reenable this test after doing so.
// #[test]
// fn test_ayuv() {
// let base_filename = "test_images/yuv/ayuv/lhzlings";
// let resolution = Resolution::new(1000, 1080);
// let format = FrameFormat::Ayuv_32;
// let img_format = ImageFormat::Png;
//
// {
// // let image_rgb8 = load_image(format!("{base_filename}.rgb"))
// let out = base_test::<Rgb<u8>>(format!("{base_filename}.yuv"), resolution, format);
//
// write_image(out, format!("{base_filename}.rgb8.png"))
// }
//
// {
// let out = base_test::<Rgba<u8>>(format!("{base_filename}.yuv"), resolution, format);
//
// write_image(out, format!("{base_filename}.rgba8.png"))
// }
// }
#[test]
fn test_nv24() {
let base_filename = "test_images/yuv/nv24/larihole";
let resolution = Resolution::new(800, 600);
let format = FrameFormat::NV24;
let img_format = ImageFormat::Png;
{
let image_rgb8 = load_image(format!("{base_filename}.rgb8.png"), img_format).to_rgb8();
let out = base_test::<Rgb<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(image_rgb8.as_raw(), out.as_raw());
// write_image(out, format!("{base_filename}.rgb8.png"));
}
{
let image_rgba8 = load_image(format!("{base_filename}.rgba8.png"), img_format).to_rgba8();
let out = base_test::<Rgba<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(image_rgba8.as_raw(), out.as_raw());
// write_image(out, format!("{base_filename}.rgba8.png"));
}
}
#[test]
fn test_nv16() {
let base_filename = "test_images/yuv/nv16/aeq";
let resolution = Resolution::new(802, 602);
let format = FrameFormat::NV16;
let img_format = ImageFormat::Png;
{
let image_rgb8 = load_image(format!("{base_filename}.rgb8.png"), img_format).to_rgb8();
let out = base_test::<Rgb<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(image_rgb8.as_raw(), out.as_raw());
// write_image(out, format!("{base_filename}.rgb8.png"));
}
{
let image_rgba8 = load_image(format!("{base_filename}.rgba8.png"), img_format).to_rgba8();
let out = base_test::<Rgba<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(image_rgba8.as_raw(), out.as_raw());
// write_image(out, format!("{base_filename}.rgba8.png"));
}
}
#[test]
fn test_p010() {
let base_filename = "test_images/yuv/p010/crimesagainstvalor";
let resolution = Resolution::new(128, 128);
let format = FrameFormat::P010;
let img_format = ImageFormat::Png;
{
let image_rgb8 = load_image(format!("{base_filename}.rgb8.png"), img_format).to_rgb8();
let out = base_test::<Rgb<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(image_rgb8.as_raw(), out.as_raw());
// write_image(out, format!("{base_filename}.rgb8.png"));
}
{
let image_rgba8 = load_image(format!("{base_filename}.rgba8.png"), img_format).to_rgba8();
let out = base_test::<Rgba<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(image_rgba8.as_raw(), out.as_raw());
// write_image(out, format!("{base_filename}.rgba8.png"));
}
// there is some kind of bug in image-rs or smth idk cba to figure it out
// {
// let out = base_test::<Rgb<u16>>(format!("{base_filename}.yuv"), resolution, format);
// // write_image(out, format!("{base_filename}.rgb16.png"));
// out.save_with_format(format!("{base_filename}.rgb16.avif"), ImageFormat::Avif).unwrap();
// }
}
#[test]
fn test_yuv420() {
let base_filename = "test_images/yuv/yuv420/youarethemurderr";
let resolution = Resolution::new(460, 460);
let format = FrameFormat::Yuv_4_2_0;
let img_format = ImageFormat::Png;
{
let image_rgb8 = load_image(format!("{base_filename}.rgb8.png"), img_format).to_rgb8();
let out = base_test::<Rgb<u8>>(format!("{base_filename}.yuv"), resolution, format);
// write_image(out, format!("{base_filename}.rgb8.png"));
assert_eq!(image_rgb8.as_raw(), out.as_raw());
}
{
let image_rgba8 = load_image(format!("{base_filename}.rgba8.png"), img_format).to_rgba8();
let out = base_test::<Rgba<u8>>(format!("{base_filename}.yuv"), resolution, format);
// write_image(out, format!("{base_filename}.rgba8.png"));
assert_eq!(image_rgba8.as_raw(), out.as_raw());
}
}
#[test]
fn test_yuyv() {
let base_filename = "test_images/yuv/yuyv/20250530_224336";
let resolution = Resolution::new(3024, 4032);
let format = FrameFormat::Yuyv_4_2_2;
let img_format = ImageFormat::Png;
{
let image_rgb8 = load_image(format!("{base_filename}.rgb8.png"), img_format).to_rgb8();
let out = base_test::<Rgb<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(image_rgb8.as_raw(), out.as_raw());
}
{
let image_rgba8 = load_image(format!("{base_filename}.rgba8.png"), img_format).to_rgba8();
let out = base_test::<Rgba<u8>>(format!("{base_filename}.yuv"), resolution, format);
assert_eq!(image_rgba8.as_raw(), out.as_raw());
}
}
}
+7
View File
@@ -0,0 +1,7 @@
ffmpeg -i crimeandsekai.png -pix_fmt nv12 -f rawvideo crimeandsekai.yuv
ffmpeg -s 1024x1520 -pix_fmt nv12 -i crimeandsekai.yuv -f image2 -pix_fmt rgb24 crimeandsekai.nv12.png
AYUV will fail on BE machines :D
-158
View File
@@ -1,158 +0,0 @@
/*
* Copyright 2022 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use nokhwa_core::format_request::FormatFilter;
use nokhwa_core::frame_format::SourceFrameFormat;
use nokhwa_core::traits::Backend;
use nokhwa_core::{
frame_buffer::FrameBuffer,
error::NokhwaError,
pixel_format::FormatDecoder,
traits::CaptureTrait,
types::{
ApiBackend, CameraFormat, CameraIndex, CameraInformation
, RequestedFormatType, Resolution,
},
};
use std::{borrow::Cow, collections::HashMap};
use nokhwa_core::control::{CameraControl, ControlValue, KnownCameraControl};
/// The main `Camera` struct. This is the struct that abstracts over all the backends, providing a simplified interface for use.
pub struct Camera {
idx: CameraIndex,
api: ApiBackend,
device: Box<dyn CaptureTrait + Backend>,
}
impl Camera {
pub fn new() -> Result<Self, NokhwaError> {}
pub fn with_api_backend() -> Result<Self, NokhwaError> {}
pub fn with_custom_backend() -> Result<Self, NokhwaError> {}
}
impl CaptureTrait for Camera {
fn init(&mut self) -> Result<(), NokhwaError> {
todo!()
}
fn init_with_format(&mut self, format: FormatFilter) -> Result<CameraFormat, NokhwaError> {
todo!()
}
fn backend(&self) -> ApiBackend {
todo!()
}
fn camera_info(&self) -> &CameraInformation {
todo!()
}
fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
todo!()
}
fn camera_format(&self) -> Option<CameraFormat> {
todo!()
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
todo!()
}
fn compatible_list_by_resolution(
&mut self,
fourcc: SourceFrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
todo!()
}
fn compatible_fourcc(&mut self) -> Result<Vec<SourceFrameFormat>, NokhwaError> {
todo!()
}
fn resolution(&self) -> Option<Resolution> {
todo!()
}
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
todo!()
}
fn frame_rate(&self) -> Option<u32> {
todo!()
}
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
todo!()
}
fn frame_format(&self) -> SourceFrameFormat {
todo!()
}
fn set_frame_format(
&mut self,
fourcc: impl Into<SourceFrameFormat>,
) -> Result<(), NokhwaError> {
todo!()
}
fn camera_control(&self, control: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
todo!()
}
fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
todo!()
}
fn set_camera_control(
&mut self,
id: KnownCameraControl,
value: ControlValue,
) -> Result<(), NokhwaError> {
todo!()
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
todo!()
}
fn is_stream_open(&self) -> bool {
todo!()
}
fn frame(&mut self) -> Result<FrameBuffer, NokhwaError> {
todo!()
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
todo!()
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
todo!()
}
}
impl Drop for Camera {
fn drop(&mut self) {
self.stop_stream().unwrap();
}
}
unsafe impl Send for Camera {}