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 frame_interval = Duration::from_secs_f64(1.0 / fps as f64);
|
||||||
let mut interval = time::interval(frame_interval);
|
let mut interval = time::interval(frame_interval);
|
||||||
|
|
||||||
// Error tracking for recovery
|
// 🚀 PERFORMANCE FIX: Separate startup phase from runtime phase
|
||||||
let mut consecutive_errors = 0;
|
let mut startup_attempts = 0;
|
||||||
const ERROR_THRESHOLD: u32 = 10; // After 10 errors, try to reinitialize
|
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 {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
|
|
||||||
match stream.capture_frame() {
|
match stream.capture_frame() {
|
||||||
Ok(mat) => {
|
Ok(mat) => {
|
||||||
// Reset error counter on success
|
// 🎉 SUCCESS - Reset all error counters and exit startup phase
|
||||||
consecutive_errors = 0;
|
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
|
// Create a new frame with timestamp
|
||||||
let frame = Frame::new(mat, Utc::now(), frame_count);
|
let frame = Frame::new(mat, Utc::now(), frame_count);
|
||||||
@ -220,13 +262,6 @@ impl CameraController {
|
|||||||
if let Err(e) = frame_buffer.push(frame.clone()) {
|
if let Err(e) = frame_buffer.push(frame.clone()) {
|
||||||
error!("Failed to add frame to buffer: {}", e);
|
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
|
// Broadcast frame to listeners
|
||||||
if let Err(e) = frame_tx.send(frame) {
|
if let Err(e) = frame_tx.send(frame) {
|
||||||
@ -234,16 +269,40 @@ impl CameraController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to capture frame: {}", e);
|
// 🔥 DISTINGUISH between startup errors and runtime errors
|
||||||
consecutive_errors += 1;
|
if is_startup_phase {
|
||||||
|
startup_attempts += 1;
|
||||||
|
|
||||||
if consecutive_errors >= ERROR_THRESHOLD {
|
// Check if this is a "video not ready" error (expected during startup)
|
||||||
error!("Too many consecutive errors ({}), attempting to reinitialize camera stream", consecutive_errors);
|
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
|
// Try to recreate the stream - this is a simplified version
|
||||||
// In a real implementation, you might want to signal the main thread
|
// In a real implementation, you might want to signal the main thread
|
||||||
// to fully reinitialize the camera
|
// to fully reinitialize the camera
|
||||||
// todo? 为啥失败太多打开 0?
|
|
||||||
match OpenCVCamera::open("0") { // Note: simplified, should use original device
|
match OpenCVCamera::open("0") { // Note: simplified, should use original device
|
||||||
Ok(mut camera) => {
|
Ok(mut camera) => {
|
||||||
// Configure minimal settings
|
// Configure minimal settings
|
||||||
@ -255,7 +314,9 @@ impl CameraController {
|
|||||||
Ok(new_stream) => {
|
Ok(new_stream) => {
|
||||||
info!("Successfully reinitialized camera stream");
|
info!("Successfully reinitialized camera stream");
|
||||||
stream = new_stream;
|
stream = new_stream;
|
||||||
consecutive_errors = 0;
|
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 restart camera streaming: {}", e)
|
||||||
}
|
}
|
||||||
@ -264,11 +325,15 @@ impl CameraController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small delay to avoid tight error loop
|
// Shorter delay during runtime to maintain responsiveness
|
||||||
time::sleep(Duration::from_millis(100)).await;
|
time::sleep(Duration::from_millis(RUNTIME_RETRY_DELAY)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reach here, the capture loop has been terminated
|
||||||
|
warn!("🛑 Camera capture loop terminated");
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("Camera capture started");
|
info!("Camera capture started");
|
||||||
|
|||||||
@ -56,20 +56,77 @@ impl OpenCVCamera {
|
|||||||
let path_str = path.as_ref().to_str()
|
let path_str = path.as_ref().to_str()
|
||||||
.ok_or_else(|| anyhow!("Invalid path"))?;
|
.ok_or_else(|| anyhow!("Invalid path"))?;
|
||||||
|
|
||||||
|
info!("Opening video file: {}", path_str);
|
||||||
let mut capture = videoio::VideoCapture::from_file(path_str, videoio::CAP_ANY)?;
|
let mut capture = videoio::VideoCapture::from_file(path_str, videoio::CAP_ANY)?;
|
||||||
|
|
||||||
if !capture.is_opened()? {
|
if !capture.is_opened()? {
|
||||||
return Err(anyhow!("Failed to open video file: {}", path_str));
|
return Err(anyhow!("Failed to open video file: {}", path_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get video properties
|
// 🚀 PERFORMANCE FIX: Ensure video is actually ready before proceeding
|
||||||
let width = capture.get(videoio::CAP_PROP_FRAME_WIDTH)? as u32;
|
// On slow devices like Raspberry Pi, the video file might not be immediately ready
|
||||||
let height = capture.get(videoio::CAP_PROP_FRAME_HEIGHT)? as u32;
|
info!("Waiting for video file to be ready...");
|
||||||
let fps = capture.get(videoio::CAP_PROP_FPS)? as u32;
|
|
||||||
let total_frames = capture.get(videoio::CAP_PROP_FRAME_COUNT)? as u32;
|
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!(
|
info!(
|
||||||
"Opened video file: {} ({}x{} @ {} fps, {} frames)",
|
"✅ Opened video file: {} ({}x{} @ {} fps, {} frames)",
|
||||||
path_str, width, height, fps, total_frames
|
path_str, width, height, fps, total_frames
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -300,6 +357,7 @@ impl OpenCVCamera {
|
|||||||
pub struct OpenCVCaptureStream {
|
pub struct OpenCVCaptureStream {
|
||||||
capture: Arc<Mutex<videoio::VideoCapture>>,
|
capture: Arc<Mutex<videoio::VideoCapture>>,
|
||||||
loop_video: bool,
|
loop_video: bool,
|
||||||
|
is_video_ready: bool, // Track if video is confirmed ready
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenCVCaptureStream {
|
impl OpenCVCaptureStream {
|
||||||
@ -308,6 +366,44 @@ impl OpenCVCaptureStream {
|
|||||||
Self {
|
Self {
|
||||||
capture,
|
capture,
|
||||||
loop_video,
|
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,12 +433,26 @@ impl OpenCVCaptureStream {
|
|||||||
return Err(anyhow!("End of video reached"));
|
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)
|
Ok(frame)
|
||||||
} else {
|
} else {
|
||||||
|
// 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"))
|
Err(anyhow!("Failed to capture frame"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for OpenCVCaptureStream {
|
impl Drop for OpenCVCaptureStream {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user