diff --git a/Cargo.lock b/Cargo.lock index 5af5536..39da5cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1241,7 +1241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" dependencies = [ "bit_field", - "flume 0.11.0", + "flume 0.11.1", "half", "lebe", "miniz_oxide", @@ -1306,9 +1306,9 @@ dependencies = [ [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", @@ -2491,7 +2491,7 @@ name = "nokhwa" version = "0.11.0" dependencies = [ "async-trait", - "flume 0.11.0", + "flume 0.11.1", "image 0.25.0", "js-sys", "mozjpeg", @@ -2516,6 +2516,7 @@ dependencies = [ name = "nokhwa-bindings-linux" version = "0.2.0" dependencies = [ + "flume 0.11.1", "nokhwa-core", "pipewire", "v4l", @@ -2531,7 +2532,7 @@ dependencies = [ "cocoa-foundation", "core-media-sys", "core-video-sys", - "flume 0.11.0", + "flume 0.11.1", "nokhwa-core", "objc", "once_cell", @@ -2553,7 +2554,7 @@ dependencies = [ "async-trait", "bytes", "derive_builder", - "flume 0.11.0", + "flume 0.11.1", "futures", "image 0.25.0", "num-rational 0.4.2", diff --git a/nokhwa-bindings-linux/Cargo.toml b/nokhwa-bindings-linux/Cargo.toml index fe174c3..d0da3da 100644 --- a/nokhwa-bindings-linux/Cargo.toml +++ b/nokhwa-bindings-linux/Cargo.toml @@ -11,10 +11,12 @@ keywords = ["v4l", "v4l2", "linux", "nokhwa", "webcam"] [features] v4l2 = ["v4l", "v4l2-sys-mit"] pw = ["pipewire"] +async = ["flume/async", "nokhwa-core/async"] [dependencies] v4l = { version = "0.14", features = ["v4l2"], optional = true } v4l2-sys-mit = { version = "0.3", optional = true } +flume = "0.11.1" [dependencies.pipewire] version = "0.8" diff --git a/nokhwa-bindings-linux/src/lib.rs b/nokhwa-bindings-linux/src/lib.rs index 752707e..8b7b8b3 100644 --- a/nokhwa-bindings-linux/src/lib.rs +++ b/nokhwa-bindings-linux/src/lib.rs @@ -16,3 +16,4 @@ #[cfg(feature = "v4l2")] pub mod v4l2; pub mod pipewire; +mod v4l2r; diff --git a/nokhwa-bindings-linux/src/v4l2.rs b/nokhwa-bindings-linux/src/v4l2.rs index c9e2f73..6abae03 100644 --- a/nokhwa-bindings-linux/src/v4l2.rs +++ b/nokhwa-bindings-linux/src/v4l2.rs @@ -1,23 +1,27 @@ -use std::collections::{HashMap, HashSet}; -use std::mem; -use std::num::NonZeroI32; -use v4l::context::enum_devices; -use v4l::{Capabilities, Device, Format, FourCC, Fraction, FrameInterval}; -use v4l::control::{Description, Flags, MenuItem, Type, Value}; -use v4l::frameinterval::FrameIntervalEnum; -use v4l::video::Output; -use v4l::video::output::Parameters; use nokhwa_core::camera::{Camera, Capture, Setting}; +use nokhwa_core::control::{ControlDescription, ControlFlags, ControlId, ControlValue, ControlValueDescriptor, Controls}; use nokhwa_core::error::{NokhwaError, NokhwaResult}; use nokhwa_core::frame_format::FrameFormat; use nokhwa_core::platform::{Backends, PlatformTrait}; -use nokhwa_core::control::{ControlBody, ControlFlags, ControlId, ControlValue, ControlValueDescriptor, Controls}; +use nokhwa_core::ranges::Range; use nokhwa_core::stream::Stream; use nokhwa_core::types::{CameraFormat, CameraIndex, CameraInformation, FrameRate, Resolution}; -use v4l2_sys_mit::{V4L2_CID_FOCUS_ABSOLUTE, V4L2_CID_FOCUS_RELATIVE, V4L2_CID_AUTO_FOCUS_RANGE, V4L2_CID_FOCUS_AUTO, V4L2_CID_AUTO_FOCUS_STATUS, V4L2_CID_ISO_SENSITIVITY, V4L2_CID_EXPOSURE_AUTO, V4L2_CID_AUTO_EXPOSURE_BIAS, V4L2_CID_EXPOSURE_METERING, V4L2_CID_EXPOSURE_ABSOLUTE, V4L2_CID_ISO_SENSITIVITY_AUTO, V4L2_CID_IRIS_ABSOLUTE, V4L2_CID_IRIS_RELATIVE, V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, V4L2_CID_ZOOM_CONTINUOUS, V4L2_CID_ZOOM_RELATIVE, V4L2_CID_ZOOM_ABSOLUTE, V4L2_CID_FLASH_LED_MODE, V4L2_CID_FLASH_STROBE, V4L2_CID_FLASH_STROBE_STATUS, V4L2_CID_CAMERA_ORIENTATION, V4L2_CTRL_FLAG_DISABLED, V4L2_CID_FLASH_STROBE_STOP, v4l2_querymenu}; -use v4l::device::Handle; -use v4l::v4l2::ioctl; -use nokhwa_core::ranges::Range; +use std::collections::hash_map::{Keys, Values}; +use std::collections::{HashMap, HashSet}; +use std::num::NonZeroI32; +use std::sync::Arc; +use std::thread::JoinHandle; +use flume::{Sender, Receiver, unbounded, bounded}; +use v4l::context::enum_devices; +use v4l::control::{Description, Flags, MenuItem, Type, Value}; +use v4l::frameinterval::FrameIntervalEnum; +use v4l::video::output::Parameters; +use v4l::video::Output; +use v4l::{Capabilities, Device, Format, FourCC, Fraction, FrameInterval}; +use v4l2_sys_mit::{V4L2_CID_AUTO_EXPOSURE_BIAS, V4L2_CID_AUTO_FOCUS_RANGE, V4L2_CID_AUTO_FOCUS_STATUS, V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_CAMERA_ORIENTATION, V4L2_CID_EXPOSURE_ABSOLUTE, V4L2_CID_EXPOSURE_AUTO, V4L2_CID_EXPOSURE_METERING, V4L2_CID_FLASH_LED_MODE, V4L2_CID_FLASH_STROBE, V4L2_CID_FLASH_STROBE_STATUS, V4L2_CID_FLASH_STROBE_STOP, V4L2_CID_FOCUS_ABSOLUTE, V4L2_CID_FOCUS_AUTO, V4L2_CID_FOCUS_RELATIVE, V4L2_CID_IRIS_ABSOLUTE, V4L2_CID_IRIS_RELATIVE, V4L2_CID_ISO_SENSITIVITY, V4L2_CID_ISO_SENSITIVITY_AUTO, V4L2_CID_ZOOM_ABSOLUTE, V4L2_CID_ZOOM_CONTINUOUS, V4L2_CID_ZOOM_RELATIVE}; +use v4l::io::traits::OutputStream; +use v4l::prelude::MmapStream; +use nokhwa_core::frame_buffer::FrameBuffer; fn index_capabilities_to_camera_info(index: u32, capabilities: Capabilities) -> CameraInformation { let name = capabilities.card; @@ -107,7 +111,22 @@ macro_rules! define_control_id_conv { }) } _ => Err(NokhwaError::ConversionError("Could not match ID".to_string()) -) + ) + } + } + + fn control_id_to_cid_ref(control_id: &ControlId) -> Result { + match control_id { + $( + $control_id => Ok($v4l_cid) + )+ + ControlId::PlatformSpecific(specific_id) => { + u32::try_from(specific_id).map_err(|why| { + NokhwaError::ConversionError("ID must be a u32".to_string()) + }) + } + _ => Err(NokhwaError::ConversionError("Could not match ID".to_string()) + ) } } @@ -186,7 +205,7 @@ fn flags(flags: Flags) -> HashSet { output_flags } -fn convert_description_to_ctrl_body(description: Description) -> Option { +fn convert_description_to_ctrl_body(description: Description) -> Option { let flags = flags(description.flags); let (descriptor, default) = match description.typ { @@ -233,9 +252,6 @@ fn convert_description_to_ctrl_body(description: Description) -> Option { - // We just initialize the values to Null for now. - // We fill it out later. - // our keys let descriptor = match description.items { Some(items) => { @@ -270,12 +286,10 @@ fn convert_description_to_ctrl_body(description: Description) -> Option return None, }; - Some( - ControlBody::new( - flags, - descriptor, - default - ) + ControlDescription::new( + flags, + descriptor, + default ) } @@ -307,8 +321,25 @@ impl PlatformTrait for V4L2Platform { }).flatten().collect::>()) } - fn open(&mut self, index: &CameraIndex) -> NokhwaResult { - todo!() + fn open(&mut self, index: CameraIndex) -> NokhwaResult { + let device = match &index { + CameraIndex::Index(i) => Device::new(*i as usize), + CameraIndex::String(path) => Device::with_path(path) + }.map_err(|why| { + NokhwaError::OpenDeviceError(index.to_string(), why.to_string()) + })?; + + let mut v4l2_camera = V4L2Camera { + device, + camera_format: None, + camera_index: index, + controls: Default::default(), + stream: None, + }; + + v4l2_camera.refresh_controls()?; + + Ok(v4l2_camera) } } @@ -316,7 +347,8 @@ pub struct V4L2Camera { device: Device, camera_format: Option, camera_index: CameraIndex, - controls: Option, + controls: Controls, + stream: Option, } impl Setting for V4L2Camera { @@ -405,13 +437,32 @@ impl Setting for V4L2Camera { Ok(()) } - fn controls(&self) -> &Controls { + fn control_ids(&self) -> Keys { + self.controls.ids() + } - match self.controls { + fn control_descriptions(&self) -> Values { + self.controls.descriptions() + } - } + fn control_values(&self) -> Values { + self.controls.values() + } - let properties = self.device.query_controls().map_err(|why| { + fn control_value(&self, id: &ControlId) -> Option<&ControlValue> { + self.controls.value(id) + } + + fn control_description(&self, id: &ControlId) -> Option<&ControlDescription> { + self.controls.description(id) + } + + fn set_control(&mut self, property: &ControlId, value: ControlValue) -> Result<(), NokhwaError> { + self.controls.set_control_value(property, value) + } + + fn refresh_controls(&mut self) -> Result<(), NokhwaError> { + let descriptions = self.device.query_controls().map_err(|why| { NokhwaError::GetPropertyError { property: "query_controls".to_string(), error: why.to_string() } })?.into_iter().map(|description| { let id = cid_to_control_id(description.id); @@ -419,17 +470,84 @@ impl Setting for V4L2Camera { convert_description_to_ctrl_body(description).map(|body| { (id, body) }) - }).flatten().collect::>(); - } + }).flatten().collect::>(); - fn set_control(&mut self, property: &ControlId, value: ControlValue) -> Result<(), NokhwaError> { - todo!() + let values = descriptions.keys().into_iter().copied().flat_map(|k| control_id_to_cid(k).map(|cid| (k, cid))).flat_map(|(id, cid)| { + self.device.control(cid).map(|v| (id, v)) + }).map(|(id, value)| { + (id, match value.value { + Value::None => ControlValue::Null, + Value::Integer(i) => ControlValue::Integer(i), + Value::Boolean(b) => ControlValue::Boolean(b), + Value::String(s) => ControlValue::String(s), + Value::CompoundU8(bin) | Value::CompoundPtr(bin) => ControlValue::Binary(bin), + Value::CompoundU16(u) | Value::CompoundU32(u) => ControlValue::Array( + u.into_iter().map(|u| ControlValue::Integer(u as i64)).collect() + ), + }) + }).collect::>(); + + match Controls::new(descriptions, values) { + Some(c) => { self.controls = c; } + None => return Err(NokhwaError::SetPropertyError { + property: "control".to_string(), + value: format!("{:?} {:?}", descriptions, values), + error: "Failed to convert to control".to_string(), + }) + } + + Ok(()) + } +} + +struct V4L2Stream { + thread: JoinHandle<()>, + control: Sender<()>, + receiver: Arc>, +} + +impl Drop for V4L2Stream { + fn drop(&mut self) { + let _ = self.control.send(()); } } impl Capture for V4L2Camera { fn open_stream(&mut self) -> Result { - todo!() + let format = match self.camera_format { + Some(fmt) => fmt, + None => return Err(NokhwaError::OpenStreamError("No Format".to_string())) + }; + + let (control, ctrl_recv) = bounded(1); + let (sender, receiver) = unbounded(); + let receiver = Arc::new(receiver); + + self.set_format(format)?; + + let mut mmap_stream = MmapStream::new(&self.device, v4l::buffer::Type::VideoCapture).map_err(|why| { + return NokhwaError::OpenStreamError(why.to_string()) + })?; + + let thread = std::thread::spawn(move || { + + loop { + if ctrl_recv.is_disconnected() || sender.is_disconnected() { + return; + } + if let Ok(_) = ctrl_recv.try_recv() { + return; + } + + match mmap_stream.next() { + Ok((data, meta)) => { + FrameBuffer::new() + } + Err(_) => {} + } + } + () + }) } fn close_stream(&mut self) -> Result<(), NokhwaError> { diff --git a/nokhwa-bindings-linux/src/v4l2r.rs b/nokhwa-bindings-linux/src/v4l2r.rs new file mode 100644 index 0000000..0ffdd02 --- /dev/null +++ b/nokhwa-bindings-linux/src/v4l2r.rs @@ -0,0 +1 @@ +// TODO \ No newline at end of file