fix: 树莓派gstreamer pipeline报错

This commit is contained in:
grabbit 2025-04-05 16:05:10 +08:00
parent 5ccaca3743
commit 5fc78d7ef9
2 changed files with 161 additions and 25 deletions

View File

@ -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<String, anyhow::Error> {
// 如果是简单的/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");

View File

@ -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");
}
}