fix: camera api endpoint

This commit is contained in:
ziesorx 2025-09-26 13:05:58 +07:00
parent 83aaf95f59
commit 519e073f7f
5 changed files with 69 additions and 68 deletions

View file

@ -377,6 +377,8 @@ class WebSocketHandler:
camera_id = subscription_id.split(';')[-1]
model_id = payload['modelId']
logger.info(f"[SUBSCRIPTION_MAPPING] subscription_id='{subscription_id}' → camera_id='{camera_id}'")
# Get tracking integration for this model
tracking_integration = tracking_integrations.get(model_id)

View file

@ -46,13 +46,7 @@ class FrameBuffer:
frame_data = self._frames[camera_id]
# Check if frame is too old
age = time.time() - frame_data['timestamp']
if age > self.max_age_seconds:
logger.debug(f"Frame for camera {camera_id} is {age:.1f}s old, discarding")
del self._frames[camera_id]
return None
# Return frame regardless of age - frames persist until replaced
return frame_data['frame'].copy()
def get_frame_info(self, camera_id: str) -> Optional[Dict[str, Any]]:
@ -64,10 +58,7 @@ class FrameBuffer:
frame_data = self._frames[camera_id]
age = time.time() - frame_data['timestamp']
if age > self.max_age_seconds:
del self._frames[camera_id]
return None
# Return frame info regardless of age - frames persist until replaced
return {
'timestamp': frame_data['timestamp'],
'age': age,
@ -95,24 +86,10 @@ class FrameBuffer:
logger.debug(f"Cleared all frames ({count} cameras)")
def get_camera_list(self) -> list:
"""Get list of cameras with valid frames."""
"""Get list of cameras with frames - all frames persist until replaced."""
with self._lock:
current_time = time.time()
valid_cameras = []
expired_cameras = []
for camera_id, frame_data in self._frames.items():
age = current_time - frame_data['timestamp']
if age <= self.max_age_seconds:
valid_cameras.append(camera_id)
else:
expired_cameras.append(camera_id)
# Clean up expired frames
for camera_id in expired_cameras:
del self._frames[camera_id]
return valid_cameras
# Return all cameras that have frames - no age-based filtering
return list(self._frames.keys())
def get_stats(self) -> Dict[str, Any]:
"""Get buffer statistics."""
@ -120,8 +97,8 @@ class FrameBuffer:
current_time = time.time()
stats = {
'total_cameras': len(self._frames),
'valid_cameras': 0,
'expired_cameras': 0,
'recent_cameras': 0,
'stale_cameras': 0,
'total_memory_mb': 0,
'cameras': {}
}
@ -130,16 +107,17 @@ class FrameBuffer:
age = current_time - frame_data['timestamp']
size_mb = frame_data.get('size_mb', 0)
# All frames are valid/available, but categorize by freshness for monitoring
if age <= self.max_age_seconds:
stats['valid_cameras'] += 1
stats['recent_cameras'] += 1
else:
stats['expired_cameras'] += 1
stats['stale_cameras'] += 1
stats['total_memory_mb'] += size_mb
stats['cameras'][camera_id] = {
'age': age,
'valid': age <= self.max_age_seconds,
'recent': age <= self.max_age_seconds, # Recent but all frames available
'shape': frame_data['shape'],
'dtype': frame_data['dtype'],
'size_mb': size_mb

View file

@ -130,6 +130,7 @@ class StreamManager:
try:
if stream_config.rtsp_url:
# RTSP stream using FFmpeg subprocess with CUDA acceleration
logger.info(f"[STREAM_START] Starting FFmpeg RTSP stream for camera_id='{camera_id}' URL={stream_config.rtsp_url}")
reader = FFmpegRTSPReader(
camera_id=camera_id,
rtsp_url=stream_config.rtsp_url,
@ -138,10 +139,11 @@ class StreamManager:
reader.set_frame_callback(self._frame_callback)
reader.start()
self._streams[camera_id] = reader
logger.info(f"Started FFmpeg RTSP stream for camera {camera_id}")
logger.info(f"[STREAM_START] ✅ Started FFmpeg RTSP stream for camera_id='{camera_id}'")
elif stream_config.snapshot_url:
# HTTP snapshot stream
logger.info(f"[STREAM_START] Starting HTTP snapshot stream for camera_id='{camera_id}' URL={stream_config.snapshot_url}")
reader = HTTPSnapshotReader(
camera_id=camera_id,
snapshot_url=stream_config.snapshot_url,
@ -151,7 +153,7 @@ class StreamManager:
reader.set_frame_callback(self._frame_callback)
reader.start()
self._streams[camera_id] = reader
logger.info(f"Started HTTP snapshot stream for camera {camera_id}")
logger.info(f"[STREAM_START] ✅ Started HTTP snapshot stream for camera_id='{camera_id}'")
else:
logger.error(f"No valid URL provided for camera {camera_id}")
@ -169,8 +171,9 @@ class StreamManager:
try:
self._streams[camera_id].stop()
del self._streams[camera_id]
shared_cache_buffer.clear_camera(camera_id)
logger.info(f"Stopped stream for camera {camera_id}")
# DON'T clear frames - they should persist until replaced
# shared_cache_buffer.clear_camera(camera_id) # REMOVED - frames should persist
logger.info(f"Stopped stream for camera {camera_id} (frames preserved in buffer)")
except Exception as e:
logger.error(f"Error stopping stream for camera {camera_id}: {e}")
@ -179,6 +182,11 @@ class StreamManager:
try:
# Store frame in shared buffer
shared_cache_buffer.put_frame(camera_id, frame)
logger.info(f"[FRAME_CALLBACK] Stored frame for camera_id='{camera_id}' in shared_cache_buffer, shape={frame.shape}")
# Log current buffer state
available_cameras = shared_cache_buffer.frame_buffer.get_camera_list()
logger.info(f"[FRAME_CALLBACK] Buffer now contains {len(available_cameras)} cameras: {available_cameras}")
# Process tracking for subscriptions with tracking integration
self._process_tracking_for_camera(camera_id, frame)

View file

@ -101,14 +101,14 @@ class FFmpegRTSPReader:
# This ensures each file is complete when written
camera_id_safe = self.camera_id.replace(' ', '_')
self.frame_prefix = f"camera_{camera_id_safe}"
# Using strftime pattern with microseconds for unique filenames
self.frame_pattern = f"{self.frame_dir}/{self.frame_prefix}_%Y%m%d_%H%M%S_%f.ppm"
# Using strftime pattern with seconds for unique filenames (avoid %f which may not work)
self.frame_pattern = f"{self.frame_dir}/{self.frame_prefix}_%Y%m%d_%H%M%S.ppm"
cmd = [
'ffmpeg',
# DO NOT REMOVE
'-hwaccel', 'cuda',
'-hwaccel_device', '0',
# '-hwaccel', 'cuda',
# '-hwaccel_device', '0',
'-rtsp_transport', 'tcp',
'-i', self.rtsp_url,
'-f', 'image2',
@ -201,14 +201,17 @@ class FFmpegRTSPReader:
# Sort by filename (which includes timestamp) and get the latest
frame_files.sort()
latest_frame = frame_files[-1]
logger.debug(f"Camera {self.camera_id}: Found {len(frame_files)} frames, processing latest: {latest_frame}")
# Read the latest frame (it's complete since FFmpeg wrote it atomically)
frame = cv2.imread(latest_frame)
if frame is not None and frame.shape == (self.height, self.width, 3):
# Call frame callback directly
if frame is not None:
logger.debug(f"Camera {self.camera_id}: Successfully read frame {frame.shape} from {latest_frame}")
# Accept any frame dimensions initially for debugging
if self.frame_callback:
self.frame_callback(self.camera_id, frame)
logger.debug(f"Camera {self.camera_id}: Called frame callback")
frame_count += 1
@ -217,6 +220,8 @@ class FFmpegRTSPReader:
if current_time - last_log_time >= 30:
logger.info(f"Camera {self.camera_id}: {frame_count} frames processed")
last_log_time = current_time
else:
logger.warning(f"Camera {self.camera_id}: Failed to read frame from {latest_frame}")
# Clean up old frame files to prevent disk filling
# Keep only the latest 5 frames
@ -226,6 +231,8 @@ class FFmpegRTSPReader:
os.remove(old_file)
except:
pass
else:
logger.warning(f"Camera {self.camera_id}: No frame files found in {self.frame_dir} with pattern {self.frame_prefix}*.ppm")
except Exception as e:
logger.debug(f"Camera {self.camera_id}: Error reading frames: {e}")