start impl v4l2 capture backend

This commit is contained in:
l1npengtul
2021-05-21 15:22:31 +09:00
parent df412f4fcd
commit 7aba8d308f
8 changed files with 359 additions and 34 deletions
+6
View File
@@ -16,6 +16,8 @@ input_opencv = ["opencv", "opencv/clang-runtime"]
input_msmf = ["windows"]
input_v4l = ["v4l"]
input_gstreamer = ["gstreamer"]
input_ffmpeg = []
docs-only = ["input_uvc", "input_opencv", "input_msmf", "input_v4l", "input_gstreamer", "input_ffmpeg"]
[dependencies]
thiserror = "1.0.24"
@@ -44,3 +46,7 @@ optional = true
[dev-dependencies.windows]
version = "0.10.0"
[package.metadata.docs.rs]
no-default-features = true
features = ["docs-only"]
+204 -11
View File
@@ -1,30 +1,62 @@
use crate::{error::NokhwaError, utils::{CameraFormat, CameraInfo}};
use crate::{
error::NokhwaError,
utils::{CameraFormat, CameraInfo},
CaptureBackendTrait, FrameFormat, Resolution,
};
use v4l::prelude::*;
use v4l::{
buffer::Type,
io::traits::CaptureStream,
video::{capture::Parameters, Capture},
Format, FourCC,
};
pub struct V4LCaptureDevice {
#[cfg(feature = "input_v4l")]
impl From<CameraFormat> for Format {
fn from(cam_fmt: CameraFormat) -> Self {
let pxfmt = match cam_fmt.format() {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
};
Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt)
}
}
/// 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).
pub struct V4LCaptureDevice<'a> {
camera_format: Option<CameraFormat>,
camera_info: CameraInfo,
device: Device,
stream_handle: Option<MmapStream<'a>>,
}
impl V4LCaptureDevice {
/// Creates a new capture device using the V4L2 backend
impl<'a> V4LCaptureDevice<'a> {
/// Creates a new capture device using the V4L2 backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
/// # Errors
/// This function will error if the camera is currently busy or if V4L2 can't read device information.
/// This function will error if the camera is currently busy or if V4L2 can't read device information.
pub fn new(index: usize) -> Result<Self, NokhwaError> {
let device = match Device::new(index) {
Ok(dev) => dev,
Err(why) => {
return Err(NokhwaError::CouldntOpenDevice(format!("V4L2 Error: {}", why.to_string())))
return Err(NokhwaError::CouldntOpenDevice(format!(
"V4L2 Error: {}",
why.to_string()
)))
}
};
let camera_info = match device.query_caps() {
Ok(caps) => {
CameraInfo::new(caps.card, "".to_string(), caps.driver, index)
}
Ok(caps) => CameraInfo::new(caps.card, "".to_string(), caps.driver, index),
Err(why) => {
return Err(NokhwaError::CouldntQueryDevice{ property: "Capabilities".to_string(), error: why.to_string() })
return Err(NokhwaError::CouldntQueryDevice {
property: "Capabilities".to_string(),
error: why.to_string(),
})
}
};
@@ -32,7 +64,168 @@ impl V4LCaptureDevice {
camera_format: None,
camera_info,
device,
stream_handle: None,
})
}
}
impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
fn get_info(&self) -> CameraInfo {
self.camera_info.clone()
}
fn get_camera_format(&self) -> Option<CameraFormat> {
self.camera_format
}
#[allow(clippy::option_if_let_else)]
fn init_camera_format_default(&mut self, overwrite: bool) -> Result<(), NokhwaError> {
match self.camera_format {
Some(_) => {
if overwrite {
return self.set_camera_format(CameraFormat::default());
}
Ok(())
}
None => self.set_camera_format(CameraFormat::default()),
}
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
let prev_format = match self.device.format() {
Ok(fmt) => fmt,
Err(why) => {
return Err(NokhwaError::CouldntQueryDevice {
property: "Resolution, FrameFormat".to_string(),
error: why.to_string(),
})
}
};
let prev_fps = match self.device.params() {
Ok(fps) => fps,
Err(why) => {
return Err(NokhwaError::CouldntQueryDevice {
property: "Framerate".to_string(),
error: why.to_string(),
})
}
};
let format: Format = new_fmt.into();
let framerate = Parameters::with_fps(new_fmt.framerate());
if let Err(why) = self.device.set_format(&format) {
return Err(NokhwaError::CouldntSetProperty {
property: "Resolution, FrameFormat".to_string(),
value: format.to_string(),
error: why.to_string(),
});
}
if let Err(why) = self.device.set_params(&framerate) {
return Err(NokhwaError::CouldntSetProperty {
property: "Framerate".to_string(),
value: framerate.to_string(),
error: why.to_string(),
});
}
if self.stream_handle.is_some() {
self.stream_handle = Some({
match MmapStream::new(&self.device, Type::VideoCapture) {
Ok(stream) => stream,
Err(why) => {
// undo
if let Err(why) = self.device.set_format(&prev_format) {
return Err(NokhwaError::CouldntSetProperty {
property: "Attempt undo due to stream acquisition failure. Resolution, FrameFormat".to_string(),
value: prev_format.to_string(),
error: why.to_string(),
});
}
if let Err(why) = self.device.set_params(&prev_fps) {
return Err(NokhwaError::CouldntSetProperty {
property:
"Attempt undo due to stream acquisition failure. Framerate"
.to_string(),
value: prev_fps.to_string(),
error: why.to_string(),
});
}
return Err(NokhwaError::CouldntOpenStream(why.to_string()));
}
}
})
}
self.camera_format = Some(new_fmt);
Ok(())
}
fn get_resolution(&self) -> Option<Resolution> {
self.camera_format.map(|fmt| fmt.resoltuion())
}
#[allow(clippy::option_if_let_else)]
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
if let Some(fmt) = self.camera_format {
let mut new_fmt = fmt;
new_fmt.set_resolution(new_res);
self.set_camera_format(new_fmt)
} else {
self.camera_format = Some(CameraFormat::new(new_res, FrameFormat::MJPEG, 0));
Ok(())
}
}
fn get_framerate(&self) -> Option<u32> {
self.camera_format.map(|fmt| fmt.framerate())
}
#[allow(clippy::option_if_let_else)]
fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
if let Some(fmt) = self.camera_format {
let mut new_fmt = fmt;
new_fmt.set_framerate(new_fps);
self.set_camera_format(new_fmt)
} else {
self.camera_format = Some(CameraFormat::new(
Resolution::new(0, 0),
FrameFormat::MJPEG,
new_fps,
));
Ok(())
}
}
fn get_frameformat(&self) -> Option<FrameFormat> {
self.camera_format.map(|fmt| fmt.format())
}
#[allow(clippy::option_if_let_else)]
fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
if let Some(fmt) = self.camera_format {
let mut new_fmt = fmt;
new_fmt.set_format(fourcc);
self.set_camera_format(new_fmt)
} else {
self.camera_format = Some(CameraFormat::new(Resolution::new(0, 0), fourcc, 0));
Ok(())
}
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
todo!()
}
fn is_stream_open(&self) -> bool {
self.stream_handle.is_some()
}
fn get_frame(&self) -> Result<image::ImageBuffer<image::Rgb<u8>, Vec<u8>>, NokhwaError> {
todo!()
}
fn get_frame_raw(&self) -> Vec<u8> {
todo!()
}
}
+1 -3
View File
@@ -1,3 +1 @@
pub struct Capture {
}
pub struct Capture {}
+67 -4
View File
@@ -1,12 +1,75 @@
use std::collections::HashMap;
use image::{ImageBuffer, Rgb};
use crate::{error::NokhwaError, utils::{CameraFormat, CameraInfo}};
use crate::{
error::NokhwaError,
utils::{CameraFormat, CameraInfo, FrameFormat, Resolution},
};
/// This trait is for any backend that allows you to grab and take frames from a camera.
pub trait CaptureBackendTrait {
fn info(&self) -> CameraInfo;
fn set_camera_format(&self, new_fmt: CameraFormat) -> Result<(), NokhwaError>;
fn open_stream(&self) -> Result<(), NokhwaError>;
/// Gets the camera information such as Name and Index as a [`CameraInfo`].
fn get_info(&self) -> CameraInfo;
/// Assigns a sensible default to the backend's `CameraFormat`. Usually 640x480 @ 15 FPS + MJPEG.
/// If there is already a value assigned to the `CameraFormat`, it will only be overwritten if `overwrite` is set to `true`.
/// If false, this function will do nothing (NO-OP).
/// This will reset the current stream if used while stream is opened.
/// # Errors
/// If you started the stream and the camera rejects the new camera format, this will return an error.
fn init_camera_format_default(&mut self, overwrite: bool) -> Result<(), NokhwaError>;
/// Gets the current [`CameraFormat`]. Will return none if no format has been set yet.
fn get_camera_format(&self) -> Option<CameraFormat>;
/// Will set the current [`CameraFormat`]
/// This will reset the current stream if used while stream is opened.
/// # Errors
/// If you started the stream and the camera rejects the new camera format, this will return an error.
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError>;
/// Gets the current camera resolution (See: [`Resolution`]). Will return none if no resolution has been set yet.
fn get_resolution(&self) -> Option<Resolution>;
/// Will set the current [`Resolution`]
/// This will reset the current stream if used while stream is opened.
/// # Errors
/// If you started the stream and the camera rejects the new resolution, this will return an error.
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>;
/// Gets the current camera framerate. Will return none if no framerate has been set yet.
fn get_framerate(&self) -> Option<u32>;
/// 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.
fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError>;
/// Gets the current camera's frame format (See: [`FrameFormat`]). Will return none if no frame format has been set yet.
fn get_frameformat(&self) -> Option<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 foramt, this will return an error.
fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>;
/// Will open the camera stream with set parameters. This will be called internally if you try and call [`CaptureBackendTrait::get_frame()`] before you call [`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>;
/// Checks if stream if open. If it is, it will return true.
fn is_stream_open(&self) -> bool;
/// Will get a frame from the camera as a Raw RGB image buffer. This will call [`CaptureBackendTrait::open_stream()`] first if you haven't already.
/// # Errors
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore) or the decoding fails (e.g. MJPEG -> u8), this will error.
fn get_frame(&self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError>;
/// Will get a frame from the camera **without** any processing applied, meaning you will usually get a frame you need to decode yourself.
/// # Errors
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), this will error.
fn get_frame_raw(&self) -> Vec<u8>;
}
/// This is for any backend that allows you to query a camera for its compatible resolutions/fourcc/framerates.
pub trait QueryBackendTrait: CaptureBackendTrait {
/// A hashmap of Framerates mapped to [`Resolution`]s.
fn get_compatible_by_framerate(&self, fourcc: FrameFormat) -> HashMap<u32, Vec<Resolution>>;
/// A hashmap of [`Resolution`]s mapped to framerates
fn get_compatible_by_resolution(&self, fourcc: FrameFormat) -> HashMap<Resolution, Vec<u32>>;
/// Gets the supported camera formats.
fn get_compatible_fourcc(&self, resolution: Resolution, framerate: u32) -> Option<FrameFormat>;
}
pub trait VirtualBackendTrait {}
+10 -4
View File
@@ -1,13 +1,19 @@
use thiserror::Error;
#[allow(clippy::clippy::module_name_repetitions)]
#[allow(clippy::module_name_repetitions)]
#[allow(clippy::pub_enum_variant_names)]
#[derive(Error, Debug, Clone)]
pub enum NokhwaError {
#[error("Could not open device: {0}")]
CouldntOpenDevice(String),
#[error("Could not query device property {property}: {error}")]
CouldntQueryDevice {
CouldntQueryDevice { property: String, error: String },
#[error("Could not set device property {property} with value {value}: {error}")]
CouldntSetProperty {
property: String,
value: String,
error: String,
}
}
},
#[error("Could not open device stream: {0}")]
CouldntOpenStream(String),
}
+8 -3
View File
@@ -1,10 +1,15 @@
#![deny(clippy::pedantic)]
#![warn(clippy::all)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::must_use_candidate)]
mod camera_traits;
pub mod backends;
mod utils;
mod camera;
mod camera_traits;
mod error;
mod query;
mod query;
mod utils;
pub use camera_traits::*;
pub use error::NokhwaError;
pub use utils::*;
+1
View File
@@ -0,0 +1 @@
+62 -9
View File
@@ -1,14 +1,19 @@
use std::fmt::Display;
use std::cmp::Ordering;
use std::{cmp::Ordering, fmt::Display};
/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`
/// YUYV is a mathmatical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr)
/// MJPEG is a motion-jpeg compressed frame, it allows for high frame rates.
#[derive(Copy, Clone, Debug, PartialEq, Hash)]
pub enum FrameFormat {
MJPEG,
YUYV,
}
/// Describes a Resolution.
/// Describes a Resolution.
/// This struct consists of a Width and a Height value (x,y).
/// Note that the [`Ord`] implementation of this struct is flipped from highest to lowest.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Resolution {
pub width_x: u32,
@@ -58,8 +63,6 @@ impl PartialOrd for Resolution {
}
impl Ord for Resolution {
// Flip around the order to make it seem the way the user would expect.
// The user would expect a descending list of resolutions (aka highest -> lowest)
fn cmp(&self, other: &Self) -> Ordering {
match self.x().cmp(&other.x()) {
Ordering::Less => Ordering::Less,
@@ -69,6 +72,8 @@ impl Ord for Resolution {
}
}
/// This is a conveinence struct that holds all information about the format of a webcam stream.
/// It consists of a Resolution, `FrameFormat`, and a framerate.
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
pub struct CameraFormat {
resolution: Resolution,
@@ -77,6 +82,7 @@ pub struct CameraFormat {
}
impl CameraFormat {
/// Construct a new [`CameraFormat`]
pub fn new(resolution: Resolution, format: FrameFormat, framerate: u32) -> Self {
CameraFormat {
resolution,
@@ -85,6 +91,7 @@ impl CameraFormat {
}
}
/// [`CameraFormat::new()`], but raw.
pub fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: u32) -> Self {
CameraFormat {
resolution: Resolution {
@@ -96,27 +103,60 @@ impl CameraFormat {
}
}
pub fn res(&self) -> Resolution {
/// Get the resolution of the current [`CameraFormat`]
pub fn resoltuion(&self) -> Resolution {
self.resolution
}
/// Get the width of the resolution of the current [`CameraFormat`]
pub fn width(&self) -> u32 {
self.resolution.width()
}
/// Get the height of the resolution of the current [`CameraFormat`]
pub fn height(&self) -> u32 {
self.resolution.height()
}
pub fn frame_format(&self) -> FrameFormat {
self.format
/// Set the [`CameraFormat`]'s resolution.
pub fn set_resolution(&mut self, resolution: Resolution) {
self.resolution = resolution;
}
/// Get the framerate of the current [`CameraFormat`]
pub fn framerate(&self) -> u32 {
self.framerate
}
/// Set the [`CameraFormat`]'s framerate.
pub fn set_framerate(&mut self, framerate: u32) {
self.framerate = framerate;
}
/// Get the [`CameraFormat`]'s format.
pub fn format(&self) -> FrameFormat {
self.format
}
/// Set the [`CameraFormat`]'s format.
pub fn set_format(&mut self, format: FrameFormat) {
self.format = format;
}
}
impl Default for CameraFormat {
fn default() -> Self {
CameraFormat {
resolution: Resolution::new(640, 480),
format: FrameFormat::MJPEG,
framerate: 15,
}
}
}
/// Information about a Camera e.g. its name.
/// `description` amd `misc` may contain backend-specific information.
/// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system.
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
pub struct CameraInfo {
human_name: String,
@@ -126,13 +166,13 @@ pub struct CameraInfo {
}
impl CameraInfo {
/// Create a new [`CameraInfo`].
pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self {
CameraInfo {
human_name,
description,
misc,
index,
}
}
@@ -189,6 +229,19 @@ impl Ord for CameraInfo {
}
}
/// The list of known capture backends to the library.
///
/// AUTO is special - it tells the Camera struct to automatically choose a backend most suited for the current platform.
///
/// V4L2 - `Video4Linux2`, a linux specific backend.
///
/// UVC - Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use.
///
/// MSMF - Microsoft Media Foundation, Winsows only (replacement for `DirectShow`)
///
/// `OpenCV` - Uses `OpenCV` to capture. Platform agnostic.
///
/// FFMPEG - Uses FFMPEG (libavdevice) to capture. Platform agnostic.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CaptureAPIBackend {
AUTO,