From 42ff286206a6f903556d5a36a8cc511a186ad58e Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Thu, 25 Nov 2021 17:08:31 +0900 Subject: [PATCH 01/89] remove erronous +nightly --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index d54a099..97c696c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -81,7 +81,7 @@ pipeline { } steps { sh 'rustup update stable' - sh 'cargo +nightly clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"' + sh 'cargo clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"' } } From 8c3a09c11803581c36fbbe85241b19b57090bc1a Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sun, 28 Nov 2021 01:47:33 +0900 Subject: [PATCH 02/89] switch to Arc --- Cargo.toml | 10 ++++++--- src/backends/capture/gst_backend.rs | 32 ++++++++++------------------- src/backends/capture/mod.rs | 10 ++++----- src/lib.rs | 4 ++++ 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 27cc9b5..26d49a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,10 @@ decoding = ["mozjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] -input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration"] +input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"] input-opencv = ["opencv", "opencv/clang-runtime"] input-ipcam = ["input-opencv"] -input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex"] +input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex", "parking_lot"] input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen"] output-wgpu = ["wgpu"] output-wasm = ["input-jscam"] @@ -39,7 +39,7 @@ thiserror = "1.0.26" paste = "1.0.5" [dependencies.flume] -version = "0.10.8" +version = "0.10.9" optional = true [target.'cfg(not(target_family = "wasm"))'.dependencies.mozjpeg] @@ -148,6 +148,10 @@ optional = true version = "^0.11" optional = true +[dependencies.lazy_static] +version = "^1.4" +optional = true + [profile.release] lto = true diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index 591d582..c226b60 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -18,7 +18,6 @@ use crate::{ mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; -use flume::Receiver; use glib::Quark; use gstreamer::{ element_error, @@ -30,11 +29,11 @@ use gstreamer::{ use gstreamer_app::{AppSink, AppSinkCallbacks}; use gstreamer_video::{VideoFormat, VideoInfo}; use image::{ImageBuffer, Rgb}; +use parking_lot::Mutex; use regex::Regex; -use std::any::Any; -use std::{borrow::Cow, collections::HashMap, str::FromStr}; +use std::{any::Any, borrow::Cow, collections::HashMap, str::FromStr, sync::Arc}; -type PipelineGenRet = (Element, AppSink, Receiver, Vec>>); +type PipelineGenRet = (Element, AppSink, Arc, Vec>>>); /// The backend struct that interfaces with `GStreamer`. /// To see what this does, please see [`CaptureBackendTrait`]. @@ -47,7 +46,7 @@ pub struct GStreamerCaptureDevice { app_sink: AppSink, camera_format: CameraFormat, camera_info: CameraInfo, - receiver: Receiver, Vec>>, + image_lock: Arc, Vec>>>, caps: Option, } @@ -128,7 +127,7 @@ impl GStreamerCaptureDevice { app_sink, camera_format, camera_info, - receiver, + image_lock: receiver, caps, }) } @@ -166,7 +165,7 @@ impl CaptureBackendTrait for GStreamerCaptureDevice { let (pipeline, app_sink, receiver) = generate_pipeline(new_fmt, self.camera_info.index())?; self.pipeline = pipeline; self.app_sink = app_sink; - self.receiver = receiver; + self.image_lock = receiver; if reopen { self.open_stream()?; } @@ -540,15 +539,7 @@ impl CaptureBackendTrait for GStreamerCaptureDevice { } } - match self.receiver.recv() { - Ok(msg) => Ok(Cow::from(msg.to_vec())), - Err(why) => { - return Err(NokhwaError::ReadFrameError(format!( - "Receiver Error: {}", - why - ))); - } - } + Ok(Cow::from(self.image_lock.lock().to_vec())) } fn stop_stream(&mut self) -> Result<(), NokhwaError> { @@ -650,7 +641,8 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result Result Date: Mon, 29 Nov 2021 13:48:36 +0900 Subject: [PATCH 03/89] 0.10 prototype: start using CamerIndex, use browser in Camera --- Cargo.toml | 19 +- nokhwa-bindings-windows/Cargo.toml | 6 +- src/backends/capture/avfoundation.rs | 30 +- src/backends/capture/browser_backend.rs | 266 ++++++++++++++++++ src/backends/capture/gst_backend.rs | 39 ++- src/backends/capture/mod.rs | 13 +- .../capture/{msmf.rs => msmf_backend.rs} | 23 +- src/backends/capture/opencv_backend.rs | 73 ++--- .../capture/{v4l2.rs => v4l2_backend.rs} | 20 +- src/camera.rs | 63 +++-- src/js_camera.rs | 21 +- src/lib.rs | 5 +- src/network_camera.rs | 12 +- src/query.rs | 49 ++-- src/threaded.rs | 30 +- src/utils.rs | 183 +++++++----- 16 files changed, 609 insertions(+), 243 deletions(-) create mode 100644 src/backends/capture/browser_backend.rs rename src/backends/capture/{msmf.rs => msmf_backend.rs} (95%) rename src/backends/capture/{v4l2.rs => v4l2_backend.rs} (97%) diff --git a/Cargo.toml b/Cargo.toml index 27cc9b5..270aed1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "nokhwa" -version = "0.9.1" +version = "0.10.0" authors = ["l1npengtul "] -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 diff --git a/nokhwa-bindings-windows/Cargo.toml b/nokhwa-bindings-windows/Cargo.toml index 5e73387..588bf86 100644 --- a/nokhwa-bindings-windows/Cargo.toml +++ b/nokhwa-bindings-windows/Cargo.toml @@ -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" diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index 1374a6f..6d75819 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -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, session: Option, data_out: Option, data_collect: Option, - 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) -> Result { + /// 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, + ) -> Result { 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) } diff --git a/src/backends/capture/browser_backend.rs b/src/backends/capture/browser_backend.rs new file mode 100644 index 0000000..08db00b --- /dev/null +++ b/src/backends/capture/browser_backend.rs @@ -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) -> Result { + 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::>(); + 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::>(); + 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::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(¤t_constraints.group_id()) + .device_id(¤t_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>, NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not Implemented".to_string(), + )) + } + + fn compatible_fourcc(&mut self) -> Result, 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, NokhwaError> { + Ok(vec![]) + } + + fn camera_control(&self, _: KnownCameraControls) -> Result { + 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>, NokhwaError> { + Ok(vec![]) + } + + fn raw_camera_control(&self, _: &dyn Any) -> Result, 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, Vec>, NokhwaError> { + self.camera.frame() + } + + fn frame_raw(&mut self) -> Result, NokhwaError> { + self.camera.frame_raw() + } + + fn stop_stream(&mut self) -> Result<(), NokhwaError> { + self.camera.stop_all() + } +} diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index 591d582..4c44e6d 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -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, Vec>> /// - `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, Vec>>, caps: Option, } -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) -> Result { + /// 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) -> Result { 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 { + pub fn new_with( + index: CameraIndex<'a>, + width: u32, + height: u32, + fps: u32, + ) -> Result { 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); } } diff --git a/src/backends/capture/mod.rs b/src/backends/capture/mod.rs index 18188bf..fb083dd 100644 --- a/src/backends/capture/mod.rs +++ b/src/backends/capture/mod.rs @@ -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")] diff --git a/src/backends/capture/msmf.rs b/src/backends/capture/msmf_backend.rs similarity index 95% rename from src/backends/capture/msmf.rs rename to src/backends/capture/msmf_backend.rs index 2e6f92f..198c4e2 100644 --- a/src/backends/capture/msmf.rs +++ b/src/backends/capture/msmf_backend.rs @@ -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) -> Result { - 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, + ) -> Result { + 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, diff --git a/src/backends/capture/opencv_backend.rs b/src/backends/capture/opencv_backend.rs index 2963432..b4cc1c0 100644 --- a/src/backends/capture/opencv_backend.rs +++ b/src/backends/capture/opencv_backend.rs @@ -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, api_pref: Option, ) -> Result { @@ -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 { - let camera_location = CameraIndexType::IPCamera(ip); + pub fn new_ip_camera(ip: impl Deref + 'a) -> Result { + 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, api_pref: Option, ) -> Result { - 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) -> Result { - let camera_location = CameraIndexType::Index(tryinto_num!(u32, index)); - OpenCvCaptureDevice::new(camera_location, cfmt, None) + pub fn new_autopref( + index: CameraIndex<'a>, + cfmt: Option, + ) -> Result { + 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(()) } diff --git a/src/backends/capture/v4l2.rs b/src/backends/capture/v4l2_backend.rs similarity index 97% rename from src/backends/capture/v4l2.rs rename to src/backends/capture/v4l2_backend.rs index 0fd31b8..8c0f4e2 100644 --- a/src/backends/capture/v4l2.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -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>, } @@ -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) -> Result { - 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) -> Result { + 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, diff --git a/src/camera.rs b/src/camera.rs index 17bad4d..5526b2b 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -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, +pub struct Camera<'a> { + idx: CameraIndex<'a>, + backend: Box, 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) -> Result { + pub fn new(index: CameraIndex<'a>, format: Option) -> Result { 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, backend: CaptureAPIBackend, ) -> Result { - let camera_backend = init_camera(index, format, backend)?; + let camera_backend: Box = + 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 = + 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 = + 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 { - 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 { 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) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](idx: CameraIndex<'_>, setting: Option) -> Option, 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) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](_idx: CameraIndex<'_>, _setting: Option) -> Option, 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, backend: CaptureAPIBackend, -) -> Result, NokhwaError> { +) -> Result, 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> {} diff --git a/src/js_camera.rs b/src/js_camera.rs index e7d8eaa..591790e 100644 --- a/src/js_camera.rs +++ b/src/js_camera.rs @@ -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, NokhwaError> { +pub async fn query_js_cameras<'a>() -> Result>, 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, 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, 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, diff --git a/src/lib.rs b/src/lib.rs index 4773890..149e40e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 //! diff --git a/src/network_camera.rs b/src/network_camera.rs index 3d1a1a8..cbf6449 100644 --- a/src/network_camera.rs +++ b/src/network_camera.rs @@ -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, + opencv_backend: RefCell>, } -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 { - 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(); } } diff --git a/src/query.rs b/src/query.rs index 3a8abd5..2df1aab 100644 --- a/src/query.rs +++ b/src/query.rs @@ -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, NokhwaError> { +pub fn query<'a>() -> Result>, NokhwaError> { query_devices(CaptureAPIBackend::Auto) } @@ -43,7 +43,7 @@ pub fn query() -> Result, 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, NokhwaError> { +pub fn query_devices<'a>(api: CaptureAPIBackend) -> Result>, NokhwaError> { match api { CaptureAPIBackend::Auto => { // determine platform @@ -113,6 +113,8 @@ pub fn query_devices(api: CaptureAPIBackend) -> Result, 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, NokhwaEr #[cfg(all(feature = "input-v4l", target_os = "linux"))] #[allow(clippy::unnecessary_wraps)] -fn query_v4l() -> Result, NokhwaError> { +fn query_v4l<'a>() -> Result>, NokhwaError> { Ok({ let camera_info: Vec = v4l::context::enum_devices() .iter() @@ -130,7 +132,7 @@ fn query_v4l() -> Result, 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, NokhwaError> { } #[cfg(any(not(feature = "input-v4l"), not(target_os = "linux")))] -fn query_v4l() -> Result, NokhwaError> { +fn query_v4l<'a>() -> Result>, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::Video4Linux, )) } #[cfg(feature = "input-uvc")] -fn query_uvc() -> Result, NokhwaError> { +fn query_uvc<'a>() -> Result>, NokhwaError> { use uvc::Device; let context = match uvc::Context::new() { Ok(ctx) => ctx, @@ -205,7 +207,7 @@ fn query_uvc() -> Result, 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, NokhwaError> { } #[cfg(not(feature = "input-uvc"))] -fn query_uvc() -> Result, NokhwaError> { +fn query_uvc<'a>() -> Result>, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::UniversalVideoClass, )) } #[cfg(feature = "input-gst")] -fn query_gstreamer() -> Result, NokhwaError> { +fn query_gstreamer<'a>() -> Result>, NokhwaError> { use gstreamer::{ prelude::{DeviceExt, DeviceMonitorExt, DeviceMonitorExtManual}, Caps, DeviceMonitor, @@ -273,7 +275,7 @@ fn query_gstreamer() -> Result, NokhwaError> { name.to_string(), class.to_string(), "".to_string(), - counter - 1, + CameraIndex::Index(counter - 1), ) }) .collect(); @@ -282,7 +284,7 @@ fn query_gstreamer() -> Result, NokhwaError> { } #[cfg(not(feature = "input-gst"))] -fn query_gstreamer() -> Result, NokhwaError> { +fn query_gstreamer<'a>() -> Result>, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::GStreamer, )) @@ -290,7 +292,7 @@ fn query_gstreamer() -> Result, 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, NokhwaError> { +fn query_msmf<'a>() -> Result>, NokhwaError> { let list: Vec = match nokhwa_bindings_windows::wmf::query_media_foundation_descriptors() { Ok(l) => l @@ -306,7 +308,7 @@ fn query_msmf() -> Result, NokhwaError> { } #[cfg(any(not(feature = "input-msmf"), not(target_os = "windows")))] -fn query_msmf() -> Result, NokhwaError> { +fn query_msmf<'a>() -> Result>, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::MediaFoundation, )) @@ -316,7 +318,7 @@ fn query_msmf() -> Result, NokhwaError> { feature = "input-avfoundation", any(target_os = "macos", target_os = "ios") ))] -fn query_avfoundation() -> Result, NokhwaError> { +fn query_avfoundation<'a>() -> Result>, NokhwaError> { use nokhwa_bindings_macos::avfoundation::query_avfoundation as q_avf; Ok(q_avf()? @@ -329,8 +331,23 @@ fn query_avfoundation() -> Result, NokhwaError> { feature = "input-avfoundation", any(target_os = "macos", target_os = "ios") )))] -fn query_avfoundation() -> Result, NokhwaError> { +fn query_avfoundation<'a>() -> Result>, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::AVFoundation, )) } + +#[cfg(feature = "input-jscam")] +fn query_wasm<'a>() -> Result>, 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>, NokhwaError> { + Err(NokhwaError::UnsupportedOperationError( + CaptureAPIBackend::Browser, + )) +} diff --git a/src/threaded.rs b/src/threaded.rs index 2046e32..823a4ee 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -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>, - frame_callback: Arc, Vec>)>>>, - last_frame_captured: Arc, Vec>>>, + camera: Arc>, + frame_callback: Arc, Vec>)>>>, + last_frame_captured: Arc, Vec>>>, die_bool: Arc, } @@ -98,23 +98,21 @@ impl ThreadedCamera { backend: CaptureAPIBackend, func: Option< fn( - _: Arc>, - _: Arc, Vec>)>>>, - _: Arc, Vec>>>, + _: Arc>, + _: Arc, Vec>)>>>, + _: Arc, Vec>>>, _: Arc, ), >, ) -> Result { - 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>, - callback: Arc, Vec>)>>>, - holding_cell: Arc, Vec>>>, + camera: Arc>, + callback: Arc, Vec>)>>>, + holding_cell: Arc, Vec>>>, die_bool: Arc, ) { loop { diff --git a/src/utils.rs b/src/utils.rs index f91d727..cd1c7bc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,24 +14,9 @@ * limitations under the License. */ -/* - * Copyright 2021 l1npengtul / 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 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 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 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( + 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>(&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>(&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>(&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 { - 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 { + match &self.index { + CameraIndex::Index(i) => Ok(*i), + CameraIndex::String(s) => match s.parse::() { + 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 -/// ://:/ -/// ``` -/// 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 { + match self { + CameraIndex::Index(i) => Ok(*i), + CameraIndex::String(s) => match s.parse::() { + 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 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 {} + +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 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 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, NokhwaError> { use mozjpeg::Decompress; @@ -1103,12 +1136,8 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, 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, NokhwaError> { - use std::convert::TryFrom; - let mut rgb_vec: Vec = 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, 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; From 69c6c9c887b284c853338007e214ff03b14ec6b9 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Tue, 30 Nov 2021 00:54:42 +0900 Subject: [PATCH 04/89] Update MediaFoundation to latest methods, fix errors in CI --- Cargo.toml | 12 +- examples/capture/src/main.rs | 15 +- nokhwa-bindings-macos/Cargo.toml | 4 +- nokhwa-bindings-windows/Cargo.toml | 9 +- nokhwa-bindings-windows/build.rs | 28 ---- nokhwa-bindings-windows/src/lib.rs | 214 +++++++++++++++---------- src/backends/capture/msmf_backend.rs | 13 +- src/backends/capture/opencv_backend.rs | 3 +- src/backends/capture/uvc_backend.rs | 2 +- src/camera_traits.rs | 2 +- src/lib.rs | 4 - src/network_camera.rs | 2 +- src/query.rs | 2 +- src/utils.rs | 29 ++-- 14 files changed, 195 insertions(+), 144 deletions(-) delete mode 100644 nokhwa-bindings-windows/build.rs diff --git a/Cargo.toml b/Cargo.toml index 270aed1..dfe767a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ repository = "https://github.com/l1npengtul/nokhwa" crate-type = ["cdylib", "rlib"] [features] -default = ["flume"] +default = ["flume", "decoding"] +decoding = ["mozjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] @@ -35,7 +36,6 @@ test-fail-warning = [] [dependencies] thiserror = "1.0.26" paste = "1.0.5" -mozjpeg = "0.8.24" [dependencies.flume] version = "0.10.8" @@ -45,6 +45,10 @@ optional = true version = "^0.23" default-features = false +[target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] +version = "0.8.24" +optional = true + [target.'cfg(target_os = "linux")'.dependencies.v4l] version = "0.12.1" optional = true @@ -75,11 +79,13 @@ features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3.2" +version = "0.3.3" +path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] version = "0.1.1" +path = "nokhwa-bindings-macos" optional = true [dependencies.gstreamer] diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index 904b573..695320e 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -22,7 +22,9 @@ use glium::{ IndexBuffer, Surface, Texture2d, VertexBuffer, }; use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder}; -use nokhwa::{nokhwa_initialize, query_devices, Camera, CaptureAPIBackend, FrameFormat}; +use nokhwa::{ + nokhwa_initialize, query_devices, Camera, CameraIndex, CaptureAPIBackend, FrameFormat, +}; use std::time::Instant; #[derive(Copy, Clone)] @@ -191,8 +193,15 @@ fn main() { .trim() .parse::() { - let mut camera = - Camera::new_with(index, width, height, fps, format, backend_value).unwrap(); + let mut camera = Camera::new_with( + CameraIndex::Index(index as u32), + width, + height, + fps, + format, + backend_value, + ) + .unwrap(); if matches_clone.is_present("query-device") { match camera.compatible_fourcc() { diff --git a/nokhwa-bindings-macos/Cargo.toml b/nokhwa-bindings-macos/Cargo.toml index 018f745..fbe60a1 100644 --- a/nokhwa-bindings-macos/Cargo.toml +++ b/nokhwa-bindings-macos/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nokhwa-bindings-macos" -version = "0.1.1" -edition = "2018" +version = "0.1.2" +edition = "2021" authors = ["l1npengtul"] license = "Apache-2.0" repository = "https://github.com/l1npengtul/nokhwa" diff --git a/nokhwa-bindings-windows/Cargo.toml b/nokhwa-bindings-windows/Cargo.toml index 588bf86..4e6d1f1 100644 --- a/nokhwa-bindings-windows/Cargo.toml +++ b/nokhwa-bindings-windows/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "nokhwa-bindings-windows" -version = "0.3.2" +version = "0.3.3" authors = ["l1npengtul"] -edition = "2018" +edition = "2021" license = "Apache-2.0" repository = "https://github.com/l1npengtul/nokhwa" description = "The Windows Media Foundation bindings crate for `nokhwa`" @@ -14,8 +14,9 @@ keywords = ["media-foundation", "windows", "capture", "webcam"] thiserror = "^1.0" [target.'cfg(all(target_os = "windows", windows))'.dependencies] -windows = "^0.28" lazy_static = "1.4.0" -[target.'cfg(all(target_os = "windows", windows))'.build-dependencies.windows] +[target.'cfg(all(target_os = "windows", windows))'.dependencies.windows] version = "^0.28" +features = ["alloc", "Win32_Media_MediaFoundation", "Win32_System_Com", "Win32_Foundation", "Win32_Media_DirectShow"] + diff --git a/nokhwa-bindings-windows/build.rs b/nokhwa-bindings-windows/build.rs deleted file mode 100644 index 35230c7..0000000 --- a/nokhwa-bindings-windows/build.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2021 l1npengtul / 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. - */ - -#[cfg(all(windows, not(feature = "docs-only")))] -fn main() { - windows::build!( - Windows::Win32::Media::MediaFoundation::*, - Windows::Win32::System::Com::{CoInitializeEx, COINIT, CoUninitialize}, - Windows::Win32::Foundation::{S_OK}, - Windows::Win32::Graphics::DirectShow::*, - ) -} - -#[cfg(any(not(windows), feature = "docs-only"))] -fn main() {} diff --git a/nokhwa-bindings-windows/src/lib.rs b/nokhwa-bindings-windows/src/lib.rs index 2eb2310..81d4c14 100644 --- a/nokhwa-bindings-windows/src/lib.rs +++ b/nokhwa-bindings-windows/src/lib.rs @@ -16,7 +16,9 @@ #![deny(clippy::pedantic)] #![warn(clippy::all)] -#![allow(clippy::must_use_candidate)] +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::too_many_lines)] //! # nokhwa-bindings-windows //! This crate is the `MediaFoundation` bindings for the `nokhwa` crate. @@ -52,7 +54,7 @@ pub enum BindingError { #[error("Failed to enumerate: {0}")] EnumerateError(String), #[error("Failed to open device {0}: {1}")] - DeviceOpenFailError(usize, String), + DeviceOpenFailError(String, String), #[error("Failed to read frame: {0}")] ReadFrameError(String), #[error("Not Implemented!")] @@ -92,6 +94,7 @@ impl Default for MFCameraFormat { } impl MFCameraFormat { + #[must_use] pub fn new(resolution: MFResolution, format: MFFrameFormat, frame_rate: u32) -> Self { MFCameraFormat { resolution, @@ -100,6 +103,7 @@ impl MFCameraFormat { } } + #[must_use] pub fn new_from(res_x: u32, res_y: u32, format: MFFrameFormat, fps: u32) -> Self { MFCameraFormat { resolution: MFResolution { @@ -111,14 +115,17 @@ impl MFCameraFormat { } } + #[must_use] pub fn resolution(&self) -> MFResolution { self.resolution } + #[must_use] pub fn width(&self) -> u32 { self.resolution.width_x } + #[must_use] pub fn height(&self) -> u32 { self.resolution.height_y } @@ -127,6 +134,7 @@ impl MFCameraFormat { self.resolution = resolution; } + #[must_use] pub fn frame_rate(&self) -> u32 { self.frame_rate } @@ -135,6 +143,7 @@ impl MFCameraFormat { self.frame_rate = frame_rate; } + #[must_use] pub fn format(&self) -> MFFrameFormat { self.format } @@ -182,22 +191,27 @@ impl<'a> MediaFoundationDeviceDescriptor<'a> { }) } + #[must_use] pub fn index(&self) -> usize { self.index } + #[must_use] pub fn name(&self) -> &Cow<[u16]> { &self.name } + #[must_use] pub fn symlink(&self) -> &Cow<[u16]> { &self.symlink } + #[must_use] pub fn name_as_string(&self) -> String { String::from_utf16_lossy(self.name.borrow()) } + #[must_use] pub fn link_as_string(&self) -> String { String::from_utf16_lossy(self.symlink.borrow()) } @@ -250,6 +264,7 @@ pub struct MFControl { impl MFControl { #[allow(clippy::too_many_arguments)] + #[must_use] pub fn new( control: MediaFoundationControls, min: i32, @@ -272,6 +287,7 @@ impl MFControl { } } + #[must_use] pub fn control(&self) -> MediaFoundationControls { self.control } @@ -280,6 +296,7 @@ impl MFControl { self.control = control; } + #[must_use] pub fn min(&self) -> i32 { self.min } @@ -288,6 +305,7 @@ impl MFControl { self.min = min; } + #[must_use] pub fn max(&self) -> i32 { self.max } @@ -296,6 +314,7 @@ impl MFControl { self.max = max; } + #[must_use] pub fn step(&self) -> i32 { self.step } @@ -304,6 +323,7 @@ impl MFControl { self.step = step; } + #[must_use] pub fn current(&self) -> i32 { self.current } @@ -311,6 +331,7 @@ impl MFControl { pub fn set_current(&mut self, current: i32) { self.current = current; } + #[must_use] pub fn default(&self) -> i32 { self.default } @@ -319,6 +340,7 @@ impl MFControl { self.default = default; } + #[must_use] pub fn manual(&self) -> bool { self.manual } @@ -327,6 +349,7 @@ impl MFControl { self.manual = manual; } + #[must_use] pub fn active(&self) -> bool { self.active } @@ -336,44 +359,16 @@ impl MFControl { } } -mod bindings { - #[cfg(all(windows, not(feature = "docs-only")))] - ::windows::include_bindings!(); -} - #[cfg(all(windows, not(feature = "docs-only")))] pub mod wmf { #![windows_subsystem = "windows"] use crate::{ - bindings::Windows::Win32::{ - Foundation::PWSTR, - Graphics::DirectShow::{ - CameraControl_Exposure, CameraControl_Focus, CameraControl_Iris, CameraControl_Pan, - CameraControl_Roll, CameraControl_Tilt, CameraControl_Zoom, IAMCameraControl, - IAMVideoProcAmp, VideoProcAmp_BacklightCompensation, VideoProcAmp_Brightness, - VideoProcAmp_ColorEnable, VideoProcAmp_Contrast, VideoProcAmp_Gain, - VideoProcAmp_Gamma, VideoProcAmp_Hue, VideoProcAmp_Saturation, - VideoProcAmp_Sharpness, VideoProcAmp_WhiteBalance, - }, - Media::MediaFoundation::{ - IMFActivate, IMFAttributes, IMFMediaSource, IMFSample, IMFSourceReader, - MFCreateAttributes, MFCreateMediaType, MFCreateSourceReaderFromMediaSource, - MFEnumDeviceSources, MFMediaType_Video, MFShutdown, MFStartup, MFSTARTUP_NOSOCKET, - MF_API_VERSION, MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, MF_MEDIASOURCE_SERVICE, - MF_MT_FRAME_RATE, MF_MT_FRAME_RATE_RANGE_MAX, MF_MT_FRAME_RATE_RANGE_MIN, - MF_MT_FRAME_SIZE, MF_MT_MAJOR_TYPE, MF_MT_SUBTYPE, MF_READWRITE_DISABLE_CONVERTERS, - }, - System::Com::{CoInitializeEx, CoUninitialize, COINIT}, - }, BindingError, MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls, MediaFoundationDeviceDescriptor, }; use std::{ borrow::Cow, cell::Cell, - ffi::c_void, mem::MaybeUninit, slice::from_raw_parts, sync::{ @@ -381,7 +376,35 @@ pub mod wmf { Arc, }, }; - use windows::{Guid, Interface}; + use windows::{ + core::{Interface, GUID}, + Win32::{ + Foundation::PWSTR, + Media::{ + DirectShow::{ + CameraControl_Exposure, CameraControl_Focus, CameraControl_Iris, + CameraControl_Pan, CameraControl_Roll, CameraControl_Tilt, CameraControl_Zoom, + IAMCameraControl, IAMVideoProcAmp, VideoProcAmp_BacklightCompensation, + VideoProcAmp_Brightness, VideoProcAmp_ColorEnable, VideoProcAmp_Contrast, + VideoProcAmp_Gain, VideoProcAmp_Gamma, VideoProcAmp_Hue, + VideoProcAmp_Saturation, VideoProcAmp_Sharpness, VideoProcAmp_WhiteBalance, + }, + MediaFoundation::{ + IMFActivate, IMFAttributes, IMFMediaSource, IMFSample, IMFSourceReader, + MFCreateAttributes, MFCreateMediaType, MFCreateSourceReaderFromMediaSource, + MFEnumDeviceSources, MFMediaType_Video, MFShutdown, MFStartup, + MFSTARTUP_NOSOCKET, MF_API_VERSION, MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, + MF_MEDIASOURCE_SERVICE, MF_MT_FRAME_RATE, MF_MT_FRAME_RATE_RANGE_MAX, + MF_MT_FRAME_RATE_RANGE_MIN, MF_MT_FRAME_SIZE, MF_MT_MAJOR_TYPE, MF_MT_SUBTYPE, + MF_READWRITE_DISABLE_CONVERTERS, + }, + }, + System::Com::{CoInitializeEx, CoUninitialize, COINIT}, + }, + }; lazy_static! { static ref INITIALIZED: Arc = Arc::new(AtomicBool::new(false)); @@ -393,13 +416,13 @@ pub mod wmf { const CO_INIT_DISABLE_OLE1DDE: COINIT = COINIT(0x4); // See: https://gix.github.io/media-types/#major-types - const MF_VIDEO_FORMAT_YUY2: Guid = Guid::from_values( + const MF_VIDEO_FORMAT_YUY2: GUID = GUID::from_values( 0x3259_5559, 0x0000, 0x0010, [0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71], ); - const MF_VIDEO_FORMAT_MJPEG: Guid = Guid::from_values( + const MF_VIDEO_FORMAT_MJPEG: GUID = GUID::from_values( 0x4750_4A4D, 0x0000, 0x0010, @@ -483,10 +506,10 @@ pub mod wmf { let mut device_list = vec![]; unsafe { from_raw_parts(unused_mf_activate.assume_init(), count as usize) } - .into_iter() + .iter() .for_each(|pointer| { if let Some(imf_activate) = pointer { - device_list.push(imf_activate.clone()) + device_list.push(imf_activate.clone()); } }); @@ -495,7 +518,7 @@ pub mod wmf { fn activate_to_descriptors( index: usize, - imf_activate: IMFActivate, + imf_activate: &IMFActivate, ) -> Result, BindingError> { let mut name: PWSTR = PWSTR(&mut 0_u16); let mut len_name = 0; @@ -528,9 +551,9 @@ pub mod wmf { )); } - Ok(unsafe { + unsafe { MediaFoundationDeviceDescriptor::new(index, name.0, symlink.0, len_name, len_symlink) - }?) + } } pub fn query_media_foundation_descriptors( @@ -538,7 +561,7 @@ pub mod wmf { let mut device_list = vec![]; for (index, activate_ptr) in query_activate_pointers()?.into_iter().enumerate() { - device_list.push(activate_to_descriptors(index, activate_ptr.clone())?); + device_list.push(activate_to_descriptors(index, &activate_ptr)?); } Ok(device_list) } @@ -557,18 +580,18 @@ pub mod wmf { .nth(index) { Some(activate) => match unsafe { activate.ActivateObject::() } { - Ok(media_source) => ( - media_source, - activate_to_descriptors(index, activate.clone())?, - ), + Ok(media_source) => (media_source, activate_to_descriptors(index, &activate)?), Err(why) => { - return Err(BindingError::DeviceOpenFailError(index, why.to_string())) + return Err(BindingError::DeviceOpenFailError( + index.to_string(), + why.to_string(), + )) } }, None => { return Err(BindingError::DeviceOpenFailError( - index, - "No Device".to_string(), + index.to_string(), + "Not Found".to_string(), )) } }; @@ -604,7 +627,12 @@ pub mod wmf { MFCreateSourceReaderFromMediaSource(&media_source, source_reader_attr) } { Ok(sr) => sr, - Err(why) => return Err(BindingError::DeviceOpenFailError(index, why.to_string())), + Err(why) => { + return Err(BindingError::DeviceOpenFailError( + index.to_string(), + why.to_string(), + )) + } }; // increment refcnt @@ -618,6 +646,32 @@ pub mod wmf { }) } + pub fn with_string(unique_id: &[u16]) -> Result { + let devicelist = query_media_foundation_descriptors()?; + let mut id_eq = None; + + for mfdev in devicelist { + if (mfdev.symlink() as &[u16]) == unique_id { + id_eq = Some(mfdev.index); + break; + } + } + + match id_eq { + Some(index) => Self::new(index), + None => { + return Err(BindingError::DeviceOpenFailError( + std::str::from_utf8( + &unique_id.iter().map(|x| *x as u8).collect::>(), + ) + .unwrap_or("") + .to_string(), + "Not Found".to_string(), + )) + } + } + } + pub fn index(&self) -> usize { self.device_specifier.index } @@ -634,17 +688,10 @@ pub mod wmf { let mut camera_format_list = vec![]; let mut index = 0; - loop { - let media_type = match unsafe { - self.source_reader - .GetNativeMediaType(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, index) - } { - Ok(mt) => mt, - Err(_) => { - break; - } - }; - + while let Ok(media_type) = unsafe { + self.source_reader + .GetNativeMediaType(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, index) + } { let fourcc = match unsafe { media_type.GetGUID(&MF_MT_SUBTYPE) } { Ok(fcc) => fcc, Err(why) => { @@ -791,7 +838,7 @@ pub mod wmf { } } - index = index + 1; + index += 1; } Ok(camera_format_list) } @@ -804,7 +851,7 @@ pub mod wmf { MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, &MF_MEDIASOURCE_SERVICE, &IMFMediaSource::IID, - &mut ptr_receiver as *mut *mut IMFMediaSource as *mut *mut c_void, + (&mut ptr_receiver as *mut *mut IMFMediaSource).cast::<*mut std::ffi::c_void>(), ) { return Err(BindingError::GUIDSetError( "MEDIA_FOUNDATION_FIRST_VIDEO_STREAM".to_string(), @@ -1291,10 +1338,7 @@ pub mod wmf { } } - let is_manual = match flag { - CAM_CTRL_MANUAL => true, - _ => false, - }; + let is_manual = matches!(flag, CAM_CTRL_MANUAL); Ok(MFControl::new( control, min, max, step, value, default, is_manual, true, @@ -1309,7 +1353,7 @@ pub mod wmf { MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, &MF_MEDIASOURCE_SERVICE, &IMFMediaSource::IID, - &mut ptr_receiver as *mut *mut IMFMediaSource as *mut *mut c_void, + (&mut ptr_receiver as *mut *mut IMFMediaSource).cast::<*mut std::ffi::c_void>(), ) { return Err(BindingError::GUIDSetError( "MEDIA_FOUNDATION_FIRST_VIDEO_STREAM".to_string(), @@ -1341,13 +1385,15 @@ pub mod wmf { }; let value = control.current; - let flags = match control.manual { - true => CAM_CTRL_MANUAL, - false => CAM_CTRL_AUTO, + let flags = if control.manual { + CAM_CTRL_MANUAL + } else { + CAM_CTRL_AUTO }; - let flag_str = match control.manual { - true => "CAM_CTRL_MANUAL", - false => "CAM_CTRL_AUTO", + let flag_str = if control.manual { + "CAM_CTRL_MANUAL" + } else { + "CAM_CTRL_AUTO" }; match control.control { @@ -1555,8 +1601,8 @@ pub mod wmf { }; // set relevant things - let resolution = ((format.resolution.width_x as u64) << 32_u64) - + (format.resolution.height_y as u64); + let resolution = (u64::from(format.resolution.width_x) << 32_u64) + + u64::from(format.resolution.height_y); let fps = { let frame_rate_u64 = 0_u64; let mut bytes: [u8; 8] = frame_rate_u64.to_le_bytes(); @@ -1718,11 +1764,14 @@ pub mod wmf { buffer_valid_length as usize, ) as &[u8]); // swallow errors - let _ = buffer.Lock( - &mut buffer_start_ptr, - std::ptr::null_mut(), - &mut buffer_valid_length, - ); + if buffer + .Lock( + &mut buffer_start_ptr, + std::ptr::null_mut(), + &mut buffer_valid_length, + ) + .is_ok() + {} } Ok(Cow::from(data_slice)) @@ -1737,15 +1786,18 @@ pub mod wmf { fn drop(&mut self) { // swallow errors unsafe { - let _ = self + if self .source_reader - .Flush(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM); + .Flush(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM) + .is_ok() + {} // decrement refcnt if CAMERA_REFCNT.load(Ordering::SeqCst) > 0 { - CAMERA_REFCNT.store(CAMERA_REFCNT.load(Ordering::SeqCst) - 1, Ordering::SeqCst) + CAMERA_REFCNT.store(CAMERA_REFCNT.load(Ordering::SeqCst) - 1, Ordering::SeqCst); } if CAMERA_REFCNT.load(Ordering::SeqCst) == 0 { + #[allow(clippy::let_underscore_drop)] let _ = de_initialize_mf(); } } diff --git a/src/backends/capture/msmf_backend.rs b/src/backends/capture/msmf_backend.rs index 198c4e2..6793b2b 100644 --- a/src/backends/capture/msmf_backend.rs +++ b/src/backends/capture/msmf_backend.rs @@ -51,8 +51,15 @@ impl<'a> MediaFoundationCaptureDevice<'a> { index: CameraIndex<'a>, camera_fmt: Option, ) -> Result { - let index = index.index_num()?; - let mut mf_device = MediaFoundationDevice::new(index as usize)?; + let mut mf_device = match &index { + CameraIndex::Index(idx) => MediaFoundationDevice::new(*idx as usize), + CameraIndex::String(lnk) => MediaFoundationDevice::with_string( + &lnk.as_bytes() + .into_iter() + .map(|x| *x as u16) + .collect::>(), + ), + }?; if let Some(fmt) = camera_fmt { mf_device.set_format(fmt.into())?; } @@ -61,7 +68,7 @@ impl<'a> MediaFoundationCaptureDevice<'a> { mf_device.name(), "MediaFoundation Camera Device".to_string(), mf_device.symlink(), - CameraIndex::Index(index), + index, ); Ok(MediaFoundationCaptureDevice { diff --git a/src/backends/capture/opencv_backend.rs b/src/backends/capture/opencv_backend.rs index b4cc1c0..7be8822 100644 --- a/src/backends/capture/opencv_backend.rs +++ b/src/backends/capture/opencv_backend.rs @@ -26,8 +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}; +use std::{any::Any, borrow::Cow, collections::HashMap, ops::Deref}; /// Converts $from into $to /// Example usage: diff --git a/src/backends/capture/uvc_backend.rs b/src/backends/capture/uvc_backend.rs index 829e914..db8624a 100644 --- a/src/backends/capture/uvc_backend.rs +++ b/src/backends/capture/uvc_backend.rs @@ -56,7 +56,7 @@ use uvc::{ #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-uvc")))] pub struct UVCCaptureDevice<'a> { camera_format: CameraFormat, - camera_info: CameraInfo, + camera_info: CameraInfo<'a>, frame_receiver: Receiver>, frame_sender: Sender>, stream_handle_init: Cell, diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 01ac17a..b5fa500 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -209,7 +209,7 @@ pub trait CaptureBackendTrait { queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { - use std::{convert::TryFrom, num::NonZeroU32}; + use std::num::NonZeroU32; let frame = self.frame()?; let rgba_frame: RgbaImage = frame.convert(); diff --git a/src/lib.rs b/src/lib.rs index 149e40e..0599c3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,10 +28,6 @@ //! //! Please read the README for more. -#[cfg(feature = "small-wasm")] -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - /// Raw access to each of Nokhwa's backends. pub mod backends; mod camera; diff --git a/src/network_camera.rs b/src/network_camera.rs index cbf6449..c853fd4 100644 --- a/src/network_camera.rs +++ b/src/network_camera.rs @@ -102,7 +102,7 @@ impl<'a> NetworkCamera<'a> { /// 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, diff --git a/src/query.rs b/src/query.rs index 2df1aab..3de1582 100644 --- a/src/query.rs +++ b/src/query.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -use crate::{CameraIndex, CameraInfo, CaptureAPIBackend, NokhwaError}; +use crate::{CameraInfo, CaptureAPIBackend, NokhwaError}; /// Query the system for a list of available devices. /// Usually the order goes Native -> UVC -> Gstreamer. diff --git a/src/utils.rs b/src/utils.rs index cd1c7bc..c1469ef 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -592,13 +592,13 @@ impl<'a> Display for CameraInfo<'a> { all(feature = "input-msmf", target_os = "windows"), all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") ))] -impl From> for CameraInfo { +impl From> for CameraInfo<'_> { fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { CameraInfo { - human_name: dev_desc.name_as_string(), - description: "Media Foundation Device".to_string(), - misc: dev_desc.link_as_string(), - index: dev_desc.index(), + human_name: Cow::from(dev_desc.name_as_string()), + description: Cow::from("Media Foundation Device"), + misc: Cow::from(dev_desc.link_as_string()), + index: CameraIndex::Index(dev_desc.index() as u32), } } } @@ -615,13 +615,13 @@ impl From> for CameraInfo { ) ))] #[allow(clippy::cast_possible_truncation)] -impl From for CameraInfo { +impl From for CameraInfo<'_> { fn from(descriptor: AVCaptureDeviceDescriptor) -> Self { CameraInfo { - human_name: descriptor.name, - description: descriptor.description, - misc: descriptor.misc, - index: descriptor.index as usize, + human_name: Cow::from(descriptor.name), + description: Cow::from(descriptor.description), + misc: Cow::from(descriptor.misc), + index: CameraIndex::Index(descriptor.index as u32), } } } @@ -1088,6 +1088,8 @@ where /// # 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 = "decoding", not(target_arch = "wasm")))] +#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { use mozjpeg::Decompress; @@ -1127,6 +1129,13 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { ) } +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) +} + // 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 From 42f45727b5da28c2e6cbfc476f66586088668d4e Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Fri, 3 Dec 2021 11:18:35 +0900 Subject: [PATCH 05/89] refactor to use CameraIndex, add JSCamera to backend types for portable WASM code --- examples/threaded-capture/src/main.rs | 4 +- src/backends/capture/avfoundation.rs | 10 +- src/backends/capture/browser_backend.rs | 34 +++---- src/backends/capture/gst_backend.rs | 22 ++--- src/backends/capture/msmf_backend.rs | 6 +- src/backends/capture/opencv_backend.rs | 47 ++++----- src/backends/capture/v4l2_backend.rs | 13 +-- src/camera.rs | 55 ++++++----- src/camera_traits.rs | 2 +- src/js_camera.rs | 26 ++--- src/lib.rs | 8 +- src/network_camera.rs | 12 +-- src/query.rs | 65 +++++++------ src/threaded.rs | 124 ++++++++++++++---------- src/utils.rs | 87 +++++++++-------- 15 files changed, 270 insertions(+), 245 deletions(-) diff --git a/examples/threaded-capture/src/main.rs b/examples/threaded-capture/src/main.rs index 7b95cb4..b5598fb 100644 --- a/examples/threaded-capture/src/main.rs +++ b/examples/threaded-capture/src/main.rs @@ -15,14 +15,14 @@ */ use image::{ImageBuffer, Rgb}; -use nokhwa::{query_devices, CaptureAPIBackend, ThreadedCamera}; +use nokhwa::{query_devices, CallbackCamera, CaptureAPIBackend}; use std::time::Duration; fn main() { let cameras = query_devices(CaptureAPIBackend::Auto).unwrap(); cameras.iter().for_each(|cam| println!("{:?}", cam)); - let mut threaded = ThreadedCamera::new(0, None).unwrap(); + let mut threaded = CallbackCamera::new(0, None).unwrap(); threaded.open_stream(callback).unwrap(); #[allow(clippy::empty_loop)] // keep it running loop { diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index 6d75819..704b628 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -34,24 +34,24 @@ use std::{any::Any, borrow::Borrow, borrow::Cow, collections::HashMap, ops::Dere /// - 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<'a> { +pub struct AVFoundationCaptureDevice { device: AVCaptureDevice, dev_input: Option, session: Option, data_out: Option, data_collect: Option, - info: CameraInfo<'a>, + info: CameraInfo, format: CameraFormat, } -impl<'a> AVFoundationCaptureDevice<'a> { +impl AVFoundationCaptureDevice { /// 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. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`. pub fn new( - index: CameraIndex<'a>, + index: &CameraIndex, camera_format: Option, ) -> Result { let camera_format = match camera_format { @@ -91,7 +91,7 @@ impl<'a> AVFoundationCaptureDevice<'a> { /// # 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: CameraIndex<'a>, + index: &CameraIndex, width: u32, height: u32, fps: u32, diff --git a/src/backends/capture/browser_backend.rs b/src/backends/capture/browser_backend.rs index 08db00b..697ca40 100644 --- a/src/backends/capture/browser_backend.rs +++ b/src/backends/capture/browser_backend.rs @@ -10,23 +10,23 @@ use std::{any::Any, borrow::Cow, collections::HashMap}; /// Captures using the Browser API. This internally wraps [`JSCamera`]. /// /// # Quirks -/// - FourCC setting is ignored +/// - `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> { +pub struct BrowserCaptureDevice { camera: JSCamera, - info: CameraInfo<'a>, + info: CameraInfo, } -impl<'a> BrowserCaptureDevice<'a> { +impl BrowserCaptureDevice { // 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) -> Result { + pub fn new(index: &CameraIndex, cam_fmt: Option) -> Result { let (group_id, device_id) = match &index { CameraIndex::Index(i) => { let query_devices = @@ -35,7 +35,7 @@ impl<'a> BrowserCaptureDevice<'a> { Some(info) => { let ids = info .to_string() - .split(" ") + .split(' ') .map(ToString::to_string) .collect::>(); match (ids.get(0), ids.get(1)) { @@ -61,7 +61,7 @@ impl<'a> BrowserCaptureDevice<'a> { CameraIndex::String(id) => { let ids = id .to_string() - .split(" ") + .split(' ') .map(ToString::to_string) .collect::>(); match (ids.get(0), ids.get(1)) { @@ -81,13 +81,12 @@ impl<'a> BrowserCaptureDevice<'a> { 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) + .aspect_ratio(f64::from(camera_format.width()) / f64::from(camera_format.height())) .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))?; @@ -100,12 +99,7 @@ impl<'a> BrowserCaptureDevice<'a> { return Ok(cam); } } - Ok(CameraInfo::new( - "".to_string(), - "videoinput".to_string(), - giddid, - index, - )) + Ok(CameraInfo::new("", "videoinput", &giddid, index.clone())) })()?; Ok(BrowserCaptureDevice { camera, info }) } @@ -115,7 +109,7 @@ impl<'a> BrowserCaptureDevice<'a> { /// # 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>, + index: &CameraIndex, width: u32, height: u32, fps: u32, @@ -132,7 +126,7 @@ impl<'a> BrowserCaptureDevice<'a> { } } -impl<'a> CaptureBackendTrait for BrowserCaptureDevice<'a> { +impl CaptureBackendTrait for BrowserCaptureDevice { fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::Browser } @@ -154,18 +148,18 @@ impl<'a> CaptureBackendTrait for BrowserCaptureDevice<'a> { let new_constraints = JSCameraConstraintsBuilder::new() .resolution(new_fmt.resolution()) - .aspect_ratio(new_fmt.width() as f64 / new_fmt.height() as f64) + .aspect_ratio(f64::from(new_fmt.width()) / f64::from(new_fmt.height())) .frame_rate(new_fmt.frame_rate()) .group_id(¤t_constraints.group_id()) .device_id(¤t_constraints.device_id()) .resize_mode(JSCameraResizeMode::Any) .build(); - let _ = self.camera.set_constraints(new_constraints); + let _constraint_err = 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 + let _returnerr = self.camera.set_constraints(current_constraints); // swallow errors - revert Err(why) } } diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index f694e15..4641fdd 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -42,16 +42,16 @@ type PipelineGenRet = (Element, AppSink, Arc, Vec> /// - `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<'a> { +pub struct GStreamerCaptureDevice { pipeline: Element, app_sink: AppSink, camera_format: CameraFormat, - camera_info: CameraInfo<'a>, + camera_info: CameraInfo, image_lock: Arc, Vec>>>, caps: Option, } -impl<'a> GStreamerCaptureDevice<'a> { +impl GStreamerCaptureDevice { /// 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. @@ -59,7 +59,7 @@ impl<'a> GStreamerCaptureDevice<'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 `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) -> Result { + pub fn new(index: &CameraIndex, cam_fmt: Option) -> Result { let camera_format = match cam_fmt { Some(fmt) => fmt, None => CameraFormat::default(), @@ -114,9 +114,9 @@ impl<'a> GStreamerCaptureDevice<'a> { let caps = device.caps(); ( CameraInfo::new( - DeviceExt::display_name(&device).to_string(), - DeviceExt::device_class(&device).to_string(), - "".to_string(), + &DeviceExt::display_name(&device), + &DeviceExt::device_class(&device), + &"", CameraIndex::Index(index), ), caps, @@ -141,7 +141,7 @@ impl<'a> GStreamerCaptureDevice<'a> { /// # Errors /// This function will error if the camera is currently busy or if `GStreamer` can't read device information. pub fn new_with( - index: CameraIndex<'a>, + index: &CameraIndex, width: u32, height: u32, fps: u32, @@ -151,7 +151,7 @@ impl<'a> GStreamerCaptureDevice<'a> { } } -impl<'a> CaptureBackendTrait for GStreamerCaptureDevice<'a> { +impl CaptureBackendTrait for GStreamerCaptureDevice { fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::GStreamer } @@ -562,7 +562,7 @@ impl<'a> CaptureBackendTrait for GStreamerCaptureDevice<'a> { } } -impl<'a> Drop for GStreamerCaptureDevice<'a> { +impl Drop for GStreamerCaptureDevice { fn drop(&mut self) { let _ = self.pipeline.set_state(State::Null); } @@ -650,7 +650,7 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result { inner: MediaFoundationDevice<'a>, - info: CameraInfo<'a>, + info: CameraInfo, } impl<'a> MediaFoundationCaptureDevice<'a> { @@ -48,7 +48,7 @@ impl<'a> MediaFoundationCaptureDevice<'a> { /// # Errors /// 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>, + index: &CameraIndex<'a>, camera_fmt: Option, ) -> Result { let mut mf_device = match &index { @@ -81,7 +81,7 @@ impl<'a> MediaFoundationCaptureDevice<'a> { /// # Errors /// 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: CameraIndex<'a>, + index: &CameraIndex<'a>, width: u32, height: u32, fps: u32, diff --git a/src/backends/capture/opencv_backend.rs b/src/backends/capture/opencv_backend.rs index 7be8822..ee2f7e6 100644 --- a/src/backends/capture/opencv_backend.rs +++ b/src/backends/capture/opencv_backend.rs @@ -69,16 +69,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<'a> { +pub struct OpenCvCaptureDevice { camera_format: CameraFormat, - camera_location: CameraIndex<'a>, - camera_info: CameraInfo<'a>, + camera_location: CameraIndex, + camera_info: CameraInfo, api_preference: i32, video_capture: VideoCapture, } #[allow(clippy::must_use_candidate)] -impl<'a> OpenCvCaptureDevice<'a> { +impl OpenCvCaptureDevice { /// 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. @@ -96,7 +96,7 @@ impl<'a> OpenCvCaptureDevice<'a> { /// # Panics /// If the API u32 -> i32 fails this will error pub fn new( - camera_location: CameraIndex<'a>, + camera_location: &CameraIndex, cfmt: Option, api_pref: Option, ) -> Result { @@ -106,8 +106,6 @@ impl<'a> OpenCvCaptureDevice<'a> { tryinto_num!(i32, get_api_pref_int()) }; - let mut index = i32::MAX as u32; - let camera_format = match cfmt { Some(cam_fmt) => cam_fmt, None => CameraFormat::default(), @@ -116,10 +114,7 @@ impl<'a> OpenCvCaptureDevice<'a> { let mut video_capture = match camera_location.clone() { CameraIndex::Index(idx) => { let vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) { - Ok(vc) => { - index = idx; - vc - } + Ok(vc) => vc, Err(why) => { return Err(NokhwaError::OpenDeviceError( idx.to_string(), @@ -140,18 +135,18 @@ impl<'a> OpenCvCaptureDevice<'a> { }, }; - set_properties(&mut video_capture, camera_format, &camera_location)?; + set_properties(&mut video_capture, camera_format, camera_location)?; let camera_info = CameraInfo::new( - format!("OpenCV Capture Device {}", camera_location), - camera_location.to_string(), - "".to_string(), - CameraIndex::Index(index), + &format!("OpenCV Capture Device {}", camera_location), + &camera_location.to_string(), + "", + camera_location.clone(), ); Ok(OpenCvCaptureDevice { camera_format, - camera_location, + camera_location: camera_location.clone(), camera_info, api_preference: api, video_capture, @@ -169,9 +164,9 @@ impl<'a> OpenCvCaptureDevice<'a> { /// 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: impl Deref + 'a) -> Result { - let camera_location = CameraIndex::String(Cow::from(ip.to_string())); - OpenCvCaptureDevice::new(camera_location, None, None) + pub fn new_ip_camera(ip: impl Deref) -> Result { + let camera_location = CameraIndex::String(ip.to_string()); + OpenCvCaptureDevice::new(&camera_location, None, None) } /// Creates a new capture device using the `OpenCV` backend. @@ -186,7 +181,7 @@ impl<'a> OpenCvCaptureDevice<'a> { api_pref: Option, ) -> Result { let camera_location = CameraIndex::Index(tryinto_num!(u32, index)); - OpenCvCaptureDevice::new(camera_location, cfmt, api_pref) + OpenCvCaptureDevice::new(&camera_location, cfmt, api_pref) } /// Creates a new capture device using the `OpenCV` backend. @@ -196,7 +191,7 @@ impl<'a> OpenCvCaptureDevice<'a> { /// # 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: CameraIndex<'a>, + index: &CameraIndex, cfmt: Option, ) -> Result { OpenCvCaptureDevice::new(index, cfmt, None) @@ -346,7 +341,7 @@ impl<'a> OpenCvCaptureDevice<'a> { } } -impl<'a> CaptureBackendTrait for OpenCvCaptureDevice<'a> { +impl CaptureBackendTrait for OpenCvCaptureDevice { fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::OpenCv } @@ -619,6 +614,12 @@ impl<'a> CaptureBackendTrait for OpenCvCaptureDevice<'a> { } } +impl Drop for OpenCvCaptureDevice { + fn drop(&mut self) { + let _close_err = self.stop_stream(); + } +} + fn get_api_pref_int() -> u32 { match std::env::consts::OS { "linux" => CAP_V4L2 as u32, diff --git a/src/backends/capture/v4l2_backend.rs b/src/backends/capture/v4l2_backend.rs index 8c0f4e2..0c6910e 100644 --- a/src/backends/capture/v4l2_backend.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -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<'a>, + camera_info: CameraInfo, device: Device, stream_handle: Option>, } @@ -182,7 +182,7 @@ 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. 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) -> Result { + pub fn new(index: &CameraIndex, cam_fmt: Option) -> Result { let index = index.as_index()?; let device = match Device::new(index as usize) { Ok(dev) => dev, @@ -195,12 +195,7 @@ impl<'a> V4LCaptureDevice<'a> { }; let camera_info = match device.query_caps() { - Ok(caps) => CameraInfo::new( - caps.card, - "".to_string(), - caps.driver, - CameraIndex::Index(index), - ), + Ok(caps) => CameraInfo::new(&caps.card, "", &caps.driver, CameraIndex::Index(index)), Err(why) => { return Err(NokhwaError::GetPropertyError { property: "Capabilities".to_string(), @@ -275,7 +270,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: CameraIndex<'a>, + index: &CameraIndex, width: u32, height: u32, fps: u32, diff --git a/src/camera.rs b/src/camera.rs index 5526b2b..c80e60b 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -28,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<'a> { - idx: CameraIndex<'a>, - backend: Box, +pub struct Camera { + idx: CameraIndex, + backend: Box, backend_api: CaptureAPIBackend, } #[allow(clippy::nonminimal_bool)] -impl<'a> Camera<'a> { +impl Camera { /// 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: CameraIndex<'a>, format: Option) -> Result { + pub fn new(index: &CameraIndex, format: Option) -> Result { Camera::with_backend(index, format, CaptureAPIBackend::Auto) } @@ -47,15 +47,14 @@ impl<'a> Camera<'a> { /// # 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: CameraIndex<'a>, + index: &CameraIndex, format: Option, backend: CaptureAPIBackend, ) -> Result { - let camera_backend: Box = - init_camera(index.clone(), format, backend)?; + let camera_backend: Box = init_camera(index, format, backend)?; Ok(Camera { - idx: index, + idx: index.clone(), backend: camera_backend, backend_api: backend, }) @@ -65,7 +64,7 @@ impl<'a> Camera<'a> { /// # 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: CameraIndex<'a>, + index: &CameraIndex, width: u32, height: u32, fps: u32, @@ -78,19 +77,19 @@ impl<'a> Camera<'a> { /// Gets the current Camera's index. #[must_use] - pub fn index(&self) -> &CameraIndex<'a> { + pub fn index(&self) -> &CameraIndex { &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: CameraIndex<'a>) -> Result<(), NokhwaError> { + pub fn set_index(&mut self, new_idx: &CameraIndex) -> Result<(), NokhwaError> { { self.backend.stop_stream()?; } let new_camera_format = self.backend.camera_format(); - let new_camera: Box = + let new_camera: Box = init_camera(new_idx, Some(new_camera_format), self.backend_api)?; self.backend = new_camera; Ok(()) @@ -110,8 +109,8 @@ impl<'a> Camera<'a> { self.backend.stop_stream()?; } let new_camera_format = self.backend.camera_format(); - let new_camera: Box = - init_camera((&self.idx).clone(), Some(new_camera_format), new_backend)?; + let new_camera: Box = + init_camera(&self.idx, Some(new_camera_format), new_backend)?; self.backend = new_camera; Ok(()) } @@ -230,7 +229,7 @@ impl<'a> Camera<'a> { .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); - for (kc, cc) in maybe_camera_controls.into_iter() { + for (kc, cc) in maybe_camera_controls { control_map.insert(kc, cc); } @@ -252,7 +251,7 @@ impl<'a> Camera<'a> { .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); - for (kc, cc) in maybe_camera_controls.into_iter() { + for (kc, cc) in maybe_camera_controls { control_map.insert(kc, cc); } @@ -391,7 +390,7 @@ impl<'a> Camera<'a> { /// 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( + pub fn frame_texture<'a>( &mut self, device: &WgpuDevice, queue: &WgpuQueue, @@ -454,9 +453,9 @@ impl<'a> Camera<'a> { } } -impl<'a> Drop for Camera<'a> { +impl Drop for Camera { fn drop(&mut self) { - let _ = self.stop_stream(); + let _drop = self.stop_stream(); } } @@ -493,7 +492,7 @@ macro_rules! cap_impl_fn { $( paste::paste! { #[cfg ($cfg) ] - fn [< init_ $backend_name>](idx: CameraIndex<'_>, setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](idx: &CameraIndex, setting: Option) -> Option, NokhwaError>> { use crate::backends::capture::$backend; match <$backend>::$init_fn(idx, setting) { Ok(cap) => Some(Ok(Box::new(cap))), @@ -501,7 +500,7 @@ macro_rules! cap_impl_fn { } } #[cfg(not( $cfg ))] - fn [< init_ $backend_name>](_idx: CameraIndex<'_>, _setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](_idx: &CameraIndex, _setting: Option) -> Option, NokhwaError>> { None } } @@ -524,7 +523,7 @@ macro_rules! cap_impl_matches { CaptureAPIBackend::$backend => { match cfg!(feature = $feature) { true => { - match $fn(i,s) { + match $fn(&i,s) { Some(cap) => match cap { Ok(c) => c, Err(why) => return Err(why), @@ -560,7 +559,7 @@ macro_rules! cap_impl_matches { CaptureAPIBackend::$backend => { match cfg!(feature = $feature) { true => { - match $fn(i,s) { + match $fn(&i,s) { Some(cap) => match cap { Ok(c) => c, Err(why) => return Err(why), @@ -601,11 +600,11 @@ cap_impl_fn! { (AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation) } -fn init_camera<'a>( - index: CameraIndex<'a>, +fn init_camera( + index: &CameraIndex, format: Option, backend: CaptureAPIBackend, -) -> Result, NokhwaError> { +) -> Result, NokhwaError> { let camera_backend = cap_impl_matches! { backend, index, format, ("input-v4l", Video4Linux, init_v4l), @@ -621,4 +620,4 @@ fn init_camera<'a>( #[cfg(feature = "output-threaded")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))] -unsafe impl<'a> Send for Camera<'a> {} +unsafe impl Send for Camera {} diff --git a/src/camera_traits.rs b/src/camera_traits.rs index b5fa500..08e30f6 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -36,7 +36,7 @@ use wgpu::{ /// - Backends, if not provided with a camera format, will be spawned with 640x480@15 FPS, MJPEG [`CameraFormat`]. /// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::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()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera. -pub trait CaptureBackendTrait { +pub trait CaptureBackendTrait: Send { /// Returns the current backend used. fn backend(&self) -> CaptureAPIBackend; diff --git a/src/js_camera.rs b/src/js_camera.rs index 591790e..261f5b3 100644 --- a/src/js_camera.rs +++ b/src/js_camera.rs @@ -244,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<'a>() -> Result>, NokhwaError> { +pub async fn query_js_cameras() -> Result, NokhwaError> { let window: Window = window()?; let navigator = window.navigator(); let media_devices = media_devices(&navigator)?; @@ -288,18 +288,18 @@ pub async fn query_js_cameras<'a>() -> Result>, NokhwaError> MediaStreamTrack::from(first).label() }; device_list.push(CameraInfo::new( - name, - format!("{:?}", media_device_info.kind()), - format!( + &name, + &format!("{:?}", media_device_info.kind()), + &format!( "{} {}", media_device_info.group_id(), media_device_info.device_id() ), - CameraIndex::String(Cow::from(format!( + CameraIndex::String(format!( "{} {}", media_device_info.group_id(), media_device_info.device_id() - ))), + )), )); tracks .iter() @@ -308,22 +308,22 @@ pub async fn query_js_cameras<'a>() -> Result>, NokhwaError> } Err(_) => { device_list.push(CameraInfo::new( - format!( + &format!( "{:?}#{}", media_device_info.kind(), idx_device ), - format!("{:?}", media_device_info.kind()), - format!( + &format!("{:?}", media_device_info.kind()), + &format!( "{} {}", media_device_info.group_id(), media_device_info.device_id() ), - CameraIndex::String(Cow::from(format!( + CameraIndex::String(format!( "{} {}", media_device_info.group_id(), media_device_info.device_id() - ))), + )), )); } } @@ -2713,3 +2713,7 @@ impl Drop for JSCamera { self.stop_all().unwrap_or(()); // swallow errors } } + +// SAFETY: JSCamera is used in WASM, it will never be sent to a different thread. This is only done to satisfy the compiler. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for JSCamera {} diff --git a/src/lib.rs b/src/lib.rs index 0599c3b..66ba442 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(clippy::pedantic)] +#![warn(clippy::all)] /* * Copyright 2021 l1npengtul / The Nokhwa Contributors * @@ -15,10 +17,6 @@ */ #![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 //! @@ -62,5 +60,5 @@ pub use network_camera::NetworkCamera; pub use query::*; #[cfg(feature = "output-threaded")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))] -pub use threaded::ThreadedCamera; +pub use threaded::CallbackCamera; pub use utils::*; diff --git a/src/network_camera.rs b/src/network_camera.rs index c853fd4..cd8492d 100644 --- a/src/network_camera.rs +++ b/src/network_camera.rs @@ -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<'a> { +pub struct NetworkCamera { ip: String, - opencv_backend: RefCell>, + opencv_backend: RefCell, } -impl<'a> NetworkCamera<'a> { +impl NetworkCamera { /// Creates a new [`NetworkCamera`] from an IP. /// # Errors /// If the IP is invalid or `OpenCV` fails to open the IP, this will error @@ -102,7 +102,7 @@ impl<'a> NetworkCamera<'a> { /// 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( + pub fn frame_texture<'a>( &mut self, device: &WgpuDevice, queue: &WgpuQueue, @@ -165,8 +165,8 @@ impl<'a> NetworkCamera<'a> { } } -impl<'a> Drop for NetworkCamera<'a> { +impl Drop for NetworkCamera { fn drop(&mut self) { - let _ = self.stop_stream(); + let _stop_stream_err = self.stop_stream(); } } diff --git a/src/query.rs b/src/query.rs index 3de1582..b7c441f 100644 --- a/src/query.rs +++ b/src/query.rs @@ -19,31 +19,32 @@ use crate::{CameraInfo, CaptureAPIBackend, NokhwaError}; /// Query the system for a list of available devices. /// Usually the order goes Native -> UVC -> Gstreamer. /// # Quirks -/// - Media Foundation: The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`]. -/// - Media Foundation: The names may contain invalid characters since they were converted from UTF16. -/// - AVFoundation: The ID of the device is stored in the `misc` attribute of the [`CameraInfo`]. -/// - AVFoundation: There is lots of miscellaneous info in the `desc` attribute. +/// - `Media Foundation`: The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`]. +/// - `Media Foundation`: The names may contain invalid characters since they were converted from UTF16. +/// - `AVFoundation`: The ID of the device is stored in the `misc` attribute of the [`CameraInfo`]. +/// - `AVFoundation`: There is lots of miscellaneous info in the `desc` attribute. +/// - `WASM`: The device ID and group ID are seperated by a space (' ') /// # 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<'a>() -> Result>, NokhwaError> { +pub fn query() -> Result, NokhwaError> { query_devices(CaptureAPIBackend::Auto) } // TODO: Update as this goes /// Query the system for a list of available devices. Please refer to the API Backends that support `Query`)
-/// Currently, these are `V4L`, `MediaFoundation`, `AVFoundation`, `UVC`, and `GST`.
/// Usually the order goes Native -> UVC -> Gstreamer. /// # Quirks -/// - Media Foundation: The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`]. -/// - Media Foundation: The names may contain invalid characters since they were converted from UTF16. -/// - AVFoundation: The ID of the device is stored in the `misc` attribute of the [`CameraInfo`]. -/// - AVFoundation: There is lots of miscellaneous info in the `desc` attribute. +/// - `Media Foundation`: The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`]. +/// - `Media Foundation`: The names may contain invalid characters since they were converted from UTF16. +/// - `AVFoundation`: The ID of the device is stored in the `misc` attribute of the [`CameraInfo`]. +/// - `AVFoundation`: There is lots of miscellaneous info in the `desc` attribute. +/// - `WASM`: The `misc` field contains the device ID and group ID are seperated by a space (' ') /// # 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. #[allow(clippy::module_name_repetitions)] -pub fn query_devices<'a>(api: CaptureAPIBackend) -> Result>, NokhwaError> { +pub fn query_devices(api: CaptureAPIBackend) -> Result, NokhwaError> { match api { CaptureAPIBackend::Auto => { // determine platform @@ -112,8 +113,9 @@ pub fn query_devices<'a>(api: CaptureAPIBackend) -> Result>, CaptureAPIBackend::UniversalVideoClass => query_uvc(), CaptureAPIBackend::MediaFoundation => query_msmf(), CaptureAPIBackend::GStreamer => query_gstreamer(), - CaptureAPIBackend::OpenCv => Err(NokhwaError::UnsupportedOperationError(api)), - CaptureAPIBackend::Network => Err(NokhwaError::UnsupportedOperationError(api)), + CaptureAPIBackend::OpenCv | CaptureAPIBackend::Network => { + Err(NokhwaError::UnsupportedOperationError(api)) + } CaptureAPIBackend::Browser => query_wasm(), } } @@ -122,16 +124,19 @@ pub fn query_devices<'a>(api: CaptureAPIBackend) -> Result>, #[cfg(all(feature = "input-v4l", target_os = "linux"))] #[allow(clippy::unnecessary_wraps)] -fn query_v4l<'a>() -> Result>, NokhwaError> { +#[allow(clippy::cast_possible_truncation)] +fn query_v4l() -> Result, NokhwaError> { + use crate::CameraIndex; Ok({ let camera_info: Vec = v4l::context::enum_devices() .iter() .map(|node| { CameraInfo::new( - node.name() + &node + .name() .unwrap_or(format!("{}", node.path().to_string_lossy())), - format!("Video4Linux Device @ {}", node.path().to_string_lossy()), - "".to_string(), + &format!("Video4Linux Device @ {}", node.path().to_string_lossy()), + &"".to_string(), CameraIndex::Index(node.index() as u32), ) }) @@ -148,8 +153,10 @@ fn query_v4l<'a>() -> Result>, NokhwaError> { } #[cfg(feature = "input-uvc")] -fn query_uvc<'a>() -> Result>, NokhwaError> { +fn query_uvc() -> Result, NokhwaError> { + use crate::CameraIndex; use uvc::Device; + let context = match uvc::Context::new() { Ok(ctx) => ctx, Err(why) => { @@ -218,14 +225,15 @@ fn query_uvc<'a>() -> Result>, NokhwaError> { } #[cfg(not(feature = "input-uvc"))] -fn query_uvc<'a>() -> Result>, NokhwaError> { +fn query_uvc() -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::UniversalVideoClass, )) } #[cfg(feature = "input-gst")] -fn query_gstreamer<'a>() -> Result>, NokhwaError> { +fn query_gstreamer() -> Result, NokhwaError> { + use crate::CameraIndex; use gstreamer::{ prelude::{DeviceExt, DeviceMonitorExt, DeviceMonitorExtManual}, Caps, DeviceMonitor, @@ -271,12 +279,7 @@ fn query_gstreamer<'a>() -> Result>, NokhwaError> { let name = DeviceExt::display_name(gst_dev); let class = DeviceExt::device_class(gst_dev); counter += 1; - CameraInfo::new( - name.to_string(), - class.to_string(), - "".to_string(), - CameraIndex::Index(counter - 1), - ) + CameraInfo::new(&name, &class, "", CameraIndex::Index(counter - 1)) }) .collect(); device_monitor.stop(); @@ -308,7 +311,7 @@ fn query_msmf<'a>() -> Result>, NokhwaError> { } #[cfg(any(not(feature = "input-msmf"), not(target_os = "windows")))] -fn query_msmf<'a>() -> Result>, NokhwaError> { +fn query_msmf() -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::MediaFoundation, )) @@ -318,7 +321,7 @@ fn query_msmf<'a>() -> Result>, NokhwaError> { feature = "input-avfoundation", any(target_os = "macos", target_os = "ios") ))] -fn query_avfoundation<'a>() -> Result>, NokhwaError> { +fn query_avfoundation() -> Result, NokhwaError> { use nokhwa_bindings_macos::avfoundation::query_avfoundation as q_avf; Ok(q_avf()? @@ -331,14 +334,14 @@ fn query_avfoundation<'a>() -> Result>, NokhwaError> { feature = "input-avfoundation", any(target_os = "macos", target_os = "ios") )))] -fn query_avfoundation<'a>() -> Result>, NokhwaError> { +fn query_avfoundation() -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::AVFoundation, )) } #[cfg(feature = "input-jscam")] -fn query_wasm<'a>() -> Result>, NokhwaError> { +fn query_wasm() -> Result, NokhwaError> { use crate::js_camera::query_js_cameras; use wasm_rs_async_executor::single_threaded::block_on; @@ -346,7 +349,7 @@ fn query_wasm<'a>() -> Result>, NokhwaError> { } #[cfg(not(feature = "input-jscam"))] -fn query_wasm<'a>() -> Result>, NokhwaError> { +fn query_wasm() -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::Browser, )) diff --git a/src/threaded.rs b/src/threaded.rs index 823a4ee..4704ce0 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -15,7 +15,7 @@ */ use crate::{ - Camera, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, FrameFormat, + Camera, CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; @@ -23,13 +23,22 @@ use parking_lot::Mutex; use std::{ any::Any, collections::HashMap, - ops::Deref, sync::{ atomic::{AtomicBool, Ordering}, Arc, }, }; +type AtomicLock = Arc>; +type OptionalAtomicLock = Arc>>; +pub type CallbackFn = fn( + _: &Arc>, + _: &Arc, Vec>)>>>, + _: &Arc, Vec>>>, + _: &Arc, +); +type HeldCallbackType = OptionalAtomicLock, Vec>)>; + /// 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 `Mutex` to ensure that this feels like a normal camera, but callback based. /// See [`Camera`] for more details on the camera itself. @@ -43,27 +52,26 @@ use std::{ /// The `Mutex` guarantees exclusive access to the underlying camera struct. They should be safe to /// impl `Send` on. #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))] -#[derive(Clone)] -pub struct ThreadedCamera { - camera: Arc>, - frame_callback: Arc, Vec>)>>>, - last_frame_captured: Arc, Vec>>>, +pub struct CallbackCamera { + camera: AtomicLock, + frame_callback: HeldCallbackType, + last_frame_captured: AtomicLock, Vec>>, die_bool: Arc, } -impl ThreadedCamera { +impl CallbackCamera { /// Create a new `ThreadedCamera` from an `index` and `format`. `format` can be `None`. /// # 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) -> Result { - ThreadedCamera::with_backend(index, format, CaptureAPIBackend::Auto) + pub fn new(index: &CameraIndex, format: Option) -> Result { + CallbackCamera::with_backend(index, format, CaptureAPIBackend::Auto) } /// Create a new camera from an `index`, `format`, and `backend`. `format` can be `None`. /// # 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, format: Option, backend: CaptureAPIBackend, ) -> Result { @@ -74,7 +82,7 @@ impl ThreadedCamera { /// # 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, width: u32, height: u32, fps: u32, @@ -82,7 +90,7 @@ impl ThreadedCamera { backend: CaptureAPIBackend, ) -> Result { let camera_format = CameraFormat::new_from(width, height, fourcc, fps); - ThreadedCamera::with_backend(index, Some(camera_format), backend) + CallbackCamera::with_backend(index, Some(camera_format), backend) } /// Create a new `ThreadedCamera` from raw values, including the raw capture function. @@ -93,60 +101,74 @@ impl ThreadedCamera { /// # 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 customized_all( - index: usize, + index: &CameraIndex, format: Option, backend: CaptureAPIBackend, - func: Option< - fn( - _: Arc>, - _: Arc, Vec>)>>>, - _: Arc, Vec>>>, - _: Arc, - ), - >, + func: Option, ) -> Result { - let camera = Arc::new(Mutex::new(Camera::with_backend(index, format, backend)?)); let format = match format { Some(fmt) => fmt, None => CameraFormat::default(), }; + let camera = Arc::new(Mutex::new(Camera::with_backend( + index, + Some(format), + backend, + )?)); let frame_callback = Arc::new(Mutex::new(None)); - let die_bool = Arc::new(AtomicBool::new(false)); - let holding_cell = Arc::new(Mutex::new(ImageBuffer::new( + let last_frame_captured = Arc::new(Mutex::new(ImageBuffer::new( format.width(), format.height(), ))); + let die_bool = Arc::new(AtomicBool::new(false)); - let die_clone = die_bool.clone(); let camera_clone = camera.clone(); - let callback_clone = frame_callback.clone(); - let holding_cell_clone = holding_cell.clone(); - let func = match func { - Some(f) => f, + let frame_callback_clone = frame_callback.clone(); + let last_frame_captured_clone = last_frame_captured.clone(); + let die_bool_clone = die_bool.clone(); + + let thread_callback = match func { + Some(cb) => cb, None => camera_frame_thread_loop, }; - std::thread::spawn(move || { - func(camera_clone, callback_clone, holding_cell_clone, die_clone) - }); - Ok(ThreadedCamera { + match std::thread::Builder::new() + .name(format!("CaptureProcessThreadofCamera {}", index)) + .spawn(move || { + thread_callback( + &camera_clone, + &frame_callback_clone, + &last_frame_captured_clone, + &die_bool_clone, + ); + }) { + Ok(handle) => handle, + Err(why) => { + return Err(NokhwaError::OpenDeviceError( + index.to_string(), + format!("ThreadError: {}", why), + )) + } + }; + + Ok(CallbackCamera { camera, frame_callback, - last_frame_captured: holding_cell, + last_frame_captured, die_bool, }) } /// Gets the current Camera's index. #[must_use] - pub fn index(&self) -> usize { - self.camera.lock().index() + pub fn index(&self) -> CameraIndex { + self.camera.lock().index().clone() } /// 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) -> Result<(), NokhwaError> { self.camera.lock().set_index(new_idx) } @@ -279,7 +301,7 @@ impl ThreadedCamera { .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); - for (kc, cc) in maybe_camera_controls.into_iter() { + for (kc, cc) in maybe_camera_controls { control_map.insert(kc, cc); } @@ -301,7 +323,7 @@ impl ThreadedCamera { .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); - for (kc, cc) in maybe_camera_controls.into_iter() { + for (kc, cc) in maybe_camera_controls { control_map.insert(kc, cc); } @@ -380,6 +402,8 @@ impl ThreadedCamera { } /// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame) + /// # Errors + /// This will error if the camera fails to capture a frame. pub fn poll_frame(&mut self) -> Result, Vec>, NokhwaError> { let frame = self.camera.lock().frame()?; *self.last_frame_captured.lock() = frame.clone(); @@ -387,11 +411,13 @@ impl ThreadedCamera { } /// Gets the last frame captured by the camera. + #[must_use] pub fn last_frame(&self) -> ImageBuffer, Vec> { self.last_frame_captured.lock().clone() } /// Checks if stream if open. If it is, it will return true. + #[must_use] pub fn is_stream_open(&self) -> bool { self.camera.lock().is_stream_open() } @@ -404,24 +430,24 @@ impl ThreadedCamera { } } -impl Drop for ThreadedCamera { +impl Drop for CallbackCamera { fn drop(&mut self) { - let _ = self.stop_stream(); + let _stop_stream_err = self.stop_stream(); self.die_bool.store(true, Ordering::SeqCst); } } fn camera_frame_thread_loop( - camera: Arc>, - callback: Arc, Vec>)>>>, - holding_cell: Arc, Vec>>>, - die_bool: Arc, + camera: &AtomicLock, + frame_callback: &HeldCallbackType, + last_frame_captured: &AtomicLock, Vec>>, + die_bool: &Arc, ) { loop { if let Ok(img) = camera.lock().frame() { - *holding_cell.lock() = img.clone(); - if let Some(cb) = callback.lock().deref() { - cb(img) + *last_frame_captured.lock() = img.clone(); + if let Some(cb) = &*frame_callback.lock() { + cb(img); } } if die_bool.load(Ordering::SeqCst) { diff --git a/src/utils.rs b/src/utils.rs index c1469ef..6d310bd 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -458,30 +458,30 @@ impl From for CaptureDeviceFormatDescriptor { /// This is exported as a `JSCameraInfo`. #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))] #[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>, +pub struct CameraInfo { + human_name: String, + description: String, + misc: String, + index: CameraIndex, } #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))] -impl<'a> CameraInfo<'a> { +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))] - pub fn new( - human_name: S, - description: S, - misc: S, - index: CameraIndex<'a>, + pub fn new( + human_name: &(impl AsRef + ?Sized), + description: &(impl AsRef + ?Sized), + misc: &(impl AsRef + ?Sized), + index: CameraIndex, ) -> Self { CameraInfo { - human_name: Cow::from(human_name.to_string()), - description: Cow::from(description.to_string()), - misc: Cow::from(misc.to_string()), + human_name: human_name.as_ref().to_string(), + description: description.as_ref().to_string(), + misc: misc.as_ref().to_string(), index, } } @@ -495,7 +495,7 @@ impl<'a> CameraInfo<'a> { wasm_bindgen(getter = HumanReadableName) )] pub fn human_name(&self) -> &'_ str { - &self.human_name.borrow() + self.human_name.borrow() } /// Set the device info's human name. @@ -506,7 +506,7 @@ impl<'a> CameraInfo<'a> { wasm_bindgen(setter = HumanReadableName) )] pub fn set_human_name>(&mut self, human_name: S) { - self.human_name = Cow::from(human_name.as_ref().to_string()); + self.human_name = human_name.as_ref().to_string(); } /// Get a reference to the device info's description. @@ -515,7 +515,7 @@ impl<'a> CameraInfo<'a> { #[must_use] #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Description))] pub fn description(&self) -> &'_ str { - &self.description.borrow() + self.description.borrow() } /// Set the device info's description. @@ -523,7 +523,7 @@ impl<'a> CameraInfo<'a> { /// This is exported as a `set_Description`. #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Description))] pub fn set_description>(&mut self, description: S) { - self.description = Cow::from(description.as_ref().to_string()); + self.description = description.as_ref().to_string(); } /// Get a reference to the device info's misc. @@ -532,7 +532,7 @@ impl<'a> CameraInfo<'a> { #[must_use] #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = MiscString))] pub fn misc(&self) -> &'_ str { - &self.misc.borrow() + self.misc.borrow() } /// Set the device info's misc. @@ -540,7 +540,7 @@ impl<'a> CameraInfo<'a> { /// This is exported as a `set_MiscString`. #[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = MiscString))] pub fn set_misc>(&mut self, misc: S) { - self.misc = Cow::from(misc.as_ref().to_string()); + self.misc = misc.as_ref().to_string(); } /// Get a reference to the device info's index. @@ -548,7 +548,7 @@ impl<'a> CameraInfo<'a> { /// This is exported as a `get_Index`. #[must_use] #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index))] - pub fn index(&self) -> &CameraIndex<'a> { + pub fn index(&self) -> &CameraIndex { &self.index } @@ -556,11 +556,13 @@ impl<'a> CameraInfo<'a> { /// # 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<'a>) { + pub fn set_index(&mut self, index: CameraIndex) { self.index = index; } - /// Gets the device info's index as an `usize`. + /// Gets the device info's index as an `u32`. + /// # Errors + /// If the index is not parsable as a `u32`, this will error. /// # JS-WASM /// This is exported as `get_Index_Int` #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] @@ -578,7 +580,7 @@ impl<'a> CameraInfo<'a> { } } -impl<'a> Display for CameraInfo<'a> { +impl Display for CameraInfo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, @@ -592,12 +594,12 @@ impl<'a> Display for CameraInfo<'a> { all(feature = "input-msmf", target_os = "windows"), all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") ))] -impl From> for CameraInfo<'_> { +impl From> for CameraInfo { fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { CameraInfo { - human_name: Cow::from(dev_desc.name_as_string()), - description: Cow::from("Media Foundation Device"), - misc: Cow::from(dev_desc.link_as_string()), + human_name: dev_desc, + description: "Media Foundation Device", + misc: dev_desc.link_as_string(), index: CameraIndex::Index(dev_desc.index() as u32), } } @@ -615,12 +617,12 @@ impl From> for CameraInfo<'_> { ) ))] #[allow(clippy::cast_possible_truncation)] -impl From for CameraInfo<'_> { +impl From for CameraInfo { fn from(descriptor: AVCaptureDeviceDescriptor) -> Self { CameraInfo { - human_name: Cow::from(descriptor.name), - description: Cow::from(descriptor.description), - misc: Cow::from(descriptor.misc), + human_name: descriptor.name, + description: descriptor.description, + misc: descriptor.misc, index: CameraIndex::Index(descriptor.index as u32), } } @@ -992,7 +994,7 @@ impl Ord for CameraControl { /// The list of known capture backends to the library.
/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform. -/// - `AVFoundation` - Uses `AVFoundation` on MacOSX +/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` /// - `V4L2` - `Video4Linux2`, a linux specific backend. /// - `UVC` - 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, @@ -1022,12 +1024,15 @@ impl Display for CaptureAPIBackend { /// 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> { +pub enum CameraIndex { Index(u32), - String(Cow<'a, str>), + String(String), } -impl<'a> CameraIndex<'a> { +impl CameraIndex { + /// Gets the device info's index as an `u32`. + /// # Errors + /// If the index is not parsable as a `u32`, this will error. pub fn as_index(&self) -> Result { match self { CameraIndex::Index(i) => Ok(*i), @@ -1042,7 +1047,7 @@ impl<'a> CameraIndex<'a> { } } -impl<'a> Display for CameraIndex<'a> { +impl Display for CameraIndex { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { CameraIndex::Index(idx) => { @@ -1055,7 +1060,7 @@ impl<'a> Display for CameraIndex<'a> { } } -impl<'a> From for CameraIndex<'a> { +impl From for CameraIndex { fn from(v: u32) -> Self { CameraIndex::Index(v) } @@ -1073,12 +1078,12 @@ 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 for CameraIndex<'a> +impl From for CameraIndex where - T: ValidString + 'a, + T: ValidString, { fn from(v: T) -> Self { - CameraIndex::String(Cow::from(v.as_ref().to_string())) + CameraIndex::String(v.as_ref().to_string()) } } From e8edd99e49014259781e129d8331593b4f7b8f05 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Fri, 3 Dec 2021 11:28:06 +0900 Subject: [PATCH 06/89] [ci skip] respect ci skip --- Jenkinsfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 97c696c..98a7412 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,6 +9,7 @@ pipeline { stage('Sanity Check') { steps { echo '$BUILD_TAG' + scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*') } } @@ -20,6 +21,7 @@ pipeline { } steps { + scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*') sh 'rustup update stable' sh 'cargo fmt --all -- --check' } @@ -35,6 +37,7 @@ pipeline { } steps { + scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) sh 'rustup update stable' sh 'cargo clippy --features "input-v4l, output-wgpu, test-fail-warning"' } @@ -48,6 +51,7 @@ pipeline { } steps { + scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) pwsh(script: 'rustup update stable', returnStatus: true) pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true) } @@ -67,6 +71,7 @@ pipeline { } steps { + scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) sh 'rustup update stable' sh 'cargo clippy --features "input-uvc, output-wgpu, test-fail-warning"' } @@ -80,6 +85,7 @@ pipeline { } steps { + scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) sh 'rustup update stable' sh 'cargo clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"' } @@ -93,6 +99,7 @@ pipeline { } steps { + scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) sh 'rustup update nightly' sh 'cargo clippy --features "input-gst, output-wgpu, test-fail-warning"' } @@ -100,6 +107,7 @@ pipeline { stage('JSCamera/WASM') { steps { + scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) sh 'rustup update stable' sh 'wasm-pack build --release -- --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' sh 'cargo clippy --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' @@ -117,6 +125,7 @@ pipeline { } steps { + scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) sh 'rustup update nightly' sh 'cargo +nightly doc --features "docs-only, docs-nolink, docs-features, test-fail-warning" --no-deps --release' } From 03fb104dcef8cd83a2ead2d9e434dde87f3e4f6a Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Wed, 15 Dec 2021 21:45:19 +0900 Subject: [PATCH 07/89] Fix camera index --- examples/capture/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index 6a26c95..1ef334c 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -23,8 +23,8 @@ use glium::{ }; use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder}; use nokhwa::{ - nokhwa_initialize, query_devices, Camera, CameraFormat, CaptureAPIBackend, FrameFormat, - Resolution, + nokhwa_initialize, query_devices, Camera, CameraFormat, CameraIndex, CaptureAPIBackend, + FrameFormat, Resolution, }; use std::time::Instant; From 4ae3ba7f7f91309e9243b1902ba2f16ec03d9402 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Wed, 15 Dec 2021 21:48:12 +0900 Subject: [PATCH 08/89] add & to main --- examples/capture/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index 1ef334c..bdfda62 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -195,7 +195,7 @@ fn main() { .parse::() { let mut camera = Camera::new_with( - CameraIndex::Index(index as u32), + &CameraIndex::Index(index as u32), width, height, fps, From 7f4e520981a8fbf35c2dbb0653bc731f5781a182 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Wed, 15 Dec 2021 23:52:08 +0900 Subject: [PATCH 09/89] Revert "0.9.2 - Merge Media Foundation back into main dev branch" --- Cargo.toml | 2 +- examples/capture/src/main.rs | 5 +- nokhwa-bindings-windows/Cargo.toml | 3 +- nokhwa-bindings-windows/src/lib.rs | 265 +++++++++------------------ src/backends/capture/msmf_backend.rs | 14 +- 5 files changed, 99 insertions(+), 190 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 53d0a76..579b382 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3.4" +version = "0.3.3" path = "nokhwa-bindings-windows" optional = true diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index bdfda62..695320e 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -23,8 +23,7 @@ use glium::{ }; use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder}; use nokhwa::{ - nokhwa_initialize, query_devices, Camera, CameraFormat, CameraIndex, CaptureAPIBackend, - FrameFormat, Resolution, + nokhwa_initialize, query_devices, Camera, CameraIndex, CaptureAPIBackend, FrameFormat, }; use std::time::Instant; @@ -195,7 +194,7 @@ fn main() { .parse::() { let mut camera = Camera::new_with( - &CameraIndex::Index(index as u32), + CameraIndex::Index(index as u32), width, height, fps, diff --git a/nokhwa-bindings-windows/Cargo.toml b/nokhwa-bindings-windows/Cargo.toml index c265e0c..4e6d1f1 100644 --- a/nokhwa-bindings-windows/Cargo.toml +++ b/nokhwa-bindings-windows/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa-bindings-windows" -version = "0.3.4" +version = "0.3.3" authors = ["l1npengtul"] edition = "2021" license = "Apache-2.0" @@ -19,3 +19,4 @@ lazy_static = "1.4.0" [target.'cfg(all(target_os = "windows", windows))'.dependencies.windows] version = "^0.28" features = ["alloc", "Win32_Media_MediaFoundation", "Win32_System_Com", "Win32_Foundation", "Win32_Media_DirectShow"] + diff --git a/nokhwa-bindings-windows/src/lib.rs b/nokhwa-bindings-windows/src/lib.rs index a35fcbd..81d4c14 100644 --- a/nokhwa-bindings-windows/src/lib.rs +++ b/nokhwa-bindings-windows/src/lib.rs @@ -61,7 +61,7 @@ pub enum BindingError { NotImplementedError, } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct MFResolution { pub width_x: u32, pub height_y: u32, @@ -73,7 +73,7 @@ pub enum MFFrameFormat { YUYV, } -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, Hash, PartialEq)] pub struct MFCameraFormat { resolution: MFResolution, format: MFFrameFormat, @@ -369,9 +369,7 @@ pub mod wmf { use std::{ borrow::Cow, cell::Cell, - collections::HashMap, mem::MaybeUninit, - ptr::null_mut, slice::from_raw_parts, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -392,11 +390,11 @@ pub mod wmf { VideoProcAmp_Saturation, VideoProcAmp_Sharpness, VideoProcAmp_WhiteBalance, }, MediaFoundation::{ - IMFActivate, IMFAttributes, IMFMediaSource, IMFMediaType, IMFSample, - IMFSourceReader, MFCreateAttributes, MFCreateMediaType, - MFCreateSourceReaderFromMediaSource, MFEnumDeviceSources, MFMediaType_Video, - MFShutdown, MFStartup, MFSTARTUP_NOSOCKET, MF_API_VERSION, - MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + IMFActivate, IMFAttributes, IMFMediaSource, IMFSample, IMFSourceReader, + MFCreateAttributes, MFCreateMediaType, MFCreateSourceReaderFromMediaSource, + MFEnumDeviceSources, MFMediaType_Video, MFShutdown, MFStartup, + MFSTARTUP_NOSOCKET, MF_API_VERSION, MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, MF_MEDIASOURCE_SERVICE, MF_MT_FRAME_RATE, MF_MT_FRAME_RATE_RANGE_MAX, @@ -576,7 +574,7 @@ pub mod wmf { } impl<'a> MediaFoundationDevice<'a> { - fn internal(index: usize) -> Result { + pub fn new(index: usize) -> Result { let (media_source, device_descriptor) = match query_activate_pointers()? .into_iter() .nth(index) @@ -648,28 +646,19 @@ pub mod wmf { }) } - pub fn new(index: usize, format: MFCameraFormat) -> Result { - let mut camera = MediaFoundationDevice::internal(index)?; - camera.set_format(format)?; - Ok(camera) - } - - pub fn with_string( - unique_id: &[u16], - format: MFCameraFormat, - ) -> Result { - let device_list = query_media_foundation_descriptors()?; + pub fn with_string(unique_id: &[u16]) -> Result { + let devicelist = query_media_foundation_descriptors()?; let mut id_eq = None; - for media_foundation_device in device_list { - if (media_foundation_device.symlink() as &[u16]) == unique_id { - id_eq = Some(media_foundation_device.index); + for mfdev in devicelist { + if (mfdev.symlink() as &[u16]) == unique_id { + id_eq = Some(mfdev.index); break; } } match id_eq { - Some(index) => Self::new(index, format), + Some(index) => Self::new(index), None => { return Err(BindingError::DeviceOpenFailError( std::str::from_utf8( @@ -695,24 +684,16 @@ pub mod wmf { self.device_specifier.link_as_string() } - fn internal_format_list( - &mut self, - ) -> Result, BindingError> { - let mut camera_format_map = HashMap::new(); + pub fn compatible_format_list(&mut self) -> Result, BindingError> { + let mut camera_format_list = vec![]; let mut index = 0; while let Ok(media_type) = unsafe { self.source_reader .GetNativeMediaType(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, index) } { - index += 1; - let fourcc = match unsafe { media_type.GetGUID(&MF_MT_SUBTYPE) } { - Ok(fcc) => match fcc { - MF_VIDEO_FORMAT_YUY2 => MFFrameFormat::YUYV, - MF_VIDEO_FORMAT_MJPEG => MFFrameFormat::MJPEG, - _ => continue, - }, + Ok(fcc) => fcc, Err(why) => { return Err(BindingError::GUIDReadError( "MF_MT_SUBTYPE".to_string(), @@ -789,60 +770,77 @@ pub mod wmf { } }; - if frame_rate_min != 0 { - camera_format_map.insert( - MFCameraFormat { + if fourcc == MF_VIDEO_FORMAT_MJPEG { + if frame_rate_min != 0 { + camera_format_list.push(MFCameraFormat { resolution: MFResolution { width_x: width, height_y: height, }, - format: fourcc, + format: MFFrameFormat::MJPEG, frame_rate: frame_rate_min, - }, - media_type.clone(), - ); - } + }); + } - if frame_rate != frame_rate_min && frame_rate != 0 { - camera_format_map.insert( - MFCameraFormat { + if frame_rate != 0 && frame_rate_min != frame_rate { + camera_format_list.push(MFCameraFormat { resolution: MFResolution { width_x: width, height_y: height, }, - format: fourcc, + format: MFFrameFormat::MJPEG, frame_rate, - }, - media_type.clone(), - ); - } + }); + } - if frame_rate_max != frame_rate - && frame_rate_max != frame_rate_min - && frame_rate_max != 0 - { - camera_format_map.insert( - MFCameraFormat { + if frame_rate_max != 0 && frame_rate != frame_rate_max { + camera_format_list.push(MFCameraFormat { resolution: MFResolution { width_x: width, height_y: height, }, - format: fourcc, + format: MFFrameFormat::MJPEG, frame_rate: frame_rate_max, - }, - media_type.clone(), - ); - } - } - Ok(camera_format_map) - } + }); + } + } else if fourcc == MF_VIDEO_FORMAT_YUY2 { + if frame_rate_min != 0 { + camera_format_list.push(MFCameraFormat { + resolution: MFResolution { + width_x: width, + height_y: height, + }, + format: MFFrameFormat::YUYV, + frame_rate: frame_rate_min, + }); + } - pub fn compatible_format_list(&mut self) -> Result, BindingError> { - Ok(self - .internal_format_list()? - .into_keys() - .into_iter() - .collect()) + if frame_rate != 0 && frame_rate_min != frame_rate { + camera_format_list.push(MFCameraFormat { + resolution: MFResolution { + width_x: width, + height_y: height, + }, + format: MFFrameFormat::YUYV, + frame_rate, + }); + } + + if frame_rate_max != 0 && frame_rate != frame_rate_max { + camera_format_list.push(MFCameraFormat { + resolution: MFResolution { + width_x: width, + height_y: height, + }, + format: MFFrameFormat::YUYV, + frame_rate: frame_rate_max, + }); + } + } + + index += 1; + } + Ok(camera_format_list) } pub fn control(&self, control: MediaFoundationControls) -> Result { @@ -1596,7 +1594,6 @@ pub mod wmf { } pub fn set_format(&mut self, format: MFCameraFormat) -> Result<(), BindingError> { - let mut index = 0; // convert to media_type let media_type = match unsafe { MFCreateMediaType() } { Ok(mt) => mt, @@ -1662,115 +1659,22 @@ pub mod wmf { )); } - while let Ok(media_type) = unsafe { - self.source_reader - .GetNativeMediaType(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, index) + let reserved = std::ptr::null_mut(); + if let Err(why) = unsafe { + self.source_reader.SetCurrentMediaType( + MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, + reserved, + media_type.clone(), + ) } { - index += 1; - - let fourcc = match unsafe { media_type.GetGUID(&MF_MT_SUBTYPE) } { - Ok(fcc) => match fcc { - MF_VIDEO_FORMAT_YUY2 => MFFrameFormat::YUYV, - MF_VIDEO_FORMAT_MJPEG => MFFrameFormat::MJPEG, - _ => continue, - }, - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_SUBTYPE".to_string(), - why.to_string(), - )) - } - }; - - let (width, height) = match unsafe { media_type.GetUINT64(&MF_MT_FRAME_SIZE) } { - Ok(res_u64) => { - let width = (res_u64 >> 32) as u32; - let height = res_u64 as u32; // the cast will truncate the upper bits - (width, height) - } - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_FRAME_SIZE".to_string(), - why.to_string(), - )) - } - }; - - // MFRatio is represented as 2 u32s in memory. This means we cann convert it to 2 - let frame_rate_max = - match unsafe { media_type.GetUINT64(&MF_MT_FRAME_RATE_RANGE_MAX) } { - Ok(fraction_u64) => { - let mut numerator = (fraction_u64 >> 32) as u32; - let denominator = fraction_u64 as u32; - if denominator != 1 { - numerator = 0; - } - numerator - } - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_FRAME_RATE_RANGE_MAX".to_string(), - why.to_string(), - )) - } - }; - - let frame_rate = match unsafe { media_type.GetUINT64(&MF_MT_FRAME_RATE) } { - Ok(fraction_u64) => { - let mut numerator = (fraction_u64 >> 32) as u32; - let denominator = fraction_u64 as u32; - if denominator != 1 { - numerator = 0; - } - numerator - } - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_FRAME_RATE".to_string(), - why.to_string(), - )) - } - }; - - let frame_rate_min = - match unsafe { media_type.GetUINT64(&MF_MT_FRAME_RATE_RANGE_MIN) } { - Ok(fraction_u64) => { - let mut numerator = (fraction_u64 >> 32) as u32; - let denominator = fraction_u64 as u32; - if denominator != 1 { - numerator = 0; - } - numerator - } - Err(why) => { - return Err(BindingError::GUIDReadError( - "MF_MT_FRAME_RATE_RANGE_MIN".to_string(), - why.to_string(), - )) - } - }; - - if format.width() == width - && format.height() == height - && [frame_rate_max, frame_rate, frame_rate_min].contains(&format.frame_rate) - && format.format == fourcc - { - return match unsafe { - self.source_reader.SetCurrentMediaType( - MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, - null_mut(), - media_type, - ) - } { - Ok(_) => { - self.device_format = format; - Ok(()) - } - Err(why) => Err(BindingError::AttributeError(why.to_string())), - }; - } + return Err(BindingError::GUIDSetError( + "MF_SOURCE_READER_FIRST_VIDEO_STREAM".to_string(), + format!("{:?}", media_type), + why.to_string(), + )); } - Err(BindingError::EnumerateError("Not Found".to_string())) + self.device_format = format; + Ok(()) } pub fn is_stream_open(&self) -> bool { @@ -1782,6 +1686,7 @@ pub mod wmf { self.source_reader .SetStreamSelection(MEDIA_FOUNDATION_FIRST_VIDEO_STREAM, true) } { + println!("a"); return Err(BindingError::ReadFrameError(why.to_string())); } diff --git a/src/backends/capture/msmf_backend.rs b/src/backends/capture/msmf_backend.rs index c5e522a..559dd76 100644 --- a/src/backends/capture/msmf_backend.rs +++ b/src/backends/capture/msmf_backend.rs @@ -47,18 +47,22 @@ 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. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`. - pub fn new(index: &CameraIndex, camera_fmt: Option) -> Result { - let format = camera_fmt.unwrap_or_default(); + pub fn new( + index: &CameraIndex<'a>, + camera_fmt: Option, + ) -> Result { let mut mf_device = match &index { - CameraIndex::Index(idx) => MediaFoundationDevice::new(*idx as usize, format.into()), + CameraIndex::Index(idx) => MediaFoundationDevice::new(*idx as usize), CameraIndex::String(lnk) => MediaFoundationDevice::with_string( &lnk.as_bytes() .into_iter() .map(|x| *x as u16) .collect::>(), - format.into(), ), }?; + if let Some(fmt) = camera_fmt { + mf_device.set_format(fmt.into())?; + } let info = CameraInfo::new( mf_device.name(), @@ -77,7 +81,7 @@ impl<'a> MediaFoundationCaptureDevice<'a> { /// # Errors /// 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: &CameraIndex, + index: &CameraIndex<'a>, width: u32, height: u32, fps: u32, From 75fe2cc048ef48a9dd466243f9eab7cb90363046 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 10 Jan 2022 10:14:07 +0900 Subject: [PATCH 10/89] Add readme badges, 0.10 status update --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7ce8107..f7fb3cd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![cargo version](https://img.shields.io/crates/v/nokhwa.svg)](https://crates.io/crates/nokhwa) ![docs.rs version](https://img.shields.io/docsrs/nokhwa) # nokhwa Nokhwa(녹화): Korean word meaning "to record". @@ -107,3 +108,6 @@ Contributions are welcome! ## Minimum Service Rust Version `nokhwa` may build on older versions of `rustc`, but there is no guarantee except for the latest stable rust. + +## 0.10 +0.10 is currently stalled due to upstream not having the necessary features (wasm-bindgen). From 749fd72da3b8451dd2002d5cd95efb505ae12e07 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Wed, 12 Jan 2022 23:50:18 +0900 Subject: [PATCH 11/89] Fix Broken README, fixing #22 Fix issue #22 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7fb3cd..30cba31 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ let mut camera = Camera::new( // open stream camera.open_stream().unwrap(); loop { - let frame = camera.get_frame().unwrap(); + let frame = camera.frame().unwrap(); println!("{}, {}", frame.width(), frame.height()); } ``` From d9140b7c6d232502edab7434aa2612f2351f884a Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Thu, 13 Jan 2022 00:11:48 +0900 Subject: [PATCH 12/89] Fix Docs.rs badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30cba31..370a8ef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![cargo version](https://img.shields.io/crates/v/nokhwa.svg)](https://crates.io/crates/nokhwa) ![docs.rs version](https://img.shields.io/docsrs/nokhwa) +[![cargo version](https://img.shields.io/crates/v/nokhwa.svg)](https://crates.io/crates/nokhwa) [![docs.rs version](https://img.shields.io/docsrs/nokhwa)](https://docs.rs/nokhwa/latest/nokhwa/) # nokhwa Nokhwa(녹화): Korean word meaning "to record". From 4c26113eecda3cf7961371eb7c0e7cbfd81f39da Mon Sep 17 00:00:00 2001 From: Stefan Date: Thu, 20 Jan 2022 14:27:07 +0100 Subject: [PATCH 13/89] Fix threaded-camera example --- examples/threaded-capture/Cargo.toml | 2 +- examples/threaded-capture/src/main.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/threaded-capture/Cargo.toml b/examples/threaded-capture/Cargo.toml index 9cb829c..d8c48cb 100644 --- a/examples/threaded-capture/Cargo.toml +++ b/examples/threaded-capture/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies.image] version = "0.23.14" -no-default-features = true +default-features = false [dependencies.nokhwa] path = "../../../nokhwa" diff --git a/examples/threaded-capture/src/main.rs b/examples/threaded-capture/src/main.rs index b5598fb..fa55c6a 100644 --- a/examples/threaded-capture/src/main.rs +++ b/examples/threaded-capture/src/main.rs @@ -16,7 +16,6 @@ use image::{ImageBuffer, Rgb}; use nokhwa::{query_devices, CallbackCamera, CaptureAPIBackend}; -use std::time::Duration; fn main() { let cameras = query_devices(CaptureAPIBackend::Auto).unwrap(); From ade641149b877e3b4d047a22d31303282ffa746d Mon Sep 17 00:00:00 2001 From: Stefan Date: Fri, 21 Jan 2022 16:43:31 +0100 Subject: [PATCH 14/89] Remove not necessary lifetime specifiers --- src/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query.rs b/src/query.rs index b7c441f..0cc2482 100644 --- a/src/query.rs +++ b/src/query.rs @@ -146,7 +146,7 @@ fn query_v4l() -> Result, NokhwaError> { } #[cfg(any(not(feature = "input-v4l"), not(target_os = "linux")))] -fn query_v4l<'a>() -> Result>, NokhwaError> { +fn query_v4l() -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::Video4Linux, )) @@ -287,7 +287,7 @@ fn query_gstreamer() -> Result, NokhwaError> { } #[cfg(not(feature = "input-gst"))] -fn query_gstreamer<'a>() -> Result>, NokhwaError> { +fn query_gstreamer() -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::GStreamer, )) From a6237f78e2efee481f11ece4bd2aed50bb472f82 Mon Sep 17 00:00:00 2001 From: Stefan Date: Fri, 21 Jan 2022 16:47:34 +0100 Subject: [PATCH 15/89] Fix threaded example camera index --- examples/threaded-capture/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/threaded-capture/src/main.rs b/examples/threaded-capture/src/main.rs index fa55c6a..a129762 100644 --- a/examples/threaded-capture/src/main.rs +++ b/examples/threaded-capture/src/main.rs @@ -15,13 +15,13 @@ */ use image::{ImageBuffer, Rgb}; -use nokhwa::{query_devices, CallbackCamera, CaptureAPIBackend}; +use nokhwa::{query_devices, CallbackCamera, CameraIndex, CaptureAPIBackend}; fn main() { let cameras = query_devices(CaptureAPIBackend::Auto).unwrap(); cameras.iter().for_each(|cam| println!("{:?}", cam)); - let mut threaded = CallbackCamera::new(0, None).unwrap(); + let mut threaded = CallbackCamera::new(&CameraIndex::Index(0), None).unwrap(); threaded.open_stream(callback).unwrap(); #[allow(clippy::empty_loop)] // keep it running loop { From 2dabcb84d27dffc67ef40e4d159f47f2fb7c62be Mon Sep 17 00:00:00 2001 From: Stefan Date: Fri, 21 Jan 2022 16:47:10 +0100 Subject: [PATCH 16/89] Add boxed callback for CallbackCamera Current code can only handle a function as callback. Make it more flexible by accepting a Box thus one can also use a closure as callback. Feature implemented backwards-compatible, no change of existing code necessary. --- src/threaded.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/threaded.rs b/src/threaded.rs index 4704ce0..3e2ab6b 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -30,14 +30,13 @@ use std::{ }; type AtomicLock = Arc>; -type OptionalAtomicLock = Arc>>; pub type CallbackFn = fn( - _: &Arc>, - _: &Arc, Vec>)>>>, - _: &Arc, Vec>>>, - _: &Arc, + _camera: &Arc>, + _frame_callback: &Arc, Vec>) + Send + Sync + 'static>>>>, + _last_frame_captured: &Arc, Vec>>>, + _die_bool: &Arc, ); -type HeldCallbackType = OptionalAtomicLock, Vec>)>; +type HeldCallbackType = Arc, Vec>) + Send + Sync + 'static>>>>; /// 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 `Mutex` to ensure that this feels like a normal camera, but callback based. @@ -388,17 +387,22 @@ impl CallbackCamera { /// The callback will be called every frame. /// # Errors /// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. - pub fn open_stream( + pub fn open_stream( &mut self, - callback: fn(ImageBuffer, Vec>), - ) -> Result<(), NokhwaError> { - *self.frame_callback.lock() = Some(callback); + mut callback: F) -> Result<(), NokhwaError> + where + F: (FnMut(ImageBuffer, Vec>)) + Send + Sync + 'static, + { + *self.frame_callback.lock() = Some(Box::new(move |image: ImageBuffer, Vec>| { callback(image) }, )); self.camera.lock().open_stream() } /// Sets the frame callback to the new specified function. This function will be called instead of the previous one(s). - pub fn set_callback(&mut self, callback: fn(ImageBuffer, Vec>)) { - *self.frame_callback.lock() = Some(callback); + pub fn set_callback(&mut self, mut callback: F) + where + F: (FnMut(ImageBuffer, Vec>)) + Send + Sync + 'static, + { + *self.frame_callback.lock() = Some(Box::new(move |image: ImageBuffer, Vec>| { callback(image) }, )); } /// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame) @@ -446,7 +450,7 @@ fn camera_frame_thread_loop( loop { if let Ok(img) = camera.lock().frame() { *last_frame_captured.lock() = img.clone(); - if let Some(cb) = &*frame_callback.lock() { + if let Some(cb) = (*frame_callback.lock()).as_mut() { cb(img); } } From c71dd669ce918b08524296d0482547086841dc24 Mon Sep 17 00:00:00 2001 From: Mathieu Amiot Date: Fri, 21 Jan 2022 17:55:35 +0100 Subject: [PATCH 17/89] Fixed in-buffer frame decoding + code cleanups --- Cargo.toml | 60 ++++--- src/backends/capture/avfoundation.rs | 6 +- src/backends/capture/gst_backend.rs | 6 +- src/backends/capture/msmf_backend.rs | 6 +- src/backends/capture/v4l2_backend.rs | 8 +- src/camera.rs | 44 ++--- src/query.rs | 4 +- src/utils.rs | 260 ++++++++++++++++----------- 8 files changed, 214 insertions(+), 180 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 579b382..6269d30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,8 @@ decoding = ["mozjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] -input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"] +# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x +# input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"] input-opencv = ["opencv", "opencv/clang-runtime"] input-ipcam = ["input-opencv"] input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex", "parking_lot"] @@ -28,80 +29,81 @@ output-wgpu = ["wgpu"] output-wasm = ["input-jscam"] output-threaded = ["parking_lot"] small-wasm = ["wee_alloc"] -docs-only = ["input-uvc", "input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] +docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"] docs-features = [] test-fail-warning = [] [dependencies] -thiserror = "1.0.26" -paste = "1.0.5" +thiserror = "1.0" +paste = "1.0" [dependencies.flume] -version = "0.10.9" +version = "0.10" optional = true [dependencies.image] -version = "^0.23" +version = "0.23" default-features = false [target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] -version = "0.8.24" +git = "https://github.com/otak/mozjpeg-rust" +branch = "otak/read_scanlines_into" optional = true [target.'cfg(target_os = "linux")'.dependencies.v4l] -version = "0.12.1" +version = "0.12" optional = true [target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit] -version = "0.2.0" +version = "0.2" optional = true [dependencies.ouroboros] -version = "^0.13" +version = "0.14" optional = true -[dependencies.uvc] -version = "0.2.0" -optional = true +# [dependencies.uvc] +# version = "0.2" +# optional = true [dependencies.usb_enumeration] version = "0.1.2" optional = true [dependencies.wgpu] -version = "^0.11" +version = "^0.12" optional = true [dependencies.opencv] -version = "0.60.0" +version = "0.62" features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3.3" +version = "0.3" path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] -version = "0.1.1" +version = "0.1" path = "nokhwa-bindings-macos" optional = true [dependencies.gstreamer] -version = "0.17.0" +version = "0.18" optional = true [dependencies.gstreamer-app] -version = "0.17.0" +version = "0.18" optional = true [dependencies.gstreamer-video] -version = "0.17.0" +version = "0.18" optional = true [dependencies.glib] -version = "0.14.0" +version = "0.15" optional = true [dependencies.regex] @@ -109,7 +111,7 @@ version = "1.4.6" optional = true [dependencies.web-sys] -version = "^0.3" +version = "0.3" # why features = [ "console", @@ -130,31 +132,31 @@ features = [ optional = true [dependencies.js-sys] -version = "^0.3" +version = "0.3" optional = true [dependencies.wasm-bindgen] -version = "^0.2" +version = "0.2" optional = true [dependencies.wasm-bindgen-futures] -version = "^0.4" +version = "0.4" optional = true [dependencies.wasm-rs-async-executor] -version = "^0.9" +version = "0.9" optional = true [dependencies.wee_alloc] -version = "0.4.5" +version = "0.4" optional = true [dependencies.parking_lot] -version = "^0.11" +version = "0.11" optional = true [dependencies.lazy_static] -version = "^1.4" +version = "1.4" optional = true [profile.release] diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index 704b628..a4fa733 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -15,7 +15,7 @@ */ use crate::{ - mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraIndex, CameraInfo, + mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; @@ -299,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.borrow())), - AVFourCC::MJPEG => Cow::from(mjpeg_to_rgb888(data.0.borrow())), + AVFourCC::YUV2 => Cow::from(yuyv422_to_rgb(data.0.borrow(), false)), + AVFourCC::MJPEG => Cow::from(mjpeg_to_rgb(data.0.borrow(), false)), }; Ok(data) } diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index 4641fdd..e918928 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -15,7 +15,7 @@ */ use crate::{ - mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraIndex, CameraInfo, + mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; @@ -709,7 +709,7 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result { - let mut decoded_buffer = match yuyv422_to_rgb888(&buffer_map) { + let mut decoded_buffer = match yuyv422_to_rgb(&buffer_map, false) { Ok(buf) => buf, Err(why) => { element_error!( @@ -771,7 +771,7 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result { - let mut decoded_buffer = match mjpeg_to_rgb888(&buffer_map) { + let mut decoded_buffer = match mjpeg_to_rgb(&buffer_map, false) { Ok(buf) => buf, Err(why) => { element_error!( diff --git a/src/backends/capture/msmf_backend.rs b/src/backends/capture/msmf_backend.rs index 559dd76..9644862 100644 --- a/src/backends/capture/msmf_backend.rs +++ b/src/backends/capture/msmf_backend.rs @@ -15,7 +15,7 @@ */ use crate::{ - all_known_camera_controls, mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, + all_known_camera_controls, mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag, KnownCameraControls, NokhwaError, Resolution, }; @@ -353,8 +353,8 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { let camera_format = self.camera_format(); let raw_data = self.frame_raw()?; let conv = match camera_format.format() { - FrameFormat::MJPEG => mjpeg_to_rgb888(raw_data.as_ref())?, - FrameFormat::YUYV => yuyv422_to_rgb888(raw_data.as_ref())?, + FrameFormat::MJPEG => mjpeg_to_rgb(raw_data.as_ref(), false)?, + FrameFormat::YUYV => yuyv422_to_rgb(raw_data.as_ref(), false)?, }; let imagebuf = diff --git a/src/backends/capture/v4l2_backend.rs b/src/backends/capture/v4l2_backend.rs index 0c6910e..ba699b9 100644 --- a/src/backends/capture/v4l2_backend.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -16,9 +16,9 @@ use crate::{ error::NokhwaError, - mjpeg_to_rgb888, + mjpeg_to_rgb, utils::{CameraFormat, CameraIndex, CameraInfo}, - yuyv422_to_rgb888, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, + yuyv422_to_rgb, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag, KnownCameraControls, Resolution, }; use image::{ImageBuffer, Rgb}; @@ -686,8 +686,8 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { let cam_fmt = self.camera_format; let raw_frame = self.frame_raw()?; let conv = match cam_fmt.format() { - FrameFormat::MJPEG => mjpeg_to_rgb888(&raw_frame)?, - FrameFormat::YUYV => yuyv422_to_rgb888(&raw_frame)?, + FrameFormat::MJPEG => mjpeg_to_rgb(&raw_frame, false)?, + FrameFormat::YUYV => yuyv422_to_rgb(&raw_frame, false)?, }; let image_buf = match ImageBuffer::from_vec(cam_fmt.width(), cam_fmt.height(), conv) { diff --git a/src/camera.rs b/src/camera.rs index c80e60b..b15d7b7 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -18,7 +18,7 @@ use crate::{ CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; -use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; +use image::{ImageBuffer, Rgb}; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -348,10 +348,10 @@ impl Camera { #[must_use] pub fn min_buffer_size(&self, rgba: bool) -> usize { let resolution = self.backend.resolution(); - if rgba { - return (resolution.width() * resolution.height() * 4) as usize; - } - (resolution.width() * resolution.height() * 3) as usize + let w = resolution.width() as usize; + let h = resolution.height() as usize; + let c = if rgba { 4 } else { 3 }; + w * h * c } /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. @@ -361,28 +361,18 @@ impl Camera { &mut self, buffer: &mut [u8], convert_rgba: bool, - ) -> Result { - let resolution = self.resolution(); - let frame = self.frame_raw()?; - if convert_rgba { - let image_data = - match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) { - Some(image) => { - let image: ImageBuffer, Cow<[u8]>> = image; - image - } - None => { - return Err(NokhwaError::ReadFrameError( - "Frame Cow Too Small".to_string(), - )) - } - }; - let rgba_image: RgbaImage = image_data.convert(); - buffer.copy_from_slice(rgba_image.as_raw()); - return Ok(rgba_image.len()); - } - buffer.copy_from_slice(frame.as_ref()); - Ok(frame.len()) + ) -> Result<(), NokhwaError> { + let camera_format = self.backend.camera_format(); + let format = camera_format.format(); + let raw_frame = self.frame_raw()?; + match format { + FrameFormat::MJPEG => crate::utils::buf_mjpeg_to_rgb(&raw_frame, buffer, convert_rgba)?, + FrameFormat::YUYV => { + crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? + } + }; + + Ok(()) } #[cfg(feature = "output-wgpu")] diff --git a/src/query.rs b/src/query.rs index b7c441f..0cc2482 100644 --- a/src/query.rs +++ b/src/query.rs @@ -146,7 +146,7 @@ fn query_v4l() -> Result, NokhwaError> { } #[cfg(any(not(feature = "input-v4l"), not(target_os = "linux")))] -fn query_v4l<'a>() -> Result>, NokhwaError> { +fn query_v4l() -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::Video4Linux, )) @@ -287,7 +287,7 @@ fn query_gstreamer() -> Result, NokhwaError> { } #[cfg(not(feature = "input-gst"))] -fn query_gstreamer<'a>() -> Result>, NokhwaError> { +fn query_gstreamer() -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::GStreamer, )) diff --git a/src/utils.rs b/src/utils.rs index 6d310bd..4641e73 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1095,20 +1095,27 @@ where /// - 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 = "decoding", not(target_arch = "wasm")))] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] -pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { use mozjpeg::Decompress; let mut jpeg_decompress = match Decompress::new_mem(data) { - Ok(decompress) => match decompress.rgb() { - Ok(decompressor) => decompressor, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } } - }, + } Err(why) => { return Err(NokhwaError::ProcessFrameError { src: FrameFormat::MJPEG, @@ -1117,25 +1124,69 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { }) } }; - let decompressed = match jpeg_decompress.read_scanlines::<[u8; 3]>() { - Some(pixels) => pixels, - None => { + + let scanlines_res: Option> = jpeg_decompress.read_scanlines_flat(); + assert!(jpeg_decompress.finish_decompress()); + + match scanlines_res { + Some(pixels) => Ok(pixels), + None => Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "Failed to get read readlines into RGB888 pixels!".to_string(), + }), + } +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) +} + +#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] +#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + use mozjpeg::Decompress; + + let mut jpeg_decompress = match Decompress::new_mem(data) { + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + } + } + Err(why) => { return Err(NokhwaError::ProcessFrameError { src: FrameFormat::MJPEG, destination: "RGB888".to_string(), - error: "Failed to get read readlines into RGB888 pixels!".to_string(), + error: why.to_string(), }) } }; - Ok( - unsafe { std::slice::from_raw_parts(decompressed.as_ptr().cast(), decompressed.len() * 3) } - .to_vec(), - ) + assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size()); + + jpeg_decompress.read_scanlines_flat_into(dest); + assert!(jpeg_decompress.finish_decompress()); + + Ok(()) } #[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] -pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { Err(NokhwaError::NotImplementedError( "Not available on WASM".to_string(), )) @@ -1151,100 +1202,85 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { /// # Errors /// This may error when the data stream size is not divisible by 4, a i32 -> u8 conversion fails, or it fails to read from a certain index. #[inline] -pub fn yuyv422_to_rgb888(data: &[u8]) -> Result, NokhwaError> { - let mut rgb_vec: Vec = vec![]; - if data.len() % 4 == 0 { - for px_idx in (0..data.len()).step_by(4) { - let y1 = match data.get(px_idx) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx - ), - }); - } - }; - - let u = match data.get(px_idx + 1) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+1, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 1 - ), - }); - } - }; - - let y2 = match data.get(px_idx + 2) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+2, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 2 - ), - }); - } - }; - - let v = match data.get(px_idx + 3) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+3, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 3 - ), - }); - } - }; - - let pixel1 = yuyv444_to_rgb888(y1, u, v); - let pixel2 = yuyv444_to_rgb888(y2, u, v); - rgb_vec.append(&mut pixel1.to_vec()); - rgb_vec.append(&mut pixel2.to_vec()); - } - Ok(rgb_vec) - } else { - Err(NokhwaError::ProcessFrameError { +pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" .to_string(), - }) + }); } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + let mut dest = Vec::with_capacity(rgb_buf_size); + buf_yuyv422_to_rgb(data, &mut dest, rgba)?; + + Ok(dest) +} + +/// Same as yuyv422_to_rgb(&data) but with a destination buffer +pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" + .to_string(), + }); + } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + if dest.len() != rgb_buf_size { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()), + }); + } + + let iter = data.chunks_exact(4); + + if rgba { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = yuyv[0] as i32; + let u = yuyv[1] as i32; + let y2 = yuyv[2] as i32; + let v = yuyv[3] as i32; + let pixel1 = yuyv444_to_rgba(y1, u, v); + let pixel2 = yuyv444_to_rgba(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + for i in 0..rgb_buf_size { + dest[i] = iter.next().unwrap(); + } + } else { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = yuyv[0] as i32; + let u = yuyv[1] as i32; + let y2 = yuyv[2] as i32; + let v = yuyv[3] as i32; + let pixel1 = yuyv444_to_rgb(y1, u, v); + let pixel2 = yuyv444_to_rgb(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + + for i in 0..rgb_buf_size { + dest[i] = iter.next().unwrap(); + } + } + + Ok(()) } // equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB @@ -1254,7 +1290,7 @@ pub fn yuyv422_to_rgb888(data: &[u8]) -> Result, NokhwaError> { #[allow(clippy::cast_sign_loss)] #[must_use] #[inline] -pub fn yuyv444_to_rgb888(y: i32, u: i32, v: i32) -> [u8; 3] { +pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { let c298 = (y - 16) * 298; let d = u - 128; let e = v - 128; @@ -1263,3 +1299,9 @@ pub fn yuyv444_to_rgb888(y: i32, u: i32, v: i32) -> [u8; 3] { let b = ((c298 + 516 * d + 128) >> 8).clamp(0, 255) as u8; [r, g, b] } + +#[inline] +pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] { + let [r, g, b] = yuyv444_to_rgb(y, u, v); + [r, g, b, 255] +} From 83f8e8eadef1a443c4d8939dfb84dd17f99de138 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Fri, 4 Feb 2022 18:47:41 +0900 Subject: [PATCH 18/89] remove 0.10 stall message 0.10 is only stalled due to MozJPEG not updated yet. --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 370a8ef..a21dd66 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,3 @@ Contributions are welcome! ## Minimum Service Rust Version `nokhwa` may build on older versions of `rustc`, but there is no guarantee except for the latest stable rust. - -## 0.10 -0.10 is currently stalled due to upstream not having the necessary features (wasm-bindgen). From 82effc946992e92094a980ef981e48fbe0b3ed71 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 14 Feb 2022 02:45:51 +0900 Subject: [PATCH 19/89] MozJPEG 0.9 --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6269d30..aca6e22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,7 @@ version = "0.23" default-features = false [target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] -git = "https://github.com/otak/mozjpeg-rust" -branch = "otak/read_scanlines_into" +version = "0.9" optional = true [target.'cfg(target_os = "linux")'.dependencies.v4l] From 66320eaec78e4305738542b4b8e1af0d84ca3500 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 10 Jan 2022 10:14:07 +0900 Subject: [PATCH 20/89] Add readme badges, 0.10 status update --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7ce8107..f7fb3cd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![cargo version](https://img.shields.io/crates/v/nokhwa.svg)](https://crates.io/crates/nokhwa) ![docs.rs version](https://img.shields.io/docsrs/nokhwa) # nokhwa Nokhwa(녹화): Korean word meaning "to record". @@ -107,3 +108,6 @@ Contributions are welcome! ## Minimum Service Rust Version `nokhwa` may build on older versions of `rustc`, but there is no guarantee except for the latest stable rust. + +## 0.10 +0.10 is currently stalled due to upstream not having the necessary features (wasm-bindgen). From 1173ffd2c07e3cd3cb3969c1cddfa65e3d7a259d Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Wed, 12 Jan 2022 23:50:18 +0900 Subject: [PATCH 21/89] Fix Broken README, fixing #22 Fix issue #22 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7fb3cd..30cba31 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ let mut camera = Camera::new( // open stream camera.open_stream().unwrap(); loop { - let frame = camera.get_frame().unwrap(); + let frame = camera.frame().unwrap(); println!("{}, {}", frame.width(), frame.height()); } ``` From 121ce2c5ebfdc80176b1c8968e09dcba6e63d938 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Thu, 13 Jan 2022 00:11:48 +0900 Subject: [PATCH 22/89] Fix Docs.rs badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30cba31..370a8ef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![cargo version](https://img.shields.io/crates/v/nokhwa.svg)](https://crates.io/crates/nokhwa) ![docs.rs version](https://img.shields.io/docsrs/nokhwa) +[![cargo version](https://img.shields.io/crates/v/nokhwa.svg)](https://crates.io/crates/nokhwa) [![docs.rs version](https://img.shields.io/docsrs/nokhwa)](https://docs.rs/nokhwa/latest/nokhwa/) # nokhwa Nokhwa(녹화): Korean word meaning "to record". From 88499e4c562b1eff2a204e76175765bac4f0e09e Mon Sep 17 00:00:00 2001 From: Stefan Date: Thu, 20 Jan 2022 22:27:07 +0900 Subject: [PATCH 23/89] Fix threaded-camera example --- examples/threaded-capture/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/threaded-capture/Cargo.toml b/examples/threaded-capture/Cargo.toml index 9cb829c..d8c48cb 100644 --- a/examples/threaded-capture/Cargo.toml +++ b/examples/threaded-capture/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies.image] version = "0.23.14" -no-default-features = true +default-features = false [dependencies.nokhwa] path = "../../../nokhwa" From f9a1abc156c7b2157393b39ce7449e6ecd3f24be Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 14 Feb 2022 18:26:21 +0900 Subject: [PATCH 24/89] merge some commits --- examples/threaded-capture/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/threaded-capture/src/main.rs b/examples/threaded-capture/src/main.rs index 7b95cb4..db7a79b 100644 --- a/examples/threaded-capture/src/main.rs +++ b/examples/threaded-capture/src/main.rs @@ -16,7 +16,6 @@ use image::{ImageBuffer, Rgb}; use nokhwa::{query_devices, CaptureAPIBackend, ThreadedCamera}; -use std::time::Duration; fn main() { let cameras = query_devices(CaptureAPIBackend::Auto).unwrap(); From 07f0ebf20d37cd42cd45c191f485570cf071c82b Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sat, 22 Jan 2022 01:55:35 +0900 Subject: [PATCH 25/89] Cherry Pick No-Copy Fixes --- Cargo.toml | 83 ++-- nokhwa-bindings-windows/src/lib.rs | 2 +- src/backends/capture/avfoundation.rs | 24 +- src/backends/capture/gst_backend.rs | 71 ++-- src/backends/capture/msmf.rs | 6 +- src/backends/capture/v4l2.rs | 21 +- src/camera.rs | 45 +-- src/utils.rs | 581 +++++++++++++-------------- 8 files changed, 403 insertions(+), 430 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 326ed34..6269d30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "nokhwa" -version = "0.9.4" +version = "0.10.0" authors = ["l1npengtul "] -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,92 +14,96 @@ repository = "https://github.com/l1npengtul/nokhwa" crate-type = ["cdylib", "rlib"] [features] -default = ["decoding", "flume"] +default = ["flume", "decoding"] decoding = ["mozjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] -input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration"] +# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x +# input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"] 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-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex", "parking_lot"] +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"] small-wasm = ["wee_alloc"] -docs-only = ["input-uvc", "input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] +docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"] docs-features = [] test-fail-warning = [] [dependencies] -thiserror = "1.0.26" -paste = "1.0.5" +thiserror = "1.0" +paste = "1.0" [dependencies.flume] -version = "0.10.8" -optional = true - -[target.'cfg(not(target_family = "wasm"))'.dependencies.mozjpeg] -version = "0.8.24" +version = "0.10" optional = true [dependencies.image] -version = "^0.23" +version = "0.23" default-features = false +[target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] +git = "https://github.com/otak/mozjpeg-rust" +branch = "otak/read_scanlines_into" +optional = true + [target.'cfg(target_os = "linux")'.dependencies.v4l] -version = "0.12.1" +version = "0.12" optional = true [target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit] -version = "0.2.0" +version = "0.2" optional = true [dependencies.ouroboros] -version = "^0.13" +version = "0.14" optional = true -[dependencies.uvc] -version = "0.2.0" -optional = true +# [dependencies.uvc] +# version = "0.2" +# optional = true [dependencies.usb_enumeration] version = "0.1.2" optional = true [dependencies.wgpu] -version = "^0.11" +version = "^0.12" optional = true [dependencies.opencv] -version = "0.60.0" +version = "0.62" features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3.4" +version = "0.3" +path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] -version = "0.1.1" +version = "0.1" +path = "nokhwa-bindings-macos" optional = true [dependencies.gstreamer] -version = "0.17.0" +version = "0.18" optional = true [dependencies.gstreamer-app] -version = "0.17.0" +version = "0.18" optional = true [dependencies.gstreamer-video] -version = "0.17.0" +version = "0.18" optional = true [dependencies.glib] -version = "0.14.0" +version = "0.15" optional = true [dependencies.regex] @@ -108,7 +111,7 @@ version = "1.4.6" optional = true [dependencies.web-sys] -version = "^0.3" +version = "0.3" # why features = [ "console", @@ -129,23 +132,31 @@ features = [ optional = true [dependencies.js-sys] -version = "^0.3" +version = "0.3" optional = true [dependencies.wasm-bindgen] -version = "^0.2" +version = "0.2" optional = true [dependencies.wasm-bindgen-futures] -version = "^0.4" +version = "0.4" +optional = true + +[dependencies.wasm-rs-async-executor] +version = "0.9" optional = true [dependencies.wee_alloc] -version = "0.4.5" +version = "0.4" optional = true [dependencies.parking_lot] -version = "^0.11" +version = "0.11" +optional = true + +[dependencies.lazy_static] +version = "1.4" optional = true [profile.release] diff --git a/nokhwa-bindings-windows/src/lib.rs b/nokhwa-bindings-windows/src/lib.rs index 2862781..87de221 100644 --- a/nokhwa-bindings-windows/src/lib.rs +++ b/nokhwa-bindings-windows/src/lib.rs @@ -1861,7 +1861,7 @@ pub mod wmf { } impl<'a> MediaFoundationDevice<'a> { - pub fn new(_: usize) -> Result { + pub fn new(_: usize, _: MFCameraFormat) -> Result { Ok(MediaFoundationDevice { phantom: &Empty() }) } diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index 1374a6f..a4fa733 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -15,15 +15,16 @@ */ use crate::{ - mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, - CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, + mjpeg_to_rgb, yuyv422_to_rgb, 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`]. @@ -48,13 +49,18 @@ impl AVFoundationCaptureDevice { /// /// 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) -> Result { + /// 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, + camera_format: Option, + ) -> Result { 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, 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_rgb(data.0.borrow(), false)), + AVFourCC::MJPEG => Cow::from(mjpeg_to_rgb(data.0.borrow(), false)), }; Ok(data) } diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index 591d582..e918928 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -15,10 +15,10 @@ */ use crate::{ - mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, - CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, + mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo, + CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, + Resolution, }; -use flume::Receiver; use glib::Quark; use gstreamer::{ element_error, @@ -30,11 +30,11 @@ use gstreamer::{ use gstreamer_app::{AppSink, AppSinkCallbacks}; use gstreamer_video::{VideoFormat, VideoInfo}; use image::{ImageBuffer, Rgb}; +use parking_lot::Mutex; use regex::Regex; -use std::any::Any; -use std::{borrow::Cow, collections::HashMap, str::FromStr}; +use std::{any::Any, borrow::Cow, collections::HashMap, str::FromStr, sync::Arc}; -type PipelineGenRet = (Element, AppSink, Receiver, Vec>>); +type PipelineGenRet = (Element, AppSink, Arc, Vec>>>); /// The backend struct that interfaces with `GStreamer`. /// To see what this does, please see [`CaptureBackendTrait`]. @@ -47,7 +47,7 @@ pub struct GStreamerCaptureDevice { app_sink: AppSink, camera_format: CameraFormat, camera_info: CameraInfo, - receiver: Receiver, Vec>>, + image_lock: Arc, Vec>>>, caps: Option, } @@ -58,13 +58,15 @@ impl GStreamerCaptureDevice { /// /// 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) -> Result { + /// 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, cam_fmt: Option) -> Result { 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 +101,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( @@ -112,23 +114,23 @@ impl GStreamerCaptureDevice { let caps = device.caps(); ( CameraInfo::new( - DeviceExt::display_name(&device).to_string(), - DeviceExt::device_class(&device).to_string(), - "".to_string(), - index, + &DeviceExt::display_name(&device), + &DeviceExt::device_class(&device), + &"", + 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, app_sink, camera_format, camera_info, - receiver, + image_lock: receiver, caps, }) } @@ -138,7 +140,12 @@ 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 { + pub fn new_with( + index: &CameraIndex, + width: u32, + height: u32, + fps: u32, + ) -> Result { let cam_fmt = CameraFormat::new(Resolution::new(width, height), FrameFormat::MJPEG, fps); GStreamerCaptureDevice::new(index, Some(cam_fmt)) } @@ -163,10 +170,11 @@ 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; + self.image_lock = receiver; if reopen { self.open_stream()?; } @@ -540,15 +548,7 @@ impl CaptureBackendTrait for GStreamerCaptureDevice { } } - match self.receiver.recv() { - Ok(msg) => Ok(Cow::from(msg.to_vec())), - Err(why) => { - return Err(NokhwaError::ReadFrameError(format!( - "Receiver Error: {}", - why - ))); - } - } + Ok(Cow::from(self.image_lock.lock().to_vec())) } fn stop_stream(&mut self) -> Result<(), NokhwaError> { @@ -564,7 +564,7 @@ impl CaptureBackendTrait for GStreamerCaptureDevice { impl Drop for GStreamerCaptureDevice { fn drop(&mut self) { - self.pipeline.set_state(State::Null).unwrap(); + let _ = self.pipeline.set_state(State::Null); } } @@ -650,7 +650,8 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result Result { - let mut decoded_buffer = match yuyv422_to_rgb888(&buffer_map) { + let mut decoded_buffer = match yuyv422_to_rgb(&buffer_map, false) { Ok(buf) => buf, Err(why) => { element_error!( @@ -770,7 +771,7 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result { - let mut decoded_buffer = match mjpeg_to_rgb888(&buffer_map) { + let mut decoded_buffer = match mjpeg_to_rgb(&buffer_map, false) { Ok(buf) => buf, Err(why) => { element_error!( @@ -816,13 +817,11 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { let camera_format = self.camera_format(); let raw_data = self.frame_raw()?; let conv = match camera_format.format() { - FrameFormat::MJPEG => mjpeg_to_rgb888(raw_data.as_ref())?, - FrameFormat::YUYV => yuyv422_to_rgb888(raw_data.as_ref())?, + FrameFormat::MJPEG => mjpeg_to_rgb(raw_data.as_ref(), false)?, + FrameFormat::YUYV => yuyv422_to_rgb(raw_data.as_ref(), false)?, }; let imagebuf = diff --git a/src/backends/capture/v4l2.rs b/src/backends/capture/v4l2.rs index 0fd31b8..ba699b9 100644 --- a/src/backends/capture/v4l2.rs +++ b/src/backends/capture/v4l2.rs @@ -16,9 +16,9 @@ use crate::{ error::NokhwaError, - mjpeg_to_rgb888, - utils::{CameraFormat, CameraInfo}, - yuyv422_to_rgb888, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, + mjpeg_to_rgb, + utils::{CameraFormat, CameraIndex, CameraInfo}, + yuyv422_to_rgb, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag, KnownCameraControls, Resolution, }; use image::{ImageBuffer, Rgb}; @@ -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) -> Result { - 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, cam_fmt: Option) -> Result { + 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,7 @@ 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, "", &caps.driver, CameraIndex::Index(index)), Err(why) => { return Err(NokhwaError::GetPropertyError { property: "Capabilities".to_string(), @@ -269,7 +270,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, width: u32, height: u32, fps: u32, @@ -685,8 +686,8 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { let cam_fmt = self.camera_format; let raw_frame = self.frame_raw()?; let conv = match cam_fmt.format() { - FrameFormat::MJPEG => mjpeg_to_rgb888(&raw_frame)?, - FrameFormat::YUYV => yuyv422_to_rgb888(&raw_frame)?, + FrameFormat::MJPEG => mjpeg_to_rgb(&raw_frame, false)?, + FrameFormat::YUYV => yuyv422_to_rgb(&raw_frame, false)?, }; let image_buf = match ImageBuffer::from_vec(cam_fmt.width(), cam_fmt.height(), conv) { diff --git a/src/camera.rs b/src/camera.rs index d3c319d..92378ce 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -18,7 +18,8 @@ use crate::{ CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; -use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; +use image::buffer::ConvertBuffer; +use image::{ImageBuffer, Rgb, RgbaImage}; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -363,10 +364,10 @@ impl Camera { #[must_use] pub fn min_buffer_size(&self, rgba: bool) -> usize { let resolution = self.backend.resolution(); - if rgba { - return (resolution.width() * resolution.height() * 4) as usize; - } - (resolution.width() * resolution.height() * 3) as usize + let w = resolution.width() as usize; + let h = resolution.height() as usize; + let c = if rgba { 4 } else { 3 }; + w * h * c } /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. @@ -376,28 +377,18 @@ impl Camera { &mut self, buffer: &mut [u8], convert_rgba: bool, - ) -> Result { - let resolution = self.resolution(); - let frame = self.frame_raw()?; - if convert_rgba { - let image_data = - match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) { - Some(image) => { - let image: ImageBuffer, Cow<[u8]>> = image; - image - } - None => { - return Err(NokhwaError::ReadFrameError( - "Frame Cow Too Small".to_string(), - )) - } - }; - let rgba_image: RgbaImage = image_data.convert(); - buffer.copy_from_slice(rgba_image.as_raw()); - return Ok(rgba_image.len()); - } - buffer.copy_from_slice(frame.as_ref()); - Ok(frame.len()) + ) -> Result<(), NokhwaError> { + let camera_format = self.backend.camera_format(); + let format = camera_format.format(); + let raw_frame = self.frame_raw()?; + match format { + FrameFormat::MJPEG => crate::utils::buf_mjpeg_to_rgb(&raw_frame, buffer, convert_rgba)?, + FrameFormat::YUYV => { + crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? + } + }; + + Ok(()) } #[cfg(feature = "output-wgpu")] diff --git a/src/utils.rs b/src/utils.rs index 239ceaa..4641e73 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,24 +14,9 @@ * limitations under the License. */ -/* - * Copyright 2021 l1npengtul / 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, @@ -170,56 +155,14 @@ impl From for AVFourCC { /// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest. /// # JS-WASM /// This is exported as `JSResolution` -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSResolution))] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] pub struct Resolution { pub width_x: u32, pub height_y: u32, } -#[cfg(not(target_feature = "output-wasm"))] -impl Resolution { - /// Create a new resolution from 2 image size coordinates. - /// # JS-WASM - /// This is exported as a constructor for [`Resolution`]. - #[must_use] - pub fn new(x: u32, y: u32) -> Self { - Resolution { - width_x: x, - height_y: y, - } - } - - /// Get the width of Resolution - /// # JS-WASM - /// This is exported as `get_Width`. - #[must_use] - pub fn width(self) -> u32 { - self.width_x - } - - /// Get the height of Resolution - /// # JS-WASM - /// This is exported as `get_Height`. - #[must_use] - pub fn height(self) -> u32 { - self.height_y - } - - /// Get the x (width) of Resolution - #[must_use] - pub fn x(self) -> u32 { - self.width_x - } - - /// Get the y (height) of Resolution - #[must_use] - pub fn y(self) -> u32 { - self.height_y - } -} - -#[cfg(target_feature = "output-wasm")] +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSResolution))] impl Resolution { /// Create a new resolution from 2 image size coordinates. /// # JS-WASM @@ -337,7 +280,7 @@ impl From 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, @@ -513,103 +456,32 @@ impl From for CaptureDeviceFormatDescriptor { /// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system. /// # JS-WASM /// This is exported as a `JSCameraInfo`. -#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))] +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] pub struct CameraInfo { human_name: String, description: String, misc: String, - index: usize, + index: CameraIndex, } -#[cfg(not(target_feature = "output-wasm"))] -impl CameraInfo { - /// Create a new [`CameraInfo`]. - /// # JS-WASM - /// This is exported as a constructor for [`CameraInfo`]. - #[must_use] - pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self { - CameraInfo { - human_name, - description, - misc, - index, - } - } - - /// Get a reference to the device info's human readable name. - /// # JS-WASM - /// This is exported as a `get_HumanReadableName`. - #[must_use] - pub fn human_name(&self) -> String { - self.human_name.clone() - } - - /// Set the device info's human name. - /// # JS-WASM - /// This is exported as a `set_HumanReadableName`. - pub fn set_human_name(&mut self, human_name: String) { - self.human_name = human_name; - } - - /// Get a reference to the device info's description. - /// # JS-WASM - /// This is exported as a `get_Description`. - #[must_use] - pub fn description(&self) -> String { - self.description.clone() - } - - /// Set the device info's description. - /// # JS-WASM - /// This is exported as a `set_Description`. - pub fn set_description(&mut self, description: String) { - self.description = description; - } - - /// Get a reference to the device info's misc. - /// # JS-WASM - /// This is exported as a `get_MiscString`. - #[must_use] - pub fn misc(&self) -> String { - self.misc.clone() - } - - /// Set the device info's misc. - /// # JS-WASM - /// This is exported as a `set_MiscString`. - pub fn set_misc(&mut self, misc: String) { - self.misc = misc; - } - - /// Get a reference to the device info's index. - /// # JS-WASM - /// This is exported as a `get_Index`. - #[must_use] - pub fn index(&self) -> usize { - self.index - } - - /// Set the device info's index. - /// # JS-WASM - /// This is exported as a `set_Index`. - pub fn set_index(&mut self, index: usize) { - self.index = index; - } -} - -#[cfg(target_feature = "output-wasm")] +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))] 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))] - pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self { + pub fn new( + human_name: &(impl AsRef + ?Sized), + description: &(impl AsRef + ?Sized), + misc: &(impl AsRef + ?Sized), + index: CameraIndex, + ) -> Self { CameraInfo { - human_name, - description, - misc, + human_name: human_name.as_ref().to_string(), + description: description.as_ref().to_string(), + misc: misc.as_ref().to_string(), index, } } @@ -619,22 +491,22 @@ impl CameraInfo { /// This is exported as a `get_HumanReadableName`. #[must_use] #[cfg_attr( - feature = "output-wasm", - wasm_bindgen(getter = HumanReadableName) + 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. /// # JS-WASM /// This is exported as a `set_HumanReadableName`. #[cfg_attr( - feature = "output-wasm", - wasm_bindgen(setter = HumanReadableName) + 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>(&mut self, human_name: S) { + self.human_name = human_name.as_ref().to_string(); } /// Get a reference to the device info's description. @@ -642,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>(&mut self, description: S) { + self.description = description.as_ref().to_string(); } /// Get a reference to the device info's misc. @@ -659,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>(&mut self, misc: S) { + self.misc = misc.as_ref().to_string(); } /// Get a reference to the device info's index. @@ -676,28 +548,35 @@ 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 { + &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) { self.index = index; } -} -impl PartialOrd for CameraInfo { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for CameraInfo { - fn cmp(&self, other: &Self) -> Ordering { - self.index.cmp(&other.index) + /// Gets the device info's index as an `u32`. + /// # Errors + /// If the index is not parsable as a `u32`, this will error. + /// # JS-WASM + /// This is exported as `get_Index_Int` + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] + pub fn index_num(&self) -> Result { + match &self.index { + CameraIndex::Index(i) => Ok(*i), + CameraIndex::String(s) => match s.parse::() { + Ok(p) => Ok(p), + Err(why) => Err(NokhwaError::GetPropertyError { + property: "index-int".to_string(), + error: why.to_string(), + }), + }, + } } } @@ -718,10 +597,10 @@ impl Display for CameraInfo { impl From> for CameraInfo { fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { CameraInfo { - human_name: dev_desc.name_as_string(), - description: "Media Foundation Device".to_string(), + human_name: dev_desc, + description: "Media Foundation Device", misc: dev_desc.link_as_string(), - index: dev_desc.index(), + index: CameraIndex::Index(dev_desc.index() as u32), } } } @@ -744,7 +623,7 @@ impl From for CameraInfo { human_name: descriptor.name, description: descriptor.description, misc: descriptor.misc, - index: descriptor.index as usize, + index: CameraIndex::Index(descriptor.index as u32), } } } @@ -1115,13 +994,15 @@ impl Ord for CameraControl { /// The list of known capture backends to the library.
/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform. -/// - `AVFoundation` - Uses `AVFoundation` on MacOSX +/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` /// - `V4L2` - `Video4Linux2`, a linux specific backend. /// - `UVC` - 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` - 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, @@ -1130,6 +1011,8 @@ pub enum CaptureAPIBackend { MediaFoundation, OpenCv, GStreamer, + Network, + Browser, } impl Display for CaptureAPIBackend { @@ -1139,54 +1022,100 @@ 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 -/// ://:/ -/// ``` -/// 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 { Index(u32), - IPCamera(String), + String(String), } -impl Display for CameraIndexType { +impl CameraIndex { + /// Gets the device info's index as an `u32`. + /// # Errors + /// If the index is not parsable as a `u32`, this will error. + pub fn as_index(&self) -> Result { + match self { + CameraIndex::Index(i) => Ok(*i), + CameraIndex::String(s) => match s.parse::() { + Ok(p) => Ok(p), + Err(why) => Err(NokhwaError::GetPropertyError { + property: "index-int".to_string(), + error: why.to_string(), + }), + }, + } + } +} + +impl Display for CameraIndex { 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 From for CameraIndex { + fn from(v: u32) -> Self { + CameraIndex::Index(v) + } +} + +/// Trait for strings that can be converted to [`CameraIndex`]es. +pub trait ValidString: AsRef {} + +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 From for CameraIndex +where + T: ValidString, +{ + fn from(v: T) -> Self { + CameraIndex::String(v.as_ref().to_string()) + } +} + /// Converts a MJPEG stream of [u8] into a Vec 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(all(feature = "decoding", not(target_arch = "wasm")))] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] -pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { use mozjpeg::Decompress; let mut jpeg_decompress = match Decompress::new_mem(data) { - Ok(decompress) => match decompress.rgb() { - Ok(decompressor) => decompressor, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } } - }, + } Err(why) => { return Err(NokhwaError::ProcessFrameError { src: FrameFormat::MJPEG, @@ -1195,21 +1124,72 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { }) } }; - let decompressed = match jpeg_decompress.read_scanlines::<[u8; 3]>() { - Some(pixels) => pixels, - None => { + + let scanlines_res: Option> = jpeg_decompress.read_scanlines_flat(); + assert!(jpeg_decompress.finish_decompress()); + + match scanlines_res { + Some(pixels) => Ok(pixels), + None => Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "Failed to get read readlines into RGB888 pixels!".to_string(), + }), + } +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) +} + +#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] +#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + use mozjpeg::Decompress; + + let mut jpeg_decompress = match Decompress::new_mem(data) { + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + } + } + Err(why) => { return Err(NokhwaError::ProcessFrameError { src: FrameFormat::MJPEG, destination: "RGB888".to_string(), - error: "Failed to get read readlines into RGB888 pixels!".to_string(), + error: why.to_string(), }) } }; - Ok( - unsafe { std::slice::from_raw_parts(decompressed.as_ptr().cast(), decompressed.len() * 3) } - .to_vec(), - ) + assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size()); + + jpeg_decompress.read_scanlines_flat_into(dest); + assert!(jpeg_decompress.finish_decompress()); + + Ok(()) +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) } // 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 @@ -1221,105 +1201,86 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, 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, NokhwaError> { - use std::convert::TryFrom; - - let mut rgb_vec: Vec = vec![]; - if data.len() % 4 == 0 { - for px_idx in (0..data.len()).step_by(4) { - let y1 = match data.get(px_idx) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx - ), - }); - } - }; - - let u = match data.get(px_idx + 1) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+1, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 1 - ), - }); - } - }; - - let y2 = match data.get(px_idx + 2) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+2, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 2 - ), - }); - } - }; - - let v = match data.get(px_idx + 3) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+3, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 3 - ), - }); - } - }; - - let pixel1 = yuyv444_to_rgb888(y1, u, v); - let pixel2 = yuyv444_to_rgb888(y2, u, v); - rgb_vec.append(&mut pixel1.to_vec()); - rgb_vec.append(&mut pixel2.to_vec()); - } - Ok(rgb_vec) - } else { - Err(NokhwaError::ProcessFrameError { +pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" .to_string(), - }) + }); } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + let mut dest = Vec::with_capacity(rgb_buf_size); + buf_yuyv422_to_rgb(data, &mut dest, rgba)?; + + Ok(dest) +} + +/// Same as yuyv422_to_rgb(&data) but with a destination buffer +pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" + .to_string(), + }); + } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + if dest.len() != rgb_buf_size { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()), + }); + } + + let iter = data.chunks_exact(4); + + if rgba { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = yuyv[0] as i32; + let u = yuyv[1] as i32; + let y2 = yuyv[2] as i32; + let v = yuyv[3] as i32; + let pixel1 = yuyv444_to_rgba(y1, u, v); + let pixel2 = yuyv444_to_rgba(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + for i in 0..rgb_buf_size { + dest[i] = iter.next().unwrap(); + } + } else { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = yuyv[0] as i32; + let u = yuyv[1] as i32; + let y2 = yuyv[2] as i32; + let v = yuyv[3] as i32; + let pixel1 = yuyv444_to_rgb(y1, u, v); + let pixel2 = yuyv444_to_rgb(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + + for i in 0..rgb_buf_size { + dest[i] = iter.next().unwrap(); + } + } + + Ok(()) } // equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB @@ -1328,10 +1289,8 @@ pub fn yuyv422_to_rgb888(data: &[u8]) -> Result, 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] { +pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { let c298 = (y - 16) * 298; let d = u - 128; let e = v - 128; @@ -1340,3 +1299,9 @@ pub fn yuyv444_to_rgb888(y: i32, u: i32, v: i32) -> [u8; 3] { let b = ((c298 + 516 * d + 128) >> 8).clamp(0, 255) as u8; [r, g, b] } + +#[inline] +pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] { + let [r, g, b] = yuyv444_to_rgb(y, u, v); + [r, g, b, 255] +} From b6bd05eb1d9f87b8efe39ba3baee0304c551f3d2 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 14 Feb 2022 18:45:27 +0900 Subject: [PATCH 26/89] Deprecate GST and UVC. They are a pain to work with and just abandoned. If anyone wants to pick up the maintanence slack, feel free to do so. --- src/backends/capture/gst_backend.rs | 4 ++++ src/backends/capture/uvc_backend.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index e918928..bd7a18a 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -42,6 +42,10 @@ type PipelineGenRet = (Element, AppSink, Arc, Vec> /// - `Drop`-ing this may cause a `panic`. /// - Setting controls is not supported. #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-gst")))] +#[deprecated( + since = "0.10", + note = "Use one of the native backends instead(V4L, AVF, MSMF) or OpenCV" +)] pub struct GStreamerCaptureDevice { pipeline: Element, app_sink: AppSink, diff --git a/src/backends/capture/uvc_backend.rs b/src/backends/capture/uvc_backend.rs index 829e914..6b9fac9 100644 --- a/src/backends/capture/uvc_backend.rs +++ b/src/backends/capture/uvc_backend.rs @@ -54,6 +54,10 @@ use uvc::{ /// - If internal variables `stream_handle_init` and `active_stream_init` become de-synchronized with the true reality (weather streamhandle/activestream is init or not) this will cause undefined behaviour. #[self_referencing] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-uvc")))] +#[deprecated( + since = "0.10", + note = "Use one of the native backends instead(V4L, AVF, MSMF) or OpenCV" +)] pub struct UVCCaptureDevice<'a> { camera_format: CameraFormat, camera_info: CameraInfo, From 74a1a844162ff4b92f27e67e1f2177c84b1c4a2f Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 28 Feb 2022 20:43:20 +0900 Subject: [PATCH 27/89] fix capture --- Cargo.toml | 3 +-- examples/capture/Cargo.toml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6269d30..aca6e22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,7 @@ version = "0.23" default-features = false [target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] -git = "https://github.com/otak/mozjpeg-rust" -branch = "otak/read_scanlines_into" +version = "0.9" optional = true [target.'cfg(target_os = "linux")'.dependencies.v4l] diff --git a/examples/capture/Cargo.toml b/examples/capture/Cargo.toml index f2b4593..77438ce 100644 --- a/examples/capture/Cargo.toml +++ b/examples/capture/Cargo.toml @@ -10,14 +10,13 @@ edition = "2018" default = ["nokhwa/default"] input-msmf = ["nokhwa/input-msmf"] input-v4l = ["nokhwa/input-v4l"] -input-uvc = ["nokhwa/input-uvc"] input-opencv = ["nokhwa/input-opencv"] input-gst = ["nokhwa/input-gst"] input-avfoundation = ["nokhwa/input-avfoundation"] [dependencies] clap = "2.33.3" -glium = "0.30.0" +glium = "0.31.0" glutin = "0.27.0" flume = "0.10.9" From 033516227309667f2a548dd7fb2e4899d390bf58 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 28 Feb 2022 22:41:53 +0900 Subject: [PATCH 28/89] clippy fix for macos --- .../UserInterfaceState.xcuserstate | Bin 30767 -> 30936 bytes nokhwa-bindings-macos/src/lib.rs | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate b/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate index 1ffedf426ef483c7376f731def7ec352fda4757d..bc42ece55cac2ed00a7abdcea30a108009607439 100644 GIT binary patch delta 9967 zcmZ{p2Ut_r+raP19jJs12uVnQgbW~o03jrV5E2LpDmIdgYpdT0NL!!(^TBg3=bn4U`+L_p=d$TOIR5}F zfTR)2z_J(4Dp%gd9%7HNzW@e!fDb}J7!ZJPAOms`0~8<@#DN5+3s4620sTQ47{a81 z7aQ^Oz8qhHzlX2H8}LTF315eQgm1(*;jQ>C zd=LH^egHp+e~ll(&*10r3;1RH3jQm81HXyi!f)gE@!#;j@#pvp0wV|_i0DKJ2?-%3 zWQ3eh5o$t1Xo(~unaC#!h(e->a1+IZhwu_5bwpR98_|;(Knx^&L?z)TMiHZlImBGz zO=2GL7BQcAn|Oy6lblDsMJ^y0lFP{z9|pzEnBo zqbjLk)N9l@YCJW8nn+EhW>BwFGpV-t6F73q1`qI- zI;)4)R*xE4K6X-LSsvr=GN`Ts3*s zR?LRkdm;Ie5&u;w7J_k*fpf4TOuHU)V7ZtRb76T{K30Gg(xJ#K0R;$Y5iO=8)?-Sn z81rCWtOV*%wjBEoyF}aQ96E`1&`!p~o5TDaDrTl~Iu+l+?qR|P>_^(( zfZeBaX~XH4?7v};FzxEqbZSS%6YP(FTK-A9{%!d;_Po6%lf#dbU~13>1Y=4N0uTUQ zO!q)4=t=iNfpnWMXWr*`%N7T8QCmLrwcJr7MvwK?)F8*{RX%*2@8wiL2qc)c35b9g zM9?1EOP4eODH<}0?urI0WsISdyC{LW0jTJ1ba!T-R3Jq<0`WkDG~^jqTRvpCuhduD zgle=%HNS@HNFJ+<7s;$3(#7dH_9; z9z>VXgXwa52t9NIu!C%n19A`@x{xUOffC4}D`+3>N57o5i%nOzmHdcySJ=I}G9DkX z=8hUZynJ+xuOhE{xUZ&+7d~YQioKL+iSj9_>3S4}sVEAkoeH=d=nML-rYqZQ3kHCJ zC<0&d$E+#cNq-ISJPGPU9@0mV}O|l0=i_v!gG{?@j4Vc8q{Fg zMlc49rAN}E8WD*a$;)9Tgm=Z2G@CgT-jmm_3Cu--JC7bmPobyNl}xfQI!FnY(34Pu z9>U?MyoR2N$_IrRsJxl3L**dRNR1My8a(V@_8Jn(0<@f22 z=u60}SBjbdyI&*t72Kc~(M!-Ao`}F4ztIdDYixuOm0!?HQF&v84X*%+%)JPm7?~I) zkl_B_(!!AyzKLDSYpWVrY-Haw$y=ZY^SNYNd!$I?fSsw9Cgas~6LYdCjM*sVqW7Ok zh3tTr3}R(829(JBAYB*~7}LSZWm58lWFyOoY6GP3dx zAa8P2RHO7!P+f!WlL~8nBN|yZG5i1XuAO9*LXJolD4dzq^j$i&PqDAEwpX=p0&>Mb zD2^Up-S+784H@Up8S5+W*Pib^qbhuTyI1?G+TJo;rJSoqbf+*S60rw;8rjFDoSg?| z&@~Ns7=4{iZF5Lm2>LbRB3w-WO5gg&_i!mL!-P%X9k2i_q{q_}=!s3Z98=;7`UaxD z$(U7CB~^t}cUO!r`pDWjPM zs>CoQo`>h-1$ZG|guBri;-T5C$5r`2EQUY^K`I2xA=n1N706-*GIcxy6p`$lni^ki zE?N|ZkMg(nj8y=pCN_#GiVbE~$LT>8^LbW3=1D<#ADqVLqBR1C_ze7Yd?s3%X5q8( zIS>Rv&o~ArNrZqlIf8{uVwTe;a=XUw|)!fC~W+0zL$x5QIS>fItX= zh^Z*755wz$dNtj>U8}3w%1l^MiUMDQH@}jc@%4C10R96A!W&Rl@-x1o3@g4F-|}j+ zZTOBrv+WQ>G~gdYAVuk&wGesjB9@)C82^;U=+7XKl<_4cfcAS@8rAh1J_4M7eB4hV7~a6;gMAP<847Q%{lIBjW* z$Z1bo1bS7_k+&dlwmz{9bi@E9$#}H$PsR87%L=90(j3dSq6Nrh#Bw{i#1%hr6plyUfoY+E4 z`-gAFOTJkU^#7M{U9TxbW_^}=BY5;9aK=_{nXk(d;#>j{p#O-#pTLHA%|GaHO zdr15pK>Lk&MEp)XCY}&ai9d)xiD$%L5R8Xl0t6Ewm;}LO2#~?1LNE=2=@8Vl5YIc% zNW2}5M7fv#7i~rd+8eD~%zZ$C0Fja55WtsYq&Q%(d}J_mp$%M*wyVQnq>NOwQIRnL zs@IX+q$)r)bCFphAvI)T8x@%lpqlmX+kn&g$W$^5QIQ5RjWm)bGMzM&8Ki~GB&`sj zMPe=lZ$dB+g0~=;55e0IyaUlk4zRF=wEaVs`;sajf<^yQE$*ON)<#v=BS6&?0;Yqi z&;Mhum)0W7$RX`qlXWCbLI~c6U?l{rAXvSIe4U(0 zLK4Ma0|ZT|27>j>*vy)Y`2kj>tVVi$zns3~bTw|>m{L=RjEl%6Xw@MZ5(VWN2$~zo zrQ|XQ)dz?}~L=k--bgik=p5FHgx2`LdJrXnZ_C8Z*%C@LC)BM=;g;1~pG zYjFYsG&tH=pn*}w`nH8a`^vx~MX4xtI~}Eg;A}e`1m_{R(1CZUE!~AtCd5ajLvZdT zA7x<%IHKZnC zYNwDH>>OvD6aYs_^;dfR8EP8K%F4vJ)O4zjqAA$7#y1>&nL@{N2yQ`e6M`EM+<#RJ zDKp*0m%c&G39!tfWnX>pkY=PYU=wTR*6$%C3GhFXk{K)gH~ zzLZ+Vbjyp$c#lFM*i6+^qmbmQC=}_Bkl1bVBd2@ZOf^zX)EcUpLQ(t#f~OGt0a+Mi z0p`QJ&*C=*7*GWM`Co>uR4cWOLK)?62>ycL8Il|+YP@t8h1SqlMBPK7$aoIH3r10p zT=)g`M(T_WjX2yWZ@0eF~}lcHP`7jv#65_@6==J38sWBLBKU3D;%eGt{JIY=B7 z5hMwc21P=a46^kfle5P;{ID@(n>HL1^!|39^zQD}_!SipkY1Oy2gDGN=b; zZf){b2mz1jHdQ|Bw5{D; zpS5oFSAe>+=o?I>^=z*+A(9Yv zNL+{}Bq2l>k{n_Qv4+?}vO^pp&XBy2>bj8nkli82L+*wA#R=wwaJU>kCyW!$5pg0o z@fgD%Z$O=UTW{u8rH3JCHk=JA~`w z`nlEIDcm{SgrjeD0z@Pc`DVqOd{mS^CZ zcxGNEFNddAoU^^7iod@{aJ%@h6 zf1Up;|0e%QC=p79b_(qh$`0j(@>3qk~30bdX%2p5P15dx_oO<)q31r~u-U=w5u90I2xPf#Ez5)=!( zf=a<;!D7KC!6Csf;bgcVJU%=tyjys6_}K88@NwbO!{>)D4zCYy4qqF-K732~j_{q~ zUxj}ieklBCUHI|vli|OIKM#K)1VWB5Oc*W{2_uEkLb*^Ovd{PUThE>#p&VBi@YI9An zk0KsRa7h;lTf&j>BqB+KL@J4r$Rrv`s>CkIlN3mbB*l_aNl!^{Nnc5S$q31K$vnw? z$qLCv$>)+UBu6CYC6^^PB#$LeBu^!OO1V;nR4;W(2TO-YE2NduD(Nt3y|h`nLAp`8 zS-MraP5Qa?i1d{7jP$Iu?!5FT={4!y$dJg;NPVO&GCR@{>5S|ZIW*E2>5r_AoDw-R za&F|($i~PGksBj7M{bQ0N5w>?M_Hq~NA-`I6*VtvN7UY^{ZU^=9f&#|busEv)Rm|o zqOM2Xh`JSZC+c3*vuG+hGCCimY@%$kY^rRoY@Tes>>XLXY^7|q ztWmZ}wnf$|+b%mOJ0m+QJ1@H^yDqyYdnWr^_CgNiUF1A@xI95_mV4wS@^11{c~5z7 zd0%;d`9OJ@yj)&4UOrJiSw2-hT~5nq$Y;uD$>+%5l)okaK>oG-b_^#bHKs?*Aigy+3>J;Y`KPa9k{!)f1Bb8~&3}rXv zAf;bft$a;6LODt~Q8`&TRXJTrD`zO@DVvlVl{=MRDUT^nC{HQRD94hUOiDgSv^fnt6x{oQqNVtrG7`fP`zILmHL$WMr>qkQmi|6VC=-$ zH)5B@z8kwDwmx=c?CRL2*tM}A#C{UHCw5<5>=&^IV!w$!5_>%MRP3497je=!SKN%a z?Qz%QW$}gaz2f`E4~Q>|9}-^?KPJ8=eq8*-_@CnMYk-E(1ZlcxLNq*0m`13H&}cO} zO^U{#F=;Y1R*hZb&^R@NG{ZF0H1BHGYIbQ3Yc6Ph)cmBmrn#%Rr+J{|YbDw^ZC$(; z{eMtv)@EvL+8k}J)}`&O9jBe4eO)_KJ4?%Gmui=5>$R)24cd*`&DyQn?FnQ;Xo4s~ zk`R@kNKhrjC1?^{38e`G63Pz?ZVOv00hBq}L7DIv*}l#ygjvL`u` zTuEJ%N|Sme^-UU(RF+hpG%{&)(%7VNNxvtvl8Iz0xpVRx$%~Q~CofH|Tb>e;5}OjA zqD|4IY)sjmvL|J4%6`2=U!t$j`}M>0BlM&7HTv=TN&2b!I{gg&GW`mDy?&*BwZ2i` ztY4@9K>wkBqkgmgsQz{;FEur_SL(#nm8ttvuNp8zX9L^7HSi4*gVGRdh&Ln{bcSSu z-e5E28eE0~gU3)(XXtL|VHj$tG}IU-8YUa28DL`kj^V!Hq2ZC?vEixVnc;84i!_i%q)}fpLj(nQ?`2m9ehDxW>5FxZSwZ_?7XH@rd!b z@r?1T@q+O?<4xle6Whc!g_^=mVw2PqZHh6eOmQZy$!>C(oTfZefvL#kF_oCQnMzH) zOnpq_Op8rBO&3ki)8*-T>BG|Jrms)mlD;kdjIq>M##^RXrdepq z49h~xN=vI{hh>-LQ_EhAnRajwRNm@oOObAvURF;x^=wgI*>TZPSU z8)h4Bn`L|7)?)k6w!ya1w%bsuM z_RjWTyU?z%o9*50lkAMW-oDPh+1_g3Vc%*0%6`Cp(Eg46l>K}A&-Ux~8}_^Q2lhwy z$Jux`nH`kfC3`~l%~q-{ zvoB>|$^Iexr|fGvtelt}SB^htUd~53r*ob-gbt&_=E!k49eIutM;}Lj#~??!qry?? zsB#Q*OmIwfOm{%XEXN$jJjZ-Ty=Dzk2sGxzjOZJ{Ly*MdDD5vdC&R4 zg}FMry13Y`2v@XA?oztqT?sCoE7@gpxm{gdrLJDC{;olFu5#B<*J#&RSFLM;>#pmW z>u=YKJXW4RuQqQ=-t;_}H#2Wu-rIQ#^A_hV&HF0vcD^)Ul^>U{%}>nF%FoX)%J<}V z%`eSAlz%$^hx}{#zvkb{zn}kG{^R_o`7a8nf{22|0%JkXf&m3n3lD_vh{}-3Q$l-Iv|pyRW)`c3*ejbl-8`cRzGLaz8KbT&yTI z78e(n7mqJ~yLer3Yw@n)E5+A}e<^-Y{M}3D0TISK)|udq;X{?|koK?=tTS z@B7|0-gVvKE-ksi0y!*UIy=T1VycfNfy+3=edvAL0c<*_idY_eqlt@ZqN>n9r zCE5~wNm@yIiKWC^l3P;j+vMBg`^}@-$9K>7(D%FViSG~J zvr2iTt8!H3^2)}_mde)39hG}44^$qm{JHX%%6pX$D<4%p_Tzra-^m~B=lFU42!EtM z+AsHO{91pa-{>#$7yC>6-TgiNef<6XgZ$Hi} zS}#B?;w|2H>rub8)_T=iuUdQ8YSq@(+SdODq{s7n{_xCZcFyCs0d+`!Fc4INp>zg# z5h}u?a22k@lkj9bi?*@!f$8`Rd>+0CUyQHBSK)8t>+pB*E%;V^C%y|mj32>|;>Ym! z@YDDi{A2tgehI&fU%@}cZ{XkIKjIJY$M_TcSNu2p5ByI85G+DU$cQLHPACW^5lyIw z7(z{Ghy)^)NFz){Hen|0L;*2?7)T5v1{0OUkN{CdR1-sq8p1~mBSsMuiAlt3#5Cd! z;%#Czv4&VntRvPFjYJdCOl%-F5}S#g#4h3>afmofoFq;WH;G%sZQ>5`IdPZxg1AR~ zNqj|oO?*#0BAyYy6VD+I2^af(u>R~nvm|+eqfQ8Try|6nR z00+WBa4@WdLtzaZ0bhk9;TSj`&VV!FEI1p^fpg(JI3F&6OW<<20lgJFxL}rt@q?Ifs9i)?Vk=@A(vNzd>tR#n!Rb(|el&m91kgt-H$=As# z<1J6AscNX|&L?u(z9kERl+VL>eRlA|;}f)rx}@ZktXykek<0BVE2rIE z7PB%k={sGd{6+)SrP0XD!nN!tI59z2)7jhx>*$TrMur zmpYvE)2?|ilo5){2#}$(<$Jpi_Llb4-D17K0gmGkSK=l-7kA^`@m_cpUW2v&u|UT!*6Zc( z%O89!#l3`OW6+G{U>5osTLW{jT+F(J6Io=#99UutmXF!70;~`#!iupHtd!zY;go=i zpoEl&5>t^am=<$kZp?#uvF=R12V)T_HJo~t8bytvB=jK8PSCY2G?WK~YHTv5X8xyQ z0V;t}ZP1XMw)C*Q0MEwE^!ME8DC>We6O_!1wPIVFn1_{)o0OHxr&6c_s)&Bdo7L+w7K(*2UT`0KfQcHhFQ~jm>>*{R44oB^ zu_svK`t?+LXTekKx4&CHqYD3N`3LrAM@zbgFoMX;o-&30o*yp-T|qa%#dR4fLc7^w#lfxBS9_!@sGnftLfVtE-YBDvIY51u~5uydls7XwNKSd*${08cECO=A?&E$7d0VcmwJW{J= z*va0WMlZ^3ue5^5PU$5Tmg zjwck&j3tvAnfwcCIg{@rwcu~nR@G3~#Nsffxh0(k zD(K;|DIKmYiw9QvflSH2%u=&77?+g_N?7sKMyigsM``d1syS$EPSDuZtn8+sj_BD@ zi`zTP=N-l!!TBZBQ4b1yoxuMJ8MwW}(&WL(OJ}US!h}uKdwpkUifSJ2SnG;b* zHFr)p3$U394^tI%o3boCDEA81TNU2&ik|K!)(U1iVRTunHn=`nTUc9J+ZexAvvyMF z7&S50pe|4!Q&$;vai~w)w(I)<`mXA?cIj@$C>d!!j}gC=E>a!Myho`c=^pB&oI2`T zD!s4U=Wpme%r~K5&tbK7?YaIPA<D`;&L#!30L4s z>U-)(s`lmj!(;F`OwD;FF)ia~KQNs4DZ}5Qs%;C_x5voNt|@r> z|LJPLv|Vr`^?>R7kbbC<)7h~}0xe#Qm*Azi19##sCJuWjN!!HO5+Lk?02hH61ZE?! z0fCPYc+9LTWkO=xi?|rToP`weQJnm-V|@(;%!W0hzLs_*=;?2rW)4o_(-=q`ArL~R zxDs{vOni2*U={*h8}T`a(MI}Z)?&u5may_zOYwyiMjb<-TP4p!KdQ^757r7*9nlb9 zf-l1on{XOmiU0?J&?eR;dJRy@shY70Kfy$sYnFnJ{qEpn}#NH0RJwd*zps4$UeAV>tSy7TB#PJT0 zV?mIbf7P~I?KJUm5aSH-K5>@#AMpWkj`)x`PkcmNK){axqvbjTh9NK zj6|TmmALpf*(Wc_K0{#CKV)M%$;P#lm3|W>`xb%Gon$`*tyaQVjY-ULr=mUmZMDb5 zFCA1r2dTye<^DBD)j+$=QYrBV@uCCkuOQa=e^pavNezTNh6<7}1a^U4VK>Nz92g42 zAhQijL|_sEuOToQf!7h3g1}S+0tiqDpjOE5q=KRjDkw$F7WZ;1M&Qj(s+kN`AK-%V z3>Hj4;Ek79Q1|~a*h_Q4bZBe`gBd}v>5SwsD+o4&UT2AtSYTc|7qkYsX8o&{;Yuum zCCugnne{WL5i;v%-hUz-^mGKHxs|+8%;wXN9+j(1ErS(U;s)3Q_JrlI7XtGUSb)Gn z1Qu<8ymps;$Ji0uy6Tk=Sm~9FA zA>$m(R<^7O4ugz)EJt7kJ=~g8Qx8XB|32Kqv9RI41ry*@EO8T@2q(eU;AHqZWQ@Qp zi&Y4`jlgOI)*!HU6AVBKQ^RTS4frMk>kwE^F}r3X0?cmMOn1%OFl6CBVs^?#!=)Wl zsXK35oi%U4w}VozWQ|5(BXzV1u7=Ds%t|}_UmZ4e=x|ddZ+N>7{WeL>Mz}NRPMhF6 zumx_0t#Aw63b(=Sa0db{2y8~66@e`XY(-!j0^1Q_%6B5Ls}=70+nx5kbf-fI?Ec4{ z_I0|`!FG4*0pAZYokgIn)1A&S?gT%C=Yt7>JqYaWcrpy&PPISi zHrInrb%360mr3Dmc(=o=J`Z};p?_5~Ugi4+J`Uo23%`Tk!yn*%_#=D(AHtvDBLt2h za1;UNL~|Sg<`D2M0`DPk5`j|)oNk3rI`H7H@VA$Ee;{zC0}lZP#I}a%h@_f)lMzI*0bM%jel6VzMwf^rz z5~(5M+tJ9lAlmuPmedvcgk6%6Or|kbCsW8&1TG+Ov5C}^Ol)Sxy7wQmlUW^R|G1LZ zz?j`ZuPf2f4~jT!OGo*oN?tWn-j^O!93GoT7BHtB(njW!%&v9?fvX67(o7bTMI^H_ z87b}}@CCiJc)rmc#PuNXDb;d?EMr+%IT)AhLG~oe$zJ`&`bIDxF_@1L2;4y6GX$<9 z@cBRUTV#F70YTyWk^K?4iNLKUav(Vff!he&p`VtRLYS)rSws5hfu+$Q&7_~KW$qFK zORe}YayY%FRAn7WGV5+5Sx?#-S;vrL5%`i3v|Zw_5cql{IgT7pP9P_eOg#Jsfo~D` z4uPK#ctoXF@{;L?#WGs#SgS)ph-nCX|8I!tb&jjRJay=9O zNG2FOX(X9Y@XLRVxT(WfKhyPfBL;Vzy2;JtZsuDG*-CC9x02h)?c@$}C%FrOrwIIt zz;6gVL*RD=o+H4x?w>7W8@Y$vOYS50lLyFyB(qxoLf{2rVTc6~i-lM?VnM_rX}fl{ zfIJQ2*1tTnl4slV^doIr40)dX=)ZzrWX@iU;Fl1KXaspw4PB6!-Hp6Xe%8^9?%{eZ zo;miBPspFiU&yEAub39GB7zGEv4n^vLM$<2MIsh+cW!&^S_8mfdXYy4YTLGYC=eY8 z=^j!RjFGHV#7d*mhhovOEG)YHQ(QvZ4R=UrxY*yxA00d+MXa20+@hBDtY`;%|C zYy1*)>q3WzjtCtYIw~{}ibCHAogO+fbav?6(D|VY zL)V5L4ZRZjTbMe`7*-SZdf4o+d0`8}7KhPc%fj9X+Y`1w>|ofDuw!8-!Y+l~3Hvte z`>^|A55j&5dlvRQ?9Z?lT)@S-1XsjWaxL5dME-01*ZEWV6n`3j4SzrXBL99kCp;`%79JC>36Bd;2u}^yha19; z;aTC`!`}$s9KJhzZ}|T3gW>1GuY?EghJPFWApCjwp8}yELy#}%FBm8oEEpoF7Sss* zf;z!)!3e=f!6?BP!Cb+7!9u}e0WDZ2SRq&`cw4YWuujk@XclY~928s>d?k1hA&oFb z^oST05s088S|WBu?2gzIaWvw+h;tDiMtl@;E#m8lA0vK=_*KXiMhTTdl~5B9#t9RI zZefM6x3I6!Cmb#uAsi_jD;y`BAeM;ApB7Hz3^Az@4`QXFGN6u zi^QU6QM^bi(utBqsUnN0KvX0u5jjL1R7r+Pe3Dwp zNXaP47)gUATV=(m!Ol49Y@eU1e-pq)aW-%5<`1 zSs+zrmRV#)vKrYi*(}*I*$UZ8+1s*hvO}^XvSYFnvP-fXvd?8d%AQ5xQ7|ebs%unz z)WoO-QFPSisNGSwqP~!a%0+UCTqc*x6XkljL2i_1$*ppm+%7Ma7t4Ff2g%3Er^^?} z7t3k+GWlBhLHS|%QTc88?+S$?Euio!sud#@^Armdixf)~OBKr%Zz)zORx5TX+7x>g z`xOTjhZUz3=M?7^7Zev2mldBYzEFIr_*(Hq@r&YD#WN+T?4sTKDp0vq6{Za

YnN=)iUKzSrpR}vp42g%!!!yVot@JiMbGSG3Ij2 z)tGBB*JHkjxgYbZnyZdd$ExGiTD4A{tj<)M)Mm9sZB^UUgVmGOYt{SJr_}GO|EK;? z{gL`(^_S{L>Yvq5)z8$=)qiS84O6hxRkK~QQ?pyMN3&0JKyyfQUUNZnQFBFe zP4k)NmgaNKJ&xs!wzdn9j{Lc8c_k?l{9GN&OadG1A#H)$76YnN|nfP_$!^EEwAM3j5#5#pe zrPJu*bt$?uok5qWGwJ&2Ue~SB?bRLAy{kK=JEOax`&f5LcSrZV?nm8Ex+l7)x@Sp5 zQb-`FTT*BeFG-LjOo~a;B*i6ZlQt*qOFEErDCuZ&c5-oYX|glflYBn;v*eq}x0COt zRHxLZj7}MwGCt*LDxMmdDoa(Qs!}zn@u`Wa$*F0nhSbbdSE@I)EVXB9uhibD{Za>{ z4oa;|txl~;MXBpkPo;jH)-5e5t!LW!v}I|5J!wbNPNbboJDv7X+O@RL(r%{RNxPeN zFYT+e$7#Q&JxlvT5A?X6)OXQ~^-_JjK1rXV*XuL&CVh@RSMS#M(~s1T);H)U=wH)M z(Np?2^fUCc_4D+N`VIO``WAhweye_mewV&YzgK@ie@K5#|1@2iZb=`KJ}tc^{lh@| z4~A|Afk9-D7-R;GA=!{>&>IW}qan*sXmA)@2Ct#qP+{n6=x-Qi7-5)Xm};O5Zy077 z<{0K178=$Xwir$rP8!Y_{%82maKUiNaMkds;fCS1;fdj?;WxwYhCd8{Wq=Gk17?I| zbj#pmXfsMPMr16=*qw1D<9A~~VazoSH2REn##fB58pj(0#%abkjWdn2jdP9jjjN37 zjE%+(#?8hp#_h(P#^c8Kj2DbojGq{<8*dvwH{LUTW&Al4WQsB+nNgX_%$UsB%!Eu` zW=f_$Gb7WH>CW_KmSy(L?3LLkvtQv`6nCd|Y#@l66#v?LlN)YDXJ8g6>kG|DvAG~P7H^tvfvnr51AT4h>eT4!oBHJdh?T1>5`t)}g! zU8XkE$EJtbf^1`UzwE%|?8fZV*oNse}2;@A-`6=hQrHduh!m|i0 zkrtUnVTrbwEj=uQEtQrbmTF7AWt3&KWvqp=%(pDE(3a(vm6p|(b(XD`9hTjeJ(m5J zLzbhKw&q`&#bg+$p&;a_QW+a^KEfliQTL zF}EeRHFtk*;9&0I++$Xq)nxTp%dF+r-qwEBf!0cEwbf^>v%X@btZ!JSTW4BlTjyFA zSQlBBSeIH?SXWy2S+823<;n8$@~ZM?=55LQAn&`p$9ccx{g(H;4YzS@TwAzJXp6K- zZBaIbO=nB9rQ3`)v&~}5v*p{$Y~{8gHlM9FU>k0$w~e+n*v8wY+m_hgv9;Q^*>>96 zZ2N2nZAWa!ZSUDm+di}1vfZ)WwcWFQZTrslgY8G#L)&B9&-wiPwESNAtl}3xoyY0!=|&K|+D9ps8R- z!LEX~f_;Uu!i2)q!t_F8p{X#h&|X+nSX$^ToK(2J@Iv9Ig*OUs7k*y&sPK8=UqzsZ zC<-Z>S~Rn0Y0=7})kW)yHWa;6)LOJPP}Ej*sOWsr=SANYcP-`>rxh0$mlyXct|;zP zJiK^9@wDRU#j}d%7B47XT)et?U2#+K#^RRZEydf4Z5NjkbXn<&(p3)V2zRI*aSp8`$&u2daV4ss504t4sR zwa!V-70wOLjm}NZ7Uy2)fq?U{^O*Bp=Sk;9=RN07F5E@By1F|) z%5Y`6Os*W4)s^ombd|UqE|;sqHN-W^HP6-TI^a6*`pWf}yPKQmHo7hDTz9Fv!adMk z>8^JB+#}re?lJCh?g{Qz_i6WS_c!jx?qA%$xu3iL@&Hc&_wYPokJKagM0?^riJoLn znkU^ez%$#k*K^Es+H=kGrRN9F1J5JR&z|R=zr4Upcu6nU%lC@C8n4cq;?;XIyg6R0 zH{V<6E%tWz_Vm_xM|&rFCwr%Qk$0ANu6KcVv6uF)@;3WOUl*Uy7vqcdCHQo{6rVoe z%kbs<3Vp>shtKWv`pSGgeG`1EeP?_>`JVZKzneeQFY?R%G5#EXuD{st@VopTe}DfV zf2F_L@AKFCNBPJ4$N4AtDL?YR;h*Pk^f&uA`8WHw`gizu`}g_}_z(M!`A_)I`!Dz} z`mfY#YW1~-T4SxLc5UsJTJg5p9ksh_kJr9id$RUSoxaXmXREW<710MKDp++#{~x~D B;>G{~ diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 32f9a18..b2f6262 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -757,7 +757,7 @@ pub mod avfoundation { Err(why) => { return Err(AVFError::ReadFrame(format!( "Failed to read frame from pipe: {}", - why.to_string() + why ))) } }, @@ -861,8 +861,7 @@ pub mod avfoundation { msg_send![value, videoSupportedFrameRateRanges] }) .into_iter() - .map(|v| [v.min(), v.max()]) - .flatten() + .flat_map(|v| [v.min(), v.max()]) .collect::>(); fps_list.sort_by(|n, m| n.partial_cmp(m).unwrap_or(Ordering::Equal)); fps_list.dedup(); From 74dfa096060da03200281f96b050d04448c6df09 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 28 Feb 2022 22:57:38 +0900 Subject: [PATCH 29/89] add GRAY8 to AVFourCC, bump macos to 0.1.2 --- nokhwa-bindings-macos/Cargo.toml | 2 +- nokhwa-bindings-macos/src/lib.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nokhwa-bindings-macos/Cargo.toml b/nokhwa-bindings-macos/Cargo.toml index 018f745..a5a026a 100644 --- a/nokhwa-bindings-macos/Cargo.toml +++ b/nokhwa-bindings-macos/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa-bindings-macos" -version = "0.1.1" +version = "0.1.2" edition = "2018" authors = ["l1npengtul"] license = "Apache-2.0" diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index b2f6262..7668e6e 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -242,9 +242,9 @@ pub mod avfoundation { use block::ConcreteBlock; use cocoa_foundation::foundation::{NSArray, NSInteger, NSString, NSUInteger}; use core_media_sys::{ - kCMPixelFormat_422YpCbCr8_yuvs, kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, - kCMVideoCodecType_JPEG_OpenDML, CMFormatDescriptionGetMediaSubType, CMSampleBufferRef, - CMVideoDimensions, + kCMPixelFormat_422YpCbCr8_yuvs, kCMPixelFormat_8IndexedGray_WhiteIsZero, + kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, kCMVideoCodecType_JPEG_OpenDML, + CMFormatDescriptionGetMediaSubType, CMSampleBufferRef, CMVideoDimensions, }; use dashmap::DashMap; use flume::{Receiver, Sender}; @@ -681,6 +681,7 @@ pub mod avfoundation { pub enum AVFourCC { YUV2, MJPEG, + GRAY8, } // Localized Name @@ -874,6 +875,7 @@ pub mod avfoundation { let fourcc = match fcc_raw { kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => AVFourCC::YUV2, kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => AVFourCC::MJPEG, + kCMPixelFormat_8IndexedGray_WhiteIsZero => AVFourCC::GRAY8, _ => { return Err(AVFError::InvalidValue { found: fcc_raw.to_string(), @@ -1109,7 +1111,7 @@ pub mod avfoundation { Ok(avf) => avf.into_raw(), Err(_) => { // should not happen - return Err(AVFError::StreamOpen("String contains null? This is a bug, please report it https://github.com/l1npengtul/nokhwa".to_string())); + return Err(AVFError::StreamOpen("String contains null? This is a bug, please report it: https://github.com/l1npengtul/nokhwa".to_string())); } }; let queue = dispatch_queue_create(avf_queue_str, NSObject(std::ptr::null_mut())); From a663010bb33d21402490f553df9570fa5199dc7c Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sat, 7 May 2022 03:23:54 +0900 Subject: [PATCH 30/89] 0.10 preliminary work --- Cargo.toml | 37 ++++---- Jenkinsfile | 127 ---------------------------- nokhwa-bindings-macos/src/lib.rs | 1 + src/backends/capture/gst_backend.rs | 2 +- src/backends/capture/v4l2.rs | 17 ++++ src/buffer.rs | 51 +++++++++++ src/camera.rs | 29 ++++--- src/camera_traits.rs | 4 + src/error.rs | 2 + src/lib.rs | 6 +- src/pixel_format.rs | 57 +++++++++++++ src/utils.rs | 16 ++++ 12 files changed, 185 insertions(+), 164 deletions(-) delete mode 100644 Jenkinsfile create mode 100644 src/buffer.rs create mode 100644 src/pixel_format.rs diff --git a/Cargo.toml b/Cargo.toml index aca6e22..900a55e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ repository = "https://github.com/l1npengtul/nokhwa" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows"] +exclude = ["examples/threaded-capture", "examples/capture"] + [lib] crate-type = ["cdylib", "rlib"] @@ -28,7 +32,7 @@ input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "was output-wgpu = ["wgpu"] output-wasm = ["input-jscam"] output-threaded = ["parking_lot"] -small-wasm = ["wee_alloc"] +small-wasm = [] docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"] docs-features = [] @@ -37,35 +41,29 @@ test-fail-warning = [] [dependencies] thiserror = "1.0" paste = "1.0" +anymap = "1.0.0-beta.2" +enum_dispatch = "0.3" [dependencies.flume] version = "0.10" optional = true [dependencies.image] -version = "0.23" +version = "0.24" default-features = false -[target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] +[dependencies.mozjpeg] version = "0.9" optional = true -[target.'cfg(target_os = "linux")'.dependencies.v4l] +[dependencies.v4l] version = "0.12" optional = true -[target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit] +[dependencies.v4l2-sys-mit] version = "0.2" optional = true -[dependencies.ouroboros] -version = "0.14" -optional = true - -# [dependencies.uvc] -# version = "0.2" -# optional = true - [dependencies.usb_enumeration] version = "0.1.2" optional = true @@ -75,17 +73,17 @@ version = "^0.12" optional = true [dependencies.opencv] -version = "0.62" +version = "0.63" features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3" +version = "0.3.4" path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] -version = "0.1" +version = "0.1.1" path = "nokhwa-bindings-macos" optional = true @@ -111,7 +109,6 @@ optional = true [dependencies.web-sys] version = "0.3" -# why features = [ "console", "CanvasRenderingContext2d", @@ -146,12 +143,8 @@ optional = true version = "0.9" optional = true -[dependencies.wee_alloc] -version = "0.4" -optional = true - [dependencies.parking_lot] -version = "0.11" +version = "0.12" optional = true [dependencies.lazy_static] diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index ac1bcf0..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,127 +0,0 @@ -pipeline { - agent { - node { - label 'ci_linux' - } - - } - stages { - stage('Sanity Check') { - steps { - echo '$BUILD_TAG' - scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*') - } - } - - stage('Cargo RustFMT') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update stable' - sh 'cargo fmt --all -- --check' - } - } - - stage('Build, Clippy') { - parallel { - stage('V4L2') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update stable' - sh 'cargo clippy --features "input-v4l, output-wgpu, test-fail-warning"' - } - } - - stage('Media Foundation') { - agent { - node { - label 'ci_windows' - } - - } - steps { - pwsh(script: 'rustup update stable', returnStatus: true) - pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true, returnStdout: true) - } - } - - stage('AVFoundation') { - steps { - sh 'echo TODO' - } - } - - stage('libUVC') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update stable' - sh 'cargo clippy --features "input-uvc, output-wgpu, test-fail-warning"' - } - } - - stage('OpenCV IPCamera') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update stable' - sh 'cargo +nightly clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"' - } - } - - stage('GStreamer') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update nightly' - sh 'cargo clippy --features "input-gst, output-wgpu, test-fail-warning"' - } - } - - stage('JSCamera/WASM') { - steps { - sh 'rustup update stable' - sh 'wasm-pack build --release -- --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' - sh 'cargo clippy --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' - } - } - - } - } - - stage('RustDOC') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update nightly' - sh 'cargo +nightly doc --features "docs-only, docs-nolink, docs-features, test-fail-warning" --no-deps --release' - } - } - - } -} \ No newline at end of file diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 7668e6e..7523df1 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -1303,6 +1303,7 @@ pub mod avfoundation { pub enum AVFourCC { YUV2, MJPEG, + GRAY8, } // Localized Name diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index bd7a18a..c8de249 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -155,7 +155,7 @@ impl GStreamerCaptureDevice { } } -impl CaptureBackendTrait for GStreamerCaptureDevice { +impl GStreamerCaptureDevice { fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::GStreamer } diff --git a/src/backends/capture/v4l2.rs b/src/backends/capture/v4l2.rs index ba699b9..ebf3142 100644 --- a/src/backends/capture/v4l2.rs +++ b/src/backends/capture/v4l2.rs @@ -35,6 +35,7 @@ use v4l::{ use std::any::Any; pub use v4l::control::{Control, Description, Flags}; +use v4l::video::Output; /// Generates a camera control from a device and a description of control /// # Error @@ -212,6 +213,7 @@ impl<'a> V4LCaptureDevice<'a> { let fourcc = match camera_format.format() { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; let new_param = Parameters::with_fps(camera_format.frame_rate()); @@ -284,6 +286,7 @@ impl<'a> V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new("GRAY"), }; match v4l::video::Capture::enum_framesizes(&self.device, format) { @@ -311,6 +314,7 @@ impl<'a> V4LCaptureDevice<'a> { } /// Get the inner device (immutable) for e.g. Controls + /// apps bloodtests contact css images index index.html injectionsupplies transfem transmasc #[allow(clippy::must_use_candidate)] pub fn inner_device(&self) -> &Device { &self.device @@ -320,6 +324,18 @@ impl<'a> V4LCaptureDevice<'a> { pub fn inner_device_mut(&mut self) -> &mut Device { &mut self.device } + + /// Force refreshes the inner [`CameraFormat`] state. + pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> { + match (self.device.params(), self.device.format()) { + (Ok(params), Ok(format)) => { + let new_format = CameraFormat::new(params.capabilities.) + } + (_, _) => { + return Err(NokhwaError::GetPropertyError { property: "parameters".to_string(), error: why.to_string() }) + } + } + } } impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { @@ -409,6 +425,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => {} }; let mut res_map = HashMap::new(); for res in resolutions { diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..4c6eeba --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,51 @@ +/* + * Copyright 2022 l1npengtul / 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::pixel_format::{PixelFormat, PixelFormats}; +use crate::{FrameFormat, NokhwaError, Resolution}; +use image::ImageBuffer; + +#[derive(Clone, Debug, Default, Hash, PartialOrd, PartialEq)] +#[cfg_attr("serde", Serialize, Deserialize)] +pub struct Buffer { + resolution: Resolution, + buffer: Vec, + +} + +impl Buffer { + pub fn new(res: Resolution, buf: Vec) -> Self { + Self { + resolution: res, + buffer: buf, + } + } + + pub fn to_image_with_custom_format(self) -> Result>, NokhwaError> + where + I: PixelFormat, + { + ImageBuffer::from_raw( + self.resolution.width_x, + self.resolution.height_y, + self.buffer, + ).ok_or(NokhwaError::ProcessFrameError { + src: , + destination: "".to_string(), + error: "".to_string() + }) + } +} diff --git a/src/camera.rs b/src/camera.rs index 92378ce..6033440 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -29,14 +29,19 @@ 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 { +pub struct Camera +where + C: CaptureBackendTrait, +{ idx: usize, - backend: Box, + backend: C, backend_api: CaptureAPIBackend, } -#[allow(clippy::nonminimal_bool)] -impl Camera { +impl Camera +where + C: CaptureBackendTrait, +{ /// 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). @@ -386,6 +391,7 @@ impl Camera { FrameFormat::YUYV => { crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? } + FrameFormat::GRAY8 => {} }; Ok(()) @@ -459,7 +465,10 @@ impl Camera { } } -impl Drop for Camera { +impl Drop for Camera +where + C: CaptureBackendTrait, +{ fn drop(&mut self) { self.stop_stream().unwrap(); } @@ -496,7 +505,7 @@ macro_rules! cap_impl_fn { $( paste::paste! { #[cfg ($cfg) ] - fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option> { use crate::backends::capture::$backend; match <$backend>::$init_fn(idx, setting) { Ok(cap) => Some(Ok(Box::new(cap))), @@ -504,7 +513,7 @@ macro_rules! cap_impl_fn { } } #[cfg(not( $cfg ))] - fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option> { None } } @@ -603,11 +612,11 @@ cap_impl_fn! { (AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation) } -fn init_camera( +fn init_camera( index: usize, format: Option, backend: CaptureAPIBackend, -) -> Result, NokhwaError> { +) -> Result { let camera_backend = cap_impl_matches! { backend, index, format, ("input-v4l", Video4Linux, init_v4l), @@ -621,4 +630,4 @@ fn init_camera( } #[cfg(feature = "output-threaded")] -unsafe impl Send for Camera {} +unsafe impl Send for Camera where C: CaptureBackendTrait {} diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 01ac17a..749a4fe 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -21,6 +21,7 @@ use crate::{ }; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; +use crate::pixel_format::PixelFormat; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -37,6 +38,9 @@ use wgpu::{ /// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::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()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera. pub trait CaptureBackendTrait { + /// Initializes the camera. You must call this before any other function. + fn init(&mut self) -> Result; + /// Returns the current backend used. fn backend(&self) -> CaptureAPIBackend; diff --git a/src/error.rs b/src/error.rs index 7f776ff..7fb8fb3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,8 @@ use thiserror::Error; #[allow(clippy::module_name_repetitions)] #[derive(Error, Debug, Clone)] pub enum NokhwaError { + #[error("Unitialized Camera. Call `init()` first!")] + UnitializedError, #[error("Could not initialize {backend}: {error}")] InitializeError { backend: CaptureAPIBackend, diff --git a/src/lib.rs b/src/lib.rs index 4773890..b5d70ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,12 +25,9 @@ //! //! Please read the README for more. -#[cfg(feature = "small-wasm")] -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - /// Raw access to each of Nokhwa's backends. pub mod backends; +pub mod buffer; mod camera; mod camera_traits; mod error; @@ -43,6 +40,7 @@ pub mod js_camera; #[cfg(feature = "input-ipcam")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))] pub mod network_camera; +pub mod pixel_format; mod query; /// A camera that runs in a different thread and can call your code based on callbacks. #[cfg(feature = "output-threaded")] diff --git a/src/pixel_format.rs b/src/pixel_format.rs new file mode 100644 index 0000000..d57bb36 --- /dev/null +++ b/src/pixel_format.rs @@ -0,0 +1,57 @@ +/* + * Copyright 2022 l1npengtul / The Nokhwa Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::buffer_output::{BufferOutput, GrayU8, RgbU8}; +use image::{Luma, Pixel, Rgb}; +use std::fmt::Debug; +use std::hash::Hash; + +pub trait PixelFormat: Copy + Clone + Debug + Default + Hash + Send + Sync { + type Output: Pixel; + + const CODE: &'static str; + + fn code(&self) -> &'static str { + CODE + } +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Mjpeg; + +impl PixelFormat for Mjpeg { + type Output = Rgb; + + const CODE: &'static str = "MJPG"; +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Yuyv; + +impl PixelFormat for Yuyv { + type Output = Rgb; + + const CODE: &'static str = "YUYV"; +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Gray; + +impl PixelFormat for Gray { + type Output = Luma; + + const CODE: &'static str = "GRAY"; +} diff --git a/src/utils.rs b/src/utils.rs index 4641e73..41623cb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -49,6 +49,7 @@ use nokhwa_bindings_windows::{ use uvc::StreamFormat; #[cfg(all(feature = "input-v4l", target_os = "linux"))] use v4l::{control::Description, Format, FourCC}; +use crate::pixel_format::PixelFormat; /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. /// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) @@ -59,6 +60,7 @@ use v4l::{control::Description, Format, FourCC}; pub enum FrameFormat { MJPEG, YUYV, + GRAY8, } impl Display for FrameFormat { @@ -70,10 +72,19 @@ impl Display for FrameFormat { FrameFormat::YUYV => { write!(f, "YUYV") } + FrameFormat::GRAY8 => { + write!(f, "GRAY8") + } } } } +impl

From

for FrameFormat where P: PixelFormat { + fn from(px: P) -> Self { + match P:: + } +} + #[cfg(feature = "input-uvc")] impl From for uvc::FrameFormat { fn from(ff: FrameFormat) -> Self { @@ -93,6 +104,7 @@ impl From for FrameFormat { match mf_ff { MFFrameFormat::MJPEG => FrameFormat::MJPEG, MFFrameFormat::YUYV => FrameFormat::YUYV, + MFFrameFormat::GRAY8 => FrameFormat::GRAY8, } } } @@ -106,6 +118,7 @@ impl From for MFFrameFormat { match ff { FrameFormat::MJPEG => MFFrameFormat::MJPEG, FrameFormat::YUYV => MFFrameFormat::YUYV, + FrameFormat::GRAY8 => MFFrameFormat::GRAY8, //FIXME } } } @@ -126,6 +139,7 @@ impl From for FrameFormat { match av_fcc { AVFourCC::YUV2 => FrameFormat::YUYV, AVFourCC::MJPEG => FrameFormat::MJPEG, + AVFourCC::GRAY8 => FrameFormat::GRAY8, } } } @@ -146,6 +160,7 @@ impl From for AVFourCC { match ff { FrameFormat::MJPEG => AVFourCC::MJPEG, FrameFormat::YUYV => AVFourCC::YUV2, + FrameFormat::GRAY8 => AVFourCC::GRAY8, } } } @@ -419,6 +434,7 @@ impl From for Format { let pxfmt = match cam_fmt.format() { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new(b"GREY"), }; Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt) From d2ddcc254654f75484643f3763fa439e507bd870 Mon Sep 17 00:00:00 2001 From: Starccy <452276725@qq.com> Date: Wed, 11 May 2022 13:49:05 +0800 Subject: [PATCH 31/89] preallocated elements in buffer --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 4641e73..6840652 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1216,7 +1216,7 @@ pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { // yuyv yields 2 3-byte pixels per yuyv chunk let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); - let mut dest = Vec::with_capacity(rgb_buf_size); + let mut dest = vec![0; rgb_buf_size]; buf_yuyv422_to_rgb(data, &mut dest, rgba)?; Ok(dest) From 878ee012ed7b567ca7abf113e6898eaee44e2e1c Mon Sep 17 00:00:00 2001 From: Starccy <452276725@qq.com> Date: Wed, 11 May 2022 13:49:05 +0800 Subject: [PATCH 32/89] preallocated elements in buffer --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 41623cb..71e677f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1232,7 +1232,7 @@ pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { // yuyv yields 2 3-byte pixels per yuyv chunk let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); - let mut dest = Vec::with_capacity(rgb_buf_size); + let mut dest = vec![0; rgb_buf_size]; buf_yuyv422_to_rgb(data, &mut dest, rgba)?; Ok(dest) From 090c2bf764cf542288ed6ddd6c987a5c0b37be5c Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Thu, 12 May 2022 02:29:30 +0900 Subject: [PATCH 33/89] buffer impl, fixes for some tings, etc --- Cargo.toml | 12 ++++- src/backends/capture/gst_backend.rs | 14 ++++++ src/backends/capture/v4l2.rs | 15 +++++-- src/buffer.rs | 68 ++++++++++++++++++++++++----- src/camera_traits.rs | 33 +++++++++----- src/pixel_format.rs | 34 +-------------- src/utils.rs | 53 ++++++++++++++-------- 7 files changed, 153 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 900a55e..b7d5111 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,13 +19,14 @@ crate-type = ["cdylib", "rlib"] [features] default = ["flume", "decoding"] +serialize = ["serde"] decoding = ["mozjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] # Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x # input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"] -input-opencv = ["opencv", "opencv/clang-runtime"] +input-opencv = ["opencv", "opencv/rgb", "rgb"] input-ipcam = ["input-opencv"] input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex", "parking_lot"] input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "wasm-rs-async-executor"] @@ -44,6 +45,10 @@ paste = "1.0" anymap = "1.0.0-beta.2" enum_dispatch = "0.3" +[dependencies.serde] +version = "1.0" +optional = true + [dependencies.flume] version = "0.10" optional = true @@ -74,7 +79,10 @@ optional = true [dependencies.opencv] version = "0.63" -features = ["clang-runtime"] +optional = true + +[dependencies.rgb] +version = "0.8" optional = true [dependencies.nokhwa-bindings-windows] diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index c8de249..60da317 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -374,6 +374,11 @@ impl GStreamerCaptureDevice { .insert(Resolution::new(width as u32, height as u32), fps_vec); } } + unsupported => { + return Err(NokhwaError::NotImplementedError(format!( + "Not supported frame format {unsupported:?}" + ))) + } } } } @@ -581,6 +586,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String { FrameFormat::YUYV => { format!("autovideosrc location=/dev/video{} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate()) } + _ => { + format!("unsupproted! if you see this, switch to something else!") + } } } @@ -593,6 +601,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String { FrameFormat::YUYV => { format!("v4l2src device=/dev/video{} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate()) } + _ => { + format!("unsupproted! if you see this, switch to something else!") + } } } @@ -605,6 +616,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String { FrameFormat::YUYV => { format!("ksvideosrc device_index={} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate()) } + _ => { + format!("unsupproted! if you see this, switch to something else!") + } } } diff --git a/src/backends/capture/v4l2.rs b/src/backends/capture/v4l2.rs index ebf3142..a5b9870 100644 --- a/src/backends/capture/v4l2.rs +++ b/src/backends/capture/v4l2.rs @@ -286,7 +286,7 @@ impl<'a> V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), - FrameFormat::GRAY8 => FourCC::new("GRAY"), + FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; match v4l::video::Capture::enum_framesizes(&self.device, format) { @@ -329,10 +329,19 @@ impl<'a> V4LCaptureDevice<'a> { pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> { match (self.device.params(), self.device.format()) { (Ok(params), Ok(format)) => { - let new_format = CameraFormat::new(params.capabilities.) + // FIXME: actually handle the fractions?????? + self.camera_format = CameraFormat::new( + Resolution::new(format.width, format.height), + FrameFormat::from(format.fourcc), + params.interval.numerator, + ); + return Ok(()); } (_, _) => { - return Err(NokhwaError::GetPropertyError { property: "parameters".to_string(), error: why.to_string() }) + return Err(NokhwaError::GetPropertyError { + property: "parameters".to_string(), + error: why.to_string(), + }) } } } diff --git a/src/buffer.rs b/src/buffer.rs index 4c6eeba..3defad0 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -17,35 +17,83 @@ use crate::pixel_format::{PixelFormat, PixelFormats}; use crate::{FrameFormat, NokhwaError, Resolution}; use image::ImageBuffer; +#[cfg(feature = "input-opencv")] +use opencv::core::Mat; +#[cfg(feature = "input-opencv")] +use rgb::{FromSlice, RGB}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Hash, PartialOrd, PartialEq)] -#[cfg_attr("serde", Serialize, Deserialize)] +#[cfg_attr(feature = "serde", Serialize, Deserialize)] pub struct Buffer { resolution: Resolution, buffer: Vec, - + source_frame_format: FrameFormat, } impl Buffer { - pub fn new(res: Resolution, buf: Vec) -> Self { + pub fn new(res: Resolution, buf: Vec, source_frame_format: FrameFormat) -> Self { Self { resolution: res, buffer: buf, + source_frame_format, } } - - pub fn to_image_with_custom_format(self) -> Result>, NokhwaError> + + pub fn to_image_with_custom_format( + self, + ) -> Result>, NokhwaError> where - I: PixelFormat, + F: PixelFormat, { + if self.source_frame_format != F::CODE { + return Err(NokhwaError::ProcessFrameError { + src: self.source_frame_format, + destination: F::CODE.to_string(), + error: "Assertion failed, wrong source!".to_string(), + }); + } ImageBuffer::from_raw( self.resolution.width_x, self.resolution.height_y, self.buffer, - ).ok_or(NokhwaError::ProcessFrameError { - src: , - destination: "".to_string(), - error: "".to_string() + ) + .ok_or(NokhwaError::ProcessFrameError { + src: F::CODE, + destination: stringify!(I::Output).to_string(), + error: "Buffer too small".to_string(), }) } + + #[cfg(feature = "input-opencv")] + pub fn to_opencv_mat(self) -> Result { + Ok(match self.source_frame_format { + FrameFormat::MJPEG | FrameFormat::YUYV => Mat::from_slice_2d( + self.buffer + .as_rgb() + .chunks(self.resolution.height_y as usize) + .collect::<&[&[RGB]]>(), + ), + FrameFormat::GRAY8 => Mat::from_slice_2d( + self.buffer + .chunks(self.resolution.height_y as usize) + .collect::<&[&[u8]]>(), + ), + } + .map_err(|why| NokhwaError::ProcessFrameError { + src: self.source_frame_format, + destination: "OpenCV Mat".to_string(), + error: why.to_string(), + })?) + } + pub fn resolution(&self) -> Resolution { + self.resolution + } + pub fn buffer(&self) -> &[u8] { + &self.buffer + } + pub fn source_frame_format(&self) -> FrameFormat { + self.source_frame_format + } } diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 749a4fe..045d690 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -14,14 +14,15 @@ * limitations under the License. */ +use crate::buffer::Buffer; +use crate::pixel_format::PixelFormat; use crate::{ error::NokhwaError, utils::{CameraFormat, CameraInfo, FrameFormat, Resolution}, CameraControl, CaptureAPIBackend, KnownCameraControls, }; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; - -use crate::pixel_format::PixelFormat; +use opencv::imgproc::FloodFillFlags; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -48,7 +49,7 @@ pub trait CaptureBackendTrait { fn camera_info(&self) -> &CameraInfo; /// Gets the current [`CameraFormat`]. - fn camera_format(&self) -> CameraFormat; + fn camera_format(&self) -> Result; /// Will set the current [`CameraFormat`] /// This will reset the current stream if used while stream is opened. @@ -70,7 +71,7 @@ pub trait CaptureBackendTrait { fn compatible_fourcc(&mut self) -> Result, NokhwaError>; /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). - fn resolution(&self) -> Resolution; + fn resolution(&self) -> Result; /// Will set the current [`Resolution`] /// This will reset the current stream if used while stream is opened. @@ -79,7 +80,7 @@ pub trait CaptureBackendTrait { fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>; /// Gets the current camera framerate (See: [`CameraFormat`]). - fn frame_rate(&self) -> u32; + fn frame_rate(&self) -> Result; /// Will set the current framerate /// This will reset the current stream if used while stream is opened. @@ -88,7 +89,7 @@ pub trait CaptureBackendTrait { fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). - fn frame_format(&self) -> FrameFormat; + fn frame_format(&self) -> Result; /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. @@ -155,7 +156,16 @@ pub trait CaptureBackendTrait { /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, /// this will error. - fn frame(&mut self) -> Result, Vec>, NokhwaError>; + fn frame(&mut self) -> Result; + + /// Will get a frame from the camera as a Raw RGB image buffer. Depending on the backend, if you have not called [`open_stream()`](CaptureBackendTrait::open_stream()) before you called this, + /// it will either return an error. + /// # Errors + /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, + /// or if the PixelFormat is invalid, this will error. + fn frame_typed( + &mut self, + ) -> Result>, NokhwaError>; /// Will get a frame from the camera **without** any processing applied, meaning you will usually get a frame you need to decode yourself. /// # Errors @@ -174,11 +184,14 @@ pub trait CaptureBackendTrait { /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. - fn write_frame_to_buffer( + fn write_frame_to_buffer( &mut self, buffer: &mut [u8], - convert_rgba: bool, - ) -> Result { + write_alpha: bool, + ) -> Result + where + F: PixelFormat, + { let resolution = self.resolution(); let frame = self.frame_raw()?; if convert_rgba { diff --git a/src/pixel_format.rs b/src/pixel_format.rs index d57bb36..ac82364 100644 --- a/src/pixel_format.rs +++ b/src/pixel_format.rs @@ -15,6 +15,7 @@ */ use crate::buffer_output::{BufferOutput, GrayU8, RgbU8}; +use crate::FrameFormat; use image::{Luma, Pixel, Rgb}; use std::fmt::Debug; use std::hash::Hash; @@ -22,36 +23,5 @@ use std::hash::Hash; pub trait PixelFormat: Copy + Clone + Debug + Default + Hash + Send + Sync { type Output: Pixel; - const CODE: &'static str; - - fn code(&self) -> &'static str { - CODE - } -} - -#[derive(Copy, Clone, Debug, Default, Hash)] -pub struct Mjpeg; - -impl PixelFormat for Mjpeg { - type Output = Rgb; - - const CODE: &'static str = "MJPG"; -} - -#[derive(Copy, Clone, Debug, Default, Hash)] -pub struct Yuyv; - -impl PixelFormat for Yuyv { - type Output = Rgb; - - const CODE: &'static str = "YUYV"; -} - -#[derive(Copy, Clone, Debug, Default, Hash)] -pub struct Gray; - -impl PixelFormat for Gray { - type Output = Luma; - - const CODE: &'static str = "GRAY"; + const SUPPORTED_CODES: &'static [FrameFormat]; } diff --git a/src/utils.rs b/src/utils.rs index 71e677f..e6141ba 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,15 +14,8 @@ * limitations under the License. */ +use crate::pixel_format::PixelFormat; use crate::NokhwaError; -use std::{ - borrow::{Borrow, Cow}, - cmp::Ordering, - fmt::{Display, Formatter}, -}; -#[cfg(feature = "output-wasm")] -use wasm_bindgen::prelude::wasm_bindgen; - #[cfg(any( all( feature = "input-avfoundation", @@ -45,11 +38,20 @@ use nokhwa_bindings_windows::{ MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls, MediaFoundationDeviceDescriptor, }; +use serde::{Deserialize, Serialize}; +#[cfg(feature = serde)] +use serde::{Deserialize, Serialize}; +use std::{ + borrow::{Borrow, Cow}, + cmp::Ordering, + fmt::{Display, Formatter}, +}; #[cfg(feature = "input-uvc")] use uvc::StreamFormat; #[cfg(all(feature = "input-v4l", target_os = "linux"))] use v4l::{control::Description, Format, FourCC}; -use crate::pixel_format::PixelFormat; +#[cfg(feature = "output-wasm")] +use wasm_bindgen::prelude::wasm_bindgen; /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. /// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) @@ -57,6 +59,7 @@ use crate::pixel_format::PixelFormat; /// # JS-WASM /// This is exported as `FrameFormat` #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FrameFormat { MJPEG, YUYV, @@ -64,7 +67,7 @@ pub enum FrameFormat { } impl Display for FrameFormat { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { FrameFormat::MJPEG => { write!(f, "MJPEG") @@ -79,9 +82,12 @@ impl Display for FrameFormat { } } -impl

From

for FrameFormat where P: PixelFormat { - fn from(px: P) -> Self { - match P:: +impl

From

for FrameFormat +where + P: PixelFormat, +{ + fn from(_: P) -> Self { + P::CODE } } @@ -171,6 +177,7 @@ impl From for AVFourCC { /// # JS-WASM /// This is exported as `JSResolution` #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSResolution))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] pub struct Resolution { pub width_x: u32, @@ -225,7 +232,7 @@ impl Resolution { } impl Display for Resolution { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}x{}", self.x(), self.y()) } } @@ -296,6 +303,7 @@ impl From 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, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CameraFormat { resolution: Resolution, format: FrameFormat, @@ -474,6 +482,7 @@ impl From for CaptureDeviceFormatDescriptor { /// This is exported as a `JSCameraInfo`. #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))] #[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CameraInfo { human_name: String, description: String, @@ -488,10 +497,13 @@ impl CameraInfo { /// 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 + ?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. pub fn new( - human_name: &(impl AsRef + ?Sized), - description: &(impl AsRef + ?Sized), - misc: &(impl AsRef + ?Sized), + human_name: impl AsRef, + description: impl AsRef, + misc: impl AsRef, index: CameraIndex, ) -> Self { CameraInfo { @@ -648,6 +660,7 @@ impl From for CameraInfo { /// These can control the picture brightness, etc.
/// Note that not all backends/devices support all these. Run [`supported_camera_controls()`](crate::CaptureBackendTrait::supported_camera_controls) to see which ones can be set. #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KnownCameraControls { Brightness, Contrast, @@ -737,7 +750,7 @@ impl From for KnownCameraControls { } #[cfg(all(feature = "input-v4l", target_os = "linux"))] -impl std::convert::TryFrom for KnownCameraControls { +impl TryFrom for KnownCameraControls { type Error = NokhwaError; fn try_from(value: Description) -> Result { @@ -786,6 +799,7 @@ impl Display for KnownCameraControlFlag { /// NOTE: Assume the values for `min` and `max` as **non-inclusive**!. /// E.g. if the [`CameraControl`] says `min` is 100, the minimum is actually 101. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CameraControl { control: KnownCameraControls, min: i32, @@ -1019,6 +1033,7 @@ impl Ord for CameraControl { /// - `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)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum CaptureAPIBackend { Auto, AVFoundation, @@ -1032,7 +1047,7 @@ pub enum CaptureAPIBackend { } impl Display for CaptureAPIBackend { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let self_str = format!("{:?}", self); write!(f, "{}", self_str) } From e81b4c3f20fad64a4db33b3feb0ea8997a8c5a0c Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 21 May 2022 18:26:33 +0200 Subject: [PATCH 34/89] Remove Sync restriction from CallbackCamera --- src/threaded.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/threaded.rs b/src/threaded.rs index 3e2ab6b..8c1b62b 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -32,11 +32,14 @@ use std::{ type AtomicLock = Arc>; pub type CallbackFn = fn( _camera: &Arc>, - _frame_callback: &Arc, Vec>) + Send + Sync + 'static>>>>, + _frame_callback: &Arc< + Mutex, Vec>) + Send + 'static>>>, + >, _last_frame_captured: &Arc, Vec>>>, _die_bool: &Arc, ); -type HeldCallbackType = Arc, Vec>) + Send + Sync + 'static>>>>; +type HeldCallbackType = + Arc, Vec>) + Send + 'static>>>>; /// 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 `Mutex` to ensure that this feels like a normal camera, but callback based. @@ -387,22 +390,26 @@ impl CallbackCamera { /// The callback will be called every frame. /// # Errors /// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. - pub fn open_stream( - &mut self, - mut callback: F) -> Result<(), NokhwaError> + pub fn open_stream(&mut self, mut callback: F) -> Result<(), NokhwaError> where - F: (FnMut(ImageBuffer, Vec>)) + Send + Sync + 'static, + F: (FnMut(ImageBuffer, Vec>)) + Send + 'static, { - *self.frame_callback.lock() = Some(Box::new(move |image: ImageBuffer, Vec>| { callback(image) }, )); + *self.frame_callback.lock() = + Some(Box::new(move |image: ImageBuffer, Vec>| { + callback(image) + })); self.camera.lock().open_stream() } /// Sets the frame callback to the new specified function. This function will be called instead of the previous one(s). pub fn set_callback(&mut self, mut callback: F) where - F: (FnMut(ImageBuffer, Vec>)) + Send + Sync + 'static, + F: (FnMut(ImageBuffer, Vec>)) + Send + 'static, { - *self.frame_callback.lock() = Some(Box::new(move |image: ImageBuffer, Vec>| { callback(image) }, )); + *self.frame_callback.lock() = + Some(Box::new(move |image: ImageBuffer, Vec>| { + callback(image) + })); } /// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame) From bf6301b8cbc0eed1fa645e4d269781ee1758c390 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sun, 22 May 2022 18:54:28 +0900 Subject: [PATCH 35/89] use enum dispatch, manually implement PR #38, update camera trait, camera, and backends (todo), update camera controls (todo), add buffer to support other types of image e.g. GRAY --- Cargo.toml | 2 +- src/backends/capture/avfoundation.rs | 16 +- src/backends/capture/gst_backend.rs | 18 +-- src/backends/capture/opencv_backend.rs | 62 ++++---- src/backends/capture/v4l2.rs | 74 +++++---- src/buffer.rs | 2 +- src/camera.rs | 164 +++++++------------ src/camera_traits.rs | 116 ++++++++++---- src/lib.rs | 3 +- src/threaded.rs | 169 ++++++++++++-------- src/utils.rs | 212 ++++++++++++++----------- 11 files changed, 453 insertions(+), 385 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b7d5111..a050fd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ version = "0.9" optional = true [dependencies.v4l] -version = "0.12" +version = "0.13" optional = true [dependencies.v4l2-sys-mit] diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index a4fa733..e23b291 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -15,9 +15,8 @@ */ use crate::{ - mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo, - CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, - Resolution, + mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, + CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use nokhwa_bindings_macos::avfoundation::{ @@ -49,18 +48,13 @@ impl AVFoundationCaptureDevice { /// /// 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. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`. - pub fn new( - index: &CameraIndex, - camera_format: Option, - ) -> Result { + /// 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) -> Result { 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 => { @@ -91,7 +85,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: &CameraIndex, + index: usize, width: u32, height: u32, fps: u32, diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index 60da317..af71e33 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -15,9 +15,8 @@ */ use crate::{ - mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo, - CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, - Resolution, + mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, + CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; use glib::Quark; use gstreamer::{ @@ -62,8 +61,8 @@ impl GStreamerCaptureDevice { /// /// 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. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`. - pub fn new(index: &CameraIndex, cam_fmt: Option) -> Result { + /// 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) -> Result { let camera_format = match cam_fmt { Some(fmt) => fmt, None => CameraFormat::default(), @@ -121,7 +120,7 @@ impl GStreamerCaptureDevice { &DeviceExt::display_name(&device), &DeviceExt::device_class(&device), &"", - CameraIndex::Index(index), + index, ), caps, ) @@ -144,12 +143,7 @@ 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: &CameraIndex, - width: u32, - height: u32, - fps: u32, - ) -> Result { + pub fn new_with(index: usize, width: u32, height: u32, fps: u32) -> Result { let cam_fmt = CameraFormat::new(Resolution::new(width, height), FrameFormat::MJPEG, fps); GStreamerCaptureDevice::new(index, Some(cam_fmt)) } diff --git a/src/backends/capture/opencv_backend.rs b/src/backends/capture/opencv_backend.rs index 2963432..0997075 100644 --- a/src/backends/capture/opencv_backend.rs +++ b/src/backends/capture/opencv_backend.rs @@ -14,9 +14,10 @@ * limitations under the License. */ +use crate::pixel_format::PixelFormat; use crate::{ - CameraControl, CameraFormat, CameraIndexType, CameraInfo, CaptureAPIBackend, - CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, + CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, + KnownCameraControls, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use opencv::{ @@ -50,7 +51,6 @@ macro_rules! tryinto_num { }}; } -// TODO: Define behaviour for IPCameras. /// The backend struct that interfaces with `OpenCV`. Note that an `opencv` matching the version that this was either compiled on must be present on the user's machine. (usually 4.5.2 or greater) /// For more information, please see [`opencv-rust`](https://github.com/twistedfall/opencv-rust) and [`OpenCV VideoCapture Docs`](https://docs.opencv.org/4.5.2/d8/dfe/classcv_1_1VideoCapture.html). /// @@ -71,7 +71,7 @@ macro_rules! tryinto_num { #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-opencv")))] pub struct OpenCvCaptureDevice { camera_format: CameraFormat, - camera_location: CameraIndexType, + camera_location: usize, camera_info: CameraInfo, api_preference: i32, video_capture: VideoCapture, @@ -79,7 +79,7 @@ pub struct OpenCvCaptureDevice { #[allow(clippy::must_use_candidate)] impl OpenCvCaptureDevice { - /// Creates a new capture device using the `OpenCV` backend. You can either use an [`Index`](CameraIndexType::Index) or [`IPCamera`](CameraIndexType::IPCamera). + /// Creates a new capture device using the `OpenCV` backend. /// /// Indexes are gives to devices by the OS, and usually numbered by order of discovery. /// @@ -95,7 +95,7 @@ impl OpenCvCaptureDevice { /// # Panics /// If the API u32 -> i32 fails this will error pub fn new( - camera_location: CameraIndexType, + index: usize, cfmt: Option, api_pref: Option, ) -> Result { @@ -105,47 +105,33 @@ impl OpenCvCaptureDevice { tryinto_num!(i32, get_api_pref_int()) }; - let mut index = i32::MAX as u32; - let camera_format = match cfmt { Some(cam_fmt) => cam_fmt, None => CameraFormat::default(), }; - let mut video_capture = match camera_location.clone() { - CameraIndexType::Index(idx) => { - let vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) { - Ok(vc) => { - index = idx; - vc - } - Err(why) => { - return Err(NokhwaError::OpenDeviceError( - idx.to_string(), - why.to_string(), - )) - } - }; - vid_cap + let mut video_capture = match VideoCapture::new(tryinto_num!(i32, idx), api) { + Ok(vc) => vc, + Err(why) => { + return Err(NokhwaError::OpenDeviceError( + idx.to_string(), + why.to_string(), + )) } - CameraIndexType::IPCamera(ip) => match VideoCapture::from_file(&*ip, CAP_ANY) { - Ok(vc) => vc, - Err(why) => return Err(NokhwaError::OpenDeviceError(ip, why.to_string())), - }, }; - set_properties(&mut video_capture, camera_format, &camera_location)?; + set_properties(&mut video_capture, camera_format, index)?; let camera_info = CameraInfo::new( - format!("OpenCV Capture Device {}", camera_location), - camera_location.to_string(), + format!("OpenCV Capture Device {}", index), + index.to_string(), "".to_string(), index as usize, ); Ok(OpenCvCaptureDevice { camera_format, - camera_location, + camera_location: index, camera_info, api_preference: api, video_capture, @@ -339,6 +325,10 @@ impl OpenCvCaptureDevice { } impl CaptureBackendTrait for OpenCvCaptureDevice { + fn init(&mut self) -> Result { + todo!() + } + fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::OpenCv } @@ -365,7 +355,7 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { self.camera_format = new_fmt; - if let Err(why) = set_properties(&mut self.video_capture, new_fmt, &self.camera_location) { + if let Err(why) = set_properties(&mut self.video_capture, new_fmt, self.camera_location) { self.camera_format = current_format; return Err(why); } @@ -598,6 +588,12 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { Ok(image_buf) } + fn frame_typed( + &mut self, + ) -> Result>, NokhwaError> { + todo!() + } + fn frame_raw(&mut self) -> Result, NokhwaError> { let cow = self.raw_frame_vec()?; Ok(cow) @@ -627,7 +623,7 @@ fn get_api_pref_int() -> u32 { fn set_properties( _vc: &mut VideoCapture, _camera_format: CameraFormat, - _camera_location: &CameraIndexType, + _camera_location: usize, ) -> Result<(), NokhwaError> { Ok(()) } diff --git a/src/backends/capture/v4l2.rs b/src/backends/capture/v4l2.rs index a5b9870..11f7506 100644 --- a/src/backends/capture/v4l2.rs +++ b/src/backends/capture/v4l2.rs @@ -17,7 +17,7 @@ use crate::{ error::NokhwaError, mjpeg_to_rgb, - utils::{CameraFormat, CameraIndex, CameraInfo}, + utils::{CameraFormat, CameraInfo}, yuyv422_to_rgb, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag, KnownCameraControls, Resolution, }; @@ -28,14 +28,17 @@ use v4l::{ frameinterval::FrameIntervalEnum, framesize::FrameSizeEnum, io::traits::CaptureStream, - prelude::*, video::{capture::Parameters, Capture}, Format, FourCC, + Device, }; +use crate::pixel_format::PixelFormat; use std::any::Any; pub use v4l::control::{Control, Description, Flags}; +use v4l::prelude::MmapStream; use v4l::video::Output; +use crate::buffer::Buffer; /// Generates a camera control from a device and a description of control /// # Error @@ -171,6 +174,7 @@ fn clone_control(ctrl: &Control) -> Control { /// - The `Any` type for `control` for [`set_raw_camera_control()`](CaptureBackendTrait::set_raw_camera_control) is [`u32`] and [`Control`] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-v4l")))] pub struct V4LCaptureDevice<'a> { + initialized: bool, camera_format: CameraFormat, camera_info: CameraInfo, device: Device, @@ -180,11 +184,12 @@ pub struct V4LCaptureDevice<'a> { impl<'a> V4LCaptureDevice<'a> { /// Creates a new capture device using the `V4L2` 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 `camera_format` is `None`, it will be spawned with a random [`CameraFormat`] as determined by [`init()`](crate::CaptureBackendTrait::init). + /// + /// If `camera_format` is not `None`, the camera will try to use it when you call [`init()`](crate::CaptureBackendTrait::init). /// # Errors - /// 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, cam_fmt: Option) -> Result { - let index = index.as_index()?; + /// 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) -> Result { let device = match Device::new(index as usize) { Ok(dev) => dev, Err(why) => { @@ -196,7 +201,7 @@ impl<'a> V4LCaptureDevice<'a> { }; let camera_info = match device.query_caps() { - Ok(caps) => CameraInfo::new(&caps.card, "", &caps.driver, CameraIndex::Index(index)), + Ok(caps) => CameraInfo::new(&caps.card, "", &caps.driver, usize), Err(why) => { return Err(NokhwaError::GetPropertyError { property: "Capabilities".to_string(), @@ -261,6 +266,7 @@ impl<'a> V4LCaptureDevice<'a> { } Ok(V4LCaptureDevice { + initialized: false, camera_format, camera_info, device, @@ -268,11 +274,11 @@ impl<'a> V4LCaptureDevice<'a> { }) } - /// Create a new `V4L2` Camera with desired settings. + /// Create a new `V4L2` Camera with desired settings. This may or may not work. /// # Errors /// This function will error if the camera is currently busy or if `V4L2` can't read device information. pub fn new_with( - index: &CameraIndex, + index: usize, width: u32, height: u32, fps: u32, @@ -289,7 +295,8 @@ impl<'a> V4LCaptureDevice<'a> { FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; - match v4l::video::Capture::enum_framesizes(&self.device, format) { + // match Capture::enum_framesizes(&self.device, format) { + match self.device.enum_framesizes(format) { Ok(frame_sizes) => { let mut resolutions = vec![]; for frame_size in frame_sizes { @@ -327,7 +334,7 @@ impl<'a> V4LCaptureDevice<'a> { /// Force refreshes the inner [`CameraFormat`] state. pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> { - match (self.device.params(), self.device.format()) { + match (Capture::format(&self.device), self.device.format()) { (Ok(params), Ok(format)) => { // FIXME: actually handle the fractions?????? self.camera_format = CameraFormat::new( @@ -335,10 +342,10 @@ impl<'a> V4LCaptureDevice<'a> { FrameFormat::from(format.fourcc), params.interval.numerator, ); - return Ok(()); + Ok(()) } (_, _) => { - return Err(NokhwaError::GetPropertyError { + Err(NokhwaError::GetPropertyError { property: "parameters".to_string(), error: why.to_string(), }) @@ -348,6 +355,23 @@ impl<'a> V4LCaptureDevice<'a> { } impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { + fn init(&mut self) -> Result { + let camera_format = self.camera_format; + let compatible = self.compatible_camera_formats()?; + if compatible.len() == 0 { + return Err(NokhwaError::InitializeError { backend: self.backend(), error: "Could not find any compatible camera formats!".to_string() }) + } + if compatible.contains(&camera_format) { + self.initialized = true; + return Ok(camera_format) + } else { + self.initialized = true; + let new_fmt = compatible[0]; + self.camera_format = new_fmt; + Ok(new_fmt) + } + } + fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::Video4Linux } @@ -434,7 +458,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), - FrameFormat::GRAY8 => {} + FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; let mut res_map = HashMap::new(); for res in resolutions { @@ -605,7 +629,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { } }; - if let Err(why) = self.device.set_control(id, Control::Value(control.value())) { + if let Err(why) = self.device.set_control(Control { id, value: Value }) { return Err(NokhwaError::SetPropertyError { property: format!("{} V4L2ID {}", control.control(), id), value: control.value().to_string(), @@ -708,25 +732,19 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { self.stream_handle.is_some() } - fn frame(&mut self) -> Result, Vec>, NokhwaError> { + fn frame(&mut self) -> Result { let cam_fmt = self.camera_format; let raw_frame = self.frame_raw()?; let conv = match cam_fmt.format() { FrameFormat::MJPEG => mjpeg_to_rgb(&raw_frame, false)?, FrameFormat::YUYV => yuyv422_to_rgb(&raw_frame, false)?, + FrameFormat::GRAY8 => raw_frame.to_vec(), }; - let image_buf = - match ImageBuffer::from_vec(cam_fmt.width(), cam_fmt.height(), conv) { - Some(buf) => { - let rgb_buf: ImageBuffer, Vec> = buf; - rgb_buf - } - None => return Err(NokhwaError::ReadFrameError( - "ImageBuffer is not large enough! This is probably a bug, please report it!" - .to_string(), - )), - }; - Ok(image_buf) + Ok(Buffer::new(cam_fmt.resolution(), conv, cam_fmt.format())) + } + + fn frame_typed(&mut self) -> Result>, NokhwaError> { + self.frame()?.to_image_with_custom_format::() } fn frame_raw(&mut self) -> Result, NokhwaError> { diff --git a/src/buffer.rs b/src/buffer.rs index 3defad0..20a1c57 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -use crate::pixel_format::{PixelFormat, PixelFormats}; +use crate::pixel_format::{PixelFormat}; use crate::{FrameFormat, NokhwaError, Resolution}; use image::ImageBuffer; #[cfg(feature = "input-opencv")] diff --git a/src/camera.rs b/src/camera.rs index 6033440..5759256 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -15,11 +15,11 @@ */ use crate::{ - CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, - KnownCameraControls, NokhwaError, Resolution, + buffer::Buffer, BackendsEnum, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, + CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; use image::buffer::ConvertBuffer; -use image::{ImageBuffer, Rgb, RgbaImage}; +use image::RgbaImage; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -29,19 +29,13 @@ 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 -where - C: CaptureBackendTrait, -{ +pub struct Camera { idx: usize, - backend: C, + backend: BackendsEnum, backend_api: CaptureAPIBackend, } -impl Camera -where - C: CaptureBackendTrait, -{ +impl Camera { /// 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). @@ -94,7 +88,7 @@ where { self.backend.stop_stream()?; } - let new_camera_format = self.backend.camera_format(); + let new_camera_format = self.backend.camera_format()?; let new_camera = init_camera(new_idx, Some(new_camera_format), self.backend_api)?; self.backend = new_camera; Ok(()) @@ -113,7 +107,7 @@ where { self.backend.stop_stream()?; } - let new_camera_format = self.backend.camera_format(); + let new_camera_format = self.backend.camera_format()?; let new_camera = init_camera(self.idx, Some(new_camera_format), new_backend)?; self.backend = new_camera; Ok(()) @@ -126,13 +120,19 @@ where } /// Gets the current [`CameraFormat`]. - #[must_use] - pub fn camera_format(&self) -> CameraFormat { + pub fn cached_camera_format(&self) -> CameraFormat { + self.backend.cached_camera_format() + } + + /// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed. + pub fn camera_format(&self) -> Result { self.backend.camera_format() } /// Will set the current [`CameraFormat`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new camera format, this will return an error. pub fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> { @@ -174,13 +174,19 @@ where } /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). - #[must_use] - pub fn resolution(&self) -> Resolution { + pub fn cached_resolution(&self) -> Resolution { + self.backend.cached_resolution() + } + + /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. + pub fn resolution(&self) -> Result { self.backend.resolution() } /// Will set the current [`Resolution`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new resolution, this will return an error. pub fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> { @@ -188,13 +194,19 @@ where } /// Gets the current camera framerate (See: [`CameraFormat`]). - #[must_use] - pub fn frame_rate(&self) -> u32 { + pub fn cached_frame_rate(&self) -> u32 { + self.backend.cached_frame_rate() + } + + /// Gets the current camera framerate (See: [`CameraFormat`]). + pub fn frame_rate(&self) -> Result { self.backend.frame_rate() } /// Will set the current framerate /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new framerate, this will return an error. pub fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { @@ -202,13 +214,19 @@ where } /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). - #[must_use] - pub fn frame_format(&self) -> FrameFormat { + pub fn cached_frame_format(&self) -> FrameFormat { + self.backend.cached_frame_format() + } + + /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. + pub fn frame_format(&self) -> Result { self.backend.frame_format() } /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new frame format, this will return an error. pub fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { @@ -351,7 +369,7 @@ where /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, /// this will error. - pub fn frame(&mut self) -> Result, Vec>, NokhwaError> { + pub fn frame(&mut self) -> Result { self.backend.frame() } @@ -365,36 +383,15 @@ where } } - /// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed. - #[must_use] - pub fn min_buffer_size(&self, rgba: bool) -> usize { - let resolution = self.backend.resolution(); - let w = resolution.width() as usize; - let h = resolution.height() as usize; - let c = if rgba { 4 } else { 3 }; - w * h * c - } - /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. - pub fn frame_to_buffer( + pub fn write_frame_to_buffer( &mut self, buffer: &mut [u8], - convert_rgba: bool, - ) -> Result<(), NokhwaError> { - let camera_format = self.backend.camera_format(); - let format = camera_format.format(); - let raw_frame = self.frame_raw()?; - match format { - FrameFormat::MJPEG => crate::utils::buf_mjpeg_to_rgb(&raw_frame, buffer, convert_rgba)?, - FrameFormat::YUYV => { - crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? - } - FrameFormat::GRAY8 => {} - }; - - Ok(()) + write_alpha: bool, + ) -> Result { + self.backend.write_frame_to_buffer(buffer, write_alpha) } #[cfg(feature = "output-wgpu")] @@ -402,59 +399,13 @@ where /// 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<'a, F: PixelFormat>( &mut self, device: &WgpuDevice, queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { - use std::{convert::TryFrom, num::NonZeroU32}; - let frame = self.frame()?; - let rgba_frame: RgbaImage = frame.convert(); - - 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, - }); - - let width_nonzero = match NonZeroU32::try_from(4 * rgba_frame.width()) { - Ok(w) => Some(w), - Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), - }; - - let height_nonzero = match NonZeroU32::try_from(rgba_frame.height()) { - Ok(h) => Some(h), - Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), - }; - - queue.write_texture( - ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: TextureAspect::All, - }, - &rgba_frame.to_vec(), - ImageDataLayout { - offset: 0, - bytes_per_row: width_nonzero, - rows_per_image: height_nonzero, - }, - texture_size, - ); - - Ok(texture) + self.backend.frame_texture(device, queue, label) } /// Will drop the stream. @@ -465,10 +416,7 @@ where } } -impl Drop for Camera -where - C: CaptureBackendTrait, -{ +impl Drop for Camera { fn drop(&mut self) { self.stop_stream().unwrap(); } @@ -505,15 +453,15 @@ macro_rules! cap_impl_fn { $( paste::paste! { #[cfg ($cfg) ] - fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option> { + fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option> { use crate::backends::capture::$backend; match <$backend>::$init_fn(idx, setting) { - Ok(cap) => Some(Ok(Box::new(cap))), + Ok(cap) => Some(Ok(cp.into())), Err(why) => Some(Err(why)), } } #[cfg(not( $cfg ))] - fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option> { + fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option> { None } } @@ -604,7 +552,7 @@ macro_rules! cap_impl_matches { } cap_impl_fn! { - (GStreamerCaptureDevice, new, feature = "input-gst", gst), + // (GStreamerCaptureDevice, new, feature = "input-gst", gst), (OpenCvCaptureDevice, new_autopref, feature = "input-opencv", opencv), // (UVCCaptureDevice, create, feature = "input-uvc", uvc), (V4LCaptureDevice, new, all(feature = "input-v4l", target_os = "linux"), v4l), @@ -612,22 +560,22 @@ cap_impl_fn! { (AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation) } -fn init_camera( +fn init_camera( index: usize, format: Option, backend: CaptureAPIBackend, -) -> Result { +) -> Result { let camera_backend = cap_impl_matches! { backend, index, format, ("input-v4l", Video4Linux, init_v4l), ("input-msmf", MediaFoundation, init_msmf), ("input-avfoundation", AVFoundation, init_avfoundation), // ("input-uvc", UniversalVideoClass, init_uvc), - ("input-gst", GStreamer, init_gst), + // ("input-gst", GStreamer, init_gst), ("input-opencv", OpenCv, init_opencv) }; Ok(camera_backend) } #[cfg(feature = "output-threaded")] -unsafe impl Send for Camera where C: CaptureBackendTrait {} +unsafe impl Send for Camera {} diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 045d690..8da5a53 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -18,12 +18,13 @@ use crate::buffer::Buffer; use crate::pixel_format::PixelFormat; use crate::{ error::NokhwaError, - utils::{CameraFormat, CameraInfo, FrameFormat, Resolution}, + frame_formats, + utils::{CameraFormat, CameraInfo, FrameFormat, Resolution, buf_mjpeg_to_rgb, buf_yuyv422_to_rgb}, CameraControl, CaptureAPIBackend, KnownCameraControls, }; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; -use opencv::imgproc::FloodFillFlags; use std::{any::Any, borrow::Cow, collections::HashMap}; +use enum_dispatch::enum_dispatch; #[cfg(feature = "output-wgpu")] use wgpu::{ Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue, @@ -31,6 +32,14 @@ use wgpu::{ TextureUsages, }; +#[enum_dispatch] +pub(crate) enum BackendsEnum { + MSMF, + AVF, + V4L2, + OCV, +} + /// 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,6 +47,7 @@ use wgpu::{ /// - Backends, if not provided with a camera format, will be spawned with 640x480@15 FPS, MJPEG [`CameraFormat`]. /// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::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()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera. +#[enum_dispatch(BackendsEnum)] pub trait CaptureBackendTrait { /// Initializes the camera. You must call this before any other function. fn init(&mut self) -> Result; @@ -49,10 +59,15 @@ pub trait CaptureBackendTrait { fn camera_info(&self) -> &CameraInfo; /// Gets the current [`CameraFormat`]. + fn cached_camera_format(&self) -> CameraFormat; + + /// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed. fn camera_format(&self) -> Result; /// Will set the current [`CameraFormat`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new camera format, this will return an error. fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError>; @@ -65,34 +80,69 @@ pub trait CaptureBackendTrait { fourcc: FrameFormat, ) -> Result>, NokhwaError>; + fn compatible_camera_formats(&mut self) -> Result, NokhwaError> { + let mut compatible_formats = vec![]; + frame_formats().map(|ff| { + if let Ok(mut fmts) = self.compatible_list_by_resolution(ff).map(|compatible| { + compatible + .into_iter() + .map(|(res, fps)| { + fps.into_iter().map(|rate| CameraFormat { + resolution: res, + format: ff, + frame_rate: rate, + }) + }) + .collect::>() + }) { + compatible_formats.append(&mut fmts) + } + }) + } + /// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most. /// # Errors /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError)). fn compatible_fourcc(&mut self) -> Result, NokhwaError>; /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). + fn cached_resolution(&self) -> Resolution; + + /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. fn resolution(&self) -> Result; /// Will set the current [`Resolution`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new resolution, this will return an error. fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>; /// Gets the current camera framerate (See: [`CameraFormat`]). + fn cached_frame_rate(&self) -> u32; + + /// Gets the current camera framerate (See: [`CameraFormat`]). This will force refresh to the current latest if it has changed. fn frame_rate(&self) -> Result; /// Will set the current framerate /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new framerate, this will return an error. fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). + fn cached_frame_format(&self) -> FrameFormat; + + /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. fn frame_format(&self) -> Result; /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new frame format, this will return an error. fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>; @@ -172,46 +222,44 @@ pub trait CaptureBackendTrait { /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. fn frame_raw(&mut self) -> Result, NokhwaError>; - /// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed. - fn min_buffer_size(&self, rgba: bool) -> usize { - let resolution = self.resolution(); - if rgba { - return (resolution.width() * resolution.height() * 4) as usize; + /// The minimum buffer size needed to write the current frame. If `alpha` is true, it will instead return the minimum size of the RGBA buffer needed. + fn decoded_buffer_size(&self, alpha: bool) -> Result { + let cfmt = self.camera_format()?; + let resolution = cfmt.resolution(); + let pxwidth = match cfmt.format() { + FrameFormat::MJPEG | FrameFormat::YUYV => 3, + FrameFormat::GRAY8 => 1, + }; + if alpha { + return (resolution.width() * resolution.height() * (pxwidth + 1)) as usize; } - (resolution.width() * resolution.height() * 3) as usize + (resolution.width() * resolution.height() * pxwidth) as usize } /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. - fn write_frame_to_buffer( + fn write_frame_to_buffer( &mut self, buffer: &mut [u8], write_alpha: bool, ) -> Result - where - F: PixelFormat, { - let resolution = self.resolution(); + let cfmt = self.camera_format()?; let frame = self.frame_raw()?; - if convert_rgba { - let image_data = - match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) { - Some(image) => { - let image: ImageBuffer, Cow<[u8]>> = image; - image - } - None => { - return Err(NokhwaError::ReadFrameError( - "Frame Cow Too Small".to_string(), - )) - } + let data = match cfmt.format() { + FrameFormat::MJPEG => buf_mjpeg_to_rgb(&frame, buffer, write_alpha), + FrameFormat::YUYV => buf_yuyv422_to_rgb(&frame, buffer, write_alpha), + FrameFormat::GRAY8 => { + let data = if write_alpha { + frame.into_iter().flat_map(|px| [*px, u8::MAX]).collect::>() + } else { + frame }; - let rgba_image: RgbaImage = image_data.convert(); - buffer.copy_from_slice(rgba_image.as_raw()); - return Ok(rgba_image.len()); - } - buffer.copy_from_slice(frame.as_ref()); + + buffer.copy_from_slice(&data); + } + }; Ok(frame.len()) } @@ -220,15 +268,15 @@ pub trait CaptureBackendTrait { /// 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>( + fn frame_texture<'a, F: PixelFormat>( &mut self, device: &WgpuDevice, queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { use std::{convert::TryFrom, num::NonZeroU32}; - let frame = self.frame()?; - let rgba_frame: RgbaImage = frame.convert(); + use image::RgbaImage; + let frame = RgbaImage::from( elf.frame()?.to_image_with_custom_format::()?); let texture_size = Extent3d { width: frame.width(), @@ -246,12 +294,12 @@ pub trait CaptureBackendTrait { usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, }); - let width_nonzero = match NonZeroU32::try_from(4 * rgba_frame.width()) { + let width_nonzero = match NonZeroU32::try_from(4 * frame.width()) { Ok(w) => Some(w), Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), }; - let height_nonzero = match NonZeroU32::try_from(rgba_frame.height()) { + let height_nonzero = match NonZeroU32::try_from(frame.height()) { Ok(h) => Some(h), Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), }; diff --git a/src/lib.rs b/src/lib.rs index b5d70ab..3916cc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ mod utils; pub use camera::Camera; pub use camera_traits::*; pub use error::NokhwaError; +pub use buffer::Buffer; pub use init::*; #[cfg(feature = "input-jscam")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))] @@ -61,5 +62,5 @@ pub use network_camera::NetworkCamera; pub use query::*; #[cfg(feature = "output-threaded")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))] -pub use threaded::ThreadedCamera; +pub use threaded::CallbackCamera; pub use utils::*; diff --git a/src/threaded.rs b/src/threaded.rs index 2046e32..2d10d27 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -15,23 +15,34 @@ */ use crate::{ - Camera, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, FrameFormat, - KnownCameraControls, NokhwaError, Resolution, + Camera, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, + FrameFormat, KnownCameraControls, NokhwaError, Resolution, Buffer }; use image::{ImageBuffer, Rgb}; -use parking_lot::FairMutex; +use parking_lot::Mutex; use std::{ any::Any, collections::HashMap, - ops::Deref, sync::{ atomic::{AtomicBool, Ordering}, Arc, }, }; +type AtomicLock = Arc>; +pub type CallbackFn = fn( + _camera: &Arc>, + _frame_callback: &Arc< + Mutex>>, + >, + _last_frame_captured: &Arc>, + _die_bool: &Arc, +); +type HeldCallbackType = + Arc>>>; + /// 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 @@ -43,20 +54,19 @@ use std::{ /// The `Mutex` guarantees exclusive access to the underlying camera struct. They should be safe to /// impl `Send` on. #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))] -#[derive(Clone)] -pub struct ThreadedCamera { - camera: Arc>, - frame_callback: Arc, Vec>)>>>, - last_frame_captured: Arc, Vec>>>, +pub struct CallbackCamera { + camera: AtomicLock, + frame_callback: HeldCallbackType, + last_frame_captured: AtomicLock, die_bool: Arc, } -impl ThreadedCamera { +impl CallbackCamera { /// Create a new `ThreadedCamera` from an `index` and `format`. `format` can be `None`. /// # 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) -> Result { - ThreadedCamera::with_backend(index, format, CaptureAPIBackend::Auto) + CallbackCamera::with_backend(index, format, CaptureAPIBackend::Auto) } /// Create a new camera from an `index`, `format`, and `backend`. `format` can be `None`. @@ -82,7 +92,7 @@ impl ThreadedCamera { backend: CaptureAPIBackend, ) -> Result { let camera_format = CameraFormat::new_from(width, height, fourcc, fps); - ThreadedCamera::with_backend(index, Some(camera_format), backend) + CallbackCamera::with_backend(index, Some(camera_format), backend) } /// Create a new `ThreadedCamera` from raw values, including the raw capture function. @@ -96,45 +106,54 @@ impl ThreadedCamera { index: usize, format: Option, backend: CaptureAPIBackend, - func: Option< - fn( - _: Arc>, - _: Arc, Vec>)>>>, - _: Arc, Vec>>>, - _: Arc, - ), - >, + func: Option, ) -> Result { - let camera = Arc::new(FairMutex::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 camera = Arc::new(Mutex::new(Camera::with_backend( + index, + Some(format), + backend, + )?)); + let frame_callback = Arc::new(Mutex::new(None)); + let last_frame_captured = Arc::new(Mutex::new(Buffer::default())); let die_bool = Arc::new(AtomicBool::new(false)); - let holding_cell = Arc::new(FairMutex::new(ImageBuffer::new( - format.width(), - format.height(), - ))); - let die_clone = die_bool.clone(); let camera_clone = camera.clone(); - let callback_clone = frame_callback.clone(); - let holding_cell_clone = holding_cell.clone(); - let func = match func { - Some(f) => f, + let frame_callback_clone = frame_callback.clone(); + let last_frame_captured_clone = last_frame_captured.clone(); + let die_bool_clone = die_bool.clone(); + + let thread_callback = match func { + Some(cb) => cb, None => camera_frame_thread_loop, }; - std::thread::spawn(move || { - func(camera_clone, callback_clone, holding_cell_clone, die_clone) - }); - Ok(ThreadedCamera { + match std::thread::Builder::new() + .name(format!("CaptureProcessThreadofCamera {}", index)) + .spawn(move || { + thread_callback( + &camera_clone, + &frame_callback_clone, + &last_frame_captured_clone, + &die_bool_clone, + ); + }) { + Ok(handle) => handle, + Err(why) => { + return Err(NokhwaError::OpenDeviceError( + index.to_string(), + format!("ThreadError: {}", why), + )) + } + }; + + Ok(CallbackCamera { camera, frame_callback, - last_frame_captured: holding_cell, + last_frame_captured, die_bool, }) } @@ -142,7 +161,7 @@ impl ThreadedCamera { /// Gets the current Camera's index. #[must_use] pub fn index(&self) -> usize { - self.camera.lock().index() + self.camera.lock().index().clone() } /// Sets the current Camera's index. Note that this re-initializes the camera. @@ -173,7 +192,7 @@ impl ThreadedCamera { /// Gets the current [`CameraFormat`]. #[must_use] - pub fn camera_format(&self) -> CameraFormat { + pub fn camera_format(&self) -> Result { self.camera.lock().camera_format() } @@ -182,7 +201,7 @@ impl ThreadedCamera { /// # Errors /// If you started the stream and the camera rejects the new camera format, this will return an error. pub fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> { - *self.last_frame_captured.lock() = ImageBuffer::new(new_fmt.width(), new_fmt.height()); + *self.last_frame_captured.lock() = Buffer::new(new_res, Vec::default(), self.camera_format()?.format()); self.camera.lock().set_camera_format(new_fmt) } @@ -205,7 +224,7 @@ impl ThreadedCamera { /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). #[must_use] - pub fn resolution(&self) -> Resolution { + pub fn resolution(&self) -> Result { self.camera.lock().resolution() } @@ -214,13 +233,13 @@ impl ThreadedCamera { /// # Errors /// If you started the stream and the camera rejects the new resolution, this will return an error. pub fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> { - *self.last_frame_captured.lock() = ImageBuffer::new(new_res.width(), new_res.height()); + *self.last_frame_captured.lock() = Buffer::new(new_res, Vec::default(), self.camera_format()?.format()); self.camera.lock().set_resolution(new_res) } /// Gets the current camera framerate (See: [`CameraFormat`]). #[must_use] - pub fn frame_rate(&self) -> u32 { + pub fn frame_rate(&self) -> Result { self.camera.lock().frame_rate() } @@ -234,7 +253,7 @@ impl ThreadedCamera { /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). #[must_use] - pub fn frame_format(&self) -> FrameFormat { + pub fn frame_format(&self) -> Result { self.camera.lock().frame_format() } @@ -281,7 +300,7 @@ impl ThreadedCamera { .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); - for (kc, cc) in maybe_camera_controls.into_iter() { + for (kc, cc) in maybe_camera_controls { control_map.insert(kc, cc); } @@ -303,7 +322,7 @@ impl ThreadedCamera { .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); - for (kc, cc) in maybe_camera_controls.into_iter() { + for (kc, cc) in maybe_camera_controls { control_map.insert(kc, cc); } @@ -368,32 +387,45 @@ impl ThreadedCamera { /// The callback will be called every frame. /// # Errors /// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. - pub fn open_stream( - &mut self, - callback: fn(ImageBuffer, Vec>), - ) -> Result<(), NokhwaError> { - *self.frame_callback.lock() = Some(callback); + pub fn open_stream(&mut self, mut callback: F) -> Result<(), NokhwaError> + where + F: (FnMut(Buffer)) + Send + 'static, + { + *self.frame_callback.lock() = + Some(Box::new(move |image: Buffer| { + callback(image) + })); self.camera.lock().open_stream() } /// Sets the frame callback to the new specified function. This function will be called instead of the previous one(s). - pub fn set_callback(&mut self, callback: fn(ImageBuffer, Vec>)) { - *self.frame_callback.lock() = Some(callback); + pub fn set_callback(&mut self, mut callback: F) + where + F: (FnMut(Buffer)) + Send + 'static, + { + *self.frame_callback.lock() = + Some(Box::new(move |image: Buffer| { + callback(image) + })); } /// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame) - pub fn poll_frame(&mut self) -> Result, Vec>, NokhwaError> { + /// # Errors + /// This will error if the camera fails to capture a frame. + pub fn poll_frame(&mut self) -> Result { let frame = self.camera.lock().frame()?; *self.last_frame_captured.lock() = frame.clone(); Ok(frame) } /// Gets the last frame captured by the camera. + #[must_use] pub fn last_frame(&self) -> ImageBuffer, Vec> { self.last_frame_captured.lock().clone() } /// Checks if stream if open. If it is, it will return true. + #[must_use] pub fn is_stream_open(&self) -> bool { self.camera.lock().is_stream_open() } @@ -406,24 +438,27 @@ impl ThreadedCamera { } } -impl Drop for ThreadedCamera { +impl Drop for CallbackCamera +where + C: CaptureBackendTrait, +{ fn drop(&mut self) { - let _ = self.stop_stream(); + let _stop_stream_err = self.stop_stream(); self.die_bool.store(true, Ordering::SeqCst); } } -fn camera_frame_thread_loop( - camera: Arc>, - callback: Arc, Vec>)>>>, - holding_cell: Arc, Vec>>>, - die_bool: Arc, +fn camera_frame_thread_loop( + camera: &AtomicLock>, + frame_callback: &HeldCallbackType, + last_frame_captured: &AtomicLock, Vec>>, + die_bool: &Arc, ) { loop { - if let Ok(img) = camera.lock().frame() { - *holding_cell.lock() = img.clone(); - if let Some(cb) = callback.lock().deref() { - cb(img) + if let Ok(img) = camera.lock().fr { + *last_frame_captured.lock() = img.clone(); + if let Some(cb) = (*frame_callback.lock()).as_mut() { + cb(img); } } if die_bool.load(Ordering::SeqCst) { diff --git a/src/utils.rs b/src/utils.rs index e6141ba..564d36e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -38,11 +38,10 @@ use nokhwa_bindings_windows::{ MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls, MediaFoundationDeviceDescriptor, }; -use serde::{Deserialize, Serialize}; #[cfg(feature = serde)] use serde::{Deserialize, Serialize}; use std::{ - borrow::{Borrow, Cow}, + borrow::Borrow, cmp::Ordering, fmt::{Display, Formatter}, }; @@ -171,6 +170,10 @@ impl From for AVFourCC { } } +pub const fn frame_formats() -> [FrameFormat; 3] { + [FrameFormat::MJPEG, FrameFormat::YUYV, FrameFormat::GRAY8] +} + /// Describes a Resolution. /// This struct consists of a Width and a Height value (x,y).
/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest. @@ -487,7 +490,7 @@ pub struct CameraInfo { human_name: String, description: String, misc: String, - index: CameraIndex, + index: usize, } #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))] @@ -504,7 +507,7 @@ impl CameraInfo { human_name: impl AsRef, description: impl AsRef, misc: impl AsRef, - index: CameraIndex, + index: usize, ) -> Self { CameraInfo { human_name: human_name.as_ref().to_string(), @@ -576,36 +579,36 @@ impl CameraInfo { /// 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 + pub fn index(&self) -> u32 { + 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: CameraIndex) { + pub fn set_index(&mut self, index: u32) { self.index = index; } - /// Gets the device info's index as an `u32`. - /// # Errors - /// If the index is not parsable as a `u32`, this will error. - /// # JS-WASM - /// This is exported as `get_Index_Int` - #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] - pub fn index_num(&self) -> Result { - match &self.index { - CameraIndex::Index(i) => Ok(*i), - CameraIndex::String(s) => match s.parse::() { - Ok(p) => Ok(p), - Err(why) => Err(NokhwaError::GetPropertyError { - property: "index-int".to_string(), - error: why.to_string(), - }), - }, - } - } + // /// Gets the device info's index as an `u32`. + // /// # Errors + // /// If the index is not parsable as a `u32`, this will error. + // /// # JS-WASM + // /// This is exported as `get_Index_Int` + // #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] + // pub fn index_num(&self) -> Result { + // match &self.index { + // CameraIndex::Index(i) => Ok(*i), + // CameraIndex::String(s) => match s.parse::() { + // Ok(p) => Ok(p), + // Err(why) => Err(NokhwaError::GetPropertyError { + // property: "index-int".to_string(), + // error: why.to_string(), + // }), + // }, + // } + // } } impl Display for CameraInfo { @@ -625,10 +628,10 @@ impl Display for CameraInfo { impl From> for CameraInfo { fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { CameraInfo { - human_name: dev_desc, - description: "Media Foundation Device", + human_name: dev_desc.name_as_string(), + description: "Media Foundation Device".to_string(), misc: dev_desc.link_as_string(), - index: CameraIndex::Index(dev_desc.index() as u32), + index: dev_desc.index() as usize, } } } @@ -651,7 +654,7 @@ impl From for CameraInfo { human_name: descriptor.name, description: descriptor.description, misc: descriptor.misc, - index: CameraIndex::Index(descriptor.index as u32), + index: descriptor.index as usize, } } } @@ -793,6 +796,37 @@ impl Display for KnownCameraControlFlag { } } +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +// TODO: use in CameraControl +pub enum ControlValue { + None, + Integer { + value: i64, + default: i64, + step: i64, + }, + IntegerRange { + min: i32, + max: i32, + value: i32, + step: i32, + default: i32, + }, + Boolean { + value: bool, + default: bool, + }, + String { + value: String, + default: String, + }, + Bytes { + value: Vec, + default: Vec, + } +} + /// This struct tells you everything about a particular [`KnownCameraControls`].
/// However, you should never need to instantiate this struct, since its usually generated for you by `nokhwa`. /// The only time you should be modifying this struct is when you need to set a value and pass it back to the camera. @@ -1041,7 +1075,7 @@ pub enum CaptureAPIBackend { UniversalVideoClass, MediaFoundation, OpenCv, - GStreamer, + // GStreamer, Network, Browser, } @@ -1053,70 +1087,70 @@ impl Display for CaptureAPIBackend { } } -/// 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 { - Index(u32), - String(String), -} +// /// 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 { +// Index(u32), +// String(String), +// } -impl CameraIndex { - /// Gets the device info's index as an `u32`. - /// # Errors - /// If the index is not parsable as a `u32`, this will error. - pub fn as_index(&self) -> Result { - match self { - CameraIndex::Index(i) => Ok(*i), - CameraIndex::String(s) => match s.parse::() { - Ok(p) => Ok(p), - Err(why) => Err(NokhwaError::GetPropertyError { - property: "index-int".to_string(), - error: why.to_string(), - }), - }, - } - } -} +// impl CameraIndex { +// /// Gets the device info's index as an `u32`. +// /// # Errors +// /// If the index is not parsable as a `u32`, this will error. +// pub fn as_index(&self) -> Result { +// match self { +// CameraIndex::Index(i) => Ok(*i), +// CameraIndex::String(s) => match s.parse::() { +// Ok(p) => Ok(p), +// Err(why) => Err(NokhwaError::GetPropertyError { +// property: "index-int".to_string(), +// error: why.to_string(), +// }), +// }, +// } +// } +// } -impl Display for CameraIndex { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CameraIndex::Index(idx) => { - write!(f, "{}", idx) - } - CameraIndex::String(ip) => { - write!(f, "{}", ip) - } - } - } -} +// impl Display for CameraIndex { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// match self { +// CameraIndex::Index(idx) => { +// write!(f, "{}", idx) +// } +// CameraIndex::String(ip) => { +// write!(f, "{}", ip) +// } +// } +// } +// } -impl From for CameraIndex { - fn from(v: u32) -> Self { - CameraIndex::Index(v) - } -} +// impl From for CameraIndex { +// fn from(v: u32) -> Self { +// CameraIndex::Index(v) +// } +// } -/// Trait for strings that can be converted to [`CameraIndex`]es. -pub trait ValidString: AsRef {} +// /// Trait for strings that can be converted to [`CameraIndex`]es. +// pub trait ValidString: AsRef {} +// +// 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 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 From for CameraIndex -where - T: ValidString, -{ - fn from(v: T) -> Self { - CameraIndex::String(v.as_ref().to_string()) - } -} +// impl From for CameraIndex +// where +// T: ValidString, +// { +// fn from(v: T) -> Self { +// CameraIndex::String(v.as_ref().to_string()) +// } +// } /// Converts a MJPEG stream of [u8] into a Vec of RGB888. (R,G,B,R,G,B,...) /// # Errors From deac1ba44a61c11f168f344edccf9d68874aac42 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Thu, 16 Dec 2021 00:00:12 +0900 Subject: [PATCH 36/89] 0.3.4 nokhwa bindings-windows --- Cargo.toml | 84 ++++++++++++++++++++++++------------------------------ 1 file changed, 37 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aca6e22..e278fd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "nokhwa" -version = "0.10.0" +version = "0.9.1" authors = ["l1npengtul "] -edition = "2021" +edition = "2018" 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" @@ -14,95 +15,92 @@ repository = "https://github.com/l1npengtul/nokhwa" crate-type = ["cdylib", "rlib"] [features] -default = ["flume", "decoding"] +default = ["decoding", "flume"] decoding = ["mozjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] -# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x -# input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"] +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", "parking_lot"] -input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "wasm-rs-async-executor"] +input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex"] +input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen"] output-wgpu = ["wgpu"] output-wasm = ["input-jscam"] output-threaded = ["parking_lot"] small-wasm = ["wee_alloc"] -docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] +docs-only = ["input-uvc", "input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"] docs-features = [] test-fail-warning = [] [dependencies] -thiserror = "1.0" -paste = "1.0" +thiserror = "1.0.26" +paste = "1.0.5" [dependencies.flume] -version = "0.10" +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" +version = "^0.23" default-features = false -[target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] -version = "0.9" -optional = true - [target.'cfg(target_os = "linux")'.dependencies.v4l] -version = "0.12" +version = "0.12.1" optional = true [target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit] -version = "0.2" +version = "0.2.0" optional = true [dependencies.ouroboros] -version = "0.14" +version = "^0.13" optional = true -# [dependencies.uvc] -# version = "0.2" -# optional = true +[dependencies.uvc] +version = "0.2.0" +optional = true [dependencies.usb_enumeration] version = "0.1.2" optional = true [dependencies.wgpu] -version = "^0.12" +version = "^0.11" optional = true [dependencies.opencv] -version = "0.62" +version = "0.60.0" features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3" -path = "nokhwa-bindings-windows" -optional = true +version = "0.3.3" +optional = 3 [dependencies.nokhwa-bindings-macos] -version = "0.1" -path = "nokhwa-bindings-macos" +version = "0.1.1" optional = true [dependencies.gstreamer] -version = "0.18" +version = "0.17.0" optional = true [dependencies.gstreamer-app] -version = "0.18" +version = "0.17.0" optional = true [dependencies.gstreamer-video] -version = "0.18" +version = "0.17.0" optional = true [dependencies.glib] -version = "0.15" +version = "0.14.0" optional = true [dependencies.regex] @@ -110,7 +108,7 @@ version = "1.4.6" optional = true [dependencies.web-sys] -version = "0.3" +version = "^0.3" # why features = [ "console", @@ -131,31 +129,23 @@ features = [ optional = true [dependencies.js-sys] -version = "0.3" +version = "^0.3" optional = true [dependencies.wasm-bindgen] -version = "0.2" +version = "^0.2" optional = true [dependencies.wasm-bindgen-futures] -version = "0.4" -optional = true - -[dependencies.wasm-rs-async-executor] -version = "0.9" +version = "^0.4" optional = true [dependencies.wee_alloc] -version = "0.4" +version = "0.4.5" optional = true [dependencies.parking_lot] -version = "0.11" -optional = true - -[dependencies.lazy_static] -version = "1.4" +version = "^0.11" optional = true [profile.release] From b305b2f04969fb8e7152baa89b3d63b87aeea90b Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Thu, 16 Dec 2021 00:14:37 +0900 Subject: [PATCH 37/89] 0.3.4MSMF/0.9.2Nokhwa - MediaFoundationFix --- Cargo.toml | 6 ++--- examples/capture/src/main.rs | 17 +++---------- src/backends/capture/msmf_backend.rs | 38 +++++++++------------------- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e278fd7..b29d0a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa" -version = "0.9.1" +version = "0.9.2" authors = ["l1npengtul "] edition = "2018" description = "A Simple-to-use, cross-platform Rust Webcam Capture Library" @@ -80,8 +80,8 @@ features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3.3" -optional = 3 +version = "0.3.4" +optional = true [dependencies.nokhwa-bindings-macos] version = "0.1.1" diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index 695320e..cf906a9 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -22,9 +22,7 @@ use glium::{ IndexBuffer, Surface, Texture2d, VertexBuffer, }; use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder}; -use nokhwa::{ - nokhwa_initialize, query_devices, Camera, CameraIndex, CaptureAPIBackend, FrameFormat, -}; +use nokhwa::{nokhwa_initialize, query_devices, Camera, CaptureAPIBackend, FrameFormat}; use std::time::Instant; #[derive(Copy, Clone)] @@ -193,15 +191,8 @@ fn main() { .trim() .parse::() { - let mut camera = Camera::new_with( - CameraIndex::Index(index as u32), - width, - height, - fps, - format, - backend_value, - ) - .unwrap(); + let mut camera = + Camera::new_with(index, width, height, fps, format, backend_value).unwrap(); if matches_clone.is_present("query-device") { match camera.compatible_fourcc() { @@ -284,7 +275,7 @@ fn main() { fps, frame.len() ); - send.send(frame).unwrap() + let _send = send.send(frame); } } } diff --git a/src/backends/capture/msmf_backend.rs b/src/backends/capture/msmf_backend.rs index 9644862..04f7078 100644 --- a/src/backends/capture/msmf_backend.rs +++ b/src/backends/capture/msmf_backend.rs @@ -15,9 +15,9 @@ */ use crate::{ - all_known_camera_controls, mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, - CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, - KnownCameraControlFlag, KnownCameraControls, NokhwaError, Resolution, + all_known_camera_controls, mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, + CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag, + KnownCameraControls, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use nokhwa_bindings_windows::{wmf::MediaFoundationDevice, MFControl, MediaFoundationControls}; @@ -34,7 +34,6 @@ 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>, @@ -46,29 +45,16 @@ 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. 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, - ) -> Result { - let mut mf_device = match &index { - CameraIndex::Index(idx) => MediaFoundationDevice::new(*idx as usize), - CameraIndex::String(lnk) => MediaFoundationDevice::with_string( - &lnk.as_bytes() - .into_iter() - .map(|x| *x as u16) - .collect::>(), - ), - }?; - if let Some(fmt) = camera_fmt { - mf_device.set_format(fmt.into())?; - } + /// This function will error if Media Foundation fails to get the device. + pub fn new(index: usize, camera_fmt: Option) -> Result { + let format = camera_fmt.unwrap_or_default(); + let mf_device = MediaFoundationDevice::new(index, format.into())?; let info = CameraInfo::new( mf_device.name(), "MediaFoundation Camera Device".to_string(), mf_device.symlink(), - index, + mf_device.index(), ); Ok(MediaFoundationCaptureDevice { @@ -79,9 +65,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 will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`. + /// This function will error if Media Foundation fails to get the device. pub fn new_with( - index: &CameraIndex<'a>, + index: usize, width: u32, height: u32, fps: u32, @@ -353,8 +339,8 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { let camera_format = self.camera_format(); let raw_data = self.frame_raw()?; let conv = match camera_format.format() { - FrameFormat::MJPEG => mjpeg_to_rgb(raw_data.as_ref(), false)?, - FrameFormat::YUYV => yuyv422_to_rgb(raw_data.as_ref(), false)?, + FrameFormat::MJPEG => mjpeg_to_rgb888(raw_data.as_ref())?, + FrameFormat::YUYV => yuyv422_to_rgb888(raw_data.as_ref())?, }; let imagebuf = From c198822f1b4003e3a2816e71b54148fed430917b Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Fri, 17 Dec 2021 19:30:22 +0900 Subject: [PATCH 38/89] add utility function for available camera formats --- Cargo.toml | 2 +- src/camera.rs | 114 +++++----- src/utils.rs | 575 ++++++++++++++++++++++++++------------------------ 3 files changed, 373 insertions(+), 318 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b29d0a7..41ea4e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa" -version = "0.9.2" +version = "0.9.3" authors = ["l1npengtul "] edition = "2018" description = "A Simple-to-use, cross-platform Rust Webcam Capture Library" diff --git a/src/camera.rs b/src/camera.rs index b15d7b7..d3c319d 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -15,10 +15,10 @@ */ use crate::{ - CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, - FrameFormat, KnownCameraControls, NokhwaError, Resolution, + CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, + KnownCameraControls, NokhwaError, Resolution, }; -use image::{ImageBuffer, Rgb}; +use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -29,7 +29,7 @@ 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: CameraIndex, + idx: usize, backend: Box, backend_api: CaptureAPIBackend, } @@ -39,7 +39,7 @@ impl Camera { /// 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: &CameraIndex, format: Option) -> Result { + pub fn new(index: usize, format: Option) -> Result { Camera::with_backend(index, format, CaptureAPIBackend::Auto) } @@ -47,14 +47,14 @@ 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: &CameraIndex, + index: usize, format: Option, backend: CaptureAPIBackend, ) -> Result { - let camera_backend: Box = init_camera(index, format, backend)?; + let camera_backend = init_camera(index, format, backend)?; Ok(Camera { - idx: index.clone(), + idx: index, backend: camera_backend, backend_api: backend, }) @@ -64,7 +64,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: &CameraIndex, + index: usize, width: u32, height: u32, fps: u32, @@ -77,20 +77,19 @@ impl Camera { /// Gets the current Camera's index. #[must_use] - pub fn index(&self) -> &CameraIndex { - &self.idx + pub fn index(&self) -> usize { + 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: &CameraIndex) -> Result<(), NokhwaError> { + pub fn set_index(&mut self, new_idx: usize) -> Result<(), NokhwaError> { { self.backend.stop_stream()?; } let new_camera_format = self.backend.camera_format(); - let new_camera: Box = - init_camera(new_idx, Some(new_camera_format), self.backend_api)?; + let new_camera = init_camera(new_idx, Some(new_camera_format), self.backend_api)?; self.backend = new_camera; Ok(()) } @@ -109,8 +108,7 @@ impl Camera { self.backend.stop_stream()?; } let new_camera_format = self.backend.camera_format(); - let new_camera: Box = - init_camera(&self.idx, Some(new_camera_format), new_backend)?; + let new_camera = init_camera(self.idx, Some(new_camera_format), new_backend)?; self.backend = new_camera; Ok(()) } @@ -152,6 +150,23 @@ impl Camera { self.backend.compatible_fourcc() } + /// A Vector of available [`CameraFormat`]s. + /// # Errors + /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a [`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError). + pub fn compatible_camera_formats(&mut self) -> Result, NokhwaError> { + let mut camera_formats = Vec::with_capacity(64); + for foramt in self.compatible_fourcc()? { + let resolution_and_fps: HashMap> = + self.compatible_list_by_resolution(foramt)?; + for (res, rates) in resolution_and_fps { + for fps in rates { + camera_formats.push(CameraFormat::new(res, foramt, fps)) + } + } + } + Ok(camera_formats) + } + /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). #[must_use] pub fn resolution(&self) -> Resolution { @@ -229,7 +244,7 @@ impl Camera { .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); - for (kc, cc) in maybe_camera_controls { + for (kc, cc) in maybe_camera_controls.into_iter() { control_map.insert(kc, cc); } @@ -251,7 +266,7 @@ impl Camera { .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); - for (kc, cc) in maybe_camera_controls { + for (kc, cc) in maybe_camera_controls.into_iter() { control_map.insert(kc, cc); } @@ -348,10 +363,10 @@ impl Camera { #[must_use] pub fn min_buffer_size(&self, rgba: bool) -> usize { let resolution = self.backend.resolution(); - let w = resolution.width() as usize; - let h = resolution.height() as usize; - let c = if rgba { 4 } else { 3 }; - w * h * c + if rgba { + return (resolution.width() * resolution.height() * 4) as usize; + } + (resolution.width() * resolution.height() * 3) as usize } /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. @@ -361,18 +376,28 @@ impl Camera { &mut self, buffer: &mut [u8], convert_rgba: bool, - ) -> Result<(), NokhwaError> { - let camera_format = self.backend.camera_format(); - let format = camera_format.format(); - let raw_frame = self.frame_raw()?; - match format { - FrameFormat::MJPEG => crate::utils::buf_mjpeg_to_rgb(&raw_frame, buffer, convert_rgba)?, - FrameFormat::YUYV => { - crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? - } - }; - - Ok(()) + ) -> Result { + let resolution = self.resolution(); + let frame = self.frame_raw()?; + if convert_rgba { + let image_data = + match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) { + Some(image) => { + let image: ImageBuffer, Cow<[u8]>> = image; + image + } + None => { + return Err(NokhwaError::ReadFrameError( + "Frame Cow Too Small".to_string(), + )) + } + }; + let rgba_image: RgbaImage = image_data.convert(); + buffer.copy_from_slice(rgba_image.as_raw()); + return Ok(rgba_image.len()); + } + buffer.copy_from_slice(frame.as_ref()); + Ok(frame.len()) } #[cfg(feature = "output-wgpu")] @@ -386,7 +411,7 @@ impl Camera { queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { - use std::num::NonZeroU32; + use std::{convert::TryFrom, num::NonZeroU32}; let frame = self.frame()?; let rgba_frame: RgbaImage = frame.convert(); @@ -445,7 +470,7 @@ impl Camera { impl Drop for Camera { fn drop(&mut self) { - let _drop = self.stop_stream(); + self.stop_stream().unwrap(); } } @@ -466,8 +491,6 @@ fn figure_out_auto() -> Option { 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; @@ -482,7 +505,7 @@ macro_rules! cap_impl_fn { $( paste::paste! { #[cfg ($cfg) ] - fn [< init_ $backend_name>](idx: &CameraIndex, setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option, NokhwaError>> { use crate::backends::capture::$backend; match <$backend>::$init_fn(idx, setting) { Ok(cap) => Some(Ok(Box::new(cap))), @@ -490,7 +513,7 @@ macro_rules! cap_impl_fn { } } #[cfg(not( $cfg ))] - fn [< init_ $backend_name>](_idx: &CameraIndex, _setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option, NokhwaError>> { None } } @@ -513,7 +536,7 @@ macro_rules! cap_impl_matches { CaptureAPIBackend::$backend => { match cfg!(feature = $feature) { true => { - match $fn(&i,s) { + match $fn(i,s) { Some(cap) => match cap { Ok(c) => c, Err(why) => return Err(why), @@ -549,7 +572,7 @@ macro_rules! cap_impl_matches { CaptureAPIBackend::$backend => { match cfg!(feature = $feature) { true => { - match $fn(&i,s) { + match $fn(i,s) { Some(cap) => match cap { Ok(c) => c, Err(why) => return Err(why), @@ -584,14 +607,13 @@ 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: &CameraIndex, + index: usize, format: Option, backend: CaptureAPIBackend, ) -> Result, NokhwaError> { @@ -602,12 +624,10 @@ fn init_camera( ("input-avfoundation", AVFoundation, init_avfoundation), // ("input-uvc", UniversalVideoClass, init_uvc), ("input-gst", GStreamer, init_gst), - ("input-opencv", OpenCv, init_opencv), - ("input-jscam", Browser, init_browser) + ("input-opencv", OpenCv, init_opencv) }; Ok(camera_backend) } #[cfg(feature = "output-threaded")] -#[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))] unsafe impl Send for Camera {} diff --git a/src/utils.rs b/src/utils.rs index 6840652..239ceaa 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,9 +14,24 @@ * limitations under the License. */ +/* + * Copyright 2021 l1npengtul / 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}, }; @@ -55,7 +70,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, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Hash, PartialOrd, Ord, Eq)] pub enum FrameFormat { MJPEG, YUYV, @@ -155,14 +170,56 @@ impl From for AVFourCC { /// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest. /// # JS-WASM /// This is exported as `JSResolution` +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSResolution))] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] pub struct Resolution { pub width_x: u32, pub height_y: u32, } -#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSResolution))] +#[cfg(not(target_feature = "output-wasm"))] +impl Resolution { + /// Create a new resolution from 2 image size coordinates. + /// # JS-WASM + /// This is exported as a constructor for [`Resolution`]. + #[must_use] + pub fn new(x: u32, y: u32) -> Self { + Resolution { + width_x: x, + height_y: y, + } + } + + /// Get the width of Resolution + /// # JS-WASM + /// This is exported as `get_Width`. + #[must_use] + pub fn width(self) -> u32 { + self.width_x + } + + /// Get the height of Resolution + /// # JS-WASM + /// This is exported as `get_Height`. + #[must_use] + pub fn height(self) -> u32 { + self.height_y + } + + /// Get the x (width) of Resolution + #[must_use] + pub fn x(self) -> u32 { + self.width_x + } + + /// Get the y (height) of Resolution + #[must_use] + pub fn y(self) -> u32 { + self.height_y + } +} + +#[cfg(target_feature = "output-wasm")] impl Resolution { /// Create a new resolution from 2 image size coordinates. /// # JS-WASM @@ -280,7 +337,7 @@ impl From 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, PartialOrd)] +#[derive(Copy, Clone, Debug, Hash, PartialEq)] pub struct CameraFormat { resolution: Resolution, format: FrameFormat, @@ -456,32 +513,103 @@ impl From for CaptureDeviceFormatDescriptor { /// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system. /// # JS-WASM /// This is exported as a `JSCameraInfo`. +#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))] -#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] pub struct CameraInfo { human_name: String, description: String, misc: String, - index: CameraIndex, + index: usize, } -#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))] +#[cfg(not(target_feature = "output-wasm"))] +impl CameraInfo { + /// Create a new [`CameraInfo`]. + /// # JS-WASM + /// This is exported as a constructor for [`CameraInfo`]. + #[must_use] + pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self { + CameraInfo { + human_name, + description, + misc, + index, + } + } + + /// Get a reference to the device info's human readable name. + /// # JS-WASM + /// This is exported as a `get_HumanReadableName`. + #[must_use] + pub fn human_name(&self) -> String { + self.human_name.clone() + } + + /// Set the device info's human name. + /// # JS-WASM + /// This is exported as a `set_HumanReadableName`. + pub fn set_human_name(&mut self, human_name: String) { + self.human_name = human_name; + } + + /// Get a reference to the device info's description. + /// # JS-WASM + /// This is exported as a `get_Description`. + #[must_use] + pub fn description(&self) -> String { + self.description.clone() + } + + /// Set the device info's description. + /// # JS-WASM + /// This is exported as a `set_Description`. + pub fn set_description(&mut self, description: String) { + self.description = description; + } + + /// Get a reference to the device info's misc. + /// # JS-WASM + /// This is exported as a `get_MiscString`. + #[must_use] + pub fn misc(&self) -> String { + self.misc.clone() + } + + /// Set the device info's misc. + /// # JS-WASM + /// This is exported as a `set_MiscString`. + pub fn set_misc(&mut self, misc: String) { + self.misc = misc; + } + + /// Get a reference to the device info's index. + /// # JS-WASM + /// This is exported as a `get_Index`. + #[must_use] + pub fn index(&self) -> usize { + self.index + } + + /// Set the device info's index. + /// # JS-WASM + /// This is exported as a `set_Index`. + pub fn set_index(&mut self, index: usize) { + self.index = index; + } +} + +#[cfg(target_feature = "output-wasm")] 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))] - pub fn new( - human_name: &(impl AsRef + ?Sized), - description: &(impl AsRef + ?Sized), - misc: &(impl AsRef + ?Sized), - index: CameraIndex, - ) -> Self { + pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self { CameraInfo { - human_name: human_name.as_ref().to_string(), - description: description.as_ref().to_string(), - misc: misc.as_ref().to_string(), + human_name, + description, + misc, index, } } @@ -491,22 +619,22 @@ impl CameraInfo { /// This is exported as a `get_HumanReadableName`. #[must_use] #[cfg_attr( - feature = "output-wasm", - wasm_bindgen(getter = HumanReadableName) + feature = "output-wasm", + wasm_bindgen(getter = HumanReadableName) )] - pub fn human_name(&self) -> &'_ str { - self.human_name.borrow() + pub fn human_name(&self) -> String { + self.human_name.clone() } /// 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) + feature = "output-wasm", + wasm_bindgen(setter = HumanReadableName) )] - pub fn set_human_name>(&mut self, human_name: S) { - self.human_name = human_name.as_ref().to_string(); + pub fn set_human_name(&mut self, human_name: String) { + self.human_name = human_name; } /// Get a reference to the device info's description. @@ -514,16 +642,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) -> &'_ str { - self.description.borrow() + pub fn description(&self) -> String { + self.description.clone() } /// 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: S) { - self.description = description.as_ref().to_string(); + pub fn set_description(&mut self, description: String) { + self.description = description; } /// Get a reference to the device info's misc. @@ -531,16 +659,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) -> &'_ str { - self.misc.borrow() + pub fn misc(&self) -> String { + self.misc.clone() } /// 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: S) { - self.misc = misc.as_ref().to_string(); + pub fn set_misc(&mut self, misc: String) { + self.misc = misc; } /// Get a reference to the device info's index. @@ -548,35 +676,28 @@ impl CameraInfo { /// 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 + pub fn index(&self) -> usize { + 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: CameraIndex) { + pub fn set_index(&mut self, index: usize) { self.index = index; } +} - /// Gets the device info's index as an `u32`. - /// # Errors - /// If the index is not parsable as a `u32`, this will error. - /// # JS-WASM - /// This is exported as `get_Index_Int` - #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] - pub fn index_num(&self) -> Result { - match &self.index { - CameraIndex::Index(i) => Ok(*i), - CameraIndex::String(s) => match s.parse::() { - Ok(p) => Ok(p), - Err(why) => Err(NokhwaError::GetPropertyError { - property: "index-int".to_string(), - error: why.to_string(), - }), - }, - } +impl PartialOrd for CameraInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CameraInfo { + fn cmp(&self, other: &Self) -> Ordering { + self.index.cmp(&other.index) } } @@ -597,10 +718,10 @@ impl Display for CameraInfo { impl From> for CameraInfo { fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { CameraInfo { - human_name: dev_desc, - description: "Media Foundation Device", + human_name: dev_desc.name_as_string(), + description: "Media Foundation Device".to_string(), misc: dev_desc.link_as_string(), - index: CameraIndex::Index(dev_desc.index() as u32), + index: dev_desc.index(), } } } @@ -623,7 +744,7 @@ impl From for CameraInfo { human_name: descriptor.name, description: descriptor.description, misc: descriptor.misc, - index: CameraIndex::Index(descriptor.index as u32), + index: descriptor.index as usize, } } } @@ -994,15 +1115,13 @@ impl Ord for CameraControl { /// The list of known capture backends to the library.
/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform. -/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` +/// - `AVFoundation` - Uses `AVFoundation` on MacOSX /// - `V4L2` - `Video4Linux2`, a linux specific backend. /// - `UVC` - 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` - Uses `GStreamer` RTP to capture. Platform agnostic. -/// - `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)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum CaptureAPIBackend { Auto, AVFoundation, @@ -1011,8 +1130,6 @@ pub enum CaptureAPIBackend { MediaFoundation, OpenCv, GStreamer, - Network, - Browser, } impl Display for CaptureAPIBackend { @@ -1022,100 +1139,54 @@ impl Display for CaptureAPIBackend { } } -/// 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 { +/// 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 +/// ://:/ +/// ``` +/// but please consult the manufacturer's specification for more details. +/// The index is a standard webcam index. +#[derive(Clone, Debug, PartialEq)] +pub enum CameraIndexType { Index(u32), - String(String), + IPCamera(String), } -impl CameraIndex { - /// Gets the device info's index as an `u32`. - /// # Errors - /// If the index is not parsable as a `u32`, this will error. - pub fn as_index(&self) -> Result { - match self { - CameraIndex::Index(i) => Ok(*i), - CameraIndex::String(s) => match s.parse::() { - Ok(p) => Ok(p), - Err(why) => Err(NokhwaError::GetPropertyError { - property: "index-int".to_string(), - error: why.to_string(), - }), - }, - } - } -} - -impl Display for CameraIndex { +impl Display for CameraIndexType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - CameraIndex::Index(idx) => { + CameraIndexType::Index(idx) => { write!(f, "{}", idx) } - CameraIndex::String(ip) => { + CameraIndexType::IPCamera(ip) => { write!(f, "{}", ip) } } } } -impl From for CameraIndex { - fn from(v: u32) -> Self { - CameraIndex::Index(v) - } -} - -/// Trait for strings that can be converted to [`CameraIndex`]es. -pub trait ValidString: AsRef {} - -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 From for CameraIndex -where - T: ValidString, -{ - fn from(v: T) -> Self { - CameraIndex::String(v.as_ref().to_string()) - } -} - /// Converts a MJPEG stream of [u8] into a Vec 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(all(feature = "decoding", not(target_arch = "wasm")))] +#[cfg(feature = "decoding")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] -pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { +pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { use mozjpeg::Decompress; let mut jpeg_decompress = match Decompress::new_mem(data) { - Ok(decompress) => { - let decompressor_res = if rgba { - decompress.rgba() - } else { - decompress.rgb() - }; - match decompressor_res { - Ok(decompressor) => decompressor, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) - } + Ok(decompress) => match decompress.rgb() { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) } - } + }, Err(why) => { return Err(NokhwaError::ProcessFrameError { src: FrameFormat::MJPEG, @@ -1124,72 +1195,21 @@ pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { }) } }; - - let scanlines_res: Option> = jpeg_decompress.read_scanlines_flat(); - assert!(jpeg_decompress.finish_decompress()); - - match scanlines_res { - Some(pixels) => Ok(pixels), - None => Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: "Failed to get read readlines into RGB888 pixels!".to_string(), - }), - } -} - -#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] -pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { - Err(NokhwaError::NotImplementedError( - "Not available on WASM".to_string(), - )) -} - -#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] -#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] -pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { - use mozjpeg::Decompress; - - let mut jpeg_decompress = match Decompress::new_mem(data) { - Ok(decompress) => { - let decompressor_res = if rgba { - decompress.rgba() - } else { - decompress.rgb() - }; - match decompressor_res { - Ok(decompressor) => decompressor, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) - } - } - } - Err(why) => { + let decompressed = match jpeg_decompress.read_scanlines::<[u8; 3]>() { + Some(pixels) => pixels, + None => { return Err(NokhwaError::ProcessFrameError { src: FrameFormat::MJPEG, destination: "RGB888".to_string(), - error: why.to_string(), + error: "Failed to get read readlines into RGB888 pixels!".to_string(), }) } }; - assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size()); - - jpeg_decompress.read_scanlines_flat_into(dest); - assert!(jpeg_decompress.finish_decompress()); - - Ok(()) -} - -#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] -pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { - Err(NokhwaError::NotImplementedError( - "Not available on WASM".to_string(), - )) + Ok( + unsafe { std::slice::from_raw_parts(decompressed.as_ptr().cast(), decompressed.len() * 3) } + .to_vec(), + ) } // 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 @@ -1201,86 +1221,105 @@ pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), /// 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_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { - if data.len() % 4 != 0 { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" - .to_string(), - }); - } +pub fn yuyv422_to_rgb888(data: &[u8]) -> Result, NokhwaError> { + use std::convert::TryFrom; - let pixel_size = if rgba { 4 } else { 3 }; - // yuyv yields 2 3-byte pixels per yuyv chunk - let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + let mut rgb_vec: Vec = vec![]; + if data.len() % 4 == 0 { + for px_idx in (0..data.len()).step_by(4) { + let y1 = match data.get(px_idx) { + Some(px) => match i32::try_from(*px) { + Ok(i) => i, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx, why) }); + } + }, + None => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!( + "Failed to get bytes at {}, this is probably a bug, please report!", + px_idx + ), + }); + } + }; - let mut dest = vec![0; rgb_buf_size]; - buf_yuyv422_to_rgb(data, &mut dest, rgba)?; + let u = match data.get(px_idx + 1) { + Some(px) => match i32::try_from(*px) { + Ok(i) => i, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+1, why) }); + } + }, + None => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!( + "Failed to get bytes at {}, this is probably a bug, please report!", + px_idx + 1 + ), + }); + } + }; - Ok(dest) -} + let y2 = match data.get(px_idx + 2) { + Some(px) => match i32::try_from(*px) { + Ok(i) => i, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+2, why) }); + } + }, + None => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!( + "Failed to get bytes at {}, this is probably a bug, please report!", + px_idx + 2 + ), + }); + } + }; -/// Same as yuyv422_to_rgb(&data) but with a destination buffer -pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { - if data.len() % 4 != 0 { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" - .to_string(), - }); - } + let v = match data.get(px_idx + 3) { + Some(px) => match i32::try_from(*px) { + Ok(i) => i, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+3, why) }); + } + }, + None => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!( + "Failed to get bytes at {}, this is probably a bug, please report!", + px_idx + 3 + ), + }); + } + }; - let pixel_size = if rgba { 4 } else { 3 }; - // yuyv yields 2 3-byte pixels per yuyv chunk - let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); - - if dest.len() != rgb_buf_size { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()), - }); - } - - let iter = data.chunks_exact(4); - - if rgba { - let mut iter = iter - .flat_map(|yuyv| { - let y1 = yuyv[0] as i32; - let u = yuyv[1] as i32; - let y2 = yuyv[2] as i32; - let v = yuyv[3] as i32; - let pixel1 = yuyv444_to_rgba(y1, u, v); - let pixel2 = yuyv444_to_rgba(y2, u, v); - [pixel1, pixel2] - }) - .flatten(); - for i in 0..rgb_buf_size { - dest[i] = iter.next().unwrap(); + let pixel1 = yuyv444_to_rgb888(y1, u, v); + let pixel2 = yuyv444_to_rgb888(y2, u, v); + rgb_vec.append(&mut pixel1.to_vec()); + rgb_vec.append(&mut pixel2.to_vec()); } + Ok(rgb_vec) } else { - let mut iter = iter - .flat_map(|yuyv| { - let y1 = yuyv[0] as i32; - let u = yuyv[1] as i32; - let y2 = yuyv[2] as i32; - let v = yuyv[3] as i32; - let pixel1 = yuyv444_to_rgb(y1, u, v); - let pixel2 = yuyv444_to_rgb(y2, u, v); - [pixel1, pixel2] - }) - .flatten(); - - for i in 0..rgb_buf_size { - dest[i] = iter.next().unwrap(); - } + Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" + .to_string(), + }) } - - Ok(()) } // equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB @@ -1289,8 +1328,10 @@ pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<() #[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_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { +pub fn yuyv444_to_rgb888(y: i32, u: i32, v: i32) -> [u8; 3] { let c298 = (y - 16) * 298; let d = u - 128; let e = v - 128; @@ -1299,9 +1340,3 @@ pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { let b = ((c298 + 516 * d + 128) >> 8).clamp(0, 255) as u8; [r, g, b] } - -#[inline] -pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] { - let [r, g, b] = yuyv444_to_rgb(y, u, v); - [r, g, b, 255] -} From cbe3be667fe3da2fb39afe46b18091bb4c46dd6f Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sat, 18 Dec 2021 00:41:48 +0900 Subject: [PATCH 39/89] fix mediafoundation warning --- Cargo.toml | 2 +- src/error.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 41ea4e5..326ed34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa" -version = "0.9.3" +version = "0.9.4" authors = ["l1npengtul "] edition = "2018" description = "A Simple-to-use, cross-platform Rust Webcam Capture Library" diff --git a/src/error.rs b/src/error.rs index 3dd17ed..7f776ff 100644 --- a/src/error.rs +++ b/src/error.rs @@ -101,7 +101,7 @@ impl From for NokhwaError { error, }, BindingError::DeviceOpenFailError(device, error) => { - NokhwaError::OpenDeviceError(device.to_string(), error) + NokhwaError::OpenDeviceError(device, error) } BindingError::ReadFrameError(error) => NokhwaError::ReadFrameError(error), BindingError::NotImplementedError => { From 0a7ead9598af185a877e780342c6c3a60d19cea6 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sat, 18 Dec 2021 00:39:32 +0900 Subject: [PATCH 40/89] [ci skip] set stdout for mediafoudnation pipeline --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 98a7412..3b00338 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -53,7 +53,7 @@ pipeline { steps { scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) pwsh(script: 'rustup update stable', returnStatus: true) - pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true) + pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true, returnStdout: true) } } From 40c1553185fcde26b244693674872edd3a32aab9 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 14 Feb 2022 18:26:21 +0900 Subject: [PATCH 41/89] merge some commits --- examples/threaded-capture/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/threaded-capture/src/main.rs b/examples/threaded-capture/src/main.rs index a129762..db7a79b 100644 --- a/examples/threaded-capture/src/main.rs +++ b/examples/threaded-capture/src/main.rs @@ -15,13 +15,13 @@ */ use image::{ImageBuffer, Rgb}; -use nokhwa::{query_devices, CallbackCamera, CameraIndex, CaptureAPIBackend}; +use nokhwa::{query_devices, CaptureAPIBackend, ThreadedCamera}; fn main() { let cameras = query_devices(CaptureAPIBackend::Auto).unwrap(); cameras.iter().for_each(|cam| println!("{:?}", cam)); - let mut threaded = CallbackCamera::new(&CameraIndex::Index(0), None).unwrap(); + let mut threaded = ThreadedCamera::new(0, None).unwrap(); threaded.open_stream(callback).unwrap(); #[allow(clippy::empty_loop)] // keep it running loop { From f22decb1f1a75914913ca2c37cc980de964a3db3 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sat, 22 Jan 2022 01:55:35 +0900 Subject: [PATCH 42/89] Cherry Pick No-Copy Fixes --- Cargo.toml | 83 ++-- nokhwa-bindings-windows/src/lib.rs | 2 +- src/backends/capture/msmf_backend.rs | 6 +- src/camera.rs | 45 +-- src/utils.rs | 581 +++++++++++++-------------- 5 files changed, 342 insertions(+), 375 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 326ed34..6269d30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "nokhwa" -version = "0.9.4" +version = "0.10.0" authors = ["l1npengtul "] -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,92 +14,96 @@ repository = "https://github.com/l1npengtul/nokhwa" crate-type = ["cdylib", "rlib"] [features] -default = ["decoding", "flume"] +default = ["flume", "decoding"] decoding = ["mozjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] -input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration"] +# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x +# input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"] 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-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex", "parking_lot"] +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"] small-wasm = ["wee_alloc"] -docs-only = ["input-uvc", "input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] +docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"] docs-features = [] test-fail-warning = [] [dependencies] -thiserror = "1.0.26" -paste = "1.0.5" +thiserror = "1.0" +paste = "1.0" [dependencies.flume] -version = "0.10.8" -optional = true - -[target.'cfg(not(target_family = "wasm"))'.dependencies.mozjpeg] -version = "0.8.24" +version = "0.10" optional = true [dependencies.image] -version = "^0.23" +version = "0.23" default-features = false +[target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] +git = "https://github.com/otak/mozjpeg-rust" +branch = "otak/read_scanlines_into" +optional = true + [target.'cfg(target_os = "linux")'.dependencies.v4l] -version = "0.12.1" +version = "0.12" optional = true [target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit] -version = "0.2.0" +version = "0.2" optional = true [dependencies.ouroboros] -version = "^0.13" +version = "0.14" optional = true -[dependencies.uvc] -version = "0.2.0" -optional = true +# [dependencies.uvc] +# version = "0.2" +# optional = true [dependencies.usb_enumeration] version = "0.1.2" optional = true [dependencies.wgpu] -version = "^0.11" +version = "^0.12" optional = true [dependencies.opencv] -version = "0.60.0" +version = "0.62" features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3.4" +version = "0.3" +path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] -version = "0.1.1" +version = "0.1" +path = "nokhwa-bindings-macos" optional = true [dependencies.gstreamer] -version = "0.17.0" +version = "0.18" optional = true [dependencies.gstreamer-app] -version = "0.17.0" +version = "0.18" optional = true [dependencies.gstreamer-video] -version = "0.17.0" +version = "0.18" optional = true [dependencies.glib] -version = "0.14.0" +version = "0.15" optional = true [dependencies.regex] @@ -108,7 +111,7 @@ version = "1.4.6" optional = true [dependencies.web-sys] -version = "^0.3" +version = "0.3" # why features = [ "console", @@ -129,23 +132,31 @@ features = [ optional = true [dependencies.js-sys] -version = "^0.3" +version = "0.3" optional = true [dependencies.wasm-bindgen] -version = "^0.2" +version = "0.2" optional = true [dependencies.wasm-bindgen-futures] -version = "^0.4" +version = "0.4" +optional = true + +[dependencies.wasm-rs-async-executor] +version = "0.9" optional = true [dependencies.wee_alloc] -version = "0.4.5" +version = "0.4" optional = true [dependencies.parking_lot] -version = "^0.11" +version = "0.11" +optional = true + +[dependencies.lazy_static] +version = "1.4" optional = true [profile.release] diff --git a/nokhwa-bindings-windows/src/lib.rs b/nokhwa-bindings-windows/src/lib.rs index 81d4c14..c7c957f 100644 --- a/nokhwa-bindings-windows/src/lib.rs +++ b/nokhwa-bindings-windows/src/lib.rs @@ -1834,7 +1834,7 @@ pub mod wmf { } impl<'a> MediaFoundationDevice<'a> { - pub fn new(_: usize) -> Result { + pub fn new(_: usize, _: MFCameraFormat) -> Result { Ok(MediaFoundationDevice { phantom: &Empty() }) } diff --git a/src/backends/capture/msmf_backend.rs b/src/backends/capture/msmf_backend.rs index 04f7078..36fe64b 100644 --- a/src/backends/capture/msmf_backend.rs +++ b/src/backends/capture/msmf_backend.rs @@ -15,7 +15,7 @@ */ use crate::{ - all_known_camera_controls, mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, + all_known_camera_controls, mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag, KnownCameraControls, NokhwaError, Resolution, }; @@ -339,8 +339,8 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { let camera_format = self.camera_format(); let raw_data = self.frame_raw()?; let conv = match camera_format.format() { - FrameFormat::MJPEG => mjpeg_to_rgb888(raw_data.as_ref())?, - FrameFormat::YUYV => yuyv422_to_rgb888(raw_data.as_ref())?, + FrameFormat::MJPEG => mjpeg_to_rgb(raw_data.as_ref(), false)?, + FrameFormat::YUYV => yuyv422_to_rgb(raw_data.as_ref(), false)?, }; let imagebuf = diff --git a/src/camera.rs b/src/camera.rs index d3c319d..92378ce 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -18,7 +18,8 @@ use crate::{ CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; -use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; +use image::buffer::ConvertBuffer; +use image::{ImageBuffer, Rgb, RgbaImage}; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -363,10 +364,10 @@ impl Camera { #[must_use] pub fn min_buffer_size(&self, rgba: bool) -> usize { let resolution = self.backend.resolution(); - if rgba { - return (resolution.width() * resolution.height() * 4) as usize; - } - (resolution.width() * resolution.height() * 3) as usize + let w = resolution.width() as usize; + let h = resolution.height() as usize; + let c = if rgba { 4 } else { 3 }; + w * h * c } /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. @@ -376,28 +377,18 @@ impl Camera { &mut self, buffer: &mut [u8], convert_rgba: bool, - ) -> Result { - let resolution = self.resolution(); - let frame = self.frame_raw()?; - if convert_rgba { - let image_data = - match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) { - Some(image) => { - let image: ImageBuffer, Cow<[u8]>> = image; - image - } - None => { - return Err(NokhwaError::ReadFrameError( - "Frame Cow Too Small".to_string(), - )) - } - }; - let rgba_image: RgbaImage = image_data.convert(); - buffer.copy_from_slice(rgba_image.as_raw()); - return Ok(rgba_image.len()); - } - buffer.copy_from_slice(frame.as_ref()); - Ok(frame.len()) + ) -> Result<(), NokhwaError> { + let camera_format = self.backend.camera_format(); + let format = camera_format.format(); + let raw_frame = self.frame_raw()?; + match format { + FrameFormat::MJPEG => crate::utils::buf_mjpeg_to_rgb(&raw_frame, buffer, convert_rgba)?, + FrameFormat::YUYV => { + crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? + } + }; + + Ok(()) } #[cfg(feature = "output-wgpu")] diff --git a/src/utils.rs b/src/utils.rs index 239ceaa..4641e73 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,24 +14,9 @@ * limitations under the License. */ -/* - * Copyright 2021 l1npengtul / 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, @@ -170,56 +155,14 @@ impl From for AVFourCC { /// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest. /// # JS-WASM /// This is exported as `JSResolution` -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSResolution))] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] pub struct Resolution { pub width_x: u32, pub height_y: u32, } -#[cfg(not(target_feature = "output-wasm"))] -impl Resolution { - /// Create a new resolution from 2 image size coordinates. - /// # JS-WASM - /// This is exported as a constructor for [`Resolution`]. - #[must_use] - pub fn new(x: u32, y: u32) -> Self { - Resolution { - width_x: x, - height_y: y, - } - } - - /// Get the width of Resolution - /// # JS-WASM - /// This is exported as `get_Width`. - #[must_use] - pub fn width(self) -> u32 { - self.width_x - } - - /// Get the height of Resolution - /// # JS-WASM - /// This is exported as `get_Height`. - #[must_use] - pub fn height(self) -> u32 { - self.height_y - } - - /// Get the x (width) of Resolution - #[must_use] - pub fn x(self) -> u32 { - self.width_x - } - - /// Get the y (height) of Resolution - #[must_use] - pub fn y(self) -> u32 { - self.height_y - } -} - -#[cfg(target_feature = "output-wasm")] +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSResolution))] impl Resolution { /// Create a new resolution from 2 image size coordinates. /// # JS-WASM @@ -337,7 +280,7 @@ impl From 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, @@ -513,103 +456,32 @@ impl From for CaptureDeviceFormatDescriptor { /// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system. /// # JS-WASM /// This is exported as a `JSCameraInfo`. -#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))] +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] pub struct CameraInfo { human_name: String, description: String, misc: String, - index: usize, + index: CameraIndex, } -#[cfg(not(target_feature = "output-wasm"))] -impl CameraInfo { - /// Create a new [`CameraInfo`]. - /// # JS-WASM - /// This is exported as a constructor for [`CameraInfo`]. - #[must_use] - pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self { - CameraInfo { - human_name, - description, - misc, - index, - } - } - - /// Get a reference to the device info's human readable name. - /// # JS-WASM - /// This is exported as a `get_HumanReadableName`. - #[must_use] - pub fn human_name(&self) -> String { - self.human_name.clone() - } - - /// Set the device info's human name. - /// # JS-WASM - /// This is exported as a `set_HumanReadableName`. - pub fn set_human_name(&mut self, human_name: String) { - self.human_name = human_name; - } - - /// Get a reference to the device info's description. - /// # JS-WASM - /// This is exported as a `get_Description`. - #[must_use] - pub fn description(&self) -> String { - self.description.clone() - } - - /// Set the device info's description. - /// # JS-WASM - /// This is exported as a `set_Description`. - pub fn set_description(&mut self, description: String) { - self.description = description; - } - - /// Get a reference to the device info's misc. - /// # JS-WASM - /// This is exported as a `get_MiscString`. - #[must_use] - pub fn misc(&self) -> String { - self.misc.clone() - } - - /// Set the device info's misc. - /// # JS-WASM - /// This is exported as a `set_MiscString`. - pub fn set_misc(&mut self, misc: String) { - self.misc = misc; - } - - /// Get a reference to the device info's index. - /// # JS-WASM - /// This is exported as a `get_Index`. - #[must_use] - pub fn index(&self) -> usize { - self.index - } - - /// Set the device info's index. - /// # JS-WASM - /// This is exported as a `set_Index`. - pub fn set_index(&mut self, index: usize) { - self.index = index; - } -} - -#[cfg(target_feature = "output-wasm")] +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))] 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))] - pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self { + pub fn new( + human_name: &(impl AsRef + ?Sized), + description: &(impl AsRef + ?Sized), + misc: &(impl AsRef + ?Sized), + index: CameraIndex, + ) -> Self { CameraInfo { - human_name, - description, - misc, + human_name: human_name.as_ref().to_string(), + description: description.as_ref().to_string(), + misc: misc.as_ref().to_string(), index, } } @@ -619,22 +491,22 @@ impl CameraInfo { /// This is exported as a `get_HumanReadableName`. #[must_use] #[cfg_attr( - feature = "output-wasm", - wasm_bindgen(getter = HumanReadableName) + 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. /// # JS-WASM /// This is exported as a `set_HumanReadableName`. #[cfg_attr( - feature = "output-wasm", - wasm_bindgen(setter = HumanReadableName) + 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>(&mut self, human_name: S) { + self.human_name = human_name.as_ref().to_string(); } /// Get a reference to the device info's description. @@ -642,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>(&mut self, description: S) { + self.description = description.as_ref().to_string(); } /// Get a reference to the device info's misc. @@ -659,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>(&mut self, misc: S) { + self.misc = misc.as_ref().to_string(); } /// Get a reference to the device info's index. @@ -676,28 +548,35 @@ 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 { + &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) { self.index = index; } -} -impl PartialOrd for CameraInfo { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for CameraInfo { - fn cmp(&self, other: &Self) -> Ordering { - self.index.cmp(&other.index) + /// Gets the device info's index as an `u32`. + /// # Errors + /// If the index is not parsable as a `u32`, this will error. + /// # JS-WASM + /// This is exported as `get_Index_Int` + #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] + pub fn index_num(&self) -> Result { + match &self.index { + CameraIndex::Index(i) => Ok(*i), + CameraIndex::String(s) => match s.parse::() { + Ok(p) => Ok(p), + Err(why) => Err(NokhwaError::GetPropertyError { + property: "index-int".to_string(), + error: why.to_string(), + }), + }, + } } } @@ -718,10 +597,10 @@ impl Display for CameraInfo { impl From> for CameraInfo { fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { CameraInfo { - human_name: dev_desc.name_as_string(), - description: "Media Foundation Device".to_string(), + human_name: dev_desc, + description: "Media Foundation Device", misc: dev_desc.link_as_string(), - index: dev_desc.index(), + index: CameraIndex::Index(dev_desc.index() as u32), } } } @@ -744,7 +623,7 @@ impl From for CameraInfo { human_name: descriptor.name, description: descriptor.description, misc: descriptor.misc, - index: descriptor.index as usize, + index: CameraIndex::Index(descriptor.index as u32), } } } @@ -1115,13 +994,15 @@ impl Ord for CameraControl { /// The list of known capture backends to the library.
/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform. -/// - `AVFoundation` - Uses `AVFoundation` on MacOSX +/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` /// - `V4L2` - `Video4Linux2`, a linux specific backend. /// - `UVC` - 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` - 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, @@ -1130,6 +1011,8 @@ pub enum CaptureAPIBackend { MediaFoundation, OpenCv, GStreamer, + Network, + Browser, } impl Display for CaptureAPIBackend { @@ -1139,54 +1022,100 @@ 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 -/// ://:/ -/// ``` -/// 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 { Index(u32), - IPCamera(String), + String(String), } -impl Display for CameraIndexType { +impl CameraIndex { + /// Gets the device info's index as an `u32`. + /// # Errors + /// If the index is not parsable as a `u32`, this will error. + pub fn as_index(&self) -> Result { + match self { + CameraIndex::Index(i) => Ok(*i), + CameraIndex::String(s) => match s.parse::() { + Ok(p) => Ok(p), + Err(why) => Err(NokhwaError::GetPropertyError { + property: "index-int".to_string(), + error: why.to_string(), + }), + }, + } + } +} + +impl Display for CameraIndex { 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 From for CameraIndex { + fn from(v: u32) -> Self { + CameraIndex::Index(v) + } +} + +/// Trait for strings that can be converted to [`CameraIndex`]es. +pub trait ValidString: AsRef {} + +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 From for CameraIndex +where + T: ValidString, +{ + fn from(v: T) -> Self { + CameraIndex::String(v.as_ref().to_string()) + } +} + /// Converts a MJPEG stream of [u8] into a Vec 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(all(feature = "decoding", not(target_arch = "wasm")))] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] -pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { use mozjpeg::Decompress; let mut jpeg_decompress = match Decompress::new_mem(data) { - Ok(decompress) => match decompress.rgb() { - Ok(decompressor) => decompressor, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJPEG, - destination: "RGB888".to_string(), - error: why.to_string(), - }) + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } } - }, + } Err(why) => { return Err(NokhwaError::ProcessFrameError { src: FrameFormat::MJPEG, @@ -1195,21 +1124,72 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, NokhwaError> { }) } }; - let decompressed = match jpeg_decompress.read_scanlines::<[u8; 3]>() { - Some(pixels) => pixels, - None => { + + let scanlines_res: Option> = jpeg_decompress.read_scanlines_flat(); + assert!(jpeg_decompress.finish_decompress()); + + match scanlines_res { + Some(pixels) => Ok(pixels), + None => Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: "Failed to get read readlines into RGB888 pixels!".to_string(), + }), + } +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) +} + +#[cfg(all(feature = "decoding", not(target_arch = "wasm")))] +#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + use mozjpeg::Decompress; + + let mut jpeg_decompress = match Decompress::new_mem(data) { + Ok(decompress) => { + let decompressor_res = if rgba { + decompress.rgba() + } else { + decompress.rgb() + }; + match decompressor_res { + Ok(decompressor) => decompressor, + Err(why) => { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::MJPEG, + destination: "RGB888".to_string(), + error: why.to_string(), + }) + } + } + } + Err(why) => { return Err(NokhwaError::ProcessFrameError { src: FrameFormat::MJPEG, destination: "RGB888".to_string(), - error: "Failed to get read readlines into RGB888 pixels!".to_string(), + error: why.to_string(), }) } }; - Ok( - unsafe { std::slice::from_raw_parts(decompressed.as_ptr().cast(), decompressed.len() * 3) } - .to_vec(), - ) + assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size()); + + jpeg_decompress.read_scanlines_flat_into(dest); + assert!(jpeg_decompress.finish_decompress()); + + Ok(()) +} + +#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))] +pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + Err(NokhwaError::NotImplementedError( + "Not available on WASM".to_string(), + )) } // 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 @@ -1221,105 +1201,86 @@ pub fn mjpeg_to_rgb888(data: &[u8]) -> Result, 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, NokhwaError> { - use std::convert::TryFrom; - - let mut rgb_vec: Vec = vec![]; - if data.len() % 4 == 0 { - for px_idx in (0..data.len()).step_by(4) { - let y1 = match data.get(px_idx) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx - ), - }); - } - }; - - let u = match data.get(px_idx + 1) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+1, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 1 - ), - }); - } - }; - - let y2 = match data.get(px_idx + 2) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+2, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 2 - ), - }); - } - }; - - let v = match data.get(px_idx + 3) { - Some(px) => match i32::try_from(*px) { - Ok(i) => i, - Err(why) => { - return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+3, why) }); - } - }, - None => { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::YUYV, - destination: "RGB888".to_string(), - error: format!( - "Failed to get bytes at {}, this is probably a bug, please report!", - px_idx + 3 - ), - }); - } - }; - - let pixel1 = yuyv444_to_rgb888(y1, u, v); - let pixel2 = yuyv444_to_rgb888(y2, u, v); - rgb_vec.append(&mut pixel1.to_vec()); - rgb_vec.append(&mut pixel2.to_vec()); - } - Ok(rgb_vec) - } else { - Err(NokhwaError::ProcessFrameError { +pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" .to_string(), - }) + }); } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + let mut dest = Vec::with_capacity(rgb_buf_size); + buf_yuyv422_to_rgb(data, &mut dest, rgba)?; + + Ok(dest) +} + +/// Same as yuyv422_to_rgb(&data) but with a destination buffer +pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { + if data.len() % 4 != 0 { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)" + .to_string(), + }); + } + + let pixel_size = if rgba { 4 } else { 3 }; + // yuyv yields 2 3-byte pixels per yuyv chunk + let rgb_buf_size = (data.len() / 4) * (2 * pixel_size); + + if dest.len() != rgb_buf_size { + return Err(NokhwaError::ProcessFrameError { + src: FrameFormat::YUYV, + destination: "RGB888".to_string(), + error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()), + }); + } + + let iter = data.chunks_exact(4); + + if rgba { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = yuyv[0] as i32; + let u = yuyv[1] as i32; + let y2 = yuyv[2] as i32; + let v = yuyv[3] as i32; + let pixel1 = yuyv444_to_rgba(y1, u, v); + let pixel2 = yuyv444_to_rgba(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + for i in 0..rgb_buf_size { + dest[i] = iter.next().unwrap(); + } + } else { + let mut iter = iter + .flat_map(|yuyv| { + let y1 = yuyv[0] as i32; + let u = yuyv[1] as i32; + let y2 = yuyv[2] as i32; + let v = yuyv[3] as i32; + let pixel1 = yuyv444_to_rgb(y1, u, v); + let pixel2 = yuyv444_to_rgb(y2, u, v); + [pixel1, pixel2] + }) + .flatten(); + + for i in 0..rgb_buf_size { + dest[i] = iter.next().unwrap(); + } + } + + Ok(()) } // equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB @@ -1328,10 +1289,8 @@ pub fn yuyv422_to_rgb888(data: &[u8]) -> Result, 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] { +pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { let c298 = (y - 16) * 298; let d = u - 128; let e = v - 128; @@ -1340,3 +1299,9 @@ pub fn yuyv444_to_rgb888(y: i32, u: i32, v: i32) -> [u8; 3] { let b = ((c298 + 516 * d + 128) >> 8).clamp(0, 255) as u8; [r, g, b] } + +#[inline] +pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] { + let [r, g, b] = yuyv444_to_rgb(y, u, v); + [r, g, b, 255] +} From 14c1c53b4f2d84c2fcdee9eeb988a7c35ed2d4bd Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 14 Feb 2022 18:45:27 +0900 Subject: [PATCH 43/89] Deprecate GST and UVC. They are a pain to work with and just abandoned. If anyone wants to pick up the maintanence slack, feel free to do so. --- src/backends/capture/gst_backend.rs | 4 ++++ src/backends/capture/uvc_backend.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index e918928..bd7a18a 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -42,6 +42,10 @@ type PipelineGenRet = (Element, AppSink, Arc, Vec> /// - `Drop`-ing this may cause a `panic`. /// - Setting controls is not supported. #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-gst")))] +#[deprecated( + since = "0.10", + note = "Use one of the native backends instead(V4L, AVF, MSMF) or OpenCV" +)] pub struct GStreamerCaptureDevice { pipeline: Element, app_sink: AppSink, diff --git a/src/backends/capture/uvc_backend.rs b/src/backends/capture/uvc_backend.rs index db8624a..18dcad3 100644 --- a/src/backends/capture/uvc_backend.rs +++ b/src/backends/capture/uvc_backend.rs @@ -54,6 +54,10 @@ use uvc::{ /// - If internal variables `stream_handle_init` and `active_stream_init` become de-synchronized with the true reality (weather streamhandle/activestream is init or not) this will cause undefined behaviour. #[self_referencing] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-uvc")))] +#[deprecated( + since = "0.10", + note = "Use one of the native backends instead(V4L, AVF, MSMF) or OpenCV" +)] pub struct UVCCaptureDevice<'a> { camera_format: CameraFormat, camera_info: CameraInfo<'a>, From 4cfa7f0c2dc0bef1bc98847ac50ecb6d9dd9f755 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 28 Feb 2022 20:43:20 +0900 Subject: [PATCH 44/89] fix capture --- Cargo.toml | 3 +-- examples/capture/Cargo.toml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6269d30..aca6e22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,7 @@ version = "0.23" default-features = false [target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] -git = "https://github.com/otak/mozjpeg-rust" -branch = "otak/read_scanlines_into" +version = "0.9" optional = true [target.'cfg(target_os = "linux")'.dependencies.v4l] diff --git a/examples/capture/Cargo.toml b/examples/capture/Cargo.toml index f2b4593..77438ce 100644 --- a/examples/capture/Cargo.toml +++ b/examples/capture/Cargo.toml @@ -10,14 +10,13 @@ edition = "2018" default = ["nokhwa/default"] input-msmf = ["nokhwa/input-msmf"] input-v4l = ["nokhwa/input-v4l"] -input-uvc = ["nokhwa/input-uvc"] input-opencv = ["nokhwa/input-opencv"] input-gst = ["nokhwa/input-gst"] input-avfoundation = ["nokhwa/input-avfoundation"] [dependencies] clap = "2.33.3" -glium = "0.30.0" +glium = "0.31.0" glutin = "0.27.0" flume = "0.10.9" From a83a3e84e9ac3924a7dc00060797475e8be4e949 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 28 Feb 2022 22:41:53 +0900 Subject: [PATCH 45/89] clippy fix for macos --- .../UserInterfaceState.xcuserstate | Bin 30767 -> 30936 bytes nokhwa-bindings-macos/src/lib.rs | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate b/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate index 1ffedf426ef483c7376f731def7ec352fda4757d..bc42ece55cac2ed00a7abdcea30a108009607439 100644 GIT binary patch delta 9967 zcmZ{p2Ut_r+raP19jJs12uVnQgbW~o03jrV5E2LpDmIdgYpdT0NL!!(^TBg3=bn4U`+L_p=d$TOIR5}F zfTR)2z_J(4Dp%gd9%7HNzW@e!fDb}J7!ZJPAOms`0~8<@#DN5+3s4620sTQ47{a81 z7aQ^Oz8qhHzlX2H8}LTF315eQgm1(*;jQ>C zd=LH^egHp+e~ll(&*10r3;1RH3jQm81HXyi!f)gE@!#;j@#pvp0wV|_i0DKJ2?-%3 zWQ3eh5o$t1Xo(~unaC#!h(e->a1+IZhwu_5bwpR98_|;(Knx^&L?z)TMiHZlImBGz zO=2GL7BQcAn|Oy6lblDsMJ^y0lFP{z9|pzEnBo zqbjLk)N9l@YCJW8nn+EhW>BwFGpV-t6F73q1`qI- zI;)4)R*xE4K6X-LSsvr=GN`Ts3*s zR?LRkdm;Ie5&u;w7J_k*fpf4TOuHU)V7ZtRb76T{K30Gg(xJ#K0R;$Y5iO=8)?-Sn z81rCWtOV*%wjBEoyF}aQ96E`1&`!p~o5TDaDrTl~Iu+l+?qR|P>_^(( zfZeBaX~XH4?7v};FzxEqbZSS%6YP(FTK-A9{%!d;_Po6%lf#dbU~13>1Y=4N0uTUQ zO!q)4=t=iNfpnWMXWr*`%N7T8QCmLrwcJr7MvwK?)F8*{RX%*2@8wiL2qc)c35b9g zM9?1EOP4eODH<}0?urI0WsISdyC{LW0jTJ1ba!T-R3Jq<0`WkDG~^jqTRvpCuhduD zgle=%HNS@HNFJ+<7s;$3(#7dH_9; z9z>VXgXwa52t9NIu!C%n19A`@x{xUOffC4}D`+3>N57o5i%nOzmHdcySJ=I}G9DkX z=8hUZynJ+xuOhE{xUZ&+7d~YQioKL+iSj9_>3S4}sVEAkoeH=d=nML-rYqZQ3kHCJ zC<0&d$E+#cNq-ISJPGPU9@0mV}O|l0=i_v!gG{?@j4Vc8q{Fg zMlc49rAN}E8WD*a$;)9Tgm=Z2G@CgT-jmm_3Cu--JC7bmPobyNl}xfQI!FnY(34Pu z9>U?MyoR2N$_IrRsJxl3L**dRNR1My8a(V@_8Jn(0<@f22 z=u60}SBjbdyI&*t72Kc~(M!-Ao`}F4ztIdDYixuOm0!?HQF&v84X*%+%)JPm7?~I) zkl_B_(!!AyzKLDSYpWVrY-Haw$y=ZY^SNYNd!$I?fSsw9Cgas~6LYdCjM*sVqW7Ok zh3tTr3}R(829(JBAYB*~7}LSZWm58lWFyOoY6GP3dx zAa8P2RHO7!P+f!WlL~8nBN|yZG5i1XuAO9*LXJolD4dzq^j$i&PqDAEwpX=p0&>Mb zD2^Up-S+784H@Up8S5+W*Pib^qbhuTyI1?G+TJo;rJSoqbf+*S60rw;8rjFDoSg?| z&@~Ns7=4{iZF5Lm2>LbRB3w-WO5gg&_i!mL!-P%X9k2i_q{q_}=!s3Z98=;7`UaxD z$(U7CB~^t}cUO!r`pDWjPM zs>CoQo`>h-1$ZG|guBri;-T5C$5r`2EQUY^K`I2xA=n1N706-*GIcxy6p`$lni^ki zE?N|ZkMg(nj8y=pCN_#GiVbE~$LT>8^LbW3=1D<#ADqVLqBR1C_ze7Yd?s3%X5q8( zIS>Rv&o~ArNrZqlIf8{uVwTe;a=XUw|)!fC~W+0zL$x5QIS>fItX= zh^Z*755wz$dNtj>U8}3w%1l^MiUMDQH@}jc@%4C10R96A!W&Rl@-x1o3@g4F-|}j+ zZTOBrv+WQ>G~gdYAVuk&wGesjB9@)C82^;U=+7XKl<_4cfcAS@8rAh1J_4M7eB4hV7~a6;gMAP<847Q%{lIBjW* z$Z1bo1bS7_k+&dlwmz{9bi@E9$#}H$PsR87%L=90(j3dSq6Nrh#Bw{i#1%hr6plyUfoY+E4 z`-gAFOTJkU^#7M{U9TxbW_^}=BY5;9aK=_{nXk(d;#>j{p#O-#pTLHA%|GaHO zdr15pK>Lk&MEp)XCY}&ai9d)xiD$%L5R8Xl0t6Ewm;}LO2#~?1LNE=2=@8Vl5YIc% zNW2}5M7fv#7i~rd+8eD~%zZ$C0Fja55WtsYq&Q%(d}J_mp$%M*wyVQnq>NOwQIRnL zs@IX+q$)r)bCFphAvI)T8x@%lpqlmX+kn&g$W$^5QIQ5RjWm)bGMzM&8Ki~GB&`sj zMPe=lZ$dB+g0~=;55e0IyaUlk4zRF=wEaVs`;sajf<^yQE$*ON)<#v=BS6&?0;Yqi z&;Mhum)0W7$RX`qlXWCbLI~c6U?l{rAXvSIe4U(0 zLK4Ma0|ZT|27>j>*vy)Y`2kj>tVVi$zns3~bTw|>m{L=RjEl%6Xw@MZ5(VWN2$~zo zrQ|XQ)dz?}~L=k--bgik=p5FHgx2`LdJrXnZ_C8Z*%C@LC)BM=;g;1~pG zYjFYsG&tH=pn*}w`nH8a`^vx~MX4xtI~}Eg;A}e`1m_{R(1CZUE!~AtCd5ajLvZdT zA7x<%IHKZnC zYNwDH>>OvD6aYs_^;dfR8EP8K%F4vJ)O4zjqAA$7#y1>&nL@{N2yQ`e6M`EM+<#RJ zDKp*0m%c&G39!tfWnX>pkY=PYU=wTR*6$%C3GhFXk{K)gH~ zzLZ+Vbjyp$c#lFM*i6+^qmbmQC=}_Bkl1bVBd2@ZOf^zX)EcUpLQ(t#f~OGt0a+Mi z0p`QJ&*C=*7*GWM`Co>uR4cWOLK)?62>ycL8Il|+YP@t8h1SqlMBPK7$aoIH3r10p zT=)g`M(T_WjX2yWZ@0eF~}lcHP`7jv#65_@6==J38sWBLBKU3D;%eGt{JIY=B7 z5hMwc21P=a46^kfle5P;{ID@(n>HL1^!|39^zQD}_!SipkY1Oy2gDGN=b; zZf){b2mz1jHdQ|Bw5{D; zpS5oFSAe>+=o?I>^=z*+A(9Yv zNL+{}Bq2l>k{n_Qv4+?}vO^pp&XBy2>bj8nkli82L+*wA#R=wwaJU>kCyW!$5pg0o z@fgD%Z$O=UTW{u8rH3JCHk=JA~`w z`nlEIDcm{SgrjeD0z@Pc`DVqOd{mS^CZ zcxGNEFNddAoU^^7iod@{aJ%@h6 zf1Up;|0e%QC=p79b_(qh$`0j(@>3qk~30bdX%2p5P15dx_oO<)q31r~u-U=w5u90I2xPf#Ez5)=!( zf=a<;!D7KC!6Csf;bgcVJU%=tyjys6_}K88@NwbO!{>)D4zCYy4qqF-K732~j_{q~ zUxj}ieklBCUHI|vli|OIKM#K)1VWB5Oc*W{2_uEkLb*^Ovd{PUThE>#p&VBi@YI9An zk0KsRa7h;lTf&j>BqB+KL@J4r$Rrv`s>CkIlN3mbB*l_aNl!^{Nnc5S$q31K$vnw? z$qLCv$>)+UBu6CYC6^^PB#$LeBu^!OO1V;nR4;W(2TO-YE2NduD(Nt3y|h`nLAp`8 zS-MraP5Qa?i1d{7jP$Iu?!5FT={4!y$dJg;NPVO&GCR@{>5S|ZIW*E2>5r_AoDw-R za&F|($i~PGksBj7M{bQ0N5w>?M_Hq~NA-`I6*VtvN7UY^{ZU^=9f&#|busEv)Rm|o zqOM2Xh`JSZC+c3*vuG+hGCCimY@%$kY^rRoY@Tes>>XLXY^7|q ztWmZ}wnf$|+b%mOJ0m+QJ1@H^yDqyYdnWr^_CgNiUF1A@xI95_mV4wS@^11{c~5z7 zd0%;d`9OJ@yj)&4UOrJiSw2-hT~5nq$Y;uD$>+%5l)okaK>oG-b_^#bHKs?*Aigy+3>J;Y`KPa9k{!)f1Bb8~&3}rXv zAf;bft$a;6LODt~Q8`&TRXJTrD`zO@DVvlVl{=MRDUT^nC{HQRD94hUOiDgSv^fnt6x{oQqNVtrG7`fP`zILmHL$WMr>qkQmi|6VC=-$ zH)5B@z8kwDwmx=c?CRL2*tM}A#C{UHCw5<5>=&^IV!w$!5_>%MRP3497je=!SKN%a z?Qz%QW$}gaz2f`E4~Q>|9}-^?KPJ8=eq8*-_@CnMYk-E(1ZlcxLNq*0m`13H&}cO} zO^U{#F=;Y1R*hZb&^R@NG{ZF0H1BHGYIbQ3Yc6Ph)cmBmrn#%Rr+J{|YbDw^ZC$(; z{eMtv)@EvL+8k}J)}`&O9jBe4eO)_KJ4?%Gmui=5>$R)24cd*`&DyQn?FnQ;Xo4s~ zk`R@kNKhrjC1?^{38e`G63Pz?ZVOv00hBq}L7DIv*}l#ygjvL`u` zTuEJ%N|Sme^-UU(RF+hpG%{&)(%7VNNxvtvl8Iz0xpVRx$%~Q~CofH|Tb>e;5}OjA zqD|4IY)sjmvL|J4%6`2=U!t$j`}M>0BlM&7HTv=TN&2b!I{gg&GW`mDy?&*BwZ2i` ztY4@9K>wkBqkgmgsQz{;FEur_SL(#nm8ttvuNp8zX9L^7HSi4*gVGRdh&Ln{bcSSu z-e5E28eE0~gU3)(XXtL|VHj$tG}IU-8YUa28DL`kj^V!Hq2ZC?vEixVnc;84i!_i%q)}fpLj(nQ?`2m9ehDxW>5FxZSwZ_?7XH@rd!b z@r?1T@q+O?<4xle6Whc!g_^=mVw2PqZHh6eOmQZy$!>C(oTfZefvL#kF_oCQnMzH) zOnpq_Op8rBO&3ki)8*-T>BG|Jrms)mlD;kdjIq>M##^RXrdepq z49h~xN=vI{hh>-LQ_EhAnRajwRNm@oOObAvURF;x^=wgI*>TZPSU z8)h4Bn`L|7)?)k6w!ya1w%bsuM z_RjWTyU?z%o9*50lkAMW-oDPh+1_g3Vc%*0%6`Cp(Eg46l>K}A&-Ux~8}_^Q2lhwy z$Jux`nH`kfC3`~l%~q-{ zvoB>|$^Iexr|fGvtelt}SB^htUd~53r*ob-gbt&_=E!k49eIutM;}Lj#~??!qry?? zsB#Q*OmIwfOm{%XEXN$jJjZ-Ty=Dzk2sGxzjOZJ{Ly*MdDD5vdC&R4 zg}FMry13Y`2v@XA?oztqT?sCoE7@gpxm{gdrLJDC{;olFu5#B<*J#&RSFLM;>#pmW z>u=YKJXW4RuQqQ=-t;_}H#2Wu-rIQ#^A_hV&HF0vcD^)Ul^>U{%}>nF%FoX)%J<}V z%`eSAlz%$^hx}{#zvkb{zn}kG{^R_o`7a8nf{22|0%JkXf&m3n3lD_vh{}-3Q$l-Iv|pyRW)`c3*ejbl-8`cRzGLaz8KbT&yTI z78e(n7mqJ~yLer3Yw@n)E5+A}e<^-Y{M}3D0TISK)|udq;X{?|koK?=tTS z@B7|0-gVvKE-ksi0y!*UIy=T1VycfNfy+3=edvAL0c<*_idY_eqlt@ZqN>n9r zCE5~wNm@yIiKWC^l3P;j+vMBg`^}@-$9K>7(D%FViSG~J zvr2iTt8!H3^2)}_mde)39hG}44^$qm{JHX%%6pX$D<4%p_Tzra-^m~B=lFU42!EtM z+AsHO{91pa-{>#$7yC>6-TgiNef<6XgZ$Hi} zS}#B?;w|2H>rub8)_T=iuUdQ8YSq@(+SdODq{s7n{_xCZcFyCs0d+`!Fc4INp>zg# z5h}u?a22k@lkj9bi?*@!f$8`Rd>+0CUyQHBSK)8t>+pB*E%;V^C%y|mj32>|;>Ym! z@YDDi{A2tgehI&fU%@}cZ{XkIKjIJY$M_TcSNu2p5ByI85G+DU$cQLHPACW^5lyIw z7(z{Ghy)^)NFz){Hen|0L;*2?7)T5v1{0OUkN{CdR1-sq8p1~mBSsMuiAlt3#5Cd! z;%#Czv4&VntRvPFjYJdCOl%-F5}S#g#4h3>afmofoFq;WH;G%sZQ>5`IdPZxg1AR~ zNqj|oO?*#0BAyYy6VD+I2^af(u>R~nvm|+eqfQ8Try|6nR z00+WBa4@WdLtzaZ0bhk9;TSj`&VV!FEI1p^fpg(JI3F&6OW<<20lgJFxL}rt@q?Ifs9i)?Vk=@A(vNzd>tR#n!Rb(|el&m91kgt-H$=As# z<1J6AscNX|&L?u(z9kERl+VL>eRlA|;}f)rx}@ZktXykek<0BVE2rIE z7PB%k={sGd{6+)SrP0XD!nN!tI59z2)7jhx>*$TrMur zmpYvE)2?|ilo5){2#}$(<$Jpi_Llb4-D17K0gmGkSK=l-7kA^`@m_cpUW2v&u|UT!*6Zc( z%O89!#l3`OW6+G{U>5osTLW{jT+F(J6Io=#99UutmXF!70;~`#!iupHtd!zY;go=i zpoEl&5>t^am=<$kZp?#uvF=R12V)T_HJo~t8bytvB=jK8PSCY2G?WK~YHTv5X8xyQ z0V;t}ZP1XMw)C*Q0MEwE^!ME8DC>We6O_!1wPIVFn1_{)o0OHxr&6c_s)&Bdo7L+w7K(*2UT`0KfQcHhFQ~jm>>*{R44oB^ zu_svK`t?+LXTekKx4&CHqYD3N`3LrAM@zbgFoMX;o-&30o*yp-T|qa%#dR4fLc7^w#lfxBS9_!@sGnftLfVtE-YBDvIY51u~5uydls7XwNKSd*${08cECO=A?&E$7d0VcmwJW{J= z*va0WMlZ^3ue5^5PU$5Tmg zjwck&j3tvAnfwcCIg{@rwcu~nR@G3~#Nsffxh0(k zD(K;|DIKmYiw9QvflSH2%u=&77?+g_N?7sKMyigsM``d1syS$EPSDuZtn8+sj_BD@ zi`zTP=N-l!!TBZBQ4b1yoxuMJ8MwW}(&WL(OJ}US!h}uKdwpkUifSJ2SnG;b* zHFr)p3$U394^tI%o3boCDEA81TNU2&ik|K!)(U1iVRTunHn=`nTUc9J+ZexAvvyMF z7&S50pe|4!Q&$;vai~w)w(I)<`mXA?cIj@$C>d!!j}gC=E>a!Myho`c=^pB&oI2`T zD!s4U=Wpme%r~K5&tbK7?YaIPA<D`;&L#!30L4s z>U-)(s`lmj!(;F`OwD;FF)ia~KQNs4DZ}5Qs%;C_x5voNt|@r> z|LJPLv|Vr`^?>R7kbbC<)7h~}0xe#Qm*Azi19##sCJuWjN!!HO5+Lk?02hH61ZE?! z0fCPYc+9LTWkO=xi?|rToP`weQJnm-V|@(;%!W0hzLs_*=;?2rW)4o_(-=q`ArL~R zxDs{vOni2*U={*h8}T`a(MI}Z)?&u5may_zOYwyiMjb<-TP4p!KdQ^757r7*9nlb9 zf-l1on{XOmiU0?J&?eR;dJRy@shY70Kfy$sYnFnJ{qEpn}#NH0RJwd*zps4$UeAV>tSy7TB#PJT0 zV?mIbf7P~I?KJUm5aSH-K5>@#AMpWkj`)x`PkcmNK){axqvbjTh9NK zj6|TmmALpf*(Wc_K0{#CKV)M%$;P#lm3|W>`xb%Gon$`*tyaQVjY-ULr=mUmZMDb5 zFCA1r2dTye<^DBD)j+$=QYrBV@uCCkuOQa=e^pavNezTNh6<7}1a^U4VK>Nz92g42 zAhQijL|_sEuOToQf!7h3g1}S+0tiqDpjOE5q=KRjDkw$F7WZ;1M&Qj(s+kN`AK-%V z3>Hj4;Ek79Q1|~a*h_Q4bZBe`gBd}v>5SwsD+o4&UT2AtSYTc|7qkYsX8o&{;Yuum zCCugnne{WL5i;v%-hUz-^mGKHxs|+8%;wXN9+j(1ErS(U;s)3Q_JrlI7XtGUSb)Gn z1Qu<8ymps;$Ji0uy6Tk=Sm~9FA zA>$m(R<^7O4ugz)EJt7kJ=~g8Qx8XB|32Kqv9RI41ry*@EO8T@2q(eU;AHqZWQ@Qp zi&Y4`jlgOI)*!HU6AVBKQ^RTS4frMk>kwE^F}r3X0?cmMOn1%OFl6CBVs^?#!=)Wl zsXK35oi%U4w}VozWQ|5(BXzV1u7=Ds%t|}_UmZ4e=x|ddZ+N>7{WeL>Mz}NRPMhF6 zumx_0t#Aw63b(=Sa0db{2y8~66@e`XY(-!j0^1Q_%6B5Ls}=70+nx5kbf-fI?Ec4{ z_I0|`!FG4*0pAZYokgIn)1A&S?gT%C=Yt7>JqYaWcrpy&PPISi zHrInrb%360mr3Dmc(=o=J`Z};p?_5~Ugi4+J`Uo23%`Tk!yn*%_#=D(AHtvDBLt2h za1;UNL~|Sg<`D2M0`DPk5`j|)oNk3rI`H7H@VA$Ee;{zC0}lZP#I}a%h@_f)lMzI*0bM%jel6VzMwf^rz z5~(5M+tJ9lAlmuPmedvcgk6%6Or|kbCsW8&1TG+Ov5C}^Ol)Sxy7wQmlUW^R|G1LZ zz?j`ZuPf2f4~jT!OGo*oN?tWn-j^O!93GoT7BHtB(njW!%&v9?fvX67(o7bTMI^H_ z87b}}@CCiJc)rmc#PuNXDb;d?EMr+%IT)AhLG~oe$zJ`&`bIDxF_@1L2;4y6GX$<9 z@cBRUTV#F70YTyWk^K?4iNLKUav(Vff!he&p`VtRLYS)rSws5hfu+$Q&7_~KW$qFK zORe}YayY%FRAn7WGV5+5Sx?#-S;vrL5%`i3v|Zw_5cql{IgT7pP9P_eOg#Jsfo~D` z4uPK#ctoXF@{;L?#WGs#SgS)ph-nCX|8I!tb&jjRJay=9O zNG2FOX(X9Y@XLRVxT(WfKhyPfBL;Vzy2;JtZsuDG*-CC9x02h)?c@$}C%FrOrwIIt zz;6gVL*RD=o+H4x?w>7W8@Y$vOYS50lLyFyB(qxoLf{2rVTc6~i-lM?VnM_rX}fl{ zfIJQ2*1tTnl4slV^doIr40)dX=)ZzrWX@iU;Fl1KXaspw4PB6!-Hp6Xe%8^9?%{eZ zo;miBPspFiU&yEAub39GB7zGEv4n^vLM$<2MIsh+cW!&^S_8mfdXYy4YTLGYC=eY8 z=^j!RjFGHV#7d*mhhovOEG)YHQ(QvZ4R=UrxY*yxA00d+MXa20+@hBDtY`;%|C zYy1*)>q3WzjtCtYIw~{}ibCHAogO+fbav?6(D|VY zL)V5L4ZRZjTbMe`7*-SZdf4o+d0`8}7KhPc%fj9X+Y`1w>|ofDuw!8-!Y+l~3Hvte z`>^|A55j&5dlvRQ?9Z?lT)@S-1XsjWaxL5dME-01*ZEWV6n`3j4SzrXBL99kCp;`%79JC>36Bd;2u}^yha19; z;aTC`!`}$s9KJhzZ}|T3gW>1GuY?EghJPFWApCjwp8}yELy#}%FBm8oEEpoF7Sss* zf;z!)!3e=f!6?BP!Cb+7!9u}e0WDZ2SRq&`cw4YWuujk@XclY~928s>d?k1hA&oFb z^oST05s088S|WBu?2gzIaWvw+h;tDiMtl@;E#m8lA0vK=_*KXiMhTTdl~5B9#t9RI zZefM6x3I6!Cmb#uAsi_jD;y`BAeM;ApB7Hz3^Az@4`QXFGN6u zi^QU6QM^bi(utBqsUnN0KvX0u5jjL1R7r+Pe3Dwp zNXaP47)gUATV=(m!Ol49Y@eU1e-pq)aW-%5<`1 zSs+zrmRV#)vKrYi*(}*I*$UZ8+1s*hvO}^XvSYFnvP-fXvd?8d%AQ5xQ7|ebs%unz z)WoO-QFPSisNGSwqP~!a%0+UCTqc*x6XkljL2i_1$*ppm+%7Ma7t4Ff2g%3Er^^?} z7t3k+GWlBhLHS|%QTc88?+S$?Euio!sud#@^Armdixf)~OBKr%Zz)zORx5TX+7x>g z`xOTjhZUz3=M?7^7Zev2mldBYzEFIr_*(Hq@r&YD#WN+T?4sTKDp0vq6{Za

YnN=)iUKzSrpR}vp42g%!!!yVot@JiMbGSG3Ij2 z)tGBB*JHkjxgYbZnyZdd$ExGiTD4A{tj<)M)Mm9sZB^UUgVmGOYt{SJr_}GO|EK;? z{gL`(^_S{L>Yvq5)z8$=)qiS84O6hxRkK~QQ?pyMN3&0JKyyfQUUNZnQFBFe zP4k)NmgaNKJ&xs!wzdn9j{Lc8c_k?l{9GN&OadG1A#H)$76YnN|nfP_$!^EEwAM3j5#5#pe zrPJu*bt$?uok5qWGwJ&2Ue~SB?bRLAy{kK=JEOax`&f5LcSrZV?nm8Ex+l7)x@Sp5 zQb-`FTT*BeFG-LjOo~a;B*i6ZlQt*qOFEErDCuZ&c5-oYX|glflYBn;v*eq}x0COt zRHxLZj7}MwGCt*LDxMmdDoa(Qs!}zn@u`Wa$*F0nhSbbdSE@I)EVXB9uhibD{Za>{ z4oa;|txl~;MXBpkPo;jH)-5e5t!LW!v}I|5J!wbNPNbboJDv7X+O@RL(r%{RNxPeN zFYT+e$7#Q&JxlvT5A?X6)OXQ~^-_JjK1rXV*XuL&CVh@RSMS#M(~s1T);H)U=wH)M z(Np?2^fUCc_4D+N`VIO``WAhweye_mewV&YzgK@ie@K5#|1@2iZb=`KJ}tc^{lh@| z4~A|Afk9-D7-R;GA=!{>&>IW}qan*sXmA)@2Ct#qP+{n6=x-Qi7-5)Xm};O5Zy077 z<{0K178=$Xwir$rP8!Y_{%82maKUiNaMkds;fCS1;fdj?;WxwYhCd8{Wq=Gk17?I| zbj#pmXfsMPMr16=*qw1D<9A~~VazoSH2REn##fB58pj(0#%abkjWdn2jdP9jjjN37 zjE%+(#?8hp#_h(P#^c8Kj2DbojGq{<8*dvwH{LUTW&Al4WQsB+nNgX_%$UsB%!Eu` zW=f_$Gb7WH>CW_KmSy(L?3LLkvtQv`6nCd|Y#@l66#v?LlN)YDXJ8g6>kG|DvAG~P7H^tvfvnr51AT4h>eT4!oBHJdh?T1>5`t)}g! zU8XkE$EJtbf^1`UzwE%|?8fZV*oNse}2;@A-`6=hQrHduh!m|i0 zkrtUnVTrbwEj=uQEtQrbmTF7AWt3&KWvqp=%(pDE(3a(vm6p|(b(XD`9hTjeJ(m5J zLzbhKw&q`&#bg+$p&;a_QW+a^KEfliQTL zF}EeRHFtk*;9&0I++$Xq)nxTp%dF+r-qwEBf!0cEwbf^>v%X@btZ!JSTW4BlTjyFA zSQlBBSeIH?SXWy2S+823<;n8$@~ZM?=55LQAn&`p$9ccx{g(H;4YzS@TwAzJXp6K- zZBaIbO=nB9rQ3`)v&~}5v*p{$Y~{8gHlM9FU>k0$w~e+n*v8wY+m_hgv9;Q^*>>96 zZ2N2nZAWa!ZSUDm+di}1vfZ)WwcWFQZTrslgY8G#L)&B9&-wiPwESNAtl}3xoyY0!=|&K|+D9ps8R- z!LEX~f_;Uu!i2)q!t_F8p{X#h&|X+nSX$^ToK(2J@Iv9Ig*OUs7k*y&sPK8=UqzsZ zC<-Z>S~Rn0Y0=7})kW)yHWa;6)LOJPP}Ej*sOWsr=SANYcP-`>rxh0$mlyXct|;zP zJiK^9@wDRU#j}d%7B47XT)et?U2#+K#^RRZEydf4Z5NjkbXn<&(p3)V2zRI*aSp8`$&u2daV4ss504t4sR zwa!V-70wOLjm}NZ7Uy2)fq?U{^O*Bp=Sk;9=RN07F5E@By1F|) z%5Y`6Os*W4)s^ombd|UqE|;sqHN-W^HP6-TI^a6*`pWf}yPKQmHo7hDTz9Fv!adMk z>8^JB+#}re?lJCh?g{Qz_i6WS_c!jx?qA%$xu3iL@&Hc&_wYPokJKagM0?^riJoLn znkU^ez%$#k*K^Es+H=kGrRN9F1J5JR&z|R=zr4Upcu6nU%lC@C8n4cq;?;XIyg6R0 zH{V<6E%tWz_Vm_xM|&rFCwr%Qk$0ANu6KcVv6uF)@;3WOUl*Uy7vqcdCHQo{6rVoe z%kbs<3Vp>shtKWv`pSGgeG`1EeP?_>`JVZKzneeQFY?R%G5#EXuD{st@VopTe}DfV zf2F_L@AKFCNBPJ4$N4AtDL?YR;h*Pk^f&uA`8WHw`gizu`}g_}_z(M!`A_)I`!Dz} z`mfY#YW1~-T4SxLc5UsJTJg5p9ksh_kJr9id$RUSoxaXmXREW<710MKDp++#{~x~D B;>G{~ diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 32f9a18..b2f6262 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -757,7 +757,7 @@ pub mod avfoundation { Err(why) => { return Err(AVFError::ReadFrame(format!( "Failed to read frame from pipe: {}", - why.to_string() + why ))) } }, @@ -861,8 +861,7 @@ pub mod avfoundation { msg_send![value, videoSupportedFrameRateRanges] }) .into_iter() - .map(|v| [v.min(), v.max()]) - .flatten() + .flat_map(|v| [v.min(), v.max()]) .collect::>(); fps_list.sort_by(|n, m| n.partial_cmp(m).unwrap_or(Ordering::Equal)); fps_list.dedup(); From fc1317bd36d7b0694ade5304193656a5692a54a5 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Mon, 28 Feb 2022 22:57:38 +0900 Subject: [PATCH 46/89] add GRAY8 to AVFourCC, bump macos to 0.1.2 --- nokhwa-bindings-macos/Cargo.toml | 2 +- nokhwa-bindings-macos/src/lib.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nokhwa-bindings-macos/Cargo.toml b/nokhwa-bindings-macos/Cargo.toml index fbe60a1..a5a026a 100644 --- a/nokhwa-bindings-macos/Cargo.toml +++ b/nokhwa-bindings-macos/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nokhwa-bindings-macos" version = "0.1.2" -edition = "2021" +edition = "2018" authors = ["l1npengtul"] license = "Apache-2.0" repository = "https://github.com/l1npengtul/nokhwa" diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index b2f6262..7668e6e 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -242,9 +242,9 @@ pub mod avfoundation { use block::ConcreteBlock; use cocoa_foundation::foundation::{NSArray, NSInteger, NSString, NSUInteger}; use core_media_sys::{ - kCMPixelFormat_422YpCbCr8_yuvs, kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, - kCMVideoCodecType_JPEG_OpenDML, CMFormatDescriptionGetMediaSubType, CMSampleBufferRef, - CMVideoDimensions, + kCMPixelFormat_422YpCbCr8_yuvs, kCMPixelFormat_8IndexedGray_WhiteIsZero, + kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, kCMVideoCodecType_JPEG_OpenDML, + CMFormatDescriptionGetMediaSubType, CMSampleBufferRef, CMVideoDimensions, }; use dashmap::DashMap; use flume::{Receiver, Sender}; @@ -681,6 +681,7 @@ pub mod avfoundation { pub enum AVFourCC { YUV2, MJPEG, + GRAY8, } // Localized Name @@ -874,6 +875,7 @@ pub mod avfoundation { let fourcc = match fcc_raw { kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => AVFourCC::YUV2, kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => AVFourCC::MJPEG, + kCMPixelFormat_8IndexedGray_WhiteIsZero => AVFourCC::GRAY8, _ => { return Err(AVFError::InvalidValue { found: fcc_raw.to_string(), @@ -1109,7 +1111,7 @@ pub mod avfoundation { Ok(avf) => avf.into_raw(), Err(_) => { // should not happen - return Err(AVFError::StreamOpen("String contains null? This is a bug, please report it https://github.com/l1npengtul/nokhwa".to_string())); + return Err(AVFError::StreamOpen("String contains null? This is a bug, please report it: https://github.com/l1npengtul/nokhwa".to_string())); } }; let queue = dispatch_queue_create(avf_queue_str, NSObject(std::ptr::null_mut())); From 78eb1fb7bb02de38c644266c6154bcca7c19a408 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sat, 7 May 2022 03:23:54 +0900 Subject: [PATCH 47/89] 0.10 preliminary work --- Cargo.toml | 37 +++----- Jenkinsfile | 135 --------------------------- nokhwa-bindings-macos/src/lib.rs | 1 + src/backends/capture/gst_backend.rs | 2 +- src/backends/capture/v4l2_backend.rs | 17 ++++ src/buffer.rs | 51 ++++++++++ src/camera.rs | 29 ++++-- src/camera_traits.rs | 8 +- src/error.rs | 2 + src/lib.rs | 2 + src/pixel_format.rs | 57 +++++++++++ src/utils.rs | 16 ++++ 12 files changed, 187 insertions(+), 170 deletions(-) delete mode 100644 Jenkinsfile create mode 100644 src/buffer.rs create mode 100644 src/pixel_format.rs diff --git a/Cargo.toml b/Cargo.toml index aca6e22..900a55e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ repository = "https://github.com/l1npengtul/nokhwa" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows"] +exclude = ["examples/threaded-capture", "examples/capture"] + [lib] crate-type = ["cdylib", "rlib"] @@ -28,7 +32,7 @@ input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "was output-wgpu = ["wgpu"] output-wasm = ["input-jscam"] output-threaded = ["parking_lot"] -small-wasm = ["wee_alloc"] +small-wasm = [] docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"] docs-features = [] @@ -37,35 +41,29 @@ test-fail-warning = [] [dependencies] thiserror = "1.0" paste = "1.0" +anymap = "1.0.0-beta.2" +enum_dispatch = "0.3" [dependencies.flume] version = "0.10" optional = true [dependencies.image] -version = "0.23" +version = "0.24" default-features = false -[target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] +[dependencies.mozjpeg] version = "0.9" optional = true -[target.'cfg(target_os = "linux")'.dependencies.v4l] +[dependencies.v4l] version = "0.12" optional = true -[target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit] +[dependencies.v4l2-sys-mit] version = "0.2" optional = true -[dependencies.ouroboros] -version = "0.14" -optional = true - -# [dependencies.uvc] -# version = "0.2" -# optional = true - [dependencies.usb_enumeration] version = "0.1.2" optional = true @@ -75,17 +73,17 @@ version = "^0.12" optional = true [dependencies.opencv] -version = "0.62" +version = "0.63" features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3" +version = "0.3.4" path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] -version = "0.1" +version = "0.1.1" path = "nokhwa-bindings-macos" optional = true @@ -111,7 +109,6 @@ optional = true [dependencies.web-sys] version = "0.3" -# why features = [ "console", "CanvasRenderingContext2d", @@ -146,12 +143,8 @@ optional = true version = "0.9" optional = true -[dependencies.wee_alloc] -version = "0.4" -optional = true - [dependencies.parking_lot] -version = "0.11" +version = "0.12" optional = true [dependencies.lazy_static] diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 3b00338..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,135 +0,0 @@ -pipeline { - agent { - node { - label 'ci_linux' - } - - } - stages { - stage('Sanity Check') { - steps { - echo '$BUILD_TAG' - scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*') - } - } - - stage('Cargo RustFMT') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*') - sh 'rustup update stable' - sh 'cargo fmt --all -- --check' - } - } - - stage('Build, Clippy') { - parallel { - stage('V4L2') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update stable' - sh 'cargo clippy --features "input-v4l, output-wgpu, test-fail-warning"' - } - } - - stage('Media Foundation') { - agent { - node { - label 'ci_windows' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - pwsh(script: 'rustup update stable', returnStatus: true) - pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true, returnStdout: true) - } - } - - stage('AVFoundation') { - steps { - sh 'echo TODO' - } - } - - stage('libUVC') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update stable' - sh 'cargo clippy --features "input-uvc, output-wgpu, test-fail-warning"' - } - } - - stage('OpenCV IPCamera') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update stable' - sh 'cargo clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"' - } - } - - stage('GStreamer') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update nightly' - sh 'cargo clippy --features "input-gst, output-wgpu, test-fail-warning"' - } - } - - stage('JSCamera/WASM') { - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update stable' - sh 'wasm-pack build --release -- --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' - sh 'cargo clippy --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' - } - } - - } - } - - stage('RustDOC') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update nightly' - sh 'cargo +nightly doc --features "docs-only, docs-nolink, docs-features, test-fail-warning" --no-deps --release' - } - } - - } -} \ No newline at end of file diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 7668e6e..7523df1 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -1303,6 +1303,7 @@ pub mod avfoundation { pub enum AVFourCC { YUV2, MJPEG, + GRAY8, } // Localized Name diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index bd7a18a..c8de249 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -155,7 +155,7 @@ impl GStreamerCaptureDevice { } } -impl CaptureBackendTrait for GStreamerCaptureDevice { +impl GStreamerCaptureDevice { fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::GStreamer } diff --git a/src/backends/capture/v4l2_backend.rs b/src/backends/capture/v4l2_backend.rs index ba699b9..ebf3142 100644 --- a/src/backends/capture/v4l2_backend.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -35,6 +35,7 @@ use v4l::{ use std::any::Any; pub use v4l::control::{Control, Description, Flags}; +use v4l::video::Output; /// Generates a camera control from a device and a description of control /// # Error @@ -212,6 +213,7 @@ impl<'a> V4LCaptureDevice<'a> { let fourcc = match camera_format.format() { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; let new_param = Parameters::with_fps(camera_format.frame_rate()); @@ -284,6 +286,7 @@ impl<'a> V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new("GRAY"), }; match v4l::video::Capture::enum_framesizes(&self.device, format) { @@ -311,6 +314,7 @@ impl<'a> V4LCaptureDevice<'a> { } /// Get the inner device (immutable) for e.g. Controls + /// apps bloodtests contact css images index index.html injectionsupplies transfem transmasc #[allow(clippy::must_use_candidate)] pub fn inner_device(&self) -> &Device { &self.device @@ -320,6 +324,18 @@ impl<'a> V4LCaptureDevice<'a> { pub fn inner_device_mut(&mut self) -> &mut Device { &mut self.device } + + /// Force refreshes the inner [`CameraFormat`] state. + pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> { + match (self.device.params(), self.device.format()) { + (Ok(params), Ok(format)) => { + let new_format = CameraFormat::new(params.capabilities.) + } + (_, _) => { + return Err(NokhwaError::GetPropertyError { property: "parameters".to_string(), error: why.to_string() }) + } + } + } } impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { @@ -409,6 +425,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => {} }; let mut res_map = HashMap::new(); for res in resolutions { diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..4c6eeba --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,51 @@ +/* + * Copyright 2022 l1npengtul / 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::pixel_format::{PixelFormat, PixelFormats}; +use crate::{FrameFormat, NokhwaError, Resolution}; +use image::ImageBuffer; + +#[derive(Clone, Debug, Default, Hash, PartialOrd, PartialEq)] +#[cfg_attr("serde", Serialize, Deserialize)] +pub struct Buffer { + resolution: Resolution, + buffer: Vec, + +} + +impl Buffer { + pub fn new(res: Resolution, buf: Vec) -> Self { + Self { + resolution: res, + buffer: buf, + } + } + + pub fn to_image_with_custom_format(self) -> Result>, NokhwaError> + where + I: PixelFormat, + { + ImageBuffer::from_raw( + self.resolution.width_x, + self.resolution.height_y, + self.buffer, + ).ok_or(NokhwaError::ProcessFrameError { + src: , + destination: "".to_string(), + error: "".to_string() + }) + } +} diff --git a/src/camera.rs b/src/camera.rs index 92378ce..6033440 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -29,14 +29,19 @@ 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 { +pub struct Camera +where + C: CaptureBackendTrait, +{ idx: usize, - backend: Box, + backend: C, backend_api: CaptureAPIBackend, } -#[allow(clippy::nonminimal_bool)] -impl Camera { +impl Camera +where + C: CaptureBackendTrait, +{ /// 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). @@ -386,6 +391,7 @@ impl Camera { FrameFormat::YUYV => { crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? } + FrameFormat::GRAY8 => {} }; Ok(()) @@ -459,7 +465,10 @@ impl Camera { } } -impl Drop for Camera { +impl Drop for Camera +where + C: CaptureBackendTrait, +{ fn drop(&mut self) { self.stop_stream().unwrap(); } @@ -496,7 +505,7 @@ macro_rules! cap_impl_fn { $( paste::paste! { #[cfg ($cfg) ] - fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option> { use crate::backends::capture::$backend; match <$backend>::$init_fn(idx, setting) { Ok(cap) => Some(Ok(Box::new(cap))), @@ -504,7 +513,7 @@ macro_rules! cap_impl_fn { } } #[cfg(not( $cfg ))] - fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option> { None } } @@ -603,11 +612,11 @@ cap_impl_fn! { (AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation) } -fn init_camera( +fn init_camera( index: usize, format: Option, backend: CaptureAPIBackend, -) -> Result, NokhwaError> { +) -> Result { let camera_backend = cap_impl_matches! { backend, index, format, ("input-v4l", Video4Linux, init_v4l), @@ -621,4 +630,4 @@ fn init_camera( } #[cfg(feature = "output-threaded")] -unsafe impl Send for Camera {} +unsafe impl Send for Camera where C: CaptureBackendTrait {} diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 08e30f6..749a4fe 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -21,6 +21,7 @@ use crate::{ }; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; +use crate::pixel_format::PixelFormat; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -36,7 +37,10 @@ use wgpu::{ /// - Backends, if not provided with a camera format, will be spawned with 640x480@15 FPS, MJPEG [`CameraFormat`]. /// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::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()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera. -pub trait CaptureBackendTrait: Send { +pub trait CaptureBackendTrait { + /// Initializes the camera. You must call this before any other function. + fn init(&mut self) -> Result; + /// Returns the current backend used. fn backend(&self) -> CaptureAPIBackend; @@ -209,7 +213,7 @@ pub trait CaptureBackendTrait: Send { queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { - use std::num::NonZeroU32; + use std::{convert::TryFrom, num::NonZeroU32}; let frame = self.frame()?; let rgba_frame: RgbaImage = frame.convert(); diff --git a/src/error.rs b/src/error.rs index 7f776ff..7fb8fb3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,8 @@ use thiserror::Error; #[allow(clippy::module_name_repetitions)] #[derive(Error, Debug, Clone)] pub enum NokhwaError { + #[error("Unitialized Camera. Call `init()` first!")] + UnitializedError, #[error("Could not initialize {backend}: {error}")] InitializeError { backend: CaptureAPIBackend, diff --git a/src/lib.rs b/src/lib.rs index 66ba442..c275aa1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ /// Raw access to each of Nokhwa's backends. pub mod backends; +pub mod buffer; mod camera; mod camera_traits; mod error; @@ -40,6 +41,7 @@ pub mod js_camera; #[cfg(feature = "input-ipcam")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))] pub mod network_camera; +pub mod pixel_format; mod query; /// A camera that runs in a different thread and can call your code based on callbacks. #[cfg(feature = "output-threaded")] diff --git a/src/pixel_format.rs b/src/pixel_format.rs new file mode 100644 index 0000000..d57bb36 --- /dev/null +++ b/src/pixel_format.rs @@ -0,0 +1,57 @@ +/* + * Copyright 2022 l1npengtul / The Nokhwa Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::buffer_output::{BufferOutput, GrayU8, RgbU8}; +use image::{Luma, Pixel, Rgb}; +use std::fmt::Debug; +use std::hash::Hash; + +pub trait PixelFormat: Copy + Clone + Debug + Default + Hash + Send + Sync { + type Output: Pixel; + + const CODE: &'static str; + + fn code(&self) -> &'static str { + CODE + } +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Mjpeg; + +impl PixelFormat for Mjpeg { + type Output = Rgb; + + const CODE: &'static str = "MJPG"; +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Yuyv; + +impl PixelFormat for Yuyv { + type Output = Rgb; + + const CODE: &'static str = "YUYV"; +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Gray; + +impl PixelFormat for Gray { + type Output = Luma; + + const CODE: &'static str = "GRAY"; +} diff --git a/src/utils.rs b/src/utils.rs index 4641e73..41623cb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -49,6 +49,7 @@ use nokhwa_bindings_windows::{ use uvc::StreamFormat; #[cfg(all(feature = "input-v4l", target_os = "linux"))] use v4l::{control::Description, Format, FourCC}; +use crate::pixel_format::PixelFormat; /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. /// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) @@ -59,6 +60,7 @@ use v4l::{control::Description, Format, FourCC}; pub enum FrameFormat { MJPEG, YUYV, + GRAY8, } impl Display for FrameFormat { @@ -70,10 +72,19 @@ impl Display for FrameFormat { FrameFormat::YUYV => { write!(f, "YUYV") } + FrameFormat::GRAY8 => { + write!(f, "GRAY8") + } } } } +impl

From

for FrameFormat where P: PixelFormat { + fn from(px: P) -> Self { + match P:: + } +} + #[cfg(feature = "input-uvc")] impl From for uvc::FrameFormat { fn from(ff: FrameFormat) -> Self { @@ -93,6 +104,7 @@ impl From for FrameFormat { match mf_ff { MFFrameFormat::MJPEG => FrameFormat::MJPEG, MFFrameFormat::YUYV => FrameFormat::YUYV, + MFFrameFormat::GRAY8 => FrameFormat::GRAY8, } } } @@ -106,6 +118,7 @@ impl From for MFFrameFormat { match ff { FrameFormat::MJPEG => MFFrameFormat::MJPEG, FrameFormat::YUYV => MFFrameFormat::YUYV, + FrameFormat::GRAY8 => MFFrameFormat::GRAY8, //FIXME } } } @@ -126,6 +139,7 @@ impl From for FrameFormat { match av_fcc { AVFourCC::YUV2 => FrameFormat::YUYV, AVFourCC::MJPEG => FrameFormat::MJPEG, + AVFourCC::GRAY8 => FrameFormat::GRAY8, } } } @@ -146,6 +160,7 @@ impl From for AVFourCC { match ff { FrameFormat::MJPEG => AVFourCC::MJPEG, FrameFormat::YUYV => AVFourCC::YUV2, + FrameFormat::GRAY8 => AVFourCC::GRAY8, } } } @@ -419,6 +434,7 @@ impl From for Format { let pxfmt = match cam_fmt.format() { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new(b"GREY"), }; Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt) From d148dc208a91460acd3e51210a8e6cb0c22df105 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Thu, 12 May 2022 02:29:30 +0900 Subject: [PATCH 48/89] buffer impl, fixes for some tings, etc --- Cargo.toml | 12 ++++- src/backends/capture/gst_backend.rs | 14 ++++++ src/backends/capture/v4l2_backend.rs | 15 ++++-- src/buffer.rs | 68 ++++++++++++++++++++++++---- src/camera_traits.rs | 33 ++++++++++---- src/pixel_format.rs | 34 +------------- src/utils.rs | 53 ++++++++++++++-------- 7 files changed, 153 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 900a55e..b7d5111 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,13 +19,14 @@ crate-type = ["cdylib", "rlib"] [features] default = ["flume", "decoding"] +serialize = ["serde"] decoding = ["mozjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] # Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x # input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"] -input-opencv = ["opencv", "opencv/clang-runtime"] +input-opencv = ["opencv", "opencv/rgb", "rgb"] input-ipcam = ["input-opencv"] input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex", "parking_lot"] input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "wasm-rs-async-executor"] @@ -44,6 +45,10 @@ paste = "1.0" anymap = "1.0.0-beta.2" enum_dispatch = "0.3" +[dependencies.serde] +version = "1.0" +optional = true + [dependencies.flume] version = "0.10" optional = true @@ -74,7 +79,10 @@ optional = true [dependencies.opencv] version = "0.63" -features = ["clang-runtime"] +optional = true + +[dependencies.rgb] +version = "0.8" optional = true [dependencies.nokhwa-bindings-windows] diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index c8de249..60da317 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -374,6 +374,11 @@ impl GStreamerCaptureDevice { .insert(Resolution::new(width as u32, height as u32), fps_vec); } } + unsupported => { + return Err(NokhwaError::NotImplementedError(format!( + "Not supported frame format {unsupported:?}" + ))) + } } } } @@ -581,6 +586,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String { FrameFormat::YUYV => { format!("autovideosrc location=/dev/video{} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate()) } + _ => { + format!("unsupproted! if you see this, switch to something else!") + } } } @@ -593,6 +601,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String { FrameFormat::YUYV => { format!("v4l2src device=/dev/video{} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate()) } + _ => { + format!("unsupproted! if you see this, switch to something else!") + } } } @@ -605,6 +616,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String { FrameFormat::YUYV => { format!("ksvideosrc device_index={} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate()) } + _ => { + format!("unsupproted! if you see this, switch to something else!") + } } } diff --git a/src/backends/capture/v4l2_backend.rs b/src/backends/capture/v4l2_backend.rs index ebf3142..a5b9870 100644 --- a/src/backends/capture/v4l2_backend.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -286,7 +286,7 @@ impl<'a> V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), - FrameFormat::GRAY8 => FourCC::new("GRAY"), + FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; match v4l::video::Capture::enum_framesizes(&self.device, format) { @@ -329,10 +329,19 @@ impl<'a> V4LCaptureDevice<'a> { pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> { match (self.device.params(), self.device.format()) { (Ok(params), Ok(format)) => { - let new_format = CameraFormat::new(params.capabilities.) + // FIXME: actually handle the fractions?????? + self.camera_format = CameraFormat::new( + Resolution::new(format.width, format.height), + FrameFormat::from(format.fourcc), + params.interval.numerator, + ); + return Ok(()); } (_, _) => { - return Err(NokhwaError::GetPropertyError { property: "parameters".to_string(), error: why.to_string() }) + return Err(NokhwaError::GetPropertyError { + property: "parameters".to_string(), + error: why.to_string(), + }) } } } diff --git a/src/buffer.rs b/src/buffer.rs index 4c6eeba..3defad0 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -17,35 +17,83 @@ use crate::pixel_format::{PixelFormat, PixelFormats}; use crate::{FrameFormat, NokhwaError, Resolution}; use image::ImageBuffer; +#[cfg(feature = "input-opencv")] +use opencv::core::Mat; +#[cfg(feature = "input-opencv")] +use rgb::{FromSlice, RGB}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Hash, PartialOrd, PartialEq)] -#[cfg_attr("serde", Serialize, Deserialize)] +#[cfg_attr(feature = "serde", Serialize, Deserialize)] pub struct Buffer { resolution: Resolution, buffer: Vec, - + source_frame_format: FrameFormat, } impl Buffer { - pub fn new(res: Resolution, buf: Vec) -> Self { + pub fn new(res: Resolution, buf: Vec, source_frame_format: FrameFormat) -> Self { Self { resolution: res, buffer: buf, + source_frame_format, } } - - pub fn to_image_with_custom_format(self) -> Result>, NokhwaError> + + pub fn to_image_with_custom_format( + self, + ) -> Result>, NokhwaError> where - I: PixelFormat, + F: PixelFormat, { + if self.source_frame_format != F::CODE { + return Err(NokhwaError::ProcessFrameError { + src: self.source_frame_format, + destination: F::CODE.to_string(), + error: "Assertion failed, wrong source!".to_string(), + }); + } ImageBuffer::from_raw( self.resolution.width_x, self.resolution.height_y, self.buffer, - ).ok_or(NokhwaError::ProcessFrameError { - src: , - destination: "".to_string(), - error: "".to_string() + ) + .ok_or(NokhwaError::ProcessFrameError { + src: F::CODE, + destination: stringify!(I::Output).to_string(), + error: "Buffer too small".to_string(), }) } + + #[cfg(feature = "input-opencv")] + pub fn to_opencv_mat(self) -> Result { + Ok(match self.source_frame_format { + FrameFormat::MJPEG | FrameFormat::YUYV => Mat::from_slice_2d( + self.buffer + .as_rgb() + .chunks(self.resolution.height_y as usize) + .collect::<&[&[RGB]]>(), + ), + FrameFormat::GRAY8 => Mat::from_slice_2d( + self.buffer + .chunks(self.resolution.height_y as usize) + .collect::<&[&[u8]]>(), + ), + } + .map_err(|why| NokhwaError::ProcessFrameError { + src: self.source_frame_format, + destination: "OpenCV Mat".to_string(), + error: why.to_string(), + })?) + } + pub fn resolution(&self) -> Resolution { + self.resolution + } + pub fn buffer(&self) -> &[u8] { + &self.buffer + } + pub fn source_frame_format(&self) -> FrameFormat { + self.source_frame_format + } } diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 749a4fe..045d690 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -14,14 +14,15 @@ * limitations under the License. */ +use crate::buffer::Buffer; +use crate::pixel_format::PixelFormat; use crate::{ error::NokhwaError, utils::{CameraFormat, CameraInfo, FrameFormat, Resolution}, CameraControl, CaptureAPIBackend, KnownCameraControls, }; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; - -use crate::pixel_format::PixelFormat; +use opencv::imgproc::FloodFillFlags; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -48,7 +49,7 @@ pub trait CaptureBackendTrait { fn camera_info(&self) -> &CameraInfo; /// Gets the current [`CameraFormat`]. - fn camera_format(&self) -> CameraFormat; + fn camera_format(&self) -> Result; /// Will set the current [`CameraFormat`] /// This will reset the current stream if used while stream is opened. @@ -70,7 +71,7 @@ pub trait CaptureBackendTrait { fn compatible_fourcc(&mut self) -> Result, NokhwaError>; /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). - fn resolution(&self) -> Resolution; + fn resolution(&self) -> Result; /// Will set the current [`Resolution`] /// This will reset the current stream if used while stream is opened. @@ -79,7 +80,7 @@ pub trait CaptureBackendTrait { fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>; /// Gets the current camera framerate (See: [`CameraFormat`]). - fn frame_rate(&self) -> u32; + fn frame_rate(&self) -> Result; /// Will set the current framerate /// This will reset the current stream if used while stream is opened. @@ -88,7 +89,7 @@ pub trait CaptureBackendTrait { fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). - fn frame_format(&self) -> FrameFormat; + fn frame_format(&self) -> Result; /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. @@ -155,7 +156,16 @@ pub trait CaptureBackendTrait { /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, /// this will error. - fn frame(&mut self) -> Result, Vec>, NokhwaError>; + fn frame(&mut self) -> Result; + + /// Will get a frame from the camera as a Raw RGB image buffer. Depending on the backend, if you have not called [`open_stream()`](CaptureBackendTrait::open_stream()) before you called this, + /// it will either return an error. + /// # Errors + /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, + /// or if the PixelFormat is invalid, this will error. + fn frame_typed( + &mut self, + ) -> Result>, NokhwaError>; /// Will get a frame from the camera **without** any processing applied, meaning you will usually get a frame you need to decode yourself. /// # Errors @@ -174,11 +184,14 @@ pub trait CaptureBackendTrait { /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. - fn write_frame_to_buffer( + fn write_frame_to_buffer( &mut self, buffer: &mut [u8], - convert_rgba: bool, - ) -> Result { + write_alpha: bool, + ) -> Result + where + F: PixelFormat, + { let resolution = self.resolution(); let frame = self.frame_raw()?; if convert_rgba { diff --git a/src/pixel_format.rs b/src/pixel_format.rs index d57bb36..ac82364 100644 --- a/src/pixel_format.rs +++ b/src/pixel_format.rs @@ -15,6 +15,7 @@ */ use crate::buffer_output::{BufferOutput, GrayU8, RgbU8}; +use crate::FrameFormat; use image::{Luma, Pixel, Rgb}; use std::fmt::Debug; use std::hash::Hash; @@ -22,36 +23,5 @@ use std::hash::Hash; pub trait PixelFormat: Copy + Clone + Debug + Default + Hash + Send + Sync { type Output: Pixel; - const CODE: &'static str; - - fn code(&self) -> &'static str { - CODE - } -} - -#[derive(Copy, Clone, Debug, Default, Hash)] -pub struct Mjpeg; - -impl PixelFormat for Mjpeg { - type Output = Rgb; - - const CODE: &'static str = "MJPG"; -} - -#[derive(Copy, Clone, Debug, Default, Hash)] -pub struct Yuyv; - -impl PixelFormat for Yuyv { - type Output = Rgb; - - const CODE: &'static str = "YUYV"; -} - -#[derive(Copy, Clone, Debug, Default, Hash)] -pub struct Gray; - -impl PixelFormat for Gray { - type Output = Luma; - - const CODE: &'static str = "GRAY"; + const SUPPORTED_CODES: &'static [FrameFormat]; } diff --git a/src/utils.rs b/src/utils.rs index 41623cb..d6e6876 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,15 +14,8 @@ * limitations under the License. */ +use crate::pixel_format::PixelFormat; use crate::NokhwaError; -use std::{ - borrow::{Borrow, Cow}, - cmp::Ordering, - fmt::{Display, Formatter}, -}; -#[cfg(feature = "output-wasm")] -use wasm_bindgen::prelude::wasm_bindgen; - #[cfg(any( all( feature = "input-avfoundation", @@ -45,11 +38,20 @@ use nokhwa_bindings_windows::{ MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls, MediaFoundationDeviceDescriptor, }; +use serde::{Deserialize, Serialize}; +#[cfg(feature = serde)] +use serde::{Deserialize, Serialize}; +use std::{ + borrow::{Borrow, Cow}, + cmp::Ordering, + fmt::{Display, Formatter}, +}; #[cfg(feature = "input-uvc")] use uvc::StreamFormat; #[cfg(all(feature = "input-v4l", target_os = "linux"))] use v4l::{control::Description, Format, FourCC}; -use crate::pixel_format::PixelFormat; +#[cfg(feature = "output-wasm")] +use wasm_bindgen::prelude::wasm_bindgen; /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. /// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) @@ -57,6 +59,7 @@ use crate::pixel_format::PixelFormat; /// # JS-WASM /// This is exported as `FrameFormat` #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FrameFormat { MJPEG, YUYV, @@ -64,7 +67,7 @@ pub enum FrameFormat { } impl Display for FrameFormat { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { FrameFormat::MJPEG => { write!(f, "MJPEG") @@ -79,9 +82,12 @@ impl Display for FrameFormat { } } -impl

From

for FrameFormat where P: PixelFormat { - fn from(px: P) -> Self { - match P:: +impl

From

for FrameFormat +where + P: PixelFormat, +{ + fn from(_: P) -> Self { + P::CODE } } @@ -171,6 +177,7 @@ impl From for AVFourCC { /// # JS-WASM /// This is exported as `JSResolution` #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSResolution))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] pub struct Resolution { pub width_x: u32, @@ -225,7 +232,7 @@ impl Resolution { } impl Display for Resolution { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}x{}", self.x(), self.y()) } } @@ -296,6 +303,7 @@ impl From 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, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CameraFormat { resolution: Resolution, format: FrameFormat, @@ -474,6 +482,7 @@ impl From for CaptureDeviceFormatDescriptor { /// This is exported as a `JSCameraInfo`. #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))] #[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CameraInfo { human_name: String, description: String, @@ -488,10 +497,13 @@ impl CameraInfo { /// 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 + ?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. pub fn new( - human_name: &(impl AsRef + ?Sized), - description: &(impl AsRef + ?Sized), - misc: &(impl AsRef + ?Sized), + human_name: impl AsRef, + description: impl AsRef, + misc: impl AsRef, index: CameraIndex, ) -> Self { CameraInfo { @@ -648,6 +660,7 @@ impl From for CameraInfo { /// These can control the picture brightness, etc.
/// Note that not all backends/devices support all these. Run [`supported_camera_controls()`](crate::CaptureBackendTrait::supported_camera_controls) to see which ones can be set. #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KnownCameraControls { Brightness, Contrast, @@ -737,7 +750,7 @@ impl From for KnownCameraControls { } #[cfg(all(feature = "input-v4l", target_os = "linux"))] -impl std::convert::TryFrom for KnownCameraControls { +impl TryFrom for KnownCameraControls { type Error = NokhwaError; fn try_from(value: Description) -> Result { @@ -786,6 +799,7 @@ impl Display for KnownCameraControlFlag { /// NOTE: Assume the values for `min` and `max` as **non-inclusive**!. /// E.g. if the [`CameraControl`] says `min` is 100, the minimum is actually 101. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CameraControl { control: KnownCameraControls, min: i32, @@ -1019,6 +1033,7 @@ impl Ord for CameraControl { /// - `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)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum CaptureAPIBackend { Auto, AVFoundation, @@ -1032,7 +1047,7 @@ pub enum CaptureAPIBackend { } impl Display for CaptureAPIBackend { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let self_str = format!("{:?}", self); write!(f, "{}", self_str) } From cea3de73a2d74857e14f8fb5750b86c1c860bd55 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sun, 22 May 2022 18:54:28 +0900 Subject: [PATCH 49/89] use enum dispatch, manually implement PR #38, update camera trait, camera, and backends (todo), update camera controls (todo), add buffer to support other types of image e.g. GRAY --- Cargo.toml | 2 +- src/backends/capture/avfoundation.rs | 16 +- src/backends/capture/gst_backend.rs | 18 +-- src/backends/capture/opencv_backend.rs | 119 +++++++------- src/backends/capture/v4l2_backend.rs | 74 +++++---- src/buffer.rs | 2 +- src/camera.rs | 164 +++++++------------ src/camera_traits.rs | 116 ++++++++++---- src/lib.rs | 1 + src/threaded.rs | 62 ++++---- src/utils.rs | 212 ++++++++++++++----------- 11 files changed, 405 insertions(+), 381 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b7d5111..a050fd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ version = "0.9" optional = true [dependencies.v4l] -version = "0.12" +version = "0.13" optional = true [dependencies.v4l2-sys-mit] diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index a4fa733..e23b291 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -15,9 +15,8 @@ */ use crate::{ - mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo, - CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, - Resolution, + mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, + CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use nokhwa_bindings_macos::avfoundation::{ @@ -49,18 +48,13 @@ impl AVFoundationCaptureDevice { /// /// 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. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`. - pub fn new( - index: &CameraIndex, - camera_format: Option, - ) -> Result { + /// 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) -> Result { 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 => { @@ -91,7 +85,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: &CameraIndex, + index: usize, width: u32, height: u32, fps: u32, diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index 60da317..af71e33 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -15,9 +15,8 @@ */ use crate::{ - mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo, - CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, - Resolution, + mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, + CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; use glib::Quark; use gstreamer::{ @@ -62,8 +61,8 @@ impl GStreamerCaptureDevice { /// /// 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. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`. - pub fn new(index: &CameraIndex, cam_fmt: Option) -> Result { + /// 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) -> Result { let camera_format = match cam_fmt { Some(fmt) => fmt, None => CameraFormat::default(), @@ -121,7 +120,7 @@ impl GStreamerCaptureDevice { &DeviceExt::display_name(&device), &DeviceExt::device_class(&device), &"", - CameraIndex::Index(index), + index, ), caps, ) @@ -144,12 +143,7 @@ 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: &CameraIndex, - width: u32, - height: u32, - fps: u32, - ) -> Result { + pub fn new_with(index: usize, width: u32, height: u32, fps: u32) -> Result { let cam_fmt = CameraFormat::new(Resolution::new(width, height), FrameFormat::MJPEG, fps); GStreamerCaptureDevice::new(index, Some(cam_fmt)) } diff --git a/src/backends/capture/opencv_backend.rs b/src/backends/capture/opencv_backend.rs index ee2f7e6..0997075 100644 --- a/src/backends/capture/opencv_backend.rs +++ b/src/backends/capture/opencv_backend.rs @@ -14,9 +14,10 @@ * limitations under the License. */ +use crate::pixel_format::PixelFormat; use crate::{ - CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, - FrameFormat, KnownCameraControls, NokhwaError, Resolution, + CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, + KnownCameraControls, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use opencv::{ @@ -26,7 +27,7 @@ use opencv::{ CAP_MSMF, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2, }, }; -use std::{any::Any, borrow::Cow, collections::HashMap, ops::Deref}; +use std::{any::Any, borrow::Cow, collections::HashMap}; /// Converts $from into $to /// Example usage: @@ -50,7 +51,6 @@ macro_rules! tryinto_num { }}; } -// TODO: Define behaviour for IPCameras. /// The backend struct that interfaces with `OpenCV`. Note that an `opencv` matching the version that this was either compiled on must be present on the user's machine. (usually 4.5.2 or greater) /// For more information, please see [`opencv-rust`](https://github.com/twistedfall/opencv-rust) and [`OpenCV VideoCapture Docs`](https://docs.opencv.org/4.5.2/d8/dfe/classcv_1_1VideoCapture.html). /// @@ -71,7 +71,7 @@ macro_rules! tryinto_num { #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-opencv")))] pub struct OpenCvCaptureDevice { camera_format: CameraFormat, - camera_location: CameraIndex, + camera_location: usize, camera_info: CameraInfo, api_preference: i32, video_capture: VideoCapture, @@ -79,7 +79,7 @@ pub struct OpenCvCaptureDevice { #[allow(clippy::must_use_candidate)] impl OpenCvCaptureDevice { - /// Creates a new capture device using the `OpenCV` backend. You can either use an [`Index`](CameraIndexType::Index) or [`IPCamera`](CameraIndexType::IPCamera). + /// Creates a new capture device using the `OpenCV` backend. /// /// Indexes are gives to devices by the OS, and usually numbered by order of discovery. /// @@ -89,14 +89,13 @@ 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@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera. + /// If `camera_format` is `None`, it will be spawned with with 640x480@15 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: &CameraIndex, + index: usize, cfmt: Option, api_pref: Option, ) -> Result { @@ -111,42 +110,28 @@ impl OpenCvCaptureDevice { None => CameraFormat::default(), }; - let mut video_capture = match camera_location.clone() { - CameraIndex::Index(idx) => { - let vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) { - Ok(vc) => vc, - Err(why) => { - return Err(NokhwaError::OpenDeviceError( - idx.to_string(), - why.to_string(), - )) - } - }; - vid_cap + let mut video_capture = match VideoCapture::new(tryinto_num!(i32, idx), api) { + Ok(vc) => vc, + Err(why) => { + return Err(NokhwaError::OpenDeviceError( + idx.to_string(), + why.to_string(), + )) } - CameraIndex::String(ip) => match VideoCapture::from_file(ip.as_ref(), CAP_ANY) { - Ok(vc) => vc, - Err(why) => { - return Err(NokhwaError::OpenDeviceError( - ip.to_string(), - why.to_string(), - )) - } - }, }; - set_properties(&mut video_capture, camera_format, camera_location)?; + set_properties(&mut video_capture, camera_format, index)?; let camera_info = CameraInfo::new( - &format!("OpenCV Capture Device {}", camera_location), - &camera_location.to_string(), - "", - camera_location.clone(), + format!("OpenCV Capture Device {}", index), + index.to_string(), + "".to_string(), + index as usize, ); Ok(OpenCvCaptureDevice { camera_format, - camera_location: camera_location.clone(), + camera_location: index, camera_info, api_preference: api, video_capture, @@ -161,18 +146,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@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera. + /// If `camera_format` is `None`, it will be spawned with with 640x480@15 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: impl Deref) -> Result { - let camera_location = CameraIndex::String(ip.to_string()); - OpenCvCaptureDevice::new(&camera_location, None, None) + pub fn new_ip_camera(ip: String) -> Result { + let camera_location = CameraIndexType::IPCamera(ip); + 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@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera. + /// If `camera_format` is `None`, it will be spawned with with 640x480@15 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( @@ -180,41 +165,39 @@ impl OpenCvCaptureDevice { cfmt: Option, api_pref: Option, ) -> Result { - let camera_location = CameraIndex::Index(tryinto_num!(u32, index)); - OpenCvCaptureDevice::new(&camera_location, cfmt, api_pref) + let camera_location = CameraIndexType::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@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera. + /// If `camera_format` is `None`, it will be spawned with with 640x480@15 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: &CameraIndex, - cfmt: Option, - ) -> Result { - OpenCvCaptureDevice::new(index, cfmt, None) + pub fn new_autopref(index: usize, cfmt: Option) -> Result { + let camera_location = CameraIndexType::Index(tryinto_num!(u32, index)); + OpenCvCaptureDevice::new(camera_location, cfmt, None) } /// Gets weather said capture device is an `IPCamera`. pub fn is_ip_camera(&self) -> bool { match self.camera_location { - CameraIndex::Index(_) => false, - CameraIndex::String(_) => true, + CameraIndexType::Index(_) => false, + CameraIndexType::IPCamera(_) => true, } } /// Gets weather said capture device is an OS-based indexed camera. pub fn is_index_camera(&self) -> bool { match self.camera_location { - CameraIndex::Index(_) => true, - CameraIndex::String(_) => false, + CameraIndexType::Index(_) => true, + CameraIndexType::IPCamera(_) => false, } } /// Gets the camera location - pub fn camera_location(&self) -> CameraIndex { + pub fn camera_location(&self) -> CameraIndexType { self.camera_location.clone() } @@ -342,6 +325,10 @@ impl OpenCvCaptureDevice { } impl CaptureBackendTrait for OpenCvCaptureDevice { + fn init(&mut self) -> Result { + todo!() + } + fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::OpenCv } @@ -368,7 +355,7 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { self.camera_format = new_fmt; - if let Err(why) = set_properties(&mut self.video_capture, new_fmt, &self.camera_location) { + if let Err(why) = set_properties(&mut self.video_capture, new_fmt, self.camera_location) { self.camera_format = current_format; return Err(why); } @@ -523,7 +510,7 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { #[allow(clippy::cast_possible_wrap)] fn open_stream(&mut self) -> Result<(), NokhwaError> { match self.camera_location.clone() { - CameraIndex::Index(idx) => { + CameraIndexType::Index(idx) => { match self .video_capture .open_1(idx as i32, get_api_pref_int() as i32) @@ -537,15 +524,15 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { } } } - CameraIndex::String(ip) => { + CameraIndexType::IPCamera(ip) => { match self .video_capture - .open_file(ip.as_ref(), get_api_pref_int() as i32) + .open_file(&*ip, get_api_pref_int() as i32) { Ok(_) => {} Err(why) => { return Err(NokhwaError::OpenDeviceError( - ip.to_string(), + ip, format!("Failed to open device: {}", why), )) } @@ -601,6 +588,12 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { Ok(image_buf) } + fn frame_typed( + &mut self, + ) -> Result>, NokhwaError> { + todo!() + } + fn frame_raw(&mut self) -> Result, NokhwaError> { let cow = self.raw_frame_vec()?; Ok(cow) @@ -614,12 +607,6 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { } } -impl Drop for OpenCvCaptureDevice { - fn drop(&mut self) { - let _close_err = self.stop_stream(); - } -} - fn get_api_pref_int() -> u32 { match std::env::consts::OS { "linux" => CAP_V4L2 as u32, @@ -636,7 +623,7 @@ fn get_api_pref_int() -> u32 { fn set_properties( _vc: &mut VideoCapture, _camera_format: CameraFormat, - _camera_location: &CameraIndex, + _camera_location: usize, ) -> Result<(), NokhwaError> { Ok(()) } diff --git a/src/backends/capture/v4l2_backend.rs b/src/backends/capture/v4l2_backend.rs index a5b9870..11f7506 100644 --- a/src/backends/capture/v4l2_backend.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -17,7 +17,7 @@ use crate::{ error::NokhwaError, mjpeg_to_rgb, - utils::{CameraFormat, CameraIndex, CameraInfo}, + utils::{CameraFormat, CameraInfo}, yuyv422_to_rgb, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag, KnownCameraControls, Resolution, }; @@ -28,14 +28,17 @@ use v4l::{ frameinterval::FrameIntervalEnum, framesize::FrameSizeEnum, io::traits::CaptureStream, - prelude::*, video::{capture::Parameters, Capture}, Format, FourCC, + Device, }; +use crate::pixel_format::PixelFormat; use std::any::Any; pub use v4l::control::{Control, Description, Flags}; +use v4l::prelude::MmapStream; use v4l::video::Output; +use crate::buffer::Buffer; /// Generates a camera control from a device and a description of control /// # Error @@ -171,6 +174,7 @@ fn clone_control(ctrl: &Control) -> Control { /// - The `Any` type for `control` for [`set_raw_camera_control()`](CaptureBackendTrait::set_raw_camera_control) is [`u32`] and [`Control`] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-v4l")))] pub struct V4LCaptureDevice<'a> { + initialized: bool, camera_format: CameraFormat, camera_info: CameraInfo, device: Device, @@ -180,11 +184,12 @@ pub struct V4LCaptureDevice<'a> { impl<'a> V4LCaptureDevice<'a> { /// Creates a new capture device using the `V4L2` 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 `camera_format` is `None`, it will be spawned with a random [`CameraFormat`] as determined by [`init()`](crate::CaptureBackendTrait::init). + /// + /// If `camera_format` is not `None`, the camera will try to use it when you call [`init()`](crate::CaptureBackendTrait::init). /// # Errors - /// 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, cam_fmt: Option) -> Result { - let index = index.as_index()?; + /// 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) -> Result { let device = match Device::new(index as usize) { Ok(dev) => dev, Err(why) => { @@ -196,7 +201,7 @@ impl<'a> V4LCaptureDevice<'a> { }; let camera_info = match device.query_caps() { - Ok(caps) => CameraInfo::new(&caps.card, "", &caps.driver, CameraIndex::Index(index)), + Ok(caps) => CameraInfo::new(&caps.card, "", &caps.driver, usize), Err(why) => { return Err(NokhwaError::GetPropertyError { property: "Capabilities".to_string(), @@ -261,6 +266,7 @@ impl<'a> V4LCaptureDevice<'a> { } Ok(V4LCaptureDevice { + initialized: false, camera_format, camera_info, device, @@ -268,11 +274,11 @@ impl<'a> V4LCaptureDevice<'a> { }) } - /// Create a new `V4L2` Camera with desired settings. + /// Create a new `V4L2` Camera with desired settings. This may or may not work. /// # Errors /// This function will error if the camera is currently busy or if `V4L2` can't read device information. pub fn new_with( - index: &CameraIndex, + index: usize, width: u32, height: u32, fps: u32, @@ -289,7 +295,8 @@ impl<'a> V4LCaptureDevice<'a> { FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; - match v4l::video::Capture::enum_framesizes(&self.device, format) { + // match Capture::enum_framesizes(&self.device, format) { + match self.device.enum_framesizes(format) { Ok(frame_sizes) => { let mut resolutions = vec![]; for frame_size in frame_sizes { @@ -327,7 +334,7 @@ impl<'a> V4LCaptureDevice<'a> { /// Force refreshes the inner [`CameraFormat`] state. pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> { - match (self.device.params(), self.device.format()) { + match (Capture::format(&self.device), self.device.format()) { (Ok(params), Ok(format)) => { // FIXME: actually handle the fractions?????? self.camera_format = CameraFormat::new( @@ -335,10 +342,10 @@ impl<'a> V4LCaptureDevice<'a> { FrameFormat::from(format.fourcc), params.interval.numerator, ); - return Ok(()); + Ok(()) } (_, _) => { - return Err(NokhwaError::GetPropertyError { + Err(NokhwaError::GetPropertyError { property: "parameters".to_string(), error: why.to_string(), }) @@ -348,6 +355,23 @@ impl<'a> V4LCaptureDevice<'a> { } impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { + fn init(&mut self) -> Result { + let camera_format = self.camera_format; + let compatible = self.compatible_camera_formats()?; + if compatible.len() == 0 { + return Err(NokhwaError::InitializeError { backend: self.backend(), error: "Could not find any compatible camera formats!".to_string() }) + } + if compatible.contains(&camera_format) { + self.initialized = true; + return Ok(camera_format) + } else { + self.initialized = true; + let new_fmt = compatible[0]; + self.camera_format = new_fmt; + Ok(new_fmt) + } + } + fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::Video4Linux } @@ -434,7 +458,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), - FrameFormat::GRAY8 => {} + FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; let mut res_map = HashMap::new(); for res in resolutions { @@ -605,7 +629,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { } }; - if let Err(why) = self.device.set_control(id, Control::Value(control.value())) { + if let Err(why) = self.device.set_control(Control { id, value: Value }) { return Err(NokhwaError::SetPropertyError { property: format!("{} V4L2ID {}", control.control(), id), value: control.value().to_string(), @@ -708,25 +732,19 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { self.stream_handle.is_some() } - fn frame(&mut self) -> Result, Vec>, NokhwaError> { + fn frame(&mut self) -> Result { let cam_fmt = self.camera_format; let raw_frame = self.frame_raw()?; let conv = match cam_fmt.format() { FrameFormat::MJPEG => mjpeg_to_rgb(&raw_frame, false)?, FrameFormat::YUYV => yuyv422_to_rgb(&raw_frame, false)?, + FrameFormat::GRAY8 => raw_frame.to_vec(), }; - let image_buf = - match ImageBuffer::from_vec(cam_fmt.width(), cam_fmt.height(), conv) { - Some(buf) => { - let rgb_buf: ImageBuffer, Vec> = buf; - rgb_buf - } - None => return Err(NokhwaError::ReadFrameError( - "ImageBuffer is not large enough! This is probably a bug, please report it!" - .to_string(), - )), - }; - Ok(image_buf) + Ok(Buffer::new(cam_fmt.resolution(), conv, cam_fmt.format())) + } + + fn frame_typed(&mut self) -> Result>, NokhwaError> { + self.frame()?.to_image_with_custom_format::() } fn frame_raw(&mut self) -> Result, NokhwaError> { diff --git a/src/buffer.rs b/src/buffer.rs index 3defad0..20a1c57 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -use crate::pixel_format::{PixelFormat, PixelFormats}; +use crate::pixel_format::{PixelFormat}; use crate::{FrameFormat, NokhwaError, Resolution}; use image::ImageBuffer; #[cfg(feature = "input-opencv")] diff --git a/src/camera.rs b/src/camera.rs index 6033440..5759256 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -15,11 +15,11 @@ */ use crate::{ - CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, - KnownCameraControls, NokhwaError, Resolution, + buffer::Buffer, BackendsEnum, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, + CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, }; use image::buffer::ConvertBuffer; -use image::{ImageBuffer, Rgb, RgbaImage}; +use image::RgbaImage; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -29,19 +29,13 @@ 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 -where - C: CaptureBackendTrait, -{ +pub struct Camera { idx: usize, - backend: C, + backend: BackendsEnum, backend_api: CaptureAPIBackend, } -impl Camera -where - C: CaptureBackendTrait, -{ +impl Camera { /// 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). @@ -94,7 +88,7 @@ where { self.backend.stop_stream()?; } - let new_camera_format = self.backend.camera_format(); + let new_camera_format = self.backend.camera_format()?; let new_camera = init_camera(new_idx, Some(new_camera_format), self.backend_api)?; self.backend = new_camera; Ok(()) @@ -113,7 +107,7 @@ where { self.backend.stop_stream()?; } - let new_camera_format = self.backend.camera_format(); + let new_camera_format = self.backend.camera_format()?; let new_camera = init_camera(self.idx, Some(new_camera_format), new_backend)?; self.backend = new_camera; Ok(()) @@ -126,13 +120,19 @@ where } /// Gets the current [`CameraFormat`]. - #[must_use] - pub fn camera_format(&self) -> CameraFormat { + pub fn cached_camera_format(&self) -> CameraFormat { + self.backend.cached_camera_format() + } + + /// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed. + pub fn camera_format(&self) -> Result { self.backend.camera_format() } /// Will set the current [`CameraFormat`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new camera format, this will return an error. pub fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> { @@ -174,13 +174,19 @@ where } /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). - #[must_use] - pub fn resolution(&self) -> Resolution { + pub fn cached_resolution(&self) -> Resolution { + self.backend.cached_resolution() + } + + /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. + pub fn resolution(&self) -> Result { self.backend.resolution() } /// Will set the current [`Resolution`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new resolution, this will return an error. pub fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> { @@ -188,13 +194,19 @@ where } /// Gets the current camera framerate (See: [`CameraFormat`]). - #[must_use] - pub fn frame_rate(&self) -> u32 { + pub fn cached_frame_rate(&self) -> u32 { + self.backend.cached_frame_rate() + } + + /// Gets the current camera framerate (See: [`CameraFormat`]). + pub fn frame_rate(&self) -> Result { self.backend.frame_rate() } /// Will set the current framerate /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new framerate, this will return an error. pub fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { @@ -202,13 +214,19 @@ where } /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). - #[must_use] - pub fn frame_format(&self) -> FrameFormat { + pub fn cached_frame_format(&self) -> FrameFormat { + self.backend.cached_frame_format() + } + + /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. + pub fn frame_format(&self) -> Result { self.backend.frame_format() } /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new frame format, this will return an error. pub fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { @@ -351,7 +369,7 @@ where /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, /// this will error. - pub fn frame(&mut self) -> Result, Vec>, NokhwaError> { + pub fn frame(&mut self) -> Result { self.backend.frame() } @@ -365,36 +383,15 @@ where } } - /// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed. - #[must_use] - pub fn min_buffer_size(&self, rgba: bool) -> usize { - let resolution = self.backend.resolution(); - let w = resolution.width() as usize; - let h = resolution.height() as usize; - let c = if rgba { 4 } else { 3 }; - w * h * c - } - /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. - pub fn frame_to_buffer( + pub fn write_frame_to_buffer( &mut self, buffer: &mut [u8], - convert_rgba: bool, - ) -> Result<(), NokhwaError> { - let camera_format = self.backend.camera_format(); - let format = camera_format.format(); - let raw_frame = self.frame_raw()?; - match format { - FrameFormat::MJPEG => crate::utils::buf_mjpeg_to_rgb(&raw_frame, buffer, convert_rgba)?, - FrameFormat::YUYV => { - crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? - } - FrameFormat::GRAY8 => {} - }; - - Ok(()) + write_alpha: bool, + ) -> Result { + self.backend.write_frame_to_buffer(buffer, write_alpha) } #[cfg(feature = "output-wgpu")] @@ -402,59 +399,13 @@ where /// 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<'a, F: PixelFormat>( &mut self, device: &WgpuDevice, queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { - use std::{convert::TryFrom, num::NonZeroU32}; - let frame = self.frame()?; - let rgba_frame: RgbaImage = frame.convert(); - - 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, - }); - - let width_nonzero = match NonZeroU32::try_from(4 * rgba_frame.width()) { - Ok(w) => Some(w), - Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), - }; - - let height_nonzero = match NonZeroU32::try_from(rgba_frame.height()) { - Ok(h) => Some(h), - Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), - }; - - queue.write_texture( - ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: TextureAspect::All, - }, - &rgba_frame.to_vec(), - ImageDataLayout { - offset: 0, - bytes_per_row: width_nonzero, - rows_per_image: height_nonzero, - }, - texture_size, - ); - - Ok(texture) + self.backend.frame_texture(device, queue, label) } /// Will drop the stream. @@ -465,10 +416,7 @@ where } } -impl Drop for Camera -where - C: CaptureBackendTrait, -{ +impl Drop for Camera { fn drop(&mut self) { self.stop_stream().unwrap(); } @@ -505,15 +453,15 @@ macro_rules! cap_impl_fn { $( paste::paste! { #[cfg ($cfg) ] - fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option> { + fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option> { use crate::backends::capture::$backend; match <$backend>::$init_fn(idx, setting) { - Ok(cap) => Some(Ok(Box::new(cap))), + Ok(cap) => Some(Ok(cp.into())), Err(why) => Some(Err(why)), } } #[cfg(not( $cfg ))] - fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option> { + fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option> { None } } @@ -604,7 +552,7 @@ macro_rules! cap_impl_matches { } cap_impl_fn! { - (GStreamerCaptureDevice, new, feature = "input-gst", gst), + // (GStreamerCaptureDevice, new, feature = "input-gst", gst), (OpenCvCaptureDevice, new_autopref, feature = "input-opencv", opencv), // (UVCCaptureDevice, create, feature = "input-uvc", uvc), (V4LCaptureDevice, new, all(feature = "input-v4l", target_os = "linux"), v4l), @@ -612,22 +560,22 @@ cap_impl_fn! { (AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation) } -fn init_camera( +fn init_camera( index: usize, format: Option, backend: CaptureAPIBackend, -) -> Result { +) -> Result { let camera_backend = cap_impl_matches! { backend, index, format, ("input-v4l", Video4Linux, init_v4l), ("input-msmf", MediaFoundation, init_msmf), ("input-avfoundation", AVFoundation, init_avfoundation), // ("input-uvc", UniversalVideoClass, init_uvc), - ("input-gst", GStreamer, init_gst), + // ("input-gst", GStreamer, init_gst), ("input-opencv", OpenCv, init_opencv) }; Ok(camera_backend) } #[cfg(feature = "output-threaded")] -unsafe impl Send for Camera where C: CaptureBackendTrait {} +unsafe impl Send for Camera {} diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 045d690..8da5a53 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -18,12 +18,13 @@ use crate::buffer::Buffer; use crate::pixel_format::PixelFormat; use crate::{ error::NokhwaError, - utils::{CameraFormat, CameraInfo, FrameFormat, Resolution}, + frame_formats, + utils::{CameraFormat, CameraInfo, FrameFormat, Resolution, buf_mjpeg_to_rgb, buf_yuyv422_to_rgb}, CameraControl, CaptureAPIBackend, KnownCameraControls, }; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; -use opencv::imgproc::FloodFillFlags; use std::{any::Any, borrow::Cow, collections::HashMap}; +use enum_dispatch::enum_dispatch; #[cfg(feature = "output-wgpu")] use wgpu::{ Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue, @@ -31,6 +32,14 @@ use wgpu::{ TextureUsages, }; +#[enum_dispatch] +pub(crate) enum BackendsEnum { + MSMF, + AVF, + V4L2, + OCV, +} + /// 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,6 +47,7 @@ use wgpu::{ /// - Backends, if not provided with a camera format, will be spawned with 640x480@15 FPS, MJPEG [`CameraFormat`]. /// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::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()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera. +#[enum_dispatch(BackendsEnum)] pub trait CaptureBackendTrait { /// Initializes the camera. You must call this before any other function. fn init(&mut self) -> Result; @@ -49,10 +59,15 @@ pub trait CaptureBackendTrait { fn camera_info(&self) -> &CameraInfo; /// Gets the current [`CameraFormat`]. + fn cached_camera_format(&self) -> CameraFormat; + + /// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed. fn camera_format(&self) -> Result; /// Will set the current [`CameraFormat`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new camera format, this will return an error. fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError>; @@ -65,34 +80,69 @@ pub trait CaptureBackendTrait { fourcc: FrameFormat, ) -> Result>, NokhwaError>; + fn compatible_camera_formats(&mut self) -> Result, NokhwaError> { + let mut compatible_formats = vec![]; + frame_formats().map(|ff| { + if let Ok(mut fmts) = self.compatible_list_by_resolution(ff).map(|compatible| { + compatible + .into_iter() + .map(|(res, fps)| { + fps.into_iter().map(|rate| CameraFormat { + resolution: res, + format: ff, + frame_rate: rate, + }) + }) + .collect::>() + }) { + compatible_formats.append(&mut fmts) + } + }) + } + /// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most. /// # Errors /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError)). fn compatible_fourcc(&mut self) -> Result, NokhwaError>; /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). + fn cached_resolution(&self) -> Resolution; + + /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. fn resolution(&self) -> Result; /// Will set the current [`Resolution`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new resolution, this will return an error. fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>; /// Gets the current camera framerate (See: [`CameraFormat`]). + fn cached_frame_rate(&self) -> u32; + + /// Gets the current camera framerate (See: [`CameraFormat`]). This will force refresh to the current latest if it has changed. fn frame_rate(&self) -> Result; /// Will set the current framerate /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new framerate, this will return an error. fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). + fn cached_frame_format(&self) -> FrameFormat; + + /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. fn frame_format(&self) -> Result; /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. + /// + /// This will also update the cache. /// # Errors /// If you started the stream and the camera rejects the new frame format, this will return an error. fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>; @@ -172,46 +222,44 @@ pub trait CaptureBackendTrait { /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. fn frame_raw(&mut self) -> Result, NokhwaError>; - /// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed. - fn min_buffer_size(&self, rgba: bool) -> usize { - let resolution = self.resolution(); - if rgba { - return (resolution.width() * resolution.height() * 4) as usize; + /// The minimum buffer size needed to write the current frame. If `alpha` is true, it will instead return the minimum size of the RGBA buffer needed. + fn decoded_buffer_size(&self, alpha: bool) -> Result { + let cfmt = self.camera_format()?; + let resolution = cfmt.resolution(); + let pxwidth = match cfmt.format() { + FrameFormat::MJPEG | FrameFormat::YUYV => 3, + FrameFormat::GRAY8 => 1, + }; + if alpha { + return (resolution.width() * resolution.height() * (pxwidth + 1)) as usize; } - (resolution.width() * resolution.height() * 3) as usize + (resolution.width() * resolution.height() * pxwidth) as usize } /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. /// # Errors /// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error. - fn write_frame_to_buffer( + fn write_frame_to_buffer( &mut self, buffer: &mut [u8], write_alpha: bool, ) -> Result - where - F: PixelFormat, { - let resolution = self.resolution(); + let cfmt = self.camera_format()?; let frame = self.frame_raw()?; - if convert_rgba { - let image_data = - match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) { - Some(image) => { - let image: ImageBuffer, Cow<[u8]>> = image; - image - } - None => { - return Err(NokhwaError::ReadFrameError( - "Frame Cow Too Small".to_string(), - )) - } + let data = match cfmt.format() { + FrameFormat::MJPEG => buf_mjpeg_to_rgb(&frame, buffer, write_alpha), + FrameFormat::YUYV => buf_yuyv422_to_rgb(&frame, buffer, write_alpha), + FrameFormat::GRAY8 => { + let data = if write_alpha { + frame.into_iter().flat_map(|px| [*px, u8::MAX]).collect::>() + } else { + frame }; - let rgba_image: RgbaImage = image_data.convert(); - buffer.copy_from_slice(rgba_image.as_raw()); - return Ok(rgba_image.len()); - } - buffer.copy_from_slice(frame.as_ref()); + + buffer.copy_from_slice(&data); + } + }; Ok(frame.len()) } @@ -220,15 +268,15 @@ pub trait CaptureBackendTrait { /// 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>( + fn frame_texture<'a, F: PixelFormat>( &mut self, device: &WgpuDevice, queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { use std::{convert::TryFrom, num::NonZeroU32}; - let frame = self.frame()?; - let rgba_frame: RgbaImage = frame.convert(); + use image::RgbaImage; + let frame = RgbaImage::from( elf.frame()?.to_image_with_custom_format::()?); let texture_size = Extent3d { width: frame.width(), @@ -246,12 +294,12 @@ pub trait CaptureBackendTrait { usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, }); - let width_nonzero = match NonZeroU32::try_from(4 * rgba_frame.width()) { + let width_nonzero = match NonZeroU32::try_from(4 * frame.width()) { Ok(w) => Some(w), Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), }; - let height_nonzero = match NonZeroU32::try_from(rgba_frame.height()) { + let height_nonzero = match NonZeroU32::try_from(frame.height()) { Ok(h) => Some(h), Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), }; diff --git a/src/lib.rs b/src/lib.rs index c275aa1..ffed68e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,7 @@ mod utils; pub use camera::Camera; pub use camera_traits::*; pub use error::NokhwaError; +pub use buffer::Buffer; pub use init::*; #[cfg(feature = "input-jscam")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))] diff --git a/src/threaded.rs b/src/threaded.rs index 8c1b62b..2d10d27 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -15,8 +15,8 @@ */ use crate::{ - Camera, CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, FrameFormat, - KnownCameraControls, NokhwaError, Resolution, + Camera, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, + FrameFormat, KnownCameraControls, NokhwaError, Resolution, Buffer }; use image::{ImageBuffer, Rgb}; use parking_lot::Mutex; @@ -33,13 +33,13 @@ type AtomicLock = Arc>; pub type CallbackFn = fn( _camera: &Arc>, _frame_callback: &Arc< - Mutex, Vec>) + Send + 'static>>>, + Mutex>>, >, - _last_frame_captured: &Arc, Vec>>>, + _last_frame_captured: &Arc>, _die_bool: &Arc, ); type HeldCallbackType = - Arc, Vec>) + Send + 'static>>>>; + Arc>>>; /// 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 `Mutex` to ensure that this feels like a normal camera, but callback based. @@ -57,7 +57,7 @@ type HeldCallbackType = pub struct CallbackCamera { camera: AtomicLock, frame_callback: HeldCallbackType, - last_frame_captured: AtomicLock, Vec>>, + last_frame_captured: AtomicLock, die_bool: Arc, } @@ -65,7 +65,7 @@ impl CallbackCamera { /// Create a new `ThreadedCamera` from an `index` and `format`. `format` can be `None`. /// # 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: &CameraIndex, format: Option) -> Result { + pub fn new(index: usize, format: Option) -> Result { CallbackCamera::with_backend(index, format, CaptureAPIBackend::Auto) } @@ -73,7 +73,7 @@ impl CallbackCamera { /// # 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: &CameraIndex, + index: usize, format: Option, backend: CaptureAPIBackend, ) -> Result { @@ -84,7 +84,7 @@ impl CallbackCamera { /// # 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: &CameraIndex, + index: usize, width: u32, height: u32, fps: u32, @@ -103,7 +103,7 @@ impl CallbackCamera { /// # 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 customized_all( - index: &CameraIndex, + index: usize, format: Option, backend: CaptureAPIBackend, func: Option, @@ -118,10 +118,7 @@ impl CallbackCamera { backend, )?)); let frame_callback = Arc::new(Mutex::new(None)); - let last_frame_captured = Arc::new(Mutex::new(ImageBuffer::new( - format.width(), - format.height(), - ))); + let last_frame_captured = Arc::new(Mutex::new(Buffer::default())); let die_bool = Arc::new(AtomicBool::new(false)); let camera_clone = camera.clone(); @@ -163,14 +160,14 @@ impl CallbackCamera { /// Gets the current Camera's index. #[must_use] - pub fn index(&self) -> CameraIndex { + pub fn index(&self) -> usize { self.camera.lock().index().clone() } /// 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: &CameraIndex) -> Result<(), NokhwaError> { + pub fn set_index(&mut self, new_idx: usize) -> Result<(), NokhwaError> { self.camera.lock().set_index(new_idx) } @@ -195,7 +192,7 @@ impl CallbackCamera { /// Gets the current [`CameraFormat`]. #[must_use] - pub fn camera_format(&self) -> CameraFormat { + pub fn camera_format(&self) -> Result { self.camera.lock().camera_format() } @@ -204,7 +201,7 @@ impl CallbackCamera { /// # Errors /// If you started the stream and the camera rejects the new camera format, this will return an error. pub fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> { - *self.last_frame_captured.lock() = ImageBuffer::new(new_fmt.width(), new_fmt.height()); + *self.last_frame_captured.lock() = Buffer::new(new_res, Vec::default(), self.camera_format()?.format()); self.camera.lock().set_camera_format(new_fmt) } @@ -227,7 +224,7 @@ impl CallbackCamera { /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). #[must_use] - pub fn resolution(&self) -> Resolution { + pub fn resolution(&self) -> Result { self.camera.lock().resolution() } @@ -236,13 +233,13 @@ impl CallbackCamera { /// # Errors /// If you started the stream and the camera rejects the new resolution, this will return an error. pub fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> { - *self.last_frame_captured.lock() = ImageBuffer::new(new_res.width(), new_res.height()); + *self.last_frame_captured.lock() = Buffer::new(new_res, Vec::default(), self.camera_format()?.format()); self.camera.lock().set_resolution(new_res) } /// Gets the current camera framerate (See: [`CameraFormat`]). #[must_use] - pub fn frame_rate(&self) -> u32 { + pub fn frame_rate(&self) -> Result { self.camera.lock().frame_rate() } @@ -256,7 +253,7 @@ impl CallbackCamera { /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). #[must_use] - pub fn frame_format(&self) -> FrameFormat { + pub fn frame_format(&self) -> Result { self.camera.lock().frame_format() } @@ -392,10 +389,10 @@ impl CallbackCamera { /// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. pub fn open_stream(&mut self, mut callback: F) -> Result<(), NokhwaError> where - F: (FnMut(ImageBuffer, Vec>)) + Send + 'static, + F: (FnMut(Buffer)) + Send + 'static, { *self.frame_callback.lock() = - Some(Box::new(move |image: ImageBuffer, Vec>| { + Some(Box::new(move |image: Buffer| { callback(image) })); self.camera.lock().open_stream() @@ -404,10 +401,10 @@ impl CallbackCamera { /// Sets the frame callback to the new specified function. This function will be called instead of the previous one(s). pub fn set_callback(&mut self, mut callback: F) where - F: (FnMut(ImageBuffer, Vec>)) + Send + 'static, + F: (FnMut(Buffer)) + Send + 'static, { *self.frame_callback.lock() = - Some(Box::new(move |image: ImageBuffer, Vec>| { + Some(Box::new(move |image: Buffer| { callback(image) })); } @@ -415,7 +412,7 @@ impl CallbackCamera { /// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame) /// # Errors /// This will error if the camera fails to capture a frame. - pub fn poll_frame(&mut self) -> Result, Vec>, NokhwaError> { + pub fn poll_frame(&mut self) -> Result { let frame = self.camera.lock().frame()?; *self.last_frame_captured.lock() = frame.clone(); Ok(frame) @@ -441,21 +438,24 @@ impl CallbackCamera { } } -impl Drop for CallbackCamera { +impl Drop for CallbackCamera +where + C: CaptureBackendTrait, +{ fn drop(&mut self) { let _stop_stream_err = self.stop_stream(); self.die_bool.store(true, Ordering::SeqCst); } } -fn camera_frame_thread_loop( - camera: &AtomicLock, +fn camera_frame_thread_loop( + camera: &AtomicLock>, frame_callback: &HeldCallbackType, last_frame_captured: &AtomicLock, Vec>>, die_bool: &Arc, ) { loop { - if let Ok(img) = camera.lock().frame() { + if let Ok(img) = camera.lock().fr { *last_frame_captured.lock() = img.clone(); if let Some(cb) = (*frame_callback.lock()).as_mut() { cb(img); diff --git a/src/utils.rs b/src/utils.rs index d6e6876..cb067e2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -38,11 +38,10 @@ use nokhwa_bindings_windows::{ MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls, MediaFoundationDeviceDescriptor, }; -use serde::{Deserialize, Serialize}; #[cfg(feature = serde)] use serde::{Deserialize, Serialize}; use std::{ - borrow::{Borrow, Cow}, + borrow::Borrow, cmp::Ordering, fmt::{Display, Formatter}, }; @@ -171,6 +170,10 @@ impl From for AVFourCC { } } +pub const fn frame_formats() -> [FrameFormat; 3] { + [FrameFormat::MJPEG, FrameFormat::YUYV, FrameFormat::GRAY8] +} + /// Describes a Resolution. /// This struct consists of a Width and a Height value (x,y).
/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest. @@ -487,7 +490,7 @@ pub struct CameraInfo { human_name: String, description: String, misc: String, - index: CameraIndex, + index: usize, } #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))] @@ -504,7 +507,7 @@ impl CameraInfo { human_name: impl AsRef, description: impl AsRef, misc: impl AsRef, - index: CameraIndex, + index: usize, ) -> Self { CameraInfo { human_name: human_name.as_ref().to_string(), @@ -576,36 +579,36 @@ impl CameraInfo { /// 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 + pub fn index(&self) -> u32 { + 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: CameraIndex) { + pub fn set_index(&mut self, index: u32) { self.index = index; } - /// Gets the device info's index as an `u32`. - /// # Errors - /// If the index is not parsable as a `u32`, this will error. - /// # JS-WASM - /// This is exported as `get_Index_Int` - #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] - pub fn index_num(&self) -> Result { - match &self.index { - CameraIndex::Index(i) => Ok(*i), - CameraIndex::String(s) => match s.parse::() { - Ok(p) => Ok(p), - Err(why) => Err(NokhwaError::GetPropertyError { - property: "index-int".to_string(), - error: why.to_string(), - }), - }, - } - } + // /// Gets the device info's index as an `u32`. + // /// # Errors + // /// If the index is not parsable as a `u32`, this will error. + // /// # JS-WASM + // /// This is exported as `get_Index_Int` + // #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))] + // pub fn index_num(&self) -> Result { + // match &self.index { + // CameraIndex::Index(i) => Ok(*i), + // CameraIndex::String(s) => match s.parse::() { + // Ok(p) => Ok(p), + // Err(why) => Err(NokhwaError::GetPropertyError { + // property: "index-int".to_string(), + // error: why.to_string(), + // }), + // }, + // } + // } } impl Display for CameraInfo { @@ -625,10 +628,10 @@ impl Display for CameraInfo { impl From> for CameraInfo { fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self { CameraInfo { - human_name: dev_desc, - description: "Media Foundation Device", + human_name: dev_desc.name_as_string(), + description: "Media Foundation Device".to_string(), misc: dev_desc.link_as_string(), - index: CameraIndex::Index(dev_desc.index() as u32), + index: dev_desc.index() as usize, } } } @@ -651,7 +654,7 @@ impl From for CameraInfo { human_name: descriptor.name, description: descriptor.description, misc: descriptor.misc, - index: CameraIndex::Index(descriptor.index as u32), + index: descriptor.index as usize, } } } @@ -793,6 +796,37 @@ impl Display for KnownCameraControlFlag { } } +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +// TODO: use in CameraControl +pub enum ControlValue { + None, + Integer { + value: i64, + default: i64, + step: i64, + }, + IntegerRange { + min: i32, + max: i32, + value: i32, + step: i32, + default: i32, + }, + Boolean { + value: bool, + default: bool, + }, + String { + value: String, + default: String, + }, + Bytes { + value: Vec, + default: Vec, + } +} + /// This struct tells you everything about a particular [`KnownCameraControls`].
/// However, you should never need to instantiate this struct, since its usually generated for you by `nokhwa`. /// The only time you should be modifying this struct is when you need to set a value and pass it back to the camera. @@ -1041,7 +1075,7 @@ pub enum CaptureAPIBackend { UniversalVideoClass, MediaFoundation, OpenCv, - GStreamer, + // GStreamer, Network, Browser, } @@ -1053,70 +1087,70 @@ impl Display for CaptureAPIBackend { } } -/// 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 { - Index(u32), - String(String), -} +// /// 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 { +// Index(u32), +// String(String), +// } -impl CameraIndex { - /// Gets the device info's index as an `u32`. - /// # Errors - /// If the index is not parsable as a `u32`, this will error. - pub fn as_index(&self) -> Result { - match self { - CameraIndex::Index(i) => Ok(*i), - CameraIndex::String(s) => match s.parse::() { - Ok(p) => Ok(p), - Err(why) => Err(NokhwaError::GetPropertyError { - property: "index-int".to_string(), - error: why.to_string(), - }), - }, - } - } -} +// impl CameraIndex { +// /// Gets the device info's index as an `u32`. +// /// # Errors +// /// If the index is not parsable as a `u32`, this will error. +// pub fn as_index(&self) -> Result { +// match self { +// CameraIndex::Index(i) => Ok(*i), +// CameraIndex::String(s) => match s.parse::() { +// Ok(p) => Ok(p), +// Err(why) => Err(NokhwaError::GetPropertyError { +// property: "index-int".to_string(), +// error: why.to_string(), +// }), +// }, +// } +// } +// } -impl Display for CameraIndex { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CameraIndex::Index(idx) => { - write!(f, "{}", idx) - } - CameraIndex::String(ip) => { - write!(f, "{}", ip) - } - } - } -} +// impl Display for CameraIndex { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// match self { +// CameraIndex::Index(idx) => { +// write!(f, "{}", idx) +// } +// CameraIndex::String(ip) => { +// write!(f, "{}", ip) +// } +// } +// } +// } -impl From for CameraIndex { - fn from(v: u32) -> Self { - CameraIndex::Index(v) - } -} +// impl From for CameraIndex { +// fn from(v: u32) -> Self { +// CameraIndex::Index(v) +// } +// } -/// Trait for strings that can be converted to [`CameraIndex`]es. -pub trait ValidString: AsRef {} +// /// Trait for strings that can be converted to [`CameraIndex`]es. +// pub trait ValidString: AsRef {} +// +// 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 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 From for CameraIndex -where - T: ValidString, -{ - fn from(v: T) -> Self { - CameraIndex::String(v.as_ref().to_string()) - } -} +// impl From for CameraIndex +// where +// T: ValidString, +// { +// fn from(v: T) -> Self { +// CameraIndex::String(v.as_ref().to_string()) +// } +// } /// Converts a MJPEG stream of [u8] into a Vec of RGB888. (R,G,B,R,G,B,...) /// # Errors From a188080b9742d5f4c1473740351d988a43dcbab5 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sun, 22 May 2022 19:05:13 +0900 Subject: [PATCH 50/89] update UVC/GST deprecation --- README.md | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 370a8ef..7751acc 100644 --- a/README.md +++ b/README.md @@ -42,16 +42,16 @@ The table below lists current Nokhwa API support. - The `Query-Device` column signifies reading device capabilities - The `Platform` column signifies what Platform this is availible on. - | Backend | Input | Query | Query-Device | Platform | - |-------------------------------------|--------------------|-------------------|--------------------|---------------------| - | Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux | - | MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows | - | AVFoundation(`input-avfoundatuin`)^^| ✅ | ✅ | ✅ | Mac | - | libuvc(`input-uvc`)^^^ | ❌ | ✅ | ❌ | Linux, Windows, Mac | - | OpenCV(`input-opencv`)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac | - | IPCamera(`input-ipcam`/OpenCV)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac | - | GStreamer(`input-gst`) | ✅ | ✅ | ✅ | Linux, Windows, Mac | - | JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) | + | Backend | Input | Query | Query-Device | Platform | + |----------------------------------------|--------------------|-------------------|--------------------|---------------------| + | Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux | + | MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows | + | AVFoundation(`input-avfoundatuin`)^^ | ✅ | ✅ | ✅ | Mac | + | libuvc(`input-uvc`) (**DEPRECATED**)^^^| ❌ | ✅ | ❌ | Linux, Windows, Mac | + | OpenCV(`input-opencv`)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac | + | IPCamera(`input-ipcam`/OpenCV)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac | + | GStreamer(`input-gst`)(**DEPRECATED**) | ✅ | ✅ | ✅ | Linux, Windows, Mac | + | JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) | ✅: Working, 🔮 : Experimental, ❌ : Not Supported, 🚧: Planned/WIP @@ -68,10 +68,10 @@ As a general rule of thumb, you would want to keep at least `input-uvc` or other - `input-v4l`: Enables the `Video4Linux` backend. (linux) - `input-msmf`: Enables the `MediaFoundation` backennd. (Windows 7 or newer) - `input-avfoundation`: Enables the `AVFoundation` backend. (MacOSX 10.7) - - `input-uvc`: Enables the `libuvc` backend. (cross-platform, libuvc statically-linked) + - `input-uvc`: Enables the `libuvc` backend. (cross-platform, libuvc statically-linked) (**DEPRECATED**) - `input-opencv`: Enables the `opencv` backend. (cross-platform) - `input-ipcam`: Enables the use of IP Cameras, please see the `NetworkCamera` struct. Note that this relies on `opencv`, so it will automatically enable the `input-opencv` feature. - - `input-gst`: Enables the `gstreamer` backend. (cross-platform) + - `input-gst`: Enables the `gstreamer` backend. (**DEPRECATED**) - `input-jscam`: Enables the use of the `JSCamera` struct, which uses browser APIs. (Web) Conversely, anything that starts with `output-*` controls a feature that controls the output of something (usually a frame from the camera) @@ -108,6 +108,3 @@ Contributions are welcome! ## Minimum Service Rust Version `nokhwa` may build on older versions of `rustc`, but there is no guarantee except for the latest stable rust. - -## 0.10 -0.10 is currently stalled due to upstream not having the necessary features (wasm-bindgen). From fb57e9b061b28b0a57c16a1b7aa2c6f1d23fe490 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sun, 22 May 2022 20:02:37 +0900 Subject: [PATCH 51/89] import adjustments --- src/camera.rs | 13 ++++--------- src/camera_traits.rs | 22 ++++++++++++---------- src/js_camera.rs | 12 ++++++++---- src/lib.rs | 5 +++-- src/pixel_format.rs | 6 ++---- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index 5759256..1eda8d9 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -15,18 +15,13 @@ */ use crate::{ - buffer::Buffer, BackendsEnum, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, - CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, + buffer::Buffer, pixel_format::PixelFormat, BackendsEnum, CameraControl, CameraFormat, + CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, + NokhwaError, Resolution, }; -use image::buffer::ConvertBuffer; -use image::RgbaImage; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] -use wgpu::{ - Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue, - Texture as WgpuTexture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, - TextureUsages, -}; +use wgpu::{Device as WgpuDevice, Queue as WgpuQueue, Texture as WgpuTexture}; /// The main `Camera` struct. This is the struct that abstracts over all the backends, providing a simplified interface for use. pub struct Camera { diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 8da5a53..cfc1d8e 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -14,17 +14,17 @@ * limitations under the License. */ -use crate::buffer::Buffer; -use crate::pixel_format::PixelFormat; use crate::{ error::NokhwaError, frame_formats, - utils::{CameraFormat, CameraInfo, FrameFormat, Resolution, buf_mjpeg_to_rgb, buf_yuyv422_to_rgb}, - CameraControl, CaptureAPIBackend, KnownCameraControls, + utils::{ + buf_mjpeg_to_rgb, buf_yuyv422_to_rgb, CameraFormat, CameraInfo, FrameFormat, Resolution, + }, + Buffer, CameraControl, CaptureAPIBackend, KnownCameraControls, PixelFormat, }; +use enum_dispatch::enum_dispatch; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; use std::{any::Any, borrow::Cow, collections::HashMap}; -use enum_dispatch::enum_dispatch; #[cfg(feature = "output-wgpu")] use wgpu::{ Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue, @@ -243,8 +243,7 @@ pub trait CaptureBackendTrait { &mut self, buffer: &mut [u8], write_alpha: bool, - ) -> Result - { + ) -> Result { let cfmt = self.camera_format()?; let frame = self.frame_raw()?; let data = match cfmt.format() { @@ -252,7 +251,10 @@ pub trait CaptureBackendTrait { FrameFormat::YUYV => buf_yuyv422_to_rgb(&frame, buffer, write_alpha), FrameFormat::GRAY8 => { let data = if write_alpha { - frame.into_iter().flat_map(|px| [*px, u8::MAX]).collect::>() + frame + .into_iter() + .flat_map(|px| [*px, u8::MAX]) + .collect::>() } else { frame }; @@ -274,9 +276,9 @@ pub trait CaptureBackendTrait { queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { - use std::{convert::TryFrom, num::NonZeroU32}; use image::RgbaImage; - let frame = RgbaImage::from( elf.frame()?.to_image_with_custom_format::()?); + use std::{convert::TryFrom, num::NonZeroU32}; + let frame = RgbaImage::from(elf.frame()?.to_image_with_custom_format::()?); let texture_size = Extent3d { width: frame.width(), diff --git a/src/js_camera.rs b/src/js_camera.rs index 261f5b3..1f02fa8 100644 --- a/src/js_camera.rs +++ b/src/js_camera.rs @@ -22,18 +22,20 @@ use crate::{CameraIndex, CameraInfo, NokhwaError, Resolution}; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbImage, Rgba}; +#[cfg(feature = "output-wasm")] use js_sys::{Array, JsString, Map, Object, Promise}; -use std::borrow::Borrow; use std::{ + borrow::Borrow, borrow::Cow, convert::TryFrom, fmt::{Debug, Display, Formatter}, ops::Deref, }; #[cfg(feature = "output-wasm")] -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{JsCast, JsValue}; +use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; +#[cfg(feature = "output-wasm")] use wasm_bindgen_futures::JsFuture; +#[cfg(feature = "output-wasm")] use web_sys::{ console::log_1, CanvasRenderingContext2d, Document, Element, HtmlCanvasElement, HtmlVideoElement, ImageData, MediaDeviceInfo, MediaDeviceKind, MediaDevices, MediaStream, @@ -1675,7 +1677,8 @@ impl Deref for JSCameraConstraints { /// A wrapper around a [`MediaStream`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.MediaStream.html) /// # JS-WASM /// This is exported as `NokhwaCamera`. -#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = NokhwaCamera))] +#[cfg(feature = "output-wasm")] +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = NokhwaCamera))#[cfg(feature = "output-wasm")]] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))] pub struct JSCamera { media_stream: MediaStream, @@ -1687,6 +1690,7 @@ pub struct JSCamera { canvas_context: Option, } +#[cfg(feature = "output-wasm")] #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = NokhwaCamera))] impl JSCamera { /// Creates a new [`JSCamera`] using [`JSCameraConstraints`]. diff --git a/src/lib.rs b/src/lib.rs index ffed68e..c86149a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,8 @@ pub mod js_camera; #[cfg(feature = "input-ipcam")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))] pub mod network_camera; -pub mod pixel_format; +mod pixel_format; +pub use pixel_format::PixelFormat; mod query; /// A camera that runs in a different thread and can call your code based on callbacks. #[cfg(feature = "output-threaded")] @@ -49,10 +50,10 @@ mod query; mod threaded; mod utils; +pub use buffer::Buffer; pub use camera::Camera; pub use camera_traits::*; pub use error::NokhwaError; -pub use buffer::Buffer; pub use init::*; #[cfg(feature = "input-jscam")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))] diff --git a/src/pixel_format.rs b/src/pixel_format.rs index ac82364..247d557 100644 --- a/src/pixel_format.rs +++ b/src/pixel_format.rs @@ -14,11 +14,9 @@ * limitations under the License. */ -use crate::buffer_output::{BufferOutput, GrayU8, RgbU8}; use crate::FrameFormat; -use image::{Luma, Pixel, Rgb}; -use std::fmt::Debug; -use std::hash::Hash; +use image::Pixel; +use std::{fmt::Debug, hash::Hash}; pub trait PixelFormat: Copy + Clone + Debug + Default + Hash + Send + Sync { type Output: Pixel; From 2b7cf702b7d4bdaeb91765be8796e4e40ff0ede3 Mon Sep 17 00:00:00 2001 From: Yoshinori Tanimura Date: Mon, 23 May 2022 22:23:21 +0900 Subject: [PATCH 52/89] fix "cannot closed window" in capture --- examples/capture/src/main.rs | 91 +++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index cf906a9..fcf6328 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -21,7 +21,12 @@ use glium::{ implement_vertex, index::PrimitiveType, program, texture::RawImage2d, uniform, Display, IndexBuffer, Surface, Texture2d, VertexBuffer, }; -use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder}; +use glutin::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + ContextBuilder, +}; use nokhwa::{nokhwa_initialize, query_devices, Camera, CaptureAPIBackend, FrameFormat}; use std::time::Instant; @@ -137,7 +142,7 @@ fn main() { } } Err(why) => { - println!("Failed to query: {}", why.to_string()) + println!("Failed to query: {why}") } } } @@ -206,13 +211,13 @@ fn main() { } } Err(why) => { - println!("Failed to get compatible resolution/FPS list for FrameFormat {}: {}", ff, why.to_string()) + println!("Failed to get compatible resolution/FPS list for FrameFormat {ff}: {why}") } } } } Err(why) => { - println!("Failed to get compatible FourCC: {}", why.to_string()) + println!("Failed to get compatible FourCC: {why}") } } } @@ -226,7 +231,7 @@ fn main() { } } Err(why) => { - println!("Failed to get camera controls: {}", why.to_string()) + println!("Failed to get camera controls: {why}") } } } @@ -237,7 +242,7 @@ fn main() { let supported = match camera.camera_controls_string() { Ok(cc) => cc, Err(why) => { - println!("Failed to get camera controls: {}", why.to_string()); + println!("Failed to get camera controls: {why}"); return; } }; @@ -330,7 +335,7 @@ fn main() { v_tex_coords = tex_coords; } ", - + outputs_srgb: true, fragment: " #version 140 uniform sampler2D tex; @@ -347,49 +352,49 @@ fn main() { // run the event loop gl_event_loop.run(move |event, _window, ctrl| { - let before_capture = Instant::now(); - let frame = recv.recv().unwrap(); - let after_capture = Instant::now(); + *ctrl = match event { + Event::MainEventsCleared => { + let instant = Instant::now(); + let frame = recv.recv().unwrap(); + let capture_elapsed = instant.elapsed().as_millis(); - let width = &frame.width(); - let height = &frame.height(); + let frame_size = (frame.width(), frame.height()); - let raw_data = RawImage2d::from_raw_rgb(frame.into_raw(), (*width, *height)); - let gl_texture = Texture2d::new(&gl_display, raw_data).unwrap(); + let raw_data = RawImage2d::from_raw_rgb(frame.into_raw(), frame_size); + let gl_texture = Texture2d::new(&gl_display, raw_data).unwrap(); - let uniforms = uniform! { - matrix: [ - [1.0, 0.0, 0.0, 0.0], - [0.0, -1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0f32] - ], - tex: &gl_texture - }; + let uniforms = uniform! { + matrix: [ + [1.0, 0.0, 0.0, 0.0], + [0.0, -1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0f32] + ], + tex: &gl_texture + }; - let mut target = gl_display.draw(); - target.clear_color(0.0, 0.0, 0.0, 0.0); - target - .draw( - &vert_buffer, - &idx_buf, - &program, - &uniforms, - &Default::default(), - ) - .unwrap(); - target.finish().unwrap(); + let mut target = gl_display.draw(); + target.clear_color(0.0, 0.0, 0.0, 0.0); + target + .draw( + &vert_buffer, + &idx_buf, + &program, + &uniforms, + &Default::default(), + ) + .unwrap(); + target.finish().unwrap(); - if let glutin::event::Event::WindowEvent { event, .. } = event { - if event == glutin::event::WindowEvent::CloseRequested { - *ctrl = glutin::event_loop::ControlFlow::Exit; + println!("Took {capture_elapsed}ms to capture",); + ControlFlow::Poll } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => ControlFlow::Exit, + _ => ControlFlow::Poll, } - - println!( - "Took {}ms to capture", - after_capture.duration_since(before_capture).as_millis() - ) }) } // dont From bf08d31afcfc74a043de1572f9f34b2691f12b33 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sat, 18 Jun 2022 20:45:41 +0900 Subject: [PATCH 53/89] resolve security issues --- Cargo.toml | 2 +- nokhwa-bindings-macos/Cargo.toml | 2 +- nokhwa-bindings-windows/Cargo.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a050fd0..dd1c3d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ version = "^0.12" optional = true [dependencies.opencv] -version = "0.63" +version = "0.65" optional = true [dependencies.rgb] diff --git a/nokhwa-bindings-macos/Cargo.toml b/nokhwa-bindings-macos/Cargo.toml index a5a026a..de67d62 100644 --- a/nokhwa-bindings-macos/Cargo.toml +++ b/nokhwa-bindings-macos/Cargo.toml @@ -18,5 +18,5 @@ core-media-sys = "0.1.2" cocoa-foundation = "0.1.0" objc = { version = "0.2.7", features = ["exception"] } block = "0.1.6" -dashmap = "4.0.2" +dashmap = "5.3.4" lazy_static = "1.4.0" \ No newline at end of file diff --git a/nokhwa-bindings-windows/Cargo.toml b/nokhwa-bindings-windows/Cargo.toml index 4e6d1f1..aef8fa4 100644 --- a/nokhwa-bindings-windows/Cargo.toml +++ b/nokhwa-bindings-windows/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa-bindings-windows" -version = "0.3.3" +version = "0.3.4" authors = ["l1npengtul"] edition = "2021" license = "Apache-2.0" @@ -17,6 +17,6 @@ thiserror = "^1.0" lazy_static = "1.4.0" [target.'cfg(all(target_os = "windows", windows))'.dependencies.windows] -version = "^0.28" +version = "0.37.0" features = ["alloc", "Win32_Media_MediaFoundation", "Win32_System_Com", "Win32_Foundation", "Win32_Media_DirectShow"] From 1e31c962b8837ff341fef40dc9dd0b105a765d8c Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sun, 19 Jun 2022 22:18:42 +0900 Subject: [PATCH 54/89] adjust cargo toml, address #45 api changes, address #37, #31 (v4l2 only) --- src/backends/capture/avfoundation.rs | 6 +- src/backends/capture/browser_backend.rs | 6 +- src/backends/capture/gst_backend.rs | 6 +- src/backends/capture/msmf_backend.rs | 112 +++--- src/backends/capture/opencv_backend.rs | 6 +- src/backends/capture/uvc_backend.rs | 12 +- src/backends/capture/v4l2_backend.rs | 486 ++++++++++-------------- src/camera.rs | 10 +- src/camera_traits.rs | 101 +++-- src/threaded.rs | 35 +- src/utils.rs | 463 +++++++++++----------- 11 files changed, 593 insertions(+), 650 deletions(-) diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index e23b291..34e4da7 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -16,7 +16,7 @@ use crate::{ mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, - CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, + CaptureBackendTrait, FrameFormat, KnownCameraControl, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use nokhwa_bindings_macos::avfoundation::{ @@ -182,13 +182,13 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { self.set_camera_format(format) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { + fn supported_camera_controls(&self) -> Result, NokhwaError> { Err(NokhwaError::NotImplementedError( "Not Implemented".to_string(), )) } - fn camera_control(&self, _: KnownCameraControls) -> Result { + fn camera_control(&self, _: KnownCameraControl) -> Result { Err(NokhwaError::NotImplementedError( "Not Implemented".to_string(), )) diff --git a/src/backends/capture/browser_backend.rs b/src/backends/capture/browser_backend.rs index 697ca40..e710f75 100644 --- a/src/backends/capture/browser_backend.rs +++ b/src/backends/capture/browser_backend.rs @@ -2,7 +2,7 @@ use crate::{ js_camera::JSCameraResizeMode, js_camera::{query_js_cameras, JSCameraConstraintsBuilder}, CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, - FrameFormat, JSCamera, KnownCameraControls, NokhwaError, Resolution, + FrameFormat, JSCamera, KnownCameraControl, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use std::{any::Any, borrow::Cow, collections::HashMap}; @@ -206,11 +206,11 @@ impl CaptureBackendTrait for BrowserCaptureDevice { Ok(()) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { + fn supported_camera_controls(&self) -> Result, NokhwaError> { Ok(vec![]) } - fn camera_control(&self, _: KnownCameraControls) -> Result { + fn camera_control(&self, _: KnownCameraControl) -> Result { Err(NokhwaError::NotImplementedError( "Not Implemented".to_string(), )) diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index af71e33..778e855 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -16,7 +16,7 @@ use crate::{ mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, - CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution, + CaptureBackendTrait, FrameFormat, KnownCameraControl, NokhwaError, Resolution, }; use glib::Quark; use gstreamer::{ @@ -443,13 +443,13 @@ impl GStreamerCaptureDevice { )) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { + fn supported_camera_controls(&self) -> Result, NokhwaError> { Err(NokhwaError::NotImplementedError( CaptureAPIBackend::GStreamer.to_string(), )) } - fn camera_control(&self, _control: KnownCameraControls) -> Result { + fn camera_control(&self, _control: KnownCameraControl) -> Result { Err(NokhwaError::NotImplementedError( CaptureAPIBackend::GStreamer.to_string(), )) diff --git a/src/backends/capture/msmf_backend.rs b/src/backends/capture/msmf_backend.rs index 36fe64b..8de5876 100644 --- a/src/backends/capture/msmf_backend.rs +++ b/src/backends/capture/msmf_backend.rs @@ -16,8 +16,8 @@ use crate::{ all_known_camera_controls, mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, - CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag, - KnownCameraControls, NokhwaError, Resolution, + CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControl, + KnownCameraControlFlag, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use nokhwa_bindings_windows::{wmf::MediaFoundationDevice, MFControl, MediaFoundationControls}; @@ -179,28 +179,28 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { self.set_camera_format(new_format) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { - let mut supported_camera_controls: Vec = vec![]; + fn supported_camera_controls(&self) -> Result, NokhwaError> { + let mut supported_camera_controls: Vec = vec![]; for camera_control in all_known_camera_controls() { let msmf_camera_control: MediaFoundationControls = match camera_control { - KnownCameraControls::Brightness => MediaFoundationControls::Brightness, - KnownCameraControls::Contrast => MediaFoundationControls::Contrast, - KnownCameraControls::Hue => MediaFoundationControls::Hue, - KnownCameraControls::Saturation => MediaFoundationControls::Saturation, - KnownCameraControls::Sharpness => MediaFoundationControls::Sharpness, - KnownCameraControls::Gamma => MediaFoundationControls::Gamma, - KnownCameraControls::ColorEnable => MediaFoundationControls::ColorEnable, - KnownCameraControls::WhiteBalance => MediaFoundationControls::WhiteBalance, - KnownCameraControls::BacklightComp => MediaFoundationControls::BacklightComp, - KnownCameraControls::Gain => MediaFoundationControls::Gain, - KnownCameraControls::Pan => MediaFoundationControls::Pan, - KnownCameraControls::Tilt => MediaFoundationControls::Tilt, - KnownCameraControls::Roll => MediaFoundationControls::Roll, - KnownCameraControls::Zoom => MediaFoundationControls::Zoom, - KnownCameraControls::Exposure => MediaFoundationControls::Exposure, - KnownCameraControls::Iris => MediaFoundationControls::Iris, - KnownCameraControls::Focus => MediaFoundationControls::Focus, + KnownCameraControl::Brightness => MediaFoundationControls::Brightness, + KnownCameraControl::Contrast => MediaFoundationControls::Contrast, + KnownCameraControl::Hue => MediaFoundationControls::Hue, + KnownCameraControl::Saturation => MediaFoundationControls::Saturation, + KnownCameraControl::Sharpness => MediaFoundationControls::Sharpness, + KnownCameraControl::Gamma => MediaFoundationControls::Gamma, + KnownCameraControl::ColorEnable => MediaFoundationControls::ColorEnable, + KnownCameraControl::WhiteBalance => MediaFoundationControls::WhiteBalance, + KnownCameraControl::BacklightComp => MediaFoundationControls::BacklightComp, + KnownCameraControl::Gain => MediaFoundationControls::Gain, + KnownCameraControl::Pan => MediaFoundationControls::Pan, + KnownCameraControl::Tilt => MediaFoundationControls::Tilt, + KnownCameraControl::Roll => MediaFoundationControls::Roll, + KnownCameraControl::Zoom => MediaFoundationControls::Zoom, + KnownCameraControl::Exposure => MediaFoundationControls::Exposure, + KnownCameraControl::Iris => MediaFoundationControls::Iris, + KnownCameraControl::Focus => MediaFoundationControls::Focus, }; if let Ok(supported) = self.inner.control(msmf_camera_control) { @@ -211,25 +211,25 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { Ok(supported_camera_controls) } - fn camera_control(&self, control: KnownCameraControls) -> Result { + fn camera_control(&self, control: KnownCameraControl) -> Result { let msmf_camera_control: MediaFoundationControls = match control { - KnownCameraControls::Brightness => MediaFoundationControls::Brightness, - KnownCameraControls::Contrast => MediaFoundationControls::Contrast, - KnownCameraControls::Hue => MediaFoundationControls::Hue, - KnownCameraControls::Saturation => MediaFoundationControls::Saturation, - KnownCameraControls::Sharpness => MediaFoundationControls::Sharpness, - KnownCameraControls::Gamma => MediaFoundationControls::Gamma, - KnownCameraControls::ColorEnable => MediaFoundationControls::ColorEnable, - KnownCameraControls::WhiteBalance => MediaFoundationControls::WhiteBalance, - KnownCameraControls::BacklightComp => MediaFoundationControls::BacklightComp, - KnownCameraControls::Gain => MediaFoundationControls::Gain, - KnownCameraControls::Pan => MediaFoundationControls::Pan, - KnownCameraControls::Tilt => MediaFoundationControls::Tilt, - KnownCameraControls::Roll => MediaFoundationControls::Roll, - KnownCameraControls::Zoom => MediaFoundationControls::Zoom, - KnownCameraControls::Exposure => MediaFoundationControls::Exposure, - KnownCameraControls::Iris => MediaFoundationControls::Iris, - KnownCameraControls::Focus => MediaFoundationControls::Focus, + KnownCameraControl::Brightness => MediaFoundationControls::Brightness, + KnownCameraControl::Contrast => MediaFoundationControls::Contrast, + KnownCameraControl::Hue => MediaFoundationControls::Hue, + KnownCameraControl::Saturation => MediaFoundationControls::Saturation, + KnownCameraControl::Sharpness => MediaFoundationControls::Sharpness, + KnownCameraControl::Gamma => MediaFoundationControls::Gamma, + KnownCameraControl::ColorEnable => MediaFoundationControls::ColorEnable, + KnownCameraControl::WhiteBalance => MediaFoundationControls::WhiteBalance, + KnownCameraControl::BacklightComp => MediaFoundationControls::BacklightComp, + KnownCameraControl::Gain => MediaFoundationControls::Gain, + KnownCameraControl::Pan => MediaFoundationControls::Pan, + KnownCameraControl::Tilt => MediaFoundationControls::Tilt, + KnownCameraControl::Roll => MediaFoundationControls::Roll, + KnownCameraControl::Zoom => MediaFoundationControls::Zoom, + KnownCameraControl::Exposure => MediaFoundationControls::Exposure, + KnownCameraControl::Iris => MediaFoundationControls::Iris, + KnownCameraControl::Focus => MediaFoundationControls::Focus, }; let ctrl = match self.inner.control(msmf_camera_control) { @@ -260,23 +260,23 @@ impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> { fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError> { let ctrl = match control.control() { - KnownCameraControls::Brightness => MediaFoundationControls::Brightness, - KnownCameraControls::Contrast => MediaFoundationControls::Contrast, - KnownCameraControls::Hue => MediaFoundationControls::Hue, - KnownCameraControls::Saturation => MediaFoundationControls::Saturation, - KnownCameraControls::Sharpness => MediaFoundationControls::Sharpness, - KnownCameraControls::Gamma => MediaFoundationControls::Gamma, - KnownCameraControls::ColorEnable => MediaFoundationControls::ColorEnable, - KnownCameraControls::WhiteBalance => MediaFoundationControls::WhiteBalance, - KnownCameraControls::BacklightComp => MediaFoundationControls::BacklightComp, - KnownCameraControls::Gain => MediaFoundationControls::Gain, - KnownCameraControls::Pan => MediaFoundationControls::Pan, - KnownCameraControls::Tilt => MediaFoundationControls::Tilt, - KnownCameraControls::Roll => MediaFoundationControls::Roll, - KnownCameraControls::Zoom => MediaFoundationControls::Zoom, - KnownCameraControls::Exposure => MediaFoundationControls::Exposure, - KnownCameraControls::Iris => MediaFoundationControls::Iris, - KnownCameraControls::Focus => MediaFoundationControls::Focus, + KnownCameraControl::Brightness => MediaFoundationControls::Brightness, + KnownCameraControl::Contrast => MediaFoundationControls::Contrast, + KnownCameraControl::Hue => MediaFoundationControls::Hue, + KnownCameraControl::Saturation => MediaFoundationControls::Saturation, + KnownCameraControl::Sharpness => MediaFoundationControls::Sharpness, + KnownCameraControl::Gamma => MediaFoundationControls::Gamma, + KnownCameraControl::ColorEnable => MediaFoundationControls::ColorEnable, + KnownCameraControl::WhiteBalance => MediaFoundationControls::WhiteBalance, + KnownCameraControl::BacklightComp => MediaFoundationControls::BacklightComp, + KnownCameraControl::Gain => MediaFoundationControls::Gain, + KnownCameraControl::Pan => MediaFoundationControls::Pan, + KnownCameraControl::Tilt => MediaFoundationControls::Tilt, + KnownCameraControl::Roll => MediaFoundationControls::Roll, + KnownCameraControl::Zoom => MediaFoundationControls::Zoom, + KnownCameraControl::Exposure => MediaFoundationControls::Exposure, + KnownCameraControl::Iris => MediaFoundationControls::Iris, + KnownCameraControl::Focus => MediaFoundationControls::Focus, }; let flag = match control.flag() { diff --git a/src/backends/capture/opencv_backend.rs b/src/backends/capture/opencv_backend.rs index 0997075..c08ad9d 100644 --- a/src/backends/capture/opencv_backend.rs +++ b/src/backends/capture/opencv_backend.rs @@ -17,7 +17,7 @@ use crate::pixel_format::PixelFormat; use crate::{ CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, - KnownCameraControls, NokhwaError, Resolution, + KnownCameraControl, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use opencv::{ @@ -417,13 +417,13 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { self.set_camera_format(current_fmt) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { + fn supported_camera_controls(&self) -> Result, NokhwaError> { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::UniversalVideoClass, )) } - fn camera_control(&self, _control: KnownCameraControls) -> Result { + fn camera_control(&self, _control: KnownCameraControl) -> Result { Err(NokhwaError::UnsupportedOperationError( CaptureAPIBackend::UniversalVideoClass, )) diff --git a/src/backends/capture/uvc_backend.rs b/src/backends/capture/uvc_backend.rs index 18dcad3..314929d 100644 --- a/src/backends/capture/uvc_backend.rs +++ b/src/backends/capture/uvc_backend.rs @@ -18,7 +18,7 @@ use crate::{ CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, - KnownCameraControlFlag, KnownCameraControls, NokhwaError, Resolution, + KnownCameraControl, KnownCameraControlFlag, NokhwaError, Resolution, }; use flume::{Receiver, Sender}; use image::{ImageBuffer, Rgb}; @@ -329,16 +329,16 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> { self.set_camera_format(current_format) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { + fn supported_camera_controls(&self) -> Result, NokhwaError> { Ok(vec![ - KnownCameraControls::Exposure, - KnownCameraControls::Focus, + KnownCameraControl::Exposure, + KnownCameraControl::Focus, ]) } - fn camera_control(&self, control: KnownCameraControls) -> Result { + fn camera_control(&self, control: KnownCameraControl) -> Result { match control { - KnownCameraControls::Focus => match self.with_device_handle(|x| x).exposure_rel() { + KnownCameraControl::Focus => match self.with_device_handle(|x| x).exposure_rel() { Ok(v) => { let v: i8 = v; match CameraControl::new( diff --git a/src/backends/capture/v4l2_backend.rs b/src/backends/capture/v4l2_backend.rs index 11f7506..941923d 100644 --- a/src/backends/capture/v4l2_backend.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -15,153 +15,74 @@ */ use crate::{ + buffer::Buffer, error::NokhwaError, mjpeg_to_rgb, + pixel_format::PixelFormat, utils::{CameraFormat, CameraInfo}, - yuyv422_to_rgb, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, - KnownCameraControlFlag, KnownCameraControls, Resolution, + yuyv422_to_rgb, CameraControl, CaptureAPIBackend, CaptureBackendTrait, ControlDescription, + ControlValueSetter, FrameFormat, KnownCameraControl, KnownCameraControlFlag, Resolution, +}; +use image::ImageBuffer; +use std::{ + borrow::Cow, + collections::HashMap, + io::{self, ErrorKind}, }; -use image::{ImageBuffer, Rgb}; -use std::{borrow::Cow, collections::HashMap}; use v4l::{ buffer::Type, + control::{Control, Description, Flags, Value}, frameinterval::FrameIntervalEnum, framesize::FrameSizeEnum, io::traits::CaptureStream, + prelude::MmapStream, video::{capture::Parameters, Capture}, - Format, FourCC, - Device, + Device, Format, FourCC, }; -use crate::pixel_format::PixelFormat; -use std::any::Any; -pub use v4l::control::{Control, Description, Flags}; -use v4l::prelude::MmapStream; -use v4l::video::Output; -use crate::buffer::Buffer; - -/// Generates a camera control from a device and a description of control -/// # Error -/// If the control is not supported, the value is invalid or string, or the control is write only/the control cannot be read from, -/// this will error. -#[allow(clippy::cast_possible_truncation)] -#[allow(clippy::cast_lossless)] -pub fn to_camera_control( - device: &Device, - value: &Description, -) -> Result { - // make sure flags is valid - if value.flags.contains(Flags::from(0x0040)) { - return Err(NokhwaError::NotImplementedError( - "Control Write Only!".to_string(), - )); - } - - let control: KnownCameraControls = match try_id_to_known_camera_control(value.id) { - Some(kcc) => kcc, - None => { - return Err(NokhwaError::StructureError { - structure: "KnownCameraControl".to_string(), - error: "Unsupported V4L2 ID".to_string(), - }) - } - }; - - // value - let current_value = match device.control(value.id) { - Ok(val) => match val { - Control::Value(v) => v, - Control::Value64(v64) => { - if v64 <= i32::MAX as i64 { - v64 as i32 - } else { - return Err(NokhwaError::GetPropertyError { - property: format!("Control V4L2ID: {}", value.id), - error: "Too large!".to_string(), - }); - } - } - Control::String(_) => { - return Err(NokhwaError::GetPropertyError { - property: format!("Control V4L2ID: {}", value.id), - error: "Unsupported Type String".to_string(), - }) - } - }, - Err(why) => { - return Err(NokhwaError::GetPropertyError { - property: format!("Control V4L2ID: {}", value.id), - error: why.to_string(), - }) - } - }; - - let active = - value.flags.contains(Flags::from(0x0001)) || value.flags.contains(Flags::from(0x0010)); - - CameraControl::new( - control, - value.minimum, - value.maximum, - current_value, - value.step, - value.default, - KnownCameraControlFlag::Manual, - active, - ) -} - /// Attempts to convert a [`KnownCameraControls`] into a V4L2 Control ID. /// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`) -pub fn try_known_camera_control_to_id(ctrl: KnownCameraControls) -> Option { +pub fn known_camera_control_to_id(ctrl: KnownCameraControl) -> u32 { match ctrl { - KnownCameraControls::Brightness => Some(9_963_776), - KnownCameraControls::Contrast => Some(9_963_777), - KnownCameraControls::Hue => Some(9_963_779), - KnownCameraControls::Saturation => Some(9_963_778), - KnownCameraControls::Sharpness => Some(9_963_803), - KnownCameraControls::Gamma => Some(9_963_792), - KnownCameraControls::WhiteBalance => Some(9_963_802), - KnownCameraControls::BacklightComp => Some(9_963_804), - KnownCameraControls::Gain => Some(9_963_795), - KnownCameraControls::Pan => Some(10_094_852), - KnownCameraControls::Tilt => Some(100_948_530), - KnownCameraControls::Zoom => Some(10_094_862), - KnownCameraControls::Exposure => Some(10_094_850), - KnownCameraControls::Iris => Some(10_094_866), - KnownCameraControls::Focus => Some(10_094_859), - _ => None, + KnownCameraControl::Brightness => 9_963_776, + KnownCameraControl::Contrast => 9_963_777, + KnownCameraControl::Hue => 9_963_779, + KnownCameraControl::Saturation => 9_963_778, + KnownCameraControl::Sharpness => 9_963_803, + KnownCameraControl::Gamma => 9_963_792, + KnownCameraControl::WhiteBalance => 9_963_802, + KnownCameraControl::BacklightComp => 9_963_804, + KnownCameraControl::Gain => 9_963_795, + KnownCameraControl::Pan => 10_094_852, + KnownCameraControl::Tilt => 100_948_530, + KnownCameraControl::Zoom => 10_094_862, + KnownCameraControl::Exposure => 10_094_850, + KnownCameraControl::Iris => 10_094_866, + KnownCameraControl::Focus => 10_094_859, + KnownCameraControl::Other(id) => id as u32, } } /// Attempts to convert a [`u32`] V4L2 Control ID into a [`KnownCameraControls`] /// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`) -pub fn try_id_to_known_camera_control(id: u32) -> Option { +pub fn id_to_known_camera_control(id: u32) -> KnownCameraControl { match id { - 9_963_776 => Some(KnownCameraControls::Brightness), - 9_963_777 => Some(KnownCameraControls::Contrast), - 9_963_779 => Some(KnownCameraControls::Hue), - 9_963_778 => Some(KnownCameraControls::Saturation), - 9_963_803 => Some(KnownCameraControls::Sharpness), - 9_963_792 => Some(KnownCameraControls::Gamma), - 9_963_802 => Some(KnownCameraControls::WhiteBalance), - 9_963_804 => Some(KnownCameraControls::BacklightComp), - 9_963_795 => Some(KnownCameraControls::Gain), - 10_094_852 => Some(KnownCameraControls::Pan), - 100_948_530 => Some(KnownCameraControls::Tilt), - 10_094_862 => Some(KnownCameraControls::Zoom), - 10_094_850 => Some(KnownCameraControls::Exposure), - 10_094_866 => Some(KnownCameraControls::Iris), - 10_094_859 => Some(KnownCameraControls::Focus), - _ => None, - } -} - -fn clone_control(ctrl: &Control) -> Control { - match ctrl { - Control::Value(v) => Control::Value(*v), - Control::Value64(v) => Control::Value64(*v), - Control::String(v) => Control::String(v.clone()), + 9_963_776 => KnownCameraControl::Brightness, + 9_963_777 => KnownCameraControl::Contrast, + 9_963_779 => KnownCameraControl::Hue, + 9_963_778 => KnownCameraControl::Saturation, + 9_963_803 => KnownCameraControl::Sharpness, + 9_963_792 => KnownCameraControl::Gamma, + 9_963_802 => KnownCameraControl::WhiteBalance, + 9_963_804 => KnownCameraControl::BacklightComp, + 9_963_795 => KnownCameraControl::Gain, + 10_094_852 => KnownCameraControl::Pan, + 100_948_530 => KnownCameraControl::Tilt, + 10_094_862 => KnownCameraControl::Zoom, + 10_094_850 => KnownCameraControl::Exposure, + 10_094_866 => KnownCameraControl::Iris, + 10_094_859 => KnownCameraControl::Focus, + id => KnownCameraControl::Other(id as u128), } } @@ -344,12 +265,10 @@ impl<'a> V4LCaptureDevice<'a> { ); Ok(()) } - (_, _) => { - Err(NokhwaError::GetPropertyError { - property: "parameters".to_string(), - error: why.to_string(), - }) - } + (_, _) => Err(NokhwaError::GetPropertyError { + property: "parameters".to_string(), + error: why.to_string(), + }), } } } @@ -359,11 +278,14 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { let camera_format = self.camera_format; let compatible = self.compatible_camera_formats()?; if compatible.len() == 0 { - return Err(NokhwaError::InitializeError { backend: self.backend(), error: "Could not find any compatible camera formats!".to_string() }) + return Err(NokhwaError::InitializeError { + backend: self.backend(), + error: "Could not find any compatible camera formats!".to_string(), + }); } if compatible.contains(&camera_format) { self.initialized = true; - return Ok(camera_format) + return Ok(camera_format); } else { self.initialized = true; let new_fmt = compatible[0]; @@ -380,6 +302,10 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { &self.camera_info } + fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> { + self.force_refresh_camera_format() + } + fn camera_format(&self) -> CameraFormat { self.camera_format } @@ -447,6 +373,16 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { }; } self.camera_format = new_fmt; + + self.force_refresh_camera_format()?; + if self.camera_format != new_fmt { + return Err(NokhwaError::SetPropertyError { + property: "CameraFormat".to_string(), + value: new_fmt.to_string(), + error: "Rejected".to_string(), + }); + } + Ok(()) } @@ -550,173 +486,153 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { self.set_camera_format(new_fmt) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { - let v4l2_controls = match self.device.query_controls() { - Ok(controls) => controls, - Err(why) => { - return Err(NokhwaError::GetPropertyError { - property: "Controls".to_string(), - error: why.to_string(), - }) - } - }; - - let mut camera_controls = vec![]; - for ctrl in v4l2_controls { - if let Ok(cam_control) = to_camera_control(&self.device, &ctrl) { - camera_controls.push(cam_control.control()); - } - } - Ok(camera_controls) - } - - fn camera_control(&self, control: KnownCameraControls) -> Result { - let id = match try_known_camera_control_to_id(control) { - Some(id) => id, - None => { - return Err(NokhwaError::GetPropertyError { - property: "KnownCameraControls V4L2ID".to_string(), - error: "Invalid".to_string(), - }) - } - }; - - match self.device.control(id) { - Ok(_) => { - let v4l2_controls = match self.device.query_controls() { - Ok(controls) => controls, - Err(why) => { - return Err(NokhwaError::GetPropertyError { - property: "Controls".to_string(), - error: why.to_string(), - }) - } - }; - - for ctrl in v4l2_controls { - if ctrl.id != id { - continue; - } - - if let Ok(cam_control) = to_camera_control(&self.device, &ctrl) { - if Some(ctrl.id) == try_known_camera_control_to_id(cam_control.control()) { - return Ok(cam_control); - } - } - } - } - Err(why) => { - return Err(NokhwaError::GetPropertyError { - property: "Camera Control".to_string(), - error: why.to_string(), - }) + fn camera_control(&self, control: KnownCameraControl) -> Result { + let controls = self.camera_controls()?; + for supported_control in controls { + if supported_control.control() == control { + return Ok(supported_control); } } Err(NokhwaError::GetPropertyError { - property: "Camera Control".to_string(), - error: "Not Found".to_string(), + property: control.to_string(), + error: "not found/not supported".to_string(), }) } - fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError> { - let id = match try_known_camera_control_to_id(control.control()) { - Some(id) => id, - None => { - return Err(NokhwaError::GetPropertyError { - property: "KnownCameraControls V4L2ID".to_string(), - error: "Invalid".to_string(), - }) - } - }; - - if let Err(why) = self.device.set_control(Control { id, value: Value }) { - return Err(NokhwaError::SetPropertyError { - property: format!("{} V4L2ID {}", control.control(), id), - value: control.value().to_string(), + fn camera_controls(&self) -> Result, NokhwaError> { + self.device + .query_controls() + .map_err(|why| NokhwaError::GetPropertyError { + property: "V4L2 Controls".to_string(), error: why.to_string(), - }); - } - Ok(()) - } - - fn raw_supported_camera_controls(&self) -> Result>, NokhwaError> { - let v4l2_controls = match self.device.query_controls() { - Ok(controls) => controls, - Err(why) => { - return Err(NokhwaError::GetPropertyError { - property: "Controls".to_string(), - error: why.to_string(), - }) - } - }; - - Ok(v4l2_controls + })? .into_iter() - .map(|c| { - let a: Box = Box::new(c); - a + .map(|desc| { + let id_as_kcc = id_to_known_camera_control(desc.id); + let ctrl_current = self.device.control(desc.id)?.value; + + let ctrl_value_desc = match (desc.typ, ctrl_current) { + ( + Type::Integer + | Type::Integer64 + | Type::Menu + | Type::U8 + | Type::U16 + | Type::U32 + | Type::IntegerMenu, + Value::Integer(current), + ) => ControlDescription::IntegerRange { + min: desc.minimum as i64, + max: desc.maximum, + value: current, + step: desc.step as i64, + default: desc.default, + }, + (Type::Boolean, Value::Boolean(current)) => ControlDescription::Boolean { + value: current, + default: desc.default != 0, + }, + + (Type::String, Value::String(current)) => ControlDescription::String { + value: current, + default: None, + }, + _ => { + return Err(io::Error::new( + ErrorKind::Unsupported, + "what is this?????? todo: support ig", + )) + } + }; + + let is_readonly = desc + .flags + .intersects(Flags::READ_ONLY) + .then(|| KnownCameraControlFlag::ReadOnly); + let is_writeonly = desc + .flags + .intersects(Flags::WRITE_ONLY) + .then(|| KnownCameraControlFlag::WriteOnly); + let is_disabled = desc + .flags + .intersects(Flags::DISABLED) + .then(|| KnownCameraControlFlag::Disabled); + let is_volatile = desc + .flags + .intersects(Flags::VOLATILE) + .then(|| KnownCameraControlFlag::Volatile); + let is_inactive = desc + .flags + .intersects(Flags::INACTIVE) + .then(|| KnownCameraControlFlag::Inactive); + let flags_vec = vec![ + is_inactive, + is_readonly, + is_volatile, + is_disabled, + is_writeonly, + ] + .into_iter() + .filter(|x| x.is_some()) + .collect::>>() + .unwrap_or_default(); + + Ok(CameraControl::new( + id_as_kcc, + desc.name, + ctrl_value_desc, + flags_vec, + !desc.flags.intersects(Flags::INACTIVE), + )) + }) + .filter(|x| x.is_ok()) + .collect::, io::Error>>() + .map_err(|x| NokhwaError::GetPropertyError { + property: "www".to_string(), + error: x.to_string(), }) - .collect()) } - fn raw_camera_control(&self, control: &dyn Any) -> Result, NokhwaError> { - let id = match control.downcast_ref::() { - Some(id) => *id, - None => { - return Err(NokhwaError::StructureError { - structure: "V4L2 ID".to_string(), - error: "Failed Any Cast".to_string(), - }) - } - }; - - match self.device.control(id) { - Ok(v) => Ok(Box::new(v)), - Err(why) => Err(NokhwaError::GetPropertyError { - property: "Control V4L2".to_string(), - error: why.to_string(), - }), - } - } - - fn set_raw_camera_control( + fn set_camera_control( &mut self, - control: &dyn Any, - value: &dyn Any, + id: KnownCameraControl, + value: ControlValueSetter, ) -> Result<(), NokhwaError> { - let id = match control.downcast_ref::() { - Some(id) => *id, - None => { - return Err(NokhwaError::StructureError { - structure: "V4L2 ID".to_string(), - error: "Failed Any Cast".to_string(), + let value = match value { + ControlValueSetter::None => Value::None, + ControlValueSetter::Integer(i) => Value::Integer(i), + ControlValueSetter::Float(f) => { + return Err(NokhwaError::SetPropertyError { + property: id.to_string(), + value: f.to_string(), + error: "not supported".to_string(), }) } + ControlValueSetter::Boolean(b) => Value::Boolean(b), + ControlValueSetter::String(s) => Value::String(s), + ControlValueSetter::Bytes(b) => Value::CompoundU8(b), }; - - let value = match value.downcast_ref::() { - Some(v) => match v { - Control::Value(v) => Control::Value(*v), - Control::Value64(v64) => Control::Value64(*v64), - Control::String(s) => Control::String(s.clone()), - }, - None => { - return Err(NokhwaError::StructureError { - structure: "V4L2 Control Value".to_string(), - error: "Failed Any Cast".to_string(), - }) - } - }; - - if let Err(why) = self.device.set_control(id, clone_control(&value)) { - return Err(NokhwaError::SetPropertyError { - property: format!("V4L2 Control ID {}", id), - value: format!("{:?}", value), + self.device + .set_control(Control { + id: known_camera_control_to_id(id), + value, + }) + .map_err(|why| NokhwaError::SetPropertyError { + property: id.to_string(), + value: value.to_string(), error: why.to_string(), - }); - } + })?; + // verify - Ok(()) + let control = self.camera_control(id)?; + if control.value().value() == value { + return Ok(()); + } + return Err(NokhwaError::SetPropertyError { + property: id.to_string(), + value: value.to_string(), + error: "Rejected".to_string(), + }); } fn open_stream(&mut self) -> Result<(), NokhwaError> { @@ -743,7 +659,9 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { Ok(Buffer::new(cam_fmt.resolution(), conv, cam_fmt.format())) } - fn frame_typed(&mut self) -> Result>, NokhwaError> { + fn frame_typed( + &mut self, + ) -> Result>, NokhwaError> { self.frame()?.to_image_with_custom_format::() } diff --git a/src/camera.rs b/src/camera.rs index 1eda8d9..373239a 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -16,7 +16,7 @@ use crate::{ buffer::Buffer, pixel_format::PixelFormat, BackendsEnum, CameraControl, CameraFormat, - CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, + CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControl, NokhwaError, Resolution, }; use std::{any::Any, borrow::Cow, collections::HashMap}; @@ -231,7 +231,7 @@ impl Camera { /// Gets the current supported list of [`KnownCameraControls`] /// # Errors /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". - pub fn supported_camera_controls(&self) -> Result, NokhwaError> { + pub fn supported_camera_controls(&self) -> Result, NokhwaError> { self.backend.supported_camera_controls() } @@ -275,14 +275,14 @@ impl Camera { /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". pub fn camera_controls_known_camera_controls( &self, - ) -> Result, NokhwaError> { + ) -> Result, NokhwaError> { let known_controls = self.supported_camera_controls()?; let maybe_camera_controls = known_controls .iter() .map(|x| (*x, self.camera_control(*x))) .filter(|(_, x)| x.is_ok()) .map(|(c, x)| (c, Result::unwrap(x))) - .collect::>(); + .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); for (kc, cc) in maybe_camera_controls.into_iter() { @@ -298,7 +298,7 @@ impl Camera { /// this will error. pub fn camera_control( &self, - control: KnownCameraControls, + control: KnownCameraControl, ) -> Result { self.backend.camera_control(control) } diff --git a/src/camera_traits.rs b/src/camera_traits.rs index cfc1d8e..3e8959c 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -20,11 +20,11 @@ use crate::{ utils::{ buf_mjpeg_to_rgb, buf_yuyv422_to_rgb, CameraFormat, CameraInfo, FrameFormat, Resolution, }, - Buffer, CameraControl, CaptureAPIBackend, KnownCameraControls, PixelFormat, + Buffer, CameraControl, CaptureAPIBackend, KnownCameraControl, PixelFormat, }; use enum_dispatch::enum_dispatch; -use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; -use std::{any::Any, borrow::Cow, collections::HashMap}; +use image::{buffer::ConvertBuffer, ImageBuffer, RgbaImage}; +use std::{borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue, @@ -33,7 +33,7 @@ use wgpu::{ }; #[enum_dispatch] -pub(crate) enum BackendsEnum { +pub enum BackendsEnum { MSMF, AVF, V4L2, @@ -58,11 +58,12 @@ pub trait CaptureBackendTrait { /// Gets the camera information such as Name and Index as a [`CameraInfo`]. fn camera_info(&self) -> &CameraInfo; - /// Gets the current [`CameraFormat`]. - fn cached_camera_format(&self) -> CameraFormat; + /// Forcefully refreshes the stored camera format, bringing it into sync with "reality" (current camera state) + /// You shouldn't have to call this as it is done for you. + fn refresh_camera_format(&mut self) -> Result<(), NokhwaError>; /// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed. - fn camera_format(&self) -> Result; + fn camera_format(&self) -> CameraFormat; /// Will set the current [`CameraFormat`] /// This will reset the current stream if used while stream is opened. @@ -105,11 +106,8 @@ pub trait CaptureBackendTrait { /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError)). fn compatible_fourcc(&mut self) -> Result, NokhwaError>; - /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). - fn cached_resolution(&self) -> Resolution; - /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. - fn resolution(&self) -> Result; + fn resolution(&self) -> Resolution; /// Will set the current [`Resolution`] /// This will reset the current stream if used while stream is opened. @@ -119,11 +117,8 @@ pub trait CaptureBackendTrait { /// If you started the stream and the camera rejects the new resolution, this will return an error. fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>; - /// Gets the current camera framerate (See: [`CameraFormat`]). - fn cached_frame_rate(&self) -> u32; - /// Gets the current camera framerate (See: [`CameraFormat`]). This will force refresh to the current latest if it has changed. - fn frame_rate(&self) -> Result; + fn frame_rate(&self) -> u32; /// Will set the current framerate /// This will reset the current stream if used while stream is opened. @@ -133,11 +128,8 @@ pub trait CaptureBackendTrait { /// If you started the stream and the camera rejects the new framerate, this will return an error. fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; - /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). - fn cached_frame_format(&self) -> FrameFormat; - /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed. - fn frame_format(&self) -> Result; + fn frame_format(&self) -> FrameFormat; /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. @@ -147,16 +139,16 @@ pub trait CaptureBackendTrait { /// If you started the stream and the camera rejects the new frame format, this will return an error. fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>; - /// Gets the current supported list of [`KnownCameraControls`] - /// # Errors - /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". - fn supported_camera_controls(&self) -> Result, NokhwaError>; - /// Gets the value of [`KnownCameraControls`]. /// # Errors /// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, etc) /// this will error. - fn camera_control(&self, control: KnownCameraControls) -> Result; + fn camera_control(&self, control: KnownCameraControl) -> Result; + + /// Gets the current supported list of [`KnownCameraControls`] + /// # Errors + /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". + fn camera_controls(&self) -> Result, NokhwaError>; /// Sets the control to `control` in the camera. /// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way @@ -164,35 +156,39 @@ pub trait CaptureBackendTrait { /// # Errors /// If the `control` is not supported, the value is invalid (less than min, greater than max, not in step), or there was an error setting the control, /// this will error. - fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError>; - - /// Gets the current supported list of Controls as an `Any` from the backend. - /// The `Any`'s type is defined by the backend itself, please check each of the backend's documentation. - /// # Errors - /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". - fn raw_supported_camera_controls(&self) -> Result>, NokhwaError>; - - /// Sets the control to `control` in the camera. - /// The control's type is defined the backend itself. It may be a string, or more likely its a integer ID. - /// The backend itself has documentation of the proper input/return values, please check each of the backend's documentation. - /// # Errors - /// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, wrong Any type) - /// this will error. - fn raw_camera_control(&self, control: &dyn Any) -> Result, NokhwaError>; - - /// Sets the control to `control` in the camera. - /// The `control`/`value`'s type is defined the backend itself. It may be a string, or more likely its a integer ID/Value. - /// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way - /// then calling one of the methods to set the value: [`set_value()`](CameraControl::set_value()) or [`with_value()`](CameraControl::with_value()). - /// # Errors - /// If the `control` is not supported, the value is invalid (wrong Any type, backend refusal), or there was an error setting the control, - /// this will error. - fn set_raw_camera_control( + fn set_camera_control( &mut self, - control: &dyn Any, - value: &dyn Any, + id: KnownCameraControl, + value: ControlValueSetter, ) -> Result<(), NokhwaError>; + // /// Gets the current supported list of Controls as an `Any` from the backend. + // /// The `Any`'s type is defined by the backend itself, please check each of the backend's documentation. + // /// # Errors + // /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". + // fn raw_supported_camera_controls(&self) -> Result>, NokhwaError>; + // + // /// Sets the control to `control` in the camera. + // /// The control's type is defined the backend itself. It may be a string, or more likely its a integer ID. + // /// The backend itself has documentation of the proper input/return values, please check each of the backend's documentation. + // /// # Errors + // /// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, wrong Any type) + // /// this will error. + // fn raw_camera_control(&self, control: &dyn Any) -> Result, NokhwaError>; + // + // /// Sets the control to `control` in the camera. + // /// The `control`/`value`'s type is defined the backend itself. It may be a string, or more likely its a integer ID/Value. + // /// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way + // /// then calling one of the methods to set the value: [`set_value()`](CameraControl::set_value()) or [`with_value()`](CameraControl::with_value()). + // /// # Errors + // /// If the `control` is not supported, the value is invalid (wrong Any type, backend refusal), or there was an error setting the control, + // /// this will error. + // fn set_raw_camera_control( + // &mut self, + // control: &dyn Any, + // value: &dyn Any, + // ) -> Result<(), NokhwaError>; + /// Will open the camera stream with set parameters. This will be called internally if you try and call [`frame()`](CaptureBackendTrait::frame()) before you call [`open_stream()`](CaptureBackendTrait::open_stream()). /// # Errors /// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. @@ -244,6 +240,7 @@ pub trait CaptureBackendTrait { buffer: &mut [u8], write_alpha: bool, ) -> Result { + // FIXME: ?????? let cfmt = self.camera_format()?; let frame = self.frame_raw()?; let data = match cfmt.format() { @@ -259,7 +256,7 @@ pub trait CaptureBackendTrait { frame }; - buffer.copy_from_slice(&data); + buffer.copy_from_slice(&data) } }; Ok(frame.len()) diff --git a/src/threaded.rs b/src/threaded.rs index 2d10d27..2539061 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -15,8 +15,8 @@ */ use crate::{ - Camera, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, - FrameFormat, KnownCameraControls, NokhwaError, Resolution, Buffer + Buffer, Camera, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, + CaptureBackendTrait, FrameFormat, KnownCameraControl, NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; use parking_lot::Mutex; @@ -32,14 +32,11 @@ use std::{ type AtomicLock = Arc>; pub type CallbackFn = fn( _camera: &Arc>, - _frame_callback: &Arc< - Mutex>>, - >, + _frame_callback: &Arc>>>, _last_frame_captured: &Arc>, _die_bool: &Arc, ); -type HeldCallbackType = - Arc>>>; +type HeldCallbackType = Arc>>>; /// 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 `Mutex` to ensure that this feels like a normal camera, but callback based. @@ -201,7 +198,8 @@ impl CallbackCamera { /// # Errors /// If you started the stream and the camera rejects the new camera format, this will return an error. pub fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> { - *self.last_frame_captured.lock() = Buffer::new(new_res, Vec::default(), self.camera_format()?.format()); + *self.last_frame_captured.lock() = + Buffer::new(new_res, Vec::default(), self.camera_format()?.format()); self.camera.lock().set_camera_format(new_fmt) } @@ -233,7 +231,8 @@ impl CallbackCamera { /// # Errors /// If you started the stream and the camera rejects the new resolution, this will return an error. pub fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> { - *self.last_frame_captured.lock() = Buffer::new(new_res, Vec::default(), self.camera_format()?.format()); + *self.last_frame_captured.lock() = + Buffer::new(new_res, Vec::default(), self.camera_format()?.format()); self.camera.lock().set_resolution(new_res) } @@ -268,7 +267,7 @@ impl CallbackCamera { /// Gets the current supported list of [`KnownCameraControls`] /// # Errors /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". - pub fn supported_camera_controls(&self) -> Result, NokhwaError> { + pub fn supported_camera_controls(&self) -> Result, NokhwaError> { self.camera.lock().supported_camera_controls() } @@ -312,14 +311,14 @@ impl CallbackCamera { /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". pub fn camera_controls_known_camera_controls( &self, - ) -> Result, NokhwaError> { + ) -> Result, NokhwaError> { let known_controls = self.supported_camera_controls()?; let maybe_camera_controls = known_controls .iter() .map(|x| (*x, self.camera_control(*x))) .filter(|(_, x)| x.is_ok()) .map(|(c, x)| (c, Result::unwrap(x))) - .collect::>(); + .collect::>(); let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); for (kc, cc) in maybe_camera_controls { @@ -335,7 +334,7 @@ impl CallbackCamera { /// this will error. pub fn camera_control( &self, - control: KnownCameraControls, + control: KnownCameraControl, ) -> Result { self.camera.lock().camera_control(control) } @@ -391,10 +390,7 @@ impl CallbackCamera { where F: (FnMut(Buffer)) + Send + 'static, { - *self.frame_callback.lock() = - Some(Box::new(move |image: Buffer| { - callback(image) - })); + *self.frame_callback.lock() = Some(Box::new(move |image: Buffer| callback(image))); self.camera.lock().open_stream() } @@ -403,10 +399,7 @@ impl CallbackCamera { where F: (FnMut(Buffer)) + Send + 'static, { - *self.frame_callback.lock() = - Some(Box::new(move |image: Buffer| { - callback(image) - })); + *self.frame_callback.lock() = Some(Box::new(move |image: Buffer| callback(image))); } /// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame) diff --git a/src/utils.rs b/src/utils.rs index 564d36e..0807d1f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,8 +14,7 @@ * limitations under the License. */ -use crate::pixel_format::PixelFormat; -use crate::NokhwaError; +use crate::{pixel_format::PixelFormat, NokhwaError}; #[cfg(any( all( feature = "input-avfoundation", @@ -490,7 +489,7 @@ pub struct CameraInfo { human_name: String, description: String, misc: String, - index: usize, + index: u32, } #[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))] @@ -507,7 +506,7 @@ impl CameraInfo { human_name: impl AsRef, description: impl AsRef, misc: impl AsRef, - index: usize, + index: u32, ) -> Self { CameraInfo { human_name: human_name.as_ref().to_string(), @@ -664,51 +663,52 @@ impl From for CameraInfo { /// Note that not all backends/devices support all these. Run [`supported_camera_controls()`](crate::CaptureBackendTrait::supported_camera_controls) to see which ones can be set. #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum KnownCameraControls { +pub enum KnownCameraControl { Brightness, Contrast, Hue, Saturation, Sharpness, Gamma, - ColorEnable, WhiteBalance, BacklightComp, Gain, Pan, Tilt, - Roll, Zoom, Exposure, Iris, Focus, + /// 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. + /// + /// THIS SHOULD ONLY BE USED WHEN YOU KNOW THE PLATFORM THAT YOU ARE RUNNING ON. + Other(u128), } /// All camera controls in an array. #[must_use] -pub fn all_known_camera_controls() -> [KnownCameraControls; 17] { +pub const fn all_known_camera_controls() -> [KnownCameraControl; 15] { [ - KnownCameraControls::Brightness, - KnownCameraControls::Contrast, - KnownCameraControls::Hue, - KnownCameraControls::Saturation, - KnownCameraControls::Sharpness, - KnownCameraControls::Gamma, - KnownCameraControls::ColorEnable, - KnownCameraControls::WhiteBalance, - KnownCameraControls::BacklightComp, - KnownCameraControls::Gain, - KnownCameraControls::Pan, - KnownCameraControls::Tilt, - KnownCameraControls::Roll, - KnownCameraControls::Zoom, - KnownCameraControls::Exposure, - KnownCameraControls::Iris, - KnownCameraControls::Focus, + KnownCameraControl::Brightness, + KnownCameraControl::Contrast, + KnownCameraControl::Hue, + KnownCameraControl::Saturation, + KnownCameraControl::Sharpness, + KnownCameraControl::Gamma, + KnownCameraControl::WhiteBalance, + KnownCameraControl::BacklightComp, + KnownCameraControl::Gain, + KnownCameraControl::Pan, + KnownCameraControl::Tilt, + KnownCameraControl::Zoom, + KnownCameraControl::Exposure, + KnownCameraControl::Iris, + KnownCameraControl::Focus, ] } -impl Display for KnownCameraControls { +impl Display for KnownCameraControl { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", &self) } @@ -718,26 +718,24 @@ impl Display for KnownCameraControls { all(feature = "input-msmf", target_os = "windows"), all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") ))] -impl From for KnownCameraControls { +impl From for KnownCameraControl { fn from(mf_c: MediaFoundationControls) -> Self { match mf_c { - MediaFoundationControls::Brightness => KnownCameraControls::Brightness, - MediaFoundationControls::Contrast => KnownCameraControls::Contrast, - MediaFoundationControls::Hue => KnownCameraControls::Hue, - MediaFoundationControls::Saturation => KnownCameraControls::Saturation, - MediaFoundationControls::Sharpness => KnownCameraControls::Sharpness, - MediaFoundationControls::Gamma => KnownCameraControls::Gamma, - MediaFoundationControls::ColorEnable => KnownCameraControls::ColorEnable, - MediaFoundationControls::WhiteBalance => KnownCameraControls::WhiteBalance, - MediaFoundationControls::BacklightComp => KnownCameraControls::BacklightComp, - MediaFoundationControls::Gain => KnownCameraControls::Gain, - MediaFoundationControls::Pan => KnownCameraControls::Pan, - MediaFoundationControls::Tilt => KnownCameraControls::Tilt, - MediaFoundationControls::Roll => KnownCameraControls::Roll, - MediaFoundationControls::Zoom => KnownCameraControls::Zoom, - MediaFoundationControls::Exposure => KnownCameraControls::Exposure, - MediaFoundationControls::Iris => KnownCameraControls::Iris, - MediaFoundationControls::Focus => KnownCameraControls::Focus, + MediaFoundationControls::Brightness => KnownCameraControl::Brightness, + MediaFoundationControls::Contrast => KnownCameraControl::Contrast, + MediaFoundationControls::Hue => KnownCameraControl::Hue, + MediaFoundationControls::Saturation => KnownCameraControl::Saturation, + MediaFoundationControls::Sharpness => KnownCameraControl::Sharpness, + MediaFoundationControls::Gamma => KnownCameraControl::Gamma, + MediaFoundationControls::WhiteBalance => KnownCameraControl::WhiteBalance, + MediaFoundationControls::BacklightComp => KnownCameraControl::BacklightComp, + MediaFoundationControls::Gain => KnownCameraControl::Gain, + MediaFoundationControls::Pan => KnownCameraControl::Pan, + MediaFoundationControls::Tilt => KnownCameraControl::Tilt, + MediaFoundationControls::Zoom => KnownCameraControl::Zoom, + MediaFoundationControls::Exposure => KnownCameraControl::Exposure, + MediaFoundationControls::Iris => KnownCameraControl::Iris, + MediaFoundationControls::Focus => KnownCameraControl::Focus, } } } @@ -746,39 +744,33 @@ impl From for KnownCameraControls { all(feature = "input-msmf", target_os = "windows"), all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf") ))] -impl From for KnownCameraControls { +impl From for KnownCameraControl { fn from(mf_cc: MFControl) -> Self { mf_cc.control().into() } } #[cfg(all(feature = "input-v4l", target_os = "linux"))] -impl TryFrom for KnownCameraControls { - type Error = NokhwaError; - - fn try_from(value: Description) -> Result { - Ok(match value.id { - 9_963_776 => KnownCameraControls::Brightness, - 9_963_777 => KnownCameraControls::Contrast, - 9_963_779 => KnownCameraControls::Hue, - 9_963_778 => KnownCameraControls::Saturation, - 9_963_803 => KnownCameraControls::Sharpness, - 9_963_792 => KnownCameraControls::Gamma, - 9_963_802 => KnownCameraControls::WhiteBalance, - 9_963_804 => KnownCameraControls::BacklightComp, - 9_963_795 => KnownCameraControls::Gain, - 10_094_852 => KnownCameraControls::Pan, - 10_094_853 => KnownCameraControls::Tilt, - 10_094_862 => KnownCameraControls::Zoom, - 10_094_850 => KnownCameraControls::Exposure, - 10_094_866 => KnownCameraControls::Iris, - 10_094_859 => KnownCameraControls::Focus, - _ => { - return Err(NokhwaError::NotImplementedError( - "Control not implemented!".to_string(), - )) - } - }) +impl From for KnownCameraControl { + fn from(value: Description) -> KnownCameraControl { + match value.id { + 9_963_776 => KnownCameraControl::Brightness, + 9_963_777 => KnownCameraControl::Contrast, + 9_963_779 => KnownCameraControl::Hue, + 9_963_778 => KnownCameraControl::Saturation, + 9_963_803 => KnownCameraControl::Sharpness, + 9_963_792 => KnownCameraControl::Gamma, + 9_963_802 => KnownCameraControl::WhiteBalance, + 9_963_804 => KnownCameraControl::BacklightComp, + 9_963_795 => KnownCameraControl::Gain, + 10_094_852 => KnownCameraControl::Pan, + 10_094_853 => KnownCameraControl::Tilt, + 10_094_862 => KnownCameraControl::Zoom, + 10_094_850 => KnownCameraControl::Exposure, + 10_094_866 => KnownCameraControl::Iris, + 10_094_859 => KnownCameraControl::Focus, + id => KnownCameraControl::Other(id as u128), + } } } @@ -788,6 +780,11 @@ impl TryFrom for KnownCameraControls { pub enum KnownCameraControlFlag { Automatic, Manual, + ReadOnly, + WriteOnly, + Volatile, + Disabled, + Inactive, } impl Display for KnownCameraControlFlag { @@ -799,7 +796,10 @@ impl Display for KnownCameraControlFlag { #[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] // TODO: use in CameraControl -pub enum ControlValue { +/// The values for a [`CameraControl`]. +/// +/// This provides a wide range of values that can be used to control a camera. +pub enum ControlDescription { None, Integer { value: i64, @@ -807,11 +807,23 @@ pub enum ControlValue { step: i64, }, IntegerRange { - min: i32, - max: i32, - value: i32, - step: i32, - default: i32, + min: i64, + max: i64, + value: i64, + step: i64, + default: i64, + }, + Float { + value: f64, + default: f64, + step: f64, + }, + FloatRange { + min: f64, + max: f64, + value: f64, + step: f64, + default: f64, }, Boolean { value: bool, @@ -819,15 +831,108 @@ pub enum ControlValue { }, String { value: String, - default: String, + default: Option, }, Bytes { value: Vec, default: Vec, + }, +} + +impl ControlDescription { + pub(crate) fn value(&self) -> ControlValueSetter { + match self { + ControlDescription::None => ControlValueSetter::None, + ControlDescription::Integer { value, .. } => ControlValueSetter::Integer(*value), + ControlDescription::IntegerRange { value, .. } => ControlValueSetter::Integer(*value), + ControlDescription::Float { value, .. } => ControlValueSetter::Float(*value), + ControlDescription::FloatRange { value, .. } => ControlValueSetter::Float(*value), + ControlDescription::Boolean { value, .. } => ControlValueSetter::Boolean(*value), + ControlDescription::String { value, .. } => ControlValueSetter::String(value.clone()), + ControlDescription::Bytes { value, .. } => ControlValueSetter::Bytes(value.clone()), + } } } -/// This struct tells you everything about a particular [`KnownCameraControls`].
+impl Display for ControlDescription { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ControlDescription::None => { + write!(f, "(None)") + } + ControlDescription::Integer { + value, + default, + step, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {})", + value, default, step + ) + } + ControlDescription::IntegerRange { + min, + max, + value, + step, + default, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {}, Range: ({}, {}))", + value, default, step, min, max + ) + } + ControlDescription::Float { + value, + default, + step, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {})", + value, default, step + ) + } + ControlDescription::FloatRange { + min, + max, + value, + step, + default, + } => { + write!( + f, + "(Current: {}, Default: {}, Step: {}, Range: ({}, {}))", + value, default, step, min, max + ) + } + ControlDescription::Boolean { value, default } => { + write!(f, "(Current: {}, Default: {})", value, default) + } + ControlDescription::String { value, default } => { + write!(f, "(Current: {}, Default: {:?})", value, default) + } + ControlDescription::Bytes { value, default } => { + write!(f, "(Current: {:x?}, Default: {:x?})", value, default) + } + } + } +} + +fn step_chk(val: i64, default: i64, step: i64) -> Result<(), NokhwaError> { + if (val - default) % step != 0 { + return Err(NokhwaError::StructureError { + structure: "Value".to_string(), + error: "Doesnt fit step".to_string(), + }); + } + Ok(()) +} + +/// This struct tells you everything about a particular [`KnownCameraControls`]. +/// /// However, you should never need to instantiate this struct, since its usually generated for you by `nokhwa`. /// The only time you should be modifying this struct is when you need to set a value and pass it back to the camera. /// NOTE: Assume the values for `min` and `max` as **non-inclusive**!. @@ -835,114 +940,48 @@ pub enum ControlValue { #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CameraControl { - control: KnownCameraControls, - min: i32, - max: i32, - value: i32, - step: i32, - default: i32, - flag: KnownCameraControlFlag, + control: KnownCameraControl, + name: String, + value: ControlDescription, + flag: Vec, active: bool, } impl CameraControl { /// Creates a new [`CameraControl`] - /// # Errors - /// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error - #[allow(clippy::too_many_arguments)] pub fn new( - control: KnownCameraControls, - minimum: i32, - maximum: i32, - value: i32, - step: i32, - default: i32, - flag: KnownCameraControlFlag, + control: KnownCameraControl, + name: String, + value: ControlDescription, + flag: Vec, active: bool, - ) -> Result { - if value >= maximum { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Value too large".to_string(), - }); - } - if value <= minimum { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Value too low".to_string(), - }); - } - if value % step != 0 { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Not aligned with step".to_string(), - }); - } - - Ok(CameraControl { + ) -> Self { + CameraControl { control, - min: minimum, - max: maximum, + name, value, - step, - default, flag, active, - }) + } } /// Gets the [`KnownCameraControls`] of this [`CameraControl`] #[must_use] - pub fn control(&self) -> KnownCameraControls { + pub fn control(&self) -> KnownCameraControl { self.control } - /// Gets the minimum value of this [`CameraControl`] - #[must_use] - pub fn minimum_value(&self) -> i32 { - self.min - } - - /// Gets the maximum value of this [`CameraControl`] - #[must_use] - pub fn maximum_value(&self) -> i32 { - self.max - } - /// Gets the current value of this [`CameraControl`] #[must_use] - pub fn value(&self) -> i32 { - self.value + pub fn value(&self) -> &ControlDescription { + &self.value } /// Sets the value of this [`CameraControl`] /// # Errors /// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error - pub fn set_value(&mut self, value: i32) -> Result<(), NokhwaError> { - if value >= self.maximum_value() { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Value too large".to_string(), - }); - } - if value <= self.minimum_value() { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Value too low".to_string(), - }); - } - if value == self.minimum_value() || value == self.maximum_value() { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Values not inclusive".to_string(), - }); - } - if value % self.step() != 0 { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Not aligned with step".to_string(), - }); - } + pub fn set_value(&mut self, value: ControlValue) -> Result<(), NokhwaError> { + value.verify_value()?; self.value = value; Ok(()) @@ -951,56 +990,22 @@ impl CameraControl { /// Creates a new [`CameraControl`] but with `value` /// # Errors /// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error - pub fn with_value(self, value: i32) -> Result { - if value >= self.maximum_value() { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Value too large".to_string(), - }); - } - if value <= self.minimum_value() { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Value too low".to_string(), - }); - } - if value % self.step() != 0 { - return Err(NokhwaError::StructureError { - structure: "CameraControl".to_string(), - error: "Not aligned with step".to_string(), - }); - } + pub fn with_value(self, value: ControlValue) -> Result { + value.verify_value()?; - Ok(CameraControl { + Ok(ControlDescription { control: self.control(), - min: self.minimum_value(), - max: self.maximum_value(), value, - step: self.step(), - default: self.default(), flag: self.flag(), active: true, }) } - /// Gets the step value of this [`CameraControl`] - /// Note that `value` must be divisible by `step` - #[must_use] - pub fn step(&self) -> i32 { - self.step - } - - /// Gets the default value of this [`CameraControl`] - #[must_use] - pub fn default(&self) -> i32 { - self.default - } - /// Gets the [`KnownCameraControlFlag`] of this [`CameraControl`], /// telling you weather this control is automatically set or manually set. #[must_use] - pub fn flag(&self) -> KnownCameraControlFlag { - self.flag + pub fn flag(&self) -> &[KnownCameraControlFlag] { + &self.flag } /// Gets `active` of this [`CameraControl`], @@ -1031,15 +1036,8 @@ impl Display for CameraControl { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "Name: {}, Range: ({}~{}), Current: {}, Step: {}, Default: {}, Flag: {}, Active: {}", - self.control, - self.min, - self.max, - self.value, - self.step, - self.default, - self.flag, - self.active + "Control: {}, Name: {}, Value: {}, Flag: {:?}, Active: {}", + self.control, self.name, self.value, self.flag, self.active ) } } @@ -1056,6 +1054,43 @@ impl Ord for CameraControl { } } +/// The setter for a control value +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ControlValueSetter { + None, + Integer(i64), + Float(f64), + Boolean(bool), + String(String), + Bytes(Vec), +} + +impl Display for ControlValueSetter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ControlValueSetter::None => { + write!(f, "Value: None") + } + ControlValueSetter::Integer(i) => { + write!(f, "Value: {}", i) + } + ControlValueSetter::Float(f) => { + write!(f, "Value: {}", f) + } + ControlValueSetter::Boolean(b) => { + write!(f, "Value: {}", b) + } + ControlValueSetter::String(s) => { + write!(f, "Value: {}", s) + } + ControlValueSetter::Bytes(b) => { + write!(f, "Value: {:x?}", b) + } + } + } +} + /// The list of known capture backends to the library.
/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform. /// - `AVFoundation` - Uses `AVFoundation` on `MacOSX` @@ -1072,7 +1107,7 @@ pub enum CaptureAPIBackend { Auto, AVFoundation, Video4Linux, - UniversalVideoClass, + // UniversalVideoClass, MediaFoundation, OpenCv, // GStreamer, @@ -1359,9 +1394,9 @@ pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] { let c298 = (y - 16) * 298; let d = u - 128; let e = v - 128; - let r = ((c298 + 409 * e + 128) >> 8).clamp(0, 255) as u8; - let g = ((c298 - 100 * d - 208 * e + 128) >> 8).clamp(0, 255) as u8; - let b = ((c298 + 516 * d + 128) >> 8).clamp(0, 255) as u8; + let r = ((c298 + 409 * e + 128) >> 8) as u8; + let g = ((c298 - 100 * d - 208 * e + 128) >> 8) as u8; + let b = ((c298 + 516 * d + 128) >> 8) as u8; [r, g, b] } From 11d1f405a9a35ca89a63d71d5b5b0259d36fd7ea Mon Sep 17 00:00:00 2001 From: liua Date: Wed, 22 Jun 2022 17:48:38 +0900 Subject: [PATCH 55/89] properly add link attributes to avfoundation --- nokhwa-bindings-macos/src/lib.rs | 82 +++++++++++--------------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 7523df1..76ce0a3 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -13,15 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#![cfg_attr( - any(target_os = "macos", target_os = "ios"), - link(name = "AVFoundation", kind = "framework") -)] -#![cfg_attr( - any(target_os = "macos", target_os = "ios"), - link(name = "CoreMedia", kind = "framework") -)] #![allow(clippy::not_unsafe_ptr_arg_deref)] #[cfg(any(target_os = "macos", target_os = "ios"))] @@ -60,14 +51,6 @@ pub enum AVFError { } #[cfg(any(target_os = "macos", target_os = "ios"))] -#[cfg_attr( - any(target_os = "macos", target_os = "ios"), - link(name = "CoreMedia", kind = "framework") -)] -#[cfg_attr( - any(target_os = "macos", target_os = "ios"), - link(name = "AVFoundation", kind = "framework") -)] #[allow(non_snake_case)] pub mod core_media { // all of this is stolen from bindgen @@ -115,20 +98,8 @@ pub mod core_media { pub type AVMediaType = NSString; - extern "C" { - pub static AVMediaTypeVideo: AVMediaType; - pub static AVMediaTypeAudio: AVMediaType; - pub static AVMediaTypeText: AVMediaType; - pub static AVMediaTypeClosedCaption: AVMediaType; - pub static AVMediaTypeSubtitle: AVMediaType; - pub static AVMediaTypeTimecode: AVMediaType; - pub static AVMediaTypeMetadata: AVMediaType; - pub static AVMediaTypeMuxed: AVMediaType; - pub static AVMediaTypeMetadataObject: AVMediaType; - pub static AVMediaTypeDepthData: AVMediaType; - } - #[allow(non_snake_case)] + #[link(name = "CoreMedia", kind = "framework")] extern "C" { pub fn CMVideoFormatDescriptionGetDimensions( videoDesc: CMFormatDescriptionRef, @@ -146,38 +117,16 @@ pub mod core_media { ) -> std::os::raw::c_int; pub fn CMSampleBufferGetDataBuffer(sbuf: CMSampleBufferRef) -> CMBlockBufferRef; - } - extern "C" { pub fn dispatch_queue_create( label: *const ::std::os::raw::c_char, attr: NSObject, ) -> NSObject; - } - extern "C" { pub fn dispatch_release(object: NSObject); - } - #[repr(C)] - #[derive(Debug, Copy, Clone)] - pub struct __CVBuffer { - _unused: [u8; 0], - } - pub type CVBufferRef = *mut __CVBuffer; - - #[allow(non_snake_case)] - extern "C" { pub fn CMSampleBufferGetImageBuffer(sbuf: CMSampleBufferRef) -> CVImageBufferRef; - } - pub type CVImageBufferRef = CVBufferRef; - pub type CVPixelBufferRef = CVImageBufferRef; - pub type CVPixelBufferLockFlags = u64; - pub type CVReturn = i32; - - #[allow(non_snake_case)] - extern "C" { pub fn CVPixelBufferLockBaseAddress( pixelBuffer: CVPixelBufferRef, lockFlags: CVPixelBufferLockFlags, @@ -193,14 +142,28 @@ pub mod core_media { pub fn CVPixelBufferGetBaseAddress( pixelBuffer: CVPixelBufferRef, ) -> *mut ::std::os::raw::c_void; + + pub fn CVPixelBufferGetPixelFormatType(pixelBuffer: CVPixelBufferRef) -> OSType; } - extern "C" { - pub static AVVideoCodecKey: NSString; + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct __CVBuffer { + _unused: [u8; 0], } + pub type CVBufferRef = *mut __CVBuffer; + + pub type CVImageBufferRef = CVBufferRef; + pub type CVPixelBufferRef = CVImageBufferRef; + pub type CVPixelBufferLockFlags = u64; + pub type CVReturn = i32; + pub type OSType = FourCharCode; pub type AVVideoCodecType = NSString; + + #[link(name = "AVFoundation", kind = "framework")] extern "C" { + pub static AVVideoCodecKey: NSString; pub static AVVideoCodecTypeHEVC: AVVideoCodecType; pub static AVVideoCodecTypeH264: AVVideoCodecType; pub static AVVideoCodecTypeJPEG: AVVideoCodecType; @@ -218,8 +181,17 @@ pub mod core_media { pub static AVVideoWidthKey: NSString; pub static AVVideoHeightKey: NSString; pub static AVVideoExpectedSourceFrameRateKey: NSString; - pub fn CVPixelBufferGetPixelFormatType(pixelBuffer: CVPixelBufferRef) -> OSType; + pub static AVMediaTypeVideo: AVMediaType; + pub static AVMediaTypeAudio: AVMediaType; + pub static AVMediaTypeText: AVMediaType; + pub static AVMediaTypeClosedCaption: AVMediaType; + pub static AVMediaTypeSubtitle: AVMediaType; + pub static AVMediaTypeTimecode: AVMediaType; + pub static AVMediaTypeMetadata: AVMediaType; + pub static AVMediaTypeMuxed: AVMediaType; + pub static AVMediaTypeMetadataObject: AVMediaType; + pub static AVMediaTypeDepthData: AVMediaType; } } From d17fe466e96df0786fe38b0f8543a9776623e62c Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Wed, 22 Jun 2022 18:00:22 +0900 Subject: [PATCH 56/89] add proper links to avfoundation #46 --- nokhwa-bindings-macos/src/lib.rs | 82 +++++++++++--------------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 7523df1..76ce0a3 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -13,15 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#![cfg_attr( - any(target_os = "macos", target_os = "ios"), - link(name = "AVFoundation", kind = "framework") -)] -#![cfg_attr( - any(target_os = "macos", target_os = "ios"), - link(name = "CoreMedia", kind = "framework") -)] #![allow(clippy::not_unsafe_ptr_arg_deref)] #[cfg(any(target_os = "macos", target_os = "ios"))] @@ -60,14 +51,6 @@ pub enum AVFError { } #[cfg(any(target_os = "macos", target_os = "ios"))] -#[cfg_attr( - any(target_os = "macos", target_os = "ios"), - link(name = "CoreMedia", kind = "framework") -)] -#[cfg_attr( - any(target_os = "macos", target_os = "ios"), - link(name = "AVFoundation", kind = "framework") -)] #[allow(non_snake_case)] pub mod core_media { // all of this is stolen from bindgen @@ -115,20 +98,8 @@ pub mod core_media { pub type AVMediaType = NSString; - extern "C" { - pub static AVMediaTypeVideo: AVMediaType; - pub static AVMediaTypeAudio: AVMediaType; - pub static AVMediaTypeText: AVMediaType; - pub static AVMediaTypeClosedCaption: AVMediaType; - pub static AVMediaTypeSubtitle: AVMediaType; - pub static AVMediaTypeTimecode: AVMediaType; - pub static AVMediaTypeMetadata: AVMediaType; - pub static AVMediaTypeMuxed: AVMediaType; - pub static AVMediaTypeMetadataObject: AVMediaType; - pub static AVMediaTypeDepthData: AVMediaType; - } - #[allow(non_snake_case)] + #[link(name = "CoreMedia", kind = "framework")] extern "C" { pub fn CMVideoFormatDescriptionGetDimensions( videoDesc: CMFormatDescriptionRef, @@ -146,38 +117,16 @@ pub mod core_media { ) -> std::os::raw::c_int; pub fn CMSampleBufferGetDataBuffer(sbuf: CMSampleBufferRef) -> CMBlockBufferRef; - } - extern "C" { pub fn dispatch_queue_create( label: *const ::std::os::raw::c_char, attr: NSObject, ) -> NSObject; - } - extern "C" { pub fn dispatch_release(object: NSObject); - } - #[repr(C)] - #[derive(Debug, Copy, Clone)] - pub struct __CVBuffer { - _unused: [u8; 0], - } - pub type CVBufferRef = *mut __CVBuffer; - - #[allow(non_snake_case)] - extern "C" { pub fn CMSampleBufferGetImageBuffer(sbuf: CMSampleBufferRef) -> CVImageBufferRef; - } - pub type CVImageBufferRef = CVBufferRef; - pub type CVPixelBufferRef = CVImageBufferRef; - pub type CVPixelBufferLockFlags = u64; - pub type CVReturn = i32; - - #[allow(non_snake_case)] - extern "C" { pub fn CVPixelBufferLockBaseAddress( pixelBuffer: CVPixelBufferRef, lockFlags: CVPixelBufferLockFlags, @@ -193,14 +142,28 @@ pub mod core_media { pub fn CVPixelBufferGetBaseAddress( pixelBuffer: CVPixelBufferRef, ) -> *mut ::std::os::raw::c_void; + + pub fn CVPixelBufferGetPixelFormatType(pixelBuffer: CVPixelBufferRef) -> OSType; } - extern "C" { - pub static AVVideoCodecKey: NSString; + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct __CVBuffer { + _unused: [u8; 0], } + pub type CVBufferRef = *mut __CVBuffer; + + pub type CVImageBufferRef = CVBufferRef; + pub type CVPixelBufferRef = CVImageBufferRef; + pub type CVPixelBufferLockFlags = u64; + pub type CVReturn = i32; + pub type OSType = FourCharCode; pub type AVVideoCodecType = NSString; + + #[link(name = "AVFoundation", kind = "framework")] extern "C" { + pub static AVVideoCodecKey: NSString; pub static AVVideoCodecTypeHEVC: AVVideoCodecType; pub static AVVideoCodecTypeH264: AVVideoCodecType; pub static AVVideoCodecTypeJPEG: AVVideoCodecType; @@ -218,8 +181,17 @@ pub mod core_media { pub static AVVideoWidthKey: NSString; pub static AVVideoHeightKey: NSString; pub static AVVideoExpectedSourceFrameRateKey: NSString; - pub fn CVPixelBufferGetPixelFormatType(pixelBuffer: CVPixelBufferRef) -> OSType; + pub static AVMediaTypeVideo: AVMediaType; + pub static AVMediaTypeAudio: AVMediaType; + pub static AVMediaTypeText: AVMediaType; + pub static AVMediaTypeClosedCaption: AVMediaType; + pub static AVMediaTypeSubtitle: AVMediaType; + pub static AVMediaTypeTimecode: AVMediaType; + pub static AVMediaTypeMetadata: AVMediaType; + pub static AVMediaTypeMuxed: AVMediaType; + pub static AVMediaTypeMetadataObject: AVMediaType; + pub static AVMediaTypeDepthData: AVMediaType; } } From 4cc71ef37823e2387baf3e11ffe27909381552ff Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Wed, 22 Jun 2022 18:24:22 +0900 Subject: [PATCH 57/89] camera trait adjustments --- src/backends/capture/avfoundation.rs | 44 +++++++++++++--------------- src/camera_traits.rs | 39 ++++++++++++------------ 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index 34e4da7..37ea515 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -14,10 +14,7 @@ * limitations under the License. */ -use crate::{ - mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, - CaptureBackendTrait, FrameFormat, KnownCameraControl, NokhwaError, Resolution, -}; +use crate::{mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControl, NokhwaError, Resolution, PixelFormat, ControlValueSetter}; use image::{ImageBuffer, Rgb}; use nokhwa_bindings_macos::avfoundation::{ query_avfoundation, AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, @@ -97,6 +94,10 @@ impl AVFoundationCaptureDevice { } impl CaptureBackendTrait for AVFoundationCaptureDevice { + fn init(&mut self) -> Result { + todo!() + } + fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::AVFoundation } @@ -105,6 +106,10 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { &self.info } + fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> { + todo!() + } + fn camera_format(&self) -> CameraFormat { self.format } @@ -182,37 +187,23 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { self.set_camera_format(format) } - fn supported_camera_controls(&self) -> Result, NokhwaError> { - Err(NokhwaError::NotImplementedError( - "Not Implemented".to_string(), - )) - } - fn camera_control(&self, _: KnownCameraControl) -> Result { Err(NokhwaError::NotImplementedError( "Not Implemented".to_string(), )) } - fn set_camera_control(&mut self, _: CameraControl) -> Result<(), NokhwaError> { + fn camera_controls(&self) -> Result, NokhwaError> { Err(NokhwaError::NotImplementedError( "Not Implemented".to_string(), )) } - fn raw_supported_camera_controls(&self) -> Result>, NokhwaError> { - Err(NokhwaError::NotImplementedError( - "Not Implemented".to_string(), - )) - } - - fn raw_camera_control(&self, _: &dyn Any) -> Result, NokhwaError> { - Err(NokhwaError::NotImplementedError( - "Not Implemented".to_string(), - )) - } - - fn set_raw_camera_control(&mut self, _: &dyn Any, _: &dyn Any) -> Result<(), NokhwaError> { + fn set_camera_control( + &mut self, + _: KnownCameraControl, + _: ControlValueSetter, + ) -> Result<(), NokhwaError> { Err(NokhwaError::NotImplementedError( "Not Implemented".to_string(), )) @@ -268,6 +259,10 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { Ok(image_buf) } + fn frame_typed(&mut self) -> Result>, NokhwaError> { + todo!() + } + fn frame_raw(&mut self) -> Result, NokhwaError> { match &self.session { Some(session) => { @@ -295,6 +290,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { let data = match data.1 { AVFourCC::YUV2 => Cow::from(yuyv422_to_rgb(data.0.borrow(), false)), AVFourCC::MJPEG => Cow::from(mjpeg_to_rgb(data.0.borrow(), false)), + AVFourCC::GRAY8 => {} }; Ok(data) } diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 3e8959c..8a8fc7f 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -20,7 +20,7 @@ use crate::{ utils::{ buf_mjpeg_to_rgb, buf_yuyv422_to_rgb, CameraFormat, CameraInfo, FrameFormat, Resolution, }, - Buffer, CameraControl, CaptureAPIBackend, KnownCameraControl, PixelFormat, + Buffer, CameraControl, CaptureAPIBackend, ControlValueSetter, KnownCameraControl, PixelFormat, }; use enum_dispatch::enum_dispatch; use image::{buffer::ConvertBuffer, ImageBuffer, RgbaImage}; @@ -82,23 +82,20 @@ pub trait CaptureBackendTrait { ) -> Result>, NokhwaError>; fn compatible_camera_formats(&mut self) -> Result, NokhwaError> { - let mut compatible_formats = vec![]; - frame_formats().map(|ff| { - if let Ok(mut fmts) = self.compatible_list_by_resolution(ff).map(|compatible| { - compatible + let compatible_formats = self + .compatible_fourcc()? + .into_iter() + .map(|ffmt| { + self.compatible_list_by_resolution(ffmt)? .into_iter() - .map(|(res, fps)| { - fps.into_iter().map(|rate| CameraFormat { - resolution: res, - format: ff, - frame_rate: rate, - }) + .map(|(resolution, fpses)| { + fpses + .into_iter() + .map(|fps| CameraFormat::new(resolution, ffmt, fps)) }) - .collect::>() - }) { - compatible_formats.append(&mut fmts) - } - }) + }) + .collect::>(); + Ok(compatible_formats) } /// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most. @@ -227,9 +224,9 @@ pub trait CaptureBackendTrait { FrameFormat::GRAY8 => 1, }; if alpha { - return (resolution.width() * resolution.height() * (pxwidth + 1)) as usize; + return Ok((resolution.width() * resolution.height() * (pxwidth + 1)) as usize); } - (resolution.width() * resolution.height() * pxwidth) as usize + Ok((resolution.width() * resolution.height() * pxwidth) as usize) } /// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture. @@ -243,9 +240,9 @@ pub trait CaptureBackendTrait { // FIXME: ?????? let cfmt = self.camera_format()?; let frame = self.frame_raw()?; - let data = match cfmt.format() { - FrameFormat::MJPEG => buf_mjpeg_to_rgb(&frame, buffer, write_alpha), - FrameFormat::YUYV => buf_yuyv422_to_rgb(&frame, buffer, write_alpha), + match cfmt.format() { + FrameFormat::MJPEG => buf_mjpeg_to_rgb(&frame, buffer, write_alpha)?, + FrameFormat::YUYV => buf_yuyv422_to_rgb(&frame, buffer, write_alpha)?, FrameFormat::GRAY8 => { let data = if write_alpha { frame From f3fb70d7f53607b12bf2170704a0ff8aea1628ca Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Wed, 22 Jun 2022 18:26:35 +0900 Subject: [PATCH 58/89] adjust copyright notices --- examples/capture/src/main.rs | 2 +- examples/jscam/setup.sh | 2 +- examples/jscam/src/index.html | 2 +- examples/threaded-capture/src/main.rs | 2 +- make-npm.sh | 2 +- nokhwa-bindings-macos/build.rs | 2 +- nokhwa-bindings-macos/src/lib.rs | 2 +- nokhwa-bindings-windows/src/lib.rs | 2 +- src/backends/capture/avfoundation.rs | 12 +++++++++--- src/backends/capture/browser_backend.rs | 16 ++++++++++++++++ src/backends/capture/gst_backend.rs | 2 +- src/backends/capture/mod.rs | 2 +- src/backends/capture/msmf_backend.rs | 2 +- src/backends/capture/opencv_backend.rs | 2 +- src/backends/capture/uvc_backend.rs | 2 +- src/backends/capture/v4l2_backend.rs | 2 +- src/backends/mod.rs | 2 +- src/camera.rs | 2 +- src/camera_traits.rs | 2 +- src/error.rs | 2 +- src/init.rs | 2 +- src/js_camera.rs | 2 +- src/lib.rs | 2 +- src/network_camera.rs | 2 +- src/query.rs | 2 +- src/threaded.rs | 2 +- src/utils.rs | 2 +- 27 files changed, 50 insertions(+), 28 deletions(-) diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index fcf6328..00db445 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -1,5 +1,5 @@ /* - * Copyright 2021 l1npengtul / The Nokhwa Contributors + * Copyright 2022 l1npengtul / 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. diff --git a/examples/jscam/setup.sh b/examples/jscam/setup.sh index 501ffa0..0178410 100644 --- a/examples/jscam/setup.sh +++ b/examples/jscam/setup.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright 2021 l1npengtul / The Nokhwa Contributors +# Copyright 2022 l1npengtul / 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. diff --git a/examples/jscam/src/index.html b/examples/jscam/src/index.html index 0f6f3f0..e9a8a76 100644 --- a/examples/jscam/src/index.html +++ b/examples/jscam/src/index.html @@ -1,5 +1,5 @@