From 78eb1fb7bb02de38c644266c6154bcca7c19a408 Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Sat, 7 May 2022 03:23:54 +0900 Subject: [PATCH] 0.10 preliminary work --- Cargo.toml | 37 +++----- Jenkinsfile | 135 --------------------------- nokhwa-bindings-macos/src/lib.rs | 1 + src/backends/capture/gst_backend.rs | 2 +- src/backends/capture/v4l2_backend.rs | 17 ++++ src/buffer.rs | 51 ++++++++++ src/camera.rs | 29 ++++-- src/camera_traits.rs | 8 +- src/error.rs | 2 + src/lib.rs | 2 + src/pixel_format.rs | 57 +++++++++++ src/utils.rs | 16 ++++ 12 files changed, 187 insertions(+), 170 deletions(-) delete mode 100644 Jenkinsfile create mode 100644 src/buffer.rs create mode 100644 src/pixel_format.rs diff --git a/Cargo.toml b/Cargo.toml index aca6e22..900a55e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ repository = "https://github.com/l1npengtul/nokhwa" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows"] +exclude = ["examples/threaded-capture", "examples/capture"] + [lib] crate-type = ["cdylib", "rlib"] @@ -28,7 +32,7 @@ input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "was output-wgpu = ["wgpu"] output-wasm = ["input-jscam"] output-threaded = ["parking_lot"] -small-wasm = ["wee_alloc"] +small-wasm = [] docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"] docs-features = [] @@ -37,35 +41,29 @@ test-fail-warning = [] [dependencies] thiserror = "1.0" paste = "1.0" +anymap = "1.0.0-beta.2" +enum_dispatch = "0.3" [dependencies.flume] version = "0.10" optional = true [dependencies.image] -version = "0.23" +version = "0.24" default-features = false -[target.'cfg(not(target_arch = "wasm"))'.dependencies.mozjpeg] +[dependencies.mozjpeg] version = "0.9" optional = true -[target.'cfg(target_os = "linux")'.dependencies.v4l] +[dependencies.v4l] version = "0.12" optional = true -[target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit] +[dependencies.v4l2-sys-mit] version = "0.2" optional = true -[dependencies.ouroboros] -version = "0.14" -optional = true - -# [dependencies.uvc] -# version = "0.2" -# optional = true - [dependencies.usb_enumeration] version = "0.1.2" optional = true @@ -75,17 +73,17 @@ version = "^0.12" optional = true [dependencies.opencv] -version = "0.62" +version = "0.63" features = ["clang-runtime"] optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3" +version = "0.3.4" path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] -version = "0.1" +version = "0.1.1" path = "nokhwa-bindings-macos" optional = true @@ -111,7 +109,6 @@ optional = true [dependencies.web-sys] version = "0.3" -# why features = [ "console", "CanvasRenderingContext2d", @@ -146,12 +143,8 @@ optional = true version = "0.9" optional = true -[dependencies.wee_alloc] -version = "0.4" -optional = true - [dependencies.parking_lot] -version = "0.11" +version = "0.12" optional = true [dependencies.lazy_static] diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 3b00338..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,135 +0,0 @@ -pipeline { - agent { - node { - label 'ci_linux' - } - - } - stages { - stage('Sanity Check') { - steps { - echo '$BUILD_TAG' - scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*') - } - } - - stage('Cargo RustFMT') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*') - sh 'rustup update stable' - sh 'cargo fmt --all -- --check' - } - } - - stage('Build, Clippy') { - parallel { - stage('V4L2') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update stable' - sh 'cargo clippy --features "input-v4l, output-wgpu, test-fail-warning"' - } - } - - stage('Media Foundation') { - agent { - node { - label 'ci_windows' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - pwsh(script: 'rustup update stable', returnStatus: true) - pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true, returnStdout: true) - } - } - - stage('AVFoundation') { - steps { - sh 'echo TODO' - } - } - - stage('libUVC') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update stable' - sh 'cargo clippy --features "input-uvc, output-wgpu, test-fail-warning"' - } - } - - stage('OpenCV IPCamera') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update stable' - sh 'cargo clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"' - } - } - - stage('GStreamer') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update nightly' - sh 'cargo clippy --features "input-gst, output-wgpu, test-fail-warning"' - } - } - - stage('JSCamera/WASM') { - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update stable' - sh 'wasm-pack build --release -- --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' - sh 'cargo clippy --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' - } - } - - } - } - - stage('RustDOC') { - agent { - node { - label 'ci_linux' - } - - } - steps { - scmSkip(skipPattern: '.*\\[ci skip\\].*', deleteBuild: true) - sh 'rustup update nightly' - sh 'cargo +nightly doc --features "docs-only, docs-nolink, docs-features, test-fail-warning" --no-deps --release' - } - } - - } -} \ No newline at end of file diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index 7668e6e..7523df1 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -1303,6 +1303,7 @@ pub mod avfoundation { pub enum AVFourCC { YUV2, MJPEG, + GRAY8, } // Localized Name diff --git a/src/backends/capture/gst_backend.rs b/src/backends/capture/gst_backend.rs index bd7a18a..c8de249 100644 --- a/src/backends/capture/gst_backend.rs +++ b/src/backends/capture/gst_backend.rs @@ -155,7 +155,7 @@ impl GStreamerCaptureDevice { } } -impl CaptureBackendTrait for GStreamerCaptureDevice { +impl GStreamerCaptureDevice { fn backend(&self) -> CaptureAPIBackend { CaptureAPIBackend::GStreamer } diff --git a/src/backends/capture/v4l2_backend.rs b/src/backends/capture/v4l2_backend.rs index ba699b9..ebf3142 100644 --- a/src/backends/capture/v4l2_backend.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -35,6 +35,7 @@ use v4l::{ use std::any::Any; pub use v4l::control::{Control, Description, Flags}; +use v4l::video::Output; /// Generates a camera control from a device and a description of control /// # Error @@ -212,6 +213,7 @@ impl<'a> V4LCaptureDevice<'a> { let fourcc = match camera_format.format() { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new(b"GRAY"), }; let new_param = Parameters::with_fps(camera_format.frame_rate()); @@ -284,6 +286,7 @@ impl<'a> V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new("GRAY"), }; match v4l::video::Capture::enum_framesizes(&self.device, format) { @@ -311,6 +314,7 @@ impl<'a> V4LCaptureDevice<'a> { } /// Get the inner device (immutable) for e.g. Controls + /// apps bloodtests contact css images index index.html injectionsupplies transfem transmasc #[allow(clippy::must_use_candidate)] pub fn inner_device(&self) -> &Device { &self.device @@ -320,6 +324,18 @@ impl<'a> V4LCaptureDevice<'a> { pub fn inner_device_mut(&mut self) -> &mut Device { &mut self.device } + + /// Force refreshes the inner [`CameraFormat`] state. + pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> { + match (self.device.params(), self.device.format()) { + (Ok(params), Ok(format)) => { + let new_format = CameraFormat::new(params.capabilities.) + } + (_, _) => { + return Err(NokhwaError::GetPropertyError { property: "parameters".to_string(), error: why.to_string() }) + } + } + } } impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { @@ -409,6 +425,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> { let format = match fourcc { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => {} }; let mut res_map = HashMap::new(); for res in resolutions { diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..4c6eeba --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,51 @@ +/* + * Copyright 2022 l1npengtul / The Nokhwa Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::pixel_format::{PixelFormat, PixelFormats}; +use crate::{FrameFormat, NokhwaError, Resolution}; +use image::ImageBuffer; + +#[derive(Clone, Debug, Default, Hash, PartialOrd, PartialEq)] +#[cfg_attr("serde", Serialize, Deserialize)] +pub struct Buffer { + resolution: Resolution, + buffer: Vec, + +} + +impl Buffer { + pub fn new(res: Resolution, buf: Vec) -> Self { + Self { + resolution: res, + buffer: buf, + } + } + + pub fn to_image_with_custom_format(self) -> Result>, NokhwaError> + where + I: PixelFormat, + { + ImageBuffer::from_raw( + self.resolution.width_x, + self.resolution.height_y, + self.buffer, + ).ok_or(NokhwaError::ProcessFrameError { + src: , + destination: "".to_string(), + error: "".to_string() + }) + } +} diff --git a/src/camera.rs b/src/camera.rs index 92378ce..6033440 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -29,14 +29,19 @@ use wgpu::{ }; /// The main `Camera` struct. This is the struct that abstracts over all the backends, providing a simplified interface for use. -pub struct Camera { +pub struct Camera +where + C: CaptureBackendTrait, +{ idx: usize, - backend: Box, + backend: C, backend_api: CaptureAPIBackend, } -#[allow(clippy::nonminimal_bool)] -impl Camera { +impl Camera +where + C: CaptureBackendTrait, +{ /// 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). @@ -386,6 +391,7 @@ impl Camera { FrameFormat::YUYV => { crate::utils::buf_yuyv422_to_rgb(&raw_frame, buffer, convert_rgba)? } + FrameFormat::GRAY8 => {} }; Ok(()) @@ -459,7 +465,10 @@ impl Camera { } } -impl Drop for Camera { +impl Drop for Camera +where + C: CaptureBackendTrait, +{ fn drop(&mut self) { self.stop_stream().unwrap(); } @@ -496,7 +505,7 @@ macro_rules! cap_impl_fn { $( paste::paste! { #[cfg ($cfg) ] - fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](idx: usize, setting: Option) -> Option> { use crate::backends::capture::$backend; match <$backend>::$init_fn(idx, setting) { Ok(cap) => Some(Ok(Box::new(cap))), @@ -504,7 +513,7 @@ macro_rules! cap_impl_fn { } } #[cfg(not( $cfg ))] - fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option, NokhwaError>> { + fn [< init_ $backend_name>](_idx: usize, _setting: Option) -> Option> { None } } @@ -603,11 +612,11 @@ cap_impl_fn! { (AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation) } -fn init_camera( +fn init_camera( index: usize, format: Option, backend: CaptureAPIBackend, -) -> Result, NokhwaError> { +) -> Result { let camera_backend = cap_impl_matches! { backend, index, format, ("input-v4l", Video4Linux, init_v4l), @@ -621,4 +630,4 @@ fn init_camera( } #[cfg(feature = "output-threaded")] -unsafe impl Send for Camera {} +unsafe impl Send for Camera where C: CaptureBackendTrait {} diff --git a/src/camera_traits.rs b/src/camera_traits.rs index 08e30f6..749a4fe 100644 --- a/src/camera_traits.rs +++ b/src/camera_traits.rs @@ -21,6 +21,7 @@ use crate::{ }; use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbaImage}; +use crate::pixel_format::PixelFormat; use std::{any::Any, borrow::Cow, collections::HashMap}; #[cfg(feature = "output-wgpu")] use wgpu::{ @@ -36,7 +37,10 @@ use wgpu::{ /// - Backends, if not provided with a camera format, will be spawned with 640x480@15 FPS, MJPEG [`CameraFormat`]. /// - Behaviour can differ from backend to backend. While the [`Camera`](crate::camera::Camera) struct abstracts most of this away, if you plan to use the raw backend structs please read the `Quirks` section of each backend. /// - If you call [`stop_stream()`](CaptureBackendTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureBackendTrait::open_stream()) to get more frames from the camera. -pub trait CaptureBackendTrait: Send { +pub trait CaptureBackendTrait { + /// Initializes the camera. You must call this before any other function. + fn init(&mut self) -> Result; + /// Returns the current backend used. fn backend(&self) -> CaptureAPIBackend; @@ -209,7 +213,7 @@ pub trait CaptureBackendTrait: Send { queue: &WgpuQueue, label: Option<&'a str>, ) -> Result { - use std::num::NonZeroU32; + use std::{convert::TryFrom, num::NonZeroU32}; let frame = self.frame()?; let rgba_frame: RgbaImage = frame.convert(); diff --git a/src/error.rs b/src/error.rs index 7f776ff..7fb8fb3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,8 @@ use thiserror::Error; #[allow(clippy::module_name_repetitions)] #[derive(Error, Debug, Clone)] pub enum NokhwaError { + #[error("Unitialized Camera. Call `init()` first!")] + UnitializedError, #[error("Could not initialize {backend}: {error}")] InitializeError { backend: CaptureAPIBackend, diff --git a/src/lib.rs b/src/lib.rs index 66ba442..c275aa1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ /// Raw access to each of Nokhwa's backends. pub mod backends; +pub mod buffer; mod camera; mod camera_traits; mod error; @@ -40,6 +41,7 @@ pub mod js_camera; #[cfg(feature = "input-ipcam")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-ipcam")))] pub mod network_camera; +pub mod pixel_format; mod query; /// A camera that runs in a different thread and can call your code based on callbacks. #[cfg(feature = "output-threaded")] diff --git a/src/pixel_format.rs b/src/pixel_format.rs new file mode 100644 index 0000000..d57bb36 --- /dev/null +++ b/src/pixel_format.rs @@ -0,0 +1,57 @@ +/* + * Copyright 2022 l1npengtul / The Nokhwa Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::buffer_output::{BufferOutput, GrayU8, RgbU8}; +use image::{Luma, Pixel, Rgb}; +use std::fmt::Debug; +use std::hash::Hash; + +pub trait PixelFormat: Copy + Clone + Debug + Default + Hash + Send + Sync { + type Output: Pixel; + + const CODE: &'static str; + + fn code(&self) -> &'static str { + CODE + } +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Mjpeg; + +impl PixelFormat for Mjpeg { + type Output = Rgb; + + const CODE: &'static str = "MJPG"; +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Yuyv; + +impl PixelFormat for Yuyv { + type Output = Rgb; + + const CODE: &'static str = "YUYV"; +} + +#[derive(Copy, Clone, Debug, Default, Hash)] +pub struct Gray; + +impl PixelFormat for Gray { + type Output = Luma; + + const CODE: &'static str = "GRAY"; +} diff --git a/src/utils.rs b/src/utils.rs index 4641e73..41623cb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -49,6 +49,7 @@ use nokhwa_bindings_windows::{ use uvc::StreamFormat; #[cfg(all(feature = "input-v4l", target_os = "linux"))] use v4l::{control::Description, Format, FourCC}; +use crate::pixel_format::PixelFormat; /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. /// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) @@ -59,6 +60,7 @@ use v4l::{control::Description, Format, FourCC}; pub enum FrameFormat { MJPEG, YUYV, + GRAY8, } impl Display for FrameFormat { @@ -70,10 +72,19 @@ impl Display for FrameFormat { FrameFormat::YUYV => { write!(f, "YUYV") } + FrameFormat::GRAY8 => { + write!(f, "GRAY8") + } } } } +impl

From

for FrameFormat where P: PixelFormat { + fn from(px: P) -> Self { + match P:: + } +} + #[cfg(feature = "input-uvc")] impl From for uvc::FrameFormat { fn from(ff: FrameFormat) -> Self { @@ -93,6 +104,7 @@ impl From for FrameFormat { match mf_ff { MFFrameFormat::MJPEG => FrameFormat::MJPEG, MFFrameFormat::YUYV => FrameFormat::YUYV, + MFFrameFormat::GRAY8 => FrameFormat::GRAY8, } } } @@ -106,6 +118,7 @@ impl From for MFFrameFormat { match ff { FrameFormat::MJPEG => MFFrameFormat::MJPEG, FrameFormat::YUYV => MFFrameFormat::YUYV, + FrameFormat::GRAY8 => MFFrameFormat::GRAY8, //FIXME } } } @@ -126,6 +139,7 @@ impl From for FrameFormat { match av_fcc { AVFourCC::YUV2 => FrameFormat::YUYV, AVFourCC::MJPEG => FrameFormat::MJPEG, + AVFourCC::GRAY8 => FrameFormat::GRAY8, } } } @@ -146,6 +160,7 @@ impl From for AVFourCC { match ff { FrameFormat::MJPEG => AVFourCC::MJPEG, FrameFormat::YUYV => AVFourCC::YUV2, + FrameFormat::GRAY8 => AVFourCC::GRAY8, } } } @@ -419,6 +434,7 @@ impl From for Format { let pxfmt = match cam_fmt.format() { FrameFormat::MJPEG => FourCC::new(b"MJPG"), FrameFormat::YUYV => FourCC::new(b"YUYV"), + FrameFormat::GRAY8 => FourCC::new(b"GREY"), }; Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt)