diff --git a/examples/camera_demo.rs b/examples/camera_demo.rs index 896a9e6..1b0592c 100644 --- a/examples/camera_demo.rs +++ b/examples/camera_demo.rs @@ -121,18 +121,29 @@ async fn main() -> Result<()> { /// 创建用于演示的配置 fn create_demo_config() -> Config { - // 根据操作系统自动选择合适的设备路径 - let device = match std::env::consts::OS { - "linux" => "/dev/video0".to_string(), - "macos" => "0".to_string(), + // 检测是否为树莓派 + let is_raspberry_pi = std::fs::read_to_string("/proc/cpuinfo") + .map(|content| content.contains("Raspberry Pi") || content.contains("BCM")) + .unwrap_or(false); + + // 根据操作系统和设备类型自动选择合适的设备路径 + let device = if is_raspberry_pi { + // 使用特定的GStreamer pipeline字符串在树莓派上适配摄像头 + // 这个pipeline使用v4l2src作为源,并确保正确初始化 + "v4l2src device=/dev/video0 ! video/x-raw,width=1280,height=720,framerate=30/1 ! videoconvert ! appsink".to_string() + } else if std::env::consts::OS == "linux" { + "/dev/video0".to_string() + } else if std::env::consts::OS == "macos" { + "0".to_string() + } else { // Windows和其他操作系统通常使用索引 - _ => "0".to_string(), + "0".to_string() }; info!("自动选择相机设备: {}", device); let camera_settings = CameraSettings { - device, // 使用根据操作系统确定的设备路径 + device, // 使用根据操作系统和设备类型确定的设备路径或pipeline resolution: Resolution::HD720p, fps: 30, exposure: ExposureMode::Auto, diff --git a/src/camera/controller.rs b/src/camera/controller.rs index 8657107..966ca56 100644 --- a/src/camera/controller.rs +++ b/src/camera/controller.rs @@ -62,6 +62,12 @@ impl CameraController { /// Initialize the camera with current settings pub async fn initialize(&mut self) -> Result<()> { + // 使用私有方法实现递归安全的异步初始化 + self.initialize_impl().await + } + + /// 内部实现,用于避免异步递归导致的无限大小 future 问题 + async fn initialize_impl(&mut self) -> Result<()> { // Open the camera let mut camera = OpenCVCamera::open(&self.settings.device).context("Failed to open camera")?; @@ -89,6 +95,55 @@ impl CameraController { .context("Failed to lock focus at infinity")?; } + // 尝试拍摄测试帧以确保相机设置正确 + info!("Testing camera by capturing a test frame..."); + let mut test_stream = match camera.start_streaming() { + Ok(stream) => stream, + Err(e) => { + error!("Failed to start test stream: {}", e); + + // 检查是否在树莓派上,以及是否已经使用GStreamer + let is_raspberry_pi = std::fs::read_to_string("/proc/cpuinfo") + .map(|content| content.contains("Raspberry Pi") || content.contains("BCM")) + .unwrap_or(false); + + if is_raspberry_pi && !self.settings.device.contains("!") { + // 在树莓派上但没有使用GStreamer pipeline,尝试使用pipeline重新初始化 + warn!("Running on Raspberry Pi without GStreamer pipeline. Attempting to reinitialize with GStreamer..."); + + // 创建GStreamer pipeline + let device_num = self.settings.device.strip_prefix("/dev/video") + .unwrap_or("0"); + + let pipeline = format!( + "v4l2src device=/dev/video{} ! video/x-raw,width={},height={},framerate={}/1 ! videoconvert ! appsink", + device_num, self.settings.resolution.dimensions().0, + self.settings.resolution.dimensions().1, self.settings.fps + ); + + info!("Trying GStreamer pipeline: {}", pipeline); + self.settings.device = pipeline; + + // 使用 Box::pin 来解决递归 async 调用问题 + // 这样创建一个固定大小的 future + return Box::pin(self.initialize_impl()).await; + } + + return Err(anyhow::anyhow!("Camera initialization failed: {}", e)); + } + }; + + // 尝试捕获测试帧 + match test_stream.capture_frame() { + Ok(_) => { + info!("Successfully captured test frame - camera is working correctly"); + }, + Err(e) => { + error!("Failed to capture test frame: {}", e); + return Err(anyhow::anyhow!("Camera initialization failed: Unable to capture test frame")); + } + } + self.camera = Some(camera); info!("Camera initialized successfully"); @@ -97,6 +152,12 @@ impl CameraController { /// Start camera capture in a background task pub async fn start_capture(&mut self) -> Result<()> { + // 使用私有方法实现递归安全的异步函数 + self.start_capture_impl().await + } + + /// 内部实现,用于避免异步递归导致的无限大小 future 问题 + async fn start_capture_impl(&mut self) -> Result<()> { if self.is_running { warn!("Camera capture is already running"); return Ok(()); @@ -108,17 +169,83 @@ impl CameraController { .ok_or_else(|| anyhow::anyhow!("Camera not initialized"))?; // Start the camera streaming - let stream = camera - .start_streaming() - .context("Failed to start camera streaming")?; + info!("Starting camera streaming with device: {}", self.settings.device); + let stream_result = camera.start_streaming(); + + let stream = match stream_result { + Ok(stream) => stream, + Err(e) => { + error!("Failed to start camera streaming: {}", e); + + // 检测是否在树莓派上 + let is_raspberry_pi = std::fs::read_to_string("/proc/cpuinfo") + .map(|content| content.contains("Raspberry Pi") || content.contains("BCM")) + .unwrap_or(false); + + if is_raspberry_pi && !self.settings.device.contains("!") { + // 在树莓派上但没有使用GStreamer pipeline,尝试使用pipeline重新初始化 + warn!("Running on Raspberry Pi without GStreamer pipeline. Attempting to reinitialize camera with GStreamer..."); + + // 关闭当前相机 + self.camera = None; + + // 创建GStreamer pipeline + let device_num = self.settings.device.strip_prefix("/dev/video") + .unwrap_or("0"); + + let pipeline = format!( + "v4l2src device=/dev/video{} ! video/x-raw,width={},height={},framerate={}/1 ! videoconvert ! appsink", + device_num, self.settings.resolution.dimensions().0, + self.settings.resolution.dimensions().1, self.settings.fps + ); + + info!("Trying GStreamer pipeline: {}", pipeline); + self.settings.device = pipeline; + + // 重新初始化相机 + match OpenCVCamera::open(&self.settings.device) { + Ok(mut camera) => { + // 基本配置 + let _ = camera.set_format(self.settings.resolution); + let _ = camera.set_fps(self.settings.fps); + + self.camera = Some(camera); + + // 使用 Box::pin 来解决递归 async 调用问题 + return Box::pin(self.start_capture_impl()).await; + }, + Err(e) => { + error!("Failed to reinitialize camera with GStreamer: {}", e); + return Err(anyhow::anyhow!("Camera streaming failed on Raspberry Pi: {}", e)); + } + } + } + + return Err(anyhow::anyhow!("Failed to start camera streaming: {}", e)); + } + }; self.stream = Some(stream); self.is_running = true; + // 尝试捕获一个测试帧,确保流工作正常 + if let Some(mut stream_test) = self.stream.as_mut() { + match stream_test.capture_frame() { + Ok(_) => info!("Successfully captured test frame - camera stream is working correctly"), + Err(e) => { + error!("Test frame capture failed: {}", e); + self.is_running = false; + self.stream = None; + return Err(anyhow::anyhow!("Camera stream test failed: {}", e)); + } + } + } + // Clone necessary values for the capture task let frame_buffer = self.frame_buffer.clone(); let frame_tx = self.frame_tx.clone(); let fps = self.settings.fps; + let device = self.settings.device.clone(); // 复制设备路径用于恢复 let mut stream = self .stream .take() @@ -163,26 +290,64 @@ impl CameraController { if consecutive_errors >= ERROR_THRESHOLD { error!("Too many consecutive errors ({}), attempting to reinitialize camera stream", consecutive_errors); - // Try to recreate the stream - this is a simplified version - // In a real implementation, you might want to signal the main thread - // to fully reinitialize the camera - match OpenCVCamera::open("0") { // Note: simplified, should use original device - Ok(mut camera) => { - // Configure minimal settings - let _ = camera.set_format(Resolution::HD720p); - let _ = camera.set_fps(fps); - - // Try to start streaming again - match camera.start_streaming() { - Ok(new_stream) => { - info!("Successfully reinitialized camera stream"); - stream = new_stream; - consecutive_errors = 0; - }, - Err(e) => error!("Failed to restart camera streaming: {}", e) - } - }, - Err(e) => error!("Failed to reopen camera: {}", e) + // 检测是否在树莓派上 + let is_raspberry_pi = std::fs::read_to_string("/proc/cpuinfo") + .map(|content| content.contains("Raspberry Pi") || content.contains("BCM")) + .unwrap_or(false); + + // 判断是否需要尝试GStreamer + let use_gstreamer = is_raspberry_pi && !device.contains("!"); + + if use_gstreamer { + // 创建GStreamer pipeline + let device_num = device.strip_prefix("/dev/video").unwrap_or("0"); + + let pipeline = format!( + "v4l2src device=/dev/video{} ! video/x-raw,width={},height={},framerate={}/1 ! videoconvert ! appsink", + device_num, 1280, 720, fps + ); + + error!("Raspberry Pi detected - trying GStreamer pipeline: {}", pipeline); + + // 尝试使用GStreamer打开摄像头 + match OpenCVCamera::open(&pipeline) { + Ok(mut camera) => { + // 配置最低限度设置 + let _ = camera.set_format(Resolution::HD720p); + let _ = camera.set_fps(fps); + + // 尝试重新启动流 + match camera.start_streaming() { + Ok(new_stream) => { + info!("Successfully reinitialized camera stream with GStreamer"); + stream = new_stream; + consecutive_errors = 0; + }, + Err(e) => error!("Failed to restart camera streaming with GStreamer: {}", e) + } + }, + Err(e) => error!("Failed to reopen camera with GStreamer: {}", e) + } + } else { + // 使用原始设备路径重新创建流 + match OpenCVCamera::open(&device) { + Ok(mut camera) => { + // 配置最低限度设置 + let _ = camera.set_format(Resolution::HD720p); + let _ = camera.set_fps(fps); + + // 尝试重新启动流 + match camera.start_streaming() { + Ok(new_stream) => { + info!("Successfully reinitialized camera stream"); + stream = new_stream; + consecutive_errors = 0; + }, + Err(e) => error!("Failed to restart camera streaming: {}", e) + } + }, + Err(e) => error!("Failed to reopen camera: {}", e) + } } } @@ -257,12 +422,12 @@ impl CameraController { self.camera = Some(camera); } else { - self.initialize().await?; + self.initialize_impl().await?; } // Restart if it was running if was_running { - self.start_capture().await?; + self.start_capture_impl().await?; } info!("Camera settings updated"); diff --git a/src/camera/opencv.rs b/src/camera/opencv.rs index e30bc50..6360255 100644 --- a/src/camera/opencv.rs +++ b/src/camera/opencv.rs @@ -52,8 +52,30 @@ impl OpenCVCamera { /// Create a VideoCapture instance from a path or device index fn create_capture_from_path(path_str: &str) -> Result { + info!("Attempting to open camera with path/pipeline: {}", path_str); + + // 检测是否为GStreamer pipeline (通常包含感叹号) + if path_str.contains("!") { + info!("Detected GStreamer pipeline, using from_file with GSTREAMER backend"); + // 如果是GStreamer pipeline,直接使用from_file方法并指定GSTREAMER后端 + match videoio::VideoCapture::from_file(path_str, videoio::CAP_GSTREAMER) { + Ok(cap) => { + if cap.is_opened()? { + info!("Successfully opened GStreamer pipeline"); + return Ok(cap); + } else { + warn!("GStreamer pipeline opened but not ready, will try alternative methods"); + } + }, + Err(e) => { + warn!("Failed to open with GStreamer: {}. Will try alternative methods.", e); + } + } + } + // Try to parse as integer index first if let Ok(device_index) = path_str.parse::() { + info!("Opening camera by index: {}", device_index); return Ok(videoio::VideoCapture::new(device_index, videoio::CAP_ANY)?); } @@ -63,6 +85,21 @@ impl OpenCVCamera { // For Linux device files like /dev/video0 if let Some(num_str) = path_str.strip_prefix("/dev/video") { if let Ok(device_index) = num_str.parse::() { + info!("Opening Linux camera from /dev/video{}", device_index); + + // 首先尝试V4L2后端,这在树莓派上通常最可靠 + match videoio::VideoCapture::new(device_index, videoio::CAP_V4L2) { + Ok(cap) => { + if cap.is_opened()? { + info!("Successfully opened camera with V4L2 backend"); + return Ok(cap); + } + }, + Err(e) => warn!("Failed to open with V4L2: {}", e) + } + + // 回退到通用后端 + info!("Falling back to default backend"); return Ok(videoio::VideoCapture::new(device_index, videoio::CAP_ANY)?); } else { return Err(anyhow!("Invalid device number in path: {}", path_str)); @@ -73,7 +110,6 @@ impl OpenCVCamera { #[cfg(target_os = "macos")] { // macOS doesn't use /dev/video* paths, but it might have a special format - // If we get a path with "camera" or "facetime" or other macOS camera identifiers if path_str.contains("camera") || path_str.contains("facetime") || path_str.contains("avfoundation") { // For macOS, try to extract any numbers in the path let nums: Vec<&str> = path_str.split(|c: char| !c.is_digit(10)) @@ -82,30 +118,87 @@ impl OpenCVCamera { if let Some(num_str) = nums.first() { if let Ok(device_index) = num_str.parse::() { + info!("Opening macOS camera with AVFoundation: {}", device_index); return Ok(videoio::VideoCapture::new(device_index, videoio::CAP_AVFOUNDATION)?); } } // If we can't extract a number, try device 0 with AVFoundation + info!("Falling back to default macOS camera (index 0)"); return Ok(videoio::VideoCapture::new(0, videoio::CAP_AVFOUNDATION)?); } } + // 对于树莓派,可能需要指定GSTREAMER后端 + if std::fs::read_to_string("/proc/cpuinfo") + .map(|content| content.contains("Raspberry Pi") || content.contains("BCM")) + .unwrap_or(false) + { + // 尝试使用带有GSTREAMER后端的方式打开 + let gst_pipeline = if path_str.starts_with("/dev/video") { + // 如果是设备路径,转换为gstreamer pipeline + let num_str = path_str.strip_prefix("/dev/video").unwrap_or("0"); + format!("v4l2src device={} ! video/x-raw,width=1280,height=720 ! videoconvert ! appsink", path_str) + } else { + path_str.to_string() + }; + + info!("Trying to open with GStreamer on Raspberry Pi: {}", gst_pipeline); + match videoio::VideoCapture::from_file(&gst_pipeline, videoio::CAP_GSTREAMER) { + Ok(cap) => { + if cap.is_opened()? { + info!("Successfully opened camera with GStreamer on Raspberry Pi"); + return Ok(cap); + } + }, + Err(e) => warn!("Failed to open with GStreamer on Raspberry Pi: {}", e) + } + } + // For URLs, video files, or any other path type - Ok(videoio::VideoCapture::from_file(path_str, videoio::CAP_ANY)?) + info!("Using generic file opening method for: {}", path_str); + let cap = videoio::VideoCapture::from_file(path_str, videoio::CAP_ANY)?; + + if !cap.is_opened()? { + warn!("Camera opened but not ready. This might indicate a configuration issue."); + } + + Ok(cap) } /// Set the camera resolution and pixel format pub fn set_format(&mut self, resolution: Resolution) -> Result<()> { let (width, height) = resolution.dimensions(); - // Set resolution - self.capture.set(videoio::CAP_PROP_FRAME_WIDTH, width as f64)?; - self.capture.set(videoio::CAP_PROP_FRAME_HEIGHT, height as f64)?; + // 检查是否为GStreamer pipeline + let is_gstreamer_pipeline = self.device.contains("!"); - // Read back actual resolution (might be different from requested) - let actual_width = self.capture.get(videoio::CAP_PROP_FRAME_WIDTH)? as u32; - let actual_height = self.capture.get(videoio::CAP_PROP_FRAME_HEIGHT)? as u32; + if !is_gstreamer_pipeline { + // 对于非GStreamer,正常设置分辨率 + info!("Setting resolution to {}x{}", width, height); + self.capture.set(videoio::CAP_PROP_FRAME_WIDTH, width as f64)?; + self.capture.set(videoio::CAP_PROP_FRAME_HEIGHT, height as f64)?; + } else { + // 对于GStreamer pipeline,不需要设置分辨率,因为已经在pipeline中指定了 + info!("Using resolution from GStreamer pipeline (cannot be changed at runtime)"); + } + + // 尝试读取实际分辨率 + let actual_width = match self.capture.get(videoio::CAP_PROP_FRAME_WIDTH) { + Ok(w) if w > 0.0 => w as u32, + _ => { + warn!("Could not read actual width, using requested width"); + width + } + }; + + let actual_height = match self.capture.get(videoio::CAP_PROP_FRAME_HEIGHT) { + Ok(h) if h > 0.0 => h as u32, + _ => { + warn!("Could not read actual height, using requested height"); + height + } + }; if actual_width != width || actual_height != height { warn!( @@ -117,23 +210,42 @@ impl OpenCVCamera { self.width = actual_width; self.height = actual_height; - info!("Set camera format: {}×{}", self.width, self.height); + info!("Camera format: {}×{}", self.width, self.height); Ok(()) } /// Set the camera frame rate pub fn set_fps(&mut self, fps: u32) -> Result<()> { - self.capture.set(videoio::CAP_PROP_FPS, fps as f64)?; + // 检查是否为GStreamer pipeline + let is_gstreamer_pipeline = self.device.contains("!"); - // Read back actual FPS - let actual_fps = self.capture.get(videoio::CAP_PROP_FPS)?; + if !is_gstreamer_pipeline { + // 对于非GStreamer,正常设置FPS + info!("Setting FPS to {}", fps); + + if let Err(e) = self.capture.set(videoio::CAP_PROP_FPS, fps as f64) { + warn!("Could not set FPS: {}. This might be expected for some cameras.", e); + } + } else { + // 对于GStreamer pipeline,不需要设置FPS,因为已经在pipeline中指定了 + info!("Using FPS from GStreamer pipeline (cannot be changed at runtime)"); + } + + // 尝试读取实际FPS + let actual_fps = match self.capture.get(videoio::CAP_PROP_FPS) { + Ok(f) if f > 0.0 => f, + _ => { + warn!("Could not read actual FPS, using requested FPS"); + fps as f64 + } + }; if (actual_fps - fps as f64).abs() > 0.1 { warn!("Requested {} fps but got {} fps", fps, actual_fps); } - info!("Set camera frame rate: {} fps", actual_fps); + info!("Camera frame rate: {} fps", actual_fps); Ok(()) } @@ -193,20 +305,63 @@ impl OpenCVCamera { return Err(anyhow!("Camera is not open")); } + info!("Creating stream from device: {}", self.device); + + // 检查是否为GStreamer pipeline + let is_gstreamer_pipeline = self.device.contains("!"); + + if is_gstreamer_pipeline { + info!("Using GStreamer pipeline streaming approach"); + } + // Create a separate VideoCapture for the stream to avoid concurrent access issues let device = self.device.clone(); - let mut stream_capture = Self::create_capture_from_path(&device)?; + let stream_capture_result = Self::create_capture_from_path(&device); - // Apply the same settings - stream_capture.set(videoio::CAP_PROP_FRAME_WIDTH, self.width as f64)?; - stream_capture.set(videoio::CAP_PROP_FRAME_HEIGHT, self.height as f64)?; + let mut stream_capture = match stream_capture_result { + Ok(cap) => cap, + Err(e) => { + error!("Failed to create stream capture: {}", e); + + if is_gstreamer_pipeline { + error!("GStreamer pipeline error. This could indicate:"); + error!("1. GStreamer is not properly installed"); + error!("2. Pipeline syntax is incorrect"); + error!("3. The specified video device doesn't exist"); + error!("4. OpenCV was not compiled with GStreamer support"); + } + + return Err(anyhow!("Failed to create stream capture: {}", e)); + } + }; - if !stream_capture.is_opened()? { - return Err(anyhow!("Failed to open camera stream")); + // Apply the same settings if not using a GStreamer pipeline + // (GStreamer pipeline already has configuration in the pipeline string) + if !is_gstreamer_pipeline { + // 尝试设置分辨率,但不抛出失败异常 + if let Err(e) = stream_capture.set(videoio::CAP_PROP_FRAME_WIDTH, self.width as f64) { + warn!("Failed to set stream width (non-critical): {}", e); + } + + if let Err(e) = stream_capture.set(videoio::CAP_PROP_FRAME_HEIGHT, self.height as f64) { + warn!("Failed to set stream height (non-critical): {}", e); + } + } + + // 确保流被正确打开 + match stream_capture.is_opened() { + Ok(is_open) => { + if !is_open { + return Err(anyhow!("Failed to open camera stream - camera reports not opened")); + } + }, + Err(e) => { + return Err(anyhow!("Failed to check if camera stream is open: {}", e)); + } } self.is_streaming = true; - info!("Started camera streaming"); + info!("Started camera streaming successfully"); Ok(OpenCVCaptureStream { capture: stream_capture,