use anyhow::{anyhow, Context, Result}; use log::{debug, error, info, warn}; use std::path::Path; use v4l::buffer::Type; use v4l::io::traits::CaptureStream; use v4l::prelude::*; use v4l::video::Capture; use v4l::{Format, FourCC}; use opencv::{core, imgproc, prelude::*}; use crate::camera::{ExposureMode, Resolution}; /// V4L2 camera driver for star-light cameras pub struct V4l2Camera { /// The open device handle device: Device, /// The current camera format format: Format, /// Whether the camera is currently streaming is_streaming: bool, } impl V4l2Camera { /// Open a camera device by path pub fn open>(path: P) -> Result { let device = Device::with_path(path.as_ref()) .context("Failed to open camera device")?; info!( "Opened camera: {} ({})", device.info().card, device.info().driver ); // Get the current format let format = device .format() .context("Failed to get camera format")?; debug!("Initial camera format: {:?}", format); Ok(Self { device, format, is_streaming: false, }) } /// Set the camera resolution and pixel format pub fn set_format(&mut self, resolution: Resolution) -> Result<()> { let (width, height) = resolution.dimensions(); // Try to set format to MJPEG or YUYV first, then fall back to others let formats = [FourCC::new(b"MJPG"), FourCC::new(b"YUYV")]; let mut success = false; let mut last_error = None; for &fourcc in &formats { let mut format = Format::new(width, height, fourcc); match self.device.set_format(&mut format) { Ok(_) => { self.format = format; success = true; break; } Err(e) => { last_error = Some(e); warn!("Failed to set format {:?}: {}", fourcc, last_error.as_ref().unwrap()); } } } if !success { return Err(anyhow!( "Failed to set any supported format: {:?}", last_error.unwrap() )); } info!( "Set camera format: {}×{} {}", self.format.width, self.format.height, String::from_utf8_lossy(&self.format.fourcc.repr) ); Ok(()) } /// Set the camera frame rate pub fn set_fps(&mut self, fps: u32) -> Result<()> { if let Some(params) = self.device.params() { let mut params = params.context("Failed to get camera parameters")?; params.set_frames_per_second(fps, 1); self.device .set_params(¶ms) .context("Failed to set frame rate")?; info!("Set camera frame rate: {} fps", fps); } else { warn!("Camera does not support frame rate adjustment"); } Ok(()) } /// Set camera exposure mode and value pub fn set_exposure(&mut self, mode: ExposureMode) -> Result<()> { // First, set auto/manual mode let ctrl_id = v4l::control::id::EXPOSURE_AUTO; let auto_value = match mode { ExposureMode::Auto => 3, // V4L2_EXPOSURE_AUTO ExposureMode::Manual(_) => 1, // V4L2_EXPOSURE_MANUAL }; self.device .set_control(ctrl_id, auto_value) .context("Failed to set exposure mode")?; // If manual, set the exposure value if let ExposureMode::Manual(exposure_time) = mode { // Exposure time in microseconds let ctrl_id = v4l::control::id::EXPOSURE_ABSOLUTE; self.device .set_control(ctrl_id, exposure_time as i64) .context("Failed to set exposure time")?; } info!("Set camera exposure: {:?}", mode); Ok(()) } /// Set camera gain (ISO) pub fn set_gain(&mut self, gain: u8) -> Result<()> { let ctrl_id = v4l::control::id::GAIN; self.device .set_control(ctrl_id, gain as i64) .context("Failed to set gain")?; info!("Set camera gain: {}", gain); Ok(()) } /// Lock focus at infinity (if supported) pub fn lock_focus_at_infinity(&mut self) -> Result<()> { // First, set focus mode to manual let auto_focus_id = v4l::control::id::FOCUS_AUTO; if let Ok(_) = self.device.set_control(auto_focus_id, 0) { // Then set focus to infinity (typically maximum value) let focus_id = v4l::control::id::FOCUS_ABSOLUTE; // Get the range of the control if let Ok(control) = self.device.control(focus_id) { let max_focus = control.maximum(); if let Ok(_) = self.device.set_control(focus_id, max_focus) { info!("Locked focus at infinity (value: {})", max_focus); return Ok(()); } } warn!("Failed to set focus to infinity"); } else { warn!("Camera does not support focus control"); } Ok(()) } /// Start streaming from the camera pub fn start_streaming(&mut self) -> Result { let queue = MmapStream::with_buffers(&self.device, Type::VideoCapture, 4) .context("Failed to create capture stream")?; self.is_streaming = true; info!("Started camera streaming"); Ok(V4l2CaptureStream { stream: queue, format: self.format.clone(), }) } /// Stop streaming from the camera pub fn stop_streaming(&mut self) -> Result<()> { // The streaming will be stopped when the CaptureStream is dropped self.is_streaming = false; info!("Stopped camera streaming"); Ok(()) } /// Check if the camera is currently streaming pub fn is_streaming(&self) -> bool { self.is_streaming } /// Get current format width pub fn width(&self) -> u32 { self.format.width } /// Get current format height pub fn height(&self) -> u32 { self.format.height } /// Get current format pixel format pub fn pixel_format(&self) -> FourCC { self.format.fourcc } } /// Wrapper around V4L2 capture stream pub struct V4l2CaptureStream { stream: MmapStream, format: Format, } impl V4l2CaptureStream { /// Capture a single frame from the camera pub fn capture_frame(&mut self) -> Result { let buffer = self.stream.next() .context("Failed to capture frame")?; let width = self.format.width as i32; let height = self.format.height as i32; // Convert the buffer to an OpenCV Mat based on the pixel format let mat = match self.format.fourcc { // MJPEG format f if f == FourCC::new(b"MJPG") => { // Decode JPEG data let data = buffer.data(); let vec_data = unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) }.to_vec(); let buf = core::Vector::from_slice(&vec_data); let img = opencv::imgcodecs::imdecode(&buf, opencv::imgcodecs::IMREAD_COLOR)?; img }, // YUYV format f if f == FourCC::new(b"YUYV") => { let data = buffer.data(); // Create a Mat from the YUYV data let mut yuyv = unsafe { let bytes_per_pixel = 2; // YUYV is 2 bytes per pixel let step = width as usize * bytes_per_pixel; core::Mat::new_rows_cols_with_data( height, width, core::CV_8UC2, data.as_ptr() as *mut _, step, )? }; // Convert YUYV to BGR let mut bgr = core::Mat::default()?; imgproc::cvt_color(&yuyv, &mut bgr, imgproc::COLOR_YUV2BGR_YUYV, 0)?; bgr }, // Unsupported format _ => { return Err(anyhow!( "Unsupported pixel format: {}", String::from_utf8_lossy(&self.format.fourcc.repr) )); } }; Ok(mat) } } impl Drop for V4l2CaptureStream { fn drop(&mut self) { debug!("V4L2 capture stream dropped"); } }