feat: 在 open_file() 中添加视频预加载和准备检查 ; 在 capture loop 中添加启动等待期和区分启动错误与运行时错误; 添加视频文件就绪状态检查函数; 优化错误处理逻辑,避免过早关闭;

This commit is contained in:
grabbit 2025-06-28 19:55:08 +08:00
parent 8b5ac0aceb
commit 52a5463664
2 changed files with 224 additions and 49 deletions

View File

@ -198,19 +198,61 @@ impl CameraController {
let frame_interval = Duration::from_secs_f64(1.0 / fps as f64);
let mut interval = time::interval(frame_interval);
// Error tracking for recovery
let mut consecutive_errors = 0;
const ERROR_THRESHOLD: u32 = 10; // After 10 errors, try to reinitialize
// 🚀 PERFORMANCE FIX: Separate startup phase from runtime phase
let mut startup_attempts = 0;
let mut consecutive_runtime_errors = 0;
let mut is_startup_phase = true;
info!("Starting camera capture at {} fps", fps);
const STARTUP_MAX_ATTEMPTS: u32 = 100; // Give video 10 seconds to start (100 * 100ms)
const RUNTIME_ERROR_THRESHOLD: u32 = 20; // More tolerance for runtime errors
const STARTUP_RETRY_DELAY: u64 = 100; // 100ms between startup retries
const RUNTIME_RETRY_DELAY: u64 = 50; // 50ms between runtime retries
info!("🚀 Starting camera capture at {} fps (startup phase)", fps);
// 🎯 SMART WAITING: For video files, do initial readiness verification
if !stream.is_ready() {
info!("📹 Video file detected, performing readiness check...");
let mut readiness_attempts = 0;
const MAX_READINESS_ATTEMPTS: u32 = 50; // 5 seconds
while readiness_attempts < MAX_READINESS_ATTEMPTS {
match stream.verify_readiness() {
Ok(true) => {
info!("✅ Video file verified ready after {} attempts", readiness_attempts);
break;
},
Ok(false) => {
readiness_attempts += 1;
if readiness_attempts % 10 == 0 {
info!("⏳ Video still not ready, attempt {}/{}", readiness_attempts, MAX_READINESS_ATTEMPTS);
}
time::sleep(Duration::from_millis(100)).await;
},
Err(e) => {
warn!("Video readiness check failed: {}, continuing anyway", e);
break;
}
}
}
if readiness_attempts >= MAX_READINESS_ATTEMPTS {
warn!("⚠️ Video readiness verification timed out, proceeding with capture anyway");
}
}
loop {
interval.tick().await;
match stream.capture_frame() {
Ok(mat) => {
// Reset error counter on success
consecutive_errors = 0;
// 🎉 SUCCESS - Reset all error counters and exit startup phase
if is_startup_phase {
is_startup_phase = false;
info!("✅ Startup phase completed successfully after {} attempts", startup_attempts);
}
startup_attempts = 0;
consecutive_runtime_errors = 0;
// Create a new frame with timestamp
let frame = Frame::new(mat, Utc::now(), frame_count);
@ -220,13 +262,6 @@ impl CameraController {
if let Err(e) = frame_buffer.push(frame.clone()) {
error!("Failed to add frame to buffer: {}", e);
}
//
// // Also add to LZ4 buffer if available (for demos) - ultra-fast compression!
// if let Some(ref lz4_buf) = lz4_buffer {
// if let Err(e) = lz4_buf.push(frame.clone()) {
// error!("Failed to add frame to LZ4 buffer: {}", e);
// }
// }
// Broadcast frame to listeners
if let Err(e) = frame_tx.send(frame) {
@ -234,41 +269,71 @@ impl CameraController {
}
}
Err(e) => {
error!("Failed to capture frame: {}", e);
consecutive_errors += 1;
if consecutive_errors >= ERROR_THRESHOLD {
error!("Too many consecutive errors ({}), attempting to reinitialize camera stream", consecutive_errors);
// 🔥 DISTINGUISH between startup errors and runtime errors
if is_startup_phase {
startup_attempts += 1;
// 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
// todo? 为啥失败太多打开 0
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)
// Check if this is a "video not ready" error (expected during startup)
let error_msg = format!("{}", e);
if error_msg.contains("Video not ready yet") {
if startup_attempts % 20 == 0 { // Log every 2 seconds
info!("⏳ Video still loading... attempt {}/{} ({})", startup_attempts, STARTUP_MAX_ATTEMPTS, e);
}
} else {
debug!("Startup error attempt {}/{}: {}", startup_attempts, STARTUP_MAX_ATTEMPTS, e);
}
if startup_attempts >= STARTUP_MAX_ATTEMPTS {
error!("❌ Video failed to start after {} attempts over {} seconds",
STARTUP_MAX_ATTEMPTS, STARTUP_MAX_ATTEMPTS as u64 * STARTUP_RETRY_DELAY / 1000);
error!("Last error: {}", e);
break; // Exit the capture loop - video can't be loaded
}
// Longer delay during startup to give video time to load
time::sleep(Duration::from_millis(STARTUP_RETRY_DELAY)).await;
} else {
// RUNTIME ERROR - after video was working
consecutive_runtime_errors += 1;
error!("Runtime capture error {}/{}: {}", consecutive_runtime_errors, RUNTIME_ERROR_THRESHOLD, e);
if consecutive_runtime_errors >= RUNTIME_ERROR_THRESHOLD {
error!("Too many consecutive runtime errors ({}), attempting to reinitialize camera stream", consecutive_runtime_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_runtime_errors = 0;
is_startup_phase = true; // Back to startup phase
startup_attempts = 0;
},
Err(e) => error!("Failed to restart camera streaming: {}", e)
}
},
Err(e) => error!("Failed to reopen camera: {}", e)
}
}
// Shorter delay during runtime to maintain responsiveness
time::sleep(Duration::from_millis(RUNTIME_RETRY_DELAY)).await;
}
// Small delay to avoid tight error loop
time::sleep(Duration::from_millis(100)).await;
}
}
}
// If we reach here, the capture loop has been terminated
warn!("🛑 Camera capture loop terminated");
});
info!("Camera capture started");

View File

@ -56,20 +56,77 @@ impl OpenCVCamera {
let path_str = path.as_ref().to_str()
.ok_or_else(|| anyhow!("Invalid path"))?;
info!("Opening video file: {}", path_str);
let mut capture = videoio::VideoCapture::from_file(path_str, videoio::CAP_ANY)?;
if !capture.is_opened()? {
return Err(anyhow!("Failed to open video file: {}", path_str));
}
// Get video properties
let width = capture.get(videoio::CAP_PROP_FRAME_WIDTH)? as u32;
let height = capture.get(videoio::CAP_PROP_FRAME_HEIGHT)? as u32;
let fps = capture.get(videoio::CAP_PROP_FPS)? as u32;
let total_frames = capture.get(videoio::CAP_PROP_FRAME_COUNT)? as u32;
// 🚀 PERFORMANCE FIX: Ensure video is actually ready before proceeding
// On slow devices like Raspberry Pi, the video file might not be immediately ready
info!("Waiting for video file to be ready...");
let mut width = 0u32;
let mut height = 0u32;
let mut fps = 0u32;
let mut total_frames = 0u32;
// Try to get video properties with retries for slow devices
let mut attempts = 0;
const MAX_PROPERTY_ATTEMPTS: u32 = 10;
while attempts < MAX_PROPERTY_ATTEMPTS {
// Try to get basic properties
let w = capture.get(videoio::CAP_PROP_FRAME_WIDTH).unwrap_or(0.0) as u32;
let h = capture.get(videoio::CAP_PROP_FRAME_HEIGHT).unwrap_or(0.0) as u32;
let f = capture.get(videoio::CAP_PROP_FPS).unwrap_or(0.0) as u32;
let t = capture.get(videoio::CAP_PROP_FRAME_COUNT).unwrap_or(0.0) as u32;
if w > 0 && h > 0 && f > 0 {
width = w;
height = h;
fps = f;
total_frames = t;
break;
}
attempts += 1;
debug!("Video properties not ready yet, attempt {}/{}", attempts, MAX_PROPERTY_ATTEMPTS);
std::thread::sleep(std::time::Duration::from_millis(200)); // Wait 200ms
}
if width == 0 || height == 0 {
return Err(anyhow!("Failed to get valid video properties after {} attempts", MAX_PROPERTY_ATTEMPTS));
}
// 🚀 CRITICAL: Try to read the first frame to ensure video is truly ready
info!("Testing video readiness by reading first frame...");
let mut test_frame = core::Mat::default();
let mut first_frame_attempts = 0;
const MAX_FIRST_FRAME_ATTEMPTS: u32 = 20;
while first_frame_attempts < MAX_FIRST_FRAME_ATTEMPTS {
if capture.read(&mut test_frame).unwrap_or(false) && !test_frame.empty() {
info!("✅ Video file is ready and first frame loaded successfully");
// Reset to beginning for actual playback
if let Err(e) = capture.set(videoio::CAP_PROP_POS_FRAMES, 0.0) {
warn!("Could not reset video to beginning: {}", e);
}
break;
}
first_frame_attempts += 1;
debug!("First frame not ready yet, attempt {}/{}", first_frame_attempts, MAX_FIRST_FRAME_ATTEMPTS);
std::thread::sleep(std::time::Duration::from_millis(100)); // Wait 100ms
}
if first_frame_attempts >= MAX_FIRST_FRAME_ATTEMPTS {
warn!("⚠️ Could not read first frame after {} attempts, proceeding anyway", MAX_FIRST_FRAME_ATTEMPTS);
}
info!(
"Opened video file: {} ({}x{} @ {} fps, {} frames)",
"Opened video file: {} ({}x{} @ {} fps, {} frames)",
path_str, width, height, fps, total_frames
);
@ -300,6 +357,7 @@ impl OpenCVCamera {
pub struct OpenCVCaptureStream {
capture: Arc<Mutex<videoio::VideoCapture>>,
loop_video: bool,
is_video_ready: bool, // Track if video is confirmed ready
}
impl OpenCVCaptureStream {
@ -308,6 +366,44 @@ impl OpenCVCaptureStream {
Self {
capture,
loop_video,
is_video_ready: false, // Will be set to true after first successful frame
}
}
/// Check if the video stream is ready and can provide frames
pub fn is_ready(&self) -> bool {
// For live cameras, assume always ready
// Note: we identify live cameras by checking if loop_video is false
// This is a bit of a hack, but works for our current architecture
if !self.loop_video {
return true;
}
// For video files, check if we've successfully read at least one frame
self.is_video_ready
}
/// Force check if the video file is ready by attempting to read a frame
/// This is useful for slow devices where the initial open might succeed
/// but the video isn't actually ready for streaming yet
pub fn verify_readiness(&mut self) -> Result<bool> {
if self.is_video_ready {
return Ok(true);
}
let mut test_frame = core::Mat::default();
let mut capture_guard = self.capture.lock().unwrap();
// Try to read without advancing position
let current_pos = capture_guard.get(videoio::CAP_PROP_POS_FRAMES).unwrap_or(0.0);
if capture_guard.read(&mut test_frame)? && !test_frame.empty() {
// Reset position
let _ = capture_guard.set(videoio::CAP_PROP_POS_FRAMES, current_pos);
self.is_video_ready = true;
Ok(true)
} else {
Ok(false)
}
}
@ -337,9 +433,23 @@ impl OpenCVCaptureStream {
return Err(anyhow!("End of video reached"));
}
}
// 🚀 Mark video as ready after first successful frame read
if !self.is_video_ready {
self.is_video_ready = true;
debug!("✅ Video stream confirmed ready - first frame successfully captured");
}
Ok(frame)
} else {
Err(anyhow!("Failed to capture frame"))
// Distinguish between "video not ready yet" and "real error"
if !self.is_video_ready {
// This might be a startup issue on slow devices
Err(anyhow!("Video not ready yet (startup phase)"))
} else {
// This is a real capture error after video was working
Err(anyhow!("Failed to capture frame"))
}
}
}
}