feat: 在 open_file() 中添加视频预加载和准备检查 ; 在 capture loop 中添加启动等待期和区分启动错误与运行时错误; 添加视频文件就绪状态检查函数; 优化错误处理逻辑,避免过早关闭;
This commit is contained in:
parent
8b5ac0aceb
commit
52a5463664
@ -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");
|
||||
|
||||
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user