diff --git a/.gitignore b/.gitignore index d992589..d328ba9 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ devenv.local.nix # pre-commit .pre-commit-config.yaml + +# this is a library so its okgit +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 16ba4fb..269b5bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ version = "0.2" optional = true [dependencies.wgpu] -version = "24" +version = "25" optional = true [dependencies.opencv] diff --git a/nokhwa-bindings-linux/src/v4l2.rs b/nokhwa-bindings-linux/src/v4l2.rs index a886223..42e5ccd 100644 --- a/nokhwa-bindings-linux/src/v4l2.rs +++ b/nokhwa-bindings-linux/src/v4l2.rs @@ -1,28 +1,28 @@ +use std::borrow::Cow; use nokhwa_core::camera::{Camera, Capture, Setting}; -use nokhwa_core::control::{ControlDescription, ControlFlags, ControlId, ControlValue, ControlValueDescriptor, Controls}; +use nokhwa_core::control::{ControlDescription, ControlFlags, ControlId, ControlValue, ControlValueDescriptor, Controls, Orientation}; use nokhwa_core::error::{NokhwaError, NokhwaResult}; use nokhwa_core::frame_format::FrameFormat; use nokhwa_core::platform::{Backends, PlatformTrait}; use nokhwa_core::ranges::Range; -use nokhwa_core::stream::{StreamHandle, StreamConfiguration, StreamInnerTrait}; +use nokhwa_core::stream::{Event, StreamBounds, StreamConfiguration, StreamHandle}; use nokhwa_core::types::{CameraFormat, CameraIndex, CameraInformation, FrameRate, Resolution}; use std::collections::hash_map::{Keys, Values}; use std::collections::{HashMap, HashSet}; use std::num::NonZeroI32; use std::sync::Arc; -use std::thread::{sleep, JoinHandle}; -use std::time::Duration; -use flume::{Sender, Receiver, unbounded, bounded}; +use std::thread::JoinHandle; +use flume::{Sender, 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::{Capabilities, Control, Device, Format, FourCC, Fraction, FrameInterval}; +use v4l2_sys_mit::{V4L2_CAMERA_ORIENTATION_BACK, V4L2_CAMERA_ORIENTATION_EXTERNAL, V4L2_CAMERA_ORIENTATION_FRONT, 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; +use nokhwa_core::frame_buffer::{CompactString, FrameBuffer, Metadata}; fn index_capabilities_to_camera_info(index: u32, capabilities: Capabilities) -> CameraInformation { let name = capabilities.card; @@ -249,7 +249,7 @@ fn convert_description_to_ctrl_body(description: Description) -> Option { ( ControlValueDescriptor::BitMask, - Some(ControlValue::BitMask(description.default)) + Some(ControlValue::BitMask(description.default as u64)) ) } Type::IntegerMenu | Type::Menu => { @@ -294,6 +294,34 @@ fn convert_description_to_ctrl_body(description: Description) -> Option Result { + let value = match control { + ControlValue::Null => Value::None, + ControlValue::Integer(i) | ControlValue::BitMask(i) => Value::Integer(i), + ControlValue::String(s) => Value::String(s), + ControlValue::Boolean(t) => Value::Boolean(t), + ControlValue::Binary(b) => Value::CompoundU8(b), + ControlValue::EnumPick(e) => { + if let ControlValue::Integer(i) = &e { + Value::Integer(*i) + } else { + return Err(NokhwaError::ConversionError("could not convert non integer enum pick".to_string())) + } + } + ControlValue::Orientation(o) => Value::Integer(match o { + Orientation::User => V4L2_CAMERA_ORIENTATION_FRONT as i64, + Orientation::Environment => V4L2_CAMERA_ORIENTATION_BACK as i64, + Orientation::Custom(i) => i, + _ => V4L2_CAMERA_ORIENTATION_EXTERNAL as i64, + }), + _ => { + return Err(NokhwaError::ConversionError("Conversion not supported for this data type.".to_string())) + } + }; + + Ok(value) +} + pub struct V4L2Platform {} @@ -349,7 +377,7 @@ pub struct V4L2Camera { camera_format: Option, camera_index: CameraIndex, controls: Controls, - stream: Option>, + stream: Option, } impl Setting for V4L2Camera { @@ -419,7 +447,7 @@ impl Setting for V4L2Camera { }).flatten().collect::>>()) } - fn set_format(&self, camera_format: CameraFormat) -> Result<(), NokhwaError> { + fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError> { let fourcc = frame_format_to_fourcc(*camera_format.format())?; self.device.set_format( &Format::new(camera_format.width(), camera_format.height(), fourcc) @@ -435,6 +463,7 @@ impl Setting for V4L2Camera { error: why.to_string(), } })?; + self.camera_format = Some(camera_format); Ok(()) } @@ -459,7 +488,24 @@ impl Setting for V4L2Camera { } fn set_control(&mut self, property: &ControlId, value: ControlValue) -> Result<(), NokhwaError> { - self.controls.set_control_value(property, value) + if !self.controls.validate(property, &value)? { + return Err(NokhwaError::SetPropertyError { + property: property.to_string(), + value: value.to_string(), + error: "failed to validate".to_string(), + }) + } + let cid = control_id_to_cid(*property)?; + let v4l_value = conv_control_value_to_v4l_value(value.clone())?; + self.device.set_control(Control { id: cid, value: v4l_value }).map_err(|why| { + Err(NokhwaError::SetPropertyError { + property: cid.to_string(), + value: value.to_string(), + error: why.to_string(), + }) + })?; + self.controls.set_control_value(property, value)?; + Ok(()) } fn refresh_controls(&mut self) -> Result<(), NokhwaError> { @@ -503,8 +549,7 @@ impl Setting for V4L2Camera { struct V4L2Stream { thread: JoinHandle<()>, - control: Sender<()>, - receiver: Arc>>, + control: Arc>, } impl Drop for V4L2Stream { @@ -513,90 +558,86 @@ impl Drop for V4L2Stream { } } -impl StreamInnerTrait for V4L2Stream { - fn configuration(&self) -> &Option { - &None - } - - - fn receiver(&self) -> Arc>> { - self.receiver.clone() - } - - fn stop(&mut self) -> NokhwaResult<()> { - self.control.send(()).map_err(|why| NokhwaError::StreamShutdownError(why.to_string()))?; - loop { - if self.thread.is_finished() { - break; - } - sleep(Duration::from_millis(1)) - } - Ok(()) - } -} - impl Capture for V4L2Camera { - fn open_stream(&mut self) -> Result, NokhwaError> { - if self.stream.is_some() { - return Err(NokhwaError::OpenStreamError("Stream Already Open".to_string())) + fn open_stream(&mut self, stream_configuration: Option) -> Result { + if let Some(_) = self.stream { + return Err(NokhwaError::OpenStreamError("StreamAlreadyOpen".to_string())) } + let stream_config = stream_configuration.unwrap_or_default(); 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); + let (control, ctrl_recv) = bounded(1); + let (sender, receiver) = match stream_config.bound { + StreamBounds::Bounded(b) => bounded(b as usize), + StreamBounds::Unbounded => unbounded(), + }; - self.set_format(format)?; + let control = Arc::new(control); let mut mmap_stream = MmapStream::new(&self.device, v4l::buffer::Type::VideoCapture).map_err(|why| { return NokhwaError::OpenStreamError(why.to_string()) })?; + let stream_handle = StreamHandle::new(receiver, control.clone(), stream_config, format); + let thread = std::thread::spawn(move || { + loop { if ctrl_recv.is_disconnected() || sender.is_disconnected() { return; } if let Ok(_) = ctrl_recv.try_recv() { + let _ = sender.send(Event::Closed); return; } match mmap_stream.next() { - Ok((data, _meta)) => { // TODO: Add metadata - if let Err(_why) = sender.send(Ok(FrameBuffer::new(data))) { - return (); - } + Ok((data, meta)) => { + let data = Cow::Owned(data.to_owned()); + let mut metadata = Metadata::new(); + + metadata.insert(CompactString::from("flags"), ControlValue::BitMask(meta.flags.bits() as u64)); + metadata.insert(CompactString::from("time_secs"), ControlValue::Integer(meta.timestamp.sec)); + metadata.insert(CompactString::from("time_usecs"), ControlValue::Integer(meta.timestamp.usec)); + metadata.insert(CompactString::from("size"), ControlValue::Integer(meta.bytesused as i64)); + metadata.insert(CompactString::from("sequence"), ControlValue::Integer(meta.sequence as i64)); + metadata.insert(CompactString::from("field"), ControlValue::Integer(meta.field as i64)); + + let _ = sender.send(Event::NewFrame(FrameBuffer::new(data, Some(metadata)))); } Err(why) => { - if let Err(_why) = sender.send(Err(NokhwaError::ReadFrameError(why.to_string()))) { - return (); - } + let _ = sender.send(Event::Error(Box::new(why))); } } } - return (); }); - - let stream = Arc::new(StreamHandle::new(Box::new(V4L2Stream { - thread, - control, - receiver, - }))); - - self.stream = Some(stream.clone()); - Ok(stream) + + self.stream = Some( + V4L2Stream { + thread, + control, + } + ); + + Ok(stream_handle) + } fn close_stream(&mut self) -> Result<(), NokhwaError> { - if let Some(stream) = self.stream.clone() { - stream.stop_stream()?; - - } + let mut stream = match std::mem::take(&mut self.stream) { + Some(s) => s, + None => return Err(NokhwaError::StreamShutdownError("No stream to shutdown".to_string())), + }; + + let _ = stream.control.send(()); + + stream.thread.join().map_err(|why| NokhwaError::StreamShutdownError(format!("{why:?}")))?; + Ok(()) } } diff --git a/nokhwa-core/Cargo.toml b/nokhwa-core/Cargo.toml index b5265d0..31a6f3e 100644 --- a/nokhwa-core/Cargo.toml +++ b/nokhwa-core/Cargo.toml @@ -25,7 +25,8 @@ thiserror = "2.0" flume = "0.11" num-traits = "0.2" ordered-float = "5" -typed-builder = "0.20" +typed-builder = "0.21" +compact_str = "0.9" [dependencies.num-rational] version = "0.4" @@ -47,7 +48,7 @@ features = ["derive"] optional = true [dependencies.wgpu] -version = "24" +version = "25" optional = true [dependencies.opencv] diff --git a/nokhwa-core/src/camera.rs b/nokhwa-core/src/camera.rs index d73fc5c..8d0032f 100644 --- a/nokhwa-core/src/camera.rs +++ b/nokhwa-core/src/camera.rs @@ -1,7 +1,7 @@ use crate::control::{ControlDescription, ControlId, ControlValue, Controls}; use crate::error::NokhwaError; use crate::frame_format::FrameFormat; -use crate::stream::StreamHandle; +use crate::stream::{StreamConfiguration, StreamHandle}; use crate::types::{CameraFormat, FrameRate, Resolution}; use std::collections::hash_map::{Keys, Values}; use std::collections::HashMap; @@ -15,7 +15,7 @@ pub trait Setting { frame_format: FrameFormat, ) -> Result>, NokhwaError>; - fn set_format(&self, camera_format: CameraFormat) -> Result<(), NokhwaError>; + fn set_format(&mut self, camera_format: CameraFormat) -> Result<(), NokhwaError>; fn control_ids(&self) -> Keys; @@ -54,8 +54,8 @@ pub trait AsyncSetting { } pub trait Capture { - // Implementations MUST guarantee that there can only ever be one stream open at once. - fn open_stream(&mut self) -> Result, NokhwaError>; + /// Implementations MUST guarantee that there can only ever be one stream open at once. + fn open_stream(&mut self, stream_configuration: Option) -> Result, NokhwaError>; // Implementations MUST be multi-close tolerant. fn close_stream(&mut self) -> Result<(), NokhwaError>; @@ -63,7 +63,7 @@ pub trait Capture { #[cfg(feature = "async")] pub trait AsyncStream { - async fn open_stream_async(&mut self) -> Result; + async fn open_stream_async(&mut self, stream_configuration: Option) -> Result; async fn close_stream_async(&mut self) -> Result<(), NokhwaError>; } diff --git a/nokhwa-core/src/control.rs b/nokhwa-core/src/control.rs index 77f77a5..f80b1fd 100644 --- a/nokhwa-core/src/control.rs +++ b/nokhwa-core/src/control.rs @@ -112,20 +112,30 @@ impl Controls { self.descriptions.keys() } + pub fn validate(&self, control_id: &ControlId, value: &ControlValue) -> Result { + let description = match self.descriptions.get(control_id) { + Some(desc) => desc, + None => return Err(NokhwaError::GetPropertyError { + property: control_id.to_string(), + error: "ID Not Found".to_string(), + }), + }; + + if let None = self.values.get(control_id) { + return Err(NokhwaError::GetPropertyError { + property: control_id.to_string(), + error: "ID Not Found".to_string(), + }); + } + + Ok(description.validate(value)) + } + pub fn set_control_value( &mut self, control_id: &ControlId, value: ControlValue, ) -> NokhwaResult<()> { - // see if it exists - if let None = self.descriptions.get(control_id) { - return Err(NokhwaError::SetPropertyError { - property: control_id.to_string(), - value: value.to_string(), - error: "ID Not Found".to_string(), - }); - } - match self.values.get_mut(control_id) { Some(old) => { *old = value; @@ -135,8 +145,7 @@ impl Controls { None => Err(NokhwaError::SetPropertyError { property: control_id.to_string(), value: value.to_string(), - error: "If you got this, its probably a bug or your camera is _horribly_ bugged :>" - .to_string(), + error: "ID Not Found".to_string(), }), } } @@ -317,7 +326,7 @@ impl ControlValueDescriptor { pub enum ControlValue { Null, Integer(i64), - BitMask(i64), + BitMask(u64), Float(OrderedFloat), String(String), Boolean(bool), diff --git a/nokhwa-core/src/error.rs b/nokhwa-core/src/error.rs index 447dd0f..a58f5d1 100644 --- a/nokhwa-core/src/error.rs +++ b/nokhwa-core/src/error.rs @@ -63,3 +63,23 @@ pub enum NokhwaError { #[error("Permission denied by user.")] PermissionDenied, } +// +// pub enum InitializeError {} +// +// pub enum QueryBackendError {} +// +// pub enum OpenDeviceError {} +// +// pub enum QueryDeviceError {} +// +// pub enum GetPropertyError {} +// +// pub enum SetPropertyError {} +// +// pub enum OpenStreamError {} +// +// pub enum CloseStreamError {} +// +// pub enum FrameError {} +// +// pub enum DecoderError {} diff --git a/nokhwa-core/src/frame_buffer.rs b/nokhwa-core/src/frame_buffer.rs index b440353..2d1b53d 100644 --- a/nokhwa-core/src/frame_buffer.rs +++ b/nokhwa-core/src/frame_buffer.rs @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +use std::borrow::Cow; use std::hash::{Hash, Hasher}; use crate::frame_format::FrameFormat; use small_map::{FxSmallMap, Iter}; use crate::control::ControlValue; +pub use compact_str::CompactString; + pub type PlatformSpecificFlag = u32; #[derive(Clone, Debug, Default)] pub struct Metadata { - flags: FxSmallMap<8, u32, ControlValue>, + flags: FxSmallMap<8, CompactString, ControlValue>, } impl Metadata { @@ -30,17 +33,17 @@ impl Metadata { Self { flags: Default::default(), } - } - - pub fn get(&self, key: u32) -> Option<&ControlValue> { + } + + pub fn get(&self, key: CompactString) -> Option<&ControlValue> { self.flags.get(&key) } - - pub fn insert(&mut self, key: u32, value: ControlValue) { + + pub fn insert(&mut self, key: CompactString, value: ControlValue) { self.flags.insert(key, value); } - - pub fn iter(&self) -> Iter<'_, 8, u32, ControlValue> { + + pub fn iter(&self) -> Iter<'_, 8, CompactString, ControlValue> { self.flags.iter() } } @@ -48,7 +51,7 @@ impl Metadata { impl Hash for Metadata { fn hash(&self, state: &mut H) { for (key, value) in self.flags { - state.write_u32(key); + state.write(key.as_bytes()); value.hash(state); } } @@ -75,7 +78,7 @@ impl PartialEq for Metadata { /// Note that decoding on the main thread **will** decrease your performance and lead to dropped frames. #[derive(Clone, Debug, Hash, PartialEq)] pub struct FrameBuffer { - buffer: Vec, + buffer: Cow<'static, [u8]>, metadata: Option, } @@ -83,24 +86,23 @@ impl FrameBuffer { /// Creates a new buffer with a [`&[u8]`]. #[must_use] #[inline] - pub fn new(buffer: Vec, metadata: Option) -> Self { + pub fn new(buffer: Cow<'static, [u8]>, metadata: Option) -> Self { Self { buffer, metadata, } } - + /// Get the data of this buffer. #[must_use] pub fn buffer(&self) -> &[u8] { &self.buffer } - #[must_use] - pub fn consume(self) -> Vec { - self.buffer + pub fn consume(self) -> (Cow<'static, [u8]>, Option) { + return (self.buffer, self.metadata) } - + #[must_use] pub fn metadata(&self) -> Option<&Metadata> { self.metadata.as_ref() diff --git a/nokhwa-core/src/stream.rs b/nokhwa-core/src/stream.rs index 96ea8cb..303f9dc 100644 --- a/nokhwa-core/src/stream.rs +++ b/nokhwa-core/src/stream.rs @@ -1,4 +1,5 @@ use std::cell::Cell; +use std::sync::Arc; use std::time::Duration; use flume::{Receiver, Sender, TryRecvError}; use typed_builder::TypedBuilder; @@ -77,6 +78,8 @@ pub enum Event { Terminating, /// The stream is closed. Closed, + /// An error from the driver + Error(Box), /// Some other message sent by the driver. This can be ignored, although logging this is preferable. Other(String) } @@ -96,14 +99,14 @@ pub enum Event { #[derive(Debug)] pub struct StreamHandle { frame: Receiver, - control: Sender<()>, + control: Arc>, configuration: StreamConfiguration, format: Cell, } impl StreamHandle { /// You shouldn't be here. - pub fn new(recv: Receiver, control: Sender<()>, configuration: StreamConfiguration, format: CameraFormat) -> Self { + pub fn new(recv: Receiver, control: Arc>, configuration: StreamConfiguration, format: CameraFormat) -> Self { Self { frame: recv, control,