mirror of
https://github.com/l1npengtul/nokhwa.git
synced 2026-07-15 14:38:53 +00:00
new frameformat, new filter, new request
This commit is contained in:
@@ -16,7 +16,7 @@ serialize = ["serde"]
|
||||
wgpu-types = ["wgpu"]
|
||||
mjpeg = ["mozjpeg"]
|
||||
opencv-mat = ["opencv"]
|
||||
docs-features = ["serialize", "wgpu-types", "opencv/docs-only", "mjpeg"]
|
||||
docs-features = ["serialize", "wgpu-types", "mjpeg"]
|
||||
test-fail-warnings = []
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[dependencies.wgpu]
|
||||
version = "0.14"
|
||||
version = "0.15"
|
||||
optional = true
|
||||
|
||||
[dependencies.opencv]
|
||||
version = "0.74"
|
||||
version = "0.76"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::{error::NokhwaError, frame_format::FrameFormat, types::Resolution};
|
||||
use crate::{frame_format::SourceFrameFormat, types::Resolution};
|
||||
use bytes::Bytes;
|
||||
use image::ImageBuffer;
|
||||
|
||||
/// A buffer returned by a camera to accommodate custom decoding.
|
||||
/// Contains information of Resolution, the buffer's [`FrameFormat`], and the buffer.
|
||||
@@ -26,14 +25,14 @@ use image::ImageBuffer;
|
||||
pub struct Buffer {
|
||||
resolution: Resolution,
|
||||
buffer: Bytes,
|
||||
source_frame_format: FrameFormat,
|
||||
source_frame_format: SourceFrameFormat,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
/// Creates a new buffer with a [`&[u8]`].
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn new(res: Resolution, buf: &[u8], source_frame_format: FrameFormat) -> Self {
|
||||
pub fn new(res: Resolution, buf: &[u8], source_frame_format: SourceFrameFormat) -> Self {
|
||||
Self {
|
||||
resolution: res,
|
||||
buffer: Bytes::copy_from_slice(buf),
|
||||
@@ -59,9 +58,9 @@ impl Buffer {
|
||||
self.buffer.clone()
|
||||
}
|
||||
|
||||
/// Get the [`FrameFormat`] of this buffer.
|
||||
/// Get the [`SourceFrameFormat`] of this buffer.
|
||||
#[must_use]
|
||||
pub fn source_frame_format(&self) -> FrameFormat {
|
||||
pub fn source_frame_format(&self) -> SourceFrameFormat {
|
||||
self.source_frame_format
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::frame_format::FrameFormat;
|
||||
use crate::types::{ApiBackend, CameraFormat, Resolution};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use crate::{
|
||||
frame_format::FrameFormat,
|
||||
types::{ApiBackend, CameraFormat, Resolution},
|
||||
};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Tells the init function what camera format to pick.
|
||||
/// - `AbsoluteHighestResolution`: Pick the highest [`Resolution`], then pick the highest frame rate of those provided.
|
||||
@@ -22,6 +24,7 @@ pub enum RequestedFormatType {
|
||||
None,
|
||||
}
|
||||
|
||||
/// How you get your [`FrameFormat`] from the
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FormatFilter {
|
||||
filter_pref: RequestedFormatType,
|
||||
@@ -48,23 +51,29 @@ impl FormatFilter {
|
||||
|
||||
pub fn add_allowed_platform_specific(&mut self, platform: ApiBackend, frame_format: u128) {
|
||||
match self.fcc_platform.get_mut(&platform) {
|
||||
Some(fccs) => fccs.push(frame_format),
|
||||
None => self
|
||||
.fcc_platform
|
||||
.insert(platform, BTreeSet::from([frame_format])),
|
||||
Some(fccs) => {
|
||||
fccs.insert(frame_format);
|
||||
}
|
||||
None => {
|
||||
self.fcc_platform
|
||||
.insert(platform, BTreeSet::from([frame_format]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn add_allowed_platform_specific_many(
|
||||
&mut self,
|
||||
platform_specifics: impl IntoPlatformSpecificMany,
|
||||
platform_specifics: impl AsRef<[(ApiBackend, u128)]>,
|
||||
) {
|
||||
for (platform, frame_format) in platform_specifics.into_psm().into_iter() {
|
||||
for (platform, frame_format) in platform_specifics.as_ref().into_iter() {
|
||||
match self.fcc_platform.get_mut(&platform) {
|
||||
Some(fccs) => fccs.push(frame_format),
|
||||
None => self
|
||||
.fcc_platform
|
||||
.insert(platform, BTreeSet::from([frame_format])),
|
||||
Some(fccs) => {
|
||||
fccs.insert(*frame_format);
|
||||
}
|
||||
None => {
|
||||
self.fcc_platform
|
||||
.insert(*platform, BTreeSet::from([*frame_format]));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -87,18 +96,13 @@ impl FormatFilter {
|
||||
platform: ApiBackend,
|
||||
frame_format: u128,
|
||||
) -> Self {
|
||||
match self.fcc_platform.get_mut(&platform) {
|
||||
Some(fccs) => fccs.push(frame_format),
|
||||
None => self
|
||||
.fcc_platform
|
||||
.insert(platform, BTreeSet::from([frame_format])),
|
||||
};
|
||||
self.add_allowed_platform_specific(platform, frame_format);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_allowed_platform_specific_many(
|
||||
mut self,
|
||||
platform_specifics: impl IntoPlatformSpecificMany,
|
||||
platform_specifics: impl AsRef<[(ApiBackend, u128)]>,
|
||||
) -> Self {
|
||||
self.add_allowed_platform_specific_many(platform_specifics);
|
||||
self
|
||||
@@ -119,63 +123,4 @@ impl Default for FormatFilter {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoPlatformSpecificMany {
|
||||
fn into_psm(self) -> Vec<(ApiBackend, u128)>;
|
||||
}
|
||||
|
||||
impl<T> IntoPlatformSpecificMany for T
|
||||
where
|
||||
T: AsRef<(ApiBackend, u128)>,
|
||||
{
|
||||
fn into_psm(self) -> Vec<(ApiBackend, u128)> {
|
||||
vec![*self.as_ref()]
|
||||
}
|
||||
}
|
||||
impl<T> IntoPlatformSpecificMany for T
|
||||
where
|
||||
T: AsRef<[(ApiBackend, u128)]>,
|
||||
{
|
||||
fn into_psm(self) -> Vec<(ApiBackend, u128)> {
|
||||
self.as_ref().iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoPlatformSpecificMany for BTreeMap<ApiBackend, T>
|
||||
where
|
||||
T: AsRef<[u128]>,
|
||||
{
|
||||
fn into_psm(self) -> Vec<(ApiBackend, u128)> {
|
||||
self.into_iter()
|
||||
.flat_map(|(backend, fourcc_lst)| fourcc_lst.as_ref().iter().map(|fcc| (backend, *fcc)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoPlatformSpecificMany for HashMap<ApiBackend, T>
|
||||
where
|
||||
T: AsRef<[u128]>,
|
||||
{
|
||||
fn into_psm(self) -> Vec<(ApiBackend, u128)> {
|
||||
self.into_iter()
|
||||
.flat_map(|(backend, fourcc_lst)| fourcc_lst.as_ref().iter().map(|fcc| (backend, *fcc)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoPlatformSpecificMany for &T
|
||||
where
|
||||
T: IntoPlatformSpecificMany + Clone,
|
||||
{
|
||||
fn into_psm(self) -> Vec<(ApiBackend, u128)> {
|
||||
self.clone().into_psm()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoPlatformSpecificMany for &mut T
|
||||
where
|
||||
T: IntoPlatformSpecificMany + Clone,
|
||||
{
|
||||
fn into_psm(self) -> Vec<(ApiBackend, u128)> {
|
||||
self.clone().into_psm()
|
||||
}
|
||||
}
|
||||
pub struct FormatFulfill {}
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
use crate::error::NokhwaError;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
/*
|
||||
* 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 crate::{buffer::Buffer, types::ApiBackend};
|
||||
use image::{ImageBuffer, Pixel};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{Display, Formatter},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`.
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
@@ -103,55 +123,34 @@ impl Display for FrameFormat {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 crate::buffer::Buffer;
|
||||
use crate::types::ApiBackend;
|
||||
use image::{ImageBuffer, Luma, LumaA, Primitive, Rgb, Rgba};
|
||||
use std::ops::Deref;
|
||||
/// The Source Format of a [`Buffer`].
|
||||
///
|
||||
/// May either be a platform specific FourCC, or a FrameFormat
|
||||
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SourceFrameFormat {
|
||||
FrameFormat(FrameFormat),
|
||||
PlatformSpecific(ApiBackend, u128),
|
||||
}
|
||||
|
||||
pub trait FormatDecoders: Send + Sync {
|
||||
impl Display for SourceFrameFormat {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FormatDecoders<T: Pixel, E: Error>: Send + Sync {
|
||||
const NAME: &'static str;
|
||||
|
||||
const PRIMARY: &'static [FrameFormat];
|
||||
|
||||
const PLATFORM_ACCEPTABLE: &'static [(ApiBackend, &'static [u128])];
|
||||
|
||||
type Primitive: Primitive;
|
||||
type Container: Deref<Target = [T::Subpixel]>;
|
||||
|
||||
type Container: Deref<Target = [Self::Primitive]>;
|
||||
}
|
||||
|
||||
pub trait RgbDecoder: FormatDecoders {
|
||||
fn decode_rgb(&self, buffer: &Buffer) -> ImageBuffer<Rgb<Self::Primitive>, Self::Container>;
|
||||
}
|
||||
|
||||
pub trait RgbADecoder: FormatDecoders {
|
||||
fn decode_rgba(&self, buffer: &Buffer) -> ImageBuffer<Rgba<Self::Primitive>, Self::Container>;
|
||||
}
|
||||
|
||||
pub trait LumaDecoder: FormatDecoders {
|
||||
fn decode_luma(&self, buffer: &Buffer) -> ImageBuffer<Luma<Self::Primitive>, Self::Container>;
|
||||
}
|
||||
|
||||
pub trait LumaADecoder: FormatDecoders {
|
||||
fn decode_luma_a(
|
||||
&self,
|
||||
buffer: &Buffer,
|
||||
) -> ImageBuffer<LumaA<Self::Primitive>, Self::Container>;
|
||||
fn decode(&self, buffer: &Buffer) -> Result<ImageBuffer<T, Self::Container>, E>;
|
||||
}
|
||||
|
||||
// TODO: Wgpu Decoder
|
||||
|
||||
// TODO: OpenCV Mat Decoder
|
||||
|
||||
@@ -14,23 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::frame_format::FrameFormat;
|
||||
use crate::types::RequestedFormat;
|
||||
use crate::frame_format::SourceFrameFormat;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
error::NokhwaError,
|
||||
format_filter::FormatFilter,
|
||||
frame_format::FrameFormat,
|
||||
types::{
|
||||
ApiBackend, CameraControl, CameraFormat, CameraInfo, ControlValueSetter,
|
||||
KnownCameraControl, Resolution,
|
||||
},
|
||||
};
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
#[cfg(feature = "wgpu-types")]
|
||||
use wgpu::{
|
||||
Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue,
|
||||
Texture as WgpuTexture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat,
|
||||
TextureUsages,
|
||||
};
|
||||
|
||||
/// This trait is for any backend that allows you to grab and take frames from a camera.
|
||||
/// Many of the backends are **blocking**, if the camera is occupied the library will block while it waits for it to become available.
|
||||
@@ -43,8 +38,8 @@ pub trait CaptureBackendTrait {
|
||||
/// Initialize the camera, preparing it for use, with a random format (usually the first one).
|
||||
fn init(&mut self) -> Result<(), NokhwaError>;
|
||||
|
||||
/// Initialize the camera, preparing it for use, with a format that fits the supplied [`RequestedFormat`].
|
||||
fn init_with_format(&mut self, format: RequestedFormat) -> Result<CameraFormat, NokhwaError>;
|
||||
/// Initialize the camera, preparing it for use, with a format that fits the supplied [`FormatFilter`].
|
||||
fn init_with_format(&mut self, format: FormatFilter) -> Result<CameraFormat, NokhwaError>;
|
||||
|
||||
/// Returns the current backend used.
|
||||
fn backend(&self) -> ApiBackend;
|
||||
@@ -70,10 +65,10 @@ pub trait CaptureBackendTrait {
|
||||
|
||||
/// A hashmap of [`Resolution`]s mapped to framerates. Not sorted!
|
||||
/// # Errors
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::error::NokhwaError::UnsupportedOperationError)).
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](NokhwaError::UnsupportedOperationError)).
|
||||
fn compatible_list_by_resolution(
|
||||
&mut self,
|
||||
fourcc: FrameFormat,
|
||||
fourcc: SourceFrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError>;
|
||||
|
||||
/// Gets the compatible [`CameraFormat`] of the camera
|
||||
@@ -94,8 +89,8 @@ pub trait CaptureBackendTrait {
|
||||
|
||||
/// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most.
|
||||
/// # Errors
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::error::NokhwaError::UnsupportedOperationError)).
|
||||
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError>;
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](NokhwaError::UnsupportedOperationError)).
|
||||
fn compatible_fourcc(&mut self) -> Result<Vec<SourceFrameFormat>, NokhwaError>;
|
||||
|
||||
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
|
||||
fn resolution(&self) -> Resolution;
|
||||
|
||||
+72
-254
@@ -1,12 +1,14 @@
|
||||
use crate::frame_format::FrameFormat;
|
||||
use crate::{error::NokhwaError, pixel_format::FormatDecoders};
|
||||
use crate::{
|
||||
error::NokhwaError,
|
||||
format_filter::RequestedFormatType,
|
||||
frame_format::{FrameFormat, SourceFrameFormat},
|
||||
};
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
cmp::Ordering,
|
||||
fmt::{Display, Formatter},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
impl Default for RequestedFormatType {
|
||||
@@ -21,177 +23,6 @@ impl Display for RequestedFormatType {
|
||||
}
|
||||
}
|
||||
|
||||
/// A request to the camera for a valid [`CameraFormat`]
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct RequestedFormat<'a> {
|
||||
requested_format: RequestedFormatType,
|
||||
wanted_decoder: &'a [FrameFormat],
|
||||
}
|
||||
|
||||
impl RequestedFormat<'_> {
|
||||
/// Creates a new [`RequestedFormat`] by using the [`RequestedFormatType`] and getting the [`FrameFormat`]
|
||||
/// constraints from a generic type.
|
||||
#[must_use]
|
||||
pub fn new<Decoder: FormatDecoders>(
|
||||
requested: RequestedFormatType,
|
||||
) -> RequestedFormat<'static> {
|
||||
RequestedFormat {
|
||||
requested_format: requested,
|
||||
wanted_decoder: Decoder::FORMATS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`RequestedFormat`] by using the [`RequestedFormatType`] and getting the [`FrameFormat`]
|
||||
/// constraints from a statically allocated slice.
|
||||
#[must_use]
|
||||
pub fn with_formats(
|
||||
requested: RequestedFormatType,
|
||||
decoder: &[FrameFormat],
|
||||
) -> RequestedFormat {
|
||||
RequestedFormat {
|
||||
requested_format: requested,
|
||||
wanted_decoder: decoder,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the [`RequestedFormatType`]
|
||||
#[must_use]
|
||||
pub fn requested_format_type(&self) -> RequestedFormatType {
|
||||
self.requested_format
|
||||
}
|
||||
|
||||
/// Fulfill the requested using a list of all available formats.
|
||||
///
|
||||
/// See [`RequestedFormatType`] for more details.
|
||||
#[must_use]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn fulfill(&self, all_formats: &[CameraFormat]) -> Option<CameraFormat> {
|
||||
match self.requested_format {
|
||||
RequestedFormatType::AbsoluteHighestResolution => {
|
||||
let mut formats = all_formats.to_vec();
|
||||
formats.sort_by_key(CameraFormat::resolution);
|
||||
let resolution = *formats.iter().last()?;
|
||||
let mut format_resolutions = formats
|
||||
.into_iter()
|
||||
.filter(|fmt| {
|
||||
fmt.resolution() == resolution.resolution()
|
||||
&& self.wanted_decoder.contains(&fmt.format())
|
||||
})
|
||||
.collect::<Vec<CameraFormat>>();
|
||||
format_resolutions.sort_by_key(CameraFormat::frame_rate);
|
||||
format_resolutions.last().copied()
|
||||
}
|
||||
RequestedFormatType::AbsoluteHighestFrameRate => {
|
||||
let mut formats = all_formats.to_vec();
|
||||
formats.sort_by_key(CameraFormat::frame_rate);
|
||||
let frame_rate = *formats.iter().last()?;
|
||||
let mut format_framerates = formats
|
||||
.into_iter()
|
||||
.filter(|fmt| {
|
||||
fmt.frame_rate() == frame_rate.frame_rate()
|
||||
&& self.wanted_decoder.contains(&fmt.format())
|
||||
})
|
||||
.collect::<Vec<CameraFormat>>();
|
||||
format_framerates.sort_by_key(CameraFormat::resolution);
|
||||
format_framerates.last().copied()
|
||||
}
|
||||
RequestedFormatType::HighestResolution(res) => {
|
||||
let mut formats = all_formats
|
||||
.iter()
|
||||
.filter(|x| x.resolution == res)
|
||||
.copied()
|
||||
.collect::<Vec<CameraFormat>>();
|
||||
formats.sort_by(|a, b| a.frame_rate.cmp(&b.frame_rate));
|
||||
let highest_fps = match formats.last() {
|
||||
Some(cf) => cf.frame_rate,
|
||||
None => return None,
|
||||
};
|
||||
formats
|
||||
.into_iter()
|
||||
.filter(|x| x.frame_rate == highest_fps)
|
||||
.last()
|
||||
}
|
||||
RequestedFormatType::HighestFrameRate(fps) => {
|
||||
let mut formats = all_formats
|
||||
.iter()
|
||||
.filter(|x| x.frame_rate == fps)
|
||||
.copied()
|
||||
.collect::<Vec<CameraFormat>>();
|
||||
formats.sort_by(|a, b| a.resolution.cmp(&b.resolution));
|
||||
let highest_res = match formats.last() {
|
||||
Some(cf) => cf.resolution,
|
||||
None => return None,
|
||||
};
|
||||
formats
|
||||
.into_iter()
|
||||
.filter(|x| x.resolution() == highest_res)
|
||||
.last()
|
||||
}
|
||||
RequestedFormatType::Exact(fmt) => {
|
||||
if self.wanted_decoder.contains(&fmt.format()) {
|
||||
Some(fmt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
RequestedFormatType::Closest(c) => {
|
||||
let same_fmt_formats = all_formats
|
||||
.iter()
|
||||
.filter(|x| {
|
||||
x.format() == c.format() && self.wanted_decoder.contains(&x.format())
|
||||
})
|
||||
.copied()
|
||||
.collect::<Vec<CameraFormat>>();
|
||||
let mut resolution_map = same_fmt_formats
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let res = x.resolution();
|
||||
let x_diff = res.x() as i32 - c.resolution().x() as i32;
|
||||
let y_diff = res.y() as i32 - c.resolution().y() as i32;
|
||||
let dist_no_sqrt = (x_diff.abs()).pow(2) + (y_diff.abs()).pow(2);
|
||||
(dist_no_sqrt, res)
|
||||
})
|
||||
.collect::<Vec<(i32, Resolution)>>();
|
||||
resolution_map.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
resolution_map.dedup_by(|a, b| a.0.eq(&b.0));
|
||||
let resolution = resolution_map.first()?.1;
|
||||
|
||||
let frame_rates = all_formats
|
||||
.iter()
|
||||
.filter_map(|cfmt| {
|
||||
if cfmt.format() == c.format() && cfmt.resolution() == c.resolution() {
|
||||
return Some(cfmt.frame_rate());
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect::<Vec<u32>>();
|
||||
// sort FPSes
|
||||
let mut framerate_map = frame_rates
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let abs = *x as i32 - c.frame_rate() as i32;
|
||||
(abs.unsigned_abs(), *x)
|
||||
})
|
||||
.collect::<Vec<(u32, u32)>>();
|
||||
framerate_map.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
let frame_rate = framerate_map.first()?.1;
|
||||
Some(CameraFormat::new(resolution, c.format(), frame_rate))
|
||||
}
|
||||
RequestedFormatType::None => all_formats
|
||||
.iter()
|
||||
.find(|fmt| self.wanted_decoder.contains(&fmt.format()))
|
||||
.copied(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RequestedFormat<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the index of the camera.
|
||||
/// - Index: A numbered index
|
||||
/// - String: A string, used for `IPCameras`.
|
||||
@@ -360,14 +191,14 @@ impl Ord for Resolution {
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
pub struct CameraFormat {
|
||||
resolution: Resolution,
|
||||
format: FrameFormat,
|
||||
format: SourceFrameFormat,
|
||||
frame_rate: u32,
|
||||
}
|
||||
|
||||
impl CameraFormat {
|
||||
/// Construct a new [`CameraFormat`]
|
||||
#[must_use]
|
||||
pub fn new(resolution: Resolution, format: FrameFormat, frame_rate: u32) -> Self {
|
||||
pub fn new(resolution: Resolution, format: SourceFrameFormat, frame_rate: u32) -> Self {
|
||||
CameraFormat {
|
||||
resolution,
|
||||
format,
|
||||
@@ -377,7 +208,7 @@ impl CameraFormat {
|
||||
|
||||
/// [`CameraFormat::new()`], but raw.
|
||||
#[must_use]
|
||||
pub fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: u32) -> Self {
|
||||
pub fn new_from(res_x: u32, res_y: u32, format: SourceFrameFormat, fps: u32) -> Self {
|
||||
CameraFormat {
|
||||
resolution: Resolution {
|
||||
width_x: res_x,
|
||||
@@ -424,12 +255,12 @@ impl CameraFormat {
|
||||
|
||||
/// Get the [`CameraFormat`]'s format.
|
||||
#[must_use]
|
||||
pub fn format(&self) -> FrameFormat {
|
||||
pub fn format(&self) -> SourceFrameFormat {
|
||||
self.format
|
||||
}
|
||||
|
||||
/// Set the [`CameraFormat`]'s format.
|
||||
pub fn set_format(&mut self, format: FrameFormat) {
|
||||
pub fn set_format(&mut self, format: SourceFrameFormat) {
|
||||
self.format = format;
|
||||
}
|
||||
}
|
||||
@@ -438,7 +269,7 @@ impl Default for CameraFormat {
|
||||
fn default() -> Self {
|
||||
CameraFormat {
|
||||
resolution: Resolution::new(640, 480),
|
||||
format: FrameFormat::MJPEG,
|
||||
format: SourceFrameFormat::FrameFormat(FrameFormat::MJpeg),
|
||||
frame_rate: 30,
|
||||
}
|
||||
}
|
||||
@@ -766,7 +597,7 @@ impl ControlValueDescription {
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies if the [setter](crate::types::ControlValueSetter) is valid for the provided [`ControlValueDescription`].
|
||||
/// Verifies if the [setter](ControlValueSetter) is valid for the provided [`ControlValueDescription`].
|
||||
/// - `true` => Is valid.
|
||||
/// - `false` => Is not valid.
|
||||
///
|
||||
@@ -1367,7 +1198,44 @@ impl Display for ApiBackend {
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Converts a MJPEG stream of `&[u8]` into a `Vec<u8>` of RGB888. (R,G,B,R,G,B,...)
|
||||
#[cfg(all(feature = "mjpeg", not(target_arch = "wasm")))]
|
||||
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "mjpeg")))]
|
||||
#[inline]
|
||||
fn decompress(
|
||||
data: impl AsRef<[u8]>,
|
||||
rgba: bool,
|
||||
) -> Result<mozjpeg::decompress::DecompressStarted, NokhwaError> {
|
||||
use mozjpeg::Decompress;
|
||||
|
||||
match Decompress::new_mem(data) {
|
||||
Ok(decompress) => {
|
||||
let decompressor_res = if rgba {
|
||||
decompress.rgba()
|
||||
} else {
|
||||
decompress.rgb()
|
||||
};
|
||||
match decompressor_res {
|
||||
Ok(decompressor) => Ok(decompressor),
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJpeg,
|
||||
destination: "RGB888".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJpeg,
|
||||
destination: "RGB888".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a MJpeg stream of `&[u8]` into a `Vec<u8>` of RGB888. (R,G,B,R,G,B,...)
|
||||
/// # Errors
|
||||
/// If `mozjpeg` fails to read scanlines or setup the decompressor, this will error.
|
||||
/// # Safety
|
||||
@@ -1379,38 +1247,13 @@ impl Display for ApiBackend {
|
||||
pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
|
||||
use mozjpeg::Decompress;
|
||||
|
||||
let mut jpeg_decompress = match Decompress::new_mem(data) {
|
||||
Ok(decompress) => {
|
||||
let decompressor_res = if rgba {
|
||||
decompress.rgba()
|
||||
} else {
|
||||
decompress.rgb()
|
||||
};
|
||||
match decompressor_res {
|
||||
Ok(decompressor) => decompressor,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJPEG,
|
||||
destination: "RGB888".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJPEG,
|
||||
destination: "RGB888".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let mut jpeg_decompress = decompress(data, rgba)?;
|
||||
|
||||
let scanlines_res: Option<Vec<u8>> = jpeg_decompress.read_scanlines_flat();
|
||||
// assert!(jpeg_decompress.finish_decompress());
|
||||
if !jpeg_decompress.finish_decompress() {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJPEG,
|
||||
src: FrameFormat::MJpeg,
|
||||
destination: "RGB888".to_string(),
|
||||
error: "JPEG Decompressor did not finish.".to_string(),
|
||||
});
|
||||
@@ -1419,7 +1262,7 @@ pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
|
||||
match scanlines_res {
|
||||
Some(pixels) => Ok(pixels),
|
||||
None => Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJPEG,
|
||||
src: FrameFormat::MJpeg,
|
||||
destination: "RGB888".to_string(),
|
||||
error: "Failed to get read readlines into RGB888 pixels!".to_string(),
|
||||
}),
|
||||
@@ -1435,44 +1278,19 @@ pub fn mjpeg_to_rgb(_data: &[u8], _rgba: bool) -> Result<Vec<u8>, NokhwaError> {
|
||||
|
||||
/// Equivalent to [`mjpeg_to_rgb`] except with a destination buffer.
|
||||
/// # Errors
|
||||
/// If the decoding fails (e.g. invalid MJPEG stream), the buffer is not large enough, or you are doing this on `WebAssembly`, this will error.
|
||||
/// If the decoding fails (e.g. invalid MJpeg stream), the buffer is not large enough, or you are doing this on `WebAssembly`, this will error.
|
||||
#[cfg(all(feature = "mjpeg", not(target_arch = "wasm")))]
|
||||
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "mjpeg")))]
|
||||
#[inline]
|
||||
pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> {
|
||||
use mozjpeg::Decompress;
|
||||
|
||||
let mut jpeg_decompress = match Decompress::new_mem(data) {
|
||||
Ok(decompress) => {
|
||||
let decompressor_res = if rgba {
|
||||
decompress.rgba()
|
||||
} else {
|
||||
decompress.rgb()
|
||||
};
|
||||
match decompressor_res {
|
||||
Ok(decompressor) => decompressor,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJPEG,
|
||||
destination: "RGB888".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJPEG,
|
||||
destination: "RGB888".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let mut jpeg_decompress = decompress(data, rgba)?;
|
||||
|
||||
// assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size());
|
||||
if dest.len() != jpeg_decompress.min_flat_buffer_size() {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJPEG,
|
||||
src: FrameFormat::MJpeg,
|
||||
destination: "RGB888".to_string(),
|
||||
error: "Bad decoded buffer size".to_string(),
|
||||
});
|
||||
@@ -1482,7 +1300,7 @@ pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(),
|
||||
// assert!(jpeg_decompress.finish_decompress());
|
||||
if !jpeg_decompress.finish_decompress() {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJPEG,
|
||||
src: FrameFormat::MJpeg,
|
||||
destination: "RGB888".to_string(),
|
||||
error: "JPEG Decompressor did not finish.".to_string(),
|
||||
});
|
||||
@@ -1497,7 +1315,7 @@ pub fn buf_mjpeg_to_rgb(_data: &[u8], _dest: &mut [u8], _rgba: bool) -> Result<(
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the predicted size of the destination YUYV422 buffer.
|
||||
/// Returns the predicted size of the destination Yuv422422 buffer.
|
||||
#[inline]
|
||||
pub fn yuyv422_predicted_size(size: usize, rgba: bool) -> usize {
|
||||
let pixel_size = if rgba { 4 } else { 3 };
|
||||
@@ -1508,10 +1326,10 @@ pub fn yuyv422_predicted_size(size: usize, rgba: bool) -> usize {
|
||||
// For those maintaining this, I recommend you read: https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#yuy2
|
||||
// https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB
|
||||
// and this too: https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte
|
||||
// The YUY2(YUYV) format is a 16 bit format. We read 4 bytes at a time to get 6 bytes of RGB888.
|
||||
// The YUY2(Yuv422) format is a 16 bit format. We read 4 bytes at a time to get 6 bytes of RGB888.
|
||||
// First, the YUY2 is converted to YCbCr 4:4:4 (4:2:2 -> 4:4:4)
|
||||
// then it is converted to 6 bytes (2 pixels) of RGB888
|
||||
/// Converts a YUYV 4:2:2 datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
|
||||
/// Converts a Yuv422 4:2:2 datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
|
||||
/// # Errors
|
||||
/// This may error when the data stream size is not divisible by 4, a i32 -> u8 conversion fails, or it fails to read from a certain index.
|
||||
#[inline]
|
||||
@@ -1528,12 +1346,12 @@ pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
|
||||
|
||||
/// Same as [`yuyv422_to_rgb`] but with a destination buffer instead of a return `Vec<u8>`
|
||||
/// # Errors
|
||||
/// If the stream is invalid YUYV, or the destination buffer is not large enough, this will error.
|
||||
/// If the stream is invalid Yuv422, or the destination buffer is not large enough, this will error.
|
||||
#[inline]
|
||||
pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> {
|
||||
if data.len() % 4 != 0 {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::YUYV,
|
||||
src: FrameFormat::Yuv422,
|
||||
destination: "RGB888".to_string(),
|
||||
error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)"
|
||||
.to_string(),
|
||||
@@ -1546,7 +1364,7 @@ pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<()
|
||||
|
||||
if dest.len() != rgb_buf_size {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::YUYV,
|
||||
src: FrameFormat::Yuv422,
|
||||
destination: "RGB888".to_string(),
|
||||
error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()),
|
||||
});
|
||||
@@ -1571,9 +1389,9 @@ pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<()
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::YUYV,
|
||||
src: FrameFormat::Yuv422,
|
||||
destination: "RGBA8888".to_string(),
|
||||
error: "Ran out of RGBA YUYV values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string()
|
||||
error: "Ran out of RGBA Yuv422 values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1596,9 +1414,9 @@ pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<()
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::YUYV,
|
||||
src: FrameFormat::Yuv422,
|
||||
destination: "RGB888".to_string(),
|
||||
error: "Ran out of RGB YUYV values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string()
|
||||
error: "Ran out of RGB Yuv422 values! (this should not happen, please file an issue: l1npengtul/nokhwa)".to_string()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1637,7 +1455,7 @@ pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] {
|
||||
[r, g, b, 255]
|
||||
}
|
||||
|
||||
/// Converts a YUYV 4:2:0 bi-planar (NV12) datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
|
||||
/// Converts a Yuv422 4:2:0 bi-planar (NV12) datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
|
||||
/// # Errors
|
||||
/// This may error when the data stream size is wrong.
|
||||
#[inline]
|
||||
@@ -1655,7 +1473,7 @@ pub fn nv12_to_rgb(
|
||||
// this depresses me
|
||||
// like, everytime i open this codebase all the life is sucked out of me
|
||||
// i hate it
|
||||
/// Converts a YUYV 4:2:0 bi-planar (NV12) datastream to a RGB888 Stream and outputs it into a destination buffer. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
|
||||
/// Converts a Yuv422 4:2:0 bi-planar (NV12) datastream to a RGB888 Stream and outputs it into a destination buffer. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
|
||||
/// # Errors
|
||||
/// This may error when the data stream size is wrong.
|
||||
#[allow(clippy::similar_names)]
|
||||
@@ -1668,7 +1486,7 @@ pub fn buf_nv12_to_rgb(
|
||||
) -> Result<(), NokhwaError> {
|
||||
if resolution.width() % 2 != 0 || resolution.height() % 2 != 0 {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::NV12,
|
||||
src: FrameFormat::Nv12,
|
||||
destination: "RGB".to_string(),
|
||||
error: "bad resolution".to_string(),
|
||||
});
|
||||
@@ -1676,7 +1494,7 @@ pub fn buf_nv12_to_rgb(
|
||||
|
||||
if data.len() != ((resolution.width() * resolution.height() * 3) / 2) as usize {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::NV12,
|
||||
src: FrameFormat::Nv12,
|
||||
destination: "RGB".to_string(),
|
||||
error: "bad input buffer size".to_string(),
|
||||
});
|
||||
@@ -1686,7 +1504,7 @@ pub fn buf_nv12_to_rgb(
|
||||
|
||||
if out.len() != (pxsize * resolution.width() * resolution.height()) as usize {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::NV12,
|
||||
src: FrameFormat::Nv12,
|
||||
destination: "RGB".to_string(),
|
||||
error: "bad output buffer size".to_string(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user