0.10 prototype: start using CamerIndex, use browser in Camera

This commit is contained in:
l1npengtul
2021-11-29 13:48:36 +09:00
parent 6f865bc5d2
commit dc35d65cce
16 changed files with 609 additions and 243 deletions
+9 -10
View File
@@ -1,11 +1,10 @@
[package]
name = "nokhwa"
version = "0.9.1"
version = "0.10.0"
authors = ["l1npengtul <l1npengtul@protonmail.com>"]
edition = "2018"
edition = "2021"
description = "A Simple-to-use, cross-platform Rust Webcam Capture Library"
keywords = ["camera", "webcam", "capture", "cross-platform"]
resolver = "2"
license = "Apache-2.0"
repository = "https://github.com/l1npengtul/nokhwa"
@@ -15,8 +14,7 @@ repository = "https://github.com/l1npengtul/nokhwa"
crate-type = ["cdylib", "rlib"]
[features]
default = ["decoding", "flume"]
decoding = ["mozjpeg"]
default = ["flume"]
input-v4l = ["v4l", "v4l2-sys-mit"]
input-msmf = ["nokhwa-bindings-windows"]
input-avfoundation = ["nokhwa-bindings-macos"]
@@ -24,7 +22,7 @@ input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration"]
input-opencv = ["opencv", "opencv/clang-runtime"]
input-ipcam = ["input-opencv"]
input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex"]
input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen"]
input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "wasm-rs-async-executor"]
output-wgpu = ["wgpu"]
output-wasm = ["input-jscam"]
output-threaded = ["parking_lot"]
@@ -37,15 +35,12 @@ test-fail-warning = []
[dependencies]
thiserror = "1.0.26"
paste = "1.0.5"
mozjpeg = "0.8.24"
[dependencies.flume]
version = "0.10.8"
optional = true
[target.'cfg(not(target_family = "wasm"))'.dependencies.mozjpeg]
version = "0.8.24"
optional = true
[dependencies.image]
version = "^0.23"
default-features = false
@@ -140,6 +135,10 @@ optional = true
version = "^0.4"
optional = true
[dependencies.wasm-rs-async-executor]
version = "^0.9"
optional = true
[dependencies.wee_alloc]
version = "0.4.5"
optional = true
+3 -3
View File
@@ -11,11 +11,11 @@ keywords = ["media-foundation", "windows", "capture", "webcam"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1.0.26"
thiserror = "^1.0"
[target.'cfg(all(target_os = "windows", windows))'.dependencies]
windows = "0.21.1"
windows = "^0.28"
lazy_static = "1.4.0"
[target.'cfg(all(target_os = "windows", windows))'.build-dependencies.windows]
version = "0.21.1"
version = "^0.28"
+18 -12
View File
@@ -15,15 +15,16 @@
*/
use crate::{
mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraIndex, CameraInfo,
CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError,
Resolution,
};
use image::{ImageBuffer, Rgb};
use nokhwa_bindings_macos::avfoundation::{
query_avfoundation, AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession,
AVCaptureVideoCallback, AVCaptureVideoDataOutput, AVFourCC,
};
use std::{any::Any, borrow::Cow, collections::HashMap};
use std::{any::Any, borrow::Borrow, borrow::Cow, collections::HashMap, ops::Deref};
/// The backend struct that interfaces with V4L2.
/// To see what this does, please see [`CaptureBackendTrait`].
@@ -33,28 +34,33 @@ use std::{any::Any, borrow::Cow, collections::HashMap};
/// - This only works on 64 bit platforms.
/// - FPS adjustment does not work.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-avfoundation")))]
pub struct AVFoundationCaptureDevice {
pub struct AVFoundationCaptureDevice<'a> {
device: AVCaptureDevice,
dev_input: Option<AVCaptureDeviceInput>,
session: Option<AVCaptureSession>,
data_out: Option<AVCaptureVideoDataOutput>,
data_collect: Option<AVCaptureVideoCallback>,
info: CameraInfo,
info: CameraInfo<'a>,
format: CameraFormat,
}
impl AVFoundationCaptureDevice {
impl<'a> AVFoundationCaptureDevice<'a> {
/// Creates a new capture device using the `AVFoundation` backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// # Errors
/// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user.
pub fn new(index: usize, camera_format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
/// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new(
index: CameraIndex<'a>,
camera_format: Option<CameraFormat>,
) -> Result<Self, NokhwaError> {
let camera_format = match camera_format {
Some(fmt) => fmt,
None => CameraFormat::default(),
};
let index = index.index_num()? as usize;
let device_descriptor: CameraInfo = match query_avfoundation()?.into_iter().nth(index) {
Some(descriptor) => descriptor.into(),
None => {
@@ -85,7 +91,7 @@ impl AVFoundationCaptureDevice {
/// # Errors
/// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user.
pub fn new_with(
index: usize,
index: CameraIndex<'a>,
width: u32,
height: u32,
fps: u32,
@@ -223,7 +229,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
let session = AVCaptureSession::new();
session.begin_configuration();
session.add_input(&input)?;
let callback = AVCaptureVideoCallback::new(self.info.index());
let callback = AVCaptureVideoCallback::new(self.info.index_num()? as usize);
let output = AVCaptureVideoDataOutput::new();
output.add_delegate(&callback)?;
session.add_output(&output)?;
@@ -293,8 +299,8 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
Some(collector) => {
let data = collector.frame_to_slice()?;
let data = match data.1 {
AVFourCC::YUV2 => Cow::from(yuyv422_to_rgb888(&data.0)?),
AVFourCC::MJPEG => Cow::from(mjpeg_to_rgb888(&data.0)?),
AVFourCC::YUV2 => Cow::from(yuyv422_to_rgb888(data.0.borrow())),
AVFourCC::MJPEG => Cow::from(mjpeg_to_rgb888(data.0.borrow())),
};
Ok(data)
}
+266
View File
@@ -0,0 +1,266 @@
use crate::{
js_camera::JSCameraResizeMode,
js_camera::{query_js_cameras, JSCameraConstraintsBuilder},
CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait,
FrameFormat, JSCamera, KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use std::{any::Any, borrow::Cow, collections::HashMap};
/// Captures using the Browser API. This internally wraps [`JSCamera`].
///
/// # Quirks
/// - FourCC setting is ignored
/// - Cannot get compatible resolution(s).
/// - CameraControl(s) are not supported.
/// - All frame capture is done by creating (then destorying) a canvas on the DOM.
/// - Many methods are blocking on user input.
pub struct BrowserCaptureDevice<'a> {
camera: JSCamera,
info: CameraInfo<'a>,
}
impl<'a> BrowserCaptureDevice<'a> {
// WARN: blocking on pass integer for index
/// Creates a new camera from an [`CameraIndex`]. It can take [`CameraIndex::Index`] or [`CameraIndex::String`] (NOTE: blocks on [`CameraIndex::Index`])
///
/// # Errors
/// If the device is not found, browser not supported, or camera is over-constrained this will error.
pub fn new(index: CameraIndex<'a>, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let (group_id, device_id) = match &index {
CameraIndex::Index(i) => {
let query_devices =
wasm_rs_async_executor::single_threaded::block_on(query_js_cameras())?;
match query_devices.into_iter().nth(*i as usize) {
Some(info) => {
let ids = info
.to_string()
.split(" ")
.map(ToString::to_string)
.collect::<Vec<String>>();
match (ids.get(0), ids.get(1)) {
(Some(group_id), Some(device_id)) => {
(group_id.clone(), device_id.clone())
}
(_, _) => {
return Err(NokhwaError::OpenDeviceError(
"Invalid Index".to_string(),
index.to_string(),
))
}
}
}
None => {
return Err(NokhwaError::OpenDeviceError(
"Device not found".to_string(),
index.to_string(),
))
}
}
}
CameraIndex::String(id) => {
let ids = id
.to_string()
.split(" ")
.map(ToString::to_string)
.collect::<Vec<String>>();
match (ids.get(0), ids.get(1)) {
(Some(group_id), Some(device_id)) => (group_id.clone(), device_id.clone()),
(_, _) => {
return Err(NokhwaError::OpenDeviceError(
"Invalid Index".to_string(),
index.to_string(),
))
}
}
}
};
let camera_format = cam_fmt.unwrap_or_default();
let constraints = JSCameraConstraintsBuilder::new()
.frame_rate(camera_format.frame_rate())
.resolution(camera_format.resolution())
.aspect_ratio(camera_format.width() as f64 / camera_format.height() as f64)
.group_id(&group_id)
.group_id_exact(true)
.device_id(&device_id)
.device_id_exact(true)
.resize_mode(JSCameraResizeMode::Any)
.resize_mode(JSCameraResizeMode::Any)
.build();
let camera = wasm_rs_async_executor::single_threaded::block_on(JSCamera::new(constraints))?;
let info = (|| {
let cameras = wasm_rs_async_executor::single_threaded::block_on(query_js_cameras())?;
let giddid = format!("{} {}", group_id, device_id);
for cam in cameras {
if cam.misc() == giddid {
return Ok(cam);
}
}
Ok(CameraInfo::new(
"".to_string(),
"videoinput".to_string(),
giddid,
index,
))
})()?;
Ok(BrowserCaptureDevice { camera, info })
}
/// Creates a new camera from an [`CameraIndex`] and raw parts. It can take [`CameraIndex::Index`] or [`CameraIndex::String`] (NOTE: blocks on [`CameraIndex::Index`])
///
/// # Errors
/// If the device is not found, browser not supported, or camera is over-constrained this will error.
pub fn new_with(
index: CameraIndex<'a>,
width: u32,
height: u32,
fps: u32,
fourcc: FrameFormat,
) -> Result<Self, NokhwaError> {
Self::new(
index,
Some(CameraFormat::new(
Resolution::new(width, height),
fourcc,
fps,
)),
)
}
}
impl<'a> CaptureBackendTrait for BrowserCaptureDevice<'a> {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::Browser
}
fn camera_info(&self) -> &CameraInfo {
&self.info
}
fn camera_format(&self) -> CameraFormat {
CameraFormat::new(
self.camera.resolution(),
FrameFormat::MJPEG,
self.camera.constraints().frame_rate(),
)
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
let current_constraints = self.camera.constraints();
let new_constraints = JSCameraConstraintsBuilder::new()
.resolution(new_fmt.resolution())
.aspect_ratio(new_fmt.width() as f64 / new_fmt.height() as f64)
.frame_rate(new_fmt.frame_rate())
.group_id(&current_constraints.group_id())
.device_id(&current_constraints.device_id())
.resize_mode(JSCameraResizeMode::Any)
.build();
let _ = self.camera.set_constraints(new_constraints);
match self.camera.apply_constraints() {
Ok(_) => Ok(()),
Err(why) => {
let _ = self.camera.set_constraints(current_constraints); // swallow errors - revert
Err(why)
}
}
}
fn compatible_list_by_resolution(
&mut self,
_: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
Ok(vec![FrameFormat::MJPEG, FrameFormat::YUYV])
}
fn resolution(&self) -> Resolution {
self.camera.resolution()
}
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
let mut current_format = self.camera_format();
current_format.set_resolution(new_res);
self.set_camera_format(current_format)
}
fn frame_rate(&self) -> u32 {
self.camera.constraints().frame_rate()
}
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
let mut current_format = self.camera_format();
current_format.set_frame_rate(new_fps);
self.set_camera_format(current_format)
}
fn frame_format(&self) -> FrameFormat {
FrameFormat::MJPEG
}
fn set_frame_format(&mut self, _: FrameFormat) -> Result<(), NokhwaError> {
Ok(())
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
Ok(vec![])
}
fn camera_control(&self, _: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn set_camera_control(&mut self, _: CameraControl) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
Ok(vec![])
}
fn raw_camera_control(&self, _: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn set_raw_camera_control(&mut self, _: &dyn Any, _: &dyn Any) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
Ok(())
}
fn is_stream_open(&self) -> bool {
self.camera.is_open()
}
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
self.camera.frame()
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
self.camera.frame_raw()
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
self.camera.stop_all()
}
}
+24 -15
View File
@@ -15,8 +15,9 @@
*/
use crate::{
mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraIndex, CameraInfo,
CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError,
Resolution,
};
use flume::Receiver;
use glib::Quark;
@@ -42,29 +43,31 @@ type PipelineGenRet = (Element, AppSink, Receiver<ImageBuffer<Rgb<u8>, Vec<u8>>>
/// - `Drop`-ing this may cause a `panic`.
/// - Setting controls is not supported.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-gst")))]
pub struct GStreamerCaptureDevice {
pub struct GStreamerCaptureDevice<'a> {
pipeline: Element,
app_sink: AppSink,
camera_format: CameraFormat,
camera_info: CameraInfo,
camera_info: CameraInfo<'a>,
receiver: Receiver<ImageBuffer<Rgb<u8>, Vec<u8>>>,
caps: Option<Caps>,
}
impl GStreamerCaptureDevice {
impl<'a> GStreamerCaptureDevice<'a> {
/// Creates a new capture device using the `GStreamer` backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
///
/// `GStreamer` uses `v4l2src` on linux, `ksvideosrc` on windows, and `autovideosrc` on mac.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// # Errors
/// This function will error if the camera is currently busy or if `GStreamer` can't read device information.
pub fn new(index: usize, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
/// This function will error if the camera is currently busy or if `GStreamer` can't read device information. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new(index: CameraIndex<'a>, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let camera_format = match cam_fmt {
Some(fmt) => fmt,
None => CameraFormat::default(),
};
let index = index.as_index()?;
if let Err(why) = gstreamer::init() {
return Err(NokhwaError::InitializeError {
backend: CaptureAPIBackend::GStreamer,
@@ -99,7 +102,7 @@ impl GStreamerCaptureDevice {
error: format!("Not started, {}", why),
});
}
let device = match device_monitor.devices().get(index) {
let device = match device_monitor.devices().get(index as usize) {
Some(dev) => dev.clone(),
None => {
return Err(NokhwaError::OpenDeviceError(
@@ -115,13 +118,13 @@ impl GStreamerCaptureDevice {
DeviceExt::display_name(&device).to_string(),
DeviceExt::device_class(&device).to_string(),
"".to_string(),
index,
CameraIndex::Index(index),
),
caps,
)
};
let (pipeline, app_sink, receiver) = generate_pipeline(camera_format, index)?;
let (pipeline, app_sink, receiver) = generate_pipeline(camera_format, index as usize)?;
Ok(GStreamerCaptureDevice {
pipeline,
@@ -138,13 +141,18 @@ impl GStreamerCaptureDevice {
/// `GStreamer` uses `v4l2src` on linux, `ksvideosrc` on windows, and `autovideosrc` on mac.
/// # Errors
/// This function will error if the camera is currently busy or if `GStreamer` can't read device information.
pub fn new_with(index: usize, width: u32, height: u32, fps: u32) -> Result<Self, NokhwaError> {
pub fn new_with(
index: CameraIndex<'a>,
width: u32,
height: u32,
fps: u32,
) -> Result<Self, NokhwaError> {
let cam_fmt = CameraFormat::new(Resolution::new(width, height), FrameFormat::MJPEG, fps);
GStreamerCaptureDevice::new(index, Some(cam_fmt))
}
}
impl CaptureBackendTrait for GStreamerCaptureDevice {
impl<'a> CaptureBackendTrait for GStreamerCaptureDevice<'a> {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::GStreamer
}
@@ -163,7 +171,8 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
self.stop_stream()?;
reopen = true;
}
let (pipeline, app_sink, receiver) = generate_pipeline(new_fmt, self.camera_info.index())?;
let (pipeline, app_sink, receiver) =
generate_pipeline(new_fmt, self.camera_info.index_num()? as usize)?;
self.pipeline = pipeline;
self.app_sink = app_sink;
self.receiver = receiver;
@@ -562,9 +571,9 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
}
}
impl Drop for GStreamerCaptureDevice {
impl<'a> Drop for GStreamerCaptureDevice<'a> {
fn drop(&mut self) {
self.pipeline.set_state(State::Null).unwrap();
let _ = self.pipeline.set_state(State::Null);
}
}
+9 -4
View File
@@ -16,21 +16,21 @@
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
// I'm too lazy to set up a skeleton facade for V4L so here it will stay
mod v4l2;
mod v4l2_backend;
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-v4l")))]
pub use v4l2::V4LCaptureDevice;
pub use v4l2_backend::V4LCaptureDevice;
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
mod msmf;
mod msmf_backend;
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-msmf")))]
pub use msmf::MediaFoundationCaptureDevice;
pub use msmf_backend::MediaFoundationCaptureDevice;
#[cfg(any(
all(
feature = "input-avfoundation",
@@ -67,6 +67,11 @@ mod gst_backend;
#[cfg(feature = "input-gst")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-gst")))]
pub use gst_backend::GStreamerCaptureDevice;
#[cfg(feature = "input-jscam")]
mod browser_backend;
#[cfg(feature = "input-jscam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))]
pub use browser_backend::BrowserCaptureDevice;
#[cfg(feature = "input-opencv")]
mod opencv_backend;
#[cfg(feature = "input-opencv")]
@@ -16,8 +16,8 @@
use crate::{
all_known_camera_controls, mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat,
CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag,
KnownCameraControls, NokhwaError, Resolution,
CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControlFlag, KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use nokhwa_bindings_windows::{wmf::MediaFoundationDevice, MFControl, MediaFoundationControls};
@@ -34,10 +34,11 @@ use std::{any::Any, borrow::Cow, collections::HashMap};
/// - The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`].
/// - The names may contain invalid characters since they were converted from UTF16.
/// - When you call new or drop the struct, `initialize`/`de_initialize` will automatically be called.
// TODO: Allow CameraIndex to contain a device string.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-msmf")))]
pub struct MediaFoundationCaptureDevice<'a> {
inner: MediaFoundationDevice<'a>,
info: CameraInfo,
info: CameraInfo<'a>,
}
impl<'a> MediaFoundationCaptureDevice<'a> {
@@ -45,9 +46,13 @@ impl<'a> MediaFoundationCaptureDevice<'a> {
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// # Errors
/// This function will error if Media Foundation fails to get the device.
pub fn new(index: usize, camera_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let mut mf_device = MediaFoundationDevice::new(index)?;
/// This function will error if Media Foundation fails to get the device. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new(
index: CameraIndex<'a>,
camera_fmt: Option<CameraFormat>,
) -> Result<Self, NokhwaError> {
let index = index.index_num()?;
let mut mf_device = MediaFoundationDevice::new(index as usize)?;
if let Some(fmt) = camera_fmt {
mf_device.set_format(fmt.into())?;
}
@@ -56,7 +61,7 @@ impl<'a> MediaFoundationCaptureDevice<'a> {
mf_device.name(),
"MediaFoundation Camera Device".to_string(),
mf_device.symlink(),
mf_device.index(),
CameraIndex::Index(index),
);
Ok(MediaFoundationCaptureDevice {
@@ -67,9 +72,9 @@ impl<'a> MediaFoundationCaptureDevice<'a> {
/// Create a new Media Foundation Device with desired settings.
/// # Errors
/// This function will error if Media Foundation fails to get the device.
/// This function will error if Media Foundation fails to get the device. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new_with(
index: usize,
index: CameraIndex<'a>,
width: u32,
height: u32,
fps: u32,
+41 -32
View File
@@ -15,8 +15,8 @@
*/
use crate::{
CameraControl, CameraFormat, CameraIndexType, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait,
FrameFormat, KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use opencv::{
@@ -26,6 +26,7 @@ use opencv::{
CAP_MSMF, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2,
},
};
use std::ops::Deref;
use std::{any::Any, borrow::Cow, collections::HashMap};
/// Converts $from into $to
@@ -69,16 +70,16 @@ macro_rules! tryinto_num {
/// - The `Any` type for [`raw_camera_control()`](CaptureBackendTrait::raw_camera_control) is [`i32`], and its return `Any` is a [`f64`]. Please check [`OpenCV Documentation Constants`](https://docs.rs/opencv/0.53.1/opencv/videoio/index.html) for more.
/// - The `Any` type for `control` for [`set_raw_camera_control()`](CaptureBackendTrait::set_raw_camera_control) is [`i32`] and [`f64`]. Please check [`OpenCV Documentation Constants`](https://docs.rs/opencv/0.53.1/opencv/videoio/index.html) for more.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-opencv")))]
pub struct OpenCvCaptureDevice {
pub struct OpenCvCaptureDevice<'a> {
camera_format: CameraFormat,
camera_location: CameraIndexType,
camera_info: CameraInfo,
camera_location: CameraIndex<'a>,
camera_info: CameraInfo<'a>,
api_preference: i32,
video_capture: VideoCapture,
}
#[allow(clippy::must_use_candidate)]
impl OpenCvCaptureDevice {
impl<'a> OpenCvCaptureDevice<'a> {
/// Creates a new capture device using the `OpenCV` backend. You can either use an [`Index`](CameraIndexType::Index) or [`IPCamera`](CameraIndexType::IPCamera).
///
/// Indexes are gives to devices by the OS, and usually numbered by order of discovery.
@@ -89,13 +90,14 @@ impl OpenCvCaptureDevice {
/// ```
/// , but please refer to the manufacturer for the actual IP format.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// If `camera_format` is `None`, it will be spawned with with 640x480@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// # Errors
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
/// [`CameraIndex::Index`] will open a local camera, and [`CameraIndex::String`] **requires** an IP.
/// # Panics
/// If the API u32 -> i32 fails this will error
pub fn new(
camera_location: CameraIndexType,
camera_location: CameraIndex<'a>,
cfmt: Option<CameraFormat>,
api_pref: Option<u32>,
) -> Result<Self, NokhwaError> {
@@ -113,7 +115,7 @@ impl OpenCvCaptureDevice {
};
let mut video_capture = match camera_location.clone() {
CameraIndexType::Index(idx) => {
CameraIndex::Index(idx) => {
let vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) {
Ok(vc) => {
index = idx;
@@ -128,9 +130,14 @@ impl OpenCvCaptureDevice {
};
vid_cap
}
CameraIndexType::IPCamera(ip) => match VideoCapture::from_file(&*ip, CAP_ANY) {
CameraIndex::String(ip) => match VideoCapture::from_file(ip.as_ref(), CAP_ANY) {
Ok(vc) => vc,
Err(why) => return Err(NokhwaError::OpenDeviceError(ip, why.to_string())),
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
ip.to_string(),
why.to_string(),
))
}
},
};
@@ -140,7 +147,7 @@ impl OpenCvCaptureDevice {
format!("OpenCV Capture Device {}", camera_location),
camera_location.to_string(),
"".to_string(),
index as usize,
CameraIndex::Index(index),
);
Ok(OpenCvCaptureDevice {
@@ -160,18 +167,18 @@ impl OpenCvCaptureDevice {
/// ```
/// , but please refer to the manufacturer for the actual IP format.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// If `camera_format` is `None`, it will be spawned with with 640x480@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// # Errors
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
pub fn new_ip_camera(ip: String) -> Result<Self, NokhwaError> {
let camera_location = CameraIndexType::IPCamera(ip);
pub fn new_ip_camera(ip: impl Deref<Target = str> + 'a) -> Result<Self, NokhwaError> {
let camera_location = CameraIndex::String(Cow::from(ip.to_string()));
OpenCvCaptureDevice::new(camera_location, None, None)
}
/// Creates a new capture device using the `OpenCV` backend.
/// Indexes are gives to devices by the OS, and usually numbered by order of discovery.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// If `camera_format` is `None`, it will be spawned with with 640x480@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// # Errors
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
pub fn new_index_camera(
@@ -179,39 +186,41 @@ impl OpenCvCaptureDevice {
cfmt: Option<CameraFormat>,
api_pref: Option<u32>,
) -> Result<Self, NokhwaError> {
let camera_location = CameraIndexType::Index(tryinto_num!(u32, index));
let camera_location = CameraIndex::Index(tryinto_num!(u32, index));
OpenCvCaptureDevice::new(camera_location, cfmt, api_pref)
}
/// Creates a new capture device using the `OpenCV` backend.
/// Indexes are gives to devices by the OS, and usually numbered by order of discovery.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// If `camera_format` is `None`, it will be spawned with with 640x480@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// # Errors
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
pub fn new_autopref(index: usize, cfmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let camera_location = CameraIndexType::Index(tryinto_num!(u32, index));
OpenCvCaptureDevice::new(camera_location, cfmt, None)
pub fn new_autopref(
index: CameraIndex<'a>,
cfmt: Option<CameraFormat>,
) -> Result<Self, NokhwaError> {
OpenCvCaptureDevice::new(index, cfmt, None)
}
/// Gets weather said capture device is an `IPCamera`.
pub fn is_ip_camera(&self) -> bool {
match self.camera_location {
CameraIndexType::Index(_) => false,
CameraIndexType::IPCamera(_) => true,
CameraIndex::Index(_) => false,
CameraIndex::String(_) => true,
}
}
/// Gets weather said capture device is an OS-based indexed camera.
pub fn is_index_camera(&self) -> bool {
match self.camera_location {
CameraIndexType::Index(_) => true,
CameraIndexType::IPCamera(_) => false,
CameraIndex::Index(_) => true,
CameraIndex::String(_) => false,
}
}
/// Gets the camera location
pub fn camera_location(&self) -> CameraIndexType {
pub fn camera_location(&self) -> CameraIndex {
self.camera_location.clone()
}
@@ -338,7 +347,7 @@ impl OpenCvCaptureDevice {
}
}
impl CaptureBackendTrait for OpenCvCaptureDevice {
impl<'a> CaptureBackendTrait for OpenCvCaptureDevice<'a> {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::OpenCv
}
@@ -520,7 +529,7 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
#[allow(clippy::cast_possible_wrap)]
fn open_stream(&mut self) -> Result<(), NokhwaError> {
match self.camera_location.clone() {
CameraIndexType::Index(idx) => {
CameraIndex::Index(idx) => {
match self
.video_capture
.open_1(idx as i32, get_api_pref_int() as i32)
@@ -534,15 +543,15 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
}
}
}
CameraIndexType::IPCamera(ip) => {
CameraIndex::String(ip) => {
match self
.video_capture
.open_file(&*ip, get_api_pref_int() as i32)
.open_file(ip.as_ref(), get_api_pref_int() as i32)
{
Ok(_) => {}
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
ip,
ip.to_string(),
format!("Failed to open device: {}", why),
))
}
@@ -627,7 +636,7 @@ fn get_api_pref_int() -> u32 {
fn set_properties(
_vc: &mut VideoCapture,
_camera_format: CameraFormat,
_camera_location: &CameraIndexType,
_camera_location: &CameraIndex,
) -> Result<(), NokhwaError> {
Ok(())
}
@@ -17,7 +17,7 @@
use crate::{
error::NokhwaError,
mjpeg_to_rgb888,
utils::{CameraFormat, CameraInfo},
utils::{CameraFormat, CameraIndex, CameraInfo},
yuyv422_to_rgb888, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControlFlag, KnownCameraControls, Resolution,
};
@@ -171,7 +171,7 @@ fn clone_control(ctrl: &Control) -> Control {
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-v4l")))]
pub struct V4LCaptureDevice<'a> {
camera_format: CameraFormat,
camera_info: CameraInfo,
camera_info: CameraInfo<'a>,
device: Device,
stream_handle: Option<MmapStream<'a>>,
}
@@ -181,9 +181,10 @@ impl<'a> V4LCaptureDevice<'a> {
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// # Errors
/// This function will error if the camera is currently busy or if `V4L2` can't read device information.
pub fn new(index: usize, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let device = match Device::new(index) {
/// This function will error if the camera is currently busy or if `V4L2` can't read device information. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new(index: CameraIndex<'a>, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let index = index.as_index()?;
let device = match Device::new(index as usize) {
Ok(dev) => dev,
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
@@ -194,7 +195,12 @@ impl<'a> V4LCaptureDevice<'a> {
};
let camera_info = match device.query_caps() {
Ok(caps) => CameraInfo::new(caps.card, "".to_string(), caps.driver, index),
Ok(caps) => CameraInfo::new(
caps.card,
"".to_string(),
caps.driver,
CameraIndex::Index(index),
),
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Capabilities".to_string(),
@@ -269,7 +275,7 @@ impl<'a> V4LCaptureDevice<'a> {
/// # Errors
/// This function will error if the camera is currently busy or if `V4L2` can't read device information.
pub fn new_with(
index: usize,
index: CameraIndex<'a>,
width: u32,
height: u32,
fps: u32,
+35 -28
View File
@@ -15,12 +15,11 @@
*/
use crate::{
CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControls, NokhwaError, Resolution,
CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait,
FrameFormat, KnownCameraControls, NokhwaError, Resolution,
};
use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage};
use std::any::Any;
use std::{borrow::Cow, collections::HashMap};
use std::{any::Any, borrow::Cow, collections::HashMap};
#[cfg(feature = "output-wgpu")]
use wgpu::{
Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue,
@@ -29,18 +28,18 @@ use wgpu::{
};
/// The main `Camera` struct. This is the struct that abstracts over all the backends, providing a simplified interface for use.
pub struct Camera {
idx: usize,
backend: Box<dyn CaptureBackendTrait>,
pub struct Camera<'a> {
idx: CameraIndex<'a>,
backend: Box<dyn CaptureBackendTrait + 'a>,
backend_api: CaptureAPIBackend,
}
#[allow(clippy::nonminimal_bool)]
impl Camera {
impl<'a> Camera<'a> {
/// Create a new camera from an `index` and `format`
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn new(index: usize, format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
pub fn new(index: CameraIndex<'a>, format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
Camera::with_backend(index, format, CaptureAPIBackend::Auto)
}
@@ -48,11 +47,12 @@ impl Camera {
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn with_backend(
index: usize,
index: CameraIndex<'a>,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
) -> Result<Self, NokhwaError> {
let camera_backend = init_camera(index, format, backend)?;
let camera_backend: Box<dyn CaptureBackendTrait + 'a> =
init_camera(index.clone(), format, backend)?;
Ok(Camera {
idx: index,
@@ -65,7 +65,7 @@ impl Camera {
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn new_with(
index: usize,
index: CameraIndex<'a>,
width: u32,
height: u32,
fps: u32,
@@ -78,19 +78,20 @@ impl Camera {
/// Gets the current Camera's index.
#[must_use]
pub fn index(&self) -> usize {
self.idx
pub fn index(&self) -> &CameraIndex<'a> {
&self.idx
}
/// Sets the current Camera's index. Note that this re-initializes the camera.
/// # Errors
/// The Backend may fail to initialize.
pub fn set_index(&mut self, new_idx: usize) -> Result<(), NokhwaError> {
pub fn set_index(&mut self, new_idx: CameraIndex<'a>) -> Result<(), NokhwaError> {
{
self.backend.stop_stream()?;
}
let new_camera_format = self.backend.camera_format();
let new_camera = init_camera(new_idx, Some(new_camera_format), self.backend_api)?;
let new_camera: Box<dyn CaptureBackendTrait + 'a> =
init_camera(new_idx, Some(new_camera_format), self.backend_api)?;
self.backend = new_camera;
Ok(())
}
@@ -109,7 +110,8 @@ impl Camera {
self.backend.stop_stream()?;
}
let new_camera_format = self.backend.camera_format();
let new_camera = init_camera(self.idx, Some(new_camera_format), new_backend)?;
let new_camera: Box<dyn CaptureBackendTrait + 'a> =
init_camera((&self.idx).clone(), Some(new_camera_format), new_backend)?;
self.backend = new_camera;
Ok(())
}
@@ -389,13 +391,13 @@ impl Camera {
/// 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.
pub fn frame_texture<'a>(
pub fn frame_texture(
&mut self,
device: &WgpuDevice,
queue: &WgpuQueue,
label: Option<&'a str>,
) -> Result<WgpuTexture, NokhwaError> {
use std::{convert::TryFrom, num::NonZeroU32};
use std::num::NonZeroU32;
let frame = self.frame()?;
let rgba_frame: RgbaImage = frame.convert();
@@ -452,9 +454,9 @@ impl Camera {
}
}
impl Drop for Camera {
impl<'a> Drop for Camera<'a> {
fn drop(&mut self) {
self.stop_stream().unwrap();
let _ = self.stop_stream();
}
}
@@ -475,6 +477,8 @@ fn figure_out_auto() -> Option<CaptureAPIBackend> {
cap = CaptureAPIBackend::GStreamer;
} else if cfg!(feature = "input-opencv") {
cap = CaptureAPIBackend::OpenCv;
} else if cfg!(feature = "input-jscam") {
cap = CaptureAPIBackend::Browser;
}
if cap == CaptureAPIBackend::Auto {
return None;
@@ -489,7 +493,7 @@ macro_rules! cap_impl_fn {
$(
paste::paste! {
#[cfg ($cfg) ]
fn [< init_ $backend_name>](idx: usize, setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
fn [< init_ $backend_name>](idx: CameraIndex<'_>, setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait + '_>, NokhwaError>> {
use crate::backends::capture::$backend;
match <$backend>::$init_fn(idx, setting) {
Ok(cap) => Some(Ok(Box::new(cap))),
@@ -497,7 +501,7 @@ macro_rules! cap_impl_fn {
}
}
#[cfg(not( $cfg ))]
fn [< init_ $backend_name>](_idx: usize, _setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
fn [< init_ $backend_name>](_idx: CameraIndex<'_>, _setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait + '_>, NokhwaError>> {
None
}
}
@@ -591,16 +595,17 @@ cap_impl_fn! {
(GStreamerCaptureDevice, new, feature = "input-gst", gst),
(OpenCvCaptureDevice, new_autopref, feature = "input-opencv", opencv),
// (UVCCaptureDevice, create, feature = "input-uvc", uvc),
(BrowserCaptureDevice, new, feature = "input-jscam", browser),
(V4LCaptureDevice, new, all(feature = "input-v4l", target_os = "linux"), v4l),
(MediaFoundationCaptureDevice, new, all(feature = "input-msmf", target_os = "windows"), msmf),
(AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation)
}
fn init_camera(
index: usize,
fn init_camera<'a>(
index: CameraIndex<'a>,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
) -> Result<Box<dyn CaptureBackendTrait>, NokhwaError> {
) -> Result<Box<dyn CaptureBackendTrait + 'a>, NokhwaError> {
let camera_backend = cap_impl_matches! {
backend, index, format,
("input-v4l", Video4Linux, init_v4l),
@@ -608,10 +613,12 @@ fn init_camera(
("input-avfoundation", AVFoundation, init_avfoundation),
// ("input-uvc", UniversalVideoClass, init_uvc),
("input-gst", GStreamer, init_gst),
("input-opencv", OpenCv, init_opencv)
("input-opencv", OpenCv, init_opencv),
("input-jscam", Browser, init_browser)
};
Ok(camera_backend)
}
#[cfg(feature = "output-threaded")]
unsafe impl Send for Camera {}
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))]
unsafe impl<'a> Send for Camera<'a> {}
+15 -6
View File
@@ -20,9 +20,10 @@
//!
//! This assumes that you are running a modern browser on the desktop.
use crate::{CameraInfo, NokhwaError, Resolution};
use crate::{CameraIndex, CameraInfo, NokhwaError, Resolution};
use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbImage, Rgba};
use js_sys::{Array, JsString, Map, Object, Promise};
use std::borrow::Borrow;
use std::{
borrow::Cow,
convert::TryFrom,
@@ -243,7 +244,7 @@ pub async fn js_request_permission() -> Result<(), JsValue> {
/// Queries Cameras using [`MediaDevices::enumerate_devices()`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.MediaDevices.html#method.enumerate_devices) [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices)
/// # Errors
/// This will error if there is no valid web context or the web API is not supported
pub async fn query_js_cameras() -> Result<Vec<CameraInfo>, NokhwaError> {
pub async fn query_js_cameras<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
let window: Window = window()?;
let navigator = window.navigator();
let media_devices = media_devices(&navigator)?;
@@ -294,7 +295,11 @@ pub async fn query_js_cameras() -> Result<Vec<CameraInfo>, NokhwaError> {
media_device_info.group_id(),
media_device_info.device_id()
),
idx_device as usize,
CameraIndex::String(Cow::from(format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
))),
));
tracks
.iter()
@@ -314,7 +319,11 @@ pub async fn query_js_cameras() -> Result<Vec<CameraInfo>, NokhwaError> {
media_device_info.group_id(),
media_device_info.device_id()
),
idx_device as usize,
CameraIndex::String(Cow::from(format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
))),
));
}
}
@@ -2544,7 +2553,7 @@ impl JSCamera {
let resolution = self.resolution();
let frame = self.frame_raw()?;
if convert_rgba {
buffer.copy_from_slice(&frame);
buffer.copy_from_slice(frame.borrow());
return Ok(frame.len());
}
let image = match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) {
@@ -2611,7 +2620,7 @@ impl JSCamera {
origin: wgpu::Origin3d::ZERO,
aspect: TextureAspect::All,
},
&frame,
frame.borrow(),
ImageDataLayout {
offset: 0,
bytes_per_row: width_nonzero,
+4 -1
View File
@@ -15,7 +15,10 @@
*/
#![cfg_attr(feature = "test-fail-warning", deny(warnings))]
#![cfg_attr(feature = "docs-features", feature(doc_cfg))]
#![deny(clippy::pedantic)]
#![deny(clippy::missing_errors_doc)]
#![deny(clippy::missing_safety_doc)]
#![warn(clippy::all)]
//! # nokhwa
//! A Simple-to-use, cross-platform Rust Webcam Capture Library
//!
+6 -6
View File
@@ -26,12 +26,12 @@ use wgpu::{
/// A struct that supports IP Cameras via the `OpenCV` backend.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))]
pub struct NetworkCamera {
pub struct NetworkCamera<'a> {
ip: String,
opencv_backend: RefCell<OpenCvCaptureDevice>,
opencv_backend: RefCell<OpenCvCaptureDevice<'a>>,
}
impl NetworkCamera {
impl<'a> NetworkCamera<'a> {
/// Creates a new [`NetworkCamera`] from an IP.
/// # Errors
/// If the IP is invalid or `OpenCV` fails to open the IP, this will error
@@ -108,7 +108,7 @@ impl NetworkCamera {
queue: &WgpuQueue,
label: Option<&'a str>,
) -> Result<WgpuTexture, NokhwaError> {
use std::{convert::TryFrom, num::NonZeroU32};
use std::num::NonZeroU32;
let frame = self.frame()?;
let rgba_frame: RgbaImage = frame.convert();
@@ -165,8 +165,8 @@ impl NetworkCamera {
}
}
impl Drop for NetworkCamera {
impl<'a> Drop for NetworkCamera<'a> {
fn drop(&mut self) {
self.stop_stream().unwrap();
let _ = self.stop_stream();
}
}
+33 -16
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
use crate::{CameraInfo, CaptureAPIBackend, NokhwaError};
use crate::{CameraIndex, CameraInfo, CaptureAPIBackend, NokhwaError};
/// Query the system for a list of available devices.
/// Usually the order goes Native -> UVC -> Gstreamer.
@@ -26,7 +26,7 @@ use crate::{CameraInfo, CaptureAPIBackend, NokhwaError};
/// # Errors
/// If you use an unsupported API (check the README or crate root for more info), incompatible backend for current platform, incompatible platform, or insufficient permissions, etc
/// this will error.
pub fn query() -> Result<Vec<CameraInfo>, NokhwaError> {
pub fn query<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
query_devices(CaptureAPIBackend::Auto)
}
@@ -43,7 +43,7 @@ pub fn query() -> Result<Vec<CameraInfo>, NokhwaError> {
/// If you use an unsupported API (check the README or crate root for more info), incompatible backend for current platform, incompatible platform, or insufficient permissions, etc
/// this will error.
#[allow(clippy::module_name_repetitions)]
pub fn query_devices(api: CaptureAPIBackend) -> Result<Vec<CameraInfo>, NokhwaError> {
pub fn query_devices<'a>(api: CaptureAPIBackend) -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
match api {
CaptureAPIBackend::Auto => {
// determine platform
@@ -113,6 +113,8 @@ pub fn query_devices(api: CaptureAPIBackend) -> Result<Vec<CameraInfo>, NokhwaEr
CaptureAPIBackend::MediaFoundation => query_msmf(),
CaptureAPIBackend::GStreamer => query_gstreamer(),
CaptureAPIBackend::OpenCv => Err(NokhwaError::UnsupportedOperationError(api)),
CaptureAPIBackend::Network => Err(NokhwaError::UnsupportedOperationError(api)),
CaptureAPIBackend::Browser => query_wasm(),
}
}
@@ -120,7 +122,7 @@ pub fn query_devices(api: CaptureAPIBackend) -> Result<Vec<CameraInfo>, NokhwaEr
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
#[allow(clippy::unnecessary_wraps)]
fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_v4l<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
Ok({
let camera_info: Vec<CameraInfo> = v4l::context::enum_devices()
.iter()
@@ -130,7 +132,7 @@ fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
.unwrap_or(format!("{}", node.path().to_string_lossy())),
format!("Video4Linux Device @ {}", node.path().to_string_lossy()),
"".to_string(),
node.index(),
CameraIndex::Index(node.index() as u32),
)
})
.collect();
@@ -139,14 +141,14 @@ fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
}
#[cfg(any(not(feature = "input-v4l"), not(target_os = "linux")))]
fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_v4l<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::Video4Linux,
))
}
#[cfg(feature = "input-uvc")]
fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_uvc<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
use uvc::Device;
let context = match uvc::Context::new() {
Ok(ctx) => ctx,
@@ -205,7 +207,7 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
desc.product_id,
desc.serial_number.unwrap_or_else(|| "".to_string())
),
counter,
CameraIndex::Index(counter as u32),
));
counter += 1;
}
@@ -216,14 +218,14 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
}
#[cfg(not(feature = "input-uvc"))]
fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_uvc<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
#[cfg(feature = "input-gst")]
fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_gstreamer<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
use gstreamer::{
prelude::{DeviceExt, DeviceMonitorExt, DeviceMonitorExtManual},
Caps, DeviceMonitor,
@@ -273,7 +275,7 @@ fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
name.to_string(),
class.to_string(),
"".to_string(),
counter - 1,
CameraIndex::Index(counter - 1),
)
})
.collect();
@@ -282,7 +284,7 @@ fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
}
#[cfg(not(feature = "input-gst"))]
fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_gstreamer<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::GStreamer,
))
@@ -290,7 +292,7 @@ fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
// please refer to https://docs.microsoft.com/en-us/windows/win32/medfound/enumerating-video-capture-devices
#[cfg(all(feature = "input-msmf", target_os = "windows"))]
fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_msmf<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
let list: Vec<CameraInfo> =
match nokhwa_bindings_windows::wmf::query_media_foundation_descriptors() {
Ok(l) => l
@@ -306,7 +308,7 @@ fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
}
#[cfg(any(not(feature = "input-msmf"), not(target_os = "windows")))]
fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_msmf<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::MediaFoundation,
))
@@ -316,7 +318,7 @@ fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
))]
fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_avfoundation<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
use nokhwa_bindings_macos::avfoundation::query_avfoundation as q_avf;
Ok(q_avf()?
@@ -329,8 +331,23 @@ fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
)))]
fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_avfoundation<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::AVFoundation,
))
}
#[cfg(feature = "input-jscam")]
fn query_wasm<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
use crate::js_camera::query_js_cameras;
use wasm_rs_async_executor::single_threaded::block_on;
block_on(query_js_cameras())
}
#[cfg(not(feature = "input-jscam"))]
fn query_wasm<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::Browser,
))
}
+14 -16
View File
@@ -19,7 +19,7 @@ use crate::{
KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use parking_lot::FairMutex;
use parking_lot::Mutex;
use std::{
any::Any,
collections::HashMap,
@@ -31,7 +31,7 @@ use std::{
};
/// Creates a camera that runs in a different thread that you can use a callback to access the frames of.
/// It uses a `Arc` and a `FairMutex` to ensure that this feels like a normal camera, but callback based.
/// It uses a `Arc` and a `Mutex` to ensure that this feels like a normal camera, but callback based.
/// See [`Camera`] for more details on the camera itself.
///
/// Your function is called every time there is a new frame. In order to avoid frame loss, it should
@@ -45,9 +45,9 @@ use std::{
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))]
#[derive(Clone)]
pub struct ThreadedCamera {
camera: Arc<FairMutex<Camera>>,
frame_callback: Arc<FairMutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
last_frame_captured: Arc<FairMutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
camera: Arc<Mutex<Camera>>,
frame_callback: Arc<Mutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
last_frame_captured: Arc<Mutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
die_bool: Arc<AtomicBool>,
}
@@ -98,23 +98,21 @@ impl ThreadedCamera {
backend: CaptureAPIBackend,
func: Option<
fn(
_: Arc<FairMutex<Camera>>,
_: Arc<FairMutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
_: Arc<FairMutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
_: Arc<Mutex<Camera>>,
_: Arc<Mutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
_: Arc<Mutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
_: Arc<AtomicBool>,
),
>,
) -> Result<Self, NokhwaError> {
let camera = Arc::new(FairMutex::new(Camera::with_backend(
index, format, backend,
)?));
let camera = Arc::new(Mutex::new(Camera::with_backend(index, format, backend)?));
let format = match format {
Some(fmt) => fmt,
None => CameraFormat::default(),
};
let frame_callback = Arc::new(FairMutex::new(None));
let frame_callback = Arc::new(Mutex::new(None));
let die_bool = Arc::new(AtomicBool::new(false));
let holding_cell = Arc::new(FairMutex::new(ImageBuffer::new(
let holding_cell = Arc::new(Mutex::new(ImageBuffer::new(
format.width(),
format.height(),
)));
@@ -414,9 +412,9 @@ impl Drop for ThreadedCamera {
}
fn camera_frame_thread_loop(
camera: Arc<FairMutex<Camera>>,
callback: Arc<FairMutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
holding_cell: Arc<FairMutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
camera: Arc<Mutex<Camera>>,
callback: Arc<Mutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
holding_cell: Arc<Mutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
die_bool: Arc<AtomicBool>,
) {
loop {
+105 -78
View File
@@ -14,24 +14,9 @@
* limitations under the License.
*/
/*
* Copyright 2021 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::NokhwaError;
use std::{
borrow::{Borrow, Cow},
cmp::Ordering,
fmt::{Display, Formatter},
};
@@ -70,7 +55,7 @@ use v4l::{control::Description, Format, FourCC};
/// - MJPEG is a motion-jpeg compressed frame, it allows for high frame rates.
/// # JS-WASM
/// This is exported as `FrameFormat`
#[derive(Copy, Clone, Debug, PartialEq, Hash, PartialOrd, Ord, Eq)]
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum FrameFormat {
MJPEG,
YUYV,
@@ -171,7 +156,7 @@ impl From<FrameFormat> for AVFourCC {
/// # JS-WASM
/// This is exported as `JSResolution`
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSResolution))]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)]
pub struct Resolution {
pub width_x: u32,
pub height_y: u32,
@@ -295,7 +280,7 @@ impl From<AVVideoResolution> for Resolution {
/// This is a convenience struct that holds all information about the format of a webcam stream.
/// It consists of a [`Resolution`], [`FrameFormat`], and a frame rate(u8).
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
#[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd)]
pub struct CameraFormat {
resolution: Resolution,
format: FrameFormat,
@@ -472,26 +457,31 @@ impl From<CameraFormat> for CaptureDeviceFormatDescriptor {
/// # JS-WASM
/// This is exported as a `JSCameraInfo`.
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))]
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
pub struct CameraInfo {
human_name: String,
description: String,
misc: String,
index: usize,
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
pub struct CameraInfo<'a> {
human_name: Cow<'a, str>,
description: Cow<'a, str>,
misc: Cow<'a, str>,
index: CameraIndex<'a>,
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))]
impl CameraInfo {
impl<'a> CameraInfo<'a> {
/// Create a new [`CameraInfo`].
/// # JS-WASM
/// This is exported as a constructor for [`CameraInfo`].
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))]
pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self {
pub fn new<S: ToString>(
human_name: S,
description: S,
misc: S,
index: CameraIndex<'a>,
) -> Self {
CameraInfo {
human_name,
description,
misc,
human_name: Cow::from(human_name.to_string()),
description: Cow::from(description.to_string()),
misc: Cow::from(misc.to_string()),
index,
}
}
@@ -504,8 +494,8 @@ impl CameraInfo {
feature = "output-wasm",
wasm_bindgen(getter = HumanReadableName)
)]
pub fn human_name(&self) -> String {
self.human_name.clone()
pub fn human_name(&self) -> &'_ str {
&self.human_name.borrow()
}
/// Set the device info's human name.
@@ -515,8 +505,8 @@ impl CameraInfo {
feature = "output-wasm",
wasm_bindgen(setter = HumanReadableName)
)]
pub fn set_human_name(&mut self, human_name: String) {
self.human_name = human_name;
pub fn set_human_name<S: AsRef<str>>(&mut self, human_name: S) {
self.human_name = Cow::from(human_name.as_ref().to_string());
}
/// Get a reference to the device info's description.
@@ -524,16 +514,16 @@ impl CameraInfo {
/// This is exported as a `get_Description`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Description))]
pub fn description(&self) -> String {
self.description.clone()
pub fn description(&self) -> &'_ str {
&self.description.borrow()
}
/// 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: String) {
self.description = description;
pub fn set_description<S: AsRef<str>>(&mut self, description: S) {
self.description = Cow::from(description.as_ref().to_string());
}
/// Get a reference to the device info's misc.
@@ -541,16 +531,16 @@ impl CameraInfo {
/// 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()
pub fn misc(&self) -> &'_ str {
&self.misc.borrow()
}
/// 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: String) {
self.misc = misc;
pub fn set_misc<S: AsRef<str>>(&mut self, misc: S) {
self.misc = Cow::from(misc.as_ref().to_string());
}
/// Get a reference to the device info's index.
@@ -558,32 +548,37 @@ impl CameraInfo {
/// This is exported as a `get_Index`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index))]
pub fn index(&self) -> usize {
self.index
pub fn index(&self) -> &CameraIndex<'a> {
&self.index
}
/// 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: usize) {
pub fn set_index(&mut self, index: CameraIndex<'a>) {
self.index = index;
}
}
impl PartialOrd for CameraInfo {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
/// Gets the device info's index as an `usize`.
/// # JS-WASM
/// This is exported as `get_Index_Int`
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))]
pub fn index_num(&self) -> Result<u32, NokhwaError> {
match &self.index {
CameraIndex::Index(i) => Ok(*i),
CameraIndex::String(s) => match s.parse::<u32>() {
Ok(p) => Ok(p),
Err(why) => Err(NokhwaError::GetPropertyError {
property: "index-int".to_string(),
error: why.to_string(),
}),
},
}
}
}
impl Ord for CameraInfo {
fn cmp(&self, other: &Self) -> Ordering {
self.index.cmp(&other.index)
}
}
impl Display for CameraInfo {
impl<'a> Display for CameraInfo<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
@@ -1003,7 +998,9 @@ impl Ord for CameraControl {
/// - `MediaFoundation` - Microsoft Media Foundation, Windows only,
/// - `OpenCV` - Uses `OpenCV` to capture. Platform agnostic.
/// - `GStreamer` - Uses `GStreamer` RTP to capture. Platform agnostic.
#[derive(Clone, Copy, Debug, PartialEq)]
/// - `Network` - Uses `OpenCV` to capture from an IP.
/// - `Browser` - Uses browser APIs to capture from a webcam.
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum CaptureAPIBackend {
Auto,
AVFoundation,
@@ -1012,6 +1009,8 @@ pub enum CaptureAPIBackend {
MediaFoundation,
OpenCv,
GStreamer,
Network,
Browser,
}
impl Display for CaptureAPIBackend {
@@ -1021,40 +1020,74 @@ impl Display for CaptureAPIBackend {
}
}
/// The `OpenCV` backend supports both native cameras and IP Cameras, so this is an enum to differentiate them
/// The `IPCamera`'s string follows the pattern
/// ```.ignore
/// <protocol>://<IP>:<port>/
/// ```
/// but please consult the manufacturer's specification for more details.
/// The index is a standard webcam index.
#[derive(Clone, Debug, PartialEq)]
pub enum CameraIndexType {
/// A webcam index that supports both strings and integers. Most backends take an int, but `IPCamera`s take a URL (string).
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
pub enum CameraIndex<'a> {
Index(u32),
IPCamera(String),
String(Cow<'a, str>),
}
impl Display for CameraIndexType {
impl<'a> CameraIndex<'a> {
pub fn as_index(&self) -> Result<u32, NokhwaError> {
match self {
CameraIndex::Index(i) => Ok(*i),
CameraIndex::String(s) => match s.parse::<u32>() {
Ok(p) => Ok(p),
Err(why) => Err(NokhwaError::GetPropertyError {
property: "index-int".to_string(),
error: why.to_string(),
}),
},
}
}
}
impl<'a> Display for CameraIndex<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CameraIndexType::Index(idx) => {
CameraIndex::Index(idx) => {
write!(f, "{}", idx)
}
CameraIndexType::IPCamera(ip) => {
CameraIndex::String(ip) => {
write!(f, "{}", ip)
}
}
}
}
impl<'a> From<u32> for CameraIndex<'a> {
fn from(v: u32) -> Self {
CameraIndex::Index(v)
}
}
/// Trait for strings that can be converted to [`CameraIndex`]es.
pub trait ValidString: AsRef<str> {}
impl ValidString for String {}
impl<'a> ValidString for &'a String {}
impl<'a> ValidString for &'a mut String {}
impl<'a> ValidString for Cow<'a, str> {}
impl<'a> ValidString for &'a Cow<'a, str> {}
impl<'a> ValidString for &'a mut Cow<'a, str> {}
impl<'a> ValidString for &'a str {}
impl<'a> ValidString for &'a mut str {}
impl<'a, T> From<T> for CameraIndex<'a>
where
T: ValidString + 'a,
{
fn from(v: T) -> Self {
CameraIndex::String(Cow::from(v.as_ref().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
/// 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(feature = "decoding")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))]
pub fn mjpeg_to_rgb888(data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
use mozjpeg::Decompress;
@@ -1103,12 +1136,8 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
/// 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)
/// # 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.
#[cfg(any(not(target_family = "wasm"), feature = "decoding"))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))]
#[inline]
pub fn yuyv422_to_rgb888(data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
use std::convert::TryFrom;
let mut rgb_vec: Vec<u8> = vec![];
if data.len() % 4 == 0 {
for px_idx in (0..data.len()).step_by(4) {
@@ -1210,8 +1239,6 @@ pub fn yuyv422_to_rgb888(data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
#[must_use]
#[cfg(any(not(target_family = "wasm"), feature = "decoding"))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))]
#[inline]
pub fn yuyv444_to_rgb888(y: i32, u: i32, v: i32) -> [u8; 3] {
let c298 = (y - 16) * 298;