refactor: enhance FFmpegRTSPReader to implement persistent file locking for PPM frame reading
All checks were successful
Build Worker Base and Application Images / check-base-changes (push) Successful in 9s
Build Worker Base and Application Images / build-base (push) Has been skipped
Build Worker Base and Application Images / build-docker (push) Successful in 3m8s
Build Worker Base and Application Images / deploy-stack (push) Successful in 14s

This commit is contained in:
Siwat Sirichai 2025-09-26 03:04:53 +07:00
parent fe0da18d0f
commit a12e3efa12

View file

@ -10,6 +10,7 @@ import requests
import numpy as np import numpy as np
import os import os
import subprocess import subprocess
import fcntl
from typing import Optional, Callable from typing import Optional, Callable
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
@ -94,7 +95,7 @@ class FFmpegRTSPReader:
self.temp_file = f"/tmp/claude/camera_{self.camera_id.replace(' ', '_')}.raw" self.temp_file = f"/tmp/claude/camera_{self.camera_id.replace(' ', '_')}.raw"
os.makedirs("/tmp/claude", exist_ok=True) os.makedirs("/tmp/claude", exist_ok=True)
# Use PPM format - uncompressed with header, supports -update 1 # Use PPM format with single file (will use file locking for concurrency)
self.temp_file = f"/tmp/claude/camera_{self.camera_id.replace(' ', '_')}.ppm" self.temp_file = f"/tmp/claude/camera_{self.camera_id.replace(' ', '_')}.ppm"
cmd = [ cmd = [
@ -176,10 +177,22 @@ class FFmpegRTSPReader:
if self.frame_ready_event.wait(timeout=restart_check_interval): if self.frame_ready_event.wait(timeout=restart_check_interval):
self.frame_ready_event.clear() self.frame_ready_event.clear()
# Read PPM frame (uncompressed with header) # Read PPM frame with persistent lock attempts until new inotify
try: try:
if os.path.exists(self.temp_file): if os.path.exists(self.temp_file):
# Read PPM with OpenCV (handles RGB->BGR conversion automatically) # Keep trying to acquire lock until new inotify event or success
max_attempts = 50 # ~500ms worth of attempts
for attempt in range(max_attempts):
# Check if new inotify event arrived (cancel current attempt)
if self.frame_ready_event.is_set():
break
try:
with open(self.temp_file, 'rb') as f:
# Try to acquire shared lock (non-blocking)
fcntl.flock(f.fileno(), fcntl.LOCK_SH | fcntl.LOCK_NB)
# Success! File is locked, safe to read
frame = cv2.imread(self.temp_file) frame = cv2.imread(self.temp_file)
if frame is not None and frame.shape == (self.height, self.width, 3): if frame is not None and frame.shape == (self.height, self.width, 3):
@ -192,15 +205,23 @@ class FFmpegRTSPReader:
# Log progress # Log progress
current_time = time.time() current_time = time.time()
if current_time - last_log_time >= 30: if current_time - last_log_time >= 30:
logger.info(f"Camera {self.camera_id}: {frame_count} PPM frames processed reactively") logger.info(f"Camera {self.camera_id}: {frame_count} PPM frames processed with persistent locking")
last_log_time = current_time last_log_time = current_time
else: # Invalid frame - just skip, no logging needed
logger.debug(f"Camera {self.camera_id}: Invalid PPM frame")
else:
logger.debug(f"Camera {self.camera_id}: PPM file not found yet")
except (IOError, OSError) as e: # Successfully processed frame
logger.debug(f"Camera {self.camera_id}: File read error: {e}") break
except (OSError, IOError):
# File is still locked, wait a bit and try again
time.sleep(0.01) # 10ms wait between attempts
continue
# If we get here, exhausted attempts or file not ready - just continue
except (IOError, OSError):
# File errors are routine, just continue
pass
except Exception as e: except Exception as e:
logger.error(f"Camera {self.camera_id}: Error in reactive frame reading: {e}") logger.error(f"Camera {self.camera_id}: Error in reactive frame reading: {e}")