diff --git a/Cargo.toml b/Cargo.toml index 29dd7cb..2eeedb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nokhwa" -version = "0.8.0" +version = "0.9.0" authors = ["l1npengtul "] edition = "2018" description = "A Simple-to-use, cross-platform Rust Webcam Capture Library" diff --git a/README.md b/README.md index be02e0a..7ce8107 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The table below lists current Nokhwa API support. | Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux | | MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows | | AVFoundation(`input-avfoundatuin`)^^| ✅ | ✅ | ✅ | Mac | - | libuvc(`input-uvc`) | ✅ | ✅ | ✅ | Linux, Windows, 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 | @@ -55,7 +55,10 @@ The table below lists current Nokhwa API support. ✅: Working, 🔮 : Experimental, ❌ : Not Supported, 🚧: Planned/WIP ^ = No CameraFormat setting support. + ^^ = No FPS setting support. + + ^^^ = `input-uvc` is disabled for now as there are lifetime/soundness holes. You can still query, however. ## Feature The default feature includes nothing. Anything starting with `input-*` is a feature that enables the specific backend. As a general rule of thumb, you would want to keep at least `input-uvc` or other backend that has querying enabled so you can get device information from `nokhwa`. diff --git a/examples/setting/Cargo.toml b/examples/setting/Cargo.toml new file mode 100644 index 0000000..30c218a --- /dev/null +++ b/examples/setting/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "setting" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[dependencies.image] +version = "0.23.14" +no-default-features = true + +[dependencies.nokhwa] +path = "../../../nokhwa" +# EDIT THIS! +features = ["input-v4l"] diff --git a/examples/setting/src/main.rs b/examples/setting/src/main.rs new file mode 100644 index 0000000..4dc5f14 --- /dev/null +++ b/examples/setting/src/main.rs @@ -0,0 +1,8 @@ +use nokhwa::{Camera, CameraFormat, KnownCameraControls}; + +fn main() { + let mut camera = Camera::new(0, None).unwrap(); + let known = camera.camera_controls_known_camera_controls().unwrap(); + let mut control = *known.get(&KnownCameraControls::Gamma).unwrap(); + control.set_value(101).unwrap(); +} diff --git a/examples/threaded-capture/Cargo.toml b/examples/threaded-capture/Cargo.toml index e729a6c..9cb829c 100644 --- a/examples/threaded-capture/Cargo.toml +++ b/examples/threaded-capture/Cargo.toml @@ -14,4 +14,4 @@ no-default-features = true [dependencies.nokhwa] path = "../../../nokhwa" # EDIT THIS! -features = ["input-v4l", "output-threaded"] \ No newline at end of file +features = ["input-v4l", "output-threaded"] diff --git a/src/backends/capture/v4l2.rs b/src/backends/capture/v4l2.rs index fcda81f..0fd31b8 100644 --- a/src/backends/capture/v4l2.rs +++ b/src/backends/capture/v4l2.rs @@ -188,7 +188,7 @@ impl<'a> V4LCaptureDevice<'a> { Err(why) => { return Err(NokhwaError::OpenDeviceError( index.to_string(), - format!("V4L2 Error: {}", why.to_string()), + format!("V4L2 Error: {}", why), )) } }; @@ -379,7 +379,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { // undo if let Err(why) = Capture::set_format(&self.device, &prev_format) { return Err(NokhwaError::SetPropertyError { - property: format!("Attempt undo due to stream acquisition failure with error {}. Resolution, FrameFormat", why.to_string()), + property: format!("Attempt undo due to stream acquisition failure with error {}. Resolution, FrameFormat", why), value: prev_format.to_string(), error: why.to_string(), }); @@ -387,7 +387,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { if let Err(why) = Capture::set_params(&self.device, &prev_fps) { return Err(NokhwaError::SetPropertyError { property: - format!("Attempt undo due to stream acquisition failure with error {}. Frame rate", why.to_string()), + format!("Attempt undo due to stream acquisition failure with error {}. Frame rate", why), value: prev_fps.to_string(), error: why.to_string(), }); diff --git a/src/camera.rs b/src/camera.rs index 3a945bf..f76ccf1 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -575,6 +575,12 @@ macro_rules! cap_impl_matches { } } )+ + + _ => { + return Err(NokhwaError::NotImplementedError( + "Platform requirements not satisfied. (Wrong Platform - Not Selected)".to_string(), + )); + } } } } @@ -583,7 +589,7 @@ macro_rules! cap_impl_matches { cap_impl_fn! { (GStreamerCaptureDevice, new, feature = "input-gst", gst), (OpenCvCaptureDevice, new_autopref, feature = "input-opencv", opencv), - (UVCCaptureDevice, create, feature = "input-uvc", uvc), + // (UVCCaptureDevice, create, feature = "input-uvc", uvc), (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) @@ -599,7 +605,7 @@ fn init_camera( ("input-v4l", Video4Linux, init_v4l), ("input-msmf", MediaFoundation, init_msmf), ("input-avfoundation", AVFoundation, init_avfoundation), - ("input-uvc", UniversalVideoClass, init_uvc), + // ("input-uvc", UniversalVideoClass, init_uvc), ("input-gst", GStreamer, init_gst), ("input-opencv", OpenCv, init_opencv) }; diff --git a/src/query.rs b/src/query.rs index 2b4c2f0..3a8abd5 100644 --- a/src/query.rs +++ b/src/query.rs @@ -153,7 +153,7 @@ fn query_uvc() -> Result, NokhwaError> { Err(why) => { return Err(NokhwaError::GeneralError(format!( "UVC Context failure: {}", - why.to_string() + why ))) } }; @@ -167,7 +167,7 @@ fn query_uvc() -> Result, NokhwaError> { Err(why) => { return Err(NokhwaError::GeneralError(format!( "UVC Context Devicelist failure: {}", - why.to_string() + why ))) } }; diff --git a/src/threaded.rs b/src/threaded.rs index 127abee..37024f0 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -20,8 +20,8 @@ use crate::{ }; use image::{ImageBuffer, Rgb}; use parking_lot::FairMutex; -use std::any::Any; use std::{ + any::Any, collections::HashMap, ops::Deref, sync::{ @@ -47,6 +47,7 @@ use std::{ pub struct ThreadedCamera { camera: Arc>, frame_callback: Arc, Vec>)>>>, + last_frame_captured: Arc, Vec>>>, die_bool: Arc, } @@ -69,19 +70,29 @@ impl ThreadedCamera { 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 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(); std::thread::spawn(move || { - camera_frame_thread_loop(camera_clone, callback_clone, die_clone) + camera_frame_thread_loop(camera_clone, callback_clone, holding_cell_clone, die_clone) }); Ok(ThreadedCamera { camera, frame_callback, + last_frame_captured: holding_cell, die_bool, }) } @@ -144,6 +155,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.camera.lock().set_camera_format(new_fmt) } @@ -175,6 +187,7 @@ 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.camera.lock().set_resolution(new_res) } @@ -340,6 +353,14 @@ impl ThreadedCamera { *self.frame_callback.lock() = Some(callback); } + pub fn poll_frame(&mut self) -> Result, Vec>, NokhwaError> { + self.camera.lock().frame() + } + + 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. pub fn is_stream_open(&self) -> bool { self.camera.lock().is_stream_open() @@ -363,10 +384,12 @@ impl Drop for ThreadedCamera { fn camera_frame_thread_loop( camera: Arc>, callback: Arc, Vec>)>>>, + holding_cell: Arc, 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) } diff --git a/src/utils.rs b/src/utils.rs index b6ab8a1..f91d727 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -169,15 +169,15 @@ impl From for AVFourCC { /// 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. /// # JS-WASM -/// This is exported as `Resolution` -#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = Resolution))] +/// This is exported as `JSResolution` +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSResolution))] #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] pub struct Resolution { pub width_x: u32, pub height_y: u32, } -#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = Resolution))] +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSResolution))] impl Resolution { /// Create a new resolution from 2 image size coordinates. /// # JS-WASM @@ -389,7 +389,7 @@ impl Default for CameraFormat { CameraFormat { resolution: Resolution::new(640, 480), format: FrameFormat::MJPEG, - frame_rate: 15, + frame_rate: 30, } } } @@ -470,8 +470,8 @@ impl From for CaptureDeviceFormatDescriptor { /// `description` amd `misc` may contain information that may differ from backend to backend. Refer to each backend for details. /// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system. /// # JS-WASM -/// This is exported as a `CameraInfo`. -#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = CameraInfo))] +/// 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, @@ -480,7 +480,7 @@ pub struct CameraInfo { index: usize, } -#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = CameraInfo))] +#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))] impl CameraInfo { /// Create a new [`CameraInfo`]. /// # JS-WASM @@ -770,6 +770,8 @@ impl Display for KnownCameraControlFlag { /// 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**!. +/// E.g. if the [`CameraControl`] says `min` is 100, the minimum is actually 101. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] pub struct CameraControl { control: KnownCameraControls, @@ -868,6 +870,12 @@ impl CameraControl { 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(),