diff --git a/.gitignore b/.gitignore index 995d5e195..2d8db0650 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ target/ **/*.rs.bk Cargo.lock .DS_Store +.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 594a5ab66..7836ee083 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "nannou_osc", "nannou_package", "nannou_timeline", + "nannou_nokhwa", "nannou_wgpu", "nature_of_code", "scripts/run_all_examples", diff --git a/guide/src/tutorials/nokhwa/nokhwa-capture.md b/guide/src/tutorials/nokhwa/nokhwa-capture.md new file mode 100644 index 000000000..4b692015e --- /dev/null +++ b/guide/src/tutorials/nokhwa/nokhwa-capture.md @@ -0,0 +1,115 @@ +# An Intro to Nokhwa + +**Tutorial Info** + +- Author: [l1npengtul](https://l1n.pengtul.net) +- Required Knowledge: + - [Anatomy of a nannou App](/tutorials/basics/anatomy-of-a-nannou-app.md) + - [Nokhwa Introduction](/tutorials/nokhwa/nokhwa-introduction.md) +- Reading Time: 5 minutes +--- + +# Preface +Keep in mind that camera capture is complicated and stands on top of the steaming stack of complexity that is modern +Operating Systems and the like. Many functions return a form of `Result` containing a `NokhwaError`, you **must** deal +with these to avoid `panic!`s. + +# Initializing Nokhwa +First, you must call `nokhwa_initialize()` +```rust,no_run +fn on_initialized(x: bool) { + println!("Initialized") +} + +nokhwa_initialize(on_initialized) +``` +(You can actually omit this on non-MacOS platforms) + +# Querying List of Cameras +Querying the list of available cameras is easily done by calling `query_devices()`: +```rust,no_run +let devices = query_devices().unwrap(); +for device in &devices { + println!("{:?}", device) +} +``` +This returns a list of `CameraInfo`s, which contain the human-readable name as well as backend-specific data and the +Camera's Index, so you can open the device. + +# Opening a device +If you want more information about a device, you need to first open the device: +```rust,no_run +let mut camera = NannouCamera::new(0, None).unwrap(); +``` + +# Querying Available Camera Formats +### What is a camera format? +-> It is a specific Resolution + Framerate + Frame Format +- Resolutions are in format (width, height) +- Framerate is a u32 integer. +- Frame formats are either YUYV(YUY2) or MJPG. + +### Querying Camera Formats +Call `NannouCamera::compatible_camera_formats`: +```rust,no_run +let frame_foramts = camera.compatible_camera_formats().unwrap(); +``` + +# Capturing Frames from the Camera +First, you must open the camera stream: +```rust,no_run +camera.open_stream().unwrap(); +``` +You can check if the camera is open by using `NannouCamera::is_sream_open` +```rust,no_run +let is_open = camera.is_stream_open(); +``` +#### A note on `ImageTexture`: +But before we get into frame capture, the `NannouCamera` returns an `ImageTexture` every time it captures a frame. This +is a thin wrapper around `ImageBuffer, Vec>` with utility functions that allow for easy integration with +`nannou_wgpu`. To convert an `ImageTexture` to a `Texture`, use either `into_texture` or `loaded_texture_with_device_and_queue`. + +The `NannouCamera` implementation is analogous to `nokhwa::ThreadedCamera`, which means +you can get frames from the camera in three ways: +### Using `NannouCamera::set_callback`: +`NannouCamera::set_callback` is a method that allows you to set a callback function, this function is called every time +a new frame is captured. The function takes a `ImageTexture`, which will be explained ahead. +Remember that this function is executed within the context of the `NannouCamera`'s thread, and that if it takes too long +you will start to drop frames. +```rust,no_run +fn frame_callback(image: ImageTexture) { + /* + - snip! - + Put things that you would do with your image here. + */ +} + +fn main() { + /* -snip- */ + camera.set_callback(frame_callback); + /* -snip- */ +} +``` +### Using `NannouCamera::last_frame`: +`NannouCamera::poll_frame` is a method that allows you to get the last captured frame. This function returns instantly +as long as the stream is open. You can also use either `last_frame_texture` or `last_frame_texture_with_device_queue_usage` to poll +a `Texture`. +```rust,no_run +let poll_frame = camera.last_frame().unwrap(); +``` +### Using `NannouCamera::poll_frame`: +`NannouCamera::poll_frame` is a method that allows you to **wait** for the next frame. This function is blocking. Keep +in mind that this function competes for the frame with `set_callback` **and** `last_frame`, so it is not recommended to use. +You can also use either `poll_texture` or `poll_texture_with_device_queue_usage` to poll +a `Texture`. +```rust,no_run +let poll_frame = camera.poll_frame().unwrap(); +``` + +# Closing the camera +You can close the camera with +```rust,no_run +camera.stop_stream().unwrap(); +``` +Alternatively, it will automatically close when dropped. + diff --git a/guide/src/tutorials/nokhwa/nokhwa-introduction.md b/guide/src/tutorials/nokhwa/nokhwa-introduction.md new file mode 100644 index 000000000..55f233510 --- /dev/null +++ b/guide/src/tutorials/nokhwa/nokhwa-introduction.md @@ -0,0 +1,35 @@ +# An Intro to Nokhwa + +**Tutorial Info** + +- Author: [l1npengtul](https://l1n.pengtul.net) +- Required Knowledge: + - [Anatomy of a nannou App](/tutorials/basics/anatomy-of-a-nannou-app.md) +- Reading Time: 5 minutes +--- + +## What is Nokhwa? + +[Nokhwa](https://crates.io/crates/nokhwa) is a crate that allows for easy use of webcams that works on Linux and Windows (with some support for macOS) + +## Setting up Nokhwa + +To use Nokhwa in nannou, it is necessary to add the `nannou_nokhwa` crate as a dependency in your nannou project. + +Open up your `Cargo.toml` file at the root of your nannou project and add the following line under the `[dependencies]` tag: + +```toml +nannou_nokhwa = "0.1.0" +``` + +The value in the quotes is the version of the Nolhwa package. At the time of writing this, `"0.1.0"` is the latest version. + +To get the latest version of the osc library, execute `cargo search nannou_nokhwa` on the command line and read the resulting version from there. + +To use the crate in your nannou-projects you can add a use-statement at the top of your `main.rs` file. + +```rust,no_run +# #![allow(unused_imports)] +use nannou_nokhwa as nokhwa; +# fn main() {} +``` diff --git a/nannou_nokhwa/Cargo.toml b/nannou_nokhwa/Cargo.toml new file mode 100644 index 000000000..523619bf7 --- /dev/null +++ b/nannou_nokhwa/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "nannou_nokhwa" +version = "0.1.0" +authors = ["l1npengtul "] +edition = "2018" +description = "The API for Nannou, the creative coding framework." +readme = "README.md" +keywords = ["webcam", "capture", "cross-platform", "nokhwa"] +license = "MIT" +homepage = "https://nannou.cc" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +wgpu = ["nannou_wgpu"] + +[dependencies] +nokhwa = { version = "^0.9", features = ["input-avfoundation", "input-msmf", "input-v4l", "output-threaded"] } +parking_lot = "^0.11" +nannou_wgpu = {version = "^0.18", path="../nannou_wgpu", features = ["image"], optional = true } + +[dependencies.image] +version = "^0.23" +no-default-features = true + +[package.metadata.docs.rs] +features = ["nokhwa/docs-only", "nokhwa/docs-nolink", "nokhwa/docs-features"] diff --git a/nannou_nokhwa/LICENSE-APACHE b/nannou_nokhwa/LICENSE-APACHE new file mode 100644 index 000000000..754d24d4e --- /dev/null +++ b/nannou_nokhwa/LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2021 nannou-org. + +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. diff --git a/nannou_nokhwa/README.md b/nannou_nokhwa/README.md new file mode 100644 index 000000000..c7791513b --- /dev/null +++ b/nannou_nokhwa/README.md @@ -0,0 +1,16 @@ +# `nannou_nokhwa` + +**A Simplified Webcam Capture API for** [**nannou**](https://nannou.cc)**, the creative coding +framework.** + +Please see [**the nannou guide**](https://guide.nannou.cc) for more information +on how to get started with nannou! + +`nannou_nokhwa` uses [`nokhwa`](https://crates.io/crates/nokhwa): a crate that allows for easy webcam capture across many different platforms. + +## Plaforms +- Windows via MediaFoundation +- Linux via Video4Linux2 +- MacOS via AVFoundation **(buggy!)** +- Web/JS/WASM via the Browser + diff --git a/nannou_nokhwa/src/lib.rs b/nannou_nokhwa/src/lib.rs new file mode 100644 index 000000000..8d4f94744 --- /dev/null +++ b/nannou_nokhwa/src/lib.rs @@ -0,0 +1,553 @@ +//! Nannou integration for [`nokhwa`](https://crates.io/crates/nokhwa). Creates a threaded camera that +//! allows you to use a callback to query from the camera. Supports Windows and Linux, and MacOS (somewhat) + +use image::{DynamicImage, ImageBuffer, Rgb, Rgba}; +#[cfg(feature = "wgpu")] +use nannou_wgpu::{Device, Queue, Texture, TextureUsages, WithDeviceQueuePair}; +use nokhwa::Camera; +#[doc(inline)] +pub use nokhwa::{ + nokhwa_check, nokhwa_initialize, query_devices, CameraControl, CameraFormat, CameraInfo, + CaptureAPIBackend, FrameFormat, KnownCameraControlFlag, KnownCameraControls, NokhwaError, + Resolution, +}; +use parking_lot::Mutex; +use std::{ + any::Any, + collections::HashMap, + ops::{Deref, DerefMut}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; +use image::buffer::ConvertBuffer; + +/// The image texture struct, which is a wrapper type for `ImageBuffer, Vec>`. +#[derive(Clone, Debug, Default)] +pub struct ImageTexture { + buffer: ImageBuffer, Vec>, +} + +impl ImageTexture { + /// Create a new `ImageTexture` + pub fn new(buffer: ImageBuffer, Vec>) -> Self { + ImageTexture { buffer } + } + + /// Get the internal buffer. + #[inline] + pub fn into_buffer(self) -> ImageBuffer, Vec> { + self.buffer + } + + /// Get the internal buffer as an RGBA + #[inline] + pub fn into_rgba_buffer(self) -> ImageBuffer, Vec> { + self.buffer.convert() + } + + /// Get the internal buffer as a `Texture`. Requires `wgpu` feature. + #[inline] + #[cfg(feature = "wgpu")] + pub fn into_texture(self, dev_queue: impl WithDeviceQueuePair) -> Texture { + Texture::from_image(dev_queue, &DynamicImage::ImageRgb8(self.buffer)) + } + + /// Get the internal buffer as a `Texture`. Requires `wgpu` feature. + #[inline] + #[cfg(feature = "wgpu")] + pub fn loaded_texture_with_device_and_queue( + self, + device: &Device, + queue: &Queue, + usage: TextureUsages, + ) -> Texture { + Texture::load_from_image_buffer(device, queue, usage, &self.into_rgba_buffer()) + } +} + +impl Deref for ImageTexture { + type Target = ImageBuffer, Vec>; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl DerefMut for ImageTexture { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} + +impl Into, Vec>> for ImageTexture { + #[inline] + fn into(self) -> ImageBuffer, Vec> { + self.into_buffer() + } +} + +impl Into for ImageTexture { + fn into(self) -> DynamicImage { + DynamicImage::ImageRgb8(self.buffer) + } +} + +/// Creates a camera that runs in a different thread that you can use a callback to access the frames of. +/// It uses a `Arc` and a `Mutex` to ensure that this feels like a normal camera, but callback based. +/// 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 +/// complete before a new frame is available. If you need to do heavy image processing, it may be +/// beneficial to directly pipe the data to a new thread to process it there. +/// +/// # SAFETY +/// The `Mutex` guarantees exclusive access to the underlying camera struct. They should be safe to +/// impl `Send` on. +#[derive(Clone)] +pub struct NannouCamera { + camera: Arc>, + frame_callback: Arc>>, + last_frame_captured: Arc>, + die_bool: Arc, +} + +impl NannouCamera { + /// 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) -> Result { + NannouCamera::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, + backend: CaptureAPIBackend, + ) -> Result { + 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 { + let camera_format = CameraFormat::new_from(width, height, fourcc, fps); + NannouCamera::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, + backend: CaptureAPIBackend, + func: Option< + fn( + _: Arc>, + _: Arc>>, + _: Arc>, + _: Arc, + ), + >, + ) -> Result { + let camera = Arc::new(Mutex::new(Camera::with_backend(index, format, backend)?)); + let format = match format { + Some(fmt) => fmt, + None => CameraFormat::default(), + }; + let frame_callback = Arc::new(Mutex::new(None)); + let die_bool = Arc::new(AtomicBool::new(false)); + let holding_cell = Arc::new(Mutex::new(ImageTexture::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(NannouCamera { + camera, + frame_callback, + last_frame_captured: holding_cell, + die_bool, + }) + } + + /// Gets the current Camera's index. + #[must_use] + pub fn index(&self) -> usize { + self.camera.lock().index() + } + + /// 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> { + self.camera.lock().set_index(new_idx) + } + + /// Gets the current Camera's backend + #[must_use] + pub fn backend(&self) -> CaptureAPIBackend { + 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> { + self.camera.lock().set_backend(new_backend) + } + + /// Gets the camera information such as Name and Index as a [`CameraInfo`]. + #[must_use] + pub fn info(&self) -> CameraInfo { + self.camera.lock().info().clone() + } + + /// Gets the current [`CameraFormat`]. + #[must_use] + pub fn camera_format(&self) -> CameraFormat { + self.camera.lock().camera_format() + } + + /// Will set the current [`CameraFormat`] + /// This will reset the current stream if used while stream is opened. + /// # 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() = + ImageTexture::new(ImageBuffer::new(new_fmt.width(), new_fmt.height())); + self.camera.lock().set_camera_format(new_fmt) + } + + /// A hashmap of [`Resolution`]s mapped to framerates + /// # 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_list_by_resolution( + &mut self, + fourcc: FrameFormat, + ) -> Result>, NokhwaError> { + self.camera.lock().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, NokhwaError> { + self.camera.lock().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, NokhwaError> { + let mut camera_formats = Vec::with_capacity(64); + for foramt in self.compatible_fourcc()? { + let resolution_and_fps: HashMap> = + self.compatible_list_by_resolution(foramt)?; + for (res, rates) in resolution_and_fps { + for fps in rates { + camera_formats.push(CameraFormat::new(res, foramt, fps)) + } + } + } + Ok(camera_formats) + } + + /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). + #[must_use] + pub fn resolution(&self) -> Resolution { + self.camera.lock().resolution() + } + + /// Will set the current [`Resolution`] + /// This will reset the current stream if used while stream is opened. + /// # 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() = + ImageTexture::new(ImageBuffer::new(new_res.width(), new_res.height())); + self.camera.lock().set_resolution(new_res) + } + + /// Gets the current camera framerate (See: [`CameraFormat`]). + #[must_use] + pub fn frame_rate(&self) -> u32 { + self.camera.lock().frame_rate() + } + + /// Will set the current framerate + /// This will reset the current stream if used while stream is opened. + /// # 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) + } + + /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). + #[must_use] + pub fn frame_format(&self) -> FrameFormat { + self.camera.lock().frame_format() + } + + /// Will set the current [`FrameFormat`] + /// This will reset the current stream if used while stream is opened. + /// # 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) + } + + /// 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". + pub fn supported_camera_controls(&self) -> Result, NokhwaError> { + self.camera.lock().supported_camera_controls() + } + + /// Gets the current supported list of [`CameraControl`]s keyed by its name as a `String`. + /// # Errors + /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". + pub fn camera_controls(&self) -> Result, NokhwaError> { + let known_controls = self.supported_camera_controls()?; + let maybe_camera_controls = known_controls + .iter() + .map(|x| self.camera_control(*x)) + .filter(Result::is_ok) + .map(Result::unwrap) + .collect::>(); + + Ok(maybe_camera_controls) + } + + /// Gets the current supported list of [`CameraControl`]s keyed by its name as a `String`. + /// # Errors + /// If the list cannot be collected, this will error. This can be treated as a "nothing supported". + pub fn camera_controls_string(&self) -> Result, NokhwaError> { + let known_controls = self.supported_camera_controls()?; + let maybe_camera_controls = known_controls + .iter() + .map(|x| (x.to_string(), self.camera_control(*x))) + .filter(|(_, x)| x.is_ok()) + .map(|(c, x)| (c, Result::unwrap(x))) + .collect::>(); + let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); + + for (kc, cc) in maybe_camera_controls.into_iter() { + control_map.insert(kc, cc); + } + + Ok(control_map) + } + + /// Gets the current supported list of [`CameraControl`]s keyed by its name as a `String`. + /// # Errors + /// 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, 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::>(); + let mut control_map = HashMap::with_capacity(maybe_camera_controls.len()); + + for (kc, cc) in maybe_camera_controls.into_iter() { + control_map.insert(kc, cc); + } + + Ok(control_map) + } + + /// Gets the value of [`KnownCameraControls`]. + /// # 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, + ) -> Result { + self.camera.lock().camera_control(control) + } + + /// Sets the control to `control` in the camera. + /// 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 (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>, 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, 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( + &mut self, + control: &dyn Any, + value: &dyn Any, + ) -> Result<(), NokhwaError> { + self.camera.lock().set_raw_camera_control(control, value) + } + + /// 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(ImageTexture)) -> Result<(), NokhwaError> { + *self.frame_callback.lock() = Some(callback); + self.camera.lock().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(ImageTexture)) { + *self.frame_callback.lock() = Some(callback); + } + + /// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame) + pub fn poll_frame(&mut self) -> Result { + let frame: ImageTexture = ImageTexture::new(self.camera.lock().frame()?); + *self.last_frame_captured.lock() = frame.clone(); + Ok(frame) + } + + /// Polls the camera as a texture. Internally calls `poll_frame()` + #[cfg(feature = "wgpu")] + pub fn poll_texture( + &mut self, + dev_queue: impl WithDeviceQueuePair, + ) -> Result { + Ok(self.poll_frame()?.into_texture(dev_queue)) + } + + /// Polls the camera as a texture. Internally calls `poll_frame()` + #[cfg(feature = "wgpu")] + pub fn poll_texture_with_device_queue_usage( + &mut self, + device: &Device, + queue: &Queue, + usages: TextureUsages, + ) -> Result { + Ok(self + .poll_frame()? + .loaded_texture_with_device_and_queue(device, queue, usages)) + } + + /// Gets the last frame captured by the camera. + pub fn last_frame(&self) -> ImageTexture { + self.last_frame_captured.lock().clone() + } + + /// The last frame from the camera as a texture. Internally calls `poll_frame()` + #[cfg(feature = "wgpu")] + pub fn last_frame_texture(&self, dev_queue: impl WithDeviceQueuePair) -> Texture { + self.last_frame().into_texture(dev_queue) + } + + /// The last frame from the camera as a texture. Internally calls `poll_frame()` + #[cfg(feature = "wgpu")] + pub fn last_frame_texture_with_device_queue_usage( + &self, + device: &Device, + queue: &Queue, + usages: TextureUsages, + ) -> Texture { + self.last_frame() + .loaded_texture_with_device_and_queue(device, queue, usages) + } + + /// Checks if stream if open. If it is, it will return true. + pub fn is_stream_open(&self) -> bool { + self.camera.lock().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.set_callback(null_frame); + self.camera.lock().stop_stream() + } +} + +impl Drop for NannouCamera { + fn drop(&mut self) { + let _ = self.stop_stream(); + self.die_bool.store(true, Ordering::SeqCst); + } +} + +fn camera_frame_thread_loop( + camera: Arc>, + callback: Arc>>, + holding_cell: Arc>, + die_bool: Arc, +) { + loop { + if let Ok(img) = camera.lock().frame() { + let texture = ImageTexture::new(img); + *holding_cell.lock() = texture.clone(); + if let Some(cb) = callback.lock().deref() { + cb(texture) + } + } + if die_bool.load(Ordering::SeqCst) { + break; + } + } +} + +fn null_frame(_: ImageTexture) {}