pub mod capture;
pub mod controls;
use self::controls::MediaFoundationControls;
use crate::errors::CameraError;
use crate::platform::metrics::PerfTracker;
use crate::types::{
CameraCapabilities, CameraControls, CameraFormat, CameraFrame, ControlApplicationResult,
};
use nokhwa::Camera;
use std::sync::Arc;
type FrameCallback = Box<dyn Fn(CameraFrame) + Send + 'static>;
pub struct WindowsCamera {
pub nokhwa_camera: Camera,
pub mf_controls: MediaFoundationControls,
pub device_id: String,
pub callback: std::sync::Mutex<Option<FrameCallback>>,
pub perf: Arc<std::sync::Mutex<PerfTracker>>,
}
impl WindowsCamera {
pub fn new(device_id: String, format: &CameraFormat) -> Result<Self, CameraError> {
log::info!("Initializing Windows camera {device_id} with MediaFoundation controls");
let nokhwa_camera = capture::initialize_camera(&device_id, format)?;
let device_index = device_id
.parse::<u32>()
.map_err(|_| CameraError::InitializationError("Invalid device ID".to_string()))?;
let mf_controls = MediaFoundationControls::new(device_index)?;
Ok(WindowsCamera {
nokhwa_camera,
mf_controls,
device_id,
callback: std::sync::Mutex::new(None),
perf: Arc::new(std::sync::Mutex::new(PerfTracker::new())),
})
}
pub fn capture_frame(&mut self) -> Result<CameraFrame, CameraError> {
let start = std::time::Instant::now();
let frame = match capture::capture_frame(&mut self.nokhwa_camera, &self.device_id) {
Ok(f) => f,
Err(e) => {
if let Ok(mut perf) = self.perf.lock() {
perf.record_drop();
}
return Err(e);
}
};
let latency_ms = start.elapsed().as_secs_f32() * 1000.0;
let process_start = std::time::Instant::now();
if let Some(ref cb) = *self
.callback
.lock()
.map_err(|_| CameraError::InitializationError("Mutex poisoned".to_string()))?
{
cb(frame.clone());
}
let processing_ms = process_start.elapsed().as_secs_f32() * 1000.0;
if let Ok(mut perf) = self.perf.lock() {
perf.record_capture(
latency_ms,
processing_ms,
Some((
frame.data.clone(),
frame.width,
frame.height,
format!("{:?}", frame.format),
)),
);
}
Ok(frame)
}
pub fn get_performance_metrics(
&self,
) -> Result<crate::types::CameraPerformanceMetrics, CameraError> {
let perf = self
.perf
.lock()
.map_err(|_| CameraError::CaptureError("Perf tracker mutex poisoned".to_string()))?;
Ok(crate::platform::metrics::build_metrics(
&perf,
&self.device_id,
))
}
pub fn apply_controls(
&mut self,
controls: &CameraControls,
) -> Result<ControlApplicationResult, CameraError> {
self.mf_controls.apply_controls(controls)
}
pub fn get_controls(&self) -> Result<CameraControls, CameraError> {
self.mf_controls.get_controls()
}
pub fn test_capabilities(&self) -> Result<CameraCapabilities, CameraError> {
self.mf_controls.get_capabilities()
}
pub fn start_stream(&mut self) -> Result<(), CameraError> {
log::debug!("Opening camera stream for device {}", self.device_id);
self.nokhwa_camera
.open_stream()
.map_err(|e| CameraError::StreamError(format!("Failed to open stream: {e}")))
}
pub fn stop_stream(&mut self) -> Result<(), CameraError> {
log::debug!("Stopping camera stream for device {}", self.device_id);
self.nokhwa_camera
.stop_stream()
.map_err(|e| CameraError::StreamError(format!("Failed to stop stream: {e}")))
}
pub fn is_stream_open(&self) -> bool {
self.nokhwa_camera.is_stream_open()
}
pub fn is_available(&self) -> bool {
self.nokhwa_camera.is_stream_open()
}
pub fn get_device_id(&self) -> &str {
&self.device_id
}
pub fn set_callback<F>(&self, callback: F) -> Result<(), CameraError>
where
F: Fn(CameraFrame) + Send + 'static,
{
*self
.callback
.lock()
.map_err(|_| CameraError::InitializationError("Mutex poisoned".to_string()))? =
Some(Box::new(callback));
Ok(())
}
}
pub use capture::{capture_frame, initialize_camera, list_cameras};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_windows_camera_new_rejects_invalid_device_id() {
let result = WindowsCamera::new("invalid-device-id".to_string(), &CameraFormat::standard());
assert!(result.is_err());
}
#[test]
fn test_windows_capture_helpers_are_callable() {
let init = initialize_camera("not-a-number", &CameraFormat::standard());
assert!(init.is_err());
let cams = list_cameras();
assert!(cams.is_ok() || cams.is_err());
}
}