fix: 树莓派gstreamer pipeline报错

This commit is contained in:
grabbit 2025-04-05 15:37:33 +08:00
parent 2cab6e8a7a
commit ddac14fb3f
3 changed files with 382 additions and 51 deletions

View File

@ -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,

View File

@ -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,16 +290,53 @@ 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
// 检测是否在树莓派上
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) => {
// 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 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");
@ -185,6 +349,7 @@ impl CameraController {
Err(e) => error!("Failed to reopen camera: {}", e)
}
}
}
// Small delay to avoid tight error loop
time::sleep(Duration::from_millis(100)).await;
@ -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");

View File

@ -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<videoio::VideoCapture> {
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::<i32>() {
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::<i32>() {
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::<i32>() {
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
// 检查是否为GStreamer pipeline
let is_gstreamer_pipeline = self.device.contains("!");
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)");
}
// 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;
// 尝试读取实际分辨率
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 !stream_capture.is_opened()? {
return Err(anyhow!("Failed to open camera stream"));
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));
}
};
// 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,