diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index a6ca448..f801a44 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ loop { println!("{}, {}", frame.width(), frame.height()); } ``` -They can be found in the `examples` folder. +A command line app made with `nokhwa` can be found in the `examples` folder. ## API Support The table below lists current Nokhwa API support. @@ -54,7 +54,7 @@ The table below lists current Nokhwa API support. | MSMF | * | * | * | Windows | | JS/WASM | * | * | * | Web | - :white_check_mark: : Working, :warning: : Experimental, :x: : Not Supported, *: Planned + :white_check_mark: : Working, :warning: : Experimental, :x: : Not Supported, *: Planned/WIP ^ = No CameraFormat setting support. diff --git a/UPDATE_CHECKLIST.md b/UPDATE_CHECKLIST.md index 58da081..99de6e6 100644 --- a/UPDATE_CHECKLIST.md +++ b/UPDATE_CHECKLIST.md @@ -2,4 +2,5 @@ - run on windows - ensure updated documentation by running `cargo doc` and reading the README - update the version number in Cargo.toml +- update changelog - do this one more time \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..99c51e5 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,23 @@ +# Examples + +## capture +Capture is a command line application designed to test features of backends to see if they are implemented correctly. +For the UVC backend, you may need to run the app as admin. + +### Capture - Usage +`<>` indicates an optional parameter. `[]` indicates a mandatory one. +- `-q `/`--query `: Queries the system for available devices. If `` is set, it will query using that backend. +- `-c `/`--capture `: Captures using device. If `` is set, it will using that device index or IP. +- `-s`/`--query-device`: Show device queries from `compatible_fourcc` and `compatible_list_by_resolution`. Requires -c to be passed to work. +- `-w [WIDTH:U32]`/`--width [WIDTH:U32]`: Set width of capture to `[WIDTH:U32]`. Does nothing if -c flag is not set. Value Has to be a `u32` +- `-h [HEIGHT:U32]`/`--height [HEIGHT:U32]`: Set height of capture to `[HEIGHT:U32]`. Does nothing if -c flag is not set. Value Has to be a `u32` +- `-rate [FPS:U32]`/`--framerate [FPS:U32]`: Set FPS of capture to `[FPS:U32]`. Does nothing if -c flag is not set. Value Has to be a `u32` +- `-4cc [FORMAT]`/`--format [FORMAT]`: Set format of capture to `[FORMAT]`. Does nothing if -c flag is not set. Possible values are MJPG and YUYV. Will be ignored if not one of those two. +- `-b [BACKEND]`/`--backend [BACKEND]`: Set the capture backend to `[BACKEND]`. Pass AUTO for automatic backend, UVC to use UVC, V4L to use Video4Linux, GST to use Gstreamer, OPENCV to use OpenCV. +- `-d`/`--display`: Enable glium display. Note: This is currently bugged as it shows an upside down feed. It also does not respond to `x` button press from window. (FIXME) + +Example Usage: +``` +./capture -q V4L -c 0 -s -w 1920 -h 1080 --framerate 30 --format MJPG -b V4L -d +``` +Query system using V4L backend, capture device with index 0 at 1920x1080 @ 30 FPS on MJPG format, using backend V4L, then display to glium window. diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index 6e8148d..71d2973 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -4,7 +4,7 @@ use glium::{ IndexBuffer, Surface, Texture2d, VertexBuffer, }; use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder}; -use nokhwa::{query_devices, Camera, CaptureAPIBackend, FrameFormat, NetworkCamera, Resolution}; +use nokhwa::{query_devices, Camera, CaptureAPIBackend, FrameFormat, NetworkCamera}; use std::time::Instant; #[derive(Copy, Clone)] @@ -60,7 +60,7 @@ fn main() { .default_value("15") .takes_value(true)) .arg(Arg::with_name("format") - .short("l") + .short("4cc") .long("format") .value_name("FORMAT") .help("Set format of capture. Does nothing if -c flag is not set. Possible values are MJPG and YUYV. Will be ignored if not either. Ignored by GStreamer backend.") diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index f3faef9..d2bb792 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -392,17 +392,17 @@ impl CaptureBackendTrait for GStreamerCaptureDevice { self.camera_format.framerate() } - fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { + fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { let mut new_fmt = self.camera_format; new_fmt.set_framerate(new_fps); self.set_camera_format(new_fmt) } - fn frameformat(&self) -> FrameFormat { + fn frame_format(&self) -> FrameFormat { self.camera_format.format() } - fn set_frameformat(&mut self, _fourcc: FrameFormat) -> Result<(), NokhwaError> { + fn set_frame_format(&mut self, _fourcc: FrameFormat) -> Result<(), NokhwaError> { Err(NokhwaError::UnsupportedOperation( CaptureAPIBackend::GStreamer, )) diff --git a/src/backends/capture/opencv_backend.rs b/src/backends/capture/opencv_backend.rs index 256e952..cff21a7 100644 --- a/src/backends/capture/opencv_backend.rs +++ b/src/backends/capture/opencv_backend.rs @@ -1,20 +1,38 @@ use crate::{ - tryinto_num, vector, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, - FrameFormat, NokhwaError, Resolution, + CameraFormat, CameraIndexType, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, + NokhwaError, Resolution, }; use image::{ImageBuffer, Rgb}; -use opencv::core::Vector; use opencv::{ core::{Mat, MatTrait, MatTraitManual, Vec3b}, videoio::{ - VideoCapture, VideoCaptureTrait, VideoWriter, CAP_ANY, CAP_AVFOUNDATION, CAP_DSHOW, - CAP_PROP_FOURCC, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2, + VideoCapture, VideoCaptureTrait, CAP_ANY, CAP_AVFOUNDATION, CAP_MSMF, CAP_PROP_FPS, + CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2, }, }; -use std::{ - collections::HashMap, - fmt::{Display, Formatter}, -}; +use std::collections::HashMap; + +/// Converts $from into $to +/// Example usage: +/// `tryinto_num(i32, a_unsigned_32_bit_num)` +/// Designed to deal with infallible. If not, it should be manually handled. +/// # Errors +/// If fails to convert(note: should not happen) then you messed up. +macro_rules! tryinto_num { + ($to:ty, $from:expr) => {{ + use std::convert::TryFrom; + match <$to>::try_from($from) { + Ok(v) => v, + Err(why) => { + return Err(crate::NokhwaError::GeneralError(format!( + "Failed to convert {}, {}", + $from, + why.to_string() + ))) + } + } + }}; +} // 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) @@ -22,17 +40,17 @@ use std::{ /// /// To see what this does, please see [`CaptureBackendTrait`] /// # Quirks -/// - **Some features don't work properly on this backend (yet)! Setting Resolution, FPS, FourCC does not work and will default to 640x480 30FPS. This is being worked on.** +/// - **Some features don't work properly on this backend (yet)! Setting [`Resolution`], FPS, [`FrameFormat`] does not work and will default to 640x480 30FPS. This is being worked on.** /// - This is a **cross-platform** backend. This means that it will work on most platforms given that `OpenCV` is present. /// - This backend can also do IP Camera input. /// - The backend's backend will default to system level APIs on Linux(V4L2), Mac(AVFoundation), and Windows(Media Foundation). Otherwise, it will decide for itself. /// - If the [`OpenCvCaptureDevice`] is initialized as a `IPCamera`, the [`CameraFormat`]'s `index` value will be [`u32::MAX`](std::u32::MAX) (4294967295). /// - `OpenCV` does not support camera querying. Camera Name and Camera supported resolution/fps/fourcc is a [`UnsupportedOperation`](NokhwaError::UnsupportedOperation). -/// Note: [`get_resolution()`](CaptureBackendTrait::get_resolution()), [`get_frameformat()`](CaptureBackendTrait::get_frameformat()), and [`get_framerate()`](CaptureBackendTrait::get_framerate()) is not affected. +/// Note: [`resolution()`](CaptureBackendTrait::resolution()), [`frame_format()`](CaptureBackendTrait::frame_format()), and [`frame_rate()`](CaptureBackendTrait::frame_rate()) is not affected. /// - [`CameraInfo`]'s human name will be "`OpenCV` Capture Device {location}" /// - [`CameraInfo`]'s description will contain the Camera's Index or IP. -/// - [`get_frame_raw()`](CaptureBackendTrait::get_frame_raw()) returns a BGR24 image instead of \. -/// - The API Preference order is the native OS API (linux => `v4l2`, mac => `AVFoundation`, windows => `directshow`) than [`CAP_AUTO`](https://docs.opencv.org/4.5.2/d4/d15/group__videoio__flags__base.html#gga023786be1ee68a9105bf2e48c700294da77ab1fe260fd182f8ec7655fab27a31d) +/// - [`frame_raw()`](CaptureBackendTrait::frame_raw()) returns a BGR24 image instead of \. +/// - The API Preference order is the native OS API (linux => `v4l2`, mac => `AVFoundation`, windows => `MSMF`) than [`CAP_AUTO`](https://docs.opencv.org/4.5.2/d4/d15/group__videoio__flags__base.html#gga023786be1ee68a9105bf2e48c700294da77ab1fe260fd182f8ec7655fab27a31d) pub struct OpenCvCaptureDevice { camera_format: CameraFormat, camera_location: CameraIndexType, @@ -223,13 +241,8 @@ impl OpenCvCaptureDevice { return match frame.is_continuous() { Ok(cont) => { if cont { + println!("{:?}", frame); let mut raw_vec: Vec = Vec::new(); - raw_vec.reserve( - (self.resolution().width() - * self.resolution().height() - * (frame.channels().unwrap_or(3)) as u32) - as usize, - ); let frame_data_vec = match Mat::data_typed::(&frame) { Ok(v) => v, @@ -307,7 +320,7 @@ impl OpenCvCaptureDevice { /// If the framerate is failed to be read (e.g. invalid or not supported), this will error. #[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_possible_truncation)] - pub fn get_framerate_raw(&self) -> Result { + pub fn raw_framerate(&self) -> Result { match self.video_capture.get(CAP_PROP_FPS) { Ok(fps) => Ok(fps as u32), Err(why) => Err(NokhwaError::CouldntQueryDevice { @@ -366,7 +379,8 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { } fn resolution(&self) -> Resolution { - self.camera_format.resolution() + self.raw_resolution() + .unwrap_or_else(|_| Resolution::new(640, 480)) } fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> { @@ -376,20 +390,20 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { } fn frame_rate(&self) -> u32 { - self.camera_format.framerate() + self.raw_framerate().unwrap_or(30) } - fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { + fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { let mut current_fmt = self.camera_format; current_fmt.set_framerate(new_fps); self.set_camera_format(current_fmt) } - fn frameformat(&self) -> FrameFormat { + fn frame_format(&self) -> FrameFormat { self.camera_format.format() } - fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { + fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { let mut current_fmt = self.camera_format; current_fmt.set_format(fourcc); self.set_camera_format(current_fmt) @@ -487,121 +501,23 @@ impl CaptureBackendTrait for OpenCvCaptureDevice { } } -/// 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), - IPCamera(String), -} - -impl Display for CameraIndexType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CameraIndexType::Index(idx) => { - write!(f, "{}", idx) - } - CameraIndexType::IPCamera(ip) => { - write!(f, "{}", ip) - } - } - } -} - fn get_api_pref_int() -> u32 { match std::env::consts::OS { "linux" => CAP_V4L2 as u32, - "windows" => CAP_DSHOW as u32, + "windows" => CAP_MSMF as u32, "mac" => CAP_AVFOUNDATION as u32, &_ => CAP_ANY as u32, } } #[allow(clippy::cast_possible_wrap)] +#[allow(clippy::unnecessary_wraps)] +// I'm done. This stupid POS refuses to actually do anything useful with camera settings +// If anyone else wants to tackle this monster, please do. fn set_properties( - vc: &mut VideoCapture, - camera_format: CameraFormat, - camera_location: &CameraIndexType, + _vc: &mut VideoCapture, + _camera_format: CameraFormat, + _camera_location: &CameraIndexType, ) -> Result<(), NokhwaError> { - let fourcc = match camera_format.format() { - FrameFormat::MJPEG => { - match VideoWriter::fourcc('m' as i8, 'j' as i8, 'p' as i8, 'g' as i8) { - Ok(fmt) => fmt, - Err(why) => { - return Err(NokhwaError::CouldntSetProperty { - property: "FrameFormat".to_string(), - value: "FourCC MJPG".to_string(), - error: why.to_string(), - }) - } - } - } - FrameFormat::YUYV => { - match VideoWriter::fourcc('y' as i8, 'u' as i8, 'y' as i8, 'v' as i8) { - Ok(fmt) => fmt, - Err(why) => { - return Err(NokhwaError::CouldntSetProperty { - property: "FrameFormat".to_string(), - value: "FourCC YUYV".to_string(), - error: why.to_string(), - }) - } - } - } - }; - - let properties: &Vector = &vector!( - CAP_PROP_FOURCC as i32, - fourcc, - CAP_PROP_FRAME_WIDTH as i32, - camera_format.width() as i32, - CAP_PROP_FRAME_HEIGHT as i32, - camera_format.height() as i32, - CAP_PROP_FPS as i32, - camera_format.framerate() as i32 - ); - - match camera_location { - CameraIndexType::Index(idx) => { - match vc.open_2(*idx as i32, get_api_pref_int() as i32, properties) { - Ok(v) => { - if !v { - return Err(NokhwaError::CouldntOpenDevice( - "Failed to re-open camera, OpenCV Bool return error".to_string(), - )); - } - } - Err(why) => { - return Err(NokhwaError::CouldntOpenDevice(format!( - "Failed to re-open camera with properties: {}", - why.to_string() - ))) - } - } - } - CameraIndexType::IPCamera(ip) => { - match vc.open(ip.as_str(), get_api_pref_int() as i32, properties) { - Ok(v) => { - if !v { - return Err(NokhwaError::CouldntOpenDevice( - "Failed to re-open camera, OpenCV Bool return error".to_string(), - )); - } - } - Err(why) => { - return Err(NokhwaError::CouldntOpenDevice(format!( - "Failed to re-open camera with properties: {}", - why.to_string() - ))) - } - } - } - } - Ok(()) } diff --git a/src/backends/capture/uvc_backend.rs b/src/backends/capture/uvc_backend.rs index b47944f..a91703d 100644 --- a/src/backends/capture/uvc_backend.rs +++ b/src/backends/capture/uvc_backend.rs @@ -297,17 +297,17 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> { self.borrow_camera_format().framerate() } - fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { + fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { let mut current_format = *self.borrow_camera_format(); current_format.set_framerate(new_fps); self.set_camera_format(current_format) } - fn frameformat(&self) -> FrameFormat { + fn frame_format(&self) -> FrameFormat { self.borrow_camera_format().format() } - fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { + fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { let mut current_format = *self.borrow_camera_format(); current_format.set_format(fourcc); self.set_camera_format(current_format) diff --git a/src/backends/capture/v4l2.rs b/src/backends/capture/v4l2.rs index 87488c5..d8e2cab 100644 --- a/src/backends/capture/v4l2.rs +++ b/src/backends/capture/v4l2.rs @@ -31,7 +31,7 @@ impl From for Format { /// The backend struct that interfaces with V4L2. /// To see what this does, please see [`CaptureBackendTrait`]. /// # Quirks -/// - Calling [`set_resolution()`](CaptureBackendTrait::set_resolution), [`set_framerate()`](CaptureBackendTrait::set_framerate), or [`set_frameformat()`](CaptureBackendTrait::set_frameformat) each internally calls [`set_camera_format()`](CaptureBackendTrait::set_camera_format). +/// - Calling [`set_resolution()`](CaptureBackendTrait::set_resolution), [`set_frame_rate()`](CaptureBackendTrait::set_frame_rate), or [`set_frame_format()`](CaptureBackendTrait::set_frame_format) each internally calls [`set_camera_format()`](CaptureBackendTrait::set_camera_format). pub struct V4LCaptureDevice<'a> { camera_format: CameraFormat, camera_info: CameraInfo, @@ -344,18 +344,18 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { } #[allow(clippy::option_if_let_else)] - fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { + fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { let mut new_fmt = self.camera_format; new_fmt.set_framerate(new_fps); self.set_camera_format(new_fmt) } - fn frameformat(&self) -> FrameFormat { + fn frame_format(&self) -> FrameFormat { self.camera_format.format() } #[allow(clippy::option_if_let_else)] - fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { + fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { let mut new_fmt = self.camera_format; new_fmt.set_format(fourcc); self.set_camera_format(new_fmt) diff --git a/src/camera.rs b/src/camera.rs index 1781107..cc4fc6e 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,6 +1,6 @@ use crate::{ - cap_impl_fn, cap_impl_matches, CameraFormat, CameraInfo, CaptureAPIBackend, - CaptureBackendTrait, FrameFormat, NokhwaError, Resolution, + CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, NokhwaError, + Resolution, }; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; use std::{cell::RefCell, collections::HashMap}; @@ -129,28 +129,28 @@ impl Camera { self.backend.borrow_mut().set_resolution(new_res) } /// Gets the current camera framerate (See: [`CameraFormat`]). - pub fn framerate(&self) -> u32 { + pub fn frame_rate(&self) -> u32 { self.backend.borrow().frame_rate() } /// Will set the current framerate /// This will reset the current stream if used while stream is opened. /// # Errors /// If you started the stream and the camera rejects the new framerate, this will return an error. - pub fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { - self.backend.borrow_mut().set_framerate(new_fps) + pub fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { + self.backend.borrow_mut().set_frame_rate(new_fps) } /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). - pub fn frameformat(&self) -> FrameFormat { - self.backend.borrow().frameformat() + pub fn frame_format(&self) -> FrameFormat { + self.backend.borrow().frame_format() } /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. /// # Errors /// If you started the stream and the camera rejects the new frame format, this will return an error. - pub fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { - self.backend.borrow_mut().set_frameformat(fourcc) + pub fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { + self.backend.borrow_mut().set_frame_format(fourcc) } - /// Will open the camera stream with set parameters. This will be called internally if you try and call [`get_frame()`](CaptureBackendTrait::get_frame()) before you call [`open_stream()`](CaptureBackendTrait::open_stream()). + /// 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. pub fn open_stream(&mut self) -> Result<(), NokhwaError> { @@ -297,6 +297,110 @@ fn figure_out_auto() -> Option { Some(cap) } +macro_rules! cap_impl_fn { + { + $( ($backend:ty, $init_fn:ident, $feature:expr, $backend_name:ident) ),+ + } => { + $( + paste::paste! { + #[cfg(feature = $feature)] + 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))), + Err(why) => Some(Err(why)), + } + } + #[cfg(not(feature = $feature))] + fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option, NokhwaError>> { + None + } + } + )+ + }; +} + +macro_rules! cap_impl_matches { + { + $use_backend: expr, $index:expr, $setting:expr, + $( ($feature:expr, $backend:ident, $fn:ident) ),+ + } => { + { + let i = $index; + let s = $setting; + match $use_backend { + CaptureAPIBackend::Auto => match figure_out_auto() { + Some(cap) => match cap { + $( + CaptureAPIBackend::$backend => { + match cfg!(feature = $feature) { + true => { + match $fn(i,s) { + Some(cap) => match cap { + Ok(c) => c, + Err(why) => return Err(why), + } + None => { + return Err(NokhwaError::NotImplemented( + "Platform requirements not satisfied.".to_string(), + )); + } + } + } + false => { + return Err(NokhwaError::NotImplemented( + "Platform requirements not satisfied.".to_string(), + )); + } + } + } + )+ + _ => { + return Err(NokhwaError::NotImplemented( + "Platform requirements not satisfied.".to_string(), + )); + } + } + None => { + return Err(NokhwaError::NotImplemented( + "Platform requirements not satisfied.".to_string(), + )); + } + } + $( + CaptureAPIBackend::$backend => { + match cfg!(feature = $feature) { + true => { + match $fn(i,s) { + Some(cap) => match cap { + Ok(c) => c, + Err(why) => return Err(why), + } + None => { + return Err(NokhwaError::NotImplemented( + "Platform requirements not satisfied.".to_string(), + )); + } + } + } + false => { + return Err(NokhwaError::NotImplemented( + "Platform requirements not satisfied.".to_string(), + )); + } + } + } + )+ + _ => { + return Err(NokhwaError::NotImplemented( + "Platform requirements not satisfied.".to_string(), + )); + } + } + }; + } +} + cap_impl_fn! { (GStreamerCaptureDevice, new, "input-gst", gst), (OpenCvCaptureDevice, new_autopref, "input-opencv", opencv), diff --git a/src/camera_traits.rs b/src/camera_traits.rs index adb1921..6d07dfa 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -60,18 +60,18 @@ pub trait CaptureBackendTrait { /// This will reset the current stream if used while stream is opened. /// # Errors /// If you started the stream and the camera rejects the new framerate, this will return an error. - fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; + fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>; /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). - fn frameformat(&self) -> FrameFormat; + fn frame_format(&self) -> FrameFormat; /// Will set the current [`FrameFormat`] /// This will reset the current stream if used while stream is opened. /// # Errors /// If you started the stream and the camera rejects the new frame format, this will return an error. - fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>; + fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>; - /// Will open the camera stream with set parameters. This will be called internally if you try and call [`get_frame()`](CaptureBackendTrait::get_frame()) before you call [`open_stream()`](CaptureBackendTrait::open_stream()). + /// 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. fn open_stream(&mut self) -> Result<(), NokhwaError>; diff --git a/src/lib.rs b/src/lib.rs index 3ab16ff..122df21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,85 +1,85 @@ +//! # nokhwa +//! Nokhwa(녹화): Korean word meaning "to record". +//! +//! A Simple-to-use, cross-platform Rust Webcam Capture Library +//! +//! ## Using nokhwa +//! You will need the latest stable Rust and Cargo. +//! +//! Nokhwa can be added to your crate by adding it to your `Cargo.toml`: +//! ```.ignore +//! [dependencies.nokhwa] +//! // TODO: replace the "*" with the latest version of `nokhwa` +//! version = "*" +//! // TODO: add some features +//! features = [""] +//! ``` +//! +//! Most likely, you will only use functionality provided by the `Camera` struct. If you need lower-level access, you may instead opt to use the raw capture backends found at `nokhwa::backends::capture::*`. +//! ## API Support +//! The table below lists current Nokhwa API support. +//! - The `Backend` column signifies the backend. +//! - The `Input` column signifies reading frames from the camera +//! - The `Query` column signifies system device list 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`) | YES | YES | YES | Linux | +//! | `libuvc`(`input-uvc`) | YES | YES | YES | Linux, Windows, Mac | +//! | `OpenCV`(`input-opencv`)^ | YES | NO | NO | Linux, Windows, Mac | +//! | `IPCamera`(`input-ipcam`/`OpenCV`)^ | YES | NO | NO | Linux, Windows, Mac | +//! | `GStreamer`(`input-gst`)^ | YES | YES | YES | Linux, Windows, Mac | +//! | `FFMpeg` | * | * | * | Linux, Windows, Mac | +//! | `AVFoundation` | * | * | * | Mac | +//! | MSMF | * | * | * | Windows | +//! | JS/WASM | * | * | * | Web | +//! +//! *: Planned/WIP +//! +//! +//! ^ = No CameraFormat setting support. +//! +//! ## 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`. +//! +//! `input-*` features: +//! - `input-v4l`: Enables the `Video4Linux` backend (linux) +//! - `input-uvc`: Enables the `libuvc` backend (cross-platform, libuvc statically-linked) +//! - `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). +//! +//! Conversely, anything that starts with `output-*` controls a feature that controls the output of something (usually a frame from the camera) +//! +//! `output-*` features: +//! - `output-wgpu`: Enables the API to copy a frame directly into a `wgpu` texture. +//! +//! You many want to pick and choose to reduce bloat. +//! ## Example +//! ```.ignore +//! // set up the Camera +//! let mut camera = Camera::new( +//! 0, // index +//! Some(CameraFormat::new_from(640, 480, FrameFormat::MJPEG, 30)), // format +//! CaptureAPIBackend::AUTO, // what backend to use (let nokhwa decide for itself) +//! ) +//! .unwrap(); +//! // open stream +//! camera.open_stream().unwrap(); +//! loop { +//! let frame = camera.get_frame().unwrap(); +//! println!("{}, {}", frame.width(), frame.height()); +//! } +//! ``` +//! They can be found in the `examples` folder. + #![deny(clippy::pedantic)] #![warn(clippy::all)] #![allow(clippy::must_use_candidate)] -#[allow(clippy::doc_markdown)] -/// # nokhwa -/// Nokhwa(녹화): Korean word meaning "to record". -/// -/// A Simple-to-use, cross-platform Rust Webcam Capture Library -/// -/// ## Using nokhwa -/// You will need the latest stable Rust and Cargo. -/// -/// Nokhwa can be added to your crate by adding it to your `Cargo.toml`: -/// ```.ignore -/// [dependencies.nokhwa] -/// // TODO: replace the "*" with the latest version of `nokhwa` -/// version = "*" -/// // TODO: add some features -/// features = [""] -/// ``` -/// -/// Most likely, you will only use functionality provided by the `Camera` struct. If you need lower-level access, you may instead opt to use the raw capture backends found at `nokhwa::backends::capture::*`. -/// ## API Support -/// The table below lists current Nokhwa API support. -/// - The `Backend` column signifies the backend. -/// - The `Input` column signifies reading frames from the camera -/// - The `Query` column signifies system device list support -/// - The `Query-Device` column signifies reading device capabilities -/// - The `OS` column signifies what OS this is availible on. -/// -/// | Backend | Input | Query | Query-Device | OS | -/// |---------------------------------|--------------------|--------------------|--------------------|---------------------| -/// | Video4Linux(`input-v4l`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux | -/// | libuvc(`input-uvc`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux, Windows, Mac | -/// | OpenCV(`input-opencv`)^ | :white_check_mark: | :x: | :x: | Linux, Windows, Mac | -/// | IPCamera(`input-ipcam`/OpenCV)^ | :white_check_mark: | :x: | :x: | Linux, Windows, Mac | -/// | GStreamer(`input-gst`)^ | :white_check_mark: | :x: | :white_check_mark: | Linux, Windows, Mac | -/// | FFMpeg | * | * | * | Linux, Windows, Mac | -/// | AVFoundation | * | * | * | Mac | -/// | MSMF | * | * | * | Windows | -/// | JS/WASM | * | * | * | Web | -/// -/// :white_check_mark: : Working, :warning: : Experimental, :x: : Not Supported, *: Planned -/// -/// ^ = No CameraFormat setting support. -/// -/// ## 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`. -/// -/// `input-*` features: -/// - `input-v4l`: Enables the `Video4Linux` backend (linux) -/// - `input-uvc`: Enables the `libuvc` backend (cross-platform, libuvc statically-linked) -/// - `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). -/// -/// Conversely, anything that starts with `output-*` controls a feature that controls the output of something (usually a frame from the camera) -/// -/// `output-*` features: -/// - `output-wgpu`: Enables the API to copy a frame directly into a `wgpu` texture. -/// -/// You many want to pick and choose to reduce bloat. -/// ## Example -/// ```rust -/// // set up the Camera -/// let mut camera = Camera::new( -/// 0, // index -/// Some(CameraFormat::new_from(640, 480, FrameFormat::MJPEG, 30)), // format -/// CaptureAPIBackend::AUTO, // what backend to use (let nokhwa decide for itself) -/// ) -/// .unwrap(); -/// // open stream -/// camera.open_stream().unwrap(); -/// loop { -/// let frame = camera.get_frame().unwrap(); -/// println!("{}, {}", frame.width(), frame.height()); -/// } -/// ``` -/// They can be found in the `examples` folder. - /// Raw access to each of Nokhwa's backends. pub mod backends; mod camera; @@ -89,8 +89,6 @@ mod error; mod network_camera; mod query; mod utils; -#[macro_use] -mod macros; pub use camera::Camera; pub use camera_traits::*; diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index 6e43266..0000000 --- a/src/macros.rs +++ /dev/null @@ -1,144 +0,0 @@ -/// Converts $from into $to -/// Example usage: -/// `tryinto_num(i32, a_unsigned_32_bit_num)` -/// Designed to deal with infallible. If not, it should be manually handled. -/// # Errors -/// If fails to convert(note: should not happen) then you messed up. -#[macro_export] -macro_rules! tryinto_num { - ($to:ty, $from:expr) => {{ - use std::convert::TryFrom; - match <$to>::try_from($from) { - Ok(v) => v, - Err(why) => { - return Err(crate::NokhwaError::GeneralError(format!( - "Failed to convert {}, {}", - $from, - why.to_string() - ))) - } - } - }}; -} - -/// Makes `init-*` functions for you. To be used in `camera.rs` -/// Example usage: check `camera.rs` -#[macro_export] -macro_rules! cap_impl_fn { - { - $( ($backend:ty, $init_fn:ident, $feature:expr, $backend_name:ident) ),+ - } => { - $( - paste::paste! { - #[cfg(feature = $feature)] - 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))), - Err(why) => Some(Err(why)), - } - } - #[cfg(not(feature = $feature))] - fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option, NokhwaError>> { - None - } - } - )+ - }; -} - -#[macro_export] -macro_rules! cap_impl_matches { - { - $use_backend: expr, $index:expr, $setting:expr, - $( ($feature:expr, $backend:ident, $fn:ident) ),+ - } => { - { - let i = $index; - let s = $setting; - match $use_backend { - CaptureAPIBackend::Auto => match figure_out_auto() { - Some(cap) => match cap { - $( - CaptureAPIBackend::$backend => { - match cfg!(feature = $feature) { - true => { - match $fn(i,s) { - Some(cap) => match cap { - Ok(c) => c, - Err(why) => return Err(why), - } - None => { - return Err(NokhwaError::NotImplemented( - "Platform requirements not satisfied.".to_string(), - )); - } - } - } - false => { - return Err(NokhwaError::NotImplemented( - "Platform requirements not satisfied.".to_string(), - )); - } - } - } - )+ - _ => { - return Err(NokhwaError::NotImplemented( - "Platform requirements not satisfied.".to_string(), - )); - } - } - None => { - return Err(NokhwaError::NotImplemented( - "Platform requirements not satisfied.".to_string(), - )); - } - } - $( - CaptureAPIBackend::$backend => { - match cfg!(feature = $feature) { - true => { - match $fn(i,s) { - Some(cap) => match cap { - Ok(c) => c, - Err(why) => return Err(why), - } - None => { - return Err(NokhwaError::NotImplemented( - "Platform requirements not satisfied.".to_string(), - )); - } - } - } - false => { - return Err(NokhwaError::NotImplemented( - "Platform requirements not satisfied.".to_string(), - )); - } - } - } - )+ - _ => { - return Err(NokhwaError::NotImplemented( - "Platform requirements not satisfied.".to_string(), - )); - } - } - }; - } -} - -#[cfg(feature = "input-opencv")] -#[macro_export] -macro_rules! vector { - ( $( $elem:expr ),* ) => { - { - let mut vector = opencv::core::Vector::new(); - $( - vector.push($elem); - )* - vector - } - }; -} diff --git a/src/query.rs b/src/query.rs index fda870a..4583319 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,5 +1,4 @@ use crate::{CameraInfo, CaptureAPIBackend, NokhwaError}; -use uvc::Device; // TODO: Update as this goes /// Query the system for a list of available devices. Please refer to the API Backends that support `Query`)
@@ -72,8 +71,9 @@ pub fn query_devices(api: CaptureAPIBackend) -> Result, NokhwaEr // TODO: More #[cfg(feature = "input-v4l")] +#[allow(clippy::unnecessary_wraps)] fn query_v4l() -> Result, NokhwaError> { - return Ok({ + Ok({ let camera_info: Vec = v4l::context::enum_devices() .iter() .map(|node| { @@ -87,7 +87,7 @@ fn query_v4l() -> Result, NokhwaError> { }) .collect(); camera_info - }); + }) } #[cfg(not(feature = "input-v4l"))] @@ -99,6 +99,7 @@ fn query_v4l() -> Result, NokhwaError> { #[cfg(feature = "input-uvc")] fn query_uvc() -> Result, NokhwaError> { + use uvc::Device; let context = match uvc::Context::new() { Ok(ctx) => ctx, Err(why) => { @@ -112,7 +113,7 @@ fn query_uvc() -> Result, NokhwaError> { let usb_devices = usb_enumeration::enumerate(None, None); let uvc_devices = match context.devices() { Ok(devs) => { - let device_vec: Vec = devs.map(|d| d).collect(); + let device_vec: Vec = devs.collect(); device_vec } Err(why) => { @@ -126,9 +127,9 @@ fn query_uvc() -> Result, NokhwaError> { let mut camera_info_vec = vec![]; let mut counter = 0_usize; - // Optimize this O(n*m) algorithem - for usb_dev in usb_devices.iter() { - for uvc_dev in uvc_devices.iter() { + // Optimize this O(n*m) algorithm + for usb_dev in &usb_devices { + for uvc_dev in &uvc_devices { if let Ok(desc) = uvc_dev.description() { if desc.product_id == usb_dev.product_id && desc.vendor_id == usb_dev.vendor_id { let name = usb_dev @@ -138,8 +139,8 @@ fn query_uvc() -> Result, NokhwaError> { "{}:{} {} {}", desc.vendor_id, desc.product_id, - desc.manufacturer.unwrap_or("Generic".to_string()), - desc.product.unwrap_or("Camera".to_string()) + desc.manufacturer.unwrap_or_else(|| "Generic".to_string()), + desc.product.unwrap_or_else(|| "Camera".to_string()) )) .clone(); @@ -154,7 +155,7 @@ fn query_uvc() -> Result, NokhwaError> { "{}:{} {}", desc.vendor_id, desc.product_id, - desc.serial_number.unwrap_or("".to_string()) + desc.serial_number.unwrap_or_else(|| "".to_string()) ), counter, )); diff --git a/src/utils.rs b/src/utils.rs index b1f16eb..875a323 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -450,3 +450,29 @@ 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] } + +/// 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), + IPCamera(String), +} + +impl Display for CameraIndexType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CameraIndexType::Index(idx) => { + write!(f, "{}", idx) + } + CameraIndexType::IPCamera(ip) => { + write!(f, "{}", ip) + } + } + } +}