preliminary browsercamera commit

This commit is contained in:
l1npengtul
2024-08-18 22:34:05 +09:00
parent 14339380c2
commit 13633ae568
21 changed files with 1425 additions and 4486 deletions
Generated
+572 -121
View File
File diff suppressed because it is too large Load Diff
+7 -3
View File
@@ -30,7 +30,7 @@ input-native = ["input-avfoundation", "input-v4l", "input-msmf"]
# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x
# input-uvc = ["uvc", "uvc/vendor", "usb_enumeration", "lazy_static"]
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"]
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-wasm = ["input-jscam"]
output-threaded = []
@@ -73,11 +73,11 @@ version = "0.2"
optional = true
[dependencies.wgpu]
version = "0.19"
version = "0.20"
optional = true
[dependencies.opencv]
version = "0.89"
version = "0.92"
default-features = false
features = ["videoio"]
optional = true
@@ -143,6 +143,10 @@ optional = true
version = "0.9"
optional = true
[dependencies.serde-wasm-bindgen]
version = "0.6"
optional = true
[dependencies.async-trait]
version = "0.1"
optional = true
+4 -8
View File
@@ -178,11 +178,11 @@ mod internal {
}
};
self.camera_format = CameraFormat::new(
self.camera_format = Some(CameraFormat::new(
Resolution::new(format.width, format.height),
frame_format: ,
fps,
);
));
Ok(())
}
Err(why) => Err(NokhwaError::GetPropertyError {
@@ -198,10 +198,6 @@ mod internal {
todo!()
}
fn init_with_format(&mut self, format: FormatFilter) -> Result<CameraFormat, NokhwaError> {
todo!()
}
fn backend(&self) -> ApiBackend {
ApiBackend::Video4Linux
}
@@ -596,8 +592,8 @@ mod internal {
fn fourcc_to_frameformat(fourcc: FourCC) -> Option<FrameFormat> {
match fourcc.str().ok()? {
"YUYV" => Some(FrameFormat::Yuv422),
"UYVY" => Some(FrameFormat::Uyv422),
"YUYV" => Some(FrameFormat::Yuy2_422),
"UYVY" => Some(FrameFormat::Uyvy_422),
"YV12" => Some(FrameFormat::Yv12),
"MJPG" => Some(FrameFormat::MJpeg),
"GRAY" => Some(FrameFormat::Luma8),
+3 -1
View File
@@ -21,5 +21,7 @@ core-video-sys = "0.1"
cocoa-foundation = "0.1"
objc = { version = "0.2", features = ["exception"] }
block = "0.1"
flume = "0.10"
flume = "0.11.0"
once_cell = "1.16"
av-foundation = "0.3.0"
+11 -2
View File
@@ -16,6 +16,7 @@ serialize = ["serde"]
wgpu-types = ["wgpu"]
opencv-mat = ["opencv"]
docs-features = ["serialize", "wgpu-types"]
conversions = ["dcv-color-primitives", "yuvutils-rs", "mozjpeg"]
async = ["async-trait"]
test-fail-warnings = []
@@ -35,11 +36,11 @@ features = ["derive"]
optional = true
[dependencies.wgpu]
version = "0.19"
version = "22"
optional = true
[dependencies.opencv]
version = "0.89.0"
version = "0.92"
default-features = false
optional = true
@@ -51,5 +52,13 @@ optional = true
version = "0.1"
optional = true
[dependencies.dcv-color-primitives]
version = "0.6"
optional = true
[dependencies.yuvutils-rs]
version = "0.3"
optional = true
[package.metadata.docs.rs]
features = ["docs-features"]
+2 -152
View File
@@ -14,8 +14,9 @@
* limitations under the License.
*/
use crate::{ types::Resolution};
use crate::types::Resolution;
use bytes::Bytes;
use crate::frame_format::FrameFormat;
/// A buffer returned by a camera to accommodate custom decoding.
/// Contains information of Resolution, the buffer's [`FrameFormat`], and the buffer.
@@ -64,154 +65,3 @@ impl Buffer {
self.source_frame_format
}
}
#[cfg(feature = "opencv-mat")]
use crate::error::NokhwaError;
#[cfg(feature = "opencv-mat")]
use image::ImageBuffer;
#[cfg(feature = "opencv-mat")]
impl Buffer {
/// Decodes a image with allocation using the provided [`FormatDecoder`].
/// # Errors
/// Will error when the decoding fails.
#[inline]
pub fn decode_image<F: FormatDecoder>(
&self,
) -> Result<ImageBuffer<F::Output, Vec<u8>>, NokhwaError> {
let new_data = F::write_output(self.source_frame_format, self.resolution, &self.buffer)?;
let image =
ImageBuffer::from_raw(self.resolution.width_x, self.resolution.height_y, new_data)
.ok_or(NokhwaError::ProcessFrameError {
src: self.source_frame_format,
destination: stringify!(F).to_string(),
error: "Failed to create buffer".to_string(),
})?;
Ok(image)
}
/// Decodes a image with allocation using the provided [`FormatDecoder`] into a `buffer`.
/// # Errors
/// Will error when the decoding fails, or the provided buffer is too small.
#[inline]
pub fn decode_image_to_buffer<F: FormatDecoder>(
&self,
buffer: &mut [u8],
) -> Result<(), NokhwaError> {
F::write_output_buffer(
self.source_frame_format,
self.resolution,
&self.buffer,
buffer,
)
}
/// Decodes a image with allocation using the provided [`FormatDecoder`] into a [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
///
/// Note that this does a clone when creating the buffer, to decouple the lifetime of the internal data to the temporary Buffer. If you want to avoid this, please see [`decode_opencv_mat`](Self::decode_opencv_mat).
/// # Errors
/// Will error when the decoding fails, or `OpenCV` failed to create/copy the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
/// # Safety
/// This function uses `unsafe` in order to create the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html). Please see [`Mat::new_rows_cols_with_data`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html#method.new_rows_cols_with_data) for more.
///
/// Most notably, the `data` **must** stay in scope for the duration of the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html) or bad, ***bad*** things happen.
#[cfg(feature = "opencv-mat")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "opencv-mat")))]
#[allow(clippy::cast_possible_wrap)]
pub fn decode_opencv_mat<F: FormatDecoder>(
&mut self,
) -> Result<opencv::core::Mat, NokhwaError> {
use image::Pixel;
use opencv::core::{Mat, Mat_AUTO_STEP, CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4};
let array_type = match F::Output::CHANNEL_COUNT {
1 => CV_8UC1,
2 => CV_8UC2,
3 => CV_8UC3,
4 => CV_8UC4,
_ => {
return Err(NokhwaError::ProcessFrameError {
src: FrameFormat::RAWRGB,
destination: "OpenCV Mat".to_string(),
error: "Invalid Decoder FormatDecoder Channel Count".to_string(),
})
}
};
unsafe {
// TODO: Look into removing this unnecessary copy.
let mat1 = Mat::new_rows_cols_with_data(
self.resolution.height_y as i32,
self.resolution.width_x as i32,
array_type,
self.buffer.as_ref().as_ptr().cast_mut().cast(),
Mat_AUTO_STEP,
)
.map_err(|why| NokhwaError::ProcessFrameError {
src: FrameFormat::Rgb8,
destination: "OpenCV Mat".to_string(),
error: why.to_string(),
})?;
Ok(mat1)
}
}
}
#[cfg(feature = "wgpu-types")]
use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, ImageCopyTexture, TextureAspect, ImageDataLayout};
use crate::frame_format::FrameFormat;
#[cfg(feature = "wgpu-types")]
impl Buffer {
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "wgpu-types")))]
/// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
/// # Errors
/// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
fn frame_texture<'a>(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
label: Option<&'a str>,
) -> Result<wgpu::Texture, NokhwaError> {
let frame = self.frame()?.decode_image::<RgbAFormat>()?;
let texture_size = Extent3d {
width: frame.width(),
height: frame.height(),
depth_or_array_layers: 1,
};
let texture = device.create_texture(&TextureDescriptor {
label,
size: texture_size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
view_formats: &[TextureFormat::Rgba8UnormSrgb],
});
let width_nonzero = 4 * frame.width();
let height_nonzero = frame.height();
queue.write_texture(
ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: TextureAspect::All,
},
&frame,
ImageDataLayout {
offset: 0,
bytes_per_row: width_nonzero,
rows_per_image: height_nonzero,
},
texture_size,
);
Ok(texture)
}
}
-74
View File
@@ -1,74 +0,0 @@
use image::{ImageBuffer, Pixel};
use crate::buffer::Buffer;
use crate::frame_format::FrameFormat;
/// Trait to define a struct that can decode a [`Buffer`]
pub trait Decoder {
/// Formats that the decoder can decode.
const ALLOWED_FORMATS: &'static FrameFormat;
/// Output pixel type (e.g. [`Rgb<u8>`](image::Rgb))
type OutputPixels: Pixel;
type PixelContainer;
/// Error that the decoder will output (use [`NokhwaError`] if you're not sure)
type Error;
/// Decode function.
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
/// Decode to user-provided Buffer
///
/// Incase that the buffer is not large enough this should error.
fn decode_buffer(&mut self, buffer: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
/// Decoder Predicted Size
fn predicted_size_of_frame(&mut self, ) -> Option<usize>;
}
/// Decoder that can be used statically (struct contains no state)
///
/// This is useful for times that a simple function is all that is required.
pub trait StaticDecoder: Decoder {
fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
fn decode_static_to_buffer(buffer: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
}
/// Decoder that does not change its internal state.
pub trait IdemptDecoder: Decoder {
/// Decoder that does not change its internal state.
fn decode_nm(&self, buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
/// Decoder that does not change its internal state, decoding to a user provided buffer.
fn decode_nm_to_buffer(&self, buffer: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
}
#[cfg(feature = "async")]
#[cfg_attr(feature = "async", async_trait::async_trait)]
pub trait AsyncDecoder: Decoder {
/// Asynchronous decoder
async fn decode_async(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
/// Asynchronous decoder to user buffer.
async fn decode_buffer(&mut self, buffer: &mut [Self::OutputPixels::Subpixel]) -> Result<(), Self::Error>;
}
#[cfg(feature = "async")]
#[cfg_attr(feature = "async", async_trait::async_trait)]
pub trait AsyncStaticDecoder: Decoder {
/// Asynchronous decoder
async fn decode_static_async(buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
/// Asynchronous decoder to user buffer.
async fn decode_static_buffer(buffer: &mut [Self::OutputPixels::Subpixel]) -> Result<(), Self::Error>;
}
#[cfg(feature = "async")]
#[cfg_attr(feature = "async", async_trait::async_trait)]
pub trait AsyncIdemptDecoder: Decoder {
/// Asynchronous decoder
async fn decode_nm_async(&self, buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
/// Asynchronous decoder to user buffer.
async fn decode_nm_buffer(&self, buffer: &mut [Self::OutputPixels::Subpixel]) -> Result<(), Self::Error>;
}
+58
View File
@@ -0,0 +1,58 @@
use dcv_color_primitives::PixelFormat;
use image::{ExtendedColorType, ImageBuffer, Pixel, PixelWithColorType};
use crate::buffer::Buffer;
use crate::decoders::Decoder;
use crate::error::NokhwaError;
use crate::frame_format::FrameFormat;
pub struct GeneralPurposeDecoder<D> where D: PixelWithColorType;
impl<D> Decoder for GeneralPurposeDecoder<D> where D: PixelWithColorType {
const ALLOWED_FORMATS: &'static [FrameFormat] = &[
FrameFormat::MJpeg, FrameFormat::Luma8, FrameFormat::Luma16, FrameFormat::Rgb8, FrameFormat::RgbA8,
FrameFormat::Nv12, FrameFormat::Nv21, FrameFormat::Uyvy_422, FrameFormat::Yuy2_422, FrameFormat::Yv12,
FrameFormat::Yuv444, FrameFormat::I420, FrameFormat::I422, FrameFormat::I444
];
type OutputPixels = D;
type PixelContainer = Vec<D::Subpixel>;
type Error = NokhwaError;
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error> {
todo!()
}
fn decode_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error> {
if !Self::ALLOWED_FORMATS.contains(&buffer.source_frame_format()) {
return Err(NokhwaError::ConversionError(format!("Invaid frame format {} (allowed formats: {:?})", buffer.source_frame_format(), Self::ALLOWED_FORMATS)))
}
let destination = match D::COLOR_TYPE {
ExtendedColorType::Rgb8 => PixelFormat::Rgb,
ExtendedColorType::Rgba8 => PixelFormat::Rgba,
ExtendedColorType::Bgr8 => PixelFormat::Bgr,
ExtendedColorType::Bgra8 => PixelFormat::Bgra,
_ => return Err(())
};
// some extra processing needed for some formats
let source = match buffer.source_frame_format() {
FrameFormat::MJpeg => PixelFormat::Rgb, // => JPEG decoder
FrameFormat::Yuy2_422 => PixelFormat::I422,
FrameFormat::Uyvy_422 => PixelFormat::I422,
FrameFormat::Yuv444 => PixelFormat::I444,
FrameFormat::Nv12 => PixelFormat::Nv12,
FrameFormat::Nv21 => PixelFormat::Nv12,
FrameFormat::Yv12 => PixelFormat::I420,
FrameFormat::I420 => PixelFormat::I420,
// already decoded
FrameFormat::Rgb8 => PixelFormat::Rgb,
FrameFormat::RgbA8 => {
PixelFormat::Rgba
}
_ => return Err(()),
};
}
}
+67
View File
@@ -0,0 +1,67 @@
use std::ops::Deref;
use image::{ExtendedColorType, ImageBuffer, Pixel};
use crate::buffer::Buffer;
use crate::error::NokhwaError;
use crate::frame_format::FrameFormat;
/// Trait to define a struct that can decode a [`Buffer`]
pub trait Decoder {
/// Formats that the decoder can decode.
const ALLOWED_FORMATS: &'static [FrameFormat];
/// Output pixel type (e.g. [`Rgb<u8>`](image::Rgb))
type OutputPixels: Pixel;
type PixelContainer: Deref<Target = [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]>;
/// Error that the decoder will output (use [`NokhwaError`] if you're not sure)
type Error;
/// Decode function.
fn decode(&mut self, buffer: &Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
/// Decode to user-provided Buffer
///
/// Incase that the buffer is not large enough this should error.
fn decode_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
/// Decoder Predicted Size
fn predicted_size_of_frame(buffer: &Buffer) -> Option<usize> {
if !Self::ALLOWED_FORMATS.contains(&buffer.source_frame_format()) {
return None
}
let res = buffer.resolution();
Some(res.x() as usize * res.y() as usize * core::mem::size_of::<<<Self as Decoder>::OutputPixels as Pixel>::Subpixel>() * <<Self as Decoder>::OutputPixels as Pixel>::CHANNEL_COUNT as usize)
}
}
/// Decoder that can be used statically (struct contains no state)
///
/// This is useful for times that a simple function is all that is required.
pub trait StaticDecoder: Decoder {
fn decode_static(buffer: &Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
fn decode_static_to_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
}
#[cfg(feature = "async")]
#[cfg_attr(feature = "async", async_trait::async_trait)]
pub trait AsyncDecoder: Decoder {
/// Asynchronous decoder
async fn decode_async(&mut self, buffer: &Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
/// Asynchronous decoder to user buffer.
async fn decode_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
}
#[cfg(feature = "async")]
#[cfg_attr(feature = "async", async_trait::async_trait)]
pub trait AsyncStaticDecoder: Decoder {
/// Asynchronous decoder
async fn decode_static_async(buffer: &Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
/// Asynchronous decoder to user buffer.
async fn decode_static_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
}
#[cfg(feature = "conversions")]
pub mod general;
+2
View File
@@ -57,4 +57,6 @@ pub enum NokhwaError {
UnsupportedOperationError(ApiBackend),
#[error("This operation is not implemented yet: {0}")]
NotImplementedError(String),
#[error("Failed To Convert: {0}")]
ConversionError(String),
}
+74 -180
View File
@@ -21,196 +21,90 @@ pub enum CustomFormatRequestType {
HighestFrameRate,
HighestResolution,
Closest,
Exact,
}
#[derive(Clone, Debug, Default, PartialOrd, PartialEq)]
pub struct FormatRequest {
resolution: Option<Range<Resolution>>,
frame_rate: Option<Range<FrameRate>>,
frame_format: Option<Vec<FrameFormat>>,
req_type: Option<CustomFormatRequestType>,
pub enum FormatRequest {
Closest {
resolution: Option<Range<Resolution>>,
frame_rate: Option<Range<FrameRate>>,
frame_format: Vec<FrameFormat>,
},
HighestFrameRate {
frame_rate: Range<FrameRate>,
frame_format: Vec<FrameFormat>,
},
HighestResolution {
resolution: Range<Resolution>,
frame_format: Vec<FrameFormat>,
},
Exact {
resolution: Resolution,
frame_rate: FrameRate,
frame_format: Vec<FrameFormat>,
},
}
impl FormatRequest {
pub fn new() -> Self {
Self::default()
}
pub fn with_standard_frame_formats(mut self) -> Self {
self.append_frame_formats(&mut vec![
FrameFormat::MJpeg,
FrameFormat::Rgb8,
FrameFormat::Yuv422,
FrameFormat::Nv12,
])
}
pub fn push_frame_format(mut self, frame_format: FrameFormat) -> Self {
match &mut self.frame_format {
Some(ffs) => ffs.push(frame_format),
None => self.frame_format = Some(vec![frame_format]),
}
self
}
pub fn remove_frame_format(mut self, frame_format: FrameFormat) -> Self {
if let Some(ffs) = &mut self.frame_format {
if let Some(idx) = ffs.iter().position(|ff| ff == &frame_format) {
ffs.remove(idx);
}
}
self
}
pub fn append_frame_formats(mut self, frame_formats: &mut Vec<FrameFormat>) -> Self {
match &mut self.frame_format {
Some(ffs) => ffs.append(frame_formats),
None => self.frame_format = Some(frame_formats.clone()),
}
self
}
pub fn reset_frame_formats(mut self) -> Self {
self.frame_format = None;
self
}
pub fn with_request_type(mut self, request_type: CustomFormatRequestType) -> Self {
self.req_type = Some(request_type);
self
}
pub fn reset_request_type(mut self) -> Self {
self.req_type = None;
self
}
pub fn set_resolution_range(mut self, resolution_range: Range<Resolution>) -> Self {
self.resolution = Some(resolution_range);
self
}
pub fn reset_resolution_range(mut self) -> Self {
self.resolution = None;
self
}
pub fn set_frame_rate_range(mut self, frame_rate_range: Range<FrameRate>) -> Self {
self.frame_rate = Some(frame_rate_range);
self
}
pub fn reset_frame_rate_range(mut self) -> Self {
self.frame_rate = None;
self
}
pub fn satisfied_by_format(&self, format: &CameraFormat) -> bool {
// check resolution
let resolution_satisfied = match self.resolution {
Some(res_range) => res_range.in_range(format.resolution()),
None => true,
};
let frame_rate_satisfied = match self.frame_rate {
Some(fps_range) => fps_range.in_range(format.frame_rate()),
None => true,
};
let frame_format_satisfied = match &self.frame_format {
Some(frame_formats) => frame_formats.contains(&format.format()),
None => true,
};
// we ignore custom bc that only makes sense in multiple formats
resolution_satisfied && frame_rate_satisfied && frame_format_satisfied
}
#[must_use]
pub fn resolve(&self, list_of_formats: &[CameraFormat]) -> Option<CameraFormat> {
// filter out bad results
let mut remaining_formats = list_of_formats.iter().filter(|x| self.satisfied_by_format(*x)).copied().collect::<Vec<CameraFormat>>();
if list_of_formats.is_empty() {
return None;
}
match self.req_type {
Some(request) => {
match request {
CustomFormatRequestType::HighestFrameRate => {
remaining_formats.sort_by(|a, b| {
a.frame_rate().partial_cmp(&b.frame_rate()).unwrap_or(Ordering::Equal)
});
Some(remaining_formats[0])
}
CustomFormatRequestType::HighestResolution => {
remaining_formats.sort_by(|a, b| {
a.frame_rate().partial_cmp(&b.frame_rate()).unwrap_or(Ordering::Equal)
});
Some(remaining_formats[0])
}
CustomFormatRequestType::Closest => {
let mut closest_type = match (&self.frame_rate, &self.resolution) {
(Some(_), Some(_)) => ClosestType::Both,
(Some(_), None) => ClosestType::FrameRate,
(None, Some(_)) => ClosestType::Resolution,
(None, None) => ClosestType::None,
};
match self {
FormatRequest::Closest { resolution, frame_rate, frame_format } => {
let resolution_point = resolution.map(|x| x.preferred())?;
match closest_type {
ClosestType::Resolution => {
let resolution_point = match self.resolution.map(|x| x.preferred()).flatten() {
Some(r) => r,
None => return None,
};
let frame_rate_point = frame_rate.map(|x| x.preferred())?;
// lets calcuate distance in 3 dimensions (add both resolution and frame_rate together)
let mut distances = remaining_formats.into_iter().map(|fmt| (fmt.resolution().distance_from(&resolution_point), fmt)).collect::<Vec<_>>();
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or_else(|| Ordering::Equal));
VecDeque::from(distances).pop_front().map(|x| x.1)
}
ClosestType::FrameRate => {
let frame_rate_point = match self.frame_rate.map(|x| x.preferred()).flatten() {
Some(f) => f,
None => return None,
};
let mut distances = remaining_formats.into_iter().map(|fmt| (fmt.frame_rate().distance_from(&frame_rate_point), fmt)).collect::<Vec<_>>();
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or_else(|| Ordering::Equal));
VecDeque::from(distances).pop_front().map(|x| x.1)
}
ClosestType::Both => {
let resolution_point = match self.resolution.map(|x| x.preferred()).flatten() {
Some(r) => r,
None => return None,
};
let frame_rate_point = match self.frame_rate.map(|x| x.preferred()).flatten() {
Some(f) => f,
None => return None,
};
// lets calcuate distance in 3 dimensions (add both resolution and frame_rate together)
let mut distances = remaining_formats.into_iter()
.map(|fmt| {
(fmt.frame_rate().distance_from(&frame_rate_point) + fmt.resolution().distance_from(&resolution_point) as f32, fmt)
})
.collect::<Vec<_>>();
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or_else(|| Ordering::Equal));
VecDeque::from(distances).pop_front().map(|x| x.1)
}
ClosestType::None => {
Some(remaining_formats[0])
}
}
}
}
let mut distances = list_of_formats.iter()
.filter(|x| {
frame_format.contains(&x.format())
})
.map(|fmt| {
((fmt.frame_rate() - frame_rate_point).abs() + fmt.resolution().distance_from(&resolution_point) as f32, fmt)
})
.collect::<Vec<_>>();
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
VecDeque::from(distances).pop_front().map(|x| x.1).copied()
}
None => {
Some(remaining_formats[0])
FormatRequest::HighestFrameRate { frame_rate, frame_format } => {
let mut formats = list_of_formats.iter().filter(|x| {
frame_format.contains(&x.format()) && frame_rate.in_range(x.frame_rate())
}).collect::<Vec<_>>();
formats.sort_by(|a, b| {
match a.frame_rate().partial_cmp(&b.frame_rate()) {
None | Some(Ordering::Equal) => a.resolution().cmp(&b.resolution()),
Some(ord) => ord,
}
});
formats.first().copied().copied()
}
FormatRequest::HighestResolution { resolution, frame_format } => {
let mut formats = list_of_formats.iter().filter(|x| {
frame_format.contains(&x.format()) && resolution.in_range(x.resolution())
}).collect::<Vec<_>>();
formats.sort_by(|a, b| {
match a.resolution().partial_cmp(&b.resolution()) {
None | Some(Ordering::Equal) => a.frame_rate().partial_cmp(&b.frame_rate()).unwrap_or(Ordering::Equal),
Some(ord) => ord,
}
});
formats.first().copied().copied()
}
FormatRequest::Exact { resolution, frame_rate, frame_format } => {
let mut formats = list_of_formats.iter().filter(|x| {
frame_format.contains(&x.format()) && resolution == &x.resolution() && frame_rate == &x.frame_rate()
}).collect::<Vec<_>>();
formats.sort_by(|a, b| {
match a.resolution().partial_cmp(&b.resolution()) {
None | Some(Ordering::Equal) => a.frame_rate().partial_cmp(&b.frame_rate()).unwrap_or(Ordering::Equal),
Some(ord) => ord,
}
});
formats.first().copied().copied()
}
}
}
+17 -8
View File
@@ -36,15 +36,20 @@ pub enum FrameFormat {
VP9,
// YCbCr formats
Yuv444,
// -> 422 16 BPP
Yuv422,
Uyv422,
Yuy2_422,
Uyvy_422,
// 420
Nv12,
Nv21,
Yv12,
I420,
I422,
I444,
// Grayscale Formats
Luma8,
@@ -72,8 +77,8 @@ impl FrameFormat {
FrameFormat::XVid,
FrameFormat::VP8,
FrameFormat::VP9,
FrameFormat::Yuv422,
FrameFormat::Uyv422,
FrameFormat::Yuy2_422,
FrameFormat::Uyvy_422,
FrameFormat::Nv12,
FrameFormat::Nv21,
FrameFormat::Yv12,
@@ -98,8 +103,8 @@ impl FrameFormat {
];
pub const CHROMA: &'static [FrameFormat] = &[
FrameFormat::Yuv422,
FrameFormat::Uyv422,
FrameFormat::Yuy2_422,
FrameFormat::Uyvy_422,
FrameFormat::Nv12,
FrameFormat::Nv21,
FrameFormat::Yv12,
@@ -121,8 +126,8 @@ impl FrameFormat {
FrameFormat::XVid,
FrameFormat::VP8,
FrameFormat::VP9,
FrameFormat::Yuv422,
FrameFormat::Uyv422,
FrameFormat::Yuy2_422,
FrameFormat::Uyvy_422,
FrameFormat::Nv12,
FrameFormat::Nv21,
FrameFormat::Yv12,
@@ -147,18 +152,22 @@ pub struct PlatformSpecific {
}
impl PlatformSpecific {
#[must_use]
pub fn new(backend: ApiBackend, format: u128) -> Self {
Self { backend, format }
}
#[must_use]
pub fn backend(&self) -> ApiBackend {
self.backend
}
#[must_use]
pub fn format(&self) -> u128 {
self.format
}
#[must_use]
pub fn as_tuple(&self) -> (ApiBackend, u128) {
(self.backend, self.format)
}
+4 -1
View File
@@ -1,5 +1,8 @@
#![deny(clippy::pedantic)]
#![warn(clippy::all)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_truncation)]
#![cfg_attr(feature = "test-fail-warning", deny(warnings))]
#![cfg_attr(feature = "docs-features", feature(doc_cfg))]
/*
@@ -25,5 +28,5 @@ pub mod format_request;
pub mod frame_format;
pub mod traits;
pub mod types;
pub mod decoder;
pub mod decoders;
pub mod utils;
+28 -27
View File
@@ -15,21 +15,14 @@
*/
use crate::{
buffer::Buffer,
error::NokhwaError,
types::{
ApiBackend, CameraControl, CameraFormat, CameraInfo, ControlValueSetter,
KnownCameraControl, Resolution,
},
buffer::Buffer, error::NokhwaError, format_request::FormatRequest, types::{
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter, KnownCameraControl, Resolution
}
};
use std::{borrow::Cow, collections::HashMap};
use crate::frame_format::FrameFormat;
use crate::types::FrameRate;
pub trait Backend {
const BACKEND: ApiBackend;
}
/// 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.
///
@@ -38,9 +31,6 @@ pub trait Backend {
/// - Behaviour can differ from backend to backend. While the Camera struct abstracts most of this away, if you plan to use the raw backend structs please read the `Quirks` section of each backend.
/// - If you call [`stop_stream()`](CaptureTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureTrait::open_stream()) to get more frames from the camera.
pub trait CaptureTrait {
/// Initialize the camera, preparing it for use, with a random format (usually the first one).
fn init(&mut self) -> Result<(), NokhwaError>;
/// Returns the current backend used.
fn backend(&self) -> ApiBackend;
@@ -235,6 +225,14 @@ pub trait CaptureTrait {
fn stop_stream(&mut self) -> Result<(), NokhwaError>;
}
/// A trait to open the capture backend.
pub trait OpenCaptureTrait: CaptureTrait {
/// Opens a camera.
/// # Errors
/// Please see implementations for errors.
fn open(index: &CameraIndex, camera_fmt: FormatRequest) -> Result<Self, NokhwaError> where Self: Sized;
}
impl<T> From<T> for Box<dyn CaptureTrait>
where
T: CaptureTrait + 'static,
@@ -247,9 +245,6 @@ where
#[cfg(feature = "async")]
#[cfg_attr(feature = "async", async_trait::async_trait)]
pub trait AsyncCaptureTrait: CaptureTrait {
/// Initialize the camera, preparing it for use, with a random format (usually the first one).
async fn init_async(&mut self) -> Result<(), NokhwaError>;
/// Forcefully refreshes the stored camera format, bringing it into sync with "reality" (current camera state)
/// # Errors
/// If the camera can not get its most recent [`CameraFormat`]. this will error.
@@ -271,16 +266,6 @@ pub trait AsyncCaptureTrait: CaptureTrait {
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError>;
/// Gets the compatible [`CameraFormat`] of the camera
/// # Errors
/// If it fails to get, this will error.
async fn compatible_camera_formats_async(&mut self) -> Result<Vec<CameraFormat>, NokhwaError>;
/// 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`](NokhwaError::UnsupportedOperationError)).
async fn compatible_fourcc_async(&mut self) -> Result<Vec<FrameFormat>, NokhwaError>;
/// Will set the current [`Resolution`]
/// This will reset the current stream if used while stream is opened.
///
@@ -353,7 +338,11 @@ where
}
}
/// Capture one frame from the camera and immediately stop.
pub trait OneShot: CaptureTrait {
/// Captures one frame from the camera, returning a [`Buffer`]
/// # Errors
/// If opening the stream or closing the stream has an error, this will also fail.
fn one_shot(&mut self) -> Result<Buffer, NokhwaError> {
if self.is_stream_open() {
self.frame()
@@ -381,7 +370,19 @@ pub trait AsyncOneShot: AsyncCaptureTrait {
}
}
pub trait VirtualBackendTrait {}
#[cfg(feature = "async")]
#[cfg_attr(feature = "async", async_trait::async_trait)]
/// A trait to open the capture backend.
pub trait AsyncOpenCaptureTrait: AsyncCaptureTrait {
/// Opens a camera.
/// # Errors
/// Please see implementations for errors.
async fn open(index: &CameraIndex, camera_fmt: FormatRequest) -> Result<Self, NokhwaError> where Self: Sized;
}
// pub trait VirtualBackendTrait {}
pub trait Distance<T> where T: PartialEq {
fn distance_from(&self, other: &Self) -> T;
+118 -167
View File
@@ -1,18 +1,15 @@
use crate::{
error::NokhwaError,
frame_format::{FrameFormat},
frame_format::FrameFormat,
};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use std::{
fmt::{
borrow::Borrow, cmp::Ordering, fmt::{
Debug,
Display,
Formatter
},
borrow::Borrow,
cmp::Ordering,
hash::{Hash, Hasher}
}, hash::{Hash, Hasher}, ops::{Add, Deref, DerefMut, Sub}
};
use crate::traits::Distance;
@@ -26,7 +23,7 @@ pub struct Range<T>
lower_inclusive: bool,
maximum: Option<T>,
upper_inclusive: bool,
preferred: Option<T>,
preferred: T,
}
impl<T> Range<T>
@@ -34,7 +31,7 @@ where
T: Copy + Clone + Debug + PartialOrd + PartialEq,
{
/// Create an upper and lower inclusive [`Range`]
pub fn new(preferred: Option<T>, min: Option<T>, max: Option<T>) -> Self {
pub fn new(preferred: T, min: Option<T>, max: Option<T>) -> Self {
Self {
minimum: min,
lower_inclusive: true,
@@ -45,7 +42,7 @@ where
}
pub fn with_inclusive(
preferred: Option<T>,
preferred: T,
min: Option<T>,
lower_inclusive: bool,
max: Option<T>,
@@ -60,21 +57,19 @@ where
}
}
pub fn with_preferred(preferred: T) -> Self {
pub fn exact(preferred: T) -> Self {
Self {
minimum: None,
lower_inclusive: true,
maximum: None,
upper_inclusive: true,
preferred: Some(preferred),
preferred,
}
}
pub fn in_range(&self, item: T) -> bool {
if let Some(pref) = self.preferred {
if pref == item {
return true
}
if item == self.preferred {
return true
}
if let Some(min) = self.minimum {
@@ -116,11 +111,7 @@ where
self.upper_inclusive = upper_inclusive;
}
pub fn set_preferred(&mut self, preferred: T) {
self.preferred = Some(preferred);
}
pub fn reset_preferred(&mut self) {
self.preferred = None;
self.preferred = preferred;
}
pub fn minimum(&self) -> Option<T> {
self.minimum
@@ -134,7 +125,7 @@ where
pub fn upper_inclusive(&self) -> bool {
self.upper_inclusive
}
pub fn preferred(&self) -> Option<T> {
pub fn preferred(&self) -> T {
self.preferred
}
}
@@ -149,7 +140,7 @@ where
lower_inclusive: true,
maximum: None,
upper_inclusive: true,
preferred: None,
preferred: T::default(),
}
}
}
@@ -158,7 +149,7 @@ where
/// Describes the index of the camera.
/// - Index: A numbered index
/// - String: A string, used for `IPCameras`.
/// - String: A string, used for `IPCameras` or on the Browser as DeviceIDs.
#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub enum CameraIndex {
@@ -237,7 +228,6 @@ impl TryFrom<CameraIndex> for usize {
/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest.
/// # JS-WASM
/// This is exported as `JSResolution`
#[cfg_attr(feature = "output-wasm", wasm_bindgen)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)]
pub struct Resolution {
@@ -245,13 +235,9 @@ pub struct Resolution {
pub height_y: u32,
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen)]
impl Resolution {
/// Create a new resolution from 2 image size coordinates.
/// # JS-WASM
/// This is exported as a constructor for [`Resolution`].
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))]
pub fn new(x: u32, y: u32) -> Self {
Resolution {
width_x: x,
@@ -260,20 +246,14 @@ impl Resolution {
}
/// Get the width of Resolution
/// # JS-WASM
/// This is exported as `get_Width`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Width))]
#[inline]
pub fn width(self) -> u32 {
self.width_x
}
/// Get the height of Resolution
/// # JS-WASM
/// This is exported as `get_Height`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Height))]
#[inline]
pub fn height(self) -> u32 {
self.height_y
@@ -281,7 +261,6 @@ impl Resolution {
/// Get the x (width) of Resolution
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))]
#[inline]
pub fn x(self) -> u32 {
self.width_x
@@ -289,11 +268,15 @@ impl Resolution {
/// Get the y (height) of Resolution
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))]
#[inline]
pub fn y(self) -> u32 {
self.height_y
}
#[must_use]
pub fn aspect_ratio(&self) -> f64 {
f64::from(self.width_x) / f64::from(self.height_y)
}
}
impl Display for Resolution {
@@ -330,139 +313,95 @@ impl Distance<u32> for Resolution {
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
/// The frame rate of a camera. DO NOT CONSTRUCT THIS ENUM DIRECTLY. YOU WILL VIOLATE INVARIANTS. Use [`FrameRate::new_integer`], [`FrameRate::new_fraction`], or [`FrameRate::new_float`] instead.
pub enum FrameRate {
/// The driver reports the frame rate as a clean integer (e.g. 30 FPS).
Integer(u32),
/// The driver reports the frame rate as a floating point number (e.g. 29.97 FPS)
Float(f32),
/// The driver reports the frame rate as a fraction (e.g. 2997/1000 FPS)
Fraction {
numerator: u16,
denominator: u16,
}
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct FrameRate(pub f32);
impl FrameRate {
pub fn new_integer(fps: u32) -> Result<Self, NokhwaError> {
if fps == 0 {
return Err(NokhwaError::StructureError { structure: "FrameRate".to_string(), error: "Framerate cannot be 0".to_string() })
}
Ok(FrameRate::Integer(fps))
#[must_use]
pub fn new(fps: f32) -> Self {
Self(fps)
}
pub fn new_float(fps: f32) -> Result<Self, NokhwaError> {
if fps.is_nan() || fps.is_infinite() || fps.is_sign_negative() || (fps > f32::EPSILON) {
return Err(NokhwaError::StructureError { structure: "FrameRate".to_string(), error: "Invalid F32 FrameRate".to_string() })
}
Ok(FrameRate::Float(fps))
}
pub fn new_fraction(numerator: u16, denominator: u16) -> Result<Self, NokhwaError> {
if numerator == 0 || denominator == 0 {
return Err(NokhwaError::StructureError { structure: "FrameRate".to_string(), error: "Invalid Fraction (denominator or numerator is 0)".to_string() })
}
Ok(
FrameRate::Fraction {
numerator,
denominator,
}
)
}
pub fn as_float(&self) -> f32 {
match self {
FrameRate::Integer(fps) => *fps as f32,
FrameRate::Float(fps) => *fps,
FrameRate::Fraction { numerator, denominator } => (*numerator as f32) / (*denominator as f32)
}
}
pub fn as_u32(&self) -> u32 {
match self {
FrameRate::Integer(fps) => *fps,
FrameRate::Float(fps) => *fps as u32,
FrameRate::Fraction { numerator, denominator } => (numerator / denominator) as u32,
}
#[must_use]
pub fn frame_rate(&self) -> f32 {
self.0
}
}
impl Default for FrameRate {
fn default() -> Self {
FrameRate::Integer(30)
impl Deref for FrameRate {
type Target = f32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialOrd for FrameRate {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let this_float = self.as_float();
let other = other.as_float();
this_float.partial_cmp(&other)
impl DerefMut for FrameRate {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Hash for FrameRate {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
FrameRate::Integer(i) => {
state.write_u32(*i)
}
FrameRate::Float(f) => {
state.write(f.to_string().as_bytes())
}
FrameRate::Fraction { denominator, numerator } => {
state.write_u16(*denominator);
state.write_u16(*numerator);
}
}
state.write_u32(self.0.to_bits());
}
}
impl Default for FrameRate {
fn default() -> Self {
FrameRate(30.0)
}
}
impl Display for FrameRate {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FrameRate::Integer(fps) => write!(f, "Framerate: {fps} FPS"),
FrameRate::Float(fps) => write!(f, "Framerate: {fps} FPS"),
FrameRate::Fraction { .. } => {
let as_float = self.as_float();
write!(f, "Framerate: {as_float} FPS")
}
}
write!(f, "{} FPS", self.0)
}
}
impl Distance<f32> for FrameRate {
fn distance_from(&self, other: &Self) -> f32 {
let self_as_float = self.as_float();
let other_as_float = other.as_float();
impl Add for FrameRate {
type Output = FrameRate;
self_as_float.powi(2) + other_as_float.powi(2)
fn add(self, rhs: Self) -> Self::Output {
(self.0 + rhs.0).into()
}
}
impl From<u32> for FrameRate {
fn from(value: u32) -> Self {
FrameRate::Integer(value)
impl Add for &FrameRate {
type Output = FrameRate;
fn add(self, rhs: Self) -> Self::Output {
(self.0 + rhs.0).into()
}
}
impl Sub for FrameRate {
type Output = FrameRate;
fn sub(self, rhs: Self) -> Self::Output {
(self.0 - rhs.0).into()
}
}
impl Sub for &FrameRate {
type Output = FrameRate;
fn sub(self, rhs: Self) -> Self::Output {
(self.0 - rhs.0).into()
}
}
impl From<f32> for FrameRate {
fn from(value: f32) -> Self {
FrameRate::Float(value)
Self(value)
}
}
impl From<(u16, u16)> for FrameRate {
fn from(value: (u16, u16)) -> Self {
FrameRate::Fraction {
numerator: value.0,
denominator: value.1,
}
impl From<FrameRate> for f32 {
fn from(value: FrameRate) -> Self {
value.0
}
}
@@ -551,7 +490,7 @@ impl Default for CameraFormat {
CameraFormat {
resolution: Resolution::new(640, 480),
format: FrameFormat::MJpeg,
frame_rate: FrameRate::Integer(30),
frame_rate: FrameRate(30.),
}
}
}
@@ -570,7 +509,6 @@ impl Display for CameraFormat {
/// `description` amd `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.
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd)]
#[cfg_attr(feature = "output-wasm", wasm_bindgen)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct CameraInfo {
human_name: String,
@@ -579,13 +517,11 @@ pub struct CameraInfo {
index: CameraIndex,
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = CameraInfo))]
impl CameraInfo {
/// Create a new [`CameraInfo`].
/// # JS-WASM
/// This is exported as a constructor for [`CameraInfo`].
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))]
// 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!
// Finally fixed this insanity. Hopefully I didnt torment anyone by actually putting this in a stable release.
@@ -602,10 +538,6 @@ impl CameraInfo {
/// # JS-WASM
/// This is exported as a `get_HumanReadableName`.
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = HumanReadableName)
)]
// yes, i know, unnecessary alloc this, unnecessary alloc that
// but wasm bindgen
pub fn human_name(&self) -> String {
@@ -615,10 +547,6 @@ impl CameraInfo {
/// Set the device info's human name.
/// # JS-WASM
/// This is exported as a `set_HumanReadableName`.
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = HumanReadableName)
)]
pub fn set_human_name(&mut self, human_name: &str) {
self.human_name = human_name.to_string();
}
@@ -627,7 +555,6 @@ impl CameraInfo {
/// # JS-WASM
/// This is exported as a `get_Description`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Description))]
pub fn description(&self) -> &str {
self.description.borrow()
}
@@ -635,7 +562,6 @@ impl CameraInfo {
/// Set the device info's description.
/// # JS-WASM
/// This is exported as a `set_Description`.
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Description))]
pub fn set_description(&mut self, description: &str) {
self.description = description.to_string();
}
@@ -644,7 +570,6 @@ impl CameraInfo {
/// # JS-WASM
/// This is exported as a `get_MiscString`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = MiscString))]
pub fn misc(&self) -> String {
self.misc.clone()
}
@@ -652,7 +577,6 @@ impl CameraInfo {
/// Set the device info's misc.
/// # JS-WASM
/// This is exported as a `set_MiscString`.
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = MiscString))]
pub fn set_misc(&mut self, misc: &str) {
self.misc = misc.to_string();
}
@@ -661,7 +585,6 @@ impl CameraInfo {
/// # JS-WASM
/// This is exported as a `get_Index`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index))]
pub fn index(&self) -> &CameraIndex {
&self.index
}
@@ -669,7 +592,6 @@ impl CameraInfo {
/// Set the device info's index.
/// # JS-WASM
/// This is exported as a `set_Index`.
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Index))]
pub fn set_index(&mut self, index: CameraIndex) {
self.index = index;
}
@@ -725,6 +647,7 @@ pub enum KnownCameraControl {
Exposure,
Iris,
Focus,
Facing,
/// Other camera control. Listed is the ID.
/// Wasteful, however is needed for a unified API across Windows, Linux, and MacOSX due to Microsoft's usage of GUIDs.
///
@@ -734,8 +657,8 @@ pub enum KnownCameraControl {
/// All camera controls in an array.
#[must_use]
pub const fn all_known_camera_controls() -> [KnownCameraControl; 15] {
[
pub const fn all_known_camera_controls() -> &'static [KnownCameraControl] {
&[
KnownCameraControl::Brightness,
KnownCameraControl::Contrast,
KnownCameraControl::Hue,
@@ -751,6 +674,7 @@ pub const fn all_known_camera_controls() -> [KnownCameraControl; 15] {
KnownCameraControl::Exposure,
KnownCameraControl::Iris,
KnownCameraControl::Focus,
KnownCameraControl::Facing,
]
}
@@ -1402,7 +1326,27 @@ impl Display for ControlValueSetter {
}
/// The list of known capture backends to the library. <br>
/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform.
/// - `Auto` - Use automatic selection.
/// - `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.
pub enum SelectableBackend {
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.
@@ -1413,7 +1357,6 @@ impl Display for ControlValueSetter {
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub enum ApiBackend {
Auto,
Custom(&'static str),
AVFoundation,
Video4Linux,
@@ -1495,7 +1438,7 @@ impl Display for ApiBackend {
// }
// }
#[cfg(all(feature = "mjpeg", not(target_arch = "wasm")))]
#[cfg(all(feature = "conversions", not(target_arch = "wasm32")))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "mjpeg")))]
#[inline]
fn decompress<'a>(
@@ -1538,7 +1481,7 @@ fn decompress<'a>(
/// # Safety
/// This function uses `unsafe`. The caller must ensure that:
/// - The input data is of the right size, does not exceed bounds, and/or the final size matches with the initial size.
#[cfg(all(feature = "mjpeg", not(target_arch = "wasm")))]
#[cfg(all(feature = "conversions", not(target_arch = "wasm32")))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "mjpeg")))]
#[inline]
pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
@@ -1564,7 +1507,11 @@ pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
}
}
#[cfg(not(all(feature = "mjpeg", not(target_arch = "wasm"))))]
/// 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.
#[cfg(not(all(feature = "conversions", not(target_arch = "wasm32"))))]
pub fn mjpeg_to_rgb(_data: &[u8], _rgba: bool) -> Result<Vec<u8>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not available on WASM".to_string(),
@@ -1574,11 +1521,11 @@ 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.
#[cfg(all(feature = "mjpeg", not(target_arch = "wasm")))]
#[cfg(not(all(feature = "conversions", not(target_arch = "wasm32"))))]
#[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> {
let mut jpeg_decompress = decompress(data, rgba)?;
let mut jpeg_decompress = mozjpeg::decompress(data, rgba)?;
// assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size());
if dest.len() != jpeg_decompress.min_flat_buffer_size() {
@@ -1601,7 +1548,11 @@ pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(),
Ok(())
}
#[cfg(not(all(feature = "mjpeg", not(target_arch = "wasm"))))]
// TODO: deprecate?
/// 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.
#[cfg(all(feature = "conversions", not(target_arch = "wasm32")))]
pub fn buf_mjpeg_to_rgb(_data: &[u8], _dest: &mut [u8], _rgba: bool) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not available on WASM".to_string(),
@@ -1632,17 +1583,17 @@ pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<()
let mut buf: Vec<u8> = Vec::new();
if data.len() % 4 != 0 {
return Err(NokhwaError::ProcessFrameError {
src: FrameFormat::Yuv422.into(),
src: FrameFormat::Yuy2_422,
destination: "RGB888".to_string(),
error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)"
.to_string(),
});
}
for chunk in data.chunks_exact(4) {
let y0 = chunk[0] as f32;
let u = chunk[1] as f32;
let y1 = chunk[2] as f32;
let v = chunk[3] as f32;
let y0 = f32::from(chunk[0]);
let u = f32::from(chunk[1]);
let y1 = f32::from(chunk[2]);
let v = f32::from(chunk[3]);
let r0 = y0 + 1.370_705 * (v - 128.);
let g0 = y0 - 0.698_001 * (v - 128.) - 0.337_633 * (u - 128.);
+1 -1
View File
@@ -11,7 +11,7 @@ pub fn min_max_range<N: Copy + PartialOrd + AddAssign<N> + Sized>(min: N, max: N
break
}
nums.push(counter)
nums.push(counter);
}
nums
File diff suppressed because it is too large Load Diff
+82 -82
View File
@@ -1,82 +1,82 @@
use image::{ImageBuffer, Rgb};
use nokhwa_core::buffer::Buffer;
use nokhwa_core::decoder::{Decoder, IdemptDecoder, StaticDecoder};
use nokhwa_core::error::NokhwaError;
use nokhwa_core::frame_format::{FrameFormat, SourceFrameFormat};
#[inline]
fn decompress(
data: &[u8],
rgba: bool,
) -> Result<, 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(),
})
}
}
}
pub struct MJPegDecoder;
impl Decoder for MJPegDecoder {
const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[SourceFrameFormat::FrameFormat(FrameFormat::MJpeg)];
type Pixel = Rgb<u8>;
type Container = Vec<u8>;
type Error = NokhwaError;
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
todo!()
}
fn predicted_size_of_frame(&mut self) -> Option<usize> {
todo!()
}
}
impl StaticDecoder for MJPegDecoder {
fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
todo!()
}
}
impl IdemptDecoder for MJPegDecoder {
fn decode_nm(&self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
todo!()
}
}
// use image::{ImageBuffer, Rgb};
// use nokhwa_core::buffer::Buffer;
// use nokhwa_core::r#mod::{Decoder, IdemptDecoder, StaticDecoder};
// use nokhwa_core::error::NokhwaError;
// use nokhwa_core::frame_format::{FrameFormat, SourceFrameFormat};
//
// #[inline]
// fn decompress(
// data: &[u8],
// rgba: bool,
// ) -> Result<, 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(),
// })
// }
// }
// }
//
//
// pub struct MJPegDecoder;
//
// impl Decoder for MJPegDecoder {
// const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[SourceFrameFormat::FrameFormat(FrameFormat::MJpeg)];
// type Pixel = Rgb<u8>;
// type Container = Vec<u8>;
// type Error = NokhwaError;
//
// fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
//
// fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
// todo!()
// }
//
// fn predicted_size_of_frame(&mut self) -> Option<usize> {
// todo!()
// }
// }
//
// impl StaticDecoder for MJPegDecoder {
// fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
//
// fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
// todo!()
// }
// }
//
// impl IdemptDecoder for MJPegDecoder {
// fn decode_nm(&self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
//
// fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
// todo!()
// }
// }
+41 -41
View File
@@ -1,41 +1,41 @@
use image::{ImageBuffer, Rgb};
use nokhwa_core::buffer::Buffer;
use nokhwa_core::decoder::{Decoder, IdemptDecoder, StaticDecoder};
use nokhwa_core::frame_format::SourceFrameFormat;
pub struct NV12Decoder {}
impl Decoder for NV12Decoder {
const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[];
type Pixel = Rgb<u8>;
type Container = Vec<u8>;
type Error = ();
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
todo!()
}
fn predicted_size_of_frame(&mut self) -> Option<usize> {
todo!()
}
}
impl StaticDecoder for NV12Decoder {
fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
todo!()
}
}
impl IdemptDecoder for NV12Decoder {
fn decode_nm(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
}
// use image::{ImageBuffer, Rgb};
// use nokhwa_core::buffer::Buffer;
// use nokhwa_core::r#mod::{Decoder, IdemptDecoder, StaticDecoder};
// use nokhwa_core::frame_format::SourceFrameFormat;
//
// pub struct NV12Decoder {}
//
// impl Decoder for NV12Decoder {
// const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[];
// type Pixel = Rgb<u8>;
// type Container = Vec<u8>;
// type Error = ();
//
// fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
//
// fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
// todo!()
// }
//
// fn predicted_size_of_frame(&mut self) -> Option<usize> {
// todo!()
// }
// }
//
// impl StaticDecoder for NV12Decoder {
// fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
//
// fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
// todo!()
// }
// }
//
// impl IdemptDecoder for NV12Decoder {
// fn decode_nm(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
// }
+54 -54
View File
@@ -1,54 +1,54 @@
use image::ImageBuffer;
use nokhwa_core::buffer::Buffer;
use nokhwa_core::decoder::{Decoder, IdemptDecoder, StaticDecoder};
use nokhwa_core::frame_format::SourceFrameFormat;
// 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(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 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.
pub struct YUYVDecoder {}
impl Decoder for YUYVDecoder {
const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[];
type Pixel = ();
type Container = ();
type Error = ();
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
todo!()
}
fn predicted_size_of_frame(&mut self) -> Option<usize> {
todo!()
}
}
impl StaticDecoder for YUYVDecoder {
fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
todo!()
}
}
impl IdemptDecoder for YUYVDecoder {
fn decode_nm(&self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
todo!()
}
fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
todo!()
}
}
// use image::ImageBuffer;
// use nokhwa_core::buffer::Buffer;
// use nokhwa_core::r#mod::{Decoder, IdemptDecoder, StaticDecoder};
// use nokhwa_core::frame_format::SourceFrameFormat;
//
// // 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(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 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.
// pub struct YUYVDecoder {}
//
// impl Decoder for YUYVDecoder {
// const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[];
// type Pixel = ();
// type Container = ();
// type Error = ();
//
// fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
//
// fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
// todo!()
// }
//
// fn predicted_size_of_frame(&mut self) -> Option<usize> {
// todo!()
// }
// }
//
// impl StaticDecoder for YUYVDecoder {
// fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
//
// fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
// todo!()
// }
// }
//
// impl IdemptDecoder for YUYVDecoder {
// fn decode_nm(&self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
// todo!()
// }
//
// fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
// todo!()
// }
// }
-2720
View File
File diff suppressed because it is too large Load Diff