mirror of
https://github.com/l1npengtul/nokhwa.git
synced 2026-07-04 02:27:26 +00:00
+26
-25
@@ -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
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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).
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
BIN
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() })
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 => {
|
||||
|
||||
@@ -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")))]
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user