refactor: simplify frame handling by removing stream type management and enhancing validation
All checks were successful
Build Worker Base and Application Images / check-base-changes (push) Successful in 7s
Build Worker Base and Application Images / build-base (push) Has been skipped
Build Worker Base and Application Images / build-docker (push) Successful in 2m55s
Build Worker Base and Application Images / deploy-stack (push) Successful in 12s

This commit is contained in:
Siwat Sirichai 2025-09-26 00:07:48 +07:00
parent dc1db635d0
commit 719d16ae4d
4 changed files with 51 additions and 182 deletions

View file

@ -37,7 +37,6 @@ class RTSPReader:
self.expected_fps = 6
# Frame processing parameters
self.frame_interval = 1.0 / self.expected_fps # ~167ms for 6fps
self.error_recovery_delay = 5.0 # Increased from 2.0 for stability
self.max_consecutive_errors = 30 # Increased from 10 to handle network jitter
self.stream_timeout = 30.0
@ -72,7 +71,6 @@ class RTSPReader:
frame_count = 0
last_log_time = time.time()
last_successful_frame_time = time.time()
last_frame_time = 0
while not self.stop_event.is_set():
try:
@ -90,12 +88,7 @@ class RTSPReader:
last_successful_frame_time = time.time()
continue
# Rate limiting for 6fps
current_time = time.time()
if current_time - last_frame_time < self.frame_interval:
time.sleep(0.01) # Small sleep to avoid busy waiting
continue
# Read frame immediately without rate limiting for minimum latency
ret, frame = self.cap.read()
if not ret or frame is None:
@ -118,15 +111,10 @@ class RTSPReader:
time.sleep(sleep_time)
continue
# Validate frame dimensions
if frame.shape[1] != self.expected_width or frame.shape[0] != self.expected_height:
logger.warning(f"Camera {self.camera_id}: Unexpected frame dimensions {frame.shape[1]}x{frame.shape[0]}")
# Try to resize if dimensions are wrong
if frame.shape[1] > 0 and frame.shape[0] > 0:
frame = cv2.resize(frame, (self.expected_width, self.expected_height))
else:
consecutive_errors += 1
continue
# Accept any valid frame dimensions - don't force specific resolution
if frame.shape[1] <= 0 or frame.shape[0] <= 0:
consecutive_errors += 1
continue
# Check for corrupted frames (all black, all white, excessive noise)
if self._is_frame_corrupted(frame):
@ -138,7 +126,6 @@ class RTSPReader:
consecutive_errors = 0
frame_count += 1
last_successful_frame_time = time.time()
last_frame_time = current_time
# Call frame callback
if self.frame_callback:
@ -148,6 +135,7 @@ class RTSPReader:
logger.error(f"Camera {self.camera_id}: Frame callback error: {e}")
# Log progress every 30 seconds
current_time = time.time()
if current_time - last_log_time >= 30:
logger.info(f"Camera {self.camera_id}: {frame_count} frames processed")
last_log_time = current_time
@ -261,14 +249,12 @@ class RTSPReader:
logger.error(f"Failed to open stream for camera {self.camera_id}")
return False
# Set capture properties for 1280x720@6fps
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.expected_width)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.expected_height)
self.cap.set(cv2.CAP_PROP_FPS, self.expected_fps)
# Don't force resolution/fps - let the stream determine its natural specs
# The camera will provide whatever resolution/fps it supports
# Set moderate buffer to handle network jitter while avoiding excessive latency
# Buffer of 3 frames provides resilience without major delay
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 3)
# Set minimal buffer for lowest latency - single frame buffer
# This ensures we always get the most recent frame
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
# Set FFMPEG options for better H.264 handling
self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'H264'))
@ -405,15 +391,10 @@ class HTTPSnapshotReader:
time.sleep(min(2.0, interval_seconds))
continue
# Validate image dimensions
if frame.shape[1] != self.expected_width or frame.shape[0] != self.expected_height:
logger.info(f"Camera {self.camera_id}: Snapshot dimensions {frame.shape[1]}x{frame.shape[0]} "
f"(expected {self.expected_width}x{self.expected_height})")
# Resize if needed (maintaining aspect ratio for high quality)
if frame.shape[1] > 0 and frame.shape[0] > 0:
# Only resize if significantly different
if abs(frame.shape[1] - self.expected_width) > 100:
frame = self._resize_maintain_aspect(frame, self.expected_width, self.expected_height)
# Accept any valid image dimensions - don't force specific resolution
if frame.shape[1] <= 0 or frame.shape[0] <= 0:
logger.warning(f"Camera {self.camera_id}: Invalid frame dimensions {frame.shape[1]}x{frame.shape[0]}")
continue
# Reset retry counter on successful fetch
retries = 0