From 087ccda39f8cc3cb025d019c4b163a64219a2273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20R=C3=B3=C5=BCycki?= Date: Sat, 19 Oct 2024 20:45:09 +0200 Subject: [PATCH] Expose an option to list all devices and create camera from selected one --- examples/window-rgb.rs | 6 +++++ src/camera.rs | 30 ++++++++++++++++++++++++- src/linux_v4l2/mod.rs | 51 ++++++++++++++++++++++++++++-------------- src/mac_avf/camera.rs | 38 ++++++++++++++++++++++++------- src/win_mf/camera.rs | 45 +++++++++++++++++-------------------- src/win_mf/mf.rs | 44 +++++++++++++++++++++++------------- src/win_mf/mod.rs | 4 ++-- src/win_mf/tests.rs | 6 +++-- 8 files changed, 153 insertions(+), 71 deletions(-) diff --git a/examples/window-rgb.rs b/examples/window-rgb.rs index 71953c2..887f1c6 100644 --- a/examples/window-rgb.rs +++ b/examples/window-rgb.rs @@ -15,6 +15,12 @@ fn main() { let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); + let devices = Device::list_all_devices(); + println!("Available devices"); + for (i, device) in devices.iter().enumerate() { + println!("#{}: {}", i, device.name()); + } + let mut camera = Camera::new_default_device(); camera.start(); diff --git a/src/camera.rs b/src/camera.rs index 09e56e5..eb4d6ba 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -7,7 +7,24 @@ use super::win_mf as backend; #[cfg(target_os = "linux")] use super::linux_v4l2 as backend; -#[derive(Debug)] +pub struct Device { + inner: backend::Device, +} + +impl Device { + pub fn list_all_devices() -> Vec { + backend::Device::list_all_devices() + .into_iter() + .map(|device| Device { inner: device }) + .collect() + } + + pub fn name(&self) -> String { + self.inner.name() + } +} + +#[derive(Debug, Clone)] pub struct Camera { inner: backend::Camera, } @@ -26,6 +43,10 @@ impl Camera { Self { inner: backend::Camera::new_default_device() } } + pub fn new_from_device(device: Device) -> Self { + Self { inner: backend::Camera::new_from_device(device.inner) } + } + pub fn start(&self) { self.inner.start(); } @@ -64,11 +85,18 @@ impl<'a> FrameData<'a> { } pub(crate) trait InnerCamera: std::fmt::Debug { + type Device; type Frame; + fn new_from_device(device: Self::Device) -> Self; fn new_default_device() -> Self; fn start(&self); fn stop(&self); fn wait_for_frame(&self) -> Option; fn change_device(&mut self); } + +pub(crate) trait InnerDevice: Sized { + fn list_all_devices() -> Vec; + fn name(&self) -> String; +} diff --git a/src/linux_v4l2/mod.rs b/src/linux_v4l2/mod.rs index 2e9d16c..366f737 100644 --- a/src/linux_v4l2/mod.rs +++ b/src/linux_v4l2/mod.rs @@ -11,7 +11,28 @@ use std::marker::PhantomData; use std::sync::RwLock; use crate::InnerCamera; +use crate::InnerDevice; + +pub struct Device(v4l::context::Node); + +impl InnerDevice for Device { + fn list_all_devices() -> Vec { + v4l::context::enum_devices() + .into_iter() + .filter_map(|node| { + v4l::Device::with_path(node.path()).ok().map(|device| (node, device)) + }) + .filter(|(_, device)| device.format().is_ok()) + .map(|(node, _)| Device(node)) + .collect() + } + fn name(&self) -> String { + name_or_path(&self.0) + } +} + +#[derive(Debug, Clone)] pub struct Camera { device: RwLock, device_name: String, @@ -22,7 +43,7 @@ fn name_or_path(device_node: &v4l::context::Node) -> String { device_node.name().unwrap_or_else(|| device_node.path().to_string_lossy().to_string()) } -fn get_next_best_format(device: &Device) -> Format { +fn get_next_best_format(device: &v4l::Device) -> Format { let _rgb = FourCC::new(b"RGB3"); let mut fmt = device.format().expect("device.format()"); let size = device @@ -52,7 +73,7 @@ fn display_node(node: &Node) { } #[allow(unused)] -fn display_device_formats(device: &Device) { +fn display_device_formats(device: &v4l::Device) { println!("Device formats:"); for fmt in device.enum_formats().unwrap() { println!(" {:?}", fmt); @@ -63,15 +84,6 @@ fn display_device_formats(device: &Device) { } } -fn enum_devices() -> Vec { - v4l::context::enum_devices() - .into_iter() - .filter_map(|node| Device::with_path(node.path()).ok().map(|device| (node, device))) - .filter(|(_, device)| device.format().is_ok()) - .map(|(node, _)| node) - .collect() -} - impl Camera { fn from_node(node: &v4l::context::Node) -> Self { let device = v4l::Device::with_path(node.path()).unwrap(); @@ -86,10 +98,15 @@ impl Camera { impl InnerCamera for Camera { type Frame = Frame; + type Device = Device; + + fn new_from_device(device: Self::Device) -> Self { + Self::from_node(&device.0) + } fn new_default_device() -> Self { - let node = enum_devices().into_iter().next().unwrap(); - Self::from_node(&node) + let device = Device::list_all_devices().into_iter().next().unwrap(); + Self::from_node(&device.0) } fn start(&self) { @@ -124,15 +141,15 @@ impl InnerCamera for Camera { } fn change_device(&mut self) { - let devices = enum_devices(); - if let Some(pos) = devices.iter().position(|n| name_or_path(n) == self.device_name) { + let devices = Device::list_all_devices(); + if let Some(pos) = devices.iter().position(|d| name_or_path(&d.0) == self.device_name) { let new_pos = (pos + 1) % devices.len(); if new_pos != pos { - *self = Self::from_node(&devices[new_pos]); + *self = Self::from_node(&devices[new_pos].0); self.start(); } } else if !devices.is_empty() { - *self = Self::from_node(&devices[0]); + *self = Self::from_node(&devices[0].0); self.start(); } else { self.stop(); diff --git a/src/mac_avf/camera.rs b/src/mac_avf/camera.rs index edad790..d21fe4d 100644 --- a/src/mac_avf/camera.rs +++ b/src/mac_avf/camera.rs @@ -1,8 +1,22 @@ +use crate::{InnerCamera, InnerDevice}; + use super::*; use objc2::rc::Id; use std::sync::Arc; -#[derive(Debug)] +pub struct Device(Id); + +impl InnerDevice for Device { + fn list_all_devices() -> Vec { + AVCaptureDevice::all_video_devices().into_iter().map(Device).collect() + } + + fn name(&self) -> String { + self.0.localized_name().to_string() + } +} + +#[derive(Debug, Clone)] pub struct Camera { device: Id, input: Id, @@ -21,9 +35,12 @@ pub struct FrameData<'a> { pixels: Pixels<'a>, } -impl Camera { - pub fn new_default_device() -> Self { - let device = AVCaptureDevice::default_video_device(); +impl InnerCamera for Camera { + type Frame = Frame; + type Device = Device; + + fn new_from_device(device: Device) -> Self { + let device = device.0; let input = AVCaptureDeviceInput::from_device(&device).unwrap(); let output = AVCaptureVideoDataOutput::new(); output.set_video_settings(&video_settings_from_pixel_format("ARGB")); @@ -37,19 +54,24 @@ impl Camera { Camera { device, input, output, session, slot } } - pub fn start(&self) { + fn new_default_device() -> Self { + let device = AVCaptureDevice::default_video_device(); + Self::new_from_device(Device(device)) + } + + fn start(&self) { self.session.start_running(); } - pub fn stop(&self) { + fn stop(&self) { self.session.stop_running(); } - pub fn wait_for_frame(&self) -> Option { + fn wait_for_frame(&self) -> Option { self.slot.wait_for_sample().map(|sample| Frame { sample }) } - pub fn change_device(&mut self) { + fn change_device(&mut self) { let devices = AVCaptureDevice::all_video_devices(); let Some(index) = devices.iter().position(|d| d.unique_id() == self.device.unique_id()) else { diff --git a/src/win_mf/camera.rs b/src/win_mf/camera.rs index 38a3003..3d8d88c 100644 --- a/src/win_mf/camera.rs +++ b/src/win_mf/camera.rs @@ -1,3 +1,5 @@ +use crate::{InnerCamera, InnerDevice}; + use super::mf::*; use std::{sync::mpsc::*, time::Duration}; @@ -5,7 +7,7 @@ use std::{sync::mpsc::*, time::Duration}; use windows::Win32::Media::MediaFoundation::*; #[allow(unused)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Camera { engine: IMFCaptureEngine, device: Device, @@ -24,8 +26,11 @@ pub struct FrameData<'a> { data: &'a [u8], } -impl Camera { - pub fn new_default_device() -> Self { +impl InnerCamera for Camera { + type Device = Device; + type Frame = Frame; + + fn new_from_device(device: Self::Device) -> Self { co_initialize_multithreaded(); media_foundation_startup().expect("media_foundation_startup"); @@ -35,9 +40,6 @@ impl Camera { let event_cb = CaptureEventCallback { event_tx }.into(); let sample_cb = CaptureSampleCallback { sample_tx }.into(); - let devices = Device::enum_devices(); - let Some(device) = devices.first().cloned() else { todo!() }; - init_capture_engine(&engine, Some(&device.source), &event_cb).unwrap(); let camera = Camera { engine, device, event_rx, sample_rx, event_cb, sample_cb }; @@ -46,24 +48,28 @@ impl Camera { camera } - pub fn start(&self) { + fn new_default_device() -> Self { + let devices = Device::list_all_devices(); + let Some(device) = devices.into_iter().next() else { todo!() }; + Self::new_from_device(device) + } + + fn start(&self) { unsafe { self.engine.StartPreview().unwrap() } } - pub fn stop(&self) { + fn stop(&self) { capture_engine_stop_preview(&self.engine).unwrap(); } - pub fn wait_for_frame(&self) -> Option { + fn wait_for_frame(&self) -> Option { self.sample_rx // TODO sometimes running two engines on the same camera breaks frame delivery, so wait not too long .recv_timeout(Duration::from_secs(3)) .ok() .flatten() .and_then(|sample| { - let Some(mt) = capture_engine_sink_get_media_type(&self.engine).ok() else { - return None; - }; + let mt = capture_engine_sink_get_media_type(&self.engine).ok()?; let width = mt.frame_width(); let height = mt.frame_height(); sample_to_locked_buffer(&sample, width, height).ok() @@ -71,7 +77,7 @@ impl Camera { .map(|buffer: LockedBuffer| Frame { buffer }) } - pub fn change_device(&mut self) { + fn change_device(&mut self) { let devices: Vec = enum_device_sources().into_iter().map(Device::new).collect(); let Some(index) = devices.iter().position(|d| d.id() == self.device.id()) else { return }; let new_index = (index + 1) % devices.len(); @@ -81,18 +87,7 @@ impl Camera { } let new_device = devices[new_index].clone(); - let engine = new_capture_engine().unwrap(); - let (event_tx, event_rx) = channel::(); - let (sample_tx, sample_rx) = channel::>(); - let event_cb = CaptureEventCallback { event_tx }.into(); - let sample_cb = CaptureSampleCallback { sample_tx }.into(); - - init_capture_engine(&engine, Some(&new_device.source), &event_cb).unwrap(); - - *self = Camera { engine, device: new_device, event_rx, sample_rx, event_cb, sample_cb }; - self.wait_for_event(CaptureEngineEvent::Initialized); - self.prepare_source_sink(); - self.start(); // TODO watch out about playing state + *self = Self::new_from_device(new_device); } } diff --git a/src/win_mf/mf.rs b/src/win_mf/mf.rs index 70327b8..de1284e 100644 --- a/src/win_mf/mf.rs +++ b/src/win_mf/mf.rs @@ -1,13 +1,21 @@ -use std::{ffi::OsString, mem::MaybeUninit, sync::mpsc::*}; +use std::{ + ffi::OsString, + mem::MaybeUninit, + sync::{mpsc::*, OnceLock}, +}; use windows::{ core::*, Win32::{Media::MediaFoundation::*, System::Com::*}, }; +use crate::InnerDevice; + use super::attributes::{mf_create_attributes, mf_get_string}; use super::media_type::MediaType; +pub static CO_INITIALIZED_MULTITHREADED: OnceLock<()> = OnceLock::new(); + #[derive(Clone, Debug)] pub struct Device { pub activate: IMFActivate, @@ -16,7 +24,6 @@ pub struct Device { impl Device { pub(crate) fn new(activate: IMFActivate) -> Self { - co_initialize_multithreaded(); let source = unsafe { activate.ActivateObject().unwrap() }; Self { activate, source } } @@ -42,12 +49,6 @@ impl std::fmt::Display for Device { #[allow(unused)] impl Device { - pub fn name(&self) -> String { - mf_get_string(&self.activate, &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME) - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|_| "NO NAME".into()) - } - pub fn id(&self) -> OsString { let symlink = &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK; mf_get_string(&self.activate, symlink).unwrap_or_else(|_| "NO ID".into()) @@ -60,8 +61,17 @@ impl Device { pub fn query_media_types_with_best_fps(&self) -> Vec { MediaType::filter_resolutions_with_max_fps(&self.query_media_types()) } +} - pub fn enum_devices() -> Vec { +impl InnerDevice for Device { + fn name(&self) -> String { + mf_get_string(&self.activate, &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME) + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_else(|_| "NO NAME".into()) + } + + fn list_all_devices() -> Vec { + co_initialize_multithreaded(); enum_device_sources().into_iter().map(Device::new).collect() } } @@ -407,14 +417,16 @@ pub(crate) struct CaptureSampleCallback { } pub fn co_initialize_multithreaded() { - if let Err(err) = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) } { - if err.code() == HRESULT(0x80010106u32 as i32) { - // "Cannot change thread mode after it is set." - // Ignore this error and hope for the best until we know better how to deal with this case. - } else { - panic!("{err}"); + CO_INITIALIZED_MULTITHREADED.get_or_init(|| { + if let Err(err) = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) } { + if err.code() == HRESULT(0x80010106u32 as i32) { + // "Cannot change thread mode after it is set." + // Ignore this error and hope for the best until we know better how to deal with this case. + } else { + panic!("{err}"); + } } - } + }); } // TODO when to use this? diff --git a/src/win_mf/mod.rs b/src/win_mf/mod.rs index ffbdaae..e0ac2d1 100644 --- a/src/win_mf/mod.rs +++ b/src/win_mf/mod.rs @@ -1,12 +1,12 @@ mod attributes; mod camera; mod media_type; -pub mod mf; +mod mf; mod source_reader_flag; #[cfg(test)] mod tests; mod video_format; pub use camera::*; -pub use media_type::*; +pub use mf::Device; pub use video_format::*; diff --git a/src/win_mf/tests.rs b/src/win_mf/tests.rs index e053fc2..73f451e 100644 --- a/src/win_mf/tests.rs +++ b/src/win_mf/tests.rs @@ -1,13 +1,15 @@ +use crate::InnerDevice; + use super::mf::*; #[test] fn device_enum_devices() { co_mta_usage(); - assert!(!Device::enum_devices().is_empty()); + assert!(!Device::list_all_devices().is_empty()); } fn first_device() -> Device { - Device::enum_devices()[0].clone() + Device::list_all_devices()[0].clone() } #[test]