diff --git a/.gitignore b/.gitignore index 96ef6c0..7a53b1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target Cargo.lock + +.idea \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index d4f3698..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/nokhwa.iml b/.idea/nokhwa.iml deleted file mode 100644 index f07c8a0..0000000 --- a/.idea/nokhwa.iml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/backends/capture/mod.rs b/src/backends/capture/mod.rs index 33863ca..8845898 100644 --- a/src/backends/capture/mod.rs +++ b/src/backends/capture/mod.rs @@ -1,4 +1,8 @@ #[cfg(feature = "input_v4l")] -pub mod v4l2; +mod v4l2; +#[cfg(feature = "input_v4l")] +pub use v4l2::V4LCaptureDevice; #[cfg(feature = "input_uvc")] -pub mod uvc; \ No newline at end of file +mod uvc_backend; +#[cfg(feature = "input_uvc")] +pub use uvc_backend::UVCCaptureDevice; diff --git a/src/backends/capture/uvc.rs b/src/backends/capture/uvc.rs deleted file mode 100644 index 9b0460a..0000000 --- a/src/backends/capture/uvc.rs +++ /dev/null @@ -1,209 +0,0 @@ -use crate::{CameraFormat, CameraInfo, CaptureBackendTrait, FrameFormat, NokhwaError, Resolution}; -use flume::{Receiver, Sender}; -use image::{ImageBuffer, Rgb}; -use ouroboros::self_referencing; -use std::{ - collections::HashMap, - sync::{atomic::AtomicUsize, Arc}, -}; -use uvc::{ActiveStream, Context, Device, DeviceHandle, DeviceList, Error, StreamHandle}; - -// ignore the IDE, this compiles -/// The backend struct that interfaces with libuvc. -/// To see what this does, please see [`CaptureBackendTrait`] -/// # Quirks -/// The indexing for this backend is based off of `libuvc`'s device ordering, not the OS. -/// You must call [`UVCCaptureDevice::create()`] instead of [`UVCCaptureDevice::new()`], some methods are auto-generated by the self-referencer and are not meant to be used. -/// # Safety -/// This backend requires use of `unsafe` due to the self-referencing structs involved. -#[self_referencing(chain_hack, no_doc)] -pub struct UVCCaptureDevice<'a> { - camera_format: Option, - camera_info: CameraInfo, - frame_receiver: Box>>, - frame_sender: Box>>, - context: Box>, - #[borrows(context)] - #[not_covariant] - device: Box>, - #[borrows(device)] - #[not_covariant] - device_handle: Box>>, - #[borrows(device_handle)] - #[not_covariant] - stream_handle: Box>>, - #[borrows(stream_handle)] - #[not_covariant] - active_stream: Box>>>, -} - -impl<'a> UVCCaptureDevice<'a> { - /// Creates a UVC Camera device with optional CameraFormat. - pub fn create(index: usize, camera_format: Option) -> Result { - let context = match Context::new() { - Ok(ctx) => Box::new(ctx), - Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())), - }; - - let device_list = match context.devices() { - Ok(device_list) => device_list, - Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())), - }; - - let device = match device_list.into_iter().nth(index) { - Some(d) => Box::new(d), - None => { - return Err(NokhwaError::CouldntOpenDevice(format!( - "Device at {} not found", - index - ))) - } - }; - - let device_desc = match device.description() { - Ok(desc) => desc, - Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())), - }; - - let device_name = match (device_desc.manufacturer, device_desc.product) { - (Some(manu), Some(prod)) => { - format!("{} {}", manu, prod) - } - (_, Some(prod)) => prod, - (Some(manu), _) => { - format!( - "{}:{} {}", - device_desc.vendor_id, device_desc.product_id, manu - ) - } - (_, _) => { - format!("{}:{}", device_desc.vendor_id, device_desc.product_id) - } - }; - - let camera_info = CameraInfo::new( - device_name, - "".to_string(), - format!("{}:{}", device_desc.vendor_id, device_desc.product_id), - index, - ); - - let (frame_sender, frame_receiver) = { - let (a, b) = flume::unbounded::>(); - (Box::new(a), Box::new(b)) - }; - - Ok(UVCCaptureDeviceBuilder { - camera_format, - camera_info, - frame_receiver, - frame_sender, - context, - device_builder: |_ctx| device, - device_handle_builder: |_device_builder| Box::new(None), - stream_handle_builder: |_device_handle_builder| Box::new(None), - active_stream_builder: |_stream_handle_builder| Box::new(None), - } - .build()) - } - - /// Create a UVC Camera with desired settings. - pub fn create_with( - index: usize, - width: u32, - height: u32, - fps: u32, - fourcc: FrameFormat, - ) -> Result { - let camera_format = Some(CameraFormat::new_from(width, height, fourcc, fps)); - UVCCaptureDevice::create(index, camera_format) - } -} - -// IDE Autocomplete ends here. Do not be afraid it your IDE does not show completion. -// Here are some docs to help you out: https://docs.rs/ouroboros/0.9.3/ouroboros/attr.self_referencing.html -impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> { - fn get_info(&self) -> CameraInfo { - self.borrow_camera_info().clone() - } - - fn init_camera_format_default(&mut self, overwrite: bool) -> Result<(), NokhwaError> { - let camera_format = CameraFormat::default(); - - self.with_camera_format_mut(|fmt| match fmt { - Some(existing_fmt) => { - if overwrite { - fmt = &mut Some(camera_format); - } - } - None => { - fmt = &mut Some(camera_format); - } - }); - - - } - - fn get_camera_format(&self) -> Option { - todo!() - } - - fn get_compatible_list_by_resolution( - &self, - fourcc: FrameFormat, - ) -> Result>, NokhwaError> { - todo!() - } - - fn get_resolution_list(&self, fourcc: FrameFormat) -> Result, NokhwaError> { - todo!() - } - - fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> { - todo!() - } - - fn get_resolution(&self) -> Option { - todo!() - } - - fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> { - todo!() - } - - fn get_framerate(&self) -> Option { - todo!() - } - - fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { - todo!() - } - - fn get_frameformat(&self) -> Option { - todo!() - } - - fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { - todo!() - } - - fn open_stream(&mut self) -> Result<(), NokhwaError> { - todo!() - } - - fn is_stream_open(&self) -> bool { - todo!() - } - - fn get_frame(&mut self) -> Result, Vec>, NokhwaError> { - todo!() - } - - fn get_frame_raw(&mut self) -> Result, NokhwaError> { - todo!() - } - - fn stop_stream(&mut self) -> Result<(), NokhwaError> { - todo!() - } -} diff --git a/src/backends/capture/uvc_backend.rs b/src/backends/capture/uvc_backend.rs new file mode 100644 index 0000000..e5b9d4b --- /dev/null +++ b/src/backends/capture/uvc_backend.rs @@ -0,0 +1,311 @@ +use crate::{CameraFormat, CameraInfo, CaptureBackendTrait, FrameFormat, NokhwaError, Resolution}; +use flume::{Receiver, Sender}; +use image::{ImageBuffer, Rgb}; +use ouroboros::self_referencing; +use std::{ + cell::RefCell, + collections::HashMap, + sync::{atomic::AtomicUsize, Arc}, +}; +use uvc::{ + ActiveStream, Context, Device, DeviceHandle, DeviceList, Error, FrameFormat as UVCFrameFormat, + StreamFormat, StreamHandle, +}; + +#[cfg(feature = "input_uvc")] +impl From for UVCFrameFormat { + fn from(ff: FrameFormat) -> Self { + match ff { + FrameFormat::MJPEG => UVCFrameFormat::MJPEG, + FrameFormat::YUYV => UVCFrameFormat::YUYV, + } + } +} + +#[cfg(feature = "input_uvc")] +impl From for StreamFormat { + fn from(cf: CameraFormat) -> Self { + StreamFormat { + width: cf.width(), + height: cf.height(), + fps: cf.framerate(), + format: cf.format().into(), + } + } +} + +// ignore the IDE, this compiles +/// The backend struct that interfaces with libuvc. +/// To see what this does, please see [`CaptureBackendTrait`] +/// # Quirks +/// The indexing for this backend is based off of `libuvc`'s device ordering, not the OS. +/// You must call [create()](UVCCaptureDevice::create()) instead `new()`, some methods are auto-generated by the self-referencer and are not meant to be used. +/// The [create()](UVCCaptureDevice::create()) method will open the device twice. +/// Calling [`set_resolution()`](CaptureBackendTrait::set_resolution), [`set_framerate()`](CaptureBackendTrait::set_framerate), or [`set_frameformat()`](CaptureBackendTrait::set_frameformat) +/// each internally calls [`set_camera_format()`](CaptureBackendTrait::set_camera_format). +/// # Safety +/// This backend requires use of `unsafe` due to the self-referencing structs involved. +#[allow(clippy::too_many_arguments)] +#[self_referencing(chain_hack)] +pub struct UVCCaptureDevice<'a> { + camera_format: Option, + camera_info: CameraInfo, + frame_receiver: Box>>, + frame_sender: Box>>, + context: Box>, + #[borrows(context)] + #[not_covariant] + device: Box>, + #[borrows(device)] + #[not_covariant] + device_handle: Box>, + #[borrows(device_handle)] + #[not_covariant] + stream_handle: Box>>>, + #[borrows(stream_handle)] + #[not_covariant] + active_stream: Box>>>>, +} + +impl<'a> UVCCaptureDevice<'a> { + /// Creates a UVC Camera device with optional [`CameraFormat`]. + /// # Panics + /// This operation may panic! If the UVC Context fails to retrieve the device from the gotten IDs, this operation will panic. + /// # Errors + /// This may error when the `libuvc` backend fails to retreive the device or its data. + pub fn create(index: usize, camera_format: Option) -> Result { + let context = match Context::new() { + Ok(ctx) => Box::new(ctx), + Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())), + }; + + let (camera_info, frame_receiver, frame_sender, vendor_id, product_id, serial) = { + let device_list = match context.devices() { + Ok(device_list) => device_list, + Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())), + }; + + let device = match device_list.into_iter().nth(index) { + Some(d) => Box::new(d), + None => { + return Err(NokhwaError::CouldntOpenDevice(format!( + "Device at {} not found", + index + ))) + } + }; + + let device_desc = match device.description() { + Ok(desc) => desc, + Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())), + }; + + let device_name = match (device_desc.manufacturer, device_desc.product) { + (Some(manu), Some(prod)) => { + format!("{} {}", manu, prod) + } + (_, Some(prod)) => prod, + (Some(manu), _) => { + format!( + "{}:{} {}", + device_desc.vendor_id, device_desc.product_id, manu + ) + } + (_, _) => { + format!("{}:{}", device_desc.vendor_id, device_desc.product_id) + } + }; + + let camera_info = CameraInfo::new( + device_name, + "".to_string(), + format!("{}:{}", device_desc.vendor_id, device_desc.product_id), + index, + ); + + let (vendor_id, product_id, serial) = ( + Some(i32::from(device_desc.product_id)), + Some(i32::from(device_desc.vendor_id)), + device_desc.serial_number, + ); + + let (frame_sender, frame_receiver) = { + let (a, b) = flume::unbounded::>(); + (Box::new(a), Box::new(b)) + }; + ( + camera_info, + frame_receiver, + frame_sender, + vendor_id, + product_id, + serial, + ) + }; + + Ok(UVCCaptureDeviceBuilder { + camera_format, + camera_info, + frame_receiver, + frame_sender, + context, + device_builder: |context_builder| { + Box::new( + context_builder + .find_device(vendor_id, product_id, serial.as_deref()) + .unwrap(), + ) + }, + device_handle_builder: |device_builder| Box::new(device_builder.open().unwrap()), + stream_handle_builder: |_device_handle_builder| Box::new(RefCell::new(None)), + active_stream_builder: |_stream_handle_builder| Box::new(RefCell::new(None)), + } + .build()) + } + + /// Create a UVC Camera with desired settings. + /// # Panics + /// This operation may panic! If the UVC Context fails to retrieve the device from the gotten IDs, this operation will panic. + /// # Errors + /// This may error when the `libuvc` backend fails to retreive the device or its data. + pub fn create_with( + index: usize, + width: u32, + height: u32, + fps: u32, + fourcc: FrameFormat, + ) -> Result { + let camera_format = Some(CameraFormat::new_from(width, height, fourcc, fps)); + UVCCaptureDevice::create(index, camera_format) + } +} + +// IDE Autocomplete ends here. Do not be afraid it your IDE does not show completion. +// Here are some docs to help you out: https://docs.rs/ouroboros/0.9.3/ouroboros/attr.self_referencing.html +impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> { + fn get_info(&self) -> CameraInfo { + self.borrow_camera_info().clone() + } + + fn init_camera_format_default(&mut self, overwrite: bool) -> Result<(), NokhwaError> { + let camera_format = CameraFormat::default(); + + self.with_mut(|fields| {}); + + Ok(()) + } + + fn get_camera_format(&self) -> Option { + todo!() + } + + fn get_compatible_list_by_resolution( + &self, + fourcc: FrameFormat, + ) -> Result>, NokhwaError> { + todo!() + } + + fn get_resolution_list(&self, fourcc: FrameFormat) -> Result, NokhwaError> { + todo!() + } + + fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> { + let ret: Result<(), NokhwaError> = self.with_mut(|fields| { + *fields.camera_format = Some(new_fmt); + let is_streamh_some = { + match fields.stream_handle.try_borrow() { + Ok(v) => v.is_some(), + Err(why) => return Err(NokhwaError::CouldntOpenStream(why.to_string())), + } + }; + if is_streamh_some { + // drop the stream handle first + { + *fields.stream_handle.borrow_mut() = None; + } + // replace with new + } + + Ok(()) + }); + Ok(()) + } + + fn get_resolution(&self) -> Option { + self.with_camera_format(|fmt| fmt.as_ref().map(CameraFormat::resoltuion)) + } + + fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> { + todo!() + } + + fn get_framerate(&self) -> Option { + self.with_camera_format(|fmt| fmt.as_ref().map(CameraFormat::framerate)) + } + + fn set_framerate(&mut self, new_fps: u32) -> Result<(), NokhwaError> { + todo!() + } + + fn get_frameformat(&self) -> Option { + self.with_camera_format(|fmt| fmt.as_ref().map(CameraFormat::format)) + } + + fn set_frameformat(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> { + todo!() + } + + fn open_stream(&mut self) -> Result<(), NokhwaError> { + let ret = self.with_mut(|fields| { + let stream_format: StreamFormat = match fields.camera_format { + Some(fmt) => { + let cameraformat: CameraFormat = *fmt; + cameraformat.into() + } + None => { + return Err(NokhwaError::CouldntOpenStream( + "Please initialise the CameraFormat first!".to_string(), + )) + } + }; + + // first, drop the existing stream by setting it to None + { + match fields.stream_handle.try_borrow_mut() { + Ok(mut streamh_raw) => *streamh_raw = None, + Err(why) => return Err(NokhwaError::CouldntOpenStream(why.to_string())), + } + } + // second, set the stream handle according to the streamformat + match fields + .device_handle + .get_stream_handle_with_format(stream_format) + { + Ok(streamh) => match fields.stream_handle.try_borrow_mut() { + Ok(mut streamh_raw) => *streamh_raw = Some(streamh), + Err(why) => return Err(NokhwaError::CouldntOpenStream(why.to_string())), + }, + Err(why) => return Err(NokhwaError::CouldntOpenStream(why.to_string())), + } + Ok(()) + }); + Ok(()) + } + + fn is_stream_open(&self) -> bool { + todo!() + } + + fn get_frame(&mut self) -> Result, Vec>, NokhwaError> { + todo!() + } + + fn get_frame_raw(&mut self) -> Result, NokhwaError> { + todo!() + } + + fn stop_stream(&mut self) -> Result<(), NokhwaError> { + todo!() + } +} diff --git a/src/backends/capture/v4l2.rs b/src/backends/capture/v4l2.rs index bccef5c..28630ed 100644 --- a/src/backends/capture/v4l2.rs +++ b/src/backends/capture/v4l2.rs @@ -73,6 +73,8 @@ impl<'a> V4LCaptureDevice<'a> { } /// Create a new V4L Camera with desired settings. + /// # Errors + /// This function will error if the camera is currently busy or if V4L2 can't read device information. pub fn new_with( index: usize, width: u32, diff --git a/src/lib.rs b/src/lib.rs index 2e3ad44..dfde5c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![allow(clippy::upper_case_acronyms)] #![allow(clippy::must_use_candidate)] +/// Raw access to each of Nokhwa's backends. pub mod backends; mod camera; mod camera_traits; diff --git a/src/utils.rs b/src/utils.rs index 59bfeda..ba7b648 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,6 @@ -use std::{cmp::Ordering, convert::TryFrom, fmt::{Display, write}, slice::from_raw_parts}; - -use mozjpeg::Decompress; - use crate::NokhwaError; +use mozjpeg::Decompress; +use std::{cmp::Ordering, convert::TryFrom, fmt::Display, slice::from_raw_parts}; /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`
/// YUYV is a mathmatical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr)