Merge branch 'senpai' into 0.9.2

This commit is contained in:
l1npengtul (Lua)
2022-10-13 00:56:13 +09:00
committed by GitHub
57 changed files with 7680 additions and 12159 deletions
Vendored
BIN
View File
Binary file not shown.
+13
View File
@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: [l1npengtul]
# patreon: # Replace with a single Patreon username
# open_collective: # Replace with a single Open Collective username
# ko_fi: # Replace with a single Ko-fi username
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
# liberapay: # Replace with a single Liberapay username
# issuehunt: # Replace with a single IssueHunt username
# otechie: # Replace with a single Otechie username
# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+3 -1
View File
@@ -9,4 +9,6 @@ nokhwa.iml
/pkg
target
target
.DS_STORE
+19
View File
@@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clippy Main" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="clippy --features &quot;output-wgpu, input-avfoundation&quot;" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="STABLE" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="true" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
+58 -49
View File
@@ -1,69 +1,70 @@
[package]
name = "nokhwa"
version = "0.9.4"
version = "0.10.0"
authors = ["l1npengtul <l1npengtul@protonmail.com>"]
edition = "2018"
edition = "2021"
description = "A Simple-to-use, cross-platform Rust Webcam Capture Library"
keywords = ["camera", "webcam", "capture", "cross-platform"]
resolver = "2"
license = "Apache-2.0"
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", "nokhwa-core", "examples/*"]
exclude = ["examples/jscam"]
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["decoding", "flume"]
decoding = ["mozjpeg"]
default = ["flume", "decoding"]
serialize = ["serde", "nokhwa-core/serialize"]
decoding = ["nokhwa-core/mjpeg"]
input-v4l = ["v4l", "v4l2-sys-mit"]
input-msmf = ["nokhwa-bindings-windows"]
input-avfoundation = ["nokhwa-bindings-macos"]
input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration"]
input-opencv = ["opencv", "opencv/clang-runtime"]
# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x
# input-uvc = ["uvc", "uvc/vendor", "usb_enumeration", "lazy_static"]
input-opencv = ["opencv", "opencv/rgb", "rgb"]
input-ipcam = ["input-opencv"]
input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex"]
input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen"]
output-wgpu = ["wgpu"]
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", "nokhwa-core/wgpu-types"]
output-wasm = ["input-jscam"]
output-threaded = ["parking_lot"]
small-wasm = ["wee_alloc"]
docs-only = ["input-uvc", "input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"]
output-threaded = []
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 = []
test-fail-warning = []
[dependencies]
thiserror = "1.0.26"
paste = "1.0.5"
thiserror = "1.0"
paste = "1.0"
[dependencies.flume]
version = "0.10.8"
[dependencies.nokhwa-core]
version = "0.1.0"
path = "nokhwa-core"
[dependencies.serde]
version = "1.0"
optional = true
[target.'cfg(not(target_family = "wasm"))'.dependencies.mozjpeg]
version = "0.8.24"
[dependencies.flume]
version = "0.10"
optional = true
[dependencies.image]
version = "^0.23"
version = "0.24"
default-features = false
[target.'cfg(target_os = "linux")'.dependencies.v4l]
version = "0.12.1"
[dependencies.v4l]
version = "0.13"
optional = true
[target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit]
version = "0.2.0"
optional = true
[dependencies.ouroboros]
version = "^0.13"
optional = true
[dependencies.uvc]
version = "0.2.0"
[dependencies.v4l2-sys-mit]
version = "0.2"
optional = true
[dependencies.usb_enumeration]
@@ -71,36 +72,41 @@ version = "0.1.2"
optional = true
[dependencies.wgpu]
version = "^0.11"
version = "0.13"
optional = true
[dependencies.opencv]
version = "0.60.0"
features = ["clang-runtime"]
version = "0.67"
optional = true
[dependencies.rgb]
version = "0.8"
optional = true
[dependencies.nokhwa-bindings-windows]
version = "0.3.4"
version = "0.4"
path = "nokhwa-bindings-windows"
optional = true
[dependencies.nokhwa-bindings-macos]
version = "0.1.1"
version = "0.2"
path = "nokhwa-bindings-macos"
optional = true
[dependencies.gstreamer]
version = "0.17.0"
version = "0.18"
optional = true
[dependencies.gstreamer-app]
version = "0.17.0"
version = "0.18"
optional = true
[dependencies.gstreamer-video]
version = "0.17.0"
version = "0.18"
optional = true
[dependencies.glib]
version = "0.14.0"
version = "0.15"
optional = true
[dependencies.regex]
@@ -108,8 +114,7 @@ version = "1.4.6"
optional = true
[dependencies.web-sys]
version = "^0.3"
# why
version = "0.3"
features = [
"console",
"CanvasRenderingContext2d",
@@ -129,23 +134,27 @@ features = [
optional = true
[dependencies.js-sys]
version = "^0.3"
version = "0.3"
optional = true
[dependencies.wasm-bindgen]
version = "^0.2"
version = "0.2"
optional = true
[dependencies.wasm-bindgen-futures]
version = "^0.4"
version = "0.4"
optional = true
[dependencies.wee_alloc]
version = "0.4.5"
[dependencies.wasm-rs-async-executor]
version = "0.9"
optional = true
[dependencies.parking_lot]
version = "^0.11"
version = "0.12"
optional = true
[dependencies.lazy_static]
version = "1.4"
optional = true
[profile.release]
Vendored
-127
View File
@@ -1,127 +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 {
sh 'rustup update stable'
sh 'cargo fmt --all -- --check'
}
}
stage('Build, Clippy') {
parallel {
stage('V4L2') {
agent {
node {
label 'ci_linux'
}
}
steps {
sh 'rustup update stable'
sh 'cargo clippy --features "input-v4l, output-wgpu, test-fail-warning"'
}
}
stage('Media Foundation') {
agent {
node {
label 'ci_windows'
}
}
steps {
pwsh(script: 'rustup update stable', returnStatus: true)
pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true, returnStdout: true)
}
}
stage('AVFoundation') {
steps {
sh 'echo TODO'
}
}
stage('libUVC') {
agent {
node {
label 'ci_linux'
}
}
steps {
sh 'rustup update stable'
sh 'cargo clippy --features "input-uvc, output-wgpu, test-fail-warning"'
}
}
stage('OpenCV IPCamera') {
agent {
node {
label 'ci_linux'
}
}
steps {
sh 'rustup update stable'
sh 'cargo +nightly clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"'
}
}
stage('GStreamer') {
agent {
node {
label 'ci_linux'
}
}
steps {
sh 'rustup update nightly'
sh 'cargo clippy --features "input-gst, output-wgpu, test-fail-warning"'
}
}
stage('JSCamera/WASM') {
steps {
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 {
sh 'rustup update nightly'
sh 'cargo +nightly doc --features "docs-only, docs-nolink, docs-features, test-fail-warning" --no-deps --release'
}
}
}
}
+24 -17
View File
@@ -1,3 +1,4 @@
[![cargo version](https://img.shields.io/crates/v/nokhwa.svg)](https://crates.io/crates/nokhwa) [![docs.rs version](https://img.shields.io/docsrs/nokhwa)](https://docs.rs/nokhwa/latest/nokhwa/)
# nokhwa
Nokhwa(녹화): Korean word meaning "to record".
@@ -5,11 +6,11 @@ A Simple-to-use, cross-platform Rust Webcam Capture Library
## Using nokhwa
Nokhwa can be added to your crate by adding it to your `Cargo.toml`:
```.ignore
```toml
[dependencies.nokhwa]
// TODO: replace the "*" with the latest version of `nokhwa`
# TODO: replace the "*" with the latest version of `nokhwa`
version = "*"
// TODO: add some features
# TODO: add some features
features = [""]
```
@@ -17,7 +18,7 @@ Most likely, you will only use functionality provided by the `Camera` struct. If
## Example
```.ignore
```rust
// set up the Camera
let mut camera = Camera::new(
0, // index
@@ -27,7 +28,7 @@ let mut camera = Camera::new(
// open stream
camera.open_stream().unwrap();
loop {
let frame = camera.get_frame().unwrap();
let frame = camera.frame().unwrap();
println!("{}, {}", frame.width(), frame.height());
}
```
@@ -41,16 +42,16 @@ The table below lists current Nokhwa API support.
- The `Query-Device` column signifies reading device capabilities
- The `Platform` column signifies what Platform this is availible on.
| Backend | Input | Query | Query-Device | Platform |
|-------------------------------------|--------------------|-------------------|--------------------|---------------------|
| Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux |
| MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows |
| AVFoundation(`input-avfoundatuin`)^^| ✅ | ✅ | ✅ | Mac |
| libuvc(`input-uvc`)^^^ | ❌ | ✅ | ❌ | Linux, Windows, Mac |
| OpenCV(`input-opencv`)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac |
| IPCamera(`input-ipcam`/OpenCV)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac |
| GStreamer(`input-gst`) | ✅ | ✅ | ✅ | Linux, Windows, Mac |
| JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) |
| Backend | Input | Query | Query-Device | Platform |
|----------------------------------------|--------------------|-------------------|--------------------|---------------------|
| Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux |
| MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows |
| AVFoundation(`input-avfoundation`)^^ | ✅ | ✅ | ✅ | Mac |
| libuvc(`input-uvc`) (**DEPRECATED**)^^^| ❌ | ✅ | ❌ | Linux, Windows, Mac |
| OpenCV(`input-opencv`)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac |
| IPCamera(`input-ipcam`/OpenCV)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac |
| GStreamer(`input-gst`)(**DEPRECATED**) | ✅ | ✅ | ✅ | Linux, Windows, Mac |
| JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) |
✅: Working, 🔮 : Experimental, ❌ : Not Supported, 🚧: Planned/WIP
@@ -63,14 +64,16 @@ The table below lists current Nokhwa API support.
The default feature includes nothing. Anything starting with `input-*` is a feature that enables the specific backend.
As a general rule of thumb, you would want to keep at least `input-uvc` or other backend that has querying enabled so you can get device information from `nokhwa`.
### ***NOTE: It is safe to have `input-v4l`, `input-msmf`, and `input-avfoundation` all enabled at the same time since it is platform gated as well!***
`input-*` features:
- `input-v4l`: Enables the `Video4Linux` backend. (linux)
- `input-msmf`: Enables the `MediaFoundation` backennd. (Windows 7 or newer)
- `input-avfoundation`: Enables the `AVFoundation` backend. (MacOSX 10.7)
- `input-uvc`: Enables the `libuvc` backend. (cross-platform, libuvc statically-linked)
- `input-uvc`: **[DEPRECATED]** Enables the `libuvc` backend. (cross-platform, libuvc statically-linked)
- `input-opencv`: Enables the `opencv` backend. (cross-platform)
- `input-ipcam`: Enables the use of IP Cameras, please see the `NetworkCamera` struct. Note that this relies on `opencv`, so it will automatically enable the `input-opencv` feature.
- `input-gst`: Enables the `gstreamer` backend. (cross-platform)
- `input-gst`: **[DEPRECATED]** Enables the `gstreamer` backend.
- `input-jscam`: Enables the use of the `JSCamera` struct, which uses browser APIs. (Web)
Conversely, anything that starts with `output-*` controls a feature that controls the output of something (usually a frame from the camera)
@@ -107,3 +110,7 @@ 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.
## Sponsors
- $5/mo sponsors:
- [remifluff](https://github.com/remifluff)
+1 -2
View File
@@ -10,14 +10,13 @@ edition = "2018"
default = ["nokhwa/default"]
input-msmf = ["nokhwa/input-msmf"]
input-v4l = ["nokhwa/input-v4l"]
input-uvc = ["nokhwa/input-uvc"]
input-opencv = ["nokhwa/input-opencv"]
input-gst = ["nokhwa/input-gst"]
input-avfoundation = ["nokhwa/input-avfoundation"]
[dependencies]
clap = "2.33.3"
glium = "0.30.0"
glium = "0.31.0"
glutin = "0.27.0"
flume = "0.10.9"
+65 -60
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -21,8 +21,13 @@ use glium::{
implement_vertex, index::PrimitiveType, program, texture::RawImage2d, uniform, Display,
IndexBuffer, Surface, Texture2d, VertexBuffer,
};
use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder};
use nokhwa::{nokhwa_initialize, query_devices, Camera, CaptureAPIBackend, FrameFormat};
use glutin::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
ContextBuilder,
};
use nokhwa::{nokhwa_initialize, query, ApiBackend, Camera, FrameFormat};
use std::time::Instant;
#[derive(Copy, Clone)]
@@ -111,33 +116,33 @@ fn main() {
// Query example
if matches.is_present("query") {
let backend_value = matches.value_of("query").unwrap();
let mut use_backend = CaptureAPIBackend::Auto;
let mut use_backend = ApiBackend::Auto;
// AUTO
if backend_value == "AUTO" {
use_backend = CaptureAPIBackend::Auto;
use_backend = ApiBackend::Auto;
} else if backend_value == "UVC" {
use_backend = CaptureAPIBackend::UniversalVideoClass;
use_backend = ApiBackend::UniversalVideoClass;
} else if backend_value == "GST" {
use_backend = CaptureAPIBackend::GStreamer;
use_backend = ApiBackend::GStreamer;
} else if backend_value == "V4L" {
use_backend = CaptureAPIBackend::Video4Linux;
use_backend = ApiBackend::Video4Linux;
} else if backend_value == "MSMF" {
use_backend = CaptureAPIBackend::MediaFoundation;
use_backend = ApiBackend::MediaFoundation;
} else if backend_value == "AVF" {
nokhwa_initialize(|x| {
println!("{}", x);
});
use_backend = CaptureAPIBackend::AVFoundation;
use_backend = ApiBackend::AVFoundation;
}
match query_devices(use_backend) {
match query(use_backend) {
Ok(devs) => {
for (idx, camera) in devs.iter().enumerate() {
println!("Device at index {}: {}", idx, camera)
}
}
Err(why) => {
println!("Failed to query: {}", why.to_string())
println!("Failed to query: {why}")
}
}
}
@@ -145,13 +150,13 @@ fn main() {
if matches.is_present("capture") {
let backend_value = {
match matches.value_of("capture-backend").unwrap() {
"UVC" => CaptureAPIBackend::UniversalVideoClass,
"GST" => CaptureAPIBackend::GStreamer,
"V4L" => CaptureAPIBackend::Video4Linux,
"OPENCV" => CaptureAPIBackend::OpenCv,
"MSMF" => CaptureAPIBackend::MediaFoundation,
"AVF" => CaptureAPIBackend::AVFoundation,
_ => CaptureAPIBackend::Auto,
"UVC" => ApiBackend::UniversalVideoClass,
"GST" => ApiBackend::GStreamer,
"V4L" => ApiBackend::Video4Linux,
"OPENCV" => ApiBackend::OpenCv,
"MSMF" => ApiBackend::MediaFoundation,
"AVF" => ApiBackend::AVFoundation,
_ => ApiBackend::Auto,
}
};
let width = matches
@@ -206,13 +211,13 @@ fn main() {
}
}
Err(why) => {
println!("Failed to get compatible resolution/FPS list for FrameFormat {}: {}", ff, why.to_string())
println!("Failed to get compatible resolution/FPS list for FrameFormat {ff}: {why}")
}
}
}
}
Err(why) => {
println!("Failed to get compatible FourCC: {}", why.to_string())
println!("Failed to get compatible FourCC: {why}")
}
}
}
@@ -226,7 +231,7 @@ fn main() {
}
}
Err(why) => {
println!("Failed to get camera controls: {}", why.to_string())
println!("Failed to get camera controls: {why}")
}
}
}
@@ -237,7 +242,7 @@ fn main() {
let supported = match camera.camera_controls_string() {
Ok(cc) => cc,
Err(why) => {
println!("Failed to get camera controls: {}", why.to_string());
println!("Failed to get camera controls: {why}");
return;
}
};
@@ -330,7 +335,7 @@ fn main() {
v_tex_coords = tex_coords;
}
",
outputs_srgb: true,
fragment: "
#version 140
uniform sampler2D tex;
@@ -347,49 +352,49 @@ fn main() {
// run the event loop
gl_event_loop.run(move |event, _window, ctrl| {
let before_capture = Instant::now();
let frame = recv.recv().unwrap();
let after_capture = Instant::now();
*ctrl = match event {
Event::MainEventsCleared => {
let instant = Instant::now();
let frame = recv.recv().unwrap();
let capture_elapsed = instant.elapsed().as_millis();
let width = &frame.width();
let height = &frame.height();
let frame_size = (frame.width(), frame.height());
let raw_data = RawImage2d::from_raw_rgb(frame.into_raw(), (*width, *height));
let gl_texture = Texture2d::new(&gl_display, raw_data).unwrap();
let raw_data = RawImage2d::from_raw_rgb(frame.into_raw(), frame_size);
let gl_texture = Texture2d::new(&gl_display, raw_data).unwrap();
let uniforms = uniform! {
matrix: [
[1.0, 0.0, 0.0, 0.0],
[0.0, -1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0f32]
],
tex: &gl_texture
};
let uniforms = uniform! {
matrix: [
[1.0, 0.0, 0.0, 0.0],
[0.0, -1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0f32]
],
tex: &gl_texture
};
let mut target = gl_display.draw();
target.clear_color(0.0, 0.0, 0.0, 0.0);
target
.draw(
&vert_buffer,
&idx_buf,
&program,
&uniforms,
&Default::default(),
)
.unwrap();
target.finish().unwrap();
let mut target = gl_display.draw();
target.clear_color(0.0, 0.0, 0.0, 0.0);
target
.draw(
&vert_buffer,
&idx_buf,
&program,
&uniforms,
&Default::default(),
)
.unwrap();
target.finish().unwrap();
if let glutin::event::Event::WindowEvent { event, .. } = event {
if event == glutin::event::WindowEvent::CloseRequested {
*ctrl = glutin::event_loop::ControlFlow::Exit;
println!("Took {capture_elapsed}ms to capture",);
ControlFlow::Poll
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => ControlFlow::Exit,
_ => ControlFlow::Poll,
}
println!(
"Took {}ms to capture",
after_capture.duration_since(before_capture).as_millis()
)
})
}
// dont
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
# 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.
+1 -1
View File
@@ -1,5 +1,5 @@
<!--
~ Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
~ 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.
+1 -1
View File
@@ -9,7 +9,7 @@ edition = "2021"
[dependencies.image]
version = "0.23.14"
no-default-features = true
default-features = false
[dependencies.nokhwa]
path = "../../../nokhwa"
+2 -2
View File
@@ -1,9 +1,9 @@
use nokhwa::{Camera, CameraFormat, KnownCameraControls};
use nokhwa::{Camera, CameraFormat, KnownCameraControl};
fn main() {
let mut camera = Camera::new(0, None).unwrap();
let known = camera.camera_controls_known_camera_controls().unwrap();
let mut control = *known.get(&KnownCameraControls::Gamma).unwrap();
let mut control = *known.get(&KnownCameraControl::Gamma).unwrap();
control.set_value(101).unwrap();
camera.set_camera_control(control).unwrap();
}
+1 -1
View File
@@ -9,7 +9,7 @@ edition = "2018"
[dependencies.image]
version = "0.23.14"
no-default-features = true
default-features = false
[dependencies.nokhwa]
path = "../../../nokhwa"
+3 -4
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -15,11 +15,10 @@
*/
use image::{ImageBuffer, Rgb};
use nokhwa::{query_devices, CaptureAPIBackend, ThreadedCamera};
use std::time::Duration;
use nokhwa::{query, ApiBackend, ThreadedCamera};
fn main() {
let cameras = query_devices(CaptureAPIBackend::Auto).unwrap();
let cameras = query(ApiBackend::Auto).unwrap();
cameras.iter().for_each(|cam| println!("{:?}", cam));
let mut threaded = ThreadedCamera::new(0, None).unwrap();
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
# 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.
+10 -10
View File
@@ -1,6 +1,6 @@
[package]
name = "nokhwa-bindings-macos"
version = "0.1.1"
version = "0.2.0"
edition = "2018"
authors = ["l1npengtul"]
license = "Apache-2.0"
@@ -10,13 +10,13 @@ keywords = ["avfoundation", "macos", "capture", "webcam"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1.0.26"
flume = "0.10.9"
flume = "0.10"
core-media-sys = "0.1"
cocoa-foundation = "0.1"
objc = { version = "0.2", features = ["exception"] }
block = "0.1"
once_cell = "1.14"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
core-media-sys = "0.1.2"
cocoa-foundation = "0.1.0"
objc = { version = "0.2.7", features = ["exception"] }
block = "0.1.6"
dashmap = "4.0.2"
lazy_static = "1.4.0"
[dependencies.nokhwa-core]
version = "0.1"
path = "../nokhwa-core"
+2 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -18,6 +18,7 @@
fn main() {
println!("cargo:rustc-link-lib=framework=CoreMedia");
println!("cargo:rustc-link-lib=framework=AVFoundation");
println!("cargo:rustc-link-lib=framework=CoreVideo");
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -1,6 +1,6 @@
[package]
name = "nokhwa-bindings-windows"
version = "0.3.4"
version = "0.4.0"
authors = ["l1npengtul"]
edition = "2021"
license = "Apache-2.0"
@@ -11,11 +11,12 @@ keywords = ["media-foundation", "windows", "capture", "webcam"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "^1.0"
[target.'cfg(all(target_os = "windows", windows))'.dependencies]
lazy_static = "1.4.0"
[target.'cfg(all(target_os = "windows", windows))'.dependencies.windows]
version = "^0.28"
features = ["alloc", "Win32_Media_MediaFoundation", "Win32_System_Com", "Win32_Foundation", "Win32_Media_DirectShow","Win32_Media_KernelStreaming"]
[dependencies.nokhwa-core]
version = "0.1"
path = "../nokhwa-core"
[dependencies.windows]
version = "0.39.0"
features = ["Win32_Media_MediaFoundation", "Win32_System_Com", "Win32_Foundation", "Win32_Media_DirectShow", "Win32_Media", "Win32", "Win32_Media_KernelStreaming"]
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
[package]
name = "nokhwa-core"
version = "0.1.0"
authors = ["l1npengtul <l1npengtul@protonmail.com>"]
edition = "2021"
description = "Core type definitions for nokhwa"
keywords = ["camera", "webcam", "capture", "cross-platform"]
license = "Apache-2.0"
repository = "https://github.com/l1npengtul/nokhwa"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = []
serialize = ["serde"]
wgpu-types = ["wgpu"]
mjpeg = ["mozjpeg"]
docs-features = []
[dependencies]
thiserror = "1.0"
bytes = "1.2"
[dependencies.image]
version = "0.24"
default-features = false
[dependencies.serde]
version = "1.0"
features = ["derive"]
optional = true
[dependencies.wgpu]
version = "0.13"
optional = true
[dependencies.mozjpeg]
version = "0.9"
optional = true
+184
View File
@@ -0,0 +1,184 @@
/*
* 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::{
error::NokhwaError,
pixel_format::FormatDecoder,
types::{FrameFormat, Resolution},
};
use bytes::Bytes;
use image::ImageBuffer;
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
/// A buffer returned by a camera to accomodate custom decoding.
/// Contains information of Resolution, the buffer's [`FrameFormat`], and the buffer.
#[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Eq)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct Buffer {
resolution: Resolution,
buffer: Bytes,
source_frame_format: FrameFormat,
}
impl Buffer {
/// Creates a new buffer with a [`&[u8]`].
#[must_use]
pub fn new(res: Resolution, buf: &[u8], source_frame_format: FrameFormat) -> Self {
Self {
resolution: res,
buffer: Bytes::copy_from_slice(buf),
source_frame_format,
}
}
/// Get the [`Resolution`] of this buffer.
#[must_use]
pub fn resolution(&self) -> Resolution {
self.resolution
}
/// Get the data of this buffer.
#[must_use]
pub fn buffer(&self) -> &[u8] {
&self.buffer
}
/// Get the [`FrameFormat`] of this buffer.
#[must_use]
pub fn source_frame_format(&self) -> FrameFormat {
self.source_frame_format
}
/// Decodes a image with allocation using the provided [`FormatDecoder`].
/// # Errors
/// Will error when the decoding fails.
pub fn decode_image<F: FormatDecoder>(
&self,
) -> Result<ImageBuffer<F::Output, Vec<u8>>, NokhwaError> {
let new_data = F::write_output(self.source_frame_format, &self.buffer)?;
let image =
ImageBuffer::from_raw(self.resolution.width_x, self.resolution.height_y, new_data)
.ok_or(NokhwaError::ProcessFrameError {
src: self.source_frame_format,
destination: stringify!(F).to_string(),
error: "Failed to create buffer".to_string(),
})?;
Ok(image)
}
/// Decodes a image with allocation using the provided [`FormatDecoder`] into a `buffer`.
/// # Errors
/// Will error when the decoding fails, or the provided buffer is too small.
pub fn decode_image_to_buffer<F: FormatDecoder>(
&self,
buffer: &mut [u8],
) -> Result<(), NokhwaError> {
F::write_output_buffer(self.source_frame_format, &self.buffer, buffer)
}
/// Decodes a image with allocation using the provided [`FormatDecoder`] into a [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
///
/// Note that this does a clone when creating the buffer, to decouple the lifetime of the internal data to the temporary Buffer. If you want to avoid this, please see [`decode_as_opencv_mat`](Self::decode_as_opencv_mat).
/// # Errors
/// Will error when the decoding fails, or `OpenCV` failed to create/copy the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
/// # Safety
/// This function uses `unsafe` in order to create the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html). Please see [`Mat::new_rows_cols_with_data`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html#method.new_rows_cols_with_data) for more.
#[cfg(feature = "input-opencv")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-opencv")))]
pub fn decode_opencv_mat<F: FormatDecoder>(&self) -> Result<Mat, NokhwaError> {
let mut buffer = F::write_output(self.source_frame_format, &self.buffer)?;
let array_type = match F::Output::CHANNEL_COUNT {
1 => CV_8UC1,
2 => CV_8UC2,
3 => CV_8UC3,
4 => CV_8UC4,
_ => {
return Err(NokhwaError::ProcessFrameError {
src: self.source_frame_format,
destination: "OpenCV Mat".to_string(),
error: "Invalid Decoder FormatDecoder Channel Count".to_string(),
})
}
};
unsafe {
// TODO: Look into removing this unnecessary copy.
let mat1 = Mat::new_rows_cols_with_data(
self.resolution.height_y as i32,
self.resolution.width_x as i32,
array_type,
&mut buffer as *mut std::os::raw::c_void,
Mat_AUTO_STEP,
)
.map_err(|why| NokhwaError::ProcessFrameError {
src: self.source_frame_format,
destination: "OpenCV Mat".to_string(),
error: why.to_string(),
})?;
Ok(mat1.clone().map_err(|why| NokhwaError::ProcessFrameError {
src: self.source_frame_format,
destination: "OpenCV Mat".to_string(),
error: why.to_string(),
})?)
}
}
/// Decodes a image with allocation using the provided [`FormatDecoder`] into a [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
/// # Errors
/// Will error when the decoding fails, or `OpenCV` failed to create/copy the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
/// # Safety
/// This function uses `unsafe` in order to create the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html). Please see [`Mat::new_rows_cols_with_data`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html#method.new_rows_cols_with_data) for more.
///
/// THIS WILL CAUSE UNSOUNDNESS IF YOU USE THE MAT WHILE THE BUFFER ITSELF IS DROPPED.
#[cfg(feature = "input-opencv")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-opencv")))]
pub unsafe fn decode_as_opencv_mat<F: FormatDecoder>(&mut self) -> Result<Mat, NokhwaError> {
let resolution = self.resolution();
let frame_format = self.source_frame_format();
let array_type = match F::Output::CHANNEL_COUNT {
1 => CV_8UC1,
2 => CV_8UC2,
3 => CV_8UC3,
4 => CV_8UC4,
_ => {
return Err(NokhwaError::ProcessFrameError {
src: frame_format,
destination: "OpenCV Mat".to_string(),
error: "Invalid Decoder FormatDecoder Channel Count".to_string(),
})
}
};
unsafe {
Ok(Mat::new_rows_cols_with_data(
resolution.height_y as i32,
resolution.width_x as i32,
array_type,
self.buffer_mut() as *mut std::os::raw::c_void,
Mat_AUTO_STEP,
)
.map_err(|why| NokhwaError::ProcessFrameError {
src: frame_format,
destination: "OpenCV Mat".to_string(),
error: why.to_string(),
})?)
}
}
}
+60
View File
@@ -0,0 +1,60 @@
/*
* 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::types::{ApiBackend, FrameFormat};
use thiserror::Error;
/// All errors in `nokhwa`.
#[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: ApiBackend, error: String },
#[error("Could not shutdown {backend}: {error}")]
ShutdownError { backend: ApiBackend, error: String },
#[error("Error: {0}")]
GeneralError(String),
#[error("Could not generate required structure {structure}: {error}")]
StructureError { structure: String, error: String },
#[error("Could not open device {0}: {1}")]
OpenDeviceError(String, String),
#[error("Could not get device property {property}: {error}")]
GetPropertyError { property: String, error: String },
#[error("Could not set device property {property} with value {value}: {error}")]
SetPropertyError {
property: String,
value: String,
error: String,
},
#[error("Could not open device stream: {0}")]
OpenStreamError(String),
#[error("Could not capture frame: {0}")]
ReadFrameError(String),
#[error("Could not process frame {src} to {destination}: {error}")]
ProcessFrameError {
src: FrameFormat,
destination: String,
error: String,
},
#[error("Could not stop stream: {0}")]
StreamShutdownError(String),
#[error("This operation is not supported by backend {0}.")]
UnsupportedOperationError(ApiBackend),
#[error("This operation is not implemented yet: {0}")]
NotImplementedError(String),
}
+10
View File
@@ -0,0 +1,10 @@
//! Core type definitions for `nokhwa`
extern crate core;
extern crate core;
pub mod buffer;
pub mod error;
pub mod pixel_format;
pub mod traits;
pub mod types;
+306
View File
@@ -0,0 +1,306 @@
/*
* 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::error::NokhwaError;
use crate::types::{
buf_mjpeg_to_rgb, buf_yuyv422_to_rgb, mjpeg_to_rgb, yuyv422_to_rgb, FrameFormat,
};
use image::{Luma, LumaA, Pixel, Rgb, Rgba};
use std::fmt::Debug;
/// Trait that has methods to convert raw data from the webcam to a proper raw image.
pub trait FormatDecoder: Copy + Clone + Debug + Default + Sized + Send + Sync {
type Output: Pixel<Subpixel = u8>;
const FORMATS: &'static [FrameFormat];
/// Allocates and returns a `Vec`
/// # Errors
/// If the data is malformed, or the source [`FrameFormat`] is incompatible, this will error.
fn write_output(fcc: FrameFormat, data: &[u8]) -> Result<Vec<u8>, NokhwaError>;
/// Writes to a user provided buffer.
/// # Errors
/// If the data is malformed, the source [`FrameFormat`] is incompatible, or the user-alloted buffer is not large enough, this will error.
fn write_output_buffer(
fcc: FrameFormat,
data: &[u8],
dest: &mut [u8],
) -> Result<(), NokhwaError>;
}
/// A Zero-Size-Type that contains the definition to convert a given image stream to an RGB888 in the [`Buffer`](crate::buffer::Buffer)'s [`.to_image()`](crate::buffer::Buffer::to_image)
///
/// ```.ignore
/// use image::{ImageBuffer, Rgb};
/// let image: ImageBuffer<Rgb<u8>, Vec<u8>> = buffer.to_image::<RgbFormat>();
/// ```
#[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct RgbFormat;
impl FormatDecoder for RgbFormat {
type Output = Rgb<u8>;
const FORMATS: &'static [FrameFormat] = &[FrameFormat::MJPEG, FrameFormat::YUYV];
fn write_output(fcc: FrameFormat, data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
match fcc {
FrameFormat::MJPEG => mjpeg_to_rgb(data, false),
FrameFormat::YUYV => yuyv422_to_rgb(data, false),
FrameFormat::GRAY => Ok(data
.iter()
.flat_map(|x| {
let pxv = *x;
[pxv, pxv, pxv]
})
.collect()),
}
}
fn write_output_buffer(
fcc: FrameFormat,
data: &[u8],
dest: &mut [u8],
) -> Result<(), NokhwaError> {
match fcc {
FrameFormat::MJPEG => buf_mjpeg_to_rgb(data, dest, false),
FrameFormat::YUYV => buf_yuyv422_to_rgb(data, dest, false),
FrameFormat::GRAY => {
if dest.len() != data.len() * 3 {
return Err(NokhwaError::ProcessFrameError {
src: fcc,
destination: "Luma => RGB".to_string(),
error: "Bad buffer length".to_string(),
});
}
data.iter().enumerate().for_each(|(idx, pixel_value)| {
let index = idx * 3;
dest[index] = *pixel_value;
dest[index + 1] = *pixel_value;
dest[index + 2] = *pixel_value;
});
Ok(())
}
}
}
}
/// A Zero-Size-Type that contains the definition to convert a given image stream to an RGBA8888 in the [`Buffer`](crate::buffer::Buffer)'s [`.to_image()`](crate::buffer::Buffer::to_image)
///
/// ```.ignore
/// use image::{ImageBuffer, Rgba};
/// let image: ImageBuffer<Rgba<u8>, Vec<u8>> = buffer.to_image::<RgbAFormat>();
/// ```
#[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct RgbAFormat;
impl FormatDecoder for RgbAFormat {
type Output = Rgba<u8>;
const FORMATS: &'static [FrameFormat] = &[FrameFormat::MJPEG, FrameFormat::YUYV];
fn write_output(fcc: FrameFormat, data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
match fcc {
FrameFormat::MJPEG => mjpeg_to_rgb(data, true),
FrameFormat::YUYV => yuyv422_to_rgb(data, true),
FrameFormat::GRAY => Ok(data
.iter()
.flat_map(|x| {
let pxv = *x;
[pxv, pxv, pxv, 255]
})
.collect()),
}
}
fn write_output_buffer(
fcc: FrameFormat,
data: &[u8],
dest: &mut [u8],
) -> Result<(), NokhwaError> {
match fcc {
FrameFormat::MJPEG => buf_mjpeg_to_rgb(data, dest, true),
FrameFormat::YUYV => buf_yuyv422_to_rgb(data, dest, true),
FrameFormat::GRAY => {
if dest.len() != data.len() * 4 {
return Err(NokhwaError::ProcessFrameError {
src: fcc,
destination: "Luma => RGBA".to_string(),
error: "Bad buffer length".to_string(),
});
}
data.iter().enumerate().for_each(|(idx, pixel_value)| {
let index = idx * 4;
dest[index] = *pixel_value;
dest[index + 1] = *pixel_value;
dest[index + 2] = *pixel_value;
dest[index + 3] = 255;
});
Ok(())
}
}
}
}
/// A Zero-Size-Type that contains the definition to convert a given image stream to an Luma8(Grayscale 8-bit) in the [`Buffer`](crate::buffer::Buffer)'s [`.to_image()`](crate::buffer::Buffer::to_image)
///
/// ```.ignore
/// use image::{ImageBuffer, Luma};
/// let image: ImageBuffer<Luma<u8>, Vec<u8>> = buffer.to_image::<LumaFormat>();
/// ```
#[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct LumaFormat;
impl FormatDecoder for LumaFormat {
type Output = Luma<u8>;
const FORMATS: &'static [FrameFormat] =
&[FrameFormat::MJPEG, FrameFormat::YUYV, FrameFormat::GRAY];
#[allow(clippy::cast_possible_truncation)]
fn write_output(fcc: FrameFormat, data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
match fcc {
FrameFormat::MJPEG => Ok(mjpeg_to_rgb(data, false)?
.as_slice()
.chunks_exact(3)
.map(|x| {
let mut avg = 0;
x.iter().for_each(|v| avg += u16::from(*v));
(avg / 3) as u8
})
.collect()),
FrameFormat::YUYV => Ok(yuyv422_to_rgb(data, false)?
.as_slice()
.chunks_exact(3)
.map(|x| {
let mut avg = 0;
x.iter().for_each(|v| avg += u16::from(*v));
(avg / 3) as u8
})
.collect()),
FrameFormat::GRAY => Ok(data.to_vec()),
}
}
fn write_output_buffer(
fcc: FrameFormat,
data: &[u8],
dest: &mut [u8],
) -> Result<(), NokhwaError> {
match fcc {
FrameFormat::MJPEG => {
// FIXME: implement!
Err(NokhwaError::ProcessFrameError {
src: fcc,
destination: "Luma => RGB".to_string(),
error: "Conversion Error".to_string(),
})
}
FrameFormat::YUYV => Err(NokhwaError::ProcessFrameError {
src: fcc,
destination: "Luma => RGB".to_string(),
error: "Conversion Error".to_string(),
}),
FrameFormat::GRAY => {
data.iter().zip(dest.iter_mut()).for_each(|(pxv, d)| {
*d = *pxv;
});
Ok(())
}
}
}
}
/// A Zero-Size-Type that contains the definition to convert a given image stream to an LumaA8(Grayscale 8-bit with 8-bit alpha) in the [`Buffer`](crate::buffer::Buffer)'s [`.to_image()`](crate::buffer::Buffer::to_image)
///
/// ```.ignore
/// use image::{ImageBuffer, LumaA};
/// let image: ImageBuffer<LumaA<u8>, Vec<u8>> = buffer.to_image::<LumaAFormat>();
/// ```
#[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct LumaAFormat;
impl FormatDecoder for LumaAFormat {
type Output = LumaA<u8>;
const FORMATS: &'static [FrameFormat] =
&[FrameFormat::MJPEG, FrameFormat::YUYV, FrameFormat::GRAY];
#[allow(clippy::cast_possible_truncation)]
fn write_output(fcc: FrameFormat, data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
match fcc {
FrameFormat::MJPEG => Ok(mjpeg_to_rgb(data, false)?
.as_slice()
.chunks_exact(3)
.flat_map(|x| {
let mut avg = 0;
x.iter().for_each(|v| avg += u16::from(*v));
[(avg / 3) as u8, 255]
})
.collect()),
FrameFormat::YUYV => Ok(yuyv422_to_rgb(data, false)?
.as_slice()
.chunks_exact(3)
.flat_map(|x| {
let mut avg = 0;
x.iter().for_each(|v| avg += u16::from(*v));
[(avg / 3) as u8, 255]
})
.collect()),
FrameFormat::GRAY => Ok(data.iter().flat_map(|x| [*x, 255]).collect()),
}
}
fn write_output_buffer(
fcc: FrameFormat,
data: &[u8],
dest: &mut [u8],
) -> Result<(), NokhwaError> {
match fcc {
FrameFormat::MJPEG => {
// FIXME: implement!
Err(NokhwaError::ProcessFrameError {
src: fcc,
destination: "MJPEG => LumaA".to_string(),
error: "Conversion Error".to_string(),
})
}
FrameFormat::YUYV => Err(NokhwaError::ProcessFrameError {
src: fcc,
destination: "YUYV => LumaA".to_string(),
error: "Conversion Error".to_string(),
}),
FrameFormat::GRAY => {
if dest.len() != data.len() * 2 {
return Err(NokhwaError::ProcessFrameError {
src: fcc,
destination: "GRAY8 => LumaA".to_string(),
error: "Conversion Error".to_string(),
});
}
data.iter()
.zip(dest.chunks_exact_mut(2))
.enumerate()
.for_each(|(idx, (pxv, d))| {
let index = idx * 2;
d[index] = *pxv;
d[index + 1] = 255;
});
Ok(())
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -14,15 +14,17 @@
* limitations under the License.
*/
use crate::types::KnownCameraControlFlag;
use crate::{
buffer::Buffer,
error::NokhwaError,
utils::{CameraFormat, CameraInfo, FrameFormat, Resolution},
CameraControl, CaptureAPIBackend, KnownCameraControls,
types::{
ApiBackend, CameraControl, CameraFormat, CameraInfo, ControlValueSetter, FrameFormat,
KnownCameraControl, Resolution,
},
};
use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage};
use std::{any::Any, borrow::Cow, collections::HashMap};
#[cfg(feature = "output-wgpu")]
use std::{borrow::Cow, collections::HashMap};
#[cfg(feature = "wgpu-types")]
use wgpu::{
Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue,
Texture as WgpuTexture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat,
@@ -38,16 +40,23 @@ use wgpu::{
/// - 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 {
/// Returns the current backend used.
fn backend(&self) -> CaptureAPIBackend;
fn backend(&self) -> ApiBackend;
/// Gets the camera information such as Name and Index as a [`CameraInfo`].
fn camera_info(&self) -> &CameraInfo;
/// Gets the current [`CameraFormat`].
/// Forcefully refreshes the stored camera format, bringing it into sync with "reality" (current camera state)
/// # Errors
/// If the camera can not get its most recent [`CameraFormat`]. this will error.
fn refresh_camera_format(&mut self) -> Result<(), NokhwaError>;
/// Gets the current [`CameraFormat`]. This will force refresh to the current latest if it has changed.
fn camera_format(&self) -> CameraFormat;
/// 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,82 +69,81 @@ pub trait CaptureBackendTrait {
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError>;
/// Gets the compatible [`CameraFormat`] of the camera
/// # Errors
/// If it fails to get, this will error.
fn compatible_camera_formats(&mut self) -> Result<Vec<CameraFormat>, NokhwaError> {
let mut compatible_formats = vec![];
for fourcc in self.compatible_fourcc()? {
for (resolution, fps_list) in self.compatible_list_by_resolution(fourcc)? {
for fps in fps_list {
compatible_formats.push(CameraFormat::new(resolution, fourcc, fps));
}
}
}
Ok(compatible_formats)
}
/// 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`]).
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
fn resolution(&self) -> 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.
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError>;
/// Gets the current camera framerate (See: [`CameraFormat`]).
/// Gets the current camera framerate (See: [`CameraFormat`]). This will force refresh to the current latest if it has changed.
fn frame_rate(&self) -> u32;
/// 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`]).
/// 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) -> FrameFormat;
/// 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>;
/// Gets the current supported list of [`KnownCameraControls`]
/// # Errors
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError>;
/// Gets the value of [`KnownCameraControls`].
/// Gets the value of [`KnownCameraControl`].
/// # Errors
/// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, etc)
/// this will error.
fn camera_control(&self, control: KnownCameraControls) -> Result<CameraControl, NokhwaError>;
fn camera_control(&self, control: KnownCameraControl) -> Result<CameraControl, NokhwaError>;
/// Gets the current supported list of [`KnownCameraControl`]
/// # Errors
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError>;
/// Sets the control to `control` in the camera.
/// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way
/// then calling one of the methods to set the value: [`set_value()`](CameraControl::set_value()) or [`with_value()`](CameraControl::with_value()).
/// then calling [`set_value()`](CameraControl::set_value())
/// # Errors
/// If the `control` is not supported, the value is invalid (less than min, greater than max, not in step), or there was an error setting the control,
/// this will error.
fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError>;
/// Gets the current supported list of Controls as an `Any` from the backend.
/// The `Any`'s type is defined by the backend itself, please check each of the backend's documentation.
/// # Errors
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError>;
/// Sets the control to `control` in the camera.
/// The control's type is defined the backend itself. It may be a string, or more likely its a integer ID.
/// The backend itself has documentation of the proper input/return values, please check each of the backend's documentation.
/// # Errors
/// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, wrong Any type)
/// this will error.
fn raw_camera_control(&self, control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError>;
/// Sets the control to `control` in the camera.
/// The `control`/`value`'s type is defined the backend itself. It may be a string, or more likely its a integer ID/Value.
/// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way
/// then calling one of the methods to set the value: [`set_value()`](CameraControl::set_value()) or [`with_value()`](CameraControl::with_value()).
/// # Errors
/// If the `control` is not supported, the value is invalid (wrong Any type, backend refusal), or there was an error setting the control,
/// this will error.
fn set_raw_camera_control(
fn set_camera_control(
&mut self,
control: &dyn Any,
value: &dyn Any,
id: KnownCameraControl,
value: ControlValueSetter,
) -> Result<(), NokhwaError>;
/// Will open the camera stream with set parameters. This will be called internally if you try and call [`frame()`](CaptureBackendTrait::frame()) before you call [`open_stream()`](CaptureBackendTrait::open_stream()).
@@ -146,60 +154,36 @@ pub trait CaptureBackendTrait {
/// Checks if stream if open. If it is, it will return true.
fn is_stream_open(&self) -> bool;
/// Will get a frame from the camera as a Raw RGB image buffer. Depending on the backend, if you have not called [`open_stream()`](CaptureBackendTrait::open_stream()) before you called this,
/// Will get a frame from the camera as a [`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,
/// 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 **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 buffer with an alpha channel as well.
/// This assumes that you are decoding to RGB/RGBA for [`FrameFormat::MJPEG`] or [`FrameFormat::YUYV`] and Luma8/LumaA8 for [`FrameFormat::GRAY`]
#[must_use]
fn decoded_buffer_size(&self, alpha: bool) -> usize {
let cfmt = self.camera_format();
let resolution = cfmt.resolution();
let pxwidth = match cfmt.format() {
FrameFormat::MJPEG | FrameFormat::YUYV => 3,
FrameFormat::GRAY => 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.
/// # 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 write_frame_to_buffer(
&mut self,
buffer: &mut [u8],
convert_rgba: bool,
) -> Result<usize, NokhwaError> {
let resolution = self.resolution();
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 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());
Ok(frame.len())
}
#[cfg(feature = "output-wgpu")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-wgpu")))]
#[cfg(feature = "wgpu-types")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "wgpu-types")))]
/// 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.
@@ -209,9 +193,9 @@ pub trait CaptureBackendTrait {
queue: &WgpuQueue,
label: Option<&'a str>,
) -> Result<WgpuTexture, NokhwaError> {
use std::{convert::TryFrom, num::NonZeroU32};
let frame = self.frame()?;
let rgba_frame: RgbaImage = frame.convert();
use crate::pixel_format::RgbAFormat;
use std::num::NonZeroU32;
let frame = self.frame()?.decode_image::<RgbAFormat>()?;
let texture_size = Extent3d {
width: frame.width(),
@@ -229,12 +213,12 @@ pub trait CaptureBackendTrait {
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())),
};
@@ -246,7 +230,7 @@ pub trait CaptureBackendTrait {
origin: wgpu::Origin3d::ZERO,
aspect: TextureAspect::All,
},
&rgba_frame.to_vec(),
&frame,
ImageDataLayout {
offset: 0,
bytes_per_row: width_nonzero,
@@ -264,4 +248,13 @@ pub trait CaptureBackendTrait {
fn stop_stream(&mut self) -> Result<(), NokhwaError>;
}
impl<T> From<T> for Box<dyn CaptureBackendTrait>
where
T: CaptureBackendTrait + 'static,
{
fn from(capbackend: T) -> Self {
Box::new(capbackend)
}
}
pub trait VirtualBackendTrait {}
File diff suppressed because it is too large Load Diff
-373
View File
@@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
-105
View File
@@ -1,105 +0,0 @@
# nokhwa
Nokhwa(녹화): Korean word meaning "to record".
A Simple-to-use, cross-platform Rust Webcam Capture Library
## Using nokhwa
Nokhwa can be added to your crate by adding it to your `Cargo.toml`:
```.ignore
[dependencies.nokhwa]
// TODO: replace the "*" with the latest version of `nokhwa`
version = "*"
// TODO: add some features
features = [""]
```
Most likely, you will only use functionality provided by the `Camera` struct. If you need lower-level access, you may instead opt to use the raw capture backends found at `nokhwa::backends::capture::*`.
## Example
```.ignore
// set up the Camera
let mut camera = Camera::new(
0, // index
Some(CameraFormat::new_from(640, 480, FrameFormat::MJPEG, 30)), // format
)
.unwrap();
// open stream
camera.open_stream().unwrap();
loop {
let frame = camera.get_frame().unwrap();
println!("{}, {}", frame.width(), frame.height());
}
```
A command line app made with `nokhwa` can be found in the `examples` folder.
## API Support
The table below lists current Nokhwa API support.
- The `Backend` column signifies the backend.
- The `Input` column signifies reading frames from the camera
- The `Query` column signifies system device list support
- The `Query-Device` column signifies reading device capabilities
- The `Platform` column signifies what Platform this is availible on.
| Backend | Input | Query | Query-Device | Platform |
|-------------------------------------|--------------------|-------------------|--------------------|---------------------|
| Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux |
| MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows |
| AVFoundation(`input-avfoundatuin`)^^| ✅ | ✅ | ✅ | Mac |
| libuvc(`input-uvc`) | ✅ | ✅ | ✅ | Linux, Windows, Mac |
| OpenCV(`input-opencv`)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac |
| IPCamera(`input-ipcam`/OpenCV)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac |
| GStreamer(`input-gst`) | ✅ | ✅ | ✅ | Linux, Windows, Mac |
| JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) |
✅: Working, 🔮 : Experimental, ❌ : Not Supported, 🚧: Planned/WIP
^ = No CameraFormat setting support.
^^ = No FPS setting support.
## Feature
The default feature includes nothing. Anything starting with `input-*` is a feature that enables the specific backend.
As a general rule of thumb, you would want to keep at least `input-uvc` or other backend that has querying enabled so you can get device information from `nokhwa`.
`input-*` features:
- `input-v4l`: Enables the `Video4Linux` backend. (linux)
- `input-msmf`: Enables the `MediaFoundation` backennd. (Windows 7 or newer)
- `input-avfoundation`: Enables the `AVFoundation` backend. (MacOSX 10.7)
- `input-uvc`: Enables the `libuvc` backend. (cross-platform, libuvc statically-linked)
- `input-opencv`: Enables the `opencv` backend. (cross-platform)
- `input-ipcam`: Enables the use of IP Cameras, please see the `NetworkCamera` struct. Note that this relies on `opencv`, so it will automatically enable the `input-opencv` feature.
- `input-gst`: Enables the `gstreamer` backend. (cross-platform)
- `input-jscam`: Enables the use of the `JSCamera` struct, which uses browser APIs. (Web)
Conversely, anything that starts with `output-*` controls a feature that controls the output of something (usually a frame from the camera)
`output-*` features:
- `output-wgpu`: Enables the API to copy a frame directly into a `wgpu` texture.
- `output-wasm`: Generate WASM API binding specific functions.
- `output-threaded`: Enable the threaded/callback based camera.
Other features:
- `decoding`: Enables `mozjpeg` decoding. Enabled by default.
Please use the following command for `wasm-pack` in order to get a functional WASM binary:
```.ignore
wasm-pack build --release -- --features "input-jscam, output-wasm, test-fail-warning" --no-default-features
```
- `docs-only`: Documentation feature. Enabled for docs.rs builds.
- `docs-nolink`: Build documentation **without** linking to any libraries. Enabled for docs.rs builds.
- `test-fail-warning`: Fails on warning. Enabled in CI.
You many want to pick and choose to reduce bloat.
## Issues
If you are making an issue, please make sure that
- It has not been made yet
- Attach what you were doing, your environment, steps to reproduce, and backtrace.
Thank you!
## Contributing
Contributions are welcome!
- Please `rustfmt` all your code and adhere to the clippy lints (unless necessary not to do so)
- Please limit use of `unsafe`
- All contributions are under the MPL 2.0 license unless otherwise specified
## Minimum Service Rust Version
`nokhwa` may build on older versions of `rustc`, but there is no guarantee except for the latest stable rust.
-824
View File
@@ -1,824 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Requests Webcam permissions from the browser using [`MediaDevices::get_user_media()`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.MediaDevices.html#method.get_user_media) [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)
* # Errors
* This will error if there is no valid web context or the web API is not supported
* # JS-WASM
* In exported JS bindings, the name of the function is `requestPermissions`. It may throw an exception.
* @returns {any}
*/
export function requestPermissions(): any;
/**
* Queries Cameras using [`MediaDevices::enumerate_devices()`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.MediaDevices.html#method.enumerate_devices) [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices)
* # Errors
* This will error if there is no valid web context or the web API is not supported
* # JS-WASM
* This is exported as `queryCameras`. It may throw an exception.
* @returns {any}
*/
export function queryCameras(): any;
/**
* Queries the browser's supported constraints using [`navigator.mediaDevices.getSupportedConstraints()`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getSupportedConstraints)
* # Errors
* This will error if there is no valid web context or the web API is not supported
* # JS-WASM
* This is exported as `queryConstraints` and returns an array of strings.
* @returns {Array<any>}
*/
export function queryConstraints(): Array<any>;
/**
* The enum describing the possible constraints for video in the browser.
* - `DeviceID`: The ID of the device
* - `GroupID`: The ID of the group that the device is in
* - `AspectRatio`: The Aspect Ratio of the final stream
* - `FacingMode`: What direction the camera is facing. This is more common on mobile. See [`JSCameraFacingMode`]
* - `FrameRate`: The Frame Rate of the final stream
* - `Height`: The height of the final stream in pixels
* - `Width`: The width of the final stream in pixels
* - `ResizeMode`: Whether the client can crop and/or scale the stream to match the resolution (width, height). See [`JSCameraResizeMode`]
* See More: [`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints) [`Capabilities, constraints, and settings`](https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints)
* # JS-WASM
* This is exported as `CameraSupportedCapabilities`.
*/
export enum CameraSupportedCapabilities {
DeviceID,
GroupID,
AspectRatio,
FacingMode,
FrameRate,
Height,
Width,
ResizeMode,
}
/**
* The Facing Mode of the camera
* - Any: Make no particular choice.
* - Environment: The camera that shows the user's environment, such as the back camera of a smartphone
* - User: The camera that shows the user, such as the front camera of a smartphone
* - Left: The camera that shows the user but to their left, such as a camera that shows a user but to their left shoulder
* - Right: The camera that shows the user but to their right, such as a camera that shows a user but to their right shoulder
* See More: [`facingMode`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode)
* # JS-WASM
* This is exported as `CameraFacingMode`.
*/
export enum CameraFacingMode {
Any,
Environment,
User,
Left,
Right,
}
/**
* Whether the browser can crop and/or scale to match the requested resolution.
* - `Any`: Make no particular choice.
* - `None`: Do not crop and/or scale.
* - `CropAndScale`: Crop and/or scale to match the requested resolution.
* See More: [`resizeMode`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#resizemode)
* # JS-WASM
* This is exported as `CameraResizeMode`.
*/
export enum CameraResizeMode {
Any,
None,
CropAndScale,
}
/**
* Constraints to create a [`JSCamera`]
*
* If you want more options, see [`JSCameraConstraintsBuilder`]
* # JS-WASM
* This is exported as `CameraConstraints`.
*/
export class CameraConstraints {
free(): void;
/**
* Applies any modified constraints.
* # JS-WASM
* This is exported as `applyConstraints`.
*/
applyConstraints(): void;
/**
* Gets the internal aspect ratio.
* # JS-WASM
* This is exported as `get_AspectRatio`.
* @returns {number}
*/
AspectRatio: number;
/**
* Gets the internal aspect ratio exact.
* # JS-WASM
* This is exported as `get_AspectRatioExact`.
* @returns {boolean}
*/
AspectRatioExact: boolean;
/**
* Gets the internal device id.
* # JS-WASM
* This is exported as `get_DeviceId`.
* @returns {string}
*/
DeviceId: string;
/**
* Gets the internal device id exact.
* # JS-WASM
* This is exported as `get_DeviceIdExact`.
* @returns {boolean}
*/
DeviceIdExact: boolean;
/**
* Gets the internal [`JSCameraFacingMode`].
* # JS-WASM
* This is exported as `get_FacingMode`.
* @returns {number}
*/
FacingMode: number;
/**
* Gets the internal facing mode exact.
* # JS-WASM
* This is exported as `get_FacingModeExact`.
* @returns {boolean}
*/
FacingModeExact: boolean;
/**
* Gets the internal frame rate.
* # JS-WASM
* This is exported as `get_FrameRate`.
* @returns {number}
*/
FrameRate: number;
/**
* Gets the internal frame rate exact.
* # JS-WASM
* This is exported as `get_FrameRateExact`.
* @returns {boolean}
*/
FrameRateExact: boolean;
/**
* Gets the internal group id.
* # JS-WASM
* This is exported as `get_GroupId`.
* @returns {string}
*/
GroupId: string;
/**
* Gets the internal group id exact.
* # JS-WASM
* This is exported as `get_GroupIdExact`.
* @returns {boolean}
*/
GroupIdExact: boolean;
/**
* Gets the maximum aspect ratio.
* # JS-WASM
* This is exported as `get_MaxAspectRatio`.
* @returns {number | undefined}
*/
MaxAspectRatio: number;
/**
* Gets the maximum internal frame rate.
* # JS-WASM
* This is exported as `get_MaxFrameRate`.
* @returns {number | undefined}
*/
MaxFrameRate: number;
/**
* Gets the maximum [`Resolution`].
* # JS-WASM
* This is exported as `get_MaxResolution`.
* @returns {JSResolution | undefined}
*/
MaxResolution: JSResolution;
/**
* Gets the internal [`MediaStreamConstraints`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.MediaStreamConstraints.html)
* # JS-WASM
* This is exported as `get_MediaStreamConstraints`.
* @returns {any}
*/
readonly MediaStreamConstraints: any;
/**
* Gets the minimum aspect ratio of the [`JSCameraConstraints`].
* # JS-WASM
* This is exported as `get_MinAspectRatio`.
* @returns {number | undefined}
*/
MinAspectRatio: number;
/**
* Gets the minimum internal frame rate.
* # JS-WASM
* This is exported as `get_MinFrameRate`.
* @returns {number | undefined}
*/
MinFrameRate: number;
/**
* Gets the minimum [`Resolution`].
* # JS-WASM
* This is exported as `get_MinResolution`.
* @returns {JSResolution | undefined}
*/
MinResolution: JSResolution;
/**
* Gets the internal [`JSCameraResizeMode`].
* # JS-WASM
* This is exported as `get_ResizeMode`.
* @returns {number}
*/
ResizeMode: number;
/**
* Gets the internal resize mode exact.
* # JS-WASM
* This is exported as `get_ResizeModeExact`.
* @returns {boolean}
*/
ResizeModeExact: boolean;
/**
* Gets the internal [`Resolution`]
* # JS-WASM
* This is exported as `get_Resolution`.
* @returns {JSResolution}
*/
Resolution: JSResolution;
/**
* Gets the internal resolution exact.
* # JS-WASM
* This is exported as `get_ResolutionExact`.
* @returns {boolean}
*/
ResolutionExact: boolean;
}
/**
* A builder that builds a [`JSCameraConstraints`] that is used to construct a [`JSCamera`].
* See More: [`Constraints MDN`](https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints), [`Properties of Media Tracks MDN`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)
* # JS-WASM
* This is exported as `CameraConstraintsBuilder`.
*/
export class CameraConstraintsBuilder {
free(): void;
/**
* Constructs a default [`JSCameraConstraintsBuilder`].
* The constructed default [`JSCameraConstraintsBuilder`] has these settings:
* - 480x234 min, 640x360 ideal, 1920x1080 max
* - 10 FPS min, 15 FPS ideal, 30 FPS max
* - 1.0 aspect ratio min, 1.77777777778 aspect ratio ideal, 2.0 aspect ratio max
* - No `exact`s
* # JS-WASM
* This is exported as a constructor.
*/
constructor();
/**
* Sets the minimum resolution for the [`JSCameraConstraintsBuilder`].
*
* Sets [`width`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/width) and [`height`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/height).
* # JS-WASM
* This is exported as `set_MinResolution`.
* @param {JSResolution} min_resolution
* @returns {CameraConstraintsBuilder}
*/
MinResolution(min_resolution: JSResolution): CameraConstraintsBuilder;
/**
* Sets the preferred resolution for the [`JSCameraConstraintsBuilder`].
*
* Sets [`width`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/width) and [`height`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/height).
* # JS-WASM
* This is exported as `set_Resolution`.
* @param {JSResolution} new_resolution
* @returns {CameraConstraintsBuilder}
*/
Resolution(new_resolution: JSResolution): CameraConstraintsBuilder;
/**
* Sets the maximum resolution for the [`JSCameraConstraintsBuilder`].
*
* Sets [`width`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/width) and [`height`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/height).
* # JS-WASM
* This is exported as `set_MaxResolution`.
* @param {JSResolution} max_resolution
* @returns {CameraConstraintsBuilder}
*/
MaxResolution(max_resolution: JSResolution): CameraConstraintsBuilder;
/**
* Sets whether the resolution fields ([`width`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/width), [`height`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/height)/[`resolution`](crate::js_camera::JSCameraConstraintsBuilder::resolution))
* should use [`exact`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraints).
* Note that this will make the builder ignore [`min_resolution`](crate::js_camera::JSCameraConstraintsBuilder::min_resolution) and [`max_resolution`](crate::js_camera::JSCameraConstraintsBuilder::max_resolution).
* # JS-WASM
* This is exported as `set_ResolutionExact`.
* @param {boolean} value
* @returns {CameraConstraintsBuilder}
*/
ResolutionExact(value: boolean): CameraConstraintsBuilder;
/**
* Sets the minimum aspect ratio of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`aspectRatio`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/aspectRatio).
* # JS-WASM
* This is exported as `set_MinAspectRatio`.
* @param {number} ratio
* @returns {CameraConstraintsBuilder}
*/
MinAspectRatio(ratio: number): CameraConstraintsBuilder;
/**
* Sets the aspect ratio of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`aspectRatio`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/aspectRatio).
* # JS-WASM
* This is exported as `set_AspectRatio`.
* @param {number} ratio
* @returns {CameraConstraintsBuilder}
*/
AspectRatio(ratio: number): CameraConstraintsBuilder;
/**
* Sets the maximum aspect ratio of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`aspectRatio`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/aspectRatio).
* # JS-WASM
* This is exported as `set_MaxAspectRatio`.
* @param {number} ratio
* @returns {CameraConstraintsBuilder}
*/
MaxAspectRatio(ratio: number): CameraConstraintsBuilder;
/**
* Sets whether the [`aspect_ratio`](crate::js_camera::JSCameraConstraintsBuilder::aspect_ratio) field should use [`exact`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraints).
* Note that this will make the builder ignore [`min_aspect_ratio`](crate::js_camera::JSCameraConstraintsBuilder::min_aspect_ratio) and [`max_aspect_ratio`](crate::js_camera::JSCameraConstraintsBuilder::max_aspect_ratio).
* # JS-WASM
* This is exported as `set_AspectRatioExact`.
* @param {boolean} value
* @returns {CameraConstraintsBuilder}
*/
AspectRatioExact(value: boolean): CameraConstraintsBuilder;
/**
* Sets the facing mode of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`facingMode`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode).
* # JS-WASM
* This is exported as `set_FacingMode`.
* @param {number} facing_mode
* @returns {CameraConstraintsBuilder}
*/
FacingMode(facing_mode: number): CameraConstraintsBuilder;
/**
* Sets whether the [`facing_mode`](crate::js_camera::JSCameraConstraintsBuilder::facing_mode) field should use [`exact`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraints).
* # JS-WASM
* This is exported as `set_FacingModeExact`.
* @param {boolean} value
* @returns {CameraConstraintsBuilder}
*/
FacingModeExact(value: boolean): CameraConstraintsBuilder;
/**
* Sets the minimum frame rate of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`frameRate`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/frameRate).
* # JS-WASM
* This is exported as `set_MinFrameRate`.
* @param {number} fps
* @returns {CameraConstraintsBuilder}
*/
MinFrameRate(fps: number): CameraConstraintsBuilder;
/**
* Sets the frame rate of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`frameRate`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/frameRate).
* # JS-WASM
* This is exported as `set_FrameRate`.
* @param {number} fps
* @returns {CameraConstraintsBuilder}
*/
FrameRate(fps: number): CameraConstraintsBuilder;
/**
* Sets the maximum frame rate of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`frameRate`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/frameRate).
* # JS-WASM
* This is exported as `set_MaxFrameRate`.
* @param {number} fps
* @returns {CameraConstraintsBuilder}
*/
MaxFrameRate(fps: number): CameraConstraintsBuilder;
/**
* Sets whether the [`frame_rate`](crate::js_camera::JSCameraConstraintsBuilder::frame_rate) field should use [`exact`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraints).
* Note that this will make the builder ignore [`min_frame_rate`](crate::js_camera::JSCameraConstraintsBuilder::min_frame_rate) and [`max_frame_rate`](crate::js_camera::JSCameraConstraintsBuilder::max_frame_rate).
* # JS-WASM
* This is exported as `set_FrameRateExact`.
* @param {boolean} value
* @returns {CameraConstraintsBuilder}
*/
FrameRateExact(value: boolean): CameraConstraintsBuilder;
/**
* Sets the resize mode of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`resizeMode`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#resizemode).
* # JS-WASM
* This is exported as `set_ResizeMode`.
* @param {number} resize_mode
* @returns {CameraConstraintsBuilder}
*/
ResizeMode(resize_mode: number): CameraConstraintsBuilder;
/**
* Sets whether the [`resize_mode`](crate::js_camera::JSCameraConstraintsBuilder::resize_mode) field should use [`exact`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraints).
* # JS-WASM
* This is exported as `set_ResizeModeExact`.
* @param {boolean} value
* @returns {CameraConstraintsBuilder}
*/
ResizeModeExact(value: boolean): CameraConstraintsBuilder;
/**
* Sets the device ID of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`deviceId`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/deviceId).
* # JS-WASM
* This is exported as `set_DeviceId`.
* @param {string} id
* @returns {CameraConstraintsBuilder}
*/
DeviceId(id: string): CameraConstraintsBuilder;
/**
* Sets whether the [`device_id`](crate::js_camera::JSCameraConstraintsBuilder::device_id) field should use [`exact`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraints).
* # JS-WASM
* This is exported as `set_DeviceIdExact`.
* @param {boolean} value
* @returns {CameraConstraintsBuilder}
*/
DeviceIdExact(value: boolean): CameraConstraintsBuilder;
/**
* Sets the group ID of the resulting constraint for the [`JSCameraConstraintsBuilder`].
*
* Sets [`groupId`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/groupId).
* # JS-WASM
* This is exported as `set_GroupId`.
* @param {string} id
* @returns {CameraConstraintsBuilder}
*/
GroupId(id: string): CameraConstraintsBuilder;
/**
* Sets whether the [`group_id`](crate::js_camera::JSCameraConstraintsBuilder::group_id) field should use [`exact`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraints).
* # JS-WASM
* This is exported as `set_GroupIdExact`.
* @param {boolean} value
* @returns {CameraConstraintsBuilder}
*/
GroupIdExact(value: boolean): CameraConstraintsBuilder;
/**
* Builds the [`JSCameraConstraints`]. Wrapper for [`build`](crate::js_camera::JSCameraConstraintsBuilder::build)
*
* Fields that use exact are marked `exact`, otherwise are marked with `ideal`. If min-max are involved, they will use `min` and `max` accordingly.
* # JS-WASM
* This is exported as `buildCameraConstraints`.
* @returns {CameraConstraints}
*/
buildCameraConstraints(): CameraConstraints;
}
/**
* Information about a Camera e.g. its name.
* `description` amd `misc` may contain information that may differ from backend to backend. Refer to each backend for details.
* `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system.
* # JS-WASM
* This is exported as a `JSCameraInfo`.
*/
export class JSCameraInfo {
free(): void;
/**
* Create a new [`CameraInfo`].
* # JS-WASM
* This is exported as a constructor for [`CameraInfo`].
* @param {string} human_name
* @param {string} description
* @param {string} misc
* @param {number} index
*/
constructor(human_name: string, description: string, misc: string, index: number);
/**
* Get a reference to the device info's description.
* # JS-WASM
* This is exported as a `get_Description`.
* @returns {string}
*/
Description: string;
/**
* Get a reference to the device info's human readable name.
* # JS-WASM
* This is exported as a `get_HumanReadableName`.
* @returns {string}
*/
HumanReadableName: string;
/**
* Get a reference to the device info's index.
* # JS-WASM
* This is exported as a `get_Index`.
* @returns {number}
*/
Index: number;
/**
* Get a reference to the device info's misc.
* # JS-WASM
* This is exported as a `get_MiscString`.
* @returns {string}
*/
MiscString: string;
}
/**
* 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`
*/
export class JSResolution {
free(): void;
/**
* Create a new resolution from 2 image size coordinates.
* # JS-WASM
* This is exported as a constructor for [`Resolution`].
* @param {number} x
* @param {number} y
*/
constructor(x: number, y: number);
/**
* Get the x (width) of Resolution
* @returns {number}
*/
x(): number;
/**
* Get the y (height) of Resolution
* @returns {number}
*/
y(): number;
/**
* Get the height of Resolution
* # JS-WASM
* This is exported as `get_Height`.
* @returns {number}
*/
readonly Height: number;
/**
* Get the width of Resolution
* # JS-WASM
* This is exported as `get_Width`.
* @returns {number}
*/
readonly Width: number;
/**
* @returns {number}
*/
height_y: number;
/**
* @returns {number}
*/
width_x: number;
}
/**
* A wrapper around a [`MediaStream`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.MediaStream.html)
* # JS-WASM
* This is exported as `NokhwaCamera`.
*/
export class NokhwaCamera {
free(): void;
/**
* Creates a new [`JSCamera`] using [`JSCameraConstraints`].
*
* # Errors
* This may error if permission is not granted, or the constraints are invalid.
* # JS-WASM
* This is the constructor for `NokhwaCamera`. It returns a promise and may throw an error.
* @param {CameraConstraints} constraints
*/
constructor(constraints: CameraConstraints);
/**
* Measures the [`Resolution`] of the internal stream. You usually do not need to call this.
*
* # Errors
* If the camera fails to attach to the created `<video>`, this will error.
*
* # JS-WASM
* This is exported as `measureResolution`. It may throw an error.
*/
measureResolution(): void;
/**
* Applies any modified constraints.
* # Errors
* This function may return an error on failing to measure the resolution. Please check [`measure_resolution()`](crate::js_camera::JSCamera::measure_resolution) for details.
* # JS-WASM
* This is exported as `applyConstraints`. It may throw an error.
*/
applyConstraints(): void;
/**
* Captures an [`ImageData`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.ImageData.html) [`MDN`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) by drawing the image to a non-existent canvas.
*
* # Errors
* If drawing to the canvas fails this will error.
* # JS-WASM
* This is exported as `captureImageData`. It may throw an error.
* @returns {ImageData}
*/
captureImageData(): ImageData;
/**
* Captures an [`ImageData`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.ImageData.html) [`MDN`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) and then returns its `URL` as a string.
* - `mime_type`: The mime type of the resulting URI. It is `image/png` by default (lossless) but can be set to `image/jpeg` or `image/webp` (lossy). Anything else is ignored.
* - `image_quality`: A number between `0` and `1` indicating the resulting image quality in case you are using a lossy image mime type. The default value is 0.92, and all other values are ignored.
*
* # Errors
* If drawing to the canvas fails or URI generation is not supported or fails this will error.
* # JS-WASM
* This is exported as `captureImageURI`. It may throw an error
* @param {string} mime_type
* @param {number} image_quality
* @returns {string}
*/
captureImageURI(mime_type: string, image_quality: number): string;
/**
* Creates an off-screen canvas and a `<video>` element (if not already attached) and returns a raw `Cow<[u8]>` RGBA frame.
* # Errors
* If a cast fails, the camera fails to attach, the currently attached node is invalid, or writing/reading from the canvas fails, this will error.
* # JS-WASM
* This is exported as `captureFrameRawData`. This may throw an error.
* @returns {Uint8Array}
*/
captureFrameRawData(): Uint8Array;
/**
* Copies camera frame to a `html_id`(by-id, canvas).
*
* If `generate_new` is true, the generated element will have an Id of `html_id`+`-canvas`. For example, if you pass "nokhwaisbest" for `html_id`, the new `<canvas>`'s ID will be "nokhwaisbest-canvas".
* # Errors
* If the internal canvas is not here, drawing fails, or a cast fails, this will error.
* # JS-WASM
* This is exported as `copyToCanvas`. It may error.
* @param {string} html_id
* @param {boolean} generate_new
*/
copyToCanvas(html_id: string, generate_new: boolean): void;
/**
* Attaches camera to a `html_id`(by-id).
*
* If `generate_new` is true, the generated element will have an Id of `html_id`+`-video`. For example, if you pass "nokhwaisbest" for `html_id`, the new `<video>`'s ID will be "nokhwaisbest-video".
* # Errors
* If the camera fails to attach, fails to generate the video element, or a cast fails, this will error.
* # JS-WASM
* This is exported as `attachToElement`. It may throw an error.
* @param {string} html_id
* @param {boolean} generate_new
*/
attachToElement(html_id: string, generate_new: boolean): void;
/**
* Detaches the camera from the `<video>` node.
* # Errors
* If the casting fails (the stored node is not a `<video>`) this will error.
* # JS-WASM
* This is exported as `detachCamera`. This may throw an error.
*/
detachCamera(): void;
/**
* Stops all streams and detaches the camera.
* # Errors
* There may be an error while detaching the camera. Please see [`detach()`](crate::js_camera::JSCamera::detach) for more details.
*/
stopAll(): void;
/**
* Gets the internal [`JSCameraConstraints`].
* Most likely, you will edit this value by taking ownership of it, then feed it back into [`set_constraints`](crate::js_camera::JSCamera::set_constraints).
* # JS-WASM
* This is exported as `get_Constraints`.
* @returns {CameraConstraints}
*/
Constraints: CameraConstraints;
/**
* Gets the internal [`MediaStream`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.MediaStream.html) [`MDN`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream)
* # JS-WASM
* This is exported as `MediaStream`.
* @returns {MediaStream}
*/
readonly MediaStream: MediaStream;
/**
* Gets the internal [`Resolution`].
*
* Note: This value is only updated after you call [`measure_resolution`](crate::js_camera::JSCamera::measure_resolution)
* # JS-WASM
* This is exported as `get_Resolution`.
* @returns {JSResolution}
*/
readonly Resolution: JSResolution;
}
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly requestPermissions: () => number;
readonly queryCameras: () => number;
readonly queryConstraints: () => number;
readonly __wbg_cameraconstraintsbuilder_free: (a: number) => void;
readonly cameraconstraintsbuilder_new: () => number;
readonly cameraconstraintsbuilder_Resolution: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_MaxResolution: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_ResolutionExact: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_MinAspectRatio: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_AspectRatio: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_MaxAspectRatio: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_AspectRatioExact: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_FacingMode: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_FacingModeExact: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_MinFrameRate: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_FrameRate: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_MaxFrameRate: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_FrameRateExact: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_ResizeMode: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_ResizeModeExact: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_DeviceId: (a: number, b: number, c: number) => number;
readonly cameraconstraintsbuilder_DeviceIdExact: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_GroupId: (a: number, b: number, c: number) => number;
readonly cameraconstraintsbuilder_GroupIdExact: (a: number, b: number) => number;
readonly cameraconstraintsbuilder_buildCameraConstraints: (a: number) => number;
readonly __wbg_cameraconstraints_free: (a: number) => void;
readonly cameraconstraints_media_constraints: (a: number) => number;
readonly cameraconstraints_min_resolution: (a: number) => number;
readonly cameraconstraints_set_min_resolution: (a: number, b: number) => void;
readonly cameraconstraints_resolution: (a: number) => number;
readonly cameraconstraints_set_resolution: (a: number, b: number) => void;
readonly cameraconstraints_max_resolution: (a: number) => number;
readonly cameraconstraints_set_max_resolution: (a: number, b: number) => void;
readonly cameraconstraints_resolution_exact: (a: number) => number;
readonly cameraconstraints_set_resolution_exact: (a: number, b: number) => void;
readonly cameraconstraints_min_aspect_ratio: (a: number, b: number) => void;
readonly cameraconstraints_set_min_aspect_ratio: (a: number, b: number) => void;
readonly cameraconstraints_aspect_ratio: (a: number) => number;
readonly cameraconstraints_set_aspect_ratio: (a: number, b: number) => void;
readonly cameraconstraints_max_aspect_ratio: (a: number, b: number) => void;
readonly cameraconstraints_set_max_aspect_ratio: (a: number, b: number) => void;
readonly cameraconstraints_aspect_ratio_exact: (a: number) => number;
readonly cameraconstraints_set_aspect_ratio_exact: (a: number, b: number) => void;
readonly cameraconstraints_facing_mode: (a: number) => number;
readonly cameraconstraints_set_facing_mode: (a: number, b: number) => void;
readonly cameraconstraints_facing_mode_exact: (a: number) => number;
readonly cameraconstraints_set_facing_mode_exact: (a: number, b: number) => void;
readonly cameraconstraints_min_frame_rate: (a: number, b: number) => void;
readonly cameraconstraints_set_min_frame_rate: (a: number, b: number) => void;
readonly cameraconstraints_frame_rate: (a: number) => number;
readonly cameraconstraints_set_frame_rate: (a: number, b: number) => void;
readonly cameraconstraints_max_frame_rate: (a: number, b: number) => void;
readonly cameraconstraints_set_max_frame_rate: (a: number, b: number) => void;
readonly cameraconstraints_frame_rate_exact: (a: number) => number;
readonly cameraconstraints_set_frame_rate_exact: (a: number, b: number) => void;
readonly cameraconstraints_resize_mode: (a: number) => number;
readonly cameraconstraints_set_resize_mode: (a: number, b: number) => void;
readonly cameraconstraints_resize_mode_exact: (a: number) => number;
readonly cameraconstraints_set_resize_mode_exact: (a: number, b: number) => void;
readonly cameraconstraints_device_id: (a: number, b: number) => void;
readonly cameraconstraints_set_device_id: (a: number, b: number, c: number) => void;
readonly cameraconstraints_device_id_exact: (a: number) => number;
readonly cameraconstraints_set_device_id_exact: (a: number, b: number) => void;
readonly cameraconstraints_group_id: (a: number, b: number) => void;
readonly cameraconstraints_set_group_id: (a: number, b: number, c: number) => void;
readonly cameraconstraints_group_id_exact: (a: number) => number;
readonly cameraconstraints_set_group_id_exact: (a: number, b: number) => void;
readonly cameraconstraints_applyConstraints: (a: number) => void;
readonly __wbg_nokhwacamera_free: (a: number) => void;
readonly nokhwacamera_js_new: (a: number) => number;
readonly nokhwacamera_constraints: (a: number) => number;
readonly nokhwacamera_js_set_constraints: (a: number, b: number) => void;
readonly nokhwacamera_resolution: (a: number) => number;
readonly nokhwacamera_measureResolution: (a: number) => void;
readonly nokhwacamera_applyConstraints: (a: number) => void;
readonly nokhwacamera_media_stream: (a: number) => number;
readonly nokhwacamera_captureImageData: (a: number) => number;
readonly nokhwacamera_captureImageURI: (a: number, b: number, c: number, d: number, e: number) => void;
readonly nokhwacamera_captureFrameRawData: (a: number, b: number) => void;
readonly nokhwacamera_copyToCanvas: (a: number, b: number, c: number, d: number) => void;
readonly nokhwacamera_attachToElement: (a: number, b: number, c: number, d: number) => void;
readonly nokhwacamera_detachCamera: (a: number) => void;
readonly nokhwacamera_stopAll: (a: number) => void;
readonly cameraconstraintsbuilder_MinResolution: (a: number, b: number) => number;
readonly __wbg_jsresolution_free: (a: number) => void;
readonly __wbg_get_jsresolution_width_x: (a: number) => number;
readonly __wbg_set_jsresolution_width_x: (a: number, b: number) => void;
readonly __wbg_get_jsresolution_height_y: (a: number) => number;
readonly __wbg_set_jsresolution_height_y: (a: number, b: number) => void;
readonly jsresolution_width: (a: number) => number;
readonly jsresolution_height: (a: number) => number;
readonly __wbg_jscamerainfo_free: (a: number) => void;
readonly jscamerainfo_new: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => number;
readonly jscamerainfo_human_name: (a: number, b: number) => void;
readonly jscamerainfo_set_human_name: (a: number, b: number, c: number) => void;
readonly jscamerainfo_description: (a: number, b: number) => void;
readonly jscamerainfo_set_description: (a: number, b: number, c: number) => void;
readonly jscamerainfo_misc: (a: number, b: number) => void;
readonly jscamerainfo_set_misc: (a: number, b: number, c: number) => void;
readonly jscamerainfo_index: (a: number) => number;
readonly jscamerainfo_set_index: (a: number, b: number) => void;
readonly jsresolution_new: (a: number, b: number) => number;
readonly jsresolution_x: (a: number) => number;
readonly jsresolution_y: (a: number) => number;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h4e37a62ffa13b731: (a: number, b: number, c: number) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__h4d59381e7733ca36: (a: number, b: number, c: number, d: number) => void;
}
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
-2055
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
-115
View File
@@ -1,115 +0,0 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export function requestPermissions(): number;
export function queryCameras(): number;
export function queryConstraints(): number;
export function __wbg_cameraconstraintsbuilder_free(a: number): void;
export function cameraconstraintsbuilder_new(): number;
export function cameraconstraintsbuilder_Resolution(a: number, b: number): number;
export function cameraconstraintsbuilder_MaxResolution(a: number, b: number): number;
export function cameraconstraintsbuilder_ResolutionExact(a: number, b: number): number;
export function cameraconstraintsbuilder_MinAspectRatio(a: number, b: number): number;
export function cameraconstraintsbuilder_AspectRatio(a: number, b: number): number;
export function cameraconstraintsbuilder_MaxAspectRatio(a: number, b: number): number;
export function cameraconstraintsbuilder_AspectRatioExact(a: number, b: number): number;
export function cameraconstraintsbuilder_FacingMode(a: number, b: number): number;
export function cameraconstraintsbuilder_FacingModeExact(a: number, b: number): number;
export function cameraconstraintsbuilder_MinFrameRate(a: number, b: number): number;
export function cameraconstraintsbuilder_FrameRate(a: number, b: number): number;
export function cameraconstraintsbuilder_MaxFrameRate(a: number, b: number): number;
export function cameraconstraintsbuilder_FrameRateExact(a: number, b: number): number;
export function cameraconstraintsbuilder_ResizeMode(a: number, b: number): number;
export function cameraconstraintsbuilder_ResizeModeExact(a: number, b: number): number;
export function cameraconstraintsbuilder_DeviceId(a: number, b: number, c: number): number;
export function cameraconstraintsbuilder_DeviceIdExact(a: number, b: number): number;
export function cameraconstraintsbuilder_GroupId(a: number, b: number, c: number): number;
export function cameraconstraintsbuilder_GroupIdExact(a: number, b: number): number;
export function cameraconstraintsbuilder_buildCameraConstraints(a: number): number;
export function __wbg_cameraconstraints_free(a: number): void;
export function cameraconstraints_media_constraints(a: number): number;
export function cameraconstraints_min_resolution(a: number): number;
export function cameraconstraints_set_min_resolution(a: number, b: number): void;
export function cameraconstraints_resolution(a: number): number;
export function cameraconstraints_set_resolution(a: number, b: number): void;
export function cameraconstraints_max_resolution(a: number): number;
export function cameraconstraints_set_max_resolution(a: number, b: number): void;
export function cameraconstraints_resolution_exact(a: number): number;
export function cameraconstraints_set_resolution_exact(a: number, b: number): void;
export function cameraconstraints_min_aspect_ratio(a: number, b: number): void;
export function cameraconstraints_set_min_aspect_ratio(a: number, b: number): void;
export function cameraconstraints_aspect_ratio(a: number): number;
export function cameraconstraints_set_aspect_ratio(a: number, b: number): void;
export function cameraconstraints_max_aspect_ratio(a: number, b: number): void;
export function cameraconstraints_set_max_aspect_ratio(a: number, b: number): void;
export function cameraconstraints_aspect_ratio_exact(a: number): number;
export function cameraconstraints_set_aspect_ratio_exact(a: number, b: number): void;
export function cameraconstraints_facing_mode(a: number): number;
export function cameraconstraints_set_facing_mode(a: number, b: number): void;
export function cameraconstraints_facing_mode_exact(a: number): number;
export function cameraconstraints_set_facing_mode_exact(a: number, b: number): void;
export function cameraconstraints_min_frame_rate(a: number, b: number): void;
export function cameraconstraints_set_min_frame_rate(a: number, b: number): void;
export function cameraconstraints_frame_rate(a: number): number;
export function cameraconstraints_set_frame_rate(a: number, b: number): void;
export function cameraconstraints_max_frame_rate(a: number, b: number): void;
export function cameraconstraints_set_max_frame_rate(a: number, b: number): void;
export function cameraconstraints_frame_rate_exact(a: number): number;
export function cameraconstraints_set_frame_rate_exact(a: number, b: number): void;
export function cameraconstraints_resize_mode(a: number): number;
export function cameraconstraints_set_resize_mode(a: number, b: number): void;
export function cameraconstraints_resize_mode_exact(a: number): number;
export function cameraconstraints_set_resize_mode_exact(a: number, b: number): void;
export function cameraconstraints_device_id(a: number, b: number): void;
export function cameraconstraints_set_device_id(a: number, b: number, c: number): void;
export function cameraconstraints_device_id_exact(a: number): number;
export function cameraconstraints_set_device_id_exact(a: number, b: number): void;
export function cameraconstraints_group_id(a: number, b: number): void;
export function cameraconstraints_set_group_id(a: number, b: number, c: number): void;
export function cameraconstraints_group_id_exact(a: number): number;
export function cameraconstraints_set_group_id_exact(a: number, b: number): void;
export function cameraconstraints_applyConstraints(a: number): void;
export function __wbg_nokhwacamera_free(a: number): void;
export function nokhwacamera_js_new(a: number): number;
export function nokhwacamera_constraints(a: number): number;
export function nokhwacamera_js_set_constraints(a: number, b: number): void;
export function nokhwacamera_resolution(a: number): number;
export function nokhwacamera_measureResolution(a: number): void;
export function nokhwacamera_applyConstraints(a: number): void;
export function nokhwacamera_media_stream(a: number): number;
export function nokhwacamera_captureImageData(a: number): number;
export function nokhwacamera_captureImageURI(a: number, b: number, c: number, d: number, e: number): void;
export function nokhwacamera_captureFrameRawData(a: number, b: number): void;
export function nokhwacamera_copyToCanvas(a: number, b: number, c: number, d: number): void;
export function nokhwacamera_attachToElement(a: number, b: number, c: number, d: number): void;
export function nokhwacamera_detachCamera(a: number): void;
export function nokhwacamera_stopAll(a: number): void;
export function cameraconstraintsbuilder_MinResolution(a: number, b: number): number;
export function __wbg_jsresolution_free(a: number): void;
export function __wbg_get_jsresolution_width_x(a: number): number;
export function __wbg_set_jsresolution_width_x(a: number, b: number): void;
export function __wbg_get_jsresolution_height_y(a: number): number;
export function __wbg_set_jsresolution_height_y(a: number, b: number): void;
export function jsresolution_width(a: number): number;
export function jsresolution_height(a: number): number;
export function __wbg_jscamerainfo_free(a: number): void;
export function jscamerainfo_new(a: number, b: number, c: number, d: number, e: number, f: number, g: number): number;
export function jscamerainfo_human_name(a: number, b: number): void;
export function jscamerainfo_set_human_name(a: number, b: number, c: number): void;
export function jscamerainfo_description(a: number, b: number): void;
export function jscamerainfo_set_description(a: number, b: number, c: number): void;
export function jscamerainfo_misc(a: number, b: number): void;
export function jscamerainfo_set_misc(a: number, b: number, c: number): void;
export function jscamerainfo_index(a: number): number;
export function jscamerainfo_set_index(a: number, b: number): void;
export function jsresolution_new(a: number, b: number): number;
export function jsresolution_x(a: number): number;
export function jsresolution_y(a: number): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export const __wbindgen_export_2: WebAssembly.Table;
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h4e37a62ffa13b731(a: number, b: number, c: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_free(a: number, b: number): void;
export function __wbindgen_exn_store(a: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__h4d59381e7733ca36(a: number, b: number, c: number, d: number): void;
-28
View File
@@ -1,28 +0,0 @@
{
"name": "nokhwa",
"collaborators": [
"l1npengtul <l1npengtul@protonmail.com>"
],
"description": "A Simple-to-use, cross-platform Rust Webcam Capture Library",
"version": "0.9.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/l1npengtul/nokhwa"
},
"files": [
"nokhwa_bg.wasm",
"nokhwa.js",
"nokhwa_bg.js",
"nokhwa.d.ts"
],
"module": "nokhwa.js",
"types": "nokhwa.d.ts",
"sideEffects": false,
"keywords": [
"camera",
"webcam",
"capture",
"cross-platform"
]
}
+118 -123
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -14,16 +14,26 @@
* limitations under the License.
*/
use crate::{
mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
use nokhwa_bindings_macos::{
AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, AVCaptureVideoCallback,
AVCaptureVideoDataOutput,
};
use image::{ImageBuffer, Rgb};
use nokhwa_bindings_macos::avfoundation::{
query_avfoundation, AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession,
AVCaptureVideoCallback, AVCaptureVideoDataOutput, AVFourCC,
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
traits::CaptureBackendTrait,
types::{
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter,
FrameFormat, KnownCameraControl, RequestedFormat, Resolution,
},
};
use std::collections::BTreeMap;
use std::ffi::CString;
use std::{
borrow::Cow,
collections::HashMap,
sync::{Arc, Mutex},
};
use std::{any::Any, borrow::Cow, collections::HashMap};
/// The backend struct that interfaces with V4L2.
/// To see what this does, please see [`CaptureBackendTrait`].
@@ -32,6 +42,7 @@ use std::{any::Any, borrow::Cow, collections::HashMap};
/// - You **must** call [`nokhwa_initialize`](crate::nokhwa_initialize) **before** doing anything with `AVFoundation`.
/// - This only works on 64 bit platforms.
/// - FPS adjustment does not work.
/// - If permission has not been granted and you call `init()` it will error.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-avfoundation")))]
pub struct AVFoundationCaptureDevice {
device: AVCaptureDevice,
@@ -40,7 +51,9 @@ pub struct AVFoundationCaptureDevice {
data_out: Option<AVCaptureVideoDataOutput>,
data_collect: Option<AVCaptureVideoCallback>,
info: CameraInfo,
buffer_name: CString,
format: CameraFormat,
frame_buffer_lock: Arc<Mutex<(Vec<u8>, FrameFormat)>>,
}
impl AVFoundationCaptureDevice {
@@ -49,25 +62,22 @@ 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.
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 device_descriptor: CameraInfo = match query_avfoundation()?.into_iter().nth(index) {
Some(descriptor) => descriptor.into(),
None => {
return Err(NokhwaError::OpenDeviceError(
index.to_string(),
"No Device".to_string(),
))
}
};
let mut device = AVCaptureDevice::from_id(&device_descriptor.misc())?;
pub fn new(index: &CameraIndex, req_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
let mut device = AVCaptureDevice::new(index)?;
device.lock()?;
device.set_all(camera_format.into())?;
let formats = device.supported_formats()?;
let camera_fmt = req_fmt.fulfill(&formats).ok_or_else(|| {
NokhwaError::OpenDeviceError("Cannot fulfill request".to_string(), req_fmt.to_string())
})?;
device.set_all(camera_fmt)?;
let device_descriptor = device.info().clone();
let buffername =
CString::new(format!("{}_INDEX{}_", device_descriptor, index)).map_err(|why| {
NokhwaError::StructureError {
structure: "CString Buffername".to_string(),
error: why.to_string(),
}
})?;
Ok(AVFoundationCaptureDevice {
device,
@@ -76,7 +86,9 @@ impl AVFoundationCaptureDevice {
data_out: None,
data_collect: None,
info: device_descriptor,
format: camera_format,
buffer_name: buffername,
format: camera_fmt,
frame_buffer_lock: Arc::new(Mutex::new((vec![], FrameFormat::MJPEG))),
})
}
@@ -84,6 +96,8 @@ 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.
#[deprecated(since = "0.10.0", note = "please use `new` instead.")]
#[allow(clippy::cast_possible_truncation)]
pub fn new_with(
index: usize,
width: u32,
@@ -91,26 +105,33 @@ impl AVFoundationCaptureDevice {
fps: u32,
fourcc: FrameFormat,
) -> Result<Self, NokhwaError> {
let camera_format = Some(CameraFormat::new_from(width, height, fourcc, fps));
AVFoundationCaptureDevice::new(index, camera_format)
let camera_format = CameraFormat::new_from(width, height, fourcc, fps);
AVFoundationCaptureDevice::new(
&CameraIndex::Index(index as u32),
RequestedFormat::Exact(camera_format),
)
}
}
impl CaptureBackendTrait for AVFoundationCaptureDevice {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::AVFoundation
fn backend(&self) -> ApiBackend {
ApiBackend::AVFoundation
}
fn camera_info(&self) -> &CameraInfo {
&self.info
}
fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
Ok(())
}
fn camera_format(&self) -> CameraFormat {
self.format
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
self.device.set_all(new_fmt.into())?;
self.device.set_all(new_fmt)?;
self.format = new_fmt;
Ok(())
}
@@ -121,23 +142,21 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
&mut self,
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
Ok(self
let supported_cfmt = self
.device
.supported_formats()?
.into_iter()
.map(|fmt| {
(
FrameFormat::from(fmt.fourcc),
Resolution::from(fmt.resolution),
(&fmt.fps_list)
.iter()
.map(|f| *f as u32)
.collect::<Vec<u32>>(),
)
})
.filter(|x| (*x).0 == fourcc)
.map(|fmt| (fmt.1, fmt.2))
.collect::<HashMap<Resolution, Vec<u32>>>())
.filter(|x| x.format() != fourcc);
let mut res_list = HashMap::new();
for format in supported_cfmt {
match res_list.get_mut(&format.resolution()) {
Some(fpses) => Vec::push(fpses, format.frame_rate()),
None => {
res_list.insert(format.resolution(), vec![format.frame_rate()]);
}
}
}
Ok(res_list)
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
@@ -145,7 +164,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
.device
.supported_formats()?
.into_iter()
.map(|fmt| FrameFormat::from(fmt.fourcc))
.map(|fmt| fmt.format())
.collect::<Vec<FrameFormat>>();
formats.sort();
formats.dedup();
@@ -182,40 +201,32 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
self.set_camera_format(format)
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
fn camera_control(&self, control: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
for ctrl in self.device.get_controls()? {
if ctrl.control() == control {
return Ok(ctrl);
}
}
return Err(NokhwaError::GetPropertyError {
property: control.to_string(),
error: "Not Found".to_string(),
});
}
fn camera_control(&self, _: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
self.device.get_controls()
}
fn set_camera_control(&mut self, _: CameraControl) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn raw_camera_control(&self, _: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn set_raw_camera_control(&mut self, _: &dyn Any, _: &dyn Any) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
fn set_camera_control(
&mut self,
id: KnownCameraControl,
value: ControlValueSetter,
) -> Result<(), NokhwaError> {
self.device.lock()?;
let res = self.device.set_control(id, value);
self.device.unlock()?;
res
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
@@ -223,16 +234,19 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
let session = AVCaptureSession::new();
session.begin_configuration();
session.add_input(&input)?;
let callback = AVCaptureVideoCallback::new(self.info.index());
let frame_mutex = self.frame_buffer_lock.clone();
let bufname = &self.buffer_name;
let videocallback = AVCaptureVideoCallback::new(bufname, frame_mutex)?;
let output = AVCaptureVideoDataOutput::new();
output.add_delegate(&callback)?;
output.add_delegate(&videocallback)?;
session.add_output(&output)?;
session.commit_configuration();
session.start()?;
self.dev_input = Some(input);
self.session = Some(session);
self.data_collect = Some(callback);
self.data_collect = Some(videocallback);
self.data_out = Some(output);
Ok(())
}
@@ -251,56 +265,37 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
}
}
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
let cam_fmt = self.camera_format();
let conv = self.frame_raw()?.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)
fn frame(&mut self) -> Result<Buffer, NokhwaError> {
self.refresh_camera_format()?;
let cfmt = self.camera_format();
let buffer = Buffer::new(cfmt.resolution(), &self.frame_raw()?, cfmt.format());
Ok(buffer)
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
match &self.session {
Some(session) => {
if !session.is_running() {
return Err(NokhwaError::ReadFrameError(
"Stream Not Started".to_string(),
));
let mut framebuffer_empty = match self.frame_buffer_lock.lock() {
Ok(f) => f.0.is_empty(),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
};
loop {
if framebuffer_empty {
match self.frame_buffer_lock.lock() {
Ok(f) => framebuffer_empty = f.0.is_empty(),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
}
if session.is_interrupted() {
return Err(NokhwaError::ReadFrameError(
"Stream Interrupted".to_string(),
));
}
}
None => {
return Err(NokhwaError::ReadFrameError(
"Stream Not Started".to_string(),
))
} else {
break;
}
}
match &self.data_collect {
Some(collector) => {
let data = collector.frame_to_slice()?;
let data = match data.1 {
AVFourCC::YUV2 => Cow::from(yuyv422_to_rgb888(&data.0)?),
AVFourCC::MJPEG => Cow::from(mjpeg_to_rgb888(&data.0)?),
};
Ok(data)
match self.frame_buffer_lock.lock() {
Ok(mut f) => {
let mut new_frame = vec![];
std::mem::swap(&mut new_frame, &mut f.0);
Ok(Cow::from(new_frame))
}
None => Err(NokhwaError::ReadFrameError(
"Stream Not Started".to_string(),
)),
Err(why) => Err(NokhwaError::ReadFrameError(why.to_string())),
}
}
+268
View File
@@ -0,0 +1,268 @@
/*
* 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::{
js_camera::JSCameraResizeMode,
js_camera::{query_js_cameras, JSCameraConstraintsBuilder},
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, CaptureBackendTrait,
FrameFormat, JSCamera, KnownCameraControl, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use std::{any::Any, borrow::Cow, collections::HashMap};
/// Captures using the Browser API. This internally wraps [`JSCamera`].
///
/// # Quirks
/// - `FourCC` setting is ignored
/// - Cannot get compatible resolution(s).
/// - CameraControl(s) are not supported.
/// - All frame capture is done by creating (then destorying) a canvas on the DOM.
/// - Many methods are blocking on user input.
pub struct BrowserCaptureDevice {
camera: JSCamera,
info: CameraInfo,
}
impl BrowserCaptureDevice {
// WARN: blocking on pass integer for index
/// Creates a new camera from an [`CameraIndex`]. It can take [`CameraIndex::Index`] or [`CameraIndex::String`] (NOTE: blocks on [`CameraIndex::Index`])
///
/// # Errors
/// If the device is not found, browser not supported, or camera is over-constrained this will error.
pub fn new(index: &CameraIndex, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let (group_id, device_id) = match &index {
CameraIndex::Index(i) => {
let query_devices =
wasm_rs_async_executor::single_threaded::block_on(query_js_cameras())?;
match query_devices.into_iter().nth(*i as usize) {
Some(info) => {
let ids = info
.to_string()
.split(' ')
.map(ToString::to_string)
.collect::<Vec<String>>();
match (ids.get(0), ids.get(1)) {
(Some(group_id), Some(device_id)) => {
(group_id.clone(), device_id.clone())
}
(_, _) => {
return Err(NokhwaError::OpenDeviceError(
"Invalid Index".to_string(),
index.to_string(),
))
}
}
}
None => {
return Err(NokhwaError::OpenDeviceError(
"Device not found".to_string(),
index.to_string(),
))
}
}
}
CameraIndex::String(id) => {
let ids = id
.to_string()
.split(' ')
.map(ToString::to_string)
.collect::<Vec<String>>();
match (ids.get(0), ids.get(1)) {
(Some(group_id), Some(device_id)) => (group_id.clone(), device_id.clone()),
(_, _) => {
return Err(NokhwaError::OpenDeviceError(
"Invalid Index".to_string(),
index.to_string(),
))
}
}
}
};
let camera_format = cam_fmt.unwrap_or_default();
let constraints = JSCameraConstraintsBuilder::new()
.frame_rate(camera_format.frame_rate())
.resolution(camera_format.resolution())
.aspect_ratio(f64::from(camera_format.width()) / f64::from(camera_format.height()))
.group_id(&group_id)
.group_id_exact(true)
.device_id(&device_id)
.device_id_exact(true)
.resize_mode(JSCameraResizeMode::Any)
.build();
let camera = wasm_rs_async_executor::single_threaded::block_on(JSCamera::new(constraints))?;
let info = (|| {
let cameras = wasm_rs_async_executor::single_threaded::block_on(query_js_cameras())?;
let giddid = format!("{} {}", group_id, device_id);
for cam in cameras {
if cam.misc() == giddid {
return Ok(cam);
}
}
Ok(CameraInfo::new("", "videoinput", &giddid, index.clone()))
})()?;
Ok(BrowserCaptureDevice { camera, info })
}
/// Creates a new camera from an [`CameraIndex`] and raw parts. It can take [`CameraIndex::Index`] or [`CameraIndex::String`] (NOTE: blocks on [`CameraIndex::Index`])
///
/// # Errors
/// If the device is not found, browser not supported, or camera is over-constrained this will error.
pub fn new_with(
index: &CameraIndex,
width: u32,
height: u32,
fps: u32,
fourcc: FrameFormat,
) -> Result<Self, NokhwaError> {
Self::new(
index,
Some(CameraFormat::new(Resolution::new(width, height))),
)
}
}
impl CaptureBackendTrait for BrowserCaptureDevice {
fn backend(&self) -> ApiBackend {
ApiBackend::Browser
}
fn camera_info(&self) -> &CameraInfo {
&self.info
}
fn camera_format(&self) -> CameraFormat {
CameraFormat::new(self.camera.resolution())
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
let current_constraints = self.camera.constraints();
let new_constraints = JSCameraConstraintsBuilder::new()
.resolution(new_fmt.resolution())
.aspect_ratio(f64::from(new_fmt.width()) / f64::from(new_fmt.height()))
.frame_rate(new_fmt.frame_rate())
.group_id(&current_constraints.group_id())
.device_id(&current_constraints.device_id())
.resize_mode(JSCameraResizeMode::Any)
.build();
let _constraint_err = self.camera.set_constraints(new_constraints);
match self.camera.apply_constraints() {
Ok(_) => Ok(()),
Err(why) => {
let _returnerr = self.camera.set_constraints(current_constraints); // swallow errors - revert
Err(why)
}
}
}
fn compatible_list_by_resolution(
&mut self,
_: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
Ok(vec![FrameFormat::MJPEG, FrameFormat::YUYV])
}
fn resolution(&self) -> Resolution {
self.camera.resolution()
}
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
let mut current_format = self.camera_format();
current_format.set_resolution(new_res);
self.set_camera_format(current_format)
}
fn frame_rate(&self) -> u32 {
self.camera.constraints().frame_rate()
}
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
let mut current_format = self.camera_format();
current_format.set_frame_rate(new_fps);
self.set_camera_format(current_format)
}
fn frame_format(&self) -> FrameFormat {
FrameFormat::MJPEG
}
fn set_frame_format(&mut self, _: FrameFormat) -> Result<(), NokhwaError> {
Ok(())
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControl>, NokhwaError> {
Ok(vec![])
}
fn camera_control(&self, _: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn set_camera_control(&mut self, _: CameraControl) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
Ok(vec![])
}
fn raw_camera_control(&self, _: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn set_raw_camera_control(&mut self, _: &dyn Any, _: &dyn Any) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not Implemented".to_string(),
))
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
Ok(())
}
fn is_stream_open(&self) -> bool {
self.camera.is_open()
}
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
self.camera.frame()
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
self.camera.frame_raw()
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
self.camera.stop_all()
}
}
+58 -47
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -15,10 +15,9 @@
*/
use crate::{
mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
mjpeg_to_rgb, yuyv422_to_rgb, ApiBackend, CameraControl, CameraFormat, CameraInfo,
CaptureBackendTrait, FrameFormat, KnownCameraControl, NokhwaError, Resolution,
};
use flume::Receiver;
use glib::Quark;
use gstreamer::{
element_error,
@@ -30,11 +29,11 @@ use gstreamer::{
use gstreamer_app::{AppSink, AppSinkCallbacks};
use gstreamer_video::{VideoFormat, VideoInfo};
use image::{ImageBuffer, Rgb};
use parking_lot::Mutex;
use regex::Regex;
use std::any::Any;
use std::{borrow::Cow, collections::HashMap, str::FromStr};
use std::{any::Any, borrow::Cow, collections::HashMap, str::FromStr, sync::Arc};
type PipelineGenRet = (Element, AppSink, Receiver<ImageBuffer<Rgb<u8>, Vec<u8>>>);
type PipelineGenRet = (Element, AppSink, Arc<Mutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>);
/// The backend struct that interfaces with `GStreamer`.
/// To see what this does, please see [`CaptureBackendTrait`].
@@ -42,12 +41,16 @@ type PipelineGenRet = (Element, AppSink, Receiver<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,
camera_format: CameraFormat,
camera_info: CameraInfo,
receiver: Receiver<ImageBuffer<Rgb<u8>, Vec<u8>>>,
image_lock: Arc<Mutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
caps: Option<Caps>,
}
@@ -65,9 +68,11 @@ impl GStreamerCaptureDevice {
None => CameraFormat::default(),
};
let index = index.as_index()?;
if let Err(why) = gstreamer::init() {
return Err(NokhwaError::InitializeError {
backend: CaptureAPIBackend::GStreamer,
backend: ApiBackend::GStreamer,
error: why.to_string(),
});
}
@@ -99,7 +104,7 @@ impl GStreamerCaptureDevice {
error: format!("Not started, {}", why),
});
}
let device = match device_monitor.devices().get(index) {
let device = match device_monitor.devices().get(index as usize) {
Some(dev) => dev.clone(),
None => {
return Err(NokhwaError::OpenDeviceError(
@@ -112,23 +117,23 @@ impl GStreamerCaptureDevice {
let caps = device.caps();
(
CameraInfo::new(
DeviceExt::display_name(&device).to_string(),
DeviceExt::device_class(&device).to_string(),
"".to_string(),
&DeviceExt::display_name(&device),
&DeviceExt::device_class(&device),
&"",
index,
),
caps,
)
};
let (pipeline, app_sink, receiver) = generate_pipeline(camera_format, index)?;
let (pipeline, app_sink, receiver) = generate_pipeline(camera_format, index as usize)?;
Ok(GStreamerCaptureDevice {
pipeline,
app_sink,
camera_format,
camera_info,
receiver,
image_lock: receiver,
caps,
})
}
@@ -139,14 +144,14 @@ impl GStreamerCaptureDevice {
/// # Errors
/// This function will error if the camera is currently busy or if `GStreamer` can't read device information.
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);
let cam_fmt = CameraFormat::new(Resolution::new(width, height));
GStreamerCaptureDevice::new(index, Some(cam_fmt))
}
}
impl CaptureBackendTrait for GStreamerCaptureDevice {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::GStreamer
impl GStreamerCaptureDevice {
fn backend(&self) -> ApiBackend {
ApiBackend::GStreamer
}
fn camera_info(&self) -> &CameraInfo {
@@ -163,10 +168,11 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
self.stop_stream()?;
reopen = true;
}
let (pipeline, app_sink, receiver) = generate_pipeline(new_fmt, self.camera_info.index())?;
let (pipeline, app_sink, receiver) =
generate_pipeline(new_fmt, self.camera_info.index_num()? as usize)?;
self.pipeline = pipeline;
self.app_sink = app_sink;
self.receiver = receiver;
self.image_lock = receiver;
if reopen {
self.open_stream()?;
}
@@ -362,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:?}"
)))
}
}
}
}
@@ -428,37 +439,37 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
fn set_frame_format(&mut self, _fourcc: FrameFormat) -> Result<(), NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::GStreamer,
ApiBackend::GStreamer,
))
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControl>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
CaptureAPIBackend::GStreamer.to_string(),
ApiBackend::GStreamer.to_string(),
))
}
fn camera_control(&self, _control: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
fn camera_control(&self, _control: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
Err(NokhwaError::NotImplementedError(
CaptureAPIBackend::GStreamer.to_string(),
ApiBackend::GStreamer.to_string(),
))
}
fn set_camera_control(&mut self, _control: CameraControl) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
CaptureAPIBackend::GStreamer.to_string(),
ApiBackend::GStreamer.to_string(),
))
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
CaptureAPIBackend::GStreamer.to_string(),
ApiBackend::GStreamer.to_string(),
))
}
fn raw_camera_control(&self, _control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
CaptureAPIBackend::GStreamer.to_string(),
ApiBackend::GStreamer.to_string(),
))
}
@@ -468,7 +479,7 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
_value: &dyn Any,
) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
CaptureAPIBackend::GStreamer.to_string(),
ApiBackend::GStreamer.to_string(),
))
}
@@ -540,15 +551,7 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
}
}
match self.receiver.recv() {
Ok(msg) => Ok(Cow::from(msg.to_vec())),
Err(why) => {
return Err(NokhwaError::ReadFrameError(format!(
"Receiver Error: {}",
why
)));
}
}
Ok(Cow::from(self.image_lock.lock().to_vec()))
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
@@ -564,7 +567,7 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
impl Drop for GStreamerCaptureDevice {
fn drop(&mut self) {
self.pipeline.set_state(State::Null).unwrap();
let _ = self.pipeline.set_state(State::Null);
}
}
@@ -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!")
}
}
}
@@ -650,7 +662,8 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result<PipelineGenRet,
pipeline.set_state(State::Playing).unwrap();
let (sender, receiver) = flume::unbounded();
let image_lock = Arc::new(Mutex::new(ImageBuffer::default()));
let img_lck_clone = image_lock.clone();
appsink.set_callbacks(
AppSinkCallbacks::builder()
@@ -708,7 +721,7 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result<PipelineGenRet,
let image_buffer = match video_info.format() {
VideoFormat::Yuy2 => {
let mut decoded_buffer = match yuyv422_to_rgb888(&buffer_map) {
let mut decoded_buffer = match yuyv422_to_rgb(&buffer_map, false) {
Ok(buf) => buf,
Err(why) => {
element_error!(
@@ -770,7 +783,7 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result<PipelineGenRet,
}
// MJPEG
VideoFormat::Encoded => {
let mut decoded_buffer = match mjpeg_to_rgb888(&buffer_map) {
let mut decoded_buffer = match mjpeg_to_rgb(&buffer_map, false) {
Ok(buf) => buf,
Err(why) => {
element_error!(
@@ -816,13 +829,11 @@ fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result<PipelineGenRet,
}
};
if sender.send(image_buffer).is_err() {
return Err(FlowError::Error);
}
*img_lck_clone.lock() = image_buffer;
Ok(FlowSuccess::Ok)
})
.build(),
);
Ok((pipeline, appsink, receiver))
Ok((pipeline, appsink, image_lock))
}
+22 -10
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -16,21 +16,21 @@
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
// I'm too lazy to set up a skeleton facade for V4L so here it will stay
mod v4l2;
mod v4l2_backend;
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-v4l")))]
pub use v4l2::V4LCaptureDevice;
pub use v4l2_backend::V4LCaptureDevice;
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
mod msmf;
mod msmf_backend;
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-msmf")))]
pub use msmf::MediaFoundationCaptureDevice;
pub use msmf_backend::MediaFoundationCaptureDevice;
#[cfg(any(
all(
feature = "input-avfoundation",
@@ -57,16 +57,28 @@ mod avfoundation;
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-avfoundation")))]
pub use avfoundation::AVFoundationCaptureDevice;
// FIXME: Fix Lifetime Issues
// #[cfg(feature = "input-uvc")]
// mod uvc_backend;
// #[cfg(feature = "input-uvc")]
// #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-uvc")))]
// pub use uvc_backend::UVCCaptureDevice;
#[cfg(feature = "input-uvc")]
mod uvc_backend;
#[cfg(feature = "input-uvc")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-uvc")))]
pub use uvc_backend::UVCCaptureDevice;
#[cfg(feature = "input-gst")]
mod gst_backend;
#[cfg(feature = "input-gst")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-gst")))]
pub use gst_backend::GStreamerCaptureDevice;
#[cfg(feature = "input-jscam")]
mod browser_backend;
#[cfg(feature = "input-jscam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))]
pub use browser_backend::BrowserCaptureDevice;
/// A camera that uses `OpenCV` to access IP (rtsp/http) on the local network
#[cfg(feature = "input-ipcam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))]
mod network_camera;
#[cfg(feature = "input-ipcam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))]
pub use network_camera::NetworkCamera;
#[cfg(feature = "input-opencv")]
mod opencv_backend;
#[cfg(feature = "input-opencv")]
-372
View File
@@ -1,372 +0,0 @@
/*
* Copyright 2021 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::{
all_known_camera_controls, mjpeg_to_rgb888, yuyv422_to_rgb888, CameraControl, CameraFormat,
CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, KnownCameraControlFlag,
KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use nokhwa_bindings_windows::{wmf::MediaFoundationDevice, MFControl, MediaFoundationControls};
use std::{any::Any, borrow::Cow, collections::HashMap};
/// The backend that deals with Media Foundation on Windows.
/// To see what this does, please see [`CaptureBackendTrait`].
///
/// Note: This requires Windows 7 or newer to work.
/// # Quirks
/// - This does build on non-windows platforms, however when you do the backend will be empty and will return an error for any given operation.
/// - Please check [`nokhwa-bindings-windows`](https://github.com/l1npengtul/nokhwa/tree/senpai/nokhwa-bindings-windows) source code to see the internal raw interface.
/// - [`raw_supported_camera_controls()`](CaptureBackendTrait::raw_supported_camera_controls), [`raw_camera_control()`](CaptureBackendTrait::raw_camera_control), [`set_raw_camera_control()`](CaptureBackendTrait::set_raw_camera_control) is **not** supported.
/// - 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.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-msmf")))]
pub struct MediaFoundationCaptureDevice<'a> {
inner: MediaFoundationDevice<'a>,
info: CameraInfo,
}
impl<'a> MediaFoundationCaptureDevice<'a> {
/// Creates a new capture device using the Media Foundation 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.
/// # Errors
/// 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(),
mf_device.index(),
);
Ok(MediaFoundationCaptureDevice {
inner: mf_device,
info,
})
}
/// Create a new Media Foundation Device with desired settings.
/// # Errors
/// This function will error if Media Foundation fails to get the device.
pub fn new_with(
index: usize,
width: u32,
height: u32,
fps: u32,
fourcc: FrameFormat,
) -> Result<Self, NokhwaError> {
let camera_format = Some(CameraFormat::new_from(width, height, fourcc, fps));
MediaFoundationCaptureDevice::new(index, camera_format)
}
}
impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::MediaFoundation
}
fn camera_info(&self) -> &CameraInfo {
&self.info
}
fn camera_format(&self) -> CameraFormat {
self.inner.format().into()
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
if let Err(why) = self.inner.set_format(new_fmt.into()) {
return Err(why.into());
}
Ok(())
}
fn compatible_list_by_resolution(
&mut self,
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
let mf_camera_format_list = self.inner.compatible_format_list()?;
let mut resolution_map: HashMap<Resolution, Vec<u32>> = HashMap::new();
for mf_camera_format in mf_camera_format_list {
let camera_format: CameraFormat = mf_camera_format.into();
// check fcc
if camera_format.format() != fourcc {
continue;
}
match resolution_map.get_mut(&camera_format.resolution()) {
Some(fps_list) => {
fps_list.push(camera_format.frame_rate());
}
None => {
if let Some(mut wtf_why_we_here_list) = resolution_map
.insert(camera_format.resolution(), vec![camera_format.frame_rate()])
{
wtf_why_we_here_list.push(camera_format.frame_rate());
resolution_map.insert(camera_format.resolution(), wtf_why_we_here_list);
}
}
}
}
Ok(resolution_map)
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
let mf_camera_format_list = self.inner.compatible_format_list()?;
let mut frame_format_list = vec![];
for mf_camera_format in mf_camera_format_list {
let camera_format: CameraFormat = mf_camera_format.into();
if !frame_format_list.contains(&camera_format.format()) {
frame_format_list.push(camera_format.format());
}
// TODO: Update as we get more frame formats!
if frame_format_list.len() == 2 {
break;
}
}
Ok(frame_format_list)
}
fn resolution(&self) -> Resolution {
self.camera_format().resolution()
}
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
let mut new_format = self.camera_format();
new_format.set_resolution(new_res);
self.set_camera_format(new_format)
}
fn frame_rate(&self) -> u32 {
self.camera_format().frame_rate()
}
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
let mut new_format = self.camera_format();
new_format.set_frame_rate(new_fps);
self.set_camera_format(new_format)
}
fn frame_format(&self) -> FrameFormat {
self.camera_format().format()
}
fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
let mut new_format = self.camera_format();
new_format.set_format(fourcc);
self.set_camera_format(new_format)
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
let mut supported_camera_controls: Vec<KnownCameraControls> = vec![];
for camera_control in all_known_camera_controls() {
let msmf_camera_control: MediaFoundationControls = match camera_control {
KnownCameraControls::Brightness => MediaFoundationControls::Brightness,
KnownCameraControls::Contrast => MediaFoundationControls::Contrast,
KnownCameraControls::Hue => MediaFoundationControls::Hue,
KnownCameraControls::Saturation => MediaFoundationControls::Saturation,
KnownCameraControls::Sharpness => MediaFoundationControls::Sharpness,
KnownCameraControls::Gamma => MediaFoundationControls::Gamma,
KnownCameraControls::ColorEnable => MediaFoundationControls::ColorEnable,
KnownCameraControls::WhiteBalance => MediaFoundationControls::WhiteBalance,
KnownCameraControls::BacklightComp => MediaFoundationControls::BacklightComp,
KnownCameraControls::Gain => MediaFoundationControls::Gain,
KnownCameraControls::Pan => MediaFoundationControls::Pan,
KnownCameraControls::Tilt => MediaFoundationControls::Tilt,
KnownCameraControls::Roll => MediaFoundationControls::Roll,
KnownCameraControls::Zoom => MediaFoundationControls::Zoom,
KnownCameraControls::Exposure => MediaFoundationControls::Exposure,
KnownCameraControls::Iris => MediaFoundationControls::Iris,
KnownCameraControls::Focus => MediaFoundationControls::Focus,
};
if let Ok(supported) = self.inner.control(msmf_camera_control) {
supported_camera_controls.push(supported.control().into());
}
}
Ok(supported_camera_controls)
}
fn camera_control(&self, control: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
let msmf_camera_control: MediaFoundationControls = match control {
KnownCameraControls::Brightness => MediaFoundationControls::Brightness,
KnownCameraControls::Contrast => MediaFoundationControls::Contrast,
KnownCameraControls::Hue => MediaFoundationControls::Hue,
KnownCameraControls::Saturation => MediaFoundationControls::Saturation,
KnownCameraControls::Sharpness => MediaFoundationControls::Sharpness,
KnownCameraControls::Gamma => MediaFoundationControls::Gamma,
KnownCameraControls::ColorEnable => MediaFoundationControls::ColorEnable,
KnownCameraControls::WhiteBalance => MediaFoundationControls::WhiteBalance,
KnownCameraControls::BacklightComp => MediaFoundationControls::BacklightComp,
KnownCameraControls::Gain => MediaFoundationControls::Gain,
KnownCameraControls::Pan => MediaFoundationControls::Pan,
KnownCameraControls::Tilt => MediaFoundationControls::Tilt,
KnownCameraControls::Roll => MediaFoundationControls::Roll,
KnownCameraControls::Zoom => MediaFoundationControls::Zoom,
KnownCameraControls::Exposure => MediaFoundationControls::Exposure,
KnownCameraControls::Iris => MediaFoundationControls::Iris,
KnownCameraControls::Focus => MediaFoundationControls::Focus,
};
let ctrl = match self.inner.control(msmf_camera_control) {
Ok(ctrl) => ctrl,
Err(why) => return Err(why.into()),
};
let flag = if ctrl.manual() {
KnownCameraControlFlag::Manual
} else {
KnownCameraControlFlag::Automatic
};
let min = MFControl::min(&ctrl);
let max = MFControl::max(&ctrl);
CameraControl::new(
control,
min,
max,
ctrl.current(),
ctrl.step(),
ctrl.default(),
flag,
ctrl.active(),
)
}
fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError> {
let ctrl = match control.control() {
KnownCameraControls::Brightness => MediaFoundationControls::Brightness,
KnownCameraControls::Contrast => MediaFoundationControls::Contrast,
KnownCameraControls::Hue => MediaFoundationControls::Hue,
KnownCameraControls::Saturation => MediaFoundationControls::Saturation,
KnownCameraControls::Sharpness => MediaFoundationControls::Sharpness,
KnownCameraControls::Gamma => MediaFoundationControls::Gamma,
KnownCameraControls::ColorEnable => MediaFoundationControls::ColorEnable,
KnownCameraControls::WhiteBalance => MediaFoundationControls::WhiteBalance,
KnownCameraControls::BacklightComp => MediaFoundationControls::BacklightComp,
KnownCameraControls::Gain => MediaFoundationControls::Gain,
KnownCameraControls::Pan => MediaFoundationControls::Pan,
KnownCameraControls::Tilt => MediaFoundationControls::Tilt,
KnownCameraControls::Roll => MediaFoundationControls::Roll,
KnownCameraControls::Zoom => MediaFoundationControls::Zoom,
KnownCameraControls::Exposure => MediaFoundationControls::Exposure,
KnownCameraControls::Iris => MediaFoundationControls::Iris,
KnownCameraControls::Focus => MediaFoundationControls::Focus,
};
let flag = match control.flag() {
KnownCameraControlFlag::Automatic => false,
KnownCameraControlFlag::Manual => true,
};
let msmf_camera_control = MFControl::new(
ctrl,
control.minimum_value(),
control.maximum_value(),
control.step(),
control.value(),
control.default(),
flag,
control.active(),
);
if let Err(why) = self.inner.set_control(msmf_camera_control) {
return Err(why.into());
}
Ok(())
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::MediaFoundation,
))
}
fn raw_camera_control(&self, _control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::MediaFoundation,
))
}
fn set_raw_camera_control(
&mut self,
_control: &dyn Any,
_value: &dyn Any,
) -> Result<(), NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::MediaFoundation,
))
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
if let Err(why) = self.inner.start_stream() {
return Err(why.into());
}
Ok(())
}
fn is_stream_open(&self) -> bool {
self.inner.is_stream_open()
}
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
let camera_format = self.camera_format();
let raw_data = self.frame_raw()?;
let conv = match camera_format.format() {
FrameFormat::MJPEG => mjpeg_to_rgb888(raw_data.as_ref())?,
FrameFormat::YUYV => yuyv422_to_rgb888(raw_data.as_ref())?,
};
let imagebuf =
match ImageBuffer::from_vec(camera_format.width(), camera_format.height(), conv) {
Some(buf) => {
let rgbbuf: ImageBuffer<Rgb<u8>, Vec<u8>> = buf;
rgbbuf
}
None => return Err(NokhwaError::ReadFrameError(
"Imagebuffer is not large enough! This is probably a bug, please report it!"
.to_string(),
)),
};
Ok(imagebuf)
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
match self.inner.raw_bytes() {
Ok(data) => Ok(data),
Err(why) => Err(why.into()),
}
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
self.inner.stop_stream();
Ok(())
}
}
+303
View File
@@ -0,0 +1,303 @@
/*
* 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 nokhwa_bindings_windows::wmf::MediaFoundationDevice;
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
traits::CaptureBackendTrait,
types::{
all_known_camera_controls, ApiBackend, CameraControl, CameraFormat, CameraIndex,
CameraInfo, ControlValueSetter, FrameFormat, KnownCameraControl, RequestedFormat,
Resolution,
},
};
use std::{borrow::Cow, collections::HashMap};
/// The backend that deals with Media Foundation on Windows.
/// To see what this does, please see [`CaptureBackendTrait`].
///
/// Note: This requires Windows 7 or newer to work.
/// # Quirks
/// - This does build on non-windows platforms, however when you do the backend will be empty and will return an error for any given operation.
/// - Please check [`nokhwa-bindings-windows`](https://github.com/l1npengtul/nokhwa/tree/senpai/nokhwa-bindings-windows) source code to see the internal raw interface.
/// - [`raw_supported_camera_controls()`](CaptureBackendTrait::raw_supported_camera_controls), [`raw_camera_control()`](CaptureBackendTrait::raw_camera_control), [`set_raw_camera_control()`](CaptureBackendTrait::set_raw_camera_control) is **not** supported.
/// - 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.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-msmf")))]
pub struct MediaFoundationCaptureDevice<'a> {
inner: MediaFoundationDevice<'a>,
info: CameraInfo,
}
impl<'a> MediaFoundationCaptureDevice<'a> {
/// Creates a new capture device using the Media Foundation backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
/// # Errors
/// This function will error if Media Foundation fails to get the device.
pub fn new(index: &CameraIndex, camera_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
let mut mf_device = MediaFoundationDevice::new(index.clone())?;
let info = CameraInfo::new(
&mf_device.name(),
&"MediaFoundation Camera Device".to_string(),
&mf_device.symlink(),
index.clone(),
);
let availible = mf_device
.compatible_format_list()?
.into_iter()
.map(|x| {
let cf: CameraFormat = x.into();
cf
})
.collect::<Vec<CameraFormat>>();
let desired = camera_fmt
.fufill(&availible)
.ok_or(NokhwaError::InitializeError {
backend: ApiBackend::MediaFoundation,
error: "Failed to fulfill requested format".to_string(),
})?;
mf_device.set_format(desired.into())?;
let mut new_cam = MediaFoundationCaptureDevice {
inner: mf_device,
info,
};
new_cam.refresh_camera_format()?;
Ok(new_cam)
}
/// Create a new Media Foundation Device with desired settings.
/// # Errors
/// This function will error if Media Foundation fails to get the device.
#[deprecated(since = "0.10", note = "please use `new` instead.")]
pub fn new_with(
index: &CameraIndex,
width: u32,
height: u32,
fps: u32,
fourcc: FrameFormat,
) -> Result<Self, NokhwaError> {
let camera_format =
RequestedFormat::Exact(CameraFormat::new_from(width, height, fourcc, fps));
MediaFoundationCaptureDevice::new(index, camera_format)
}
/// Gets the list of supported [`KnownCameraControl`]s
/// # Errors
/// May error if there is an error from `MediaFoundation`.
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControl>, NokhwaError> {
let mut supported_camera_controls: Vec<KnownCameraControl> = vec![];
for camera_control in all_known_camera_controls() {
let msmf_camera_control: MediaFoundationControls = match camera_control {
KnownCameraControl::Brightness => MediaFoundationControls::Brightness,
KnownCameraControl::Contrast => MediaFoundationControls::Contrast,
KnownCameraControl::Hue => MediaFoundationControls::Hue,
KnownCameraControl::Saturation => MediaFoundationControls::Saturation,
KnownCameraControl::Sharpness => MediaFoundationControls::Sharpness,
KnownCameraControl::Gamma => MediaFoundationControls::Gamma,
KnownCameraControl::WhiteBalance => MediaFoundationControls::WhiteBalance,
KnownCameraControl::BacklightComp => MediaFoundationControls::BacklightComp,
KnownCameraControl::Gain => MediaFoundationControls::Gain,
KnownCameraControl::Pan => MediaFoundationControls::Pan,
KnownCameraControl::Tilt => MediaFoundationControls::Tilt,
KnownCameraControl::Zoom => MediaFoundationControls::Zoom,
KnownCameraControl::Exposure => MediaFoundationControls::Exposure,
KnownCameraControl::Iris => MediaFoundationControls::Iris,
KnownCameraControl::Focus => MediaFoundationControls::Focus,
KnownCameraControl::Other(id) => match id {
0 => MediaFoundationControls::ColorEnable,
1 => MediaFoundationControls::Roll,
_ => continue,
},
};
if let Ok(supported) = self.inner.control(msmf_camera_control) {
supported_camera_controls.push(supported.control().into());
}
}
Ok(supported_camera_controls)
}
}
impl<'a> CaptureBackendTrait for MediaFoundationCaptureDevice<'a> {
fn backend(&self) -> ApiBackend {
ApiBackend::MediaFoundation
}
fn camera_info(&self) -> &CameraInfo {
&self.info
}
fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
let _ = self.inner.format_refreshed()?;
Ok(())
}
fn camera_format(&self) -> CameraFormat {
self.inner.format().into()
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
if let Err(why) = self.inner.set_format(new_fmt.into()) {
return Err(why.into());
}
Ok(())
}
fn compatible_list_by_resolution(
&mut self,
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
let mf_camera_format_list = self.inner.compatible_format_list()?;
let mut resolution_map: HashMap<Resolution, Vec<u32>> = HashMap::new();
for mf_camera_format in mf_camera_format_list {
let camera_format: CameraFormat = mf_camera_format.into();
// check fcc
if camera_format.format() != fourcc {
continue;
}
match resolution_map.get_mut(&camera_format.resolution()) {
Some(fps_list) => {
fps_list.push(camera_format.frame_rate());
}
None => {
if let Some(mut wtf_why_we_here_list) = resolution_map
.insert(camera_format.resolution(), vec![camera_format.frame_rate()])
{
wtf_why_we_here_list.push(camera_format.frame_rate());
resolution_map.insert(camera_format.resolution(), wtf_why_we_here_list);
}
}
}
}
Ok(resolution_map)
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
let mf_camera_format_list = self.inner.compatible_format_list()?;
let mut frame_format_list = vec![];
for mf_camera_format in mf_camera_format_list {
let camera_format: CameraFormat = mf_camera_format.into();
if !frame_format_list.contains(&camera_format.format()) {
frame_format_list.push(camera_format.format());
}
// TODO: Update as we get more frame formats!
if frame_format_list.len() == 2 {
break;
}
}
Ok(frame_format_list)
}
fn resolution(&self) -> Resolution {
self.camera_format().resolution()
}
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
let mut new_format = self.camera_format();
new_format.set_resolution(new_res);
self.set_camera_format(new_format)
}
fn frame_rate(&self) -> u32 {
self.camera_format().frame_rate()
}
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
let mut new_format = self.camera_format();
new_format.set_frame_rate(new_fps);
self.set_camera_format(new_format)
}
fn frame_format(&self) -> FrameFormat {
self.camera_format().format()
}
fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
let mut new_format = self.camera_format();
new_format.set_format(fourcc);
self.set_camera_format(new_format)
}
fn camera_control(&self, control: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
self.inner.control(control)
}
fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
let mut camera_ctrls = Vec::with_capacity(15);
for ctrl_id in all_known_camera_controls() {
let ctrl = match self.camera_control(ctrl_id) {
Ok(v) => v,
Err(_) => continue,
};
camera_ctrls.push(ctrl);
}
camera_ctrls.shrink_to_fit();
Ok(camera_ctrls)
}
fn set_camera_control(
&mut self,
id: KnownCameraControl,
value: ControlValueSetter,
) -> Result<(), NokhwaError> {
self.inner.set_control(id, value)
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
if let Err(why) = self.inner.start_stream() {
return Err(why.into());
}
Ok(())
}
fn is_stream_open(&self) -> bool {
self.inner.is_stream_open()
}
fn frame(&mut self) -> Result<Buffer, NokhwaError> {
self.refresh_camera_format()?;
let self_ctrl = self.camera_format();
Ok(Buffer::new(
self_ctrl.resolution(),
&self.inner.raw_bytes()?,
self_ctrl.format(),
))
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
self.inner.raw_bytes()
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
self.inner.stop_stream();
Ok(())
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
use crate::{backends::capture::OpenCvCaptureDevice, CaptureBackendTrait, NokhwaError};
use crate::backends::capture::OpenCvCaptureDevice;
use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage};
use std::cell::RefCell;
use nokhwa_core::{error::NokhwaError, traits::CaptureBackendTrait};
use std::{borrow::Cow, cell::RefCell, collections::HashMap};
#[cfg(feature = "output-wgpu")]
use wgpu::{
Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue,
@@ -26,6 +27,10 @@ use wgpu::{
/// A struct that supports IP Cameras via the `OpenCV` backend.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))]
#[deprecated(
since = "0.10.0",
note = "please use `Camera` with `CameraIndex::String` and `input-opencv` enabled."
)]
pub struct NetworkCamera {
ip: String,
opencv_backend: RefCell<OpenCvCaptureDevice>,
@@ -60,19 +65,19 @@ impl NetworkCamera {
/// Opens stream.
/// # Errors
/// If the backend fails to capture the stream this will error
pub fn open_stream(&self) -> Result<(), NokhwaError> {
fn open_stream(&self) -> Result<(), NokhwaError> {
self.opencv_backend.borrow_mut().open_stream()
}
/// Gets the frame decoded as a RGB24 frame
/// # Errors
/// If the backend fails to capture the stream, or if the decoding fails this will error
pub fn frame(&self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
fn frame(&self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
self.opencv_backend.borrow_mut().frame()
}
/// 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.
pub fn min_buffer_size(&self, rgba: bool) -> usize {
fn min_buffer_size(&self, rgba: bool) -> usize {
let resolution = self.opencv_backend.borrow().resolution();
if rgba {
return (resolution.width() * resolution.height() * 4) as usize;
@@ -82,11 +87,7 @@ impl NetworkCamera {
/// 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(
&self,
buffer: &mut [u8],
convert_rgba: bool,
) -> Result<usize, NokhwaError> {
fn frame_to_buffer(&self, buffer: &mut [u8], convert_rgba: bool) -> Result<usize, NokhwaError> {
let frame = self.frame()?;
let mut frame_data = frame.to_vec();
if convert_rgba {
@@ -102,13 +103,13 @@ impl NetworkCamera {
/// 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>(
fn frame_texture<'a>(
&mut self,
device: &WgpuDevice,
queue: &WgpuQueue,
label: Option<&'a str>,
) -> Result<WgpuTexture, NokhwaError> {
use std::{convert::TryFrom, num::NonZeroU32};
use std::num::NonZeroU32;
let frame = self.frame()?;
let rgba_frame: RgbaImage = frame.convert();
@@ -160,13 +161,13 @@ impl NetworkCamera {
/// Will drop the stream.
/// # Errors
/// Please check the `Quirks` section of each backend.
pub fn stop_stream(&mut self) -> Result<(), NokhwaError> {
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
self.opencv_backend.borrow_mut().stop_stream()
}
}
impl Drop for NetworkCamera {
fn drop(&mut self) {
self.stop_stream().unwrap();
let _stop_stream_err = self.stop_stream();
}
}
+209 -226
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -14,16 +14,22 @@
* limitations under the License.
*/
use crate::{
CameraControl, CameraFormat, CameraIndexType, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use nokhwa_core::{
error::NokhwaError,
pixel_format::FormatDecoder,
traits::CaptureBackendTrait,
types::{
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueDescription,
ControlValueSetter, FrameFormat, KnownCameraControl, RequestedFormat, Resolution,
},
};
use opencv::{
core::{Mat, MatTraitConst, MatTraitConstManual, Vec3b},
videoio::{
VideoCapture, VideoCaptureTrait, VideoCaptureTraitConst, CAP_ANY, CAP_AVFOUNDATION,
CAP_MSMF, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2,
CAP_MSMF, CAP_PROP_FOURCC, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH,
CAP_V4L2,
},
};
use std::{any::Any, borrow::Cow, collections::HashMap};
@@ -50,7 +56,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 +76,7 @@ macro_rules! tryinto_num {
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-opencv")))]
pub struct OpenCvCaptureDevice {
camera_format: CameraFormat,
camera_location: CameraIndexType,
camera_location: CameraIndex,
camera_info: CameraInfo,
api_preference: i32,
video_capture: VideoCapture,
@@ -79,7 +84,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,130 +94,70 @@ 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@15 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. This camera **cannot** resolve any request that is not
/// [`RequestedFormat::Exact`] or [`RequestedFormat::None`].
/// # 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.
/// # Panics
/// If the API u32 -> i32 fails this will error
pub fn new(
camera_location: CameraIndexType,
cfmt: Option<CameraFormat>,
api_pref: Option<u32>,
) -> Result<Self, NokhwaError> {
let api = if let Some(a) = api_pref {
tryinto_num!(i32, a)
pub fn new(index: &CameraIndex, cfmt: RequestedFormat) -> Result<Self, NokhwaError> {
let api_pref = if index.is_string() {
CAP_ANY
} else {
tryinto_num!(i32, get_api_pref_int())
get_api_pref_int()
};
let mut index = i32::MAX as u32;
let mut video_capture = match &index {
CameraIndex::Index(idx) => VideoCapture::new(*idx as i32, api_pref),
CameraIndex::String(ip) => VideoCapture::from_file(ip.as_str(), api_pref),
}
.map_err(|why| {
NokhwaError::OpenDeviceError(format!("Failed to open {}", index), why.to_string())
})?;
let camera_format = match cfmt {
Some(cam_fmt) => cam_fmt,
None => CameraFormat::default(),
let real = match cfmt {
RequestedFormat::Exact(e) => e,
RequestedFormat::None => CameraFormat::default(),
_ => return Err(NokhwaError::UnsupportedOperationError(ApiBackend::OpenCv)),
};
let mut video_capture = match camera_location.clone() {
CameraIndexType::Index(idx) => {
let vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) {
Ok(vc) => {
index = idx;
vc
}
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
idx.to_string(),
why.to_string(),
))
}
};
vid_cap
}
CameraIndexType::IPCamera(ip) => match VideoCapture::from_file(&*ip, CAP_ANY) {
Ok(vc) => vc,
Err(why) => return Err(NokhwaError::OpenDeviceError(ip, why.to_string())),
},
};
set_properties(&mut video_capture, camera_format, &camera_location)?;
set_properties(&mut video_capture, real)?;
let camera_info = CameraInfo::new(
format!("OpenCV Capture Device {}", camera_location),
camera_location.to_string(),
"".to_string(),
index as usize,
format!("OpenCV Capture Device {}", index).as_str(),
index.to_string().as_str(),
"",
index.clone(),
);
Ok(OpenCvCaptureDevice {
camera_format,
camera_location,
camera_location: index.clone(),
camera_info,
api_preference: api,
video_capture,
})
}
/// Creates a new capture device using the `OpenCV` backend.
///
/// `IPCameras` follow the format
/// ```.ignore
/// <protocol>://<IP>:<port>/
/// ```
/// , but please refer to the manufacturer for the actual IP format.
///
/// 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: 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@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(
index: usize,
cfmt: Option<CameraFormat>,
api_pref: Option<u32>,
) -> Result<Self, NokhwaError> {
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@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: 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 {
CameraIndexType::Index(_) => false,
CameraIndexType::IPCamera(_) => true,
CameraIndex::Index(_) => false,
CameraIndex::String(_) => true,
}
}
/// Gets weather said capture device is an OS-based indexed camera.
pub fn is_index_camera(&self) -> bool {
match self.camera_location {
CameraIndexType::Index(_) => true,
CameraIndexType::IPCamera(_) => false,
CameraIndex::Index(_) => true,
CameraIndex::String(_) => false,
}
}
/// Gets the camera location
pub fn camera_location(&self) -> CameraIndexType {
self.camera_location.clone()
pub fn camera_location(&self) -> &CameraIndex {
&self.camera_location
}
/// Gets the `OpenCV` API Preference number. Please refer to [`OpenCV VideoCapture Flag Docs`](https://docs.opencv.org/4.5.2/d4/d15/group__videoio__flags__base.html).
@@ -339,14 +284,43 @@ impl OpenCvCaptureDevice {
}
impl CaptureBackendTrait for OpenCvCaptureDevice {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::OpenCv
fn backend(&self) -> ApiBackend {
ApiBackend::OpenCv
}
fn camera_info(&self) -> &CameraInfo {
&self.camera_info
}
fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
let width = vc
.set(CAP_PROP_FRAME_WIDTH, camera_format.width() as f64)
.map_err(|why| NokhwaError::SetPropertyError {
property: "Resolution Width".to_string(),
value: camera_format.to_string(),
error: why.to_string(),
})? as u32;
let height = vc
.set(CAP_PROP_FRAME_HEIGHT, camera_format.height() as f64)
.map_err(|why| NokhwaError::SetPropertyError {
property: "Resolution Height".to_string(),
value: camera_format.to_string(),
error: why.to_string(),
})? as u32;
let fps = vc
.get(CAP_PROP_FPS, camera_format.frame_rate() as f64)
.map_err(|why| NokhwaError::SetPropertyError {
property: "FPS".to_string(),
value: camera_format.to_string(),
error: why.to_string(),
})? as u32;
let ffmt = self.frame_format();
self.set_camera_format(CameraFormat::new_from(width, height, ffmt, fps))?;
Ok(())
}
fn camera_format(&self) -> CameraFormat {
self.camera_format
}
@@ -365,7 +339,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_format = current_format;
return Err(why);
}
@@ -385,15 +359,11 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
&mut self,
_fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::OpenCv,
))
Err(NokhwaError::UnsupportedOperationError(ApiBackend::OpenCv))
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::OpenCv,
))
Err(NokhwaError::UnsupportedOperationError(ApiBackend::OpenCv))
}
fn resolution(&self) -> Resolution {
@@ -427,128 +397,105 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
self.set_camera_format(current_fmt)
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
fn camera_control(&self, _control: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
fn set_camera_control(&mut self, _control: CameraControl) -> Result<(), NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
fn raw_camera_control(&self, control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
let id = match control.downcast_ref::<i32>() {
Some(id) => *id,
None => {
return Err(NokhwaError::StructureError {
structure: "OpenCV ID".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
match self.video_capture.get(id) {
Ok(v) => Ok(Box::new(v)),
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: format!("OpenCV PROP ID {}", id),
error: why.to_string(),
})
}
}
}
fn set_raw_camera_control(
&mut self,
control: &dyn Any,
value: &dyn Any,
) -> Result<(), NokhwaError> {
let id = match control.downcast_ref::<i32>() {
Some(id) => *id,
None => {
return Err(NokhwaError::StructureError {
structure: "OpenCV ID".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
let value = match value.downcast_ref::<f64>() {
Some(id) => *id,
None => {
return Err(NokhwaError::StructureError {
structure: "OpenCV Value".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
match self.video_capture.set(id, value) {
Ok(b) => {
if b {
return Ok(());
}
Err(NokhwaError::SetPropertyError {
property: format!("OpenCV PROP ID {}", id),
value: value.to_string(),
error: "OpenCV returned false".to_string(),
})
}
Err(why) => Err(NokhwaError::SetPropertyError {
property: format!("OpenCV PROP ID {}", id),
value: value.to_string(),
fn camera_control(&self, control: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
let id: i32 = control.into();
let current = self
.video_capture
.get(id)
.map_err(|x| NokhwaError::GetPropertyError {
property: id.to_string(),
error: why.to_string(),
}),
})?;
Ok(CameraControl::new(
control,
id.to_string(),
ControlValueDescription::Float {
value: current,
default: 0.0,
step: 0.0,
},
vec![],
true,
))
}
fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(ApiBackend::OpenCv))
}
fn set_camera_control(
&mut self,
id: KnownCameraControl,
value: ControlValueSetter,
) -> Result<(), NokhwaError> {
let control_val = match value {
ControlValueSetter::Integer(i) => i as f64,
ControlValueSetter::Float(f) => f,
ControlValueSetter::Boolean(b) => b as f64,
val => {
return Err(NokhwaError::SetPropertyError {
property: "Camera Control".to_string(),
value: val.to_string(),
error: "unsupported value".to_string(),
})
}
};
if !self
.video_capture
.set(id.into(), control_val)
.map_err(|why| NokhwaError::SetPropertyError {
property: "Camera Control".to_string(),
value: control_val.to_string(),
error: why.to_string(),
})?
{
return Err(NokhwaError::SetPropertyError {
property: "Camera Control".to_string(),
value: control_val.to_string(),
error: "false".to_string(),
});
}
let set_value = self.camera_control(id)?.value();
if set_value != value {
return Err(NokhwaError::SetPropertyError {
property: "Camera Control".to_string(),
value: control_val.to_string(),
error: "failed to set value: rejected".to_string(),
});
}
Ok(())
}
#[allow(clippy::cast_possible_wrap)]
fn open_stream(&mut self) -> Result<(), NokhwaError> {
match self.camera_location.clone() {
CameraIndexType::Index(idx) => {
CameraIndex::Index(idx) => {
match self
.video_capture
.open_1(idx as i32, get_api_pref_int() as i32)
{
Ok(_) => {}
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
idx.to_string(),
format!("Failed to open device: {}", why),
Ok(open) => {
if open {
return Ok(());
}
Err(NokhwaError::OpenStreamError(
"Stream is not opened after stream open attempt opencv".to_string(),
))
}
Err(why) => Err(NokhwaError::OpenDeviceError(
idx.to_string(),
format!("Failed to open device: {}", why),
)),
}
}
CameraIndexType::IPCamera(ip) => {
match self
.video_capture
.open_file(&*ip, get_api_pref_int() as i32)
{
Ok(_) => {}
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
ip,
format!("Failed to open device: {}", why),
))
}
}
}
};
CameraIndex::String(_) => Err(NokhwaError::OpenDeviceError(
"Cannot open".to_string(),
"String index not supported (try NetworkCamera instead)".to_string(),
)),
}?;
match self.video_capture.is_opened() {
Ok(open) => {
@@ -611,23 +558,59 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
}
}
fn get_api_pref_int() -> u32 {
fn get_api_pref_int() -> i32 {
match std::env::consts::OS {
"linux" => CAP_V4L2 as u32,
"windows" => CAP_MSMF as u32,
"mac" => CAP_AVFOUNDATION as u32,
&_ => CAP_ANY as u32,
"linux" => CAP_V4L2,
"windows" => CAP_MSMF,
"mac" => CAP_AVFOUNDATION,
&_ => CAP_ANY,
}
}
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::unnecessary_wraps)]
// I'm done. This stupid POS refuses to actually do anything useful with camera settings
// If anyone else wants to tackle this monster, please do.
fn set_properties(
_vc: &mut VideoCapture,
_camera_format: CameraFormat,
_camera_location: &CameraIndexType,
) -> Result<(), NokhwaError> {
fn set_properties(vc: &mut VideoCapture, camera_format: CameraFormat) -> Result<(), NokhwaError> {
if !vc
.set(CAP_PROP_FRAME_WIDTH, camera_format.width() as f64)
.map_err(|why| NokhwaError::SetPropertyError {
property: "Resolution Width".to_string(),
value: camera_format.to_string(),
error: why.to_string(),
})?
{
return Err(NokhwaError::SetPropertyError {
property: "Resolution Width".to_string(),
value: camera_format.to_string(),
error: "false".to_string(),
});
}
if !vc
.set(CAP_PROP_FRAME_HEIGHT, camera_format.height() as f64)
.map_err(|why| NokhwaError::SetPropertyError {
property: "Resolution Height".to_string(),
value: camera_format.to_string(),
error: why.to_string(),
})?
{
return Err(NokhwaError::SetPropertyError {
property: "Resolution Height".to_string(),
value: camera_format.to_string(),
error: "false".to_string(),
});
}
if !vc
.set(CAP_PROP_FPS, camera_format.frame_rate() as f64)
.map_err(|why| NokhwaError::SetPropertyError {
property: "FPS".to_string(),
value: camera_format.to_string(),
error: why.to_string(),
})?
{
return Err(NokhwaError::SetPropertyError {
property: "FPS".to_string(),
value: camera_format.to_string(),
error: "false".to_string(),
});
}
Ok(())
}
+19 -15
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -17,8 +17,8 @@
#![allow(clippy::too_many_arguments)]
use crate::{
CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControlFlag, KnownCameraControls, NokhwaError, Resolution,
ApiBackend, CameraControl, CameraFormat, CameraInfo, CaptureBackendTrait, FrameFormat,
KnownCameraControl, KnownCameraControlFlag, NokhwaError, Resolution,
};
use flume::{Receiver, Sender};
use image::{ImageBuffer, Rgb};
@@ -54,9 +54,13 @@ 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,
camera_info: CameraInfo<'a>,
frame_receiver: Receiver<Vec<u8>>,
frame_sender: Sender<Vec<u8>>,
stream_handle_init: Cell<bool>,
@@ -199,8 +203,8 @@ impl<'a> UVCCaptureDevice<'a> {
// IDE Autocomplete ends here. Do not be afraid it your IDE does not show completion.
// Here are some docs to help you out: https://docs.rs/ouroboros/0.9.3/ouroboros/attr.self_referencing.html
impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::UniversalVideoClass
fn backend(&self) -> ApiBackend {
ApiBackend::UniversalVideoClass
}
fn camera_info(&self) -> &CameraInfo {
@@ -325,16 +329,16 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
self.set_camera_format(current_format)
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControl>, NokhwaError> {
Ok(vec![
KnownCameraControls::Exposure,
KnownCameraControls::Focus,
KnownCameraControl::Exposure,
KnownCameraControl::Focus,
])
}
fn camera_control(&self, control: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
fn camera_control(&self, control: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
match control {
KnownCameraControls::Focus => match self.with_device_handle(|x| x).exposure_rel() {
KnownCameraControl::Focus => match self.with_device_handle(|x| x).exposure_rel() {
Ok(v) => {
let v: i8 = v;
match CameraControl::new(
@@ -368,19 +372,19 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
fn set_camera_control(&mut self, _control: CameraControl) -> Result<(), NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
ApiBackend::UniversalVideoClass,
))
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
ApiBackend::UniversalVideoClass,
))
}
fn raw_camera_control(&self, _control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
ApiBackend::UniversalVideoClass,
))
}
@@ -390,7 +394,7 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
_value: &dyn Any,
) -> Result<(), NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
ApiBackend::UniversalVideoClass,
))
}
-723
View File
@@ -1,723 +0,0 @@
/*
* Copyright 2021 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::{
error::NokhwaError,
mjpeg_to_rgb888,
utils::{CameraFormat, CameraInfo},
yuyv422_to_rgb888, CameraControl, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControlFlag, KnownCameraControls, Resolution,
};
use image::{ImageBuffer, Rgb};
use std::{borrow::Cow, collections::HashMap};
use v4l::{
buffer::Type,
frameinterval::FrameIntervalEnum,
framesize::FrameSizeEnum,
io::traits::CaptureStream,
prelude::*,
video::{capture::Parameters, Capture},
Format, FourCC,
};
use std::any::Any;
pub use v4l::control::{Control, Description, Flags};
/// Generates a camera control from a device and a description of control
/// # Error
/// If the control is not supported, the value is invalid or string, or the control is write only/the control cannot be read from,
/// this will error.
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_lossless)]
pub fn to_camera_control(
device: &Device,
value: &Description,
) -> Result<CameraControl, NokhwaError> {
// make sure flags is valid
if value.flags.contains(Flags::from(0x0040)) {
return Err(NokhwaError::NotImplementedError(
"Control Write Only!".to_string(),
));
}
let control: KnownCameraControls = match try_id_to_known_camera_control(value.id) {
Some(kcc) => kcc,
None => {
return Err(NokhwaError::StructureError {
structure: "KnownCameraControl".to_string(),
error: "Unsupported V4L2 ID".to_string(),
})
}
};
// value
let current_value = match device.control(value.id) {
Ok(val) => match val {
Control::Value(v) => v,
Control::Value64(v64) => {
if v64 <= i32::MAX as i64 {
v64 as i32
} else {
return Err(NokhwaError::GetPropertyError {
property: format!("Control V4L2ID: {}", value.id),
error: "Too large!".to_string(),
});
}
}
Control::String(_) => {
return Err(NokhwaError::GetPropertyError {
property: format!("Control V4L2ID: {}", value.id),
error: "Unsupported Type String".to_string(),
})
}
},
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: format!("Control V4L2ID: {}", value.id),
error: why.to_string(),
})
}
};
let active =
value.flags.contains(Flags::from(0x0001)) || value.flags.contains(Flags::from(0x0010));
CameraControl::new(
control,
value.minimum,
value.maximum,
current_value,
value.step,
value.default,
KnownCameraControlFlag::Manual,
active,
)
}
/// Attempts to convert a [`KnownCameraControls`] into a V4L2 Control ID.
/// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`)
pub fn try_known_camera_control_to_id(ctrl: KnownCameraControls) -> Option<u32> {
match ctrl {
KnownCameraControls::Brightness => Some(9_963_776),
KnownCameraControls::Contrast => Some(9_963_777),
KnownCameraControls::Hue => Some(9_963_779),
KnownCameraControls::Saturation => Some(9_963_778),
KnownCameraControls::Sharpness => Some(9_963_803),
KnownCameraControls::Gamma => Some(9_963_792),
KnownCameraControls::WhiteBalance => Some(9_963_802),
KnownCameraControls::BacklightComp => Some(9_963_804),
KnownCameraControls::Gain => Some(9_963_795),
KnownCameraControls::Pan => Some(10_094_852),
KnownCameraControls::Tilt => Some(100_948_530),
KnownCameraControls::Zoom => Some(10_094_862),
KnownCameraControls::Exposure => Some(10_094_850),
KnownCameraControls::Iris => Some(10_094_866),
KnownCameraControls::Focus => Some(10_094_859),
_ => None,
}
}
/// Attempts to convert a [`u32`] V4L2 Control ID into a [`KnownCameraControls`]
/// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`)
pub fn try_id_to_known_camera_control(id: u32) -> Option<KnownCameraControls> {
match id {
9_963_776 => Some(KnownCameraControls::Brightness),
9_963_777 => Some(KnownCameraControls::Contrast),
9_963_779 => Some(KnownCameraControls::Hue),
9_963_778 => Some(KnownCameraControls::Saturation),
9_963_803 => Some(KnownCameraControls::Sharpness),
9_963_792 => Some(KnownCameraControls::Gamma),
9_963_802 => Some(KnownCameraControls::WhiteBalance),
9_963_804 => Some(KnownCameraControls::BacklightComp),
9_963_795 => Some(KnownCameraControls::Gain),
10_094_852 => Some(KnownCameraControls::Pan),
100_948_530 => Some(KnownCameraControls::Tilt),
10_094_862 => Some(KnownCameraControls::Zoom),
10_094_850 => Some(KnownCameraControls::Exposure),
10_094_866 => Some(KnownCameraControls::Iris),
10_094_859 => Some(KnownCameraControls::Focus),
_ => None,
}
}
fn clone_control(ctrl: &Control) -> Control {
match ctrl {
Control::Value(v) => Control::Value(*v),
Control::Value64(v) => Control::Value64(*v),
Control::String(v) => Control::String(v.clone()),
}
}
/// The backend struct that interfaces with V4L2.
/// To see what this does, please see [`CaptureBackendTrait`].
/// # Quirks
/// - Calling [`set_resolution()`](CaptureBackendTrait::set_resolution), [`set_frame_rate()`](CaptureBackendTrait::set_frame_rate), or [`set_frame_format()`](CaptureBackendTrait::set_frame_format) each internally calls [`set_camera_format()`](CaptureBackendTrait::set_camera_format).
/// - The `Any` return type for [`raw_supported_camera_controls()`](CaptureBackendTrait::raw_supported_camera_controls) is [`Description`]
/// - The `Any` type for [`raw_camera_control()`](CaptureBackendTrait::raw_camera_control) is [`u32`], and its return `Any` is a [`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> {
camera_format: CameraFormat,
camera_info: CameraInfo,
device: Device,
stream_handle: Option<MmapStream<'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.
/// # Errors
/// 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) {
Ok(dev) => dev,
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
index.to_string(),
format!("V4L2 Error: {}", why),
))
}
};
let camera_info = match device.query_caps() {
Ok(caps) => CameraInfo::new(caps.card, "".to_string(), caps.driver, index),
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Capabilities".to_string(),
error: why.to_string(),
})
}
};
let camera_format = match cam_fmt {
Some(c_fmt) => c_fmt,
None => CameraFormat::default(),
};
let fourcc = match camera_format.format() {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
};
let new_param = Parameters::with_fps(camera_format.frame_rate());
let new_v4l_fmt = Format::new(camera_format.width(), camera_format.height(), fourcc);
match Capture::set_format(&device, &new_v4l_fmt) {
Ok(v4l_fmt) => {
if v4l_fmt.height != new_v4l_fmt.height
&& v4l_fmt.width != new_v4l_fmt.width
&& v4l_fmt.fourcc != new_v4l_fmt.fourcc
{
return Err(NokhwaError::SetPropertyError {
property: "Format(V4L Resolution, FourCC)".to_string(),
value: camera_format.to_string(),
error: "Rejected".to_string(),
});
}
}
Err(why) => {
return Err(NokhwaError::SetPropertyError {
property: "Format(V4L Resolution, FourCC)".to_string(),
value: camera_format.to_string(),
error: why.to_string(),
})
}
}
match Capture::set_params(&device, &new_param) {
Ok(param) => {
if new_param.interval.denominator != param.interval.denominator {
return Err(NokhwaError::SetPropertyError {
property: "Parameter(V4L FPS)".to_string(),
value: camera_format.frame_rate().to_string(),
error: "Rejected".to_string(),
});
}
}
Err(why) => {
return Err(NokhwaError::SetPropertyError {
property: "Parameter(V4L FPS)".to_string(),
value: camera_format.frame_rate().to_string(),
error: why.to_string(),
})
}
}
Ok(V4LCaptureDevice {
camera_format,
camera_info,
device,
stream_handle: None,
})
}
/// Create a new `V4L2` Camera with desired settings.
/// # Errors
/// This function will error if the camera is currently busy or if `V4L2` can't read device information.
pub fn new_with(
index: usize,
width: u32,
height: u32,
fps: u32,
fourcc: FrameFormat,
) -> Result<Self, NokhwaError> {
let camera_format = Some(CameraFormat::new_from(width, height, fourcc, fps));
V4LCaptureDevice::new(index, camera_format)
}
fn get_resolution_list(&self, fourcc: FrameFormat) -> Result<Vec<Resolution>, NokhwaError> {
let format = match fourcc {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
};
match v4l::video::Capture::enum_framesizes(&self.device, format) {
Ok(frame_sizes) => {
let mut resolutions = vec![];
for frame_size in frame_sizes {
match frame_size.size {
FrameSizeEnum::Discrete(dis) => {
resolutions.push(Resolution::new(dis.width, dis.height));
}
FrameSizeEnum::Stepwise(step) => {
resolutions.push(Resolution::new(step.min_width, step.min_height));
resolutions.push(Resolution::new(step.max_width, step.max_height));
// TODO: Respect step size
}
}
}
Ok(resolutions)
}
Err(why) => Err(NokhwaError::GetPropertyError {
property: "Resolutions".to_string(),
error: why.to_string(),
}),
}
}
/// Get the inner device (immutable) for e.g. Controls
#[allow(clippy::must_use_candidate)]
pub fn inner_device(&self) -> &Device {
&self.device
}
/// Get the inner device (mutable) for e.g. Controls
pub fn inner_device_mut(&mut self) -> &mut Device {
&mut self.device
}
}
impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::Video4Linux
}
fn camera_info(&self) -> &CameraInfo {
&self.camera_info
}
fn camera_format(&self) -> CameraFormat {
self.camera_format
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
let prev_format = match Capture::format(&self.device) {
Ok(fmt) => fmt,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Resolution, FrameFormat".to_string(),
error: why.to_string(),
})
}
};
let prev_fps = match Capture::params(&self.device) {
Ok(fps) => fps,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Frame rate".to_string(),
error: why.to_string(),
})
}
};
let format: Format = new_fmt.into();
let frame_rate = Parameters::with_fps(new_fmt.frame_rate());
if let Err(why) = Capture::set_format(&self.device, &format) {
return Err(NokhwaError::SetPropertyError {
property: "Resolution, FrameFormat".to_string(),
value: format.to_string(),
error: why.to_string(),
});
}
if let Err(why) = Capture::set_params(&self.device, &frame_rate) {
return Err(NokhwaError::SetPropertyError {
property: "Frame rate".to_string(),
value: frame_rate.to_string(),
error: why.to_string(),
});
}
if self.stream_handle.is_some() {
return match self.open_stream() {
Ok(_) => Ok(()),
Err(why) => {
// undo
if let Err(why) = Capture::set_format(&self.device, &prev_format) {
return Err(NokhwaError::SetPropertyError {
property: format!("Attempt undo due to stream acquisition failure with error {}. Resolution, FrameFormat", why),
value: prev_format.to_string(),
error: why.to_string(),
});
}
if let Err(why) = Capture::set_params(&self.device, &prev_fps) {
return Err(NokhwaError::SetPropertyError {
property:
format!("Attempt undo due to stream acquisition failure with error {}. Frame rate", why),
value: prev_fps.to_string(),
error: why.to_string(),
});
}
Err(why)
}
};
}
self.camera_format = new_fmt;
Ok(())
}
fn compatible_list_by_resolution(
&mut self,
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
let resolutions = self.get_resolution_list(fourcc)?;
let format = match fourcc {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
};
let mut res_map = HashMap::new();
for res in resolutions {
let mut compatible_fps = vec![];
match Capture::enum_frameintervals(&self.device, format, res.width(), res.height()) {
Ok(intervals) => {
for interval in intervals {
match interval.interval {
FrameIntervalEnum::Discrete(dis) => {
compatible_fps.push(dis.denominator);
}
FrameIntervalEnum::Stepwise(step) => {
compatible_fps.push(step.min.denominator);
compatible_fps.push(step.max.denominator);
}
}
}
}
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Frame rate".to_string(),
error: why.to_string(),
})
}
}
res_map.insert(res, compatible_fps);
}
Ok(res_map)
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
match Capture::enum_formats(&self.device) {
Ok(formats) => {
let mut frame_format_vec = vec![];
for format in formats {
let format_as_string = match format.fourcc.str() {
Ok(s) => s,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "FrameFormat".to_string(),
error: why.to_string(),
})
}
};
match format_as_string {
"YUYV" => frame_format_vec.push(FrameFormat::YUYV),
"MJPG" => frame_format_vec.push(FrameFormat::MJPEG),
_ => {}
}
}
frame_format_vec.sort();
frame_format_vec.dedup();
Ok(frame_format_vec)
}
Err(why) => Err(NokhwaError::GetPropertyError {
property: "FrameFormat".to_string(),
error: why.to_string(),
}),
}
}
fn resolution(&self) -> Resolution {
self.camera_format.resolution()
}
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
let mut new_fmt = self.camera_format;
new_fmt.set_resolution(new_res);
self.set_camera_format(new_fmt)
}
fn frame_rate(&self) -> u32 {
self.camera_format.frame_rate()
}
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
let mut new_fmt = self.camera_format;
new_fmt.set_frame_rate(new_fps);
self.set_camera_format(new_fmt)
}
fn frame_format(&self) -> FrameFormat {
self.camera_format.format()
}
fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
let mut new_fmt = self.camera_format;
new_fmt.set_format(fourcc);
self.set_camera_format(new_fmt)
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
let v4l2_controls = match self.device.query_controls() {
Ok(controls) => controls,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Controls".to_string(),
error: why.to_string(),
})
}
};
let mut camera_controls = vec![];
for ctrl in v4l2_controls {
if let Ok(cam_control) = to_camera_control(&self.device, &ctrl) {
camera_controls.push(cam_control.control());
}
}
Ok(camera_controls)
}
fn camera_control(&self, control: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
let id = match try_known_camera_control_to_id(control) {
Some(id) => id,
None => {
return Err(NokhwaError::GetPropertyError {
property: "KnownCameraControls V4L2ID".to_string(),
error: "Invalid".to_string(),
})
}
};
match self.device.control(id) {
Ok(_) => {
let v4l2_controls = match self.device.query_controls() {
Ok(controls) => controls,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Controls".to_string(),
error: why.to_string(),
})
}
};
for ctrl in v4l2_controls {
if ctrl.id != id {
continue;
}
if let Ok(cam_control) = to_camera_control(&self.device, &ctrl) {
if Some(ctrl.id) == try_known_camera_control_to_id(cam_control.control()) {
return Ok(cam_control);
}
}
}
}
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Camera Control".to_string(),
error: why.to_string(),
})
}
}
Err(NokhwaError::GetPropertyError {
property: "Camera Control".to_string(),
error: "Not Found".to_string(),
})
}
fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError> {
let id = match try_known_camera_control_to_id(control.control()) {
Some(id) => id,
None => {
return Err(NokhwaError::GetPropertyError {
property: "KnownCameraControls V4L2ID".to_string(),
error: "Invalid".to_string(),
})
}
};
if let Err(why) = self.device.set_control(id, Control::Value(control.value())) {
return Err(NokhwaError::SetPropertyError {
property: format!("{} V4L2ID {}", control.control(), id),
value: control.value().to_string(),
error: why.to_string(),
});
}
Ok(())
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
let v4l2_controls = match self.device.query_controls() {
Ok(controls) => controls,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Controls".to_string(),
error: why.to_string(),
})
}
};
Ok(v4l2_controls
.into_iter()
.map(|c| {
let a: Box<dyn Any> = Box::new(c);
a
})
.collect())
}
fn raw_camera_control(&self, control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
let id = match control.downcast_ref::<u32>() {
Some(id) => *id,
None => {
return Err(NokhwaError::StructureError {
structure: "V4L2 ID".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
match self.device.control(id) {
Ok(v) => Ok(Box::new(v)),
Err(why) => Err(NokhwaError::GetPropertyError {
property: "Control V4L2".to_string(),
error: why.to_string(),
}),
}
}
fn set_raw_camera_control(
&mut self,
control: &dyn Any,
value: &dyn Any,
) -> Result<(), NokhwaError> {
let id = match control.downcast_ref::<u32>() {
Some(id) => *id,
None => {
return Err(NokhwaError::StructureError {
structure: "V4L2 ID".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
let value = match value.downcast_ref::<Control>() {
Some(v) => match v {
Control::Value(v) => Control::Value(*v),
Control::Value64(v64) => Control::Value64(*v64),
Control::String(s) => Control::String(s.clone()),
},
None => {
return Err(NokhwaError::StructureError {
structure: "V4L2 Control Value".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
if let Err(why) = self.device.set_control(id, clone_control(&value)) {
return Err(NokhwaError::SetPropertyError {
property: format!("V4L2 Control ID {}", id),
value: format!("{:?}", value),
error: why.to_string(),
});
}
Ok(())
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
let stream = match MmapStream::new(&self.device, Type::VideoCapture) {
Ok(s) => s,
Err(why) => return Err(NokhwaError::OpenStreamError(why.to_string())),
};
self.stream_handle = Some(stream);
Ok(())
}
fn is_stream_open(&self) -> bool {
self.stream_handle.is_some()
}
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
let cam_fmt = self.camera_format;
let raw_frame = self.frame_raw()?;
let conv = match cam_fmt.format() {
FrameFormat::MJPEG => mjpeg_to_rgb888(&raw_frame)?,
FrameFormat::YUYV => yuyv422_to_rgb888(&raw_frame)?,
};
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)
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
match &mut self.stream_handle {
Some(stream_handler) => match stream_handler.next() {
Ok((data, _)) => Ok(Cow::from(data)),
Err(why) => Err(NokhwaError::ReadFrameError(why.to_string())),
},
None => Err(NokhwaError::ReadFrameError(
"Stream not initialized! Please call \"open_stream()\" first!".to_string(),
)),
}
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
if self.stream_handle.is_some() {
self.stream_handle = None;
}
Ok(())
}
}
+757
View File
@@ -0,0 +1,757 @@
/*
* 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 image::ImageBuffer;
use nokhwa_core::types::{CameraFormat, CameraInfo};
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
traits::CaptureBackendTrait,
types::{
mjpeg_to_rgb, yuyv422_to_rgb, ApiBackend, CameraControl, CameraIndex,
ControlValueDescription, ControlValueSetter, FrameFormat, KnownCameraControl,
KnownCameraControlFlag, RequestedFormat, Resolution,
},
};
use std::{
borrow::Cow,
collections::HashMap,
fmt::format,
io::{self, ErrorKind},
iter::Repeat,
};
use v4l::io::mmap::Stream;
use v4l::{
buffer::Type,
control::{Control, Flags, Value},
frameinterval::FrameIntervalEnum,
framesize::FrameSizeEnum,
io::traits::CaptureStream,
prelude::MmapStream,
video::{capture::Parameters, Capture},
Device, Format, FourCC, Fraction,
};
/// Attempts to convert a [`KnownCameraControl`] into a V4L2 Control ID.
/// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`)
pub fn known_camera_control_to_id(ctrl: KnownCameraControl) -> u32 {
match ctrl {
KnownCameraControl::Brightness => 9_963_776,
KnownCameraControl::Contrast => 9_963_777,
KnownCameraControl::Hue => 9_963_779,
KnownCameraControl::Saturation => 9_963_778,
KnownCameraControl::Sharpness => 9_963_803,
KnownCameraControl::Gamma => 9_963_792,
KnownCameraControl::WhiteBalance => 9_963_802,
KnownCameraControl::BacklightComp => 9_963_804,
KnownCameraControl::Gain => 9_963_795,
KnownCameraControl::Pan => 10_094_852,
KnownCameraControl::Tilt => 100_948_530,
KnownCameraControl::Zoom => 10_094_862,
KnownCameraControl::Exposure => 10_094_850,
KnownCameraControl::Iris => 10_094_866,
KnownCameraControl::Focus => 10_094_859,
KnownCameraControl::Other(id) => id as u32,
}
}
/// Attempts to convert a [`u32`] V4L2 Control ID into a [`KnownCameraControl`]
/// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`)
pub fn id_to_known_camera_control(id: u32) -> KnownCameraControl {
match id {
9_963_776 => KnownCameraControl::Brightness,
9_963_777 => KnownCameraControl::Contrast,
9_963_779 => KnownCameraControl::Hue,
9_963_778 => KnownCameraControl::Saturation,
9_963_803 => KnownCameraControl::Sharpness,
9_963_792 => KnownCameraControl::Gamma,
9_963_802 => KnownCameraControl::WhiteBalance,
9_963_804 => KnownCameraControl::BacklightComp,
9_963_795 => KnownCameraControl::Gain,
10_094_852 => KnownCameraControl::Pan,
100_948_530 => KnownCameraControl::Tilt,
10_094_862 => KnownCameraControl::Zoom,
10_094_850 => KnownCameraControl::Exposure,
10_094_866 => KnownCameraControl::Iris,
10_094_859 => KnownCameraControl::Focus,
id => KnownCameraControl::Other(id as u128),
}
}
/// The backend struct that interfaces with V4L2.
/// To see what this does, please see [`CaptureBackendTrait`].
/// # Quirks
/// - Calling [`set_resolution()`](CaptureBackendTrait::set_resolution), [`set_frame_rate()`](CaptureBackendTrait::set_frame_rate), or [`set_frame_format()`](CaptureBackendTrait::set_frame_format) each internally calls [`set_camera_format()`](CaptureBackendTrait::set_camera_format).
/// - The `Any` return type for [`raw_supported_camera_controls()`](CaptureBackendTrait::raw_supported_camera_controls) is [`Description`]
/// - The `Any` type for [`raw_camera_control()`](CaptureBackendTrait::raw_camera_control) is [`u32`], and its return `Any` is a [`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,
stream_handle: Option<MmapStream<'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 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.
pub fn new(index: &CameraIndex, cam_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
let mut device = match Device::new(index.as_index()? as usize) {
Ok(dev) => dev,
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
index.to_string(),
format!("V4L2 Error: {}", why),
))
}
};
// get all formats
// get all fcc
let mut camera_formats = vec![];
let frame_formats = match device.enum_formats() {
Ok(formats) => {
let mut frame_format_vec = vec![];
formats
.iter()
.for_each(|fmt| frame_format_vec.push(fmt.fourcc));
frame_format_vec.sort();
frame_format_vec.dedup();
Ok(frame_format_vec)
}
Err(why) => Err(NokhwaError::GetPropertyError {
property: "FrameFormat".to_string(),
error: why.to_string(),
}),
}?;
for ff in frame_formats {
let framefmt = match fourcc_to_frameformat(ff) {
Some(s) => s,
None => continue,
};
// i write unmaintainable blobs of code because i am so cute uwu~~
let mut formats = device
.enum_framesizes(ff)
.map_err(|why| NokhwaError::GetPropertyError {
property: "ResolutionList".to_string(),
error: why.to_string(),
})?
.into_iter()
.flat_map(|x| match x.size {
FrameSizeEnum::Discrete(d) => Some(d).into_iter(),
// we step over each step, getting a new resolution.
FrameSizeEnum::Stepwise(s) => (s.min_width..s.max_width)
.step_by(s.step_width as usize)
.zip((s.min_height..s.max_height).step_by(s.step_height as usize))
.map(|(x, y)| Resolution::new(x, y))
.into_iter(),
})
.flat_map(|res| {
device
.enum_frameintervals(ff, res.x(), res.y())?
.iter()
.flat_map(|x| match &x.interval {
FrameIntervalEnum::Discrete(dis) => {
if dis.denominator == 1 {
Some(CameraFormat::new(
Resolution::new(x.width, x.height),
framefmt,
dis.numerator,
))
.into_iter()
} else {
None.into_iter()
}
}
FrameIntervalEnum::Stepwise(step) => {
let mut intvec = vec![];
for fstep in (step.min.numerator..step.max.numerator)
.step_by(step.step.numerator as usize)
{
if step.max.denominator != 1 || step.min.denominator != 1 {
intvec.push(CameraFormat::new(
Resolution::new(x.width, x.height),
framefmt,
fstep,
))
}
}
intvec.into_iter()
}
})
})
.collect::<Result<Vec<CameraFormat>, NokhwaError>>()?;
camera_formats.append(&mut formats);
}
let format = cam_fmt
.fufill(&camera_formats)
.ok_or(NokhwaError::GetPropertyError {
property: "CameraFormat".to_string(),
error: "Failed to Fufill".to_string(),
})?;
if let Err(why) = device.set_format(&Format::new(
format.width(),
format.height(),
frameformat_to_fourcc(format.format()),
)) {
return Err(NokhwaError::SetPropertyError {
property: "Resolution, FrameFormat".to_string(),
value: format.to_string(),
error: why.to_string(),
});
}
if let Err(why) = device.set_params(&Parameters::with_fps(format.frame_rate())) {
return Err(NokhwaError::SetPropertyError {
property: "Frame rate".to_string(),
value: frame_rate.to_string(),
error: why.to_string(),
});
}
let mut v4l2 = V4LCaptureDevice {
initialized: false,
camera_format: format,
camera_info,
device,
stream_handle: None,
};
v4l2.force_refresh_camera_format()?;
if v4l2.camera_format() == format {
return Err(NokhwaError::SetPropertyError {
property: "CameraFormat".to_string(),
value: "".to_string(),
error: "Not same/Rejected".to_string(),
});
}
Ok(v4l2)
}
/// 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.
#[deprecated(since = "0.10.0", note = "please use `new` instead.")]
pub fn new_with(
index: CameraIndex,
width: u32,
height: u32,
fps: u32,
fourcc: FrameFormat,
) -> Result<Self, NokhwaError> {
let camera_format = CameraFormat::new_from(width, height, fourcc, fps);
V4LCaptureDevice::new(index, RequestedFormat::Exact(camera_format))
}
fn get_resolution_list(&self, fourcc: FrameFormat) -> Result<Vec<Resolution>, NokhwaError> {
let format = frameformat_to_fourcc(fourcc);
// 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 {
match frame_size.size {
FrameSizeEnum::Discrete(dis) => {
resolutions.push(Resolution::new(dis.width, dis.height));
}
FrameSizeEnum::Stepwise(step) => {
resolutions.push(Resolution::new(step.min_width, step.min_height));
resolutions.push(Resolution::new(step.max_width, step.max_height));
// TODO: Respect step size
}
}
}
Ok(resolutions)
}
Err(why) => Err(NokhwaError::GetPropertyError {
property: "Resolutions".to_string(),
error: why.to_string(),
}),
}
}
/// 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
}
/// Get the inner device (mutable) for e.g. Controls
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 self.device.format() {
Ok(format) => {
let frame_format =
fourcc_to_frameformat(format.fourcc).ok_or(NokhwaError::GetPropertyError {
property: "FrameFormat".to_string(),
error: "unsupported".to_string(),
})?;
let fps = match self.device.params() {
Ok(params) => {
if params.interval.denominator != 1
|| params.interval.numerator % params.interval.denominator != 0
{
return Err(NokhwaError::GetPropertyError {
property: "V4L2 FrameRate".to_string(),
error: format!(
"Framerate not whole number: {} / {}",
params.interval.numerator, params.interval.denominator
),
});
}
if params.interval.denominator != 1 {
params.interval.numerator / params.interval.denominator
} else {
params.interval.numerator
}
}
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "V4L2 FrameRate".to_string(),
error: why.to_string(),
})
}
};
self.camera_format = CameraFormat::new(
Resolution::new(format.width, format.height),
frame_format,
fps,
);
Ok(())
}
Err(why) => Err(NokhwaError::GetPropertyError {
property: "parameters".to_string(),
error: why.to_string(),
}),
}
}
}
impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
fn backend(&self) -> ApiBackend {
ApiBackend::Video4Linux
}
fn camera_info(&self) -> &CameraInfo {
&self.camera_info
}
fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
self.force_refresh_camera_format()
}
fn camera_format(&self) -> CameraFormat {
self.camera_format
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
let prev_format = match Capture::format(&self.device) {
Ok(fmt) => fmt,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Resolution, FrameFormat".to_string(),
error: why.to_string(),
})
}
};
let prev_fps = match Capture::params(&self.device) {
Ok(fps) => fps,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Frame rate".to_string(),
error: why.to_string(),
})
}
};
let format: Format = new_fmt.into();
let frame_rate = Parameters::with_fps(new_fmt.frame_rate());
if let Err(why) = Capture::set_format(&self.device, &format) {
return Err(NokhwaError::SetPropertyError {
property: "Resolution, FrameFormat".to_string(),
value: format.to_string(),
error: why.to_string(),
});
}
if let Err(why) = Capture::set_params(&self.device, &frame_rate) {
return Err(NokhwaError::SetPropertyError {
property: "Frame rate".to_string(),
value: frame_rate.to_string(),
error: why.to_string(),
});
}
if self.stream_handle.is_some() {
return match self.open_stream() {
Ok(_) => Ok(()),
Err(why) => {
// undo
if let Err(why) = Capture::set_format(&self.device, &prev_format) {
return Err(NokhwaError::SetPropertyError {
property: format!("Attempt undo due to stream acquisition failure with error {}. Resolution, FrameFormat", why),
value: prev_format.to_string(),
error: why.to_string(),
});
}
if let Err(why) = Capture::set_params(&self.device, &prev_fps) {
return Err(NokhwaError::SetPropertyError {
property:
format!("Attempt undo due to stream acquisition failure with error {}. Frame rate", why),
value: prev_fps.to_string(),
error: why.to_string(),
});
}
Err(why)
}
};
}
self.camera_format = new_fmt;
self.force_refresh_camera_format()?;
if self.camera_format != new_fmt {
return Err(NokhwaError::SetPropertyError {
property: "CameraFormat".to_string(),
value: new_fmt.to_string(),
error: "Rejected".to_string(),
});
}
Ok(())
}
fn compatible_list_by_resolution(
&mut self,
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
let resolutions = self.get_resolution_list(fourcc)?;
let format = frameformat_to_fourcc(fourcc);
let mut res_map = HashMap::new();
for res in resolutions {
let mut compatible_fps = vec![];
match self.device.enum_frameintervals(format) {
Ok(intervals) => {
for interval in intervals {
match interval.interval {
FrameIntervalEnum::Discrete(dis) => {
compatible_fps.push(dis.denominator);
}
FrameIntervalEnum::Stepwise(step) => {
for fstep in (step.min.numerator..step.max.numerator)
.step_by(step.step.numerator as usize)
{
if step.max.denominator != 1 || step.min.denominator != 1 {
compatible_fps.push(fstep)
}
}
}
}
}
}
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Frame rate".to_string(),
error: why.to_string(),
})
}
}
res_map.insert(res, compatible_fps);
}
Ok(res_map)
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
match self.device.enum_formats() {
Ok(formats) => {
let mut frame_format_vec = vec![];
for format in formats {
match fourcc_to_frameformat(format.fourcc) {
Some(ff) => frame_format_vec.push(ff),
None => continue,
}
}
frame_format_vec.sort();
frame_format_vec.dedup();
Ok(frame_format_vec)
}
Err(why) => Err(NokhwaError::GetPropertyError {
property: "FrameFormat".to_string(),
error: why.to_string(),
}),
}
}
fn resolution(&self) -> Resolution {
self.camera_format.resolution()
}
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
let mut new_fmt = self.camera_format;
new_fmt.set_resolution(new_res);
self.set_camera_format(new_fmt)
}
fn frame_rate(&self) -> u32 {
self.camera_format.frame_rate()
}
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
let mut new_fmt = self.camera_format;
new_fmt.set_frame_rate(new_fps);
self.set_camera_format(new_fmt)
}
fn frame_format(&self) -> FrameFormat {
self.camera_format.format()
}
fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
let mut new_fmt = self.camera_format;
new_fmt.set_format(fourcc);
self.set_camera_format(new_fmt)
}
fn camera_control(&self, control: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
let controls = self.camera_controls()?;
for supported_control in controls {
if supported_control.control() == control {
return Ok(supported_control);
}
}
Err(NokhwaError::GetPropertyError {
property: control.to_string(),
error: "not found/not supported".to_string(),
})
}
fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
self.device
.query_controls()
.map_err(|why| NokhwaError::GetPropertyError {
property: "V4L2 Controls".to_string(),
error: why.to_string(),
})?
.into_iter()
.map(|desc| {
let id_as_kcc = id_to_known_camera_control(desc.id);
let ctrl_current = self.device.control(desc.id)?.value;
let ctrl_value_desc = match (desc.typ, ctrl_current) {
(
Type::Integer
| Type::Integer64
| Type::Menu
| Type::U8
| Type::U16
| Type::U32
| Type::IntegerMenu,
Value::Integer(current),
) => ControlValueDescription::IntegerRange {
min: desc.minimum as i64,
max: desc.maximum,
value: current,
step: desc.step as i64,
default: desc.default,
},
(Type::Boolean, Value::Boolean(current)) => ControlValueDescription::Boolean {
value: current,
default: desc.default != 0,
},
(Type::String, Value::String(current)) => ControlValueDescription::String {
value: current,
default: None,
},
_ => {
return Err(io::Error::new(
ErrorKind::Unsupported,
"what is this?????? todo: support ig",
))
}
};
let is_readonly = desc
.flags
.intersects(Flags::READ_ONLY)
.then(|| KnownCameraControlFlag::ReadOnly);
let is_writeonly = desc
.flags
.intersects(Flags::WRITE_ONLY)
.then(|| KnownCameraControlFlag::WriteOnly);
let is_disabled = desc
.flags
.intersects(Flags::DISABLED)
.then(|| KnownCameraControlFlag::Disabled);
let is_volatile = desc
.flags
.intersects(Flags::VOLATILE)
.then(|| KnownCameraControlFlag::Volatile);
let is_inactive = desc
.flags
.intersects(Flags::INACTIVE)
.then(|| KnownCameraControlFlag::Inactive);
let flags_vec = vec![
is_inactive,
is_readonly,
is_volatile,
is_disabled,
is_writeonly,
]
.into_iter()
.filter(|x| x.is_some())
.collect::<Option<Vec<KnownCameraControlFlag>>>()
.unwrap_or_default();
Ok(CameraControl::new(
id_as_kcc,
desc.name,
ctrl_value_desc,
flags_vec,
!desc.flags.intersects(Flags::INACTIVE),
))
})
.filter(|x| x.is_ok())
.collect::<Result<Vec<CameraControl>, io::Error>>()
.map_err(|x| NokhwaError::GetPropertyError {
property: "www".to_string(),
error: x.to_string(),
})
}
fn set_camera_control(
&mut self,
id: KnownCameraControl,
value: ControlValueSetter,
) -> Result<(), NokhwaError> {
let value = match value {
ControlValueSetter::None => Value::None,
ControlValueSetter::Integer(i) => Value::Integer(i),
ControlValueSetter::Float(f) => {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: f.to_string(),
error: "not supported".to_string(),
})
}
ControlValueSetter::Boolean(b) => Value::Boolean(b),
ControlValueSetter::String(s) => Value::String(s),
ControlValueSetter::Bytes(b) => Value::CompoundU8(b),
};
self.device
.set_control(Control {
id: known_camera_control_to_id(id),
value,
})
.map_err(|why| NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: why.to_string(),
})?;
// verify
let control = self.camera_control(id)?;
if control.value() != value {
return Err(NokhwaError::SetPropertyError {
property: id.to_string(),
value: value.to_string(),
error: "Rejected".to_string(),
});
}
Ok(())
}
fn open_stream(&mut self) -> Result<(), NokhwaError> {
let stream = match MmapStream::new(&self.device, Type::VideoCapture) {
Ok(s) => s,
Err(why) => return Err(NokhwaError::OpenStreamError(why.to_string())),
};
self.stream_handle = Some(stream);
Ok(())
}
fn is_stream_open(&self) -> bool {
self.stream_handle.is_some()
}
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::GRAY => raw_frame.to_vec(),
};
Ok(Buffer::new_with_vec(
cam_fmt.resolution(),
conv,
cam_fmt.format(),
))
}
fn frame_raw(&mut self) -> Result<Cow<'a, [u8]>, NokhwaError> {
match &self.stream_handle {
Some(mut sh) => match sh.next() {
Ok((data, _)) => Ok(Cow::Borrowed(data)),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
},
None => {}
}
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
if self.stream_handle.is_some() {
self.stream_handle = None;
}
Ok(())
}
}
fn fourcc_to_frameformat(fourcc: FourCC) -> Option<FrameFormat> {
match fourcc.str().ok()? {
"YUYV" => Some(FrameFormat::YUYV),
"MJPG" => Some(FrameFormat::MJPEG),
"GRAY" => Some(FrameFormat::GRAY),
_ => None,
}
}
fn frameformat_to_fourcc(fourcc: FrameFormat) -> FourCC {
match fourcc {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
FrameFormat::GRAY => FourCC::new(b"GRAY"),
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
+148 -221
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -14,123 +14,161 @@
* limitations under the License.
*/
use crate::{
CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
KnownCameraControls, NokhwaError, Resolution,
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
pixel_format::FormatDecoder,
traits::CaptureBackendTrait,
types::{
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter,
FrameFormat, KnownCameraControl, RequestedFormat, Resolution,
},
};
use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage};
use std::{any::Any, borrow::Cow, collections::HashMap};
use std::{borrow::Cow, collections::HashMap};
#[cfg(feature = "output-wgpu")]
use wgpu::{
Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue,
Texture as WgpuTexture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat,
TextureUsages,
};
use wgpu::{Device as WgpuDevice, Queue as WgpuQueue, Texture as WgpuTexture};
/// The main `Camera` struct. This is the struct that abstracts over all the backends, providing a simplified interface for use.
pub struct Camera {
idx: usize,
backend: Box<dyn CaptureBackendTrait>,
backend_api: CaptureAPIBackend,
idx: CameraIndex,
api: ApiBackend,
device: Box<dyn CaptureBackendTrait>,
}
#[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: usize, format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
Camera::with_backend(index, format, CaptureAPIBackend::Auto)
pub fn new(index: CameraIndex, format: RequestedFormat) -> Result<Self, NokhwaError> {
Camera::with_backend(index, format, ApiBackend::Auto)
}
/// Create a new camera from an `index`, `format`, and `backend`. `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 with_backend(
index: usize,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
index: CameraIndex,
format: RequestedFormat,
backend: ApiBackend,
) -> Result<Self, NokhwaError> {
let camera_backend = init_camera(index, format, backend)?;
let camera_backend = init_camera(&index, format, backend)?;
Ok(Camera {
idx: index,
backend: camera_backend,
backend_api: backend,
api: backend,
device: camera_backend,
})
}
/// Create a new `Camera` from raw values.
/// # 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).
#[deprecated(since = "0.10.0", note = "please use `new` instead.")]
pub fn new_with(
index: usize,
index: CameraIndex,
width: u32,
height: u32,
fps: u32,
fourcc: FrameFormat,
backend: CaptureAPIBackend,
backend: ApiBackend,
) -> Result<Self, NokhwaError> {
let camera_format = CameraFormat::new_from(width, height, fourcc, fps);
Camera::with_backend(index, Some(camera_format), backend)
Camera::with_backend(index, RequestedFormat::Exact(camera_format), backend)
}
/// Gets the current Camera's index.
#[must_use]
pub fn index(&self) -> usize {
self.idx
pub fn index(&self) -> &CameraIndex {
&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: usize) -> Result<(), NokhwaError> {
pub fn set_index(&mut self, new_idx: &CameraIndex) -> Result<(), NokhwaError> {
{
self.backend.stop_stream()?;
self.device.stop_stream()?;
}
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;
let new_camera_format = self.device.camera_format();
let new_camera = init_camera(new_idx, RequestedFormat::Exact(new_camera_format), self.api)?;
self.device = new_camera;
Ok(())
}
/// Gets the current Camera's backend
#[must_use]
pub fn backend(&self) -> CaptureAPIBackend {
self.backend_api
pub fn backend(&self) -> ApiBackend {
self.api
}
/// Sets the current Camera's backend. Note that this re-initializes the camera.
/// # Errors
/// The new backend may not exist or may fail to initialize the new camera.
pub fn set_backend(&mut self, new_backend: CaptureAPIBackend) -> Result<(), NokhwaError> {
pub fn set_backend(&mut self, new_backend: ApiBackend) -> Result<(), NokhwaError> {
{
self.backend.stop_stream()?;
self.device.stop_stream()?;
}
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;
let new_camera_format = self.device.camera_format();
let new_camera = init_camera(
&self.idx,
RequestedFormat::Exact(new_camera_format),
new_backend,
)?;
self.device = new_camera;
Ok(())
}
/// Gets the camera information such as Name and Index as a [`CameraInfo`].
#[must_use]
pub fn info(&self) -> &CameraInfo {
self.backend.camera_info()
self.device.camera_info()
}
/// Gets the current [`CameraFormat`].
#[must_use]
pub fn camera_format(&self) -> CameraFormat {
self.backend.camera_format()
self.device.camera_format()
}
/// Forcefully refreshes the stored camera format, bringing it into sync with "reality" (current camera state)
/// # Errors
/// If the camera can not get its most recent [`CameraFormat`]. this will error.
pub fn refresh_camera_format(&mut self) -> Result<CameraFormat, NokhwaError> {
self.device.refresh_camera_format()?;
Ok(self.device.camera_format())
}
/// Will set the current [`CameraFormat`], using a [`RequestedFormat.`]
/// This will reset the current stream if used while stream is opened.
///
/// This will also update the cache.
///
/// This will return the new [`CameraFormat`]
/// # Errors
/// If nothing fits the requested criteria, this will return an error.
pub fn set_camera_requset(
&mut self,
request: RequestedFormat,
) -> Result<CameraFormat, NokhwaError> {
let new_format = request
.fulfill(self.device.compatible_camera_formats()?.as_slice())
.ok_or(NokhwaError::GetPropertyError {
property: "Compatible Camera Format by request".to_string(),
error: "Failed to fufill".to_string(),
})?;
self.device.set_camera_format(new_format)?;
Ok(new_format)
}
#[deprecated(since = "0.10.0", note = "please use `set_camera_requset` instead.")]
/// 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> {
self.backend.set_camera_format(new_fmt)
self.device.set_camera_format(new_fmt)
}
/// A hashmap of [`Resolution`]s mapped to framerates
@@ -140,80 +178,81 @@ impl Camera {
&mut self,
fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
self.backend.compatible_list_by_resolution(fourcc)
self.device.compatible_list_by_resolution(fourcc)
}
/// A Vector of compatible [`FrameFormat`]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_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
self.backend.compatible_fourcc()
self.device.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)
self.device.compatible_camera_formats()
}
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]).
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
#[must_use]
pub fn resolution(&self) -> Resolution {
self.backend.resolution()
self.device.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> {
self.backend.set_resolution(new_res)
self.device.set_resolution(new_res)
}
/// Gets the current camera framerate (See: [`CameraFormat`]).
#[must_use]
pub fn frame_rate(&self) -> u32 {
self.backend.frame_rate()
self.device.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> {
self.backend.set_frame_rate(new_fps)
self.device.set_frame_rate(new_fps)
}
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]).
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
#[must_use]
pub fn frame_format(&self) -> FrameFormat {
self.backend.frame_format()
self.device.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> {
self.backend.set_frame_format(fourcc)
self.device.set_frame_format(fourcc)
}
/// Gets the current supported list of [`KnownCameraControls`]
/// Gets the current supported list of [`KnownCameraControl`](crate::utils::KnownCameraControl)
/// # Errors
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
pub fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
self.backend.supported_camera_controls()
pub fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControl>, NokhwaError> {
Ok(self
.device
.camera_controls()?
.iter()
.map(CameraControl::control)
.collect())
}
/// Gets the current supported list of [`CameraControl`]s keyed by its name as a `String`.
@@ -244,7 +283,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.into_iter() {
for (kc, cc) in maybe_camera_controls {
control_map.insert(kc, cc);
}
@@ -256,88 +295,59 @@ impl Camera {
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
pub fn camera_controls_known_camera_controls(
&self,
) -> Result<HashMap<KnownCameraControls, CameraControl>, NokhwaError> {
) -> Result<HashMap<KnownCameraControl, CameraControl>, NokhwaError> {
let known_controls = self.supported_camera_controls()?;
let maybe_camera_controls = known_controls
.iter()
.map(|x| (*x, self.camera_control(*x)))
.filter(|(_, x)| x.is_ok())
.map(|(c, x)| (c, Result::unwrap(x)))
.collect::<Vec<(KnownCameraControls, CameraControl)>>();
.collect::<Vec<(KnownCameraControl, CameraControl)>>();
let mut control_map = HashMap::with_capacity(maybe_camera_controls.len());
for (kc, cc) in maybe_camera_controls.into_iter() {
for (kc, cc) in maybe_camera_controls {
control_map.insert(kc, cc);
}
Ok(control_map)
}
/// Gets the value of [`KnownCameraControls`].
/// Gets the value of [`KnownCameraControl`].
/// # Errors
/// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, etc)
/// this will error.
pub fn camera_control(
&self,
control: KnownCameraControls,
control: KnownCameraControl,
) -> Result<CameraControl, NokhwaError> {
self.backend.camera_control(control)
self.device.camera_control(control)
}
/// Sets the control to `control` in the camera.
/// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way
/// then calling one of the methods to set the value: [`set_value()`](CameraControl::set_value()) or [`with_value()`](CameraControl::with_value()).
/// then calling [`set_value()`](CameraControl::set_value())
/// # Errors
/// If the `control` is not supported, the value is invalid (less than min, greater than max, not in step), or there was an error setting the control,
/// this will error.
pub fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError> {
self.backend.set_camera_control(control)
}
/// Gets the current supported list of Controls as an `Any` from the backend.
/// The `Any`'s type is defined by the backend itself, please check each of the backend's documentation.
/// # Errors
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
pub fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
self.backend.raw_supported_camera_controls()
}
/// Sets the control to `control` in the camera.
/// The control's type is defined the backend itself. It may be a string, or more likely its a integer ID.
/// The backend itself has documentation of the proper input/return values, please check each of the backend's documentation.
/// # Errors
/// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, wrong Any type)
/// this will error.
pub fn raw_camera_control(&self, control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
self.backend.raw_camera_control(control)
}
/// Sets the control to `control` in the camera.
/// The `control`/`value`'s type is defined the backend itself. It may be a string, or more likely its a integer ID/Value.
/// Usually, the pipeline is calling [`camera_control()`](CaptureBackendTrait::camera_control), getting a camera control that way
/// then calling one of the methods to set the value: [`set_value()`](CameraControl::set_value()) or [`with_value()`](CameraControl::with_value()).
/// # Errors
/// If the `control` is not supported, the value is invalid (wrong Any type, backend refusal), or there was an error setting the control,
/// this will error.
pub fn set_raw_camera_control(
pub fn set_camera_control(
&mut self,
control: &dyn Any,
value: &dyn Any,
id: KnownCameraControl,
value: ControlValueSetter,
) -> Result<(), NokhwaError> {
self.backend.set_raw_camera_control(control, value)
self.device.set_camera_control(id, value)
}
/// Will open the camera stream with set parameters. This will be called internally if you try and call [`frame()`](CaptureBackendTrait::frame()) before you call [`open_stream()`](CaptureBackendTrait::open_stream()).
/// # Errors
/// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error.
pub fn open_stream(&mut self) -> Result<(), NokhwaError> {
self.backend.open_stream()
self.device.open_stream()
}
/// Checks if stream if open. If it is, it will return true.
#[must_use]
pub fn is_stream_open(&self) -> bool {
self.backend.is_stream_open()
self.device.is_stream_open()
}
/// 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,
@@ -345,59 +355,28 @@ 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> {
self.backend.frame()
pub fn frame(&mut self) -> Result<Buffer, NokhwaError> {
self.device.frame()
}
/// 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.
pub fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
match self.backend.frame_raw() {
match self.device.frame_raw() {
Ok(f) => Ok(f),
Err(why) => Err(why),
}
}
/// 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();
if rgba {
return (resolution.width() * resolution.height() * 4) as usize;
}
(resolution.width() * resolution.height() * 3) 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.
/// Directly writes the current frame into said `buffer`.
/// # 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<F: FormatDecoder>(
&mut self,
buffer: &mut [u8],
convert_rgba: bool,
) -> Result<usize, NokhwaError> {
let resolution = self.resolution();
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 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());
Ok(frame.len())
) -> Result<(), NokhwaError> {
self.device.frame()?.decode_image_to_buffer::<F>(buffer)
}
#[cfg(feature = "output-wgpu")]
@@ -405,66 +384,20 @@ 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: FormatDecoder>(
&mut self,
device: &WgpuDevice,
queue: &WgpuQueue,
label: Option<&'a str>,
) -> Result<WgpuTexture, NokhwaError> {
use std::{convert::TryFrom, 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.device.frame_texture(device, queue, label)
}
/// Will drop the stream.
/// # Errors
/// Please check the `Quirks` section of each backend.
pub fn stop_stream(&mut self) -> Result<(), NokhwaError> {
self.backend.stop_stream()
self.device.stop_stream()
}
}
@@ -476,23 +409,19 @@ impl Drop for Camera {
// TODO: Update as we go
#[allow(clippy::ifs_same_cond)]
fn figure_out_auto() -> Option<CaptureAPIBackend> {
fn figure_out_auto() -> Option<ApiBackend> {
let platform = std::env::consts::OS;
let mut cap = CaptureAPIBackend::Auto;
let mut cap = ApiBackend::Auto;
if cfg!(feature = "input-v4l") && platform == "linux" {
cap = CaptureAPIBackend::Video4Linux;
cap = ApiBackend::Video4Linux;
} else if cfg!(feature = "input-msmf") && platform == "windows" {
cap = CaptureAPIBackend::MediaFoundation;
cap = ApiBackend::MediaFoundation;
} else if cfg!(feature = "input-avfoundation") && (platform == "macos" || platform == "ios") {
cap = CaptureAPIBackend::AVFoundation;
} else if cfg!(feature = "input-uvc") {
cap = CaptureAPIBackend::UniversalVideoClass;
} else if cfg!(feature = "input-gst") {
cap = CaptureAPIBackend::GStreamer;
cap = ApiBackend::AVFoundation;
} else if cfg!(feature = "input-opencv") {
cap = CaptureAPIBackend::OpenCv;
cap = ApiBackend::OpenCv;
}
if cap == CaptureAPIBackend::Auto {
if cap == ApiBackend::Auto {
return None;
}
Some(cap)
@@ -505,15 +434,15 @@ macro_rules! cap_impl_fn {
$(
paste::paste! {
#[cfg ($cfg) ]
fn [< init_ $backend_name>](idx: usize, setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
fn [< init_ $backend_name>](idx: &CameraIndex, setting: RequestedFormat) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
use crate::backends::capture::$backend;
match <$backend>::$init_fn(idx, setting) {
Ok(cap) => Some(Ok(Box::new(cap))),
Ok(cap) => Some(Ok(cap.into())),
Err(why) => Some(Err(why)),
}
}
#[cfg(not( $cfg ))]
fn [< init_ $backend_name>](_idx: usize, _setting: Option<CameraFormat>) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
fn [< init_ $backend_name>](_idx: &CameraIndex, _setting: RequestedFormat) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
None
}
}
@@ -530,10 +459,10 @@ macro_rules! cap_impl_matches {
let i = $index;
let s = $setting;
match $use_backend {
CaptureAPIBackend::Auto => match figure_out_auto() {
ApiBackend::Auto => match figure_out_auto() {
Some(cap) => match cap {
$(
CaptureAPIBackend::$backend => {
ApiBackend::$backend => {
match cfg!(feature = $feature) {
true => {
match $fn(i,s) {
@@ -569,7 +498,7 @@ macro_rules! cap_impl_matches {
}
}
$(
CaptureAPIBackend::$backend => {
ApiBackend::$backend => {
match cfg!(feature = $feature) {
true => {
match $fn(i,s) {
@@ -604,8 +533,8 @@ macro_rules! cap_impl_matches {
}
cap_impl_fn! {
(GStreamerCaptureDevice, new, feature = "input-gst", gst),
(OpenCvCaptureDevice, new_autopref, feature = "input-opencv", opencv),
// (GStreamerCaptureDevice, new, feature = "input-gst", gst),
(OpenCvCaptureDevice, new, feature = "input-opencv", opencv),
// (UVCCaptureDevice, create, feature = "input-uvc", uvc),
(V4LCaptureDevice, new, all(feature = "input-v4l", target_os = "linux"), v4l),
(MediaFoundationCaptureDevice, new, all(feature = "input-msmf", target_os = "windows"), msmf),
@@ -613,17 +542,15 @@ cap_impl_fn! {
}
fn init_camera(
index: usize,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
index: &CameraIndex,
format: RequestedFormat,
backend: ApiBackend,
) -> Result<Box<dyn CaptureBackendTrait>, 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)
};
Ok(camera_backend)
-176
View File
@@ -1,176 +0,0 @@
/*
* Copyright 2021 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::{CaptureAPIBackend, FrameFormat};
use thiserror::Error;
/// All errors in `nokhwa`.
#[allow(clippy::module_name_repetitions)]
#[derive(Error, Debug, Clone)]
pub enum NokhwaError {
#[error("Could not initialize {backend}: {error}")]
InitializeError {
backend: CaptureAPIBackend,
error: String,
},
#[error("Could not shutdown {backend}: {error}")]
ShutdownError {
backend: CaptureAPIBackend,
error: String,
},
#[error("Error: {0}")]
GeneralError(String),
#[error("Could not generate required structure {structure}: {error}")]
StructureError { structure: String, error: String },
#[error("Could not open device {0}: {1}")]
OpenDeviceError(String, String),
#[error("Could not get device property {property}: {error}")]
GetPropertyError { property: String, error: String },
#[error("Could not set device property {property} with value {value}: {error}")]
SetPropertyError {
property: String,
value: String,
error: String,
},
#[error("Could not open device stream: {0}")]
OpenStreamError(String),
#[error("Could not capture frame: {0}")]
ReadFrameError(String),
#[error("Could not process frame {src} to {destination}: {error}")]
ProcessFrameError {
src: FrameFormat,
destination: String,
error: String,
},
#[error("Could not stop stream: {0}")]
StreamShutdownError(String),
#[error("This operation is not supported by backend {0}.")]
UnsupportedOperationError(CaptureAPIBackend),
#[error("This operation is not implemented yet: {0}")]
NotImplementedError(String),
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
use nokhwa_bindings_windows::BindingError;
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<BindingError> for NokhwaError {
fn from(err: BindingError) -> Self {
match err {
BindingError::InitializeError(error) => NokhwaError::InitializeError {
backend: CaptureAPIBackend::MediaFoundation,
error,
},
BindingError::DeInitializeError(error) => NokhwaError::ShutdownError {
backend: CaptureAPIBackend::MediaFoundation,
error,
},
BindingError::GUIDSetError(property, value, error) => NokhwaError::SetPropertyError {
property,
value,
error,
},
BindingError::GUIDReadError(property, error) => {
NokhwaError::GetPropertyError { property, error }
}
BindingError::AttributeError(error) => NokhwaError::StructureError {
structure: "IMFAttribute".to_string(),
error,
},
BindingError::EnumerateError(error) => NokhwaError::GetPropertyError {
property: "Devices".to_string(),
error,
},
BindingError::DeviceOpenFailError(device, error) => {
NokhwaError::OpenDeviceError(device, error)
}
BindingError::ReadFrameError(error) => NokhwaError::ReadFrameError(error),
BindingError::NotImplementedError => {
NokhwaError::NotImplementedError("Docs-Only MediaFoundation".to_string())
}
}
}
}
#[cfg(any(
all(
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
),
all(
feature = "docs-only",
feature = "docs-nolink",
feature = "input-avfoundation"
)
))]
use nokhwa_bindings_macos::AVFError;
#[cfg(any(
all(
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
),
all(
feature = "docs-only",
feature = "docs-nolink",
feature = "input-avfoundation"
)
))]
impl From<AVFError> for NokhwaError {
fn from(avf_error: AVFError) -> Self {
match avf_error {
AVFError::InvalidType { expected, found } => NokhwaError::GetPropertyError {
property: format!("type of {}", expected),
error: format!("Invalid type, found {}", found),
},
AVFError::InvalidValue { found } => NokhwaError::GetPropertyError {
property: found,
error: "Invalid Value".to_string(),
},
AVFError::AlreadyBusy(why) => {
NokhwaError::GeneralError(format!("Already Busy: {}", why))
}
AVFError::FailedToOpenDevice { index, why } => {
NokhwaError::OpenDeviceError(index.to_string(), why)
}
AVFError::ConfigNotAccepted => NokhwaError::SetPropertyError {
property: "Configuration".to_string(),
value: "Invalid".to_string(),
error: "Rejected by AVFoundation".to_string(),
},
AVFError::General(why) => {
NokhwaError::GeneralError(format!("AVFoundation Error: {}", why))
}
AVFError::RejectedInput => {
NokhwaError::OpenStreamError("AVFoundation Input Rejection".to_string())
}
AVFError::RejectedOutput => {
NokhwaError::OpenStreamError("AVFoundation Output Rejection".to_string())
}
AVFError::StreamOpen(why) => NokhwaError::OpenStreamError(why),
AVFError::ReadFrame(why) => NokhwaError::ReadFrameError(why),
AVFError::NotSupported => {
NokhwaError::UnsupportedOperationError(CaptureAPIBackend::AVFoundation)
}
}
}
}
+8 -9
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -18,7 +18,7 @@
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
)))]
fn init_avfoundation(callback: fn(bool)) {
fn init_avfoundation(callback: impl Fn(bool) + Send + 'static) {
callback(true);
}
@@ -26,10 +26,11 @@ fn init_avfoundation(callback: fn(bool)) {
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
))]
fn init_avfoundation(callback: fn(bool)) {
use nokhwa_bindings_macos::avfoundation::request_permission_with_callback;
fn init_avfoundation(callback: impl Fn(bool) + Send + Sync + 'static) {
use nokhwa_bindings_macos::request_permission_with_callback;
request_permission_with_callback(callback);
let boxed: Box<dyn Fn(bool) + Send + Sync + 'static> = Box::new(callback);
request_permission_with_callback(boxed);
}
#[cfg(not(all(
@@ -45,9 +46,7 @@ fn status_avfoundation() -> bool {
any(target_os = "macos", target_os = "ios")
))]
fn status_avfoundation() -> bool {
use nokhwa_bindings_macos::avfoundation::{
current_authorization_status, AVAuthorizationStatus,
};
use nokhwa_bindings_macos::{current_authorization_status, AVAuthorizationStatus};
matches!(
current_authorization_status(),
@@ -60,7 +59,7 @@ fn status_avfoundation() -> bool {
///
/// The `on_complete` is called after initialization (a.k.a User granted permission). The callback's argument
/// is weather the initialization was successful or not
pub fn nokhwa_initialize(on_complete: fn(bool)) {
pub fn nokhwa_initialize(on_complete: impl Fn(bool) + Send + Sync + 'static) {
init_avfoundation(on_complete);
}
+34 -15
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -20,19 +20,24 @@
//!
//! This assumes that you are running a modern browser on the desktop.
use crate::{CameraInfo, NokhwaError, Resolution};
use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbImage, Rgba};
#[cfg(feature = "output-wasm")]
use js_sys::{Array, JsString, Map, Object, Promise};
use nokhwa_core::{
error::NokhwaError,
types::{CameraIndex, CameraInfo, Resolution},
};
use std::{
borrow::Cow,
borrow::{Borrow, Cow},
convert::TryFrom,
fmt::{Debug, Display, Formatter},
ops::Deref,
};
#[cfg(feature = "output-wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
#[cfg(feature = "output-wasm")]
use wasm_bindgen_futures::JsFuture;
#[cfg(feature = "output-wasm")]
use web_sys::{
console::log_1, CanvasRenderingContext2d, Document, Element, HtmlCanvasElement,
HtmlVideoElement, ImageData, MediaDeviceInfo, MediaDeviceKind, MediaDevices, MediaStream,
@@ -287,14 +292,18 @@ pub async fn query_js_cameras() -> Result<Vec<CameraInfo>, NokhwaError> {
MediaStreamTrack::from(first).label()
};
device_list.push(CameraInfo::new(
name,
format!("{:?}", media_device_info.kind()),
format!(
&name,
&format!("{:?}", media_device_info.kind()),
&format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
),
idx_device as usize,
CameraIndex::String(format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
)),
));
tracks
.iter()
@@ -303,18 +312,22 @@ pub async fn query_js_cameras() -> Result<Vec<CameraInfo>, NokhwaError> {
}
Err(_) => {
device_list.push(CameraInfo::new(
format!(
&format!(
"{:?}#{}",
media_device_info.kind(),
idx_device
),
format!("{:?}", media_device_info.kind()),
format!(
&format!("{:?}", media_device_info.kind()),
&format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
),
idx_device as usize,
CameraIndex::String(format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
)),
));
}
}
@@ -1666,6 +1679,7 @@ impl Deref for JSCameraConstraints {
/// A wrapper around a [`MediaStream`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.MediaStream.html)
/// # JS-WASM
/// This is exported as `NokhwaCamera`.
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = NokhwaCamera))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))]
pub struct JSCamera {
@@ -1678,6 +1692,7 @@ pub struct JSCamera {
canvas_context: Option<CanvasRenderingContext2d>,
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = NokhwaCamera))]
impl JSCamera {
/// Creates a new [`JSCamera`] using [`JSCameraConstraints`].
@@ -2544,7 +2559,7 @@ impl JSCamera {
let resolution = self.resolution();
let frame = self.frame_raw()?;
if convert_rgba {
buffer.copy_from_slice(&frame);
buffer.copy_from_slice(frame.borrow());
return Ok(frame.len());
}
let image = match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) {
@@ -2611,7 +2626,7 @@ impl JSCamera {
origin: wgpu::Origin3d::ZERO,
aspect: TextureAspect::All,
},
&frame,
frame.borrow(),
ImageDataLayout {
offset: 0,
bytes_per_row: width_nonzero,
@@ -2704,3 +2719,7 @@ impl Drop for JSCamera {
self.stop_all().unwrap_or(()); // swallow errors
}
}
// SAFETY: JSCamera is used in WASM, it will never be sent to a different thread. This is only done to satisfy the compiler.
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for JSCamera {}
+37 -21
View File
@@ -1,5 +1,8 @@
#![deny(clippy::pedantic)]
#![warn(clippy::all)]
#![allow(clippy::module_name_repetitions)]
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -15,7 +18,6 @@
*/
#![cfg_attr(feature = "test-fail-warning", deny(warnings))]
#![cfg_attr(feature = "docs-features", feature(doc_cfg))]
//! # nokhwa
//! A Simple-to-use, cross-platform Rust Webcam Capture Library
//!
@@ -25,43 +27,57 @@
//!
//! Please read the README for more.
#[cfg(feature = "small-wasm")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
/// Raw access to each of Nokhwa's backends.
pub mod backends;
mod camera;
mod camera_traits;
mod error;
mod init;
/// A camera that uses native browser APIs meant for WASM applications.
#[cfg(feature = "input-jscam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))]
pub mod js_camera;
/// A camera that uses `OpenCV` to access IP (rtsp/http) on the local network
#[cfg(feature = "input-ipcam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))]
pub mod network_camera;
pub use nokhwa_core::pixel_format::FormatDecoder;
mod query;
/// A camera that runs in a different thread and can call your code based on callbacks.
#[cfg(feature = "output-threaded")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))]
mod threaded;
mod utils;
pub mod threaded;
#[cfg(feature = "input-ipcam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))]
#[deprecated(
since = "0.10.0",
note = "please use `Camera` with `CameraIndex::String` and `input-opencv` enabled."
)]
pub use backends::capture::NetworkCamera;
pub use camera::Camera;
pub use camera_traits::*;
pub use error::NokhwaError;
pub use init::*;
#[cfg(feature = "input-jscam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))]
pub use js_camera::JSCamera;
#[cfg(feature = "input-ipcam")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))]
pub use network_camera::NetworkCamera;
pub use nokhwa_core::buffer::Buffer;
pub use nokhwa_core::error::NokhwaError;
pub use query::*;
#[cfg(feature = "output-threaded")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))]
pub use threaded::ThreadedCamera;
pub use utils::*;
pub use threaded::CallbackCamera;
pub mod utils {
pub use nokhwa_core::types::*;
}
pub mod error {
pub use nokhwa_core::error::NokhwaError;
}
pub mod camera_traits {
pub use nokhwa_core::traits::*;
}
pub mod pixel_format {
pub use nokhwa_core::pixel_format::*;
}
pub mod buffer {
pub use nokhwa_core::buffer::*;
}
+77 -84
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -14,105 +14,83 @@
* limitations under the License.
*/
use crate::{CameraInfo, CaptureAPIBackend, NokhwaError};
/// Query the system for a list of available devices.
/// Usually the order goes Native -> UVC -> Gstreamer.
/// # Quirks
/// - Media Foundation: The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`].
/// - Media Foundation: The names may contain invalid characters since they were converted from UTF16.
/// - AVFoundation: The ID of the device is stored in the `misc` attribute of the [`CameraInfo`].
/// - AVFoundation: There is lots of miscellaneous info in the `desc` attribute.
/// # Errors
/// If you use an unsupported API (check the README or crate root for more info), incompatible backend for current platform, incompatible platform, or insufficient permissions, etc
/// this will error.
pub fn query() -> Result<Vec<CameraInfo>, NokhwaError> {
query_devices(CaptureAPIBackend::Auto)
}
use nokhwa_core::{
error::NokhwaError,
types::{ApiBackend, CameraInfo},
};
// TODO: Update as this goes
/// Query the system for a list of available devices. Please refer to the API Backends that support `Query`) <br>
/// Currently, these are `V4L`, `MediaFoundation`, `AVFoundation`, `UVC`, and `GST`. <br>
/// Usually the order goes Native -> UVC -> Gstreamer.
/// # Quirks
/// - Media Foundation: The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`].
/// - Media Foundation: The names may contain invalid characters since they were converted from UTF16.
/// - AVFoundation: The ID of the device is stored in the `misc` attribute of the [`CameraInfo`].
/// - AVFoundation: There is lots of miscellaneous info in the `desc` attribute.
/// - `Media Foundation`: The symbolic link for the device is listed in the `misc` attribute of the [`CameraInfo`].
/// - `Media Foundation`: The names may contain invalid characters since they were converted from UTF16.
/// - `AVFoundation`: The ID of the device is stored in the `misc` attribute of the [`CameraInfo`].
/// - `AVFoundation`: There is lots of miscellaneous info in the `desc` attribute.
/// - `WASM`: The `misc` field contains the device ID and group ID are seperated by a space (' ')
/// # Errors
/// If you use an unsupported API (check the README or crate root for more info), incompatible backend for current platform, incompatible platform, or insufficient permissions, etc
/// this will error.
#[allow(clippy::module_name_repetitions)]
pub fn query_devices(api: CaptureAPIBackend) -> Result<Vec<CameraInfo>, NokhwaError> {
pub fn query(api: ApiBackend) -> Result<Vec<CameraInfo>, NokhwaError> {
match api {
CaptureAPIBackend::Auto => {
ApiBackend::Auto => {
// determine platform
match std::env::consts::OS {
"linux" => {
if cfg!(feature = "input-v4l") && cfg!(target_os = "linux") {
query_devices(CaptureAPIBackend::Video4Linux)
} else if cfg!(feature = "input-uvc") {
query_devices(CaptureAPIBackend::UniversalVideoClass)
} else if cfg!(feature = "input-gstreamer") {
query_devices(CaptureAPIBackend::GStreamer)
query(ApiBackend::Video4Linux)
} else if cfg!(feature = "input-opencv") {
query_devices(CaptureAPIBackend::OpenCv)
query(ApiBackend::OpenCv)
} else {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::Auto,
))
dbg!("Error: No suitable Backends availible. Perhaps you meant to enable one of the backends such as `input-v4l`? (Please read the docs.)");
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Auto))
}
}
"windows" => {
if cfg!(feature = "input-msmf") && cfg!(target_os = "windows") {
query_devices(CaptureAPIBackend::MediaFoundation)
} else if cfg!(feature = "input-uvc") {
query_devices(CaptureAPIBackend::UniversalVideoClass)
} else if cfg!(feature = "input-gstreamer") {
query_devices(CaptureAPIBackend::GStreamer)
query(ApiBackend::MediaFoundation)
} else if cfg!(feature = "input-opencv") {
query_devices(CaptureAPIBackend::OpenCv)
query(ApiBackend::OpenCv)
} else {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::Auto,
))
dbg!("Error: No suitable Backends availible. Perhaps you meant to enable one of the backends such as `input-msmf`? (Please read the docs.)");
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Auto))
}
}
"macos" => {
if cfg!(feature = "input-avfoundation") {
query_devices(CaptureAPIBackend::AVFoundation)
} else if cfg!(feature = "input-uvc") {
query_devices(CaptureAPIBackend::UniversalVideoClass)
} else if cfg!(feature = "input-gstreamer") {
query_devices(CaptureAPIBackend::GStreamer)
query(ApiBackend::AVFoundation)
} else if cfg!(feature = "input-opencv") {
query_devices(CaptureAPIBackend::OpenCv)
query(ApiBackend::OpenCv)
} else {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::Auto,
))
dbg!("Error: No suitable Backends availible. Perhaps you meant to enable one of the backends such as `input-avfoundation`? (Please read the docs.)");
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Auto))
}
}
"ios" => {
if cfg!(feature = "input-avfoundation") {
query_devices(CaptureAPIBackend::AVFoundation)
query(ApiBackend::AVFoundation)
} else {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::Auto,
))
dbg!("Error: No suitable Backends availible. Perhaps you meant to enable one of the backends such as `input-avfoundation`? (Please read the docs.)");
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Auto))
}
}
_ => Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::Auto,
)),
_ => {
dbg!("Error: No suitable Backends availible. You are on an unsupported platform.");
Err(NokhwaError::NotImplementedError("Bad Platform".to_string()))
}
}
}
CaptureAPIBackend::AVFoundation => query_avfoundation(),
CaptureAPIBackend::Video4Linux => query_v4l(),
CaptureAPIBackend::UniversalVideoClass => query_uvc(),
CaptureAPIBackend::MediaFoundation => query_msmf(),
CaptureAPIBackend::GStreamer => query_gstreamer(),
CaptureAPIBackend::OpenCv => Err(NokhwaError::UnsupportedOperationError(api)),
ApiBackend::AVFoundation => query_avfoundation(),
ApiBackend::Video4Linux => query_v4l(),
#[allow(deprecated)]
ApiBackend::UniversalVideoClass => query_uvc(),
ApiBackend::MediaFoundation => query_msmf(),
#[allow(deprecated)]
ApiBackend::GStreamer => query_gstreamer(),
ApiBackend::OpenCv | ApiBackend::Network => {
Err(NokhwaError::UnsupportedOperationError(api))
}
ApiBackend::Browser => query_wasm(),
}
}
@@ -120,17 +98,20 @@ pub fn query_devices(api: CaptureAPIBackend) -> Result<Vec<CameraInfo>, NokhwaEr
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
#[allow(clippy::unnecessary_wraps)]
#[allow(clippy::cast_possible_truncation)]
fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
use crate::CameraIndex;
Ok({
let camera_info: Vec<CameraInfo> = v4l::context::enum_devices()
.iter()
.map(|node| {
CameraInfo::new(
node.name()
&node
.name()
.unwrap_or(format!("{}", node.path().to_string_lossy())),
format!("Video4Linux Device @ {}", node.path().to_string_lossy()),
"".to_string(),
node.index(),
&format!("Video4Linux Device @ {}", node.path().to_string_lossy()),
&"".to_string(),
CameraIndex::Index(node.index() as u32),
)
})
.collect();
@@ -141,13 +122,15 @@ fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
#[cfg(any(not(feature = "input-v4l"), not(target_os = "linux")))]
fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::Video4Linux,
ApiBackend::Video4Linux,
))
}
#[cfg(feature = "input-uvc")]
fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
use crate::CameraIndex;
use uvc::Device;
let context = match uvc::Context::new() {
Ok(ctx) => ctx,
Err(why) => {
@@ -205,7 +188,7 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
desc.product_id,
desc.serial_number.unwrap_or_else(|| "".to_string())
),
counter,
CameraIndex::Index(counter as u32),
));
counter += 1;
}
@@ -216,14 +199,16 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
}
#[cfg(not(feature = "input-uvc"))]
#[allow(deprecated)]
fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
ApiBackend::UniversalVideoClass,
))
}
#[cfg(feature = "input-gst")]
fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
use crate::CameraIndex;
use gstreamer::{
prelude::{DeviceExt, DeviceMonitorExt, DeviceMonitorExtManual},
Caps, DeviceMonitor,
@@ -269,12 +254,7 @@ fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
let name = DeviceExt::display_name(gst_dev);
let class = DeviceExt::device_class(gst_dev);
counter += 1;
CameraInfo::new(
name.to_string(),
class.to_string(),
"".to_string(),
counter - 1,
)
CameraInfo::new(&name, &class, "", CameraIndex::Index(counter - 1))
})
.collect();
device_monitor.stop();
@@ -282,15 +262,16 @@ fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
}
#[cfg(not(feature = "input-gst"))]
#[allow(deprecated)]
fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::GStreamer,
ApiBackend::GStreamer,
))
}
// please refer to https://docs.microsoft.com/en-us/windows/win32/medfound/enumerating-video-capture-devices
#[cfg(all(feature = "input-msmf", target_os = "windows"))]
fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
fn query_msmf<'a>() -> Result<Vec<CameraInfo<'a>>, NokhwaError> {
let list: Vec<CameraInfo> =
match nokhwa_bindings_windows::wmf::query_media_foundation_descriptors() {
Ok(l) => l
@@ -308,7 +289,7 @@ fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
#[cfg(any(not(feature = "input-msmf"), not(target_os = "windows")))]
fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::MediaFoundation,
ApiBackend::MediaFoundation,
))
}
@@ -317,11 +298,10 @@ fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
any(target_os = "macos", target_os = "ios")
))]
fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
use nokhwa_bindings_macos::avfoundation::query_avfoundation as q_avf;
use nokhwa_bindings_macos::query_avfoundation;
Ok(q_avf()?
Ok(query_avfoundation()?
.into_iter()
.map(CameraInfo::from)
.collect::<Vec<CameraInfo>>())
}
@@ -331,6 +311,19 @@ fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
)))]
fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::AVFoundation,
ApiBackend::AVFoundation,
))
}
#[cfg(feature = "input-jscam")]
fn query_wasm() -> Result<Vec<CameraInfo>, NokhwaError> {
use crate::js_camera::query_js_cameras;
use wasm_rs_async_executor::single_threaded::block_on;
block_on(query_js_cameras())
}
#[cfg(not(feature = "input-jscam"))]
fn query_wasm() -> Result<Vec<CameraInfo>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Browser))
}
+190 -181
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
* 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.
@@ -14,24 +14,37 @@
* limitations under the License.
*/
use crate::{
Camera, CameraControl, CameraFormat, CameraInfo, CaptureAPIBackend, FrameFormat,
KnownCameraControls, NokhwaError, Resolution,
};
use crate::Camera;
use image::{ImageBuffer, Rgb};
use parking_lot::FairMutex;
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
traits::CaptureBackendTrait,
types::{
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter,
FrameFormat, KnownCameraControl, RequestedFormat, Resolution,
},
};
use std::{
any::Any,
collections::HashMap,
ops::Deref,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
Arc, Mutex,
},
};
type AtomicLock<T> = Arc<Mutex<T>>;
pub type CallbackFn = fn(
_camera: &Arc<Mutex<Camera>>,
_frame_callback: &Arc<Mutex<Option<Box<dyn FnMut(Buffer) + Send + 'static>>>>,
_last_frame_captured: &Arc<Mutex<Buffer>>,
_die_bool: &Arc<AtomicBool>,
);
type HeldCallbackType = Arc<Mutex<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 `FairMutex` to ensure that this feels like a normal camera, but callback based.
/// It uses a `Arc` and a `Mutex` to ensure that this feels like a normal camera, but callback based.
/// See [`Camera`] for more details on the camera itself.
///
/// Your function is called every time there is a new frame. In order to avoid frame loss, it should
@@ -43,106 +56,39 @@ use std::{
/// The `Mutex` guarantees exclusive access to the underlying camera struct. They should be safe to
/// impl `Send` on.
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))]
#[derive(Clone)]
pub struct ThreadedCamera {
camera: Arc<FairMutex<Camera>>,
frame_callback: Arc<FairMutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
last_frame_captured: Arc<FairMutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
pub struct CallbackCamera {
camera: AtomicLock<Camera>,
frame_callback: HeldCallbackType,
last_frame_captured: AtomicLock<Buffer>,
die_bool: Arc<AtomicBool>,
}
impl ThreadedCamera {
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: usize, format: Option<CameraFormat>) -> Result<Self, NokhwaError> {
ThreadedCamera::with_backend(index, format, CaptureAPIBackend::Auto)
}
/// Create a new camera from an `index`, `format`, and `backend`. `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 with_backend(
index: usize,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
pub fn new(
index: CameraIndex,
format: RequestedFormat,
callback: impl FnMut(Buffer) + Send + 'static,
) -> Result<Self, NokhwaError> {
Self::customized_all(index, format, backend, None)
}
/// Create a new `ThreadedCamera` from raw values.
/// # 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: usize,
width: u32,
height: u32,
fps: u32,
fourcc: FrameFormat,
backend: CaptureAPIBackend,
) -> Result<Self, NokhwaError> {
let camera_format = CameraFormat::new_from(width, height, fourcc, fps);
ThreadedCamera::with_backend(index, Some(camera_format), backend)
}
/// Create a new `ThreadedCamera` from raw values, including the raw capture function.
///
/// **This is meant for advanced users only.**
///
/// An example capture function can be found by clicking `[src]` and scrolling down to the bottom to function `camera_frame_thread_loop()`.
/// # 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: usize,
format: Option<CameraFormat>,
backend: CaptureAPIBackend,
func: Option<
fn(
_: Arc<FairMutex<Camera>>,
_: Arc<FairMutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
_: Arc<FairMutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
_: Arc<AtomicBool>,
),
>,
) -> Result<Self, NokhwaError> {
let camera = Arc::new(FairMutex::new(Camera::with_backend(
index, format, backend,
)?));
let format = match format {
Some(fmt) => fmt,
None => CameraFormat::default(),
};
let frame_callback = Arc::new(FairMutex::new(None));
let die_bool = Arc::new(AtomicBool::new(false));
let holding_cell = Arc::new(FairMutex::new(ImageBuffer::new(
format.width(),
format.height(),
)));
let die_clone = die_bool.clone();
let camera_clone = camera.clone();
let callback_clone = frame_callback.clone();
let holding_cell_clone = holding_cell.clone();
let func = match func {
Some(f) => f,
None => camera_frame_thread_loop,
};
std::thread::spawn(move || {
func(camera_clone, callback_clone, holding_cell_clone, die_clone)
});
Ok(ThreadedCamera {
camera,
frame_callback,
last_frame_captured: holding_cell,
die_bool,
let arc_camera = Arc::new(Mutex::new(Camera::new(index, format)?));
Ok(CallbackCamera {
camera: arc_camera,
frame_callback: Arc::new(Mutex::new(Box::new(callback))),
last_frame_captured: Arc::new(Mutex::new(Buffer::new_with_vec(
Resolution::new(0, 0),
&vec![],
FrameFormat::GRAY,
))),
die_bool: Arc::new(Default::default()),
})
}
/// Gets the current Camera's index.
#[must_use]
pub fn index(&self) -> usize {
self.camera.lock().index()
self.camera.lock().index().clone()
}
/// Sets the current Camera's index. Note that this re-initializes the camera.
@@ -154,14 +100,14 @@ impl ThreadedCamera {
/// Gets the current Camera's backend
#[must_use]
pub fn backend(&self) -> CaptureAPIBackend {
pub fn backend(&self) -> ApiBackend {
self.camera.lock().backend()
}
/// Sets the current Camera's backend. Note that this re-initializes the camera.
/// # Errors
/// The new backend may not exist or may fail to initialize the new camera.
pub fn set_backend(&mut self, new_backend: CaptureAPIBackend) -> Result<(), NokhwaError> {
pub fn set_backend(&mut self, new_backend: ApiBackend) -> Result<(), NokhwaError> {
self.camera.lock().set_backend(new_backend)
}
@@ -172,8 +118,7 @@ impl ThreadedCamera {
}
/// 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()
}
@@ -182,7 +127,8 @@ impl ThreadedCamera {
/// # 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)
}
@@ -204,9 +150,15 @@ impl ThreadedCamera {
}
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]).
#[must_use]
pub fn resolution(&self) -> Resolution {
self.camera.lock().resolution()
pub fn resolution(&self) -> Result<Resolution, NokhwaError> {
Ok(self
.camera
.lock()
.map_err(|why| NokhwaError::GetPropertyError {
property: "Resolution".to_string(),
error: why.to_string(),
})?
.resolution())
}
/// Will set the current [`Resolution`]
@@ -214,14 +166,28 @@ impl ThreadedCamera {
/// # 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.camera.lock().set_resolution(new_res)
*self.last_frame_captured.lock() =
Buffer::new_with_vec(new_res, Vec::default(), self.camera_format()?.format());
self.camera
.lock()
.map_err(|why| NokhwaError::SetPropertyError {
property: "Resolution".to_string(),
value: new_res.to_string(),
error: why.to_string(),
})?
.set_resolution(new_res)
}
/// Gets the current camera framerate (See: [`CameraFormat`]).
#[must_use]
pub fn frame_rate(&self) -> u32 {
self.camera.lock().frame_rate()
pub fn frame_rate(&self) -> Result<u32, NokhwaError> {
Ok(self
.camera
.lock()
.map_err(|why| NokhwaError::GetPropertyError {
property: "Framerate".to_string(),
error: why.to_string(),
})?
.frame_rate())
}
/// Will set the current framerate
@@ -229,13 +195,26 @@ impl ThreadedCamera {
/// # 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> {
self.camera.lock().set_frame_rate(new_fps)
self.camera
.lock()
.map_err(|why| NokhwaError::SetPropertyError {
property: "Framerate".to_string(),
value: new_fps.to_string(),
error: why.to_string(),
})?
.set_frame_rate(new_fps)
}
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]).
#[must_use]
pub fn frame_format(&self) -> FrameFormat {
self.camera.lock().frame_format()
pub fn frame_format(&self) -> Result<FrameFormat, NokhwaError> {
Ok(self
.camera
.lock()
.map_err(|why| NokhwaError::GetPropertyError {
property: "Frameformat".to_string(),
error: why.to_string(),
})?
.frame_format())
}
/// Will set the current [`FrameFormat`]
@@ -243,14 +222,27 @@ impl ThreadedCamera {
/// # 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> {
self.camera.lock().set_frame_format(fourcc)
self.camera
.lock()
.map_err(|why| NokhwaError::SetPropertyError {
property: "Framerate".to_string(),
value: fourcc.to_string(),
error: why.to_string(),
})?
.set_frame_format(fourcc)
}
/// Gets the current supported list of [`KnownCameraControls`]
/// Gets the current supported list of [`KnownCameraControl`]
/// # Errors
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
pub fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
self.camera.lock().supported_camera_controls()
pub fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControl>, NokhwaError> {
self.camera
.lock()
.map_err(|why| NokhwaError::GetPropertyError {
property: "Supported Camera Controls".to_string(),
error: why.to_string(),
})
.supported_camera_controls()
}
/// Gets the current supported list of [`CameraControl`]s keyed by its name as a `String`.
@@ -281,7 +273,7 @@ impl ThreadedCamera {
.collect::<Vec<(String, CameraControl)>>();
let mut control_map = HashMap::with_capacity(maybe_camera_controls.len());
for (kc, cc) in maybe_camera_controls.into_iter() {
for (kc, cc) in maybe_camera_controls {
control_map.insert(kc, cc);
}
@@ -293,32 +285,38 @@ impl ThreadedCamera {
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
pub fn camera_controls_known_camera_controls(
&self,
) -> Result<HashMap<KnownCameraControls, CameraControl>, NokhwaError> {
) -> Result<HashMap<KnownCameraControl, CameraControl>, NokhwaError> {
let known_controls = self.supported_camera_controls()?;
let maybe_camera_controls = known_controls
.iter()
.map(|x| (*x, self.camera_control(*x)))
.filter(|(_, x)| x.is_ok())
.map(|(c, x)| (c, Result::unwrap(x)))
.collect::<Vec<(KnownCameraControls, CameraControl)>>();
.collect::<Vec<(KnownCameraControl, CameraControl)>>();
let mut control_map = HashMap::with_capacity(maybe_camera_controls.len());
for (kc, cc) in maybe_camera_controls.into_iter() {
for (kc, cc) in maybe_camera_controls {
control_map.insert(kc, cc);
}
Ok(control_map)
}
/// Gets the value of [`KnownCameraControls`].
/// Gets the value of [`KnownCameraControl`].
/// # Errors
/// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, etc)
/// this will error.
pub fn camera_control(
&self,
control: KnownCameraControls,
control: KnownCameraControl,
) -> Result<CameraControl, NokhwaError> {
self.camera.lock().camera_control(control)
self.camera
.lock()
.map_err(|why| NokhwaError::GetPropertyError {
property: "Camera Control".to_string(),
error: why.to_string(),
})?
.camera_control(control)
}
/// Sets the control to `control` in the camera.
@@ -327,103 +325,114 @@ impl ThreadedCamera {
/// # Errors
/// If the `control` is not supported, the value is invalid (less than min, greater than max, not in step), or there was an error setting the control,
/// this will error.
pub fn set_camera_control(&mut self, control: CameraControl) -> Result<(), NokhwaError> {
self.camera.lock().set_camera_control(control)
}
/// Gets the current supported list of Controls as an `Any` from the backend.
/// The `Any`'s type is defined by the backend itself, please check each of the backend's documentation.
/// # Errors
/// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
pub fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
self.camera.lock().raw_supported_camera_controls()
}
/// Sets the control to `control` in the camera.
/// The control's type is defined the backend itself. It may be a string, or more likely its a integer ID.
/// The backend itself has documentation of the proper input/return values, please check each of the backend's documentation.
/// # Errors
/// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, wrong Any type)
/// this will error.
pub fn raw_camera_control(&self, control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
self.camera.lock().raw_camera_control(control)
}
/// Sets the control to `control` in the camera.
/// The `control`/`value`'s type is defined the backend itself. It may be a string, or more likely its a integer ID/Value.
/// Usually, the pipeline is calling [`camera_control()`](crate::CaptureBackendTrait::camera_control()), getting a camera control that way
/// then calling one of the methods to set the value: [`set_value()`](CameraControl::set_value()) or [`with_value()`](CameraControl::with_value()).
/// # Errors
/// If the `control` is not supported, the value is invalid (wrong Any type, backend refusal), or there was an error setting the control,
/// this will error.
pub fn set_raw_camera_control(
pub fn set_camera_control(
&mut self,
control: &dyn Any,
value: &dyn Any,
id: KnownCameraControl,
control: ControlValueSetter,
) -> Result<(), NokhwaError> {
self.camera.lock().set_raw_camera_control(control, value)
self.camera
.lock()
.map_err(|why| NokhwaError::SetPropertyError {
property: "Camera Control".to_string(),
value: format!("{}: {}", id, control),
error: why.to_string(),
})?
.set_camera_control(id, control)
}
/// Will open the camera stream with set parameters. This will be called internally if you try and call [`frame()`](crate::Camera::frame()) before you call [`open_stream()`](crate::Camera::open_stream()).
/// The callback will be called every frame.
/// # Errors
/// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error.
pub fn open_stream(
&mut self,
callback: fn(ImageBuffer<Rgb<u8>, Vec<u8>>),
) -> Result<(), NokhwaError> {
*self.frame_callback.lock() = Some(callback);
self.camera.lock().open_stream()
pub fn open_stream(&mut self) -> Result<(), NokhwaError> {
self.camera
.lock()
.map_err(|why| NokhwaError::SetPropertyError {
property: "camera".to_string(),
value: "callback".to_string(),
error: why.to_string(),
})?
.open_stream()
}
/// Sets the frame callback to the new specified function. This function will be called instead of the previous one(s).
pub fn set_callback(&mut self, callback: fn(ImageBuffer<Rgb<u8>, Vec<u8>>)) {
*self.frame_callback.lock() = Some(callback);
pub fn set_callback(
&mut self,
callback: impl FnMut(Buffer) + Send + 'static,
) -> Result<(), NokhwaError> {
*self
.frame_callback
.lock()
.map_err(|why| NokhwaError::GetPropertyError {
property: "frame_callback".to_string(),
error: why.to_string(),
})? = Box::new(callback);
Ok(())
}
/// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame)
pub fn poll_frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
let frame = self.camera.lock().frame()?;
/// # Errors
/// This will error if the camera fails to capture a frame.
pub fn poll_frame(&mut self) -> Result<Buffer, NokhwaError> {
let frame = self
.camera
.lock()
.map_err(|why| NokhwaError::ReadFrameError(why.to_string()))?
.frame()?;
*self.last_frame_captured.lock() = frame.clone();
Ok(frame)
}
/// Gets the last frame captured by the camera.
pub fn last_frame(&self) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
self.last_frame_captured.lock().clone()
#[must_use]
pub fn last_frame(&self) -> Buffer {
self.last_frame_captured
.lock()
.map_err(|why| NokhwaError::ReadFrameError(why.to_string()))?
.clone()
}
/// Checks if stream if open. If it is, it will return true.
#[must_use]
pub fn is_stream_open(&self) -> bool {
self.camera.lock().is_stream_open()
self.camera
.lock()
.map_err(|why| NokhwaError::GetPropertyError {
property: "is stream open".to_string(),
error: why.to_string(),
})?
.is_stream_open()
}
/// Will drop the stream.
/// # Errors
/// Please check the `Quirks` section of each backend.
pub fn stop_stream(&mut self) -> Result<(), NokhwaError> {
self.camera.lock().stop_stream()
self.camera
.lock()
.map_err(|why| NokhwaError::StreamShutdownError(why.to_string()))
.stop_stream()
}
}
impl Drop for ThreadedCamera {
impl Drop for CallbackCamera {
fn drop(&mut self) {
let _ = self.stop_stream();
let _stop_stream_err = self.stop_stream();
self.die_bool.store(true, Ordering::SeqCst);
}
}
fn camera_frame_thread_loop(
camera: Arc<FairMutex<Camera>>,
callback: Arc<FairMutex<Option<fn(ImageBuffer<Rgb<u8>, Vec<u8>>)>>>,
holding_cell: Arc<FairMutex<ImageBuffer<Rgb<u8>, Vec<u8>>>>,
die_bool: Arc<AtomicBool>,
camera: &AtomicLock<Camera>,
frame_callback: &HeldCallbackType,
last_frame_captured: &AtomicLock<ImageBuffer<Rgb<u8>, Vec<u8>>>,
die_bool: &Arc<AtomicBool>,
) {
loop {
if let Ok(img) = camera.lock().frame() {
*holding_cell.lock() = img.clone();
if let Some(cb) = callback.lock().deref() {
cb(img)
if let Ok(img) = camera.lock().fr {
*last_frame_captured.lock() = img.clone();
if let Some(cb) = (*frame_callback.lock()).as_mut() {
cb(img);
}
}
if die_bool.load(Ordering::SeqCst) {
-1342
View File
File diff suppressed because it is too large Load Diff