mirror of
https://github.com/l1npengtul/nokhwa.git
synced 2026-07-04 10:37:26 +00:00
Merge branch 'senpai' into 0.9.2
This commit is contained in:
@@ -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']
|
||||
@@ -10,3 +10,5 @@ nokhwa.iml
|
||||
/pkg
|
||||
|
||||
target
|
||||
|
||||
.DS_STORE
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Clippy Main" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="clippy --features "output-wgpu, input-avfoundation"" />
|
||||
<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
@@ -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
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
[](https://crates.io/crates/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());
|
||||
}
|
||||
```
|
||||
@@ -42,14 +43,14 @@ The table below lists current Nokhwa API support.
|
||||
- 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 |
|
||||
| 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`) | ✅ | ✅ | ✅ | 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)
|
||||
|
||||
@@ -10,14 +10,13 @@ edition = "2018"
|
||||
default = ["nokhwa/default"]
|
||||
input-msmf = ["nokhwa/input-msmf"]
|
||||
input-v4l = ["nokhwa/input-v4l"]
|
||||
input-uvc = ["nokhwa/input-uvc"]
|
||||
input-opencv = ["nokhwa/input-opencv"]
|
||||
input-gst = ["nokhwa/input-gst"]
|
||||
input-avfoundation = ["nokhwa/input-avfoundation"]
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.3"
|
||||
glium = "0.30.0"
|
||||
glium = "0.31.0"
|
||||
glutin = "0.27.0"
|
||||
flume = "0.10.9"
|
||||
|
||||
|
||||
BIN
Binary file not shown.
@@ -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,14 +352,15 @@ fn main() {
|
||||
// run the event loop
|
||||
|
||||
gl_event_loop.run(move |event, _window, ctrl| {
|
||||
let before_capture = Instant::now();
|
||||
*ctrl = match event {
|
||||
Event::MainEventsCleared => {
|
||||
let instant = Instant::now();
|
||||
let frame = recv.recv().unwrap();
|
||||
let after_capture = Instant::now();
|
||||
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 raw_data = RawImage2d::from_raw_rgb(frame.into_raw(), frame_size);
|
||||
let gl_texture = Texture2d::new(&gl_display, raw_data).unwrap();
|
||||
|
||||
let uniforms = uniform! {
|
||||
@@ -380,16 +386,15 @@ fn main() {
|
||||
.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,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,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.
|
||||
|
||||
@@ -9,7 +9,7 @@ edition = "2021"
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.23.14"
|
||||
no-default-features = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.nokhwa]
|
||||
path = "../../../nokhwa"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ edition = "2018"
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.23.14"
|
||||
no-default-features = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.nokhwa]
|
||||
path = "../../../nokhwa"
|
||||
|
||||
@@ -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
@@ -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,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"
|
||||
|
||||
@@ -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")))]
|
||||
|
||||
+1600
-727
File diff suppressed because it is too large
Load Diff
@@ -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"]
|
||||
|
||||
+680
-1257
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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(),
|
||||
})?)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
(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.
|
||||
/// # 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(),
|
||||
))
|
||||
}
|
||||
/// 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,
|
||||
};
|
||||
let rgba_image: RgbaImage = image_data.convert();
|
||||
buffer.copy_from_slice(rgba_image.as_raw());
|
||||
return Ok(rgba_image.len());
|
||||
if alpha {
|
||||
return (resolution.width() * resolution.height() * (pxwidth + 1)) as usize;
|
||||
}
|
||||
buffer.copy_from_slice(frame.as_ref());
|
||||
Ok(frame.len())
|
||||
(resolution.width() * resolution.height() * pxwidth) as usize
|
||||
}
|
||||
|
||||
#[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
@@ -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.
|
||||
@@ -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.
|
||||
Vendored
-824
@@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Vendored
-115
@@ -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;
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
fn camera_control(&self, _: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
|
||||
Err(NokhwaError::NotImplementedError(
|
||||
"Not Implemented".to_string(),
|
||||
))
|
||||
return Err(NokhwaError::GetPropertyError {
|
||||
property: control.to_string(),
|
||||
error: "Not Found".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
fn set_camera_control(&mut self, _: CameraControl) -> Result<(), NokhwaError> {
|
||||
Err(NokhwaError::NotImplementedError(
|
||||
"Not Implemented".to_string(),
|
||||
))
|
||||
fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
|
||||
self.device.get_controls()
|
||||
}
|
||||
|
||||
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())),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(¤t_constraints.group_id())
|
||||
.device_id(¤t_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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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")]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 camera_format = match cfmt {
|
||||
Some(cam_fmt) => cam_fmt,
|
||||
None => CameraFormat::default(),
|
||||
};
|
||||
|
||||
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
|
||||
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),
|
||||
}
|
||||
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())),
|
||||
},
|
||||
.map_err(|why| {
|
||||
NokhwaError::OpenDeviceError(format!("Failed to open {}", index), why.to_string())
|
||||
})?;
|
||||
|
||||
let real = match cfmt {
|
||||
RequestedFormat::Exact(e) => e,
|
||||
RequestedFormat::None => CameraFormat::default(),
|
||||
_ => return Err(NokhwaError::UnsupportedOperationError(ApiBackend::OpenCv)),
|
||||
};
|
||||
|
||||
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),
|
||||
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 set_raw_camera_control(
|
||||
fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
|
||||
Err(NokhwaError::UnsupportedOperationError(ApiBackend::OpenCv))
|
||||
}
|
||||
|
||||
fn set_camera_control(
|
||||
&mut self,
|
||||
control: &dyn Any,
|
||||
value: &dyn Any,
|
||||
id: KnownCameraControl,
|
||||
value: ControlValueSetter,
|
||||
) -> 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 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(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
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(),
|
||||
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(
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user