Merge pull request #40 from l1npengtul/0.10

move 0.10 to senpai
This commit is contained in:
l1npengtul
2022-05-22 19:02:45 +09:00
committed by GitHub
24 changed files with 733 additions and 616 deletions
+26 -25
View File
@@ -10,25 +10,30 @@ repository = "https://github.com/l1npengtul/nokhwa"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows"]
exclude = ["examples/threaded-capture", "examples/capture"]
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["flume", "decoding"]
serialize = ["serde"]
decoding = ["mozjpeg"]
input-v4l = ["v4l", "v4l2-sys-mit"]
input-msmf = ["nokhwa-bindings-windows"]
input-avfoundation = ["nokhwa-bindings-macos"]
# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x
# input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration", "lazy_static"]
input-opencv = ["opencv", "opencv/clang-runtime"]
input-opencv = ["opencv", "opencv/rgb", "rgb"]
input-ipcam = ["input-opencv"]
input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex", "parking_lot"]
input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "wasm-rs-async-executor"]
output-wgpu = ["wgpu"]
output-wasm = ["input-jscam"]
output-threaded = ["parking_lot"]
small-wasm = ["wee_alloc"]
small-wasm = []
docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"]
docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"]
docs-features = []
@@ -37,35 +42,33 @@ test-fail-warning = []
[dependencies]
thiserror = "1.0"
paste = "1.0"
anymap = "1.0.0-beta.2"
enum_dispatch = "0.3"
[dependencies.serde]
version = "1.0"
optional = true
[dependencies.flume]
version = "0.10"
optional = true
[dependencies.image]
version = "0.23"
version = "0.24"
default-features = false
[target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg]
[dependencies.mozjpeg]
version = "0.9"
optional = true
[target.'cfg(target_os = "linux")'.dependencies.v4l]
version = "0.12"
[dependencies.v4l]
version = "0.13"
optional = true
[target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit]
[dependencies.v4l2-sys-mit]
version = "0.2"
optional = true
[dependencies.ouroboros]
version = "0.14"
optional = true
# [dependencies.uvc]
# version = "0.2"
# optional = true
[dependencies.usb_enumeration]
version = "0.1.2"
optional = true
@@ -75,17 +78,20 @@ version = "^0.12"
optional = true
[dependencies.opencv]
version = "0.62"
features = ["clang-runtime"]
version = "0.63"
optional = true
[dependencies.rgb]
version = "0.8"
optional = true
[dependencies.nokhwa-bindings-windows]
version = "0.3"
version = "0.3.4"
path = "nokhwa-bindings-windows"
optional = true
[dependencies.nokhwa-bindings-macos]
version = "0.1"
version = "0.1.1"
path = "nokhwa-bindings-macos"
optional = true
@@ -111,7 +117,6 @@ optional = true
[dependencies.web-sys]
version = "0.3"
# why
features = [
"console",
"CanvasRenderingContext2d",
@@ -146,12 +151,8 @@ optional = true
version = "0.9"
optional = true
[dependencies.wee_alloc]
version = "0.4"
optional = true
[dependencies.parking_lot]
version = "0.11"
version = "0.12"
optional = true
[dependencies.lazy_static]
Vendored
-135
View File
@@ -1,135 +0,0 @@
pipeline {
agent {
node {
label 'ci_linux'
}
}
stages {
stage('Sanity Check') {
steps {
echo '$BUILD_TAG'
scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*')
}
}
stage('Cargo RustFMT') {
agent {
node {
label 'ci_linux'
}
}
steps {
scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*')
sh 'rustup update stable'
sh 'cargo fmt --all -- --check'
}
}
stage('Build, Clippy') {
parallel {
stage('V4L2') {
agent {
node {
label 'ci_linux'
}
}
steps {
scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true)
sh 'rustup update stable'
sh 'cargo clippy --features "input-v4l, output-wgpu, test-fail-warning"'
}
}
stage('Media Foundation') {
agent {
node {
label 'ci_windows'
}
}
steps {
scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true)
pwsh(script: 'rustup update stable', returnStatus: true)
pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true)
}
}
stage('AVFoundation') {
steps {
sh 'echo TODO'
}
}
stage('libUVC') {
agent {
node {
label 'ci_linux'
}
}
steps {
scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true)
sh 'rustup update stable'
sh 'cargo clippy --features "input-uvc, output-wgpu, test-fail-warning"'
}
}
stage('OpenCV IPCamera') {
agent {
node {
label 'ci_linux'
}
}
steps {
scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true)
sh 'rustup update stable'
sh 'cargo clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"'
}
}
stage('GStreamer') {
agent {
node {
label 'ci_linux'
}
}
steps {
scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true)
sh 'rustup update nightly'
sh 'cargo clippy --features "input-gst, output-wgpu, test-fail-warning"'
}
}
stage('JSCamera/WASM') {
steps {
scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true)
sh 'rustup update stable'
sh 'wasm-pack build --release -- --features "input-jscam, output-wasm, test-fail-warning" --no-default-features'
sh 'cargo clippy --features "input-jscam, output-wasm, test-fail-warning" --no-default-features'
}
}
}
}
stage('RustDOC') {
agent {
node {
label 'ci_linux'
}
}
steps {
scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true)
sh 'rustup update nightly'
sh 'cargo +nightly doc --features "docs-only, docs-nolink, docs-features, test-fail-warning" --no-deps --release'
}
}
}
}
+3
View File
@@ -108,3 +108,6 @@ Contributions are welcome!
## Minimum Service Rust Version
`nokhwa` may build on older versions of `rustc`, but there is no guarantee except for the latest stable rust.
## 0.10
0.10 is currently stalled due to upstream not having the necessary features (wasm-bindgen).
+1 -2
View File
@@ -10,14 +10,13 @@ edition = "2018"
default = ["nokhwa/default"]
input-msmf = ["nokhwa/input-msmf"]
input-v4l = ["nokhwa/input-v4l"]
input-uvc = ["nokhwa/input-uvc"]
input-opencv = ["nokhwa/input-opencv"]
input-gst = ["nokhwa/input-gst"]
input-avfoundation = ["nokhwa/input-avfoundation"]
[dependencies]
clap = "2.33.3"
glium = "0.30.0"
glium = "0.31.0"
glutin = "0.27.0"
flume = "0.10.9"
+4 -13
View File
@@ -22,9 +22,7 @@ use glium::{
IndexBuffer, Surface, Texture2d, VertexBuffer,
};
use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder};
use nokhwa::{
nokhwa_initialize, query_devices, Camera, CameraIndex, CaptureAPIBackend, FrameFormat,
};
use nokhwa::{nokhwa_initialize, query_devices, Camera, CaptureAPIBackend, FrameFormat};
use std::time::Instant;
#[derive(Copy, Clone)]
@@ -193,15 +191,8 @@ fn main() {
.trim()
.parse::<usize>()
{
let mut camera = Camera::new_with(
CameraIndex::Index(index as u32),
width,
height,
fps,
format,
backend_value,
)
.unwrap();
let mut camera =
Camera::new_with(index, width, height, fps, format, backend_value).unwrap();
if matches_clone.is_present("query-device") {
match camera.compatible_fourcc() {
@@ -284,7 +275,7 @@ fn main() {
fps,
frame.len()
);
send.send(frame).unwrap()
let _send = send.send(frame);
}
}
}
+2 -2
View File
@@ -15,13 +15,13 @@
*/
use image::{ImageBuffer, Rgb};
use nokhwa::{query_devices, CallbackCamera, CameraIndex, CaptureAPIBackend};
use nokhwa::{query_devices, CaptureAPIBackend, ThreadedCamera};
fn main() {
let cameras = query_devices(CaptureAPIBackend::Auto).unwrap();
cameras.iter().for_each(|cam| println!("{:?}", cam));
let mut threaded = CallbackCamera::new(&CameraIndex::Index(0), None).unwrap();
let mut threaded = ThreadedCamera::new(0, None).unwrap();
threaded.open_stream(callback).unwrap();
#[allow(clippy::empty_loop)] // keep it running
loop {
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nokhwa-bindings-macos"
version = "0.1.2"
edition = "2021"
edition = "2018"
authors = ["l1npengtul"]
license = "Apache-2.0"
repository = "https://github.com/l1npengtul/nokhwa"
+9 -7
View File
@@ -242,9 +242,9 @@ pub mod avfoundation {
use block::ConcreteBlock;
use cocoa_foundation::foundation::{NSArray, NSInteger, NSString, NSUInteger};
use core_media_sys::{
kCMPixelFormat_422YpCbCr8_yuvs, kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG,
kCMVideoCodecType_JPEG_OpenDML, CMFormatDescriptionGetMediaSubType, CMSampleBufferRef,
CMVideoDimensions,
kCMPixelFormat_422YpCbCr8_yuvs, kCMPixelFormat_8IndexedGray_WhiteIsZero,
kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, kCMVideoCodecType_JPEG_OpenDML,
CMFormatDescriptionGetMediaSubType, CMSampleBufferRef, CMVideoDimensions,
};
use dashmap::DashMap;
use flume::{Receiver, Sender};
@@ -681,6 +681,7 @@ pub mod avfoundation {
pub enum AVFourCC {
YUV2,
MJPEG,
GRAY8,
}
// Localized Name
@@ -757,7 +758,7 @@ pub mod avfoundation {
Err(why) => {
return Err(AVFError::ReadFrame(format!(
"Failed to read frame from pipe: {}",
why.to_string()
why
)))
}
},
@@ -861,8 +862,7 @@ pub mod avfoundation {
msg_send![value, videoSupportedFrameRateRanges]
})
.into_iter()
.map(|v| [v.min(), v.max()])
.flatten()
.flat_map(|v| [v.min(), v.max()])
.collect::<Vec<f64>>();
fps_list.sort_by(|n, m| n.partial_cmp(m).unwrap_or(Ordering::Equal));
fps_list.dedup();
@@ -875,6 +875,7 @@ pub mod avfoundation {
let fourcc = match fcc_raw {
kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => AVFourCC::YUV2,
kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => AVFourCC::MJPEG,
kCMPixelFormat_8IndexedGray_WhiteIsZero => AVFourCC::GRAY8,
_ => {
return Err(AVFError::InvalidValue {
found: fcc_raw.to_string(),
@@ -1110,7 +1111,7 @@ pub mod avfoundation {
Ok(avf) => avf.into_raw(),
Err(_) => {
// should not happen
return Err(AVFError::StreamOpen("String contains null? This is a bug, please report it https://github.com/l1npengtul/nokhwa".to_string()));
return Err(AVFError::StreamOpen("String contains null? This is a bug, please report it: https://github.com/l1npengtul/nokhwa".to_string()));
}
};
let queue = dispatch_queue_create(avf_queue_str, NSObject(std::ptr::null_mut()));
@@ -1302,6 +1303,7 @@ pub mod avfoundation {
pub enum AVFourCC {
YUV2,
MJPEG,
GRAY8,
}
// Localized Name
+1 -1
View File
@@ -1834,7 +1834,7 @@ pub mod wmf {
}
impl<'a> MediaFoundationDevice<'a> {
pub fn new(_: usize) -> Result<Self, BindingError> {
pub fn new(_: usize, _: MFCameraFormat) -> Result<Self, BindingError> {
Ok(MediaFoundationDevice { phantom: &Empty() })
}
+5 -11
View File
@@ -15,9 +15,8 @@
*/
use crate::{
mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo,
CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError,
Resolution,
mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use nokhwa_bindings_macos::avfoundation::{
@@ -49,18 +48,13 @@ impl AVFoundationCaptureDevice {
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// # Errors
/// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new(
index: &CameraIndex,
camera_format: Option<CameraFormat>,
) -> Result<Self, NokhwaError> {
/// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user.
pub fn new(index: usize, camera_format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let camera_format = match camera_format {
Some(fmt) => fmt,
None => CameraFormat::default(),
};
let index = index.index_num()? as usize;
let device_descriptor: CameraInfo = match query_avfoundation()?.into_iter().nth(index) {
Some(descriptor) => descriptor.into(),
None => {
@@ -91,7 +85,7 @@ impl AVFoundationCaptureDevice {
/// # Errors
/// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user.
pub fn new_with(
index: &CameraIndex,
index: usize,
width: u32,
height: u32,
fps: u32,
+25 -13
View File
@@ -15,9 +15,8 @@
*/
use crate::{
mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraIndex, CameraInfo,
CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError,
Resolution,
mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
};
use glib::Quark;
use gstreamer::{
@@ -42,6 +41,10 @@ type PipelineGenRet = (Element, AppSink, Arc<Mutex<ImageBuffer<Rgb<u8>, Vec<u8>>
/// - `Drop`-ing this may cause a `panic`.
/// - Setting controls is not supported.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-gst")))]
#[deprecated(
since = "0.10",
note = "Use one of the native backends instead(V4L, AVF, MSMF) or OpenCV"
)]
pub struct GStreamerCaptureDevice {
pipeline: Element,
app_sink: AppSink,
@@ -58,8 +61,8 @@ impl GStreamerCaptureDevice {
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// # Errors
/// This function will error if the camera is currently busy or if `GStreamer` can't read device information. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new(index: &CameraIndex, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
/// This function will error if the camera is currently busy or if `GStreamer` can't read device information.
pub fn new(index: usize, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let camera_format = match cam_fmt {
Some(fmt) => fmt,
None => CameraFormat::default(),
@@ -117,7 +120,7 @@ impl GStreamerCaptureDevice {
&DeviceExt::display_name(&device),
&DeviceExt::device_class(&device),
&"",
CameraIndex::Index(index),
index,
),
caps,
)
@@ -140,18 +143,13 @@ impl GStreamerCaptureDevice {
/// `GStreamer` uses `v4l2src` on linux, `ksvideosrc` on windows, and `autovideosrc` on mac.
/// # Errors
/// This function will error if the camera is currently busy or if `GStreamer` can't read device information.
pub fn new_with(
index: &CameraIndex,
width: u32,
height: u32,
fps: u32,
) -> Result<Self, NokhwaError> {
pub fn new_with(index: usize, width: u32, height: u32, fps: u32) -> Result<Self, NokhwaError> {
let cam_fmt = CameraFormat::new(Resolution::new(width, height), FrameFormat::MJPEG, fps);
GStreamerCaptureDevice::new(index, Some(cam_fmt))
}
}
impl CaptureBackendTrait for GStreamerCaptureDevice {
impl GStreamerCaptureDevice {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::GStreamer
}
@@ -370,6 +368,11 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
.insert(Resolution::new(width as u32, height as u32), fps_vec);
}
}
unsupported => {
return Err(NokhwaError::NotImplementedError(format!(
"Not supported frame format {unsupported:?}"
)))
}
}
}
}
@@ -577,6 +580,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String {
FrameFormat::YUYV => {
format!("autovideosrc location=/dev/video{} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate())
}
_ => {
format!("unsupproted! if you see this, switch to something else!")
}
}
}
@@ -589,6 +595,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String {
FrameFormat::YUYV => {
format!("v4l2src device=/dev/video{} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate())
}
_ => {
format!("unsupproted! if you see this, switch to something else!")
}
}
}
@@ -601,6 +610,9 @@ fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String {
FrameFormat::YUYV => {
format!("ksvideosrc device_index={} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.frame_rate())
}
_ => {
format!("unsupproted! if you see this, switch to something else!")
}
}
}
+9 -23
View File
@@ -16,8 +16,8 @@
use crate::{
all_known_camera_controls, mjpeg_to_rgb, yuyv422_to_rgb, CameraControl, CameraFormat,
CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControlFlag, KnownCameraControls, NokhwaError, Resolution,
CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag,
KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use nokhwa_bindings_windows::{wmf::MediaFoundationDevice, MFControl, MediaFoundationControls};
@@ -34,7 +34,6 @@ use std::{any::Any, borrow::Cow, collections::HashMap};
/// - The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`].
/// - The names may contain invalid characters since they were converted from UTF16.
/// - When you call new or drop the struct, `initialize`/`de_initialize` will automatically be called.
// TODO: Allow CameraIndex to contain a device string.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-msmf")))]
pub struct MediaFoundationCaptureDevice<'a> {
inner: MediaFoundationDevice<'a>,
@@ -46,29 +45,16 @@ impl<'a> MediaFoundationCaptureDevice<'a> {
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// # Errors
/// This function will error if Media Foundation fails to get the device. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new(
index: &CameraIndex<'a>,
camera_fmt: Option<CameraFormat>,
) -> Result<Self, NokhwaError> {
let mut mf_device = match &index {
CameraIndex::Index(idx) => MediaFoundationDevice::new(*idx as usize),
CameraIndex::String(lnk) => MediaFoundationDevice::with_string(
&lnk.as_bytes()
.into_iter()
.map(|x| *x as u16)
.collect::<Vec<u16>>(),
),
}?;
if let Some(fmt) = camera_fmt {
mf_device.set_format(fmt.into())?;
}
/// This function will error if Media Foundation fails to get the device.
pub fn new(index: usize, camera_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let format = camera_fmt.unwrap_or_default();
let mf_device = MediaFoundationDevice::new(index, format.into())?;
let info = CameraInfo::new(
mf_device.name(),
"MediaFoundation Camera Device".to_string(),
mf_device.symlink(),
index,
mf_device.index(),
);
Ok(MediaFoundationCaptureDevice {
@@ -79,9 +65,9 @@ impl<'a> MediaFoundationCaptureDevice<'a> {
/// Create a new Media Foundation Device with desired settings.
/// # Errors
/// This function will error if Media Foundation fails to get the device. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
/// This function will error if Media Foundation fails to get the device.
pub fn new_with(
index: &CameraIndex<'a>,
index: usize,
width: u32,
height: u32,
fps: u32,
+53 -66
View File
@@ -14,9 +14,10 @@
* limitations under the License.
*/
use crate::pixel_format::PixelFormat;
use crate::{
CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait,
FrameFormat, KnownCameraControls, NokhwaError, Resolution,
CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use opencv::{
@@ -26,7 +27,7 @@ use opencv::{
CAP_MSMF, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2,
},
};
use std::{any::Any, borrow::Cow, collections::HashMap, ops::Deref};
use std::{any::Any, borrow::Cow, collections::HashMap};
/// Converts $from into $to
/// Example usage:
@@ -50,7 +51,6 @@ macro_rules! tryinto_num {
}};
}
// TODO: Define behaviour for IPCameras.
/// The backend struct that interfaces with `OpenCV`. Note that an `opencv` matching the version that this was either compiled on must be present on the user's machine. (usually 4.5.2 or greater)
/// For more information, please see [`opencv-rust`](https://github.com/twistedfall/opencv-rust) and [`OpenCV VideoCapture Docs`](https://docs.opencv.org/4.5.2/d8/dfe/classcv_1_1VideoCapture.html).
///
@@ -71,7 +71,7 @@ macro_rules! tryinto_num {
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-opencv")))]
pub struct OpenCvCaptureDevice {
camera_format: CameraFormat,
camera_location: CameraIndex,
camera_location: usize,
camera_info: CameraInfo,
api_preference: i32,
video_capture: VideoCapture,
@@ -79,7 +79,7 @@ pub struct OpenCvCaptureDevice {
#[allow(clippy::must_use_candidate)]
impl OpenCvCaptureDevice {
/// Creates a new capture device using the `OpenCV` backend. You can either use an [`Index`](CameraIndexType::Index) or [`IPCamera`](CameraIndexType::IPCamera).
/// Creates a new capture device using the `OpenCV` backend.
///
/// Indexes are gives to devices by the OS, and usually numbered by order of discovery.
///
@@ -89,14 +89,13 @@ impl OpenCvCaptureDevice {
/// ```
/// , but please refer to the manufacturer for the actual IP format.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// # Errors
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
/// [`CameraIndex::Index`] will open a local camera, and [`CameraIndex::String`] **requires** an IP.
/// # Panics
/// If the API u32 -> i32 fails this will error
pub fn new(
camera_location: &CameraIndex,
index: usize,
cfmt: Option<CameraFormat>,
api_pref: Option<u32>,
) -> Result<Self, NokhwaError> {
@@ -111,42 +110,28 @@ impl OpenCvCaptureDevice {
None => CameraFormat::default(),
};
let mut video_capture = match camera_location.clone() {
CameraIndex::Index(idx) => {
let vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) {
Ok(vc) => vc,
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
idx.to_string(),
why.to_string(),
))
}
};
vid_cap
let mut video_capture = match VideoCapture::new(tryinto_num!(i32, idx), api) {
Ok(vc) => vc,
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
idx.to_string(),
why.to_string(),
))
}
CameraIndex::String(ip) => match VideoCapture::from_file(ip.as_ref(), CAP_ANY) {
Ok(vc) => vc,
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
ip.to_string(),
why.to_string(),
))
}
},
};
set_properties(&mut video_capture, camera_format, camera_location)?;
set_properties(&mut video_capture, camera_format, index)?;
let camera_info = CameraInfo::new(
&format!("OpenCV Capture Device {}", camera_location),
&camera_location.to_string(),
"",
camera_location.clone(),
format!("OpenCV Capture Device {}", index),
index.to_string(),
"".to_string(),
index as usize,
);
Ok(OpenCvCaptureDevice {
camera_format,
camera_location: camera_location.clone(),
camera_location: index,
camera_info,
api_preference: api,
video_capture,
@@ -161,18 +146,18 @@ impl OpenCvCaptureDevice {
/// ```
/// , but please refer to the manufacturer for the actual IP format.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// # Errors
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
pub fn new_ip_camera(ip: impl Deref<Target = str>) -> Result<Self, NokhwaError> {
let camera_location = CameraIndex::String(ip.to_string());
OpenCvCaptureDevice::new(&camera_location, None, None)
pub fn new_ip_camera(ip: String) -> Result<Self, NokhwaError> {
let camera_location = CameraIndexType::IPCamera(ip);
OpenCvCaptureDevice::new(camera_location, None, None)
}
/// Creates a new capture device using the `OpenCV` backend.
/// Indexes are gives to devices by the OS, and usually numbered by order of discovery.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// # Errors
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
pub fn new_index_camera(
@@ -180,41 +165,39 @@ impl OpenCvCaptureDevice {
cfmt: Option<CameraFormat>,
api_pref: Option<u32>,
) -> Result<Self, NokhwaError> {
let camera_location = CameraIndex::Index(tryinto_num!(u32, index));
OpenCvCaptureDevice::new(&camera_location, cfmt, api_pref)
let camera_location = CameraIndexType::Index(tryinto_num!(u32, index));
OpenCvCaptureDevice::new(camera_location, cfmt, api_pref)
}
/// Creates a new capture device using the `OpenCV` backend.
/// Indexes are gives to devices by the OS, and usually numbered by order of discovery.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@30 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default if it is a index camera.
/// # Errors
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
pub fn new_autopref(
index: &CameraIndex,
cfmt: Option<CameraFormat>,
) -> Result<Self, NokhwaError> {
OpenCvCaptureDevice::new(index, cfmt, None)
pub fn new_autopref(index: usize, cfmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let camera_location = CameraIndexType::Index(tryinto_num!(u32, index));
OpenCvCaptureDevice::new(camera_location, cfmt, None)
}
/// Gets weather said capture device is an `IPCamera`.
pub fn is_ip_camera(&self) -> bool {
match self.camera_location {
CameraIndex::Index(_) => false,
CameraIndex::String(_) => true,
CameraIndexType::Index(_) => false,
CameraIndexType::IPCamera(_) => true,
}
}
/// Gets weather said capture device is an OS-based indexed camera.
pub fn is_index_camera(&self) -> bool {
match self.camera_location {
CameraIndex::Index(_) => true,
CameraIndex::String(_) => false,
CameraIndexType::Index(_) => true,
CameraIndexType::IPCamera(_) => false,
}
}
/// Gets the camera location
pub fn camera_location(&self) -> CameraIndex {
pub fn camera_location(&self) -> CameraIndexType {
self.camera_location.clone()
}
@@ -342,6 +325,10 @@ impl OpenCvCaptureDevice {
}
impl CaptureBackendTrait for OpenCvCaptureDevice {
fn init(&mut self) -> Result<CameraFormat, NokhwaError> {
todo!()
}
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::OpenCv
}
@@ -368,7 +355,7 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
self.camera_format = new_fmt;
if let Err(why) = set_properties(&mut self.video_capture, new_fmt, &self.camera_location) {
if let Err(why) = set_properties(&mut self.video_capture, new_fmt, self.camera_location) {
self.camera_format = current_format;
return Err(why);
}
@@ -523,7 +510,7 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
#[allow(clippy::cast_possible_wrap)]
fn open_stream(&mut self) -> Result<(), NokhwaError> {
match self.camera_location.clone() {
CameraIndex::Index(idx) => {
CameraIndexType::Index(idx) => {
match self
.video_capture
.open_1(idx as i32, get_api_pref_int() as i32)
@@ -537,15 +524,15 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
}
}
}
CameraIndex::String(ip) => {
CameraIndexType::IPCamera(ip) => {
match self
.video_capture
.open_file(ip.as_ref(), get_api_pref_int() as i32)
.open_file(&*ip, get_api_pref_int() as i32)
{
Ok(_) => {}
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
ip.to_string(),
ip,
format!("Failed to open device: {}", why),
))
}
@@ -601,6 +588,12 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
Ok(image_buf)
}
fn frame_typed<F: PixelFormat>(
&mut self,
) -> Result<ImageBuffer<crate::pixel_format::Output, Vec<u8>>, NokhwaError> {
todo!()
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
let cow = self.raw_frame_vec()?;
Ok(cow)
@@ -614,12 +607,6 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
}
}
impl Drop for OpenCvCaptureDevice {
fn drop(&mut self) {
let _close_err = self.stop_stream();
}
}
fn get_api_pref_int() -> u32 {
match std::env::consts::OS {
"linux" => CAP_V4L2 as u32,
@@ -636,7 +623,7 @@ fn get_api_pref_int() -> u32 {
fn set_properties(
_vc: &mut VideoCapture,
_camera_format: CameraFormat,
_camera_location: &CameraIndex,
_camera_location: usize,
) -> Result<(), NokhwaError> {
Ok(())
}
+4
View File
@@ -54,6 +54,10 @@ use uvc::{
/// - If internal variables `stream_handle_init` and `active_stream_init` become de-synchronized with the true reality (weather streamhandle/activestream is init or not) this will cause undefined behaviour.
#[self_referencing]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-uvc")))]
#[deprecated(
since = "0.10",
note = "Use one of the native backends instead(V4L, AVF, MSMF) or OpenCV"
)]
pub struct UVCCaptureDevice<'a> {
camera_format: CameraFormat,
camera_info: CameraInfo<'a>,
+68 -24
View File
@@ -17,7 +17,7 @@
use crate::{
error::NokhwaError,
mjpeg_to_rgb,
utils::{CameraFormat, CameraIndex, CameraInfo},
utils::{CameraFormat, CameraInfo},
yuyv422_to_rgb, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControlFlag, KnownCameraControls, Resolution,
};
@@ -28,13 +28,17 @@ use v4l::{
frameinterval::FrameIntervalEnum,
framesize::FrameSizeEnum,
io::traits::CaptureStream,
prelude::*,
video::{capture::Parameters, Capture},
Format, FourCC,
Device,
};
use crate::pixel_format::PixelFormat;
use std::any::Any;
pub use v4l::control::{Control, Description, Flags};
use v4l::prelude::MmapStream;
use v4l::video::Output;
use crate::buffer::Buffer;
/// Generates a camera control from a device and a description of control
/// # Error
@@ -170,6 +174,7 @@ fn clone_control(ctrl: &Control) -> Control {
/// - The `Any` type for `control` for [`set_raw_camera_control()`](CaptureBackendTrait::set_raw_camera_control) is [`u32`] and [`Control`]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-v4l")))]
pub struct V4LCaptureDevice<'a> {
initialized: bool,
camera_format: CameraFormat,
camera_info: CameraInfo,
device: Device,
@@ -179,11 +184,12 @@ pub struct V4LCaptureDevice<'a> {
impl<'a> V4LCaptureDevice<'a> {
/// Creates a new capture device using the `V4L2` backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
///
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// If `camera_format` is `None`, it will be spawned with a random [`CameraFormat`] as determined by [`init()`](crate::CaptureBackendTrait::init).
///
/// If `camera_format` is not `None`, the camera will try to use it when you call [`init()`](crate::CaptureBackendTrait::init).
/// # Errors
/// This function will error if the camera is currently busy or if `V4L2` can't read device information. This will also error if the index is a [`CameraIndex::String`] that cannot be parsed into a `usize`.
pub fn new(index: &CameraIndex, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let index = index.as_index()?;
/// This function will error if the camera is currently busy or if `V4L2` can't read device information.
pub fn new(index: usize, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let device = match Device::new(index as usize) {
Ok(dev) => dev,
Err(why) => {
@@ -195,7 +201,7 @@ impl<'a> V4LCaptureDevice<'a> {
};
let camera_info = match device.query_caps() {
Ok(caps) => CameraInfo::new(&caps.card, "", &caps.driver, CameraIndex::Index(index)),
Ok(caps) => CameraInfo::new(&caps.card, "", &caps.driver, usize),
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Capabilities".to_string(),
@@ -212,6 +218,7 @@ impl<'a> V4LCaptureDevice<'a> {
let fourcc = match camera_format.format() {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
FrameFormat::GRAY8 => FourCC::new(b"GRAY"),
};
let new_param = Parameters::with_fps(camera_format.frame_rate());
@@ -259,6 +266,7 @@ impl<'a> V4LCaptureDevice<'a> {
}
Ok(V4LCaptureDevice {
initialized: false,
camera_format,
camera_info,
device,
@@ -266,11 +274,11 @@ impl<'a> V4LCaptureDevice<'a> {
})
}
/// Create a new `V4L2` Camera with desired settings.
/// Create a new `V4L2` Camera with desired settings. This may or may not work.
/// # Errors
/// This function will error if the camera is currently busy or if `V4L2` can't read device information.
pub fn new_with(
index: &CameraIndex,
index: usize,
width: u32,
height: u32,
fps: u32,
@@ -284,9 +292,11 @@ impl<'a> V4LCaptureDevice<'a> {
let format = match fourcc {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
FrameFormat::GRAY8 => FourCC::new(b"GRAY"),
};
match v4l::video::Capture::enum_framesizes(&self.device, format) {
// match Capture::enum_framesizes(&self.device, format) {
match self.device.enum_framesizes(format) {
Ok(frame_sizes) => {
let mut resolutions = vec![];
for frame_size in frame_sizes {
@@ -311,6 +321,7 @@ impl<'a> V4LCaptureDevice<'a> {
}
/// Get the inner device (immutable) for e.g. Controls
/// apps bloodtests contact css images index index.html injectionsupplies transfem transmasc
#[allow(clippy::must_use_candidate)]
pub fn inner_device(&self) -> &Device {
&self.device
@@ -320,9 +331,47 @@ impl<'a> V4LCaptureDevice<'a> {
pub fn inner_device_mut(&mut self) -> &mut Device {
&mut self.device
}
/// Force refreshes the inner [`CameraFormat`] state.
pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
match (Capture::format(&self.device), self.device.format()) {
(Ok(params), Ok(format)) => {
// FIXME: actually handle the fractions??????
self.camera_format = CameraFormat::new(
Resolution::new(format.width, format.height),
FrameFormat::from(format.fourcc),
params.interval.numerator,
);
Ok(())
}
(_, _) => {
Err(NokhwaError::GetPropertyError {
property: "parameters".to_string(),
error: why.to_string(),
})
}
}
}
}
impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
fn init(&mut self) -> Result<CameraFormat, NokhwaError> {
let camera_format = self.camera_format;
let compatible = self.compatible_camera_formats()?;
if compatible.len() == 0 {
return Err(NokhwaError::InitializeError { backend: self.backend(), error: "Could not find any compatible camera formats!".to_string() })
}
if compatible.contains(&camera_format) {
self.initialized = true;
return Ok(camera_format)
} else {
self.initialized = true;
let new_fmt = compatible[0];
self.camera_format = new_fmt;
Ok(new_fmt)
}
}
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::Video4Linux
}
@@ -409,6 +458,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
let format = match fourcc {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
FrameFormat::GRAY8 => FourCC::new(b"GRAY"),
};
let mut res_map = HashMap::new();
for res in resolutions {
@@ -579,7 +629,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
}
};
if let Err(why) = self.device.set_control(id, Control::Value(control.value())) {
if let Err(why) = self.device.set_control(Control { id, value: Value }) {
return Err(NokhwaError::SetPropertyError {
property: format!("{} V4L2ID {}", control.control(), id),
value: control.value().to_string(),
@@ -682,25 +732,19 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
self.stream_handle.is_some()
}
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
fn frame(&mut self) -> Result<Buffer, NokhwaError> {
let cam_fmt = self.camera_format;
let raw_frame = self.frame_raw()?;
let conv = match cam_fmt.format() {
FrameFormat::MJPEG => mjpeg_to_rgb(&raw_frame, false)?,
FrameFormat::YUYV => yuyv422_to_rgb(&raw_frame, false)?,
FrameFormat::GRAY8 => raw_frame.to_vec(),
};
let image_buf =
match ImageBuffer::from_vec(cam_fmt.width(), cam_fmt.height(), conv) {
Some(buf) => {
let rgb_buf: ImageBuffer<Rgb<u8>, Vec<u8>> = buf;
rgb_buf
}
None => return Err(NokhwaError::ReadFrameError(
"ImageBuffer is not large enough! This is probably a bug, please report it!"
.to_string(),
)),
};
Ok(image_buf)
Ok(Buffer::new(cam_fmt.resolution(), conv, cam_fmt.format()))
}
fn frame_typed<F: PixelFormat>(&mut self) -> Result<ImageBuffer<F::Output, Vec<u8>>, NokhwaError> {
self.frame()?.to_image_with_custom_format::<F>()
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
+99
View File
@@ -0,0 +1,99 @@
/*
* Copyright 2022 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::pixel_format::{PixelFormat};
use crate::{FrameFormat, NokhwaError, Resolution};
use image::ImageBuffer;
#[cfg(feature = "input-opencv")]
use opencv::core::Mat;
#[cfg(feature = "input-opencv")]
use rgb::{FromSlice, RGB};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Hash, PartialOrd, PartialEq)]
#[cfg_attr(feature = "serde", Serialize, Deserialize)]
pub struct Buffer {
resolution: Resolution,
buffer: Vec<u8>,
source_frame_format: FrameFormat,
}
impl Buffer {
pub fn new(res: Resolution, buf: Vec<u8>, source_frame_format: FrameFormat) -> Self {
Self {
resolution: res,
buffer: buf,
source_frame_format,
}
}
pub fn to_image_with_custom_format<F>(
self,
) -> Result<ImageBuffer<F::Output, Vec<u8>>, NokhwaError>
where
F: PixelFormat,
{
if self.source_frame_format != F::CODE {
return Err(NokhwaError::ProcessFrameError {
src: self.source_frame_format,
destination: F::CODE.to_string(),
error: "Assertion failed, wrong source!".to_string(),
});
}
ImageBuffer::from_raw(
self.resolution.width_x,
self.resolution.height_y,
self.buffer,
)
.ok_or(NokhwaError::ProcessFrameError {
src: F::CODE,
destination: stringify!(I::Output).to_string(),
error: "Buffer too small".to_string(),
})
}
#[cfg(feature = "input-opencv")]
pub fn to_opencv_mat(self) -> Result<Mat, NokhwaError> {
Ok(match self.source_frame_format {
FrameFormat::MJPEG | FrameFormat::YUYV => Mat::from_slice_2d(
self.buffer
.as_rgb()
.chunks(self.resolution.height_y as usize)
.collect::<&[&[RGB<u8>]]>(),
),
FrameFormat::GRAY8 => Mat::from_slice_2d(
self.buffer
.chunks(self.resolution.height_y as usize)
.collect::<&[&[u8]]>(),
),
}
.map_err(|why| NokhwaError::ProcessFrameError {
src: self.source_frame_format,
destination: "OpenCV Mat".to_string(),
error: why.to_string(),
})?)
}
pub fn resolution(&self) -> Resolution {
self.resolution
}
pub fn buffer(&self) -> &[u8] {
&self.buffer
}
pub fn source_frame_format(&self) -> FrameFormat {
self.source_frame_format
}
}
+87 -119
View File
@@ -15,10 +15,11 @@
*/
use crate::{
CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, CaptureBackendTrait,
FrameFormat, KnownCameraControls, NokhwaError, Resolution,
buffer::Buffer, BackendsEnum, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use image::buffer::ConvertBuffer;
use image::RgbaImage;
use std::{any::Any, borrow::Cow, collections::HashMap};
#[cfg(feature = "output-wgpu")]
use wgpu::{
@@ -29,17 +30,16 @@ use wgpu::{
/// The main `Camera` struct. This is the struct that abstracts over all the backends, providing a simplified interface for use.
pub struct Camera {
idx: CameraIndex,
backend: Box<dyn CaptureBackendTrait>,
idx: usize,
backend: BackendsEnum,
backend_api: CaptureAPIBackend,
}
#[allow(clippy::nonminimal_bool)]
impl Camera {
/// Create a new camera from an `index` and `format`
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn new(index: &CameraIndex, format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
pub fn new(index: usize, format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
Camera::with_backend(index, format, CaptureAPIBackend::Auto)
}
@@ -47,14 +47,14 @@ impl Camera {
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn with_backend(
index: &CameraIndex,
index: usize,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
) -> Result<Self, NokhwaError> {
let camera_backend: Box<dyn CaptureBackendTrait> = init_camera(index, format, backend)?;
let camera_backend = init_camera(index, format, backend)?;
Ok(Camera {
idx: index.clone(),
idx: index,
backend: camera_backend,
backend_api: backend,
})
@@ -64,7 +64,7 @@ impl Camera {
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn new_with(
index: &CameraIndex,
index: usize,
width: u32,
height: u32,
fps: u32,
@@ -77,20 +77,19 @@ impl Camera {
/// Gets the current Camera's index.
#[must_use]
pub fn index(&self) -> &CameraIndex {
&self.idx
pub fn index(&self) -> usize {
self.idx
}
/// Sets the current Camera's index. Note that this re-initializes the camera.
/// # Errors
/// The Backend may fail to initialize.
pub fn set_index(&mut self, new_idx: &CameraIndex) -> Result<(), NokhwaError> {
pub fn set_index(&mut self, new_idx: usize) -> Result<(), NokhwaError> {
{
self.backend.stop_stream()?;
}
let new_camera_format = self.backend.camera_format();
let new_camera: Box<dyn CaptureBackendTrait> =
init_camera(new_idx, Some(new_camera_format), self.backend_api)?;
let new_camera_format = self.backend.camera_format()?;
let new_camera = init_camera(new_idx, Some(new_camera_format), self.backend_api)?;
self.backend = new_camera;
Ok(())
}
@@ -108,9 +107,8 @@ impl Camera {
{
self.backend.stop_stream()?;
}
let new_camera_format = self.backend.camera_format();
let new_camera: Box<dyn CaptureBackendTrait> =
init_camera(&self.idx, Some(new_camera_format), new_backend)?;
let new_camera_format = self.backend.camera_format()?;
let new_camera = init_camera(self.idx, Some(new_camera_format), new_backend)?;
self.backend = new_camera;
Ok(())
}
@@ -122,13 +120,19 @@ impl Camera {
}
/// Gets the current [`CameraFormat`].
#[must_use]
pub fn camera_format(&self) -> CameraFormat {
pub fn cached_camera_format(&self) -> CameraFormat {
self.backend.cached_camera_format()
}
/// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed.
pub fn camera_format(&self) -> Result<CameraFormat, NokhwaError> {
self.backend.camera_format()
}
/// Will set the current [`CameraFormat`]
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
/// # Errors
/// If you started the stream and the camera rejects the new camera format, this will return an error.
pub fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
@@ -152,14 +156,37 @@ impl Camera {
self.backend.compatible_fourcc()
}
/// A Vector of available [`CameraFormat`]s.
/// # Errors
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a [`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError).
pub fn compatible_camera_formats(&mut self) -> Result<Vec<CameraFormat>, NokhwaError> {
let mut camera_formats = Vec::with_capacity(64);
for foramt in self.compatible_fourcc()? {
let resolution_and_fps: HashMap<Resolution, Vec<u32>> =
self.compatible_list_by_resolution(foramt)?;
for (res, rates) in resolution_and_fps {
for fps in rates {
camera_formats.push(CameraFormat::new(res, foramt, fps))
}
}
}
Ok(camera_formats)
}
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]).
#[must_use]
pub fn resolution(&self) -> Resolution {
pub fn cached_resolution(&self) -> Resolution {
self.backend.cached_resolution()
}
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
pub fn resolution(&self) -> Result<Resolution, NokhwaError> {
self.backend.resolution()
}
/// Will set the current [`Resolution`]
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
/// # Errors
/// If you started the stream and the camera rejects the new resolution, this will return an error.
pub fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
@@ -167,13 +194,19 @@ impl Camera {
}
/// Gets the current camera framerate (See: [`CameraFormat`]).
#[must_use]
pub fn frame_rate(&self) -> u32 {
pub fn cached_frame_rate(&self) -> u32 {
self.backend.cached_frame_rate()
}
/// Gets the current camera framerate (See: [`CameraFormat`]).
pub fn frame_rate(&self) -> Result<u32, NokhwaError> {
self.backend.frame_rate()
}
/// Will set the current framerate
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
/// # Errors
/// If you started the stream and the camera rejects the new framerate, this will return an error.
pub fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
@@ -181,13 +214,19 @@ impl Camera {
}
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]).
#[must_use]
pub fn frame_format(&self) -> FrameFormat {
pub fn cached_frame_format(&self) -> FrameFormat {
self.backend.cached_frame_format()
}
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
pub fn frame_format(&self) -> Result<FrameFormat, NokhwaError> {
self.backend.frame_format()
}
/// Will set the current [`FrameFormat`]
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
/// # Errors
/// If you started the stream and the camera rejects the new frame format, this will return an error.
pub fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
@@ -229,7 +268,7 @@ impl Camera {
.collect::<Vec<(String, CameraControl)>>();
let mut control_map = HashMap::with_capacity(maybe_camera_controls.len());
for (kc, cc) in maybe_camera_controls {
for (kc, cc) in maybe_camera_controls.into_iter() {
control_map.insert(kc, cc);
}
@@ -251,7 +290,7 @@ impl Camera {
.collect::<Vec<(KnownCameraControls, CameraControl)>>();
let mut control_map = HashMap::with_capacity(maybe_camera_controls.len());
for (kc, cc) in maybe_camera_controls {
for (kc, cc) in maybe_camera_controls.into_iter() {
control_map.insert(kc, cc);
}
@@ -330,7 +369,7 @@ impl Camera {
/// # Errors
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet,
/// this will error.
pub fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
pub fn frame(&mut self) -> Result<Buffer, NokhwaError> {
self.backend.frame()
}
@@ -344,35 +383,15 @@ impl Camera {
}
}
/// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed.
#[must_use]
pub fn min_buffer_size(&self, rgba: bool) -> usize {
let resolution = self.backend.resolution();
let w = resolution.width() as usize;
let h = resolution.height() as usize;
let c = if rgba { 4 } else { 3 };
w * h * c
}
/// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture.
/// # Errors
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error.
pub fn frame_to_buffer(
pub fn write_frame_to_buffer(
&mut self,
buffer: &mut [u8],
convert_rgba: bool,
) -> Result<(), NokhwaError> {
let camera_format = self.backend.camera_format();
let format = camera_format.format();
let raw_frame = self.frame_raw()?;
match format {
FrameFormat::MJPEG => crate::utils::buf_mjpeg_to_rgb(&raw_frame, buffer, convert_rgba)?,
FrameFormat::YUYV => {
crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)?
}
};
Ok(())
write_alpha: bool,
) -> Result<usize, NokhwaError> {
self.backend.write_frame_to_buffer(buffer, write_alpha)
}
#[cfg(feature = "output-wgpu")]
@@ -380,59 +399,13 @@ impl Camera {
/// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
/// # Errors
/// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
pub fn frame_texture<'a>(
pub fn frame_texture<'a, F: PixelFormat>(
&mut self,
device: &WgpuDevice,
queue: &WgpuQueue,
label: Option<&'a str>,
) -> Result<WgpuTexture, NokhwaError> {
use std::num::NonZeroU32;
let frame = self.frame()?;
let rgba_frame: RgbaImage = frame.convert();
let texture_size = Extent3d {
width: frame.width(),
height: frame.height(),
depth_or_array_layers: 1,
};
let texture = device.create_texture(&TextureDescriptor {
label,
size: texture_size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
});
let width_nonzero = match NonZeroU32::try_from(4 * rgba_frame.width()) {
Ok(w) => Some(w),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
};
let height_nonzero = match NonZeroU32::try_from(rgba_frame.height()) {
Ok(h) => Some(h),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
};
queue.write_texture(
ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: TextureAspect::All,
},
&rgba_frame.to_vec(),
ImageDataLayout {
offset: 0,
bytes_per_row: width_nonzero,
rows_per_image: height_nonzero,
},
texture_size,
);
Ok(texture)
self.backend.frame_texture(device, queue, label)
}
/// Will drop the stream.
@@ -445,7 +418,7 @@ impl Camera {
impl Drop for Camera {
fn drop(&mut self) {
let _drop = self.stop_stream();
self.stop_stream().unwrap();
}
}
@@ -466,8 +439,6 @@ fn figure_out_auto() -> Option<CaptureAPIBackend> {
cap = CaptureAPIBackend::GStreamer;
} else if cfg!(feature = "input-opencv") {
cap = CaptureAPIBackend::OpenCv;
} else if cfg!(feature = "input-jscam") {
cap = CaptureAPIBackend::Browser;
}
if cap == CaptureAPIBackend::Auto {
return None;
@@ -482,15 +453,15 @@ macro_rules! cap_impl_fn {
$(
paste::paste! {
#[cfg ($cfg) ]
fn [< init_ $backend_name>](idx: &CameraIndex, setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
fn [< init_ $backend_name>](idx: usize, setting: Option<CameraFormat>) -> Option<Result<BackendsEnum, NokhwaError>> {
use crate::backends::capture::$backend;
match <$backend>::$init_fn(idx, setting) {
Ok(cap) => Some(Ok(Box::new(cap))),
Ok(cap) => Some(Ok(cp.into())),
Err(why) => Some(Err(why)),
}
}
#[cfg(not( $cfg ))]
fn [< init_ $backend_name>](_idx: &CameraIndex, _setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
fn [< init_ $backend_name>](_idx: usize, _setting: Option<CameraFormat>) -> Option<Result<BackendsEnum, NokhwaError>> {
None
}
}
@@ -513,7 +484,7 @@ macro_rules! cap_impl_matches {
CaptureAPIBackend::$backend => {
match cfg!(feature = $feature) {
true => {
match $fn(&i,s) {
match $fn(i,s) {
Some(cap) => match cap {
Ok(c) => c,
Err(why) => return Err(why),
@@ -549,7 +520,7 @@ macro_rules! cap_impl_matches {
CaptureAPIBackend::$backend => {
match cfg!(feature = $feature) {
true => {
match $fn(&i,s) {
match $fn(i,s) {
Some(cap) => match cap {
Ok(c) => c,
Err(why) => return Err(why),
@@ -581,33 +552,30 @@ macro_rules! cap_impl_matches {
}
cap_impl_fn! {
(GStreamerCaptureDevice, new, feature = "input-gst", gst),
// (GStreamerCaptureDevice, new, feature = "input-gst", gst),
(OpenCvCaptureDevice, new_autopref, feature = "input-opencv", opencv),
// (UVCCaptureDevice, create, feature = "input-uvc", uvc),
(BrowserCaptureDevice, new, feature = "input-jscam", browser),
(V4LCaptureDevice, new, all(feature = "input-v4l", target_os = "linux"), v4l),
(MediaFoundationCaptureDevice, new, all(feature = "input-msmf", target_os = "windows"), msmf),
(AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation)
}
fn init_camera(
index: &CameraIndex,
index: usize,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
) -> Result<Box<dyn CaptureBackendTrait>, NokhwaError> {
) -> Result<BackendsEnum, NokhwaError> {
let camera_backend = cap_impl_matches! {
backend, index, format,
("input-v4l", Video4Linux, init_v4l),
("input-msmf", MediaFoundation, init_msmf),
("input-avfoundation", AVFoundation, init_avfoundation),
// ("input-uvc", UniversalVideoClass, init_uvc),
("input-gst", GStreamer, init_gst),
("input-opencv", OpenCv, init_opencv),
("input-jscam", Browser, init_browser)
// ("input-gst", GStreamer, init_gst),
("input-opencv", OpenCv, init_opencv)
};
Ok(camera_backend)
}
#[cfg(feature = "output-threaded")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))]
unsafe impl Send for Camera {}
+105 -40
View File
@@ -14,14 +14,17 @@
* limitations under the License.
*/
use crate::buffer::Buffer;
use crate::pixel_format::PixelFormat;
use crate::{
error::NokhwaError,
utils::{CameraFormat, CameraInfo, FrameFormat, Resolution},
frame_formats,
utils::{CameraFormat, CameraInfo, FrameFormat, Resolution, buf_mjpeg_to_rgb, buf_yuyv422_to_rgb},
CameraControl, CaptureAPIBackend, KnownCameraControls,
};
use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage};
use std::{any::Any, borrow::Cow, collections::HashMap};
use enum_dispatch::enum_dispatch;
#[cfg(feature = "output-wgpu")]
use wgpu::{
Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue,
@@ -29,6 +32,14 @@ use wgpu::{
TextureUsages,
};
#[enum_dispatch]
pub(crate) enum BackendsEnum {
MSMF,
AVF,
V4L2,
OCV,
}
/// This trait is for any backend that allows you to grab and take frames from a camera.
/// Many of the backends are **blocking**, if the camera is occupied the library will block while it waits for it to become available.
///
@@ -36,7 +47,11 @@ use wgpu::{
/// - Backends, if not provided with a camera format, will be spawned with 640x480@15 FPS, MJPEG [`CameraFormat`].
/// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::Camera) struct abstracts most of this away, if you plan to use the raw backend structs please read the `Quirks` section of each backend.
/// - If you call [`stop_stream()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera.
pub trait CaptureBackendTrait: Send {
#[enum_dispatch(BackendsEnum)]
pub trait CaptureBackendTrait {
/// Initializes the camera. You must call this before any other function.
fn init(&mut self) -> Result<CameraFormat, NokhwaError>;
/// Returns the current backend used.
fn backend(&self) -> CaptureAPIBackend;
@@ -44,10 +59,15 @@ pub trait CaptureBackendTrait: Send {
fn camera_info(&self) -> &CameraInfo;
/// Gets the current [`CameraFormat`].
fn camera_format(&self) -> CameraFormat;
fn cached_camera_format(&self) -> CameraFormat;
/// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed.
fn camera_format(&self) -> Result<CameraFormat, NokhwaError>;
/// Will set the current [`CameraFormat`]
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
/// # Errors
/// If you started the stream and the camera rejects the new camera format, this will return an error.
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError>;
@@ -60,34 +80,69 @@ pub trait CaptureBackendTrait: Send {
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError>;
fn compatible_camera_formats(&mut self) -> Result<Vec<CameraFormat>, NokhwaError> {
let mut compatible_formats = vec![];
frame_formats().map(|ff| {
if let Ok(mut fmts) = self.compatible_list_by_resolution(ff).map(|compatible| {
compatible
.into_iter()
.map(|(res, fps)| {
fps.into_iter().map(|rate| CameraFormat {
resolution: res,
format: ff,
frame_rate: rate,
})
})
.collect::<Vec<CameraFormat>>()
}) {
compatible_formats.append(&mut fmts)
}
})
}
/// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most.
/// # Errors
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError)).
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError>;
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]).
fn resolution(&self) -> Resolution;
fn cached_resolution(&self) -> Resolution;
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
fn resolution(&self) -> Result<Resolution, NokhwaError>;
/// Will set the current [`Resolution`]
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
/// # Errors
/// If you started the stream and the camera rejects the new resolution, this will return an error.
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>;
/// Gets the current camera framerate (See: [`CameraFormat`]).
fn frame_rate(&self) -> u32;
fn cached_frame_rate(&self) -> u32;
/// Gets the current camera framerate (See: [`CameraFormat`]). This will force refresh to the current latest if it has changed.
fn frame_rate(&self) -> Result<u32, NokhwaError>;
/// Will set the current framerate
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
/// # Errors
/// If you started the stream and the camera rejects the new framerate, this will return an error.
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError>;
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]).
fn frame_format(&self) -> FrameFormat;
fn cached_frame_format(&self) -> FrameFormat;
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
fn frame_format(&self) -> Result<FrameFormat, NokhwaError>;
/// Will set the current [`FrameFormat`]
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
/// # Errors
/// If you started the stream and the camera rejects the new frame format, this will return an error.
fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError>;
@@ -151,20 +206,34 @@ pub trait CaptureBackendTrait: Send {
/// # Errors
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet,
/// this will error.
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError>;
fn frame(&mut self) -> Result<Buffer, NokhwaError>;
/// Will get a frame from the camera as a Raw RGB image buffer. Depending on the backend, if you have not called [`open_stream()`](CaptureBackendTrait::open_stream()) before you called this,
/// it will either return an error.
/// # Errors
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet,
/// or if the PixelFormat is invalid, this will error.
fn frame_typed<F: PixelFormat>(
&mut self,
) -> Result<ImageBuffer<F::Output, 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), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error.
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError>;
/// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed.
fn min_buffer_size(&self, rgba: bool) -> usize {
let resolution = self.resolution();
if rgba {
return (resolution.width() * resolution.height() * 4) as usize;
/// The minimum buffer size needed to write the current frame. If `alpha` is true, it will instead return the minimum size of the RGBA buffer needed.
fn decoded_buffer_size(&self, alpha: bool) -> Result<usize, NokhwaError> {
let cfmt = self.camera_format()?;
let resolution = cfmt.resolution();
let pxwidth = match cfmt.format() {
FrameFormat::MJPEG | FrameFormat::YUYV => 3,
FrameFormat::GRAY8 => 1,
};
if alpha {
return (resolution.width() * resolution.height() * (pxwidth + 1)) as usize;
}
(resolution.width() * resolution.height() * 3) as usize
(resolution.width() * resolution.height() * pxwidth) as usize
}
/// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture.
@@ -173,28 +242,24 @@ pub trait CaptureBackendTrait: Send {
fn write_frame_to_buffer(
&mut self,
buffer: &mut [u8],
convert_rgba: bool,
) -> Result<usize, NokhwaError> {
let resolution = self.resolution();
write_alpha: bool,
) -> Result<usize, NokhwaError>
{
let cfmt = self.camera_format()?;
let frame = self.frame_raw()?;
if convert_rgba {
let image_data =
match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) {
Some(image) => {
let image: ImageBuffer<Rgb<u8>, Cow<[u8]>> = image;
image
}
None => {
return Err(NokhwaError::ReadFrameError(
"Frame Cow Too Small".to_string(),
))
}
let data = match cfmt.format() {
FrameFormat::MJPEG => buf_mjpeg_to_rgb(&frame, buffer, write_alpha),
FrameFormat::YUYV => buf_yuyv422_to_rgb(&frame, buffer, write_alpha),
FrameFormat::GRAY8 => {
let data = if write_alpha {
frame.into_iter().flat_map(|px| [*px, u8::MAX]).collect::<Cow<[u8]>>()
} else {
frame
};
let rgba_image: RgbaImage = image_data.convert();
buffer.copy_from_slice(rgba_image.as_raw());
return Ok(rgba_image.len());
}
buffer.copy_from_slice(frame.as_ref());
buffer.copy_from_slice(&data);
}
};
Ok(frame.len())
}
@@ -203,15 +268,15 @@ pub trait CaptureBackendTrait: Send {
/// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
/// # Errors
/// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
fn frame_texture<'a>(
fn frame_texture<'a, F: PixelFormat>(
&mut self,
device: &WgpuDevice,
queue: &WgpuQueue,
label: Option<&'a str>,
) -> Result<WgpuTexture, NokhwaError> {
use std::num::NonZeroU32;
let frame = self.frame()?;
let rgba_frame: RgbaImage = frame.convert();
use std::{convert::TryFrom, num::NonZeroU32};
use image::RgbaImage;
let frame = RgbaImage::from( elf.frame()?.to_image_with_custom_format::<F>()?);
let texture_size = Extent3d {
width: frame.width(),
@@ -229,12 +294,12 @@ pub trait CaptureBackendTrait: Send {
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
});
let width_nonzero = match NonZeroU32::try_from(4 * rgba_frame.width()) {
let width_nonzero = match NonZeroU32::try_from(4 * frame.width()) {
Ok(w) => Some(w),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
};
let height_nonzero = match NonZeroU32::try_from(rgba_frame.height()) {
let height_nonzero = match NonZeroU32::try_from(frame.height()) {
Ok(h) => Some(h),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
};
+3 -1
View File
@@ -21,6 +21,8 @@ use thiserror::Error;
#[allow(clippy::module_name_repetitions)]
#[derive(Error, Debug, Clone)]
pub enum NokhwaError {
#[error("Unitialized Camera. Call `init()` first!")]
UnitializedError,
#[error("Could not initialize {backend}: {error}")]
InitializeError {
backend: CaptureAPIBackend,
@@ -101,7 +103,7 @@ impl From<BindingError> for NokhwaError {
error,
},
BindingError::DeviceOpenFailError(device, error) => {
NokhwaError::OpenDeviceError(device.to_string(), error)
NokhwaError::OpenDeviceError(device, error)
}
BindingError::ReadFrameError(error) => NokhwaError::ReadFrameError(error),
BindingError::NotImplementedError => {
+3
View File
@@ -28,6 +28,7 @@
/// Raw access to each of Nokhwa's backends.
pub mod backends;
pub mod buffer;
mod camera;
mod camera_traits;
mod error;
@@ -40,6 +41,7 @@ pub mod js_camera;
#[cfg(feature = "input-ipcam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))]
pub mod network_camera;
pub mod pixel_format;
mod query;
/// A camera that runs in a different thread and can call your code based on callbacks.
#[cfg(feature = "output-threaded")]
@@ -50,6 +52,7 @@ mod utils;
pub use camera::Camera;
pub use camera_traits::*;
pub use error::NokhwaError;
pub use buffer::Buffer;
pub use init::*;
#[cfg(feature = "input-jscam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))]
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright 2022 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::buffer_output::{BufferOutput, GrayU8, RgbU8};
use crate::FrameFormat;
use image::{Luma, Pixel, Rgb};
use std::fmt::Debug;
use std::hash::Hash;
pub trait PixelFormat: Copy + Clone + Debug + Default + Hash + Send + Sync {
type Output: Pixel;
const SUPPORTED_CODES: &'static [FrameFormat];
}
+31 -31
View File
@@ -15,8 +15,8 @@
*/
use crate::{
Camera, CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureAPIBackend, FrameFormat,
KnownCameraControls, NokhwaError, Resolution,
Camera, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait,
FrameFormat, KnownCameraControls, NokhwaError, Resolution, Buffer
};
use image::{ImageBuffer, Rgb};
use parking_lot::Mutex;
@@ -33,13 +33,13 @@ type AtomicLock<T> = Arc<Mutex<T>>;
pub type CallbackFn = fn(
_camera: &Arc<Mutex<Camera>>,
_frame_callback: &Arc<
Mutex<Option<Box<dyn FnMut(ImageBuffer<Rgb<u8>, Vec<u8>>) + Send + 'static>>>,
Mutex<Option<Box<dyn FnMut(Buffer) + Send + 'static>>>,
>,
_last_frame_captured: &Arc<Mutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
_last_frame_captured: &Arc<Mutex<Buffer>>,
_die_bool: &Arc<AtomicBool>,
);
type HeldCallbackType =
Arc<Mutex<Option<Box<dyn FnMut(ImageBuffer<Rgb<u8>, Vec<u8>>) + Send + 'static>>>>;
Arc<Mutex<Option<Box<dyn FnMut(Buffer) + Send + 'static>>>>;
/// Creates a camera that runs in a different thread that you can use a callback to access the frames of.
/// It uses a `Arc` and a `Mutex` to ensure that this feels like a normal camera, but callback based.
@@ -57,7 +57,7 @@ type HeldCallbackType =
pub struct CallbackCamera {
camera: AtomicLock<Camera>,
frame_callback: HeldCallbackType,
last_frame_captured: AtomicLock<ImageBuffer<Rgb<u8>, Vec<u8>>>,
last_frame_captured: AtomicLock<Buffer>,
die_bool: Arc<AtomicBool>,
}
@@ -65,7 +65,7 @@ impl CallbackCamera {
/// Create a new `ThreadedCamera` from an `index` and `format`. `format` can be `None`.
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn new(index: &CameraIndex, format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
pub fn new(index: usize, format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
CallbackCamera::with_backend(index, format, CaptureAPIBackend::Auto)
}
@@ -73,7 +73,7 @@ impl CallbackCamera {
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn with_backend(
index: &CameraIndex,
index: usize,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
) -> Result<Self, NokhwaError> {
@@ -84,7 +84,7 @@ impl CallbackCamera {
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn new_with(
index: &CameraIndex,
index: usize,
width: u32,
height: u32,
fps: u32,
@@ -103,7 +103,7 @@ impl CallbackCamera {
/// # Errors
/// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
pub fn customized_all(
index: &CameraIndex,
index: usize,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
func: Option<CallbackFn>,
@@ -118,10 +118,7 @@ impl CallbackCamera {
backend,
)?));
let frame_callback = Arc::new(Mutex::new(None));
let last_frame_captured = Arc::new(Mutex::new(ImageBuffer::new(
format.width(),
format.height(),
)));
let last_frame_captured = Arc::new(Mutex::new(Buffer::default()));
let die_bool = Arc::new(AtomicBool::new(false));
let camera_clone = camera.clone();
@@ -163,14 +160,14 @@ impl CallbackCamera {
/// Gets the current Camera's index.
#[must_use]
pub fn index(&self) -> CameraIndex {
pub fn index(&self) -> usize {
self.camera.lock().index().clone()
}
/// Sets the current Camera's index. Note that this re-initializes the camera.
/// # Errors
/// The Backend may fail to initialize.
pub fn set_index(&mut self, new_idx: &CameraIndex) -> Result<(), NokhwaError> {
pub fn set_index(&mut self, new_idx: usize) -> Result<(), NokhwaError> {
self.camera.lock().set_index(new_idx)
}
@@ -195,7 +192,7 @@ impl CallbackCamera {
/// Gets the current [`CameraFormat`].
#[must_use]
pub fn camera_format(&self) -> CameraFormat {
pub fn camera_format(&self) -> Result<CameraFormat, NokhwaError> {
self.camera.lock().camera_format()
}
@@ -204,7 +201,7 @@ impl CallbackCamera {
/// # Errors
/// If you started the stream and the camera rejects the new camera format, this will return an error.
pub fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
*self.last_frame_captured.lock() = ImageBuffer::new(new_fmt.width(), new_fmt.height());
*self.last_frame_captured.lock() = Buffer::new(new_res, Vec::default(), self.camera_format()?.format());
self.camera.lock().set_camera_format(new_fmt)
}
@@ -227,7 +224,7 @@ impl CallbackCamera {
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]).
#[must_use]
pub fn resolution(&self) -> Resolution {
pub fn resolution(&self) -> Result<Resolution, NokhwaError> {
self.camera.lock().resolution()
}
@@ -236,13 +233,13 @@ impl CallbackCamera {
/// # Errors
/// If you started the stream and the camera rejects the new resolution, this will return an error.
pub fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
*self.last_frame_captured.lock() = ImageBuffer::new(new_res.width(), new_res.height());
*self.last_frame_captured.lock() = Buffer::new(new_res, Vec::default(), self.camera_format()?.format());
self.camera.lock().set_resolution(new_res)
}
/// Gets the current camera framerate (See: [`CameraFormat`]).
#[must_use]
pub fn frame_rate(&self) -> u32 {
pub fn frame_rate(&self) -> Result<u32, NokhwaError> {
self.camera.lock().frame_rate()
}
@@ -256,7 +253,7 @@ impl CallbackCamera {
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]).
#[must_use]
pub fn frame_format(&self) -> FrameFormat {
pub fn frame_format(&self) -> Result<FrameFormat, NokhwaError> {
self.camera.lock().frame_format()
}
@@ -392,10 +389,10 @@ impl CallbackCamera {
/// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error.
pub fn open_stream<F>(&mut self, mut callback: F) -> Result<(), NokhwaError>
where
F: (FnMut(ImageBuffer<Rgb<u8>, Vec<u8>>)) + Send + 'static,
F: (FnMut(Buffer)) + Send + 'static,
{
*self.frame_callback.lock() =
Some(Box::new(move |image: ImageBuffer<Rgb<u8>, Vec<u8>>| {
Some(Box::new(move |image: Buffer| {
callback(image)
}));
self.camera.lock().open_stream()
@@ -404,10 +401,10 @@ impl CallbackCamera {
/// Sets the frame callback to the new specified function. This function will be called instead of the previous one(s).
pub fn set_callback<F>(&mut self, mut callback: F)
where
F: (FnMut(ImageBuffer<Rgb<u8>, Vec<u8>>)) + Send + 'static,
F: (FnMut(Buffer)) + Send + 'static,
{
*self.frame_callback.lock() =
Some(Box::new(move |image: ImageBuffer<Rgb<u8>, Vec<u8>>| {
Some(Box::new(move |image: Buffer| {
callback(image)
}));
}
@@ -415,7 +412,7 @@ impl CallbackCamera {
/// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame)
/// # Errors
/// This will error if the camera fails to capture a frame.
pub fn poll_frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
pub fn poll_frame(&mut self) -> Result<Buffer, NokhwaError> {
let frame = self.camera.lock().frame()?;
*self.last_frame_captured.lock() = frame.clone();
Ok(frame)
@@ -441,21 +438,24 @@ impl CallbackCamera {
}
}
impl Drop for CallbackCamera {
impl<C> Drop for CallbackCamera<C>
where
C: CaptureBackendTrait,
{
fn drop(&mut self) {
let _stop_stream_err = self.stop_stream();
self.die_bool.store(true, Ordering::SeqCst);
}
}
fn camera_frame_thread_loop(
camera: &AtomicLock<Camera>,
fn camera_frame_thread_loop<C: CaptureBackendTrait>(
camera: &AtomicLock<Camera<C>>,
frame_callback: &HeldCallbackType,
last_frame_captured: &AtomicLock<ImageBuffer<Rgb<u8>, Vec<u8>>>,
die_bool: &Arc<AtomicBool>,
) {
loop {
if let Ok(img) = camera.lock().frame() {
if let Ok(img) = camera.lock().fr {
*last_frame_captured.lock() = img.clone();
if let Some(cb) = (*frame_callback.lock()).as_mut() {
cb(img);
+167 -102
View File
@@ -14,15 +14,8 @@
* limitations under the License.
*/
use crate::pixel_format::PixelFormat;
use crate::NokhwaError;
use std::{
borrow::{Borrow, Cow},
cmp::Ordering,
fmt::{Display, Formatter},
};
#[cfg(feature = "output-wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
#[cfg(any(
all(
feature = "input-avfoundation",
@@ -45,10 +38,19 @@ use nokhwa_bindings_windows::{
MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls,
MediaFoundationDeviceDescriptor,
};
#[cfg(feature = serde)]
use serde::{Deserialize, Serialize};
use std::{
borrow::Borrow,
cmp::Ordering,
fmt::{Display, Formatter},
};
#[cfg(feature = "input-uvc")]
use uvc::StreamFormat;
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
use v4l::{control::Description, Format, FourCC};
#[cfg(feature = "output-wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`.
/// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr)
@@ -56,13 +58,15 @@ use v4l::{control::Description, Format, FourCC};
/// # JS-WASM
/// This is exported as `FrameFormat`
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FrameFormat {
MJPEG,
YUYV,
GRAY8,
}
impl Display for FrameFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FrameFormat::MJPEG => {
write!(f, "MJPEG")
@@ -70,10 +74,22 @@ impl Display for FrameFormat {
FrameFormat::YUYV => {
write!(f, "YUYV")
}
FrameFormat::GRAY8 => {
write!(f, "GRAY8")
}
}
}
}
impl<P> From<P> for FrameFormat
where
P: PixelFormat,
{
fn from(_: P) -> Self {
P::CODE
}
}
#[cfg(feature = "input-uvc")]
impl From<FrameFormat> for uvc::FrameFormat {
fn from(ff: FrameFormat) -> Self {
@@ -93,6 +109,7 @@ impl From<MFFrameFormat> for FrameFormat {
match mf_ff {
MFFrameFormat::MJPEG => FrameFormat::MJPEG,
MFFrameFormat::YUYV => FrameFormat::YUYV,
MFFrameFormat::GRAY8 => FrameFormat::GRAY8,
}
}
}
@@ -106,6 +123,7 @@ impl From<FrameFormat> for MFFrameFormat {
match ff {
FrameFormat::MJPEG => MFFrameFormat::MJPEG,
FrameFormat::YUYV => MFFrameFormat::YUYV,
FrameFormat::GRAY8 => MFFrameFormat::GRAY8, //FIXME
}
}
}
@@ -126,6 +144,7 @@ impl From<AVFourCC> for FrameFormat {
match av_fcc {
AVFourCC::YUV2 => FrameFormat::YUYV,
AVFourCC::MJPEG => FrameFormat::MJPEG,
AVFourCC::GRAY8 => FrameFormat::GRAY8,
}
}
}
@@ -146,16 +165,22 @@ impl From<FrameFormat> for AVFourCC {
match ff {
FrameFormat::MJPEG => AVFourCC::MJPEG,
FrameFormat::YUYV => AVFourCC::YUV2,
FrameFormat::GRAY8 => AVFourCC::GRAY8,
}
}
}
pub const fn frame_formats() -> [FrameFormat; 3] {
[FrameFormat::MJPEG, FrameFormat::YUYV, FrameFormat::GRAY8]
}
/// Describes a Resolution.
/// This struct consists of a Width and a Height value (x,y). <br>
/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest.
/// # JS-WASM
/// This is exported as `JSResolution`
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSResolution))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)]
pub struct Resolution {
pub width_x: u32,
@@ -210,7 +235,7 @@ impl Resolution {
}
impl Display for Resolution {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}x{}", self.x(), self.y())
}
}
@@ -281,6 +306,7 @@ impl From<AVVideoResolution> for Resolution {
/// This is a convenience struct that holds all information about the format of a webcam stream.
/// It consists of a [`Resolution`], [`FrameFormat`], and a frame rate(u8).
#[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CameraFormat {
resolution: Resolution,
format: FrameFormat,
@@ -419,6 +445,7 @@ impl From<CameraFormat> for Format {
let pxfmt = match cam_fmt.format() {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
FrameFormat::GRAY8 => FourCC::new(b"GREY"),
};
Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt)
@@ -458,11 +485,12 @@ impl From<CameraFormat> for CaptureDeviceFormatDescriptor {
/// This is exported as a `JSCameraInfo`.
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))]
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CameraInfo {
human_name: String,
description: String,
misc: String,
index: CameraIndex,
index: usize,
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))]
@@ -472,11 +500,14 @@ impl CameraInfo {
/// This is exported as a constructor for [`CameraInfo`].
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))]
// OK, i just checkeed back on this code. WTF was I on when I wrote `&(impl AsRef<str> + ?Sized)` ????
// I need to get on the same shit that my previous self was on, because holy shit that stuff is strong as FUCK!
// Finally fixed this insanity. Hopefully I didnt torment anyone by actually putting this in a stable release.
pub fn new(
human_name: &(impl AsRef<str> + ?Sized),
description: &(impl AsRef<str> + ?Sized),
misc: &(impl AsRef<str> + ?Sized),
index: CameraIndex,
human_name: impl AsRef<str>,
description: impl AsRef<str>,
misc: impl AsRef<str>,
index: usize,
) -> Self {
CameraInfo {
human_name: human_name.as_ref().to_string(),
@@ -548,36 +579,36 @@ impl CameraInfo {
/// This is exported as a `get_Index`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index))]
pub fn index(&self) -> &CameraIndex {
&self.index
pub fn index(&self) -> u32 {
self.index
}
/// Set the device info's index.
/// # JS-WASM
/// This is exported as a `set_Index`.
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Index))]
pub fn set_index(&mut self, index: CameraIndex) {
pub fn set_index(&mut self, index: u32) {
self.index = index;
}
/// Gets the device info's index as an `u32`.
/// # Errors
/// If the index is not parsable as a `u32`, this will error.
/// # JS-WASM
/// This is exported as `get_Index_Int`
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))]
pub fn index_num(&self) -> Result<u32, NokhwaError> {
match &self.index {
CameraIndex::Index(i) => Ok(*i),
CameraIndex::String(s) => match s.parse::<u32>() {
Ok(p) => Ok(p),
Err(why) => Err(NokhwaError::GetPropertyError {
property: "index-int".to_string(),
error: why.to_string(),
}),
},
}
}
// /// Gets the device info's index as an `u32`.
// /// # Errors
// /// If the index is not parsable as a `u32`, this will error.
// /// # JS-WASM
// /// This is exported as `get_Index_Int`
// #[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))]
// pub fn index_num(&self) -> Result<u32, NokhwaError> {
// match &self.index {
// CameraIndex::Index(i) => Ok(*i),
// CameraIndex::String(s) => match s.parse::<u32>() {
// Ok(p) => Ok(p),
// Err(why) => Err(NokhwaError::GetPropertyError {
// property: "index-int".to_string(),
// error: why.to_string(),
// }),
// },
// }
// }
}
impl Display for CameraInfo {
@@ -597,10 +628,10 @@ impl Display for CameraInfo {
impl From<MediaFoundationDeviceDescriptor<'_>> for CameraInfo {
fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self {
CameraInfo {
human_name: dev_desc,
description: "Media Foundation Device",
human_name: dev_desc.name_as_string(),
description: "Media Foundation Device".to_string(),
misc: dev_desc.link_as_string(),
index: CameraIndex::Index(dev_desc.index() as u32),
index: dev_desc.index() as usize,
}
}
}
@@ -623,7 +654,7 @@ impl From<AVCaptureDeviceDescriptor> for CameraInfo {
human_name: descriptor.name,
description: descriptor.description,
misc: descriptor.misc,
index: CameraIndex::Index(descriptor.index as u32),
index: descriptor.index as usize,
}
}
}
@@ -632,6 +663,7 @@ impl From<AVCaptureDeviceDescriptor> for CameraInfo {
/// These can control the picture brightness, etc. <br>
/// Note that not all backends/devices support all these. Run [`supported_camera_controls()`](crate::CaptureBackendTrait::supported_camera_controls) to see which ones can be set.
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KnownCameraControls {
Brightness,
Contrast,
@@ -721,7 +753,7 @@ impl From<MFControl> for KnownCameraControls {
}
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
impl std::convert::TryFrom<Description> for KnownCameraControls {
impl TryFrom<Description> for KnownCameraControls {
type Error = NokhwaError;
fn try_from(value: Description) -> Result<Self, Self::Error> {
@@ -764,12 +796,44 @@ impl Display for KnownCameraControlFlag {
}
}
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// TODO: use in CameraControl
pub enum ControlValue {
None,
Integer {
value: i64,
default: i64,
step: i64,
},
IntegerRange {
min: i32,
max: i32,
value: i32,
step: i32,
default: i32,
},
Boolean {
value: bool,
default: bool,
},
String {
value: String,
default: String,
},
Bytes {
value: Vec<u8>,
default: Vec<u8>,
}
}
/// This struct tells you everything about a particular [`KnownCameraControls`]. <br>
/// However, you should never need to instantiate this struct, since its usually generated for you by `nokhwa`.
/// The only time you should be modifying this struct is when you need to set a value and pass it back to the camera.
/// NOTE: Assume the values for `min` and `max` as **non-inclusive**!.
/// E.g. if the [`CameraControl`] says `min` is 100, the minimum is actually 101.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CameraControl {
control: KnownCameraControls,
min: i32,
@@ -1003,6 +1067,7 @@ impl Ord for CameraControl {
/// - `Network` - Uses `OpenCV` to capture from an IP.
/// - `Browser` - Uses browser APIs to capture from a webcam.
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CaptureAPIBackend {
Auto,
AVFoundation,
@@ -1010,82 +1075,82 @@ pub enum CaptureAPIBackend {
UniversalVideoClass,
MediaFoundation,
OpenCv,
GStreamer,
// GStreamer,
Network,
Browser,
}
impl Display for CaptureAPIBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let self_str = format!("{:?}", self);
write!(f, "{}", self_str)
}
}
/// A webcam index that supports both strings and integers. Most backends take an int, but `IPCamera`s take a URL (string).
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
pub enum CameraIndex {
Index(u32),
String(String),
}
// /// A webcam index that supports both strings and integers. Most backends take an int, but `IPCamera`s take a URL (string).
// #[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
// pub enum CameraIndex {
// Index(u32),
// String(String),
// }
impl CameraIndex {
/// Gets the device info's index as an `u32`.
/// # Errors
/// If the index is not parsable as a `u32`, this will error.
pub fn as_index(&self) -> Result<u32, NokhwaError> {
match self {
CameraIndex::Index(i) => Ok(*i),
CameraIndex::String(s) => match s.parse::<u32>() {
Ok(p) => Ok(p),
Err(why) => Err(NokhwaError::GetPropertyError {
property: "index-int".to_string(),
error: why.to_string(),
}),
},
}
}
}
// impl CameraIndex {
// /// Gets the device info's index as an `u32`.
// /// # Errors
// /// If the index is not parsable as a `u32`, this will error.
// pub fn as_index(&self) -> Result<u32, NokhwaError> {
// match self {
// CameraIndex::Index(i) => Ok(*i),
// CameraIndex::String(s) => match s.parse::<u32>() {
// Ok(p) => Ok(p),
// Err(why) => Err(NokhwaError::GetPropertyError {
// property: "index-int".to_string(),
// error: why.to_string(),
// }),
// },
// }
// }
// }
impl Display for CameraIndex {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CameraIndex::Index(idx) => {
write!(f, "{}", idx)
}
CameraIndex::String(ip) => {
write!(f, "{}", ip)
}
}
}
}
// impl Display for CameraIndex {
// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// match self {
// CameraIndex::Index(idx) => {
// write!(f, "{}", idx)
// }
// CameraIndex::String(ip) => {
// write!(f, "{}", ip)
// }
// }
// }
// }
impl From<u32> for CameraIndex {
fn from(v: u32) -> Self {
CameraIndex::Index(v)
}
}
// impl From<u32> for CameraIndex {
// fn from(v: u32) -> Self {
// CameraIndex::Index(v)
// }
// }
/// Trait for strings that can be converted to [`CameraIndex`]es.
pub trait ValidString: AsRef<str> {}
// /// Trait for strings that can be converted to [`CameraIndex`]es.
// pub trait ValidString: AsRef<str> {}
//
// impl ValidString for String {}
// impl<'a> ValidString for &'a String {}
// impl<'a> ValidString for &'a mut String {}
// impl<'a> ValidString for Cow<'a, str> {}
// impl<'a> ValidString for &'a Cow<'a, str> {}
// impl<'a> ValidString for &'a mut Cow<'a, str> {}
// impl<'a> ValidString for &'a str {}
// impl<'a> ValidString for &'a mut str {}
impl ValidString for String {}
impl<'a> ValidString for &'a String {}
impl<'a> ValidString for &'a mut String {}
impl<'a> ValidString for Cow<'a, str> {}
impl<'a> ValidString for &'a Cow<'a, str> {}
impl<'a> ValidString for &'a mut Cow<'a, str> {}
impl<'a> ValidString for &'a str {}
impl<'a> ValidString for &'a mut str {}
impl<T> From<T> for CameraIndex
where
T: ValidString,
{
fn from(v: T) -> Self {
CameraIndex::String(v.as_ref().to_string())
}
}
// impl<T> From<T> for CameraIndex
// where
// T: ValidString,
// {
// fn from(v: T) -> Self {
// CameraIndex::String(v.as_ref().to_string())
// }
// }
/// Converts a MJPEG stream of [u8] into a Vec<u8> of RGB888. (R,G,B,R,G,B,...)
/// # Errors