diff --git a/src/camera/controller.rs b/src/camera/controller.rs index 42383c6..5d11bb5 100644 --- a/src/camera/controller.rs +++ b/src/camera/controller.rs @@ -102,8 +102,24 @@ impl CameraController { // 尝试拍摄测试帧以确保相机设置正确 info!("Testing camera by capturing a test frame..."); - // 使用已打开的相机实例直接捕获一帧,而不是创建新的流 - match camera.capture_test_frame() { + // 为测试帧创建一个新的临时相机实例,而不是使用主实例 + let test_frame_result = { + info!("Creating temporary camera instance for test frame"); + let mut test_camera = OpenCVCamera::open(&self.settings.device) + .context("Failed to open temporary camera for test frame")?; + + // 应用最小必要设置 + let _ = test_camera.set_format(self.settings.resolution); + + // 捕获测试帧 + let result = test_camera.capture_test_frame(); + + // test_camera将在此作用域结束时自动释放 + result + }; + + // 处理测试帧结果 + match test_frame_result { Ok(_) => { info!("Successfully captured test frame - camera is working correctly"); }, @@ -133,6 +149,9 @@ impl CameraController { return Err(anyhow::anyhow!("Camera initialization failed: {}", e)); } } + + // 给设备一点时间完全释放资源 + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; self.camera = Some(camera); info!("Camera initialized successfully"); @@ -158,9 +177,66 @@ impl CameraController { .as_mut() .ok_or_else(|| anyhow::anyhow!("Camera not initialized"))?; + // 先释放当前相机实例,以确保没有资源冲突 + self.camera = None; + + // 等待一小段时间以确保资源被完全释放 + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + // 重新创建相机实例 + let mut new_camera = match OpenCVCamera::open(&self.settings.device) { + Ok(camera) => camera, + Err(e) => { + error!("Failed to reopen camera for streaming: {}", e); + + // 尝试使用不同的方法打开相机 + let device_busy = e.to_string().contains("busy") || e.to_string().contains("已被占用"); + + if device_busy { + warn!("Camera device is busy. Attempting to force release the device..."); + + // 使用v4l2-ctl尝试重置设备 + if let Ok(device_path) = extract_device_path(&self.settings.device) { + info!("Trying to reset device: {}", device_path); + + match std::process::Command::new("v4l2-ctl") + .args(&["--device", &device_path, "--set-ctrl=power_line_frequency=0"]) + .output() + { + Ok(_) => info!("Device reset command sent to {}", device_path), + Err(e) => warn!("Failed to reset device: {}", e) + }; + + // 等待设备重置 + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; + + // 再次尝试打开设备 + match OpenCVCamera::open(&self.settings.device) { + Ok(camera) => { + info!("Successfully reopened camera after reset"); + camera + }, + Err(e) => { + error!("Still failed to open camera after reset: {}", e); + return Err(anyhow::anyhow!("Failed to open camera for streaming: {}", e)); + } + } + } else { + return Err(anyhow::anyhow!("Failed to extract device path from: {}", self.settings.device)); + } + } else { + return Err(anyhow::anyhow!("Failed to open camera for streaming: {}", e)); + } + } + }; + + // 应用基本设置 + let _ = new_camera.set_format(self.settings.resolution); + let _ = new_camera.set_fps(self.settings.fps); + // Start the camera streaming info!("Starting camera streaming with device: {}", self.settings.device); - let stream_result = camera.start_streaming(); + let stream_result = new_camera.start_streaming(); let mut stream = match stream_result { Ok(stream) => stream, @@ -176,37 +252,23 @@ impl CameraController { // 在树莓派上但没有使用GStreamer pipeline,尝试使用pipeline重新初始化 warn!("Running on Raspberry Pi without GStreamer pipeline. Attempting to reinitialize camera with GStreamer..."); - // 关闭当前相机 - self.camera = None; - // 创建更简单的GStreamer pipeline - let pipeline = "v4l2src ! videoconvert ! appsink".to_string(); + let pipeline = format!("v4l2src device={} ! videoconvert ! appsink", + extract_device_path(&self.settings.device).unwrap_or("/dev/video0".to_string())); - info!("Trying simple GStreamer pipeline: {}", pipeline); + 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)); - } - } + // 使用 Box::pin 来解决递归 async 调用问题 + return Box::pin(self.start_capture_impl()).await; } return Err(anyhow::anyhow!("Failed to start camera streaming: {}", e)); } }; + + // 保存新的相机实例 + self.camera = Some(new_camera); // 先保存一个测试帧副本 let test_frame = match stream.capture_frame() { @@ -580,6 +642,40 @@ impl CameraController { } } +/// 从设备字符串中提取/dev/video设备路径 +fn extract_device_path(device_str: &str) -> Result { + // 如果是简单的/dev/video路径,直接返回 + if device_str.starts_with("/dev/video") { + return Ok(device_str.to_string()); + } + + // 如果是GStreamer pipeline,尝试提取device参数 + if device_str.contains("v4l2src") && device_str.contains("device=") { + // 匹配device=/dev/videoX 格式 + if let Some(start_idx) = device_str.find("device=") { + let device_part = &device_str[start_idx + 7..]; // "device=".len() == 7 + + // 如果device路径用引号括起来 + if device_part.starts_with('"') || device_part.starts_with('\'') { + let quote = device_part.chars().next().unwrap(); + if let Some(end_idx) = device_part[1..].find(quote) { + return Ok(device_part[1..=end_idx].to_string()); + } + } + // 否则找到下一个空格或感叹号 + else if let Some(end_idx) = device_part.find(|c| c == ' ' || c == '!') { + return Ok(device_part[..end_idx].to_string()); + } else { + // 如果没有终止符,假设它是整个剩余部分 + return Ok(device_part.to_string()); + } + } + } + + // 没找到设备路径,返回默认值 + Err(anyhow::anyhow!("Could not extract device path from: {}", device_str)) +} + impl Drop for CameraController { fn drop(&mut self) { info!("Cleaning up camera controller resources"); diff --git a/src/camera/opencv.rs b/src/camera/opencv.rs index da8acb9..21a3b0a 100644 --- a/src/camera/opencv.rs +++ b/src/camera/opencv.rs @@ -243,6 +243,9 @@ impl OpenCVCamera { info!("Using resolution from GStreamer pipeline (cannot be changed at runtime)"); } + // 等待一小段时间让摄像头设置生效 + std::thread::sleep(std::time::Duration::from_millis(100)); + // 尝试读取实际分辨率 let actual_width = match self.capture.get(videoio::CAP_PROP_FRAME_WIDTH) { Ok(w) if w > 0.0 => w as u32, @@ -430,6 +433,21 @@ impl OpenCVCamera { /// Stop streaming from the camera pub fn stop_streaming(&mut self) -> Result<()> { + // 尝试重置或释放相机资源,以避免下次打开时出现"设备忙"的错误 + + // 对于V4L2设备,可以尝试通过v4l2-ctl重置设备 + if self.device.starts_with("/dev/video") { + // 记录日志但继续执行,不要因为这个失败而中断 + match std::process::Command::new("v4l2-ctl") + .args(&["--device", &self.device, "--set-ctrl=power_line_frequency=0"]) + .output() + { + Ok(_) => info!("Successfully reset V4L2 device: {}", self.device), + Err(e) => warn!("Failed to reset V4L2 device (non-critical): {}", e) + } + } + + // 在GStreamer pipeline中,这可能不那么重要 self.is_streaming = false; info!("Stopped camera streaming"); Ok(()) @@ -498,3 +516,25 @@ impl Drop for OpenCVCaptureStream { debug!("OpenCV capture stream dropped"); } } + +impl Drop for OpenCVCamera { + fn drop(&mut self) { + debug!("OpenCV camera dropping, releasing resources"); + + // 在释放资源前尝试执行一些清理操作 + if self.is_streaming { + if let Err(e) = self.stop_streaming() { + warn!("Error stopping camera during drop: {}", e); + } + } + + // VideoCapture在超出作用域时会自动释放,但我们可以尝试显式释放 + if self.device.starts_with("/dev/video") { + let _ = std::process::Command::new("v4l2-ctl") + .args(&["--device", &self.device, "--set-ctrl=power_line_frequency=0"]) + .output(); + } + + debug!("OpenCV camera resources released"); + } +}