This commit is contained in:
l1npengtul
2025-11-10 13:40:36 +09:00
parent 6f5d4efac5
commit d25151da6d
15 changed files with 145 additions and 131 deletions
+10 -13
View File
@@ -17,7 +17,6 @@ members = [
"nokhwa-bindings-windows",
"nokhwa-bindings-linux",
"nokhwa-core",
"examples/*",
]
exclude = [ "examples/jscam" ]
@@ -46,29 +45,27 @@ input-opencv = [
"opencv/clang-runtime",
"nokhwa-core/opencv-mat",
]
input-jscam = [
"web-sys",
"js-sys",
"wasm-bindgen-futures",
"wasm-bindgen",
"wasm-rs-async-executor",
]
# 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", "camera-sync-impl" ]
# output-wasm = [ "input-jscam" ]
output-threaded = [ "parking_lot", "camera-sync-impl" ]
camera-sync-impl = [ ]
small-wasm = [ ]
# small-wasm = [ ]
docs-only = [
"input-native",
"input-opencv",
"input-jscam",
"output-wgpu",
"output-threaded",
"serialize",
]
docs-nolink = [ "nokhwa-core/docs-features" ]
docs-features = [ ]
test-fail-warning = [ ]
[dependencies]
thiserror = "2.0"
+48 -38
View File
@@ -5,50 +5,60 @@
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
outputs = {
self,
nixpkgs,
rust-overlay,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {
inherit system overlays;
inherit system;
overlays = [rust-overlay.overlays.default];
};
rustbin = pkgs.rust-bin.selectLatestNightlyWith (toolchain:
toolchain.default.override {
extensions = ["rust-src" "clippy" "rustfmt" "miri"];
});
in
{
devShells.default = pkgs.mkShell {
#LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
#BINDGEN_EXTRA_CLANG_ARGS = "-isystem ${pkgs.libclang.lib}/lib/clang/${flake-utils.lib.getVersion pkgs.clang}/include";
nativeBuildInputs = [
pkgs.pkg-config
pkgs.cmake
pkgs.vcpkg
];
packages = with pkgs; [
rust-analyzer
pkg-config
opencv
alsa-lib
systemdLibs
cmake
fontconfig
linuxHeaders
rustPlatform.bindgenHook
llvmPackages.libclang.lib
llvmPackages.clang
libv4l
v4l-utils
rustbin
extensions = [
"rust-src"
"clippy"
"rustfmt"
"miri"
"rust-analyzer"
];
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
shellHook = ''
export LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
cargo version
'';
});
in {
formatter = pkgs.alejandra;
devShells.default = pkgs.mkShell {
packages =
[
rustbin
]
++ (with pkgs; [
rust-analyzer
pkg-config
opencv
alsa-lib
systemdLibs
cmake
fontconfig
linuxHeaders
rustPlatform.bindgenHook
llvmPackages.libclang.lib
llvmPackages.clang
libv4l
v4l-utils
rustbin
]);
env.RUST_SRC_PATH = "${rustbin}/lib/rustlib/src/rust/library";
env.LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
shellHook = ''
echo "WONDERHOOOOOY!!!!"
'';
};
}
);
+9 -9
View File
@@ -1,23 +1,23 @@
[package]
name = "nokhwa-bindings-linux"
version = "0.1.2"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/l1npengtul/nokhwa"
name = "nokhwa-bindings-linux"
version = "0.1.3"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/l1npengtul/nokhwa"
description = "The V4L2 bindings crate for `nokhwa`"
keywords = ["v4l", "v4l2", "linux", "capture", "webcam"]
keywords = [ "v4l", "v4l2", "linux", "capture", "webcam" ]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
# Disable arena buffer of v4l2 for the crate to work on raspberry pi with faulty v4l2 driver
# https://github.com/l1npengtul/nokhwa/pull/121
no-arena-buffer = []
no-arena-buffer = [ ]
[dependencies]
[dependencies.nokhwa-core]
version = "0.1"
path = "../nokhwa-core"
path = "../nokhwa-core"
[target.'cfg(target_os="linux")'.dependencies]
v4l = { version = "0.14", features = ["v4l2-sys"] }
v4l = { version = "0.14", features = [ "v4l2-sys" ] }
+3 -3
View File
@@ -41,7 +41,7 @@ mod internal {
control::{Control, Flags, Type, Value},
frameinterval::FrameIntervalEnum,
framesize::FrameSizeEnum,
io::traits::CaptureStream,
io::traits::{CaptureStream, Stream},
prelude::MmapStream,
video::{capture::Parameters, Capture},
Device, Format, FourCC,
@@ -885,7 +885,7 @@ mod internal {
))
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
match &mut self.stream_handle {
Some(sh) => match sh.next() {
Ok((data, _)) => Ok(Cow::Borrowed(data)),
@@ -1090,7 +1090,7 @@ mod internal {
todo!()
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
todo!()
}
+2 -2
View File
@@ -1129,7 +1129,7 @@ pub mod wmf {
Ok(())
}
pub fn raw_bytes(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
pub fn raw_bytes(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
let mut imf_sample: Option<IMFSample> = match unsafe { MFCreateSample() } {
Ok(sample) => Some(sample),
Err(why) => {
@@ -1333,7 +1333,7 @@ pub mod wmf {
))
}
pub fn raw_bytes(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
pub fn raw_bytes(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Only on Windows".to_string(),
))
+4 -2
View File
@@ -117,7 +117,9 @@ impl Buffer {
/// Most notably, the `data` **must** stay in scope for the duration of the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html) or bad, ***bad*** things happen.
#[cfg(feature = "opencv-mat")]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "opencv-mat")))]
pub fn decode_opencv_mat<F: FormatDecoder>(&mut self) -> Result<BoxedRef<Mat>, NokhwaError> {
pub fn decode_opencv_mat<F: FormatDecoder>(
&mut self,
) -> Result<BoxedRef<'_, Mat>, NokhwaError> {
use crate::buffer::channel_defs::make_mat;
make_mat::<F>(self.resolution, self.buffer())
@@ -230,7 +232,7 @@ pub mod channel_defs {
pub(crate) fn make_mat<F>(
resolution: Resolution,
data: &[u8],
) -> Result<opencv::boxed_ref::BoxedRef<opencv::core::Mat>, NokhwaError>
) -> Result<opencv::boxed_ref::BoxedRef<'_, opencv::core::Mat>, NokhwaError>
where
F: FormatDecoder,
{
+10 -8
View File
@@ -25,7 +25,7 @@ use crate::{
use std::{borrow::Cow, collections::HashMap};
#[cfg(feature = "wgpu-types")]
use wgpu::{
Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue,
Device as WgpuDevice, Extent3d, Queue as WgpuQueue, TexelCopyBufferLayout,
Texture as WgpuTexture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat,
TextureUsages,
};
@@ -163,7 +163,7 @@ pub trait CaptureBackendTrait {
/// 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>;
fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError>;
/// 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`]
@@ -190,12 +190,14 @@ pub trait CaptureBackendTrait {
/// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
/// # Errors
/// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
fn frame_texture<'a>(
fn frame_texture(
&mut self,
device: &WgpuDevice,
queue: &WgpuQueue,
label: Option<&'a str>,
label: Option<&str>,
) -> Result<WgpuTexture, NokhwaError> {
use wgpu::{Origin3d, TexelCopyTextureInfoBase};
use crate::pixel_format::RgbAFormat;
let frame = self.frame()?.decode_image::<RgbAFormat>()?;
@@ -221,14 +223,14 @@ pub trait CaptureBackendTrait {
let height_nonzero = frame.height();
queue.write_texture(
ImageCopyTexture {
TexelCopyTextureInfoBase {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
mip_level: 1,
origin: Origin3d { x: 0, y: 0, z: 0 },
aspect: TextureAspect::All,
},
&frame,
ImageDataLayout {
TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width_nonzero),
rows_per_image: Some(height_nonzero),
+1 -1
View File
@@ -64,7 +64,7 @@ impl RequestedFormat<'_> {
pub fn with_formats(
requested: RequestedFormatType,
decoder: &[FrameFormat],
) -> RequestedFormat {
) -> RequestedFormat<'_> {
RequestedFormat {
requested_format: requested,
wanted_decoder: decoder,
+5 -4
View File
@@ -23,14 +23,15 @@ use nokhwa_bindings_macos::{
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
pixel_format::RgbFormat,
traits::CaptureBackendTrait,
types::{
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter,
FrameFormat, KnownCameraControl, RequestedFormat, RequestedFormatType, Resolution,
FrameFormat, KnownCameraControl, RequestedFormat, Resolution,
},
};
#[cfg(target_os = "macos")]
use nokhwa_core::{pixel_format::RgbFormat, types::RequestedFormatType};
#[cfg(target_os = "macos")]
use std::{ffi::CString, sync::Arc};
use std::{borrow::Cow, collections::HashMap};
@@ -287,7 +288,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
Ok(buffer)
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
let result = match self.frame_buffer_receiver.recv() {
Ok(recv) => Ok(Cow::from(recv.0)),
Err(why) => Err(NokhwaError::ReadFrameError(why.to_string())),
@@ -480,7 +481,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
todo!()
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
todo!()
}
+1 -1
View File
@@ -251,7 +251,7 @@ impl CaptureBackendTrait for MediaFoundationCaptureDevice {
))
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
self.inner.raw_bytes()
}
+2 -2
View File
@@ -169,7 +169,7 @@ impl OpenCvCaptureDevice {
/// # Errors
/// If the frame is failed to be read, this will error.
#[allow(clippy::cast_sign_loss)]
pub fn raw_frame_vec(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
pub fn raw_frame_vec(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
if !self.is_stream_open() {
return Err(NokhwaError::ReadFrameError(
"Stream is not open!".to_string(),
@@ -541,7 +541,7 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
))
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
let cow = self.raw_frame_vec()?;
Ok(cow)
}
+1 -1
View File
@@ -384,7 +384,7 @@ impl Camera {
/// 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> {
pub fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
match self.device.frame_raw() {
Ok(f) => Ok(f),
Err(why) => Err(why),
+39 -35
View File
@@ -32,7 +32,7 @@ use std::{
fmt::{Debug, Display, Formatter},
ops::Deref,
};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
console::log_1, CanvasRenderingContext2d, Document, Element, HtmlCanvasElement,
@@ -41,8 +41,9 @@ use web_sys::{
};
#[cfg(feature = "output-wgpu")]
use wgpu::{
Device, Extent3d, ImageCopyTexture, ImageDataLayout, Queue, Texture, TextureAspect,
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
Device as WgpuDevice, Extent3d, Queue as WgpuQueue, TexelCopyBufferLayout,
Texture as WgpuTexture, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat,
TextureUsages,
};
// why no code completion
@@ -203,8 +204,8 @@ pub async fn request_permission() -> Result<(), NokhwaError> {
match media_devices.get_user_media_with_constraints(
MediaStreamConstraints::new()
.video(&JsValue::from_bool(true))
.audio(&JsValue::from_bool(false)),
.set_video(&JsValue::from_bool(true))
.set_audio(&JsValue::from_bool(false)),
) {
Ok(promise) => {
let js_future = JsFuture::from(promise);
@@ -266,8 +267,8 @@ pub async fn query_js_cameras() -> Result<Vec<CameraInfo>, NokhwaError> {
if media_device_info.kind() == MediaDeviceKind::Videoinput {
match media_devices.get_user_media_with_constraints(
MediaStreamConstraints::new()
.audio(&jsv!(false))
.video(&jsv!(obj!((
.set_audio(&jsv!(false))
.set_video(&jsv!(obj!((
"deviceId",
media_device_info.device_id()
)))),
@@ -603,8 +604,8 @@ impl JSCameraConstraintsBuilder {
feature = "output-wasm",
wasm_bindgen(js_name = MinResolution)
)]
pub fn min_resolution(mut self, min_resolution: Resolution) -> JSCameraConstraintsBuilder {
self.min_resolution = Some(min_resolution);
pub fn min_resolution(mut self, min_resolution: &Resolution) -> JSCameraConstraintsBuilder {
self.min_resolution = Some(*min_resolution);
self
}
@@ -618,8 +619,8 @@ impl JSCameraConstraintsBuilder {
feature = "output-wasm",
wasm_bindgen(js_name = Resolution)
)]
pub fn resolution(mut self, new_resolution: Resolution) -> JSCameraConstraintsBuilder {
self.preferred_resolution = new_resolution;
pub fn resolution(mut self, new_resolution: &Resolution) -> JSCameraConstraintsBuilder {
self.preferred_resolution = *new_resolution;
self
}
@@ -633,8 +634,8 @@ impl JSCameraConstraintsBuilder {
feature = "output-wasm",
wasm_bindgen(js_name = MaxResolution)
)]
pub fn max_resolution(mut self, max_resolution: Resolution) -> JSCameraConstraintsBuilder {
self.min_resolution = Some(max_resolution);
pub fn max_resolution(mut self, max_resolution: &Resolution) -> JSCameraConstraintsBuilder {
self.min_resolution = Some(*max_resolution);
self
}
@@ -1038,8 +1039,8 @@ impl JSCameraConstraintsBuilder {
}
let media_stream_constraints = MediaStreamConstraints::new()
.audio(&jsv!(false))
.video(&jsv!(video_object))
.set_audio(&jsv!(false))
.set_video(&jsv!(video_object))
.clone();
JSCameraConstraints {
@@ -2577,20 +2578,20 @@ impl JSCamera {
#[cfg(feature = "output-wgpu")]
/// 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(
&mut self,
device: &Device,
queue: &Queue,
label: Option<&'a str>,
) -> Result<Texture, NokhwaError> {
use std::num::NonZeroU32;
let resolution = self.resolution();
let frame = self.frame_raw()?;
device: &WgpuDevice,
queue: &WgpuQueue,
label: Option<&str>,
) -> Result<WgpuTexture, NokhwaError> {
use wgpu::{Origin3d, TexelCopyTextureInfoBase};
use crate::pixel_format::RgbAFormat;
let frame = self.frame()?.decode_image::<RgbAFormat>()?;
let texture_size = Extent3d {
width: resolution.width(),
height: resolution.height(),
width: frame.width(),
height: frame.height(),
depth_or_array_layers: 1,
};
@@ -2602,28 +2603,31 @@ impl JSCamera {
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
view_formats: &[TextureFormat::Rgba8UnormSrgb],
view_formats: &[],
});
let width_nonzero = 4 * frame.width();
let height_nonzero = frame.height();
queue.write_texture(
ImageCopyTexture {
TexelCopyTextureInfoBase {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
mip_level: 1,
origin: Origin3d { x: 0, y: 0, z: 0 },
aspect: TextureAspect::All,
},
frame.borrow(),
ImageDataLayout {
&frame,
TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * resolution.width()),
rows_per_image: Some(resolution.height()),
bytes_per_row: Some(width_nonzero),
rows_per_image: Some(height_nonzero),
},
texture_size,
);
Ok(texture)
}
/// Checks if the stream is open.
pub fn is_open(&self) -> bool {
let stream = self
+4 -5
View File
@@ -16,7 +16,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#![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
@@ -34,10 +33,10 @@
pub mod backends;
mod camera;
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 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;
pub use nokhwa_core::pixel_format::FormatDecoder;
mod query;
+6 -7
View File
@@ -295,15 +295,14 @@ fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
))
}
#[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;
// #[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())
}
// block_on(query_js_cameras())
// }
#[cfg(not(feature = "input-jscam"))]
fn query_wasm() -> Result<Vec<CameraInfo>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Browser))
}