add finishing touches for 0.3.0 release

This commit is contained in:
l1npengtul
2021-07-02 18:11:02 +09:00
parent eec98eb34a
commit 0aecde7658
15 changed files with 317 additions and 392 deletions
View File
+2 -2
View File
@@ -32,7 +32,7 @@ loop {
println!("{}, {}", frame.width(), frame.height()); 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 ## API Support
The table below lists current Nokhwa API support. The table below lists current Nokhwa API support.
@@ -54,7 +54,7 @@ The table below lists current Nokhwa API support.
| MSMF | * | * | * | Windows | | MSMF | * | * | * | Windows |
| JS/WASM | * | * | * | Web | | 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. ^ = No CameraFormat setting support.
+1
View File
@@ -2,4 +2,5 @@
- run on windows - run on windows
- ensure updated documentation by running `cargo doc` and reading the README - ensure updated documentation by running `cargo doc` and reading the README
- update the version number in Cargo.toml - update the version number in Cargo.toml
- update changelog
- do this one more time - do this one more time
+23
View File
@@ -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 <BACKEND>`/`--query <BACKEND>`: Queries the system for available devices. If `<BACKEND>` is set, it will query using that backend.
- `-c <DEVICE>`/`--capture <DEVICE>`: Captures using device. If `<DEVICE>` 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.
+2 -2
View File
@@ -4,7 +4,7 @@ use glium::{
IndexBuffer, Surface, Texture2d, VertexBuffer, IndexBuffer, Surface, Texture2d, VertexBuffer,
}; };
use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder}; 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; use std::time::Instant;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@@ -60,7 +60,7 @@ fn main() {
.default_value("15") .default_value("15")
.takes_value(true)) .takes_value(true))
.arg(Arg::with_name("format") .arg(Arg::with_name("format")
.short("l") .short("4cc")
.long("format") .long("format")
.value_name("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.") .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.")
+3 -3
View File
@@ -392,17 +392,17 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
self.camera_format.framerate() 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; let mut new_fmt = self.camera_format;
new_fmt.set_framerate(new_fps); new_fmt.set_framerate(new_fps);
self.set_camera_format(new_fmt) self.set_camera_format(new_fmt)
} }
fn frameformat(&self) -> FrameFormat { fn frame_format(&self) -> FrameFormat {
self.camera_format.format() 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( Err(NokhwaError::UnsupportedOperation(
CaptureAPIBackend::GStreamer, CaptureAPIBackend::GStreamer,
)) ))
+46 -130
View File
@@ -1,20 +1,38 @@
use crate::{ use crate::{
tryinto_num, vector, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, CameraFormat, CameraIndexType, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
FrameFormat, NokhwaError, Resolution, NokhwaError, Resolution,
}; };
use image::{ImageBuffer, Rgb}; use image::{ImageBuffer, Rgb};
use opencv::core::Vector;
use opencv::{ use opencv::{
core::{Mat, MatTrait, MatTraitManual, Vec3b}, core::{Mat, MatTrait, MatTraitManual, Vec3b},
videoio::{ videoio::{
VideoCapture, VideoCaptureTrait, VideoWriter, CAP_ANY, CAP_AVFOUNDATION, CAP_DSHOW, VideoCapture, VideoCaptureTrait, CAP_ANY, CAP_AVFOUNDATION, CAP_MSMF, CAP_PROP_FPS,
CAP_PROP_FOURCC, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2,
}, },
}; };
use std::{ use std::collections::HashMap;
collections::HashMap,
fmt::{Display, Formatter}, /// 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. // 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) /// 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`] /// To see what this does, please see [`CaptureBackendTrait`]
/// # Quirks /// # 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 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. /// - 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. /// - 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). /// - 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). /// - `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 human name will be "`OpenCV` Capture Device {location}"
/// - [`CameraInfo`]'s description will contain the Camera's Index or IP. /// - [`CameraInfo`]'s description will contain the Camera's Index or IP.
/// - [`get_frame_raw()`](CaptureBackendTrait::get_frame_raw()) returns a BGR24 image instead of \<native format>. /// - [`frame_raw()`](CaptureBackendTrait::frame_raw()) returns a BGR24 image instead of \<native format>.
/// - 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) /// - 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 { pub struct OpenCvCaptureDevice {
camera_format: CameraFormat, camera_format: CameraFormat,
camera_location: CameraIndexType, camera_location: CameraIndexType,
@@ -223,13 +241,8 @@ impl OpenCvCaptureDevice {
return match frame.is_continuous() { return match frame.is_continuous() {
Ok(cont) => { Ok(cont) => {
if cont { if cont {
println!("{:?}", frame);
let mut raw_vec: Vec<u8> = Vec::new(); let mut raw_vec: Vec<u8> = 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::<Vec3b>(&frame) { let frame_data_vec = match Mat::data_typed::<Vec3b>(&frame) {
Ok(v) => v, 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. /// 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_sign_loss)]
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
pub fn get_framerate_raw(&self) -> Result<u32, NokhwaError> { pub fn raw_framerate(&self) -> Result<u32, NokhwaError> {
match self.video_capture.get(CAP_PROP_FPS) { match self.video_capture.get(CAP_PROP_FPS) {
Ok(fps) => Ok(fps as u32), Ok(fps) => Ok(fps as u32),
Err(why) => Err(NokhwaError::CouldntQueryDevice { Err(why) => Err(NokhwaError::CouldntQueryDevice {
@@ -366,7 +379,8 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
} }
fn resolution(&self) -> Resolution { 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> { fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
@@ -376,20 +390,20 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
} }
fn frame_rate(&self) -> u32 { 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; let mut current_fmt = self.camera_format;
current_fmt.set_framerate(new_fps); current_fmt.set_framerate(new_fps);
self.set_camera_format(current_fmt) self.set_camera_format(current_fmt)
} }
fn frameformat(&self) -> FrameFormat { fn frame_format(&self) -> FrameFormat {
self.camera_format.format() 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; let mut current_fmt = self.camera_format;
current_fmt.set_format(fourcc); current_fmt.set_format(fourcc);
self.set_camera_format(current_fmt) 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
/// <protocol>://<IP>:<port>/
/// ```
/// but please consult the manufacturer's specification for more details.
/// The index is a standard webcam index.
#[derive(Clone, Debug, PartialEq)]
pub enum CameraIndexType {
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 { fn get_api_pref_int() -> u32 {
match std::env::consts::OS { match std::env::consts::OS {
"linux" => CAP_V4L2 as u32, "linux" => CAP_V4L2 as u32,
"windows" => CAP_DSHOW as u32, "windows" => CAP_MSMF as u32,
"mac" => CAP_AVFOUNDATION as u32, "mac" => CAP_AVFOUNDATION as u32,
&_ => CAP_ANY as u32, &_ => CAP_ANY as u32,
} }
} }
#[allow(clippy::cast_possible_wrap)] #[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( fn set_properties(
vc: &mut VideoCapture, _vc: &mut VideoCapture,
camera_format: CameraFormat, _camera_format: CameraFormat,
camera_location: &CameraIndexType, _camera_location: &CameraIndexType,
) -> Result<(), NokhwaError> { ) -> 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<i32> = &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(()) Ok(())
} }
+3 -3
View File
@@ -297,17 +297,17 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
self.borrow_camera_format().framerate() 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(); let mut current_format = *self.borrow_camera_format();
current_format.set_framerate(new_fps); current_format.set_framerate(new_fps);
self.set_camera_format(current_format) self.set_camera_format(current_format)
} }
fn frameformat(&self) -> FrameFormat { fn frame_format(&self) -> FrameFormat {
self.borrow_camera_format().format() 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(); let mut current_format = *self.borrow_camera_format();
current_format.set_format(fourcc); current_format.set_format(fourcc);
self.set_camera_format(current_format) self.set_camera_format(current_format)
+4 -4
View File
@@ -31,7 +31,7 @@ impl From<CameraFormat> for Format {
/// The backend struct that interfaces with V4L2. /// The backend struct that interfaces with V4L2.
/// To see what this does, please see [`CaptureBackendTrait`]. /// To see what this does, please see [`CaptureBackendTrait`].
/// # Quirks /// # 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> { pub struct V4LCaptureDevice<'a> {
camera_format: CameraFormat, camera_format: CameraFormat,
camera_info: CameraInfo, camera_info: CameraInfo,
@@ -344,18 +344,18 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
} }
#[allow(clippy::option_if_let_else)] #[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; let mut new_fmt = self.camera_format;
new_fmt.set_framerate(new_fps); new_fmt.set_framerate(new_fps);
self.set_camera_format(new_fmt) self.set_camera_format(new_fmt)
} }
fn frameformat(&self) -> FrameFormat { fn frame_format(&self) -> FrameFormat {
self.camera_format.format() self.camera_format.format()
} }
#[allow(clippy::option_if_let_else)] #[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; let mut new_fmt = self.camera_format;
new_fmt.set_format(fourcc); new_fmt.set_format(fourcc);
self.set_camera_format(new_fmt) self.set_camera_format(new_fmt)
+114 -10
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
cap_impl_fn, cap_impl_matches, CameraFormat, CameraInfo, CaptureAPIBackend, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, NokhwaError,
CaptureBackendTrait, FrameFormat, NokhwaError, Resolution, Resolution,
}; };
use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage};
use std::{cell::RefCell, collections::HashMap}; use std::{cell::RefCell, collections::HashMap};
@@ -129,28 +129,28 @@ impl Camera {
self.backend.borrow_mut().set_resolution(new_res) self.backend.borrow_mut().set_resolution(new_res)
} }
/// Gets the current camera framerate (See: [`CameraFormat`]). /// Gets the current camera framerate (See: [`CameraFormat`]).
pub fn framerate(&self) -> u32 { pub fn frame_rate(&self) -> u32 {
self.backend.borrow().frame_rate() self.backend.borrow().frame_rate()
} }
/// Will set the current framerate /// Will set the current framerate
/// This will reset the current stream if used while stream is opened. /// This will reset the current stream if used while stream is opened.
/// # Errors /// # Errors
/// If you started the stream and the camera rejects the new framerate, this will return an error. /// 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> { pub fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
self.backend.borrow_mut().set_framerate(new_fps) self.backend.borrow_mut().set_frame_rate(new_fps)
} }
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]).
pub fn frameformat(&self) -> FrameFormat { pub fn frame_format(&self) -> FrameFormat {
self.backend.borrow().frameformat() self.backend.borrow().frame_format()
} }
/// Will set the current [`FrameFormat`] /// Will set the current [`FrameFormat`]
/// This will reset the current stream if used while stream is opened. /// This will reset the current stream if used while stream is opened.
/// # Errors /// # Errors
/// If you started the stream and the camera rejects the new frame format, this will return an error. /// 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> { pub fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
self.backend.borrow_mut().set_frameformat(fourcc) 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 /// # Errors
/// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. /// 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> { pub fn open_stream(&mut self) -> Result<(), NokhwaError> {
@@ -297,6 +297,110 @@ fn figure_out_auto() -> Option<CaptureAPIBackend> {
Some(cap) 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<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
use crate::backends::capture::$backend;
match <$backend>::$init_fn(idx, setting) {
Ok(cap) => Some(Ok(Box::new(cap))),
Err(why) => Some(Err(why)),
}
}
#[cfg(not(feature = $feature))]
fn [< init_ $backend_name>](_idx: usize, _setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, 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! { cap_impl_fn! {
(GStreamerCaptureDevice, new, "input-gst", gst), (GStreamerCaptureDevice, new, "input-gst", gst),
(OpenCvCaptureDevice, new_autopref, "input-opencv", opencv), (OpenCvCaptureDevice, new_autopref, "input-opencv", opencv),
+4 -4
View File
@@ -60,18 +60,18 @@ pub trait CaptureBackendTrait {
/// This will reset the current stream if used while stream is opened. /// This will reset the current stream if used while stream is opened.
/// # Errors /// # Errors
/// If you started the stream and the camera rejects the new framerate, this will return an error. /// 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`]). /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]).
fn frameformat(&self) -> FrameFormat; fn frame_format(&self) -> FrameFormat;
/// Will set the current [`FrameFormat`] /// Will set the current [`FrameFormat`]
/// This will reset the current stream if used while stream is opened. /// This will reset the current stream if used while stream is opened.
/// # Errors /// # Errors
/// If you started the stream and the camera rejects the new frame format, this will return an error. /// 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 /// # Errors
/// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error. /// 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>; fn open_stream(&mut self) -> Result<(), NokhwaError>;
+78 -80
View File
@@ -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)] #![deny(clippy::pedantic)]
#![warn(clippy::all)] #![warn(clippy::all)]
#![allow(clippy::must_use_candidate)] #![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. /// Raw access to each of Nokhwa's backends.
pub mod backends; pub mod backends;
mod camera; mod camera;
@@ -89,8 +89,6 @@ mod error;
mod network_camera; mod network_camera;
mod query; mod query;
mod utils; mod utils;
#[macro_use]
mod macros;
pub use camera::Camera; pub use camera::Camera;
pub use camera_traits::*; pub use camera_traits::*;
-144
View File
@@ -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<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
use crate::backends::capture::$backend;
match <$backend>::$init_fn(idx, setting) {
Ok(cap) => Some(Ok(Box::new(cap))),
Err(why) => Some(Err(why)),
}
}
#[cfg(not(feature = $feature))]
fn [< init_ $backend_name>](_idx: usize, _setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, 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
}
};
}
+11 -10
View File
@@ -1,5 +1,4 @@
use crate::{CameraInfo, CaptureAPIBackend, NokhwaError}; use crate::{CameraInfo, CaptureAPIBackend, NokhwaError};
use uvc::Device;
// TODO: Update as this goes // TODO: Update as this goes
/// Query the system for a list of available devices. Please refer to the API Backends that support `Query`) <br> /// Query the system for a list of available devices. Please refer to the API Backends that support `Query`) <br>
@@ -72,8 +71,9 @@ pub fn query_devices(api: CaptureAPIBackend) -> Result<Vec<CameraInfo>, NokhwaEr
// TODO: More // TODO: More
#[cfg(feature = "input-v4l")] #[cfg(feature = "input-v4l")]
#[allow(clippy::unnecessary_wraps)]
fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> { fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
return Ok({ Ok({
let camera_info: Vec<CameraInfo> = v4l::context::enum_devices() let camera_info: Vec<CameraInfo> = v4l::context::enum_devices()
.iter() .iter()
.map(|node| { .map(|node| {
@@ -87,7 +87,7 @@ fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
}) })
.collect(); .collect();
camera_info camera_info
}); })
} }
#[cfg(not(feature = "input-v4l"))] #[cfg(not(feature = "input-v4l"))]
@@ -99,6 +99,7 @@ fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
#[cfg(feature = "input-uvc")] #[cfg(feature = "input-uvc")]
fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> { fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
use uvc::Device;
let context = match uvc::Context::new() { let context = match uvc::Context::new() {
Ok(ctx) => ctx, Ok(ctx) => ctx,
Err(why) => { Err(why) => {
@@ -112,7 +113,7 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
let usb_devices = usb_enumeration::enumerate(None, None); let usb_devices = usb_enumeration::enumerate(None, None);
let uvc_devices = match context.devices() { let uvc_devices = match context.devices() {
Ok(devs) => { Ok(devs) => {
let device_vec: Vec<Device> = devs.map(|d| d).collect(); let device_vec: Vec<Device> = devs.collect();
device_vec device_vec
} }
Err(why) => { Err(why) => {
@@ -126,9 +127,9 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
let mut camera_info_vec = vec![]; let mut camera_info_vec = vec![];
let mut counter = 0_usize; let mut counter = 0_usize;
// Optimize this O(n*m) algorithem // Optimize this O(n*m) algorithm
for usb_dev in usb_devices.iter() { for usb_dev in &usb_devices {
for uvc_dev in uvc_devices.iter() { for uvc_dev in &uvc_devices {
if let Ok(desc) = uvc_dev.description() { if let Ok(desc) = uvc_dev.description() {
if desc.product_id == usb_dev.product_id && desc.vendor_id == usb_dev.vendor_id { if desc.product_id == usb_dev.product_id && desc.vendor_id == usb_dev.vendor_id {
let name = usb_dev let name = usb_dev
@@ -138,8 +139,8 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
"{}:{} {} {}", "{}:{} {} {}",
desc.vendor_id, desc.vendor_id,
desc.product_id, desc.product_id,
desc.manufacturer.unwrap_or("Generic".to_string()), desc.manufacturer.unwrap_or_else(|| "Generic".to_string()),
desc.product.unwrap_or("Camera".to_string()) desc.product.unwrap_or_else(|| "Camera".to_string())
)) ))
.clone(); .clone();
@@ -154,7 +155,7 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
"{}:{} {}", "{}:{} {}",
desc.vendor_id, desc.vendor_id,
desc.product_id, desc.product_id,
desc.serial_number.unwrap_or("".to_string()) desc.serial_number.unwrap_or_else(|| "".to_string())
), ),
counter, counter,
)); ));
+26
View File
@@ -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; let b = ((c298 + 516 * d + 128) >> 8).clamp(0, 255) as u8;
[r, g, b] [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
/// <protocol>://<IP>:<port>/
/// ```
/// but please consult the manufacturer's specification for more details.
/// The index is a standard webcam index.
#[derive(Clone, Debug, PartialEq)]
pub enum CameraIndexType {
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)
}
}
}
}