From 73c33676811c1c3e15abc468faab6394fdded6fe Mon Sep 17 00:00:00 2001 From: Siwat Sirichai Date: Fri, 26 Sep 2025 02:51:30 +0700 Subject: [PATCH] refactor: update FFmpegRTSPReader to use JPG format for single frame updates and improve image quality --- core/streaming/readers.py | 42 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/core/streaming/readers.py b/core/streaming/readers.py index f9df506..b623c49 100644 --- a/core/streaming/readers.py +++ b/core/streaming/readers.py @@ -94,16 +94,19 @@ class FFmpegRTSPReader: self.temp_file = f"/tmp/claude/camera_{self.camera_id.replace(' ', '_')}.raw" os.makedirs("/tmp/claude", exist_ok=True) + # Change to JPG format which properly supports -update 1 + self.temp_file = f"/tmp/claude/camera_{self.camera_id.replace(' ', '_')}.jpg" + cmd = [ 'ffmpeg', '-hwaccel', 'cuda', '-hwaccel_device', '0', '-rtsp_transport', 'tcp', '-i', self.rtsp_url, - '-f', 'rawvideo', - '-pix_fmt', 'bgr24', + '-f', 'image2', + '-update', '1', # This actually works with image2 format + '-q:v', '2', # High quality JPEG '-an', # No audio - '-update', '1', # Update single frame in place '-y', # Overwrite output file self.temp_file ] @@ -173,32 +176,27 @@ class FFmpegRTSPReader: if self.frame_ready_event.wait(timeout=restart_check_interval): self.frame_ready_event.clear() - # Read current frame with concurrency safety + # Read JPEG frame with concurrency safety try: - # Try to read frame multiple times to handle race conditions - frame_data = None + # Try to read JPEG multiple times to handle race conditions + frame = None for attempt in range(3): try: - with open(self.temp_file, 'rb') as f: - frame_data = f.read(bytes_per_frame) + # Read and decode JPEG directly + frame = cv2.imread(self.temp_file) - # Validate we got a complete frame - if len(frame_data) == bytes_per_frame: - break - else: - logger.debug(f"Camera {self.camera_id}: Partial read {len(frame_data)}/{bytes_per_frame}, attempt {attempt+1}") - time.sleep(0.01) # Brief wait before retry + if frame is not None and frame.shape == (self.height, self.width, 3): + break + else: + logger.debug(f"Camera {self.camera_id}: Invalid frame shape or None, attempt {attempt+1}") + time.sleep(0.01) # Brief wait before retry except (IOError, OSError) as e: logger.debug(f"Camera {self.camera_id}: Read error on attempt {attempt+1}: {e}") time.sleep(0.01) - if frame_data and len(frame_data) == bytes_per_frame: - # Convert to numpy array - frame = np.frombuffer(frame_data, dtype=np.uint8) - frame = frame.reshape((self.height, self.width, 3)) - - # Call frame callback directly - trust the retry logic caught corruption + if frame is not None: + # Call frame callback directly if self.frame_callback: self.frame_callback(self.camera_id, frame) @@ -207,10 +205,10 @@ class FFmpegRTSPReader: # Log progress current_time = time.time() if current_time - last_log_time >= 30: - logger.info(f"Camera {self.camera_id}: {frame_count} frames processed reactively") + logger.info(f"Camera {self.camera_id}: {frame_count} JPEG frames processed reactively") last_log_time = current_time else: - logger.debug(f"Camera {self.camera_id}: Failed to read complete frame after retries") + logger.debug(f"Camera {self.camera_id}: Failed to read valid JPEG after retries") except (IOError, OSError) as e: logger.debug(f"Camera {self.camera_id}: File read error: {e}")