Fix birdseye crash when dynamically adding a camera (#18821)

This commit is contained in:
Josh Hawkins 2025-06-21 15:38:34 -05:00 committed by GitHub
parent 20dac9d05e
commit 55c6008ff0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 28 deletions

View File

@ -423,6 +423,7 @@ class FrigateApp:
self.camera_metrics, self.camera_metrics,
self.ptz_metrics, self.ptz_metrics,
self.stop_event, self.stop_event,
self.metrics_manager,
) )
self.camera_maintainer.start() self.camera_maintainer.start()

View File

@ -6,7 +6,7 @@ import os
import shutil import shutil
import threading import threading
from multiprocessing import Queue from multiprocessing import Queue
from multiprocessing.managers import DictProxy from multiprocessing.managers import DictProxy, SyncManager
from multiprocessing.synchronize import Event as MpEvent from multiprocessing.synchronize import Event as MpEvent
from frigate.camera import CameraMetrics, PTZMetrics from frigate.camera import CameraMetrics, PTZMetrics
@ -35,6 +35,7 @@ class CameraMaintainer(threading.Thread):
camera_metrics: DictProxy, camera_metrics: DictProxy,
ptz_metrics: dict[str, PTZMetrics], ptz_metrics: dict[str, PTZMetrics],
stop_event: MpEvent, stop_event: MpEvent,
metrics_manager: SyncManager,
): ):
super().__init__(name="camera_processor") super().__init__(name="camera_processor")
self.config = config self.config = config
@ -56,6 +57,7 @@ class CameraMaintainer(threading.Thread):
self.shm_count = self.__calculate_shm_frame_count() self.shm_count = self.__calculate_shm_frame_count()
self.camera_processes: dict[str, mp.Process] = {} self.camera_processes: dict[str, mp.Process] = {}
self.capture_processes: dict[str, mp.Process] = {} self.capture_processes: dict[str, mp.Process] = {}
self.metrics_manager = metrics_manager
def __init_historical_regions(self) -> None: def __init_historical_regions(self) -> None:
# delete region grids for removed or renamed cameras # delete region grids for removed or renamed cameras
@ -128,7 +130,7 @@ class CameraMaintainer(threading.Thread):
return return
if runtime: if runtime:
self.camera_metrics[name] = CameraMetrics() self.camera_metrics[name] = CameraMetrics(self.metrics_manager)
self.ptz_metrics[name] = PTZMetrics(autotracker_enabled=False) self.ptz_metrics[name] = PTZMetrics(autotracker_enabled=False)
self.region_grids[name] = get_camera_regions_grid( self.region_grids[name] = get_camera_regions_grid(
name, name,

View File

@ -319,7 +319,16 @@ class BirdsEyeFrameManager:
self.frame[:] = self.blank_frame self.frame[:] = self.blank_frame
self.cameras = {} self.cameras = {}
for camera, settings in self.config.cameras.items(): for camera in self.config.cameras.keys():
self.add_camera(camera)
self.camera_layout = []
self.active_cameras = set()
self.last_output_time = 0.0
def add_camera(self, cam: str):
"""Add a camera to self.cameras with the correct structure."""
settings = self.config.cameras[cam]
# precalculate the coordinates for all the channels # precalculate the coordinates for all the channels
y, u1, u2, v1, v2 = get_yuv_crop( y, u1, u2, v1, v2 = get_yuv_crop(
settings.frame_shape_yuv, settings.frame_shape_yuv,
@ -330,8 +339,11 @@ class BirdsEyeFrameManager:
settings.frame_shape[0], settings.frame_shape[0],
), ),
) )
self.cameras[camera] = { self.cameras[cam] = {
"dimensions": [settings.detect.width, settings.detect.height], "dimensions": [
settings.detect.width,
settings.detect.height,
],
"last_active_frame": 0.0, "last_active_frame": 0.0,
"current_frame": 0.0, "current_frame": 0.0,
"layout_frame": 0.0, "layout_frame": 0.0,
@ -344,9 +356,10 @@ class BirdsEyeFrameManager:
}, },
} }
self.camera_layout = [] def remove_camera(self, cam: str):
self.active_cameras = set() """Remove a camera from self.cameras."""
self.last_output_time = 0.0 if cam in self.cameras:
del self.cameras[cam]
def clear_frame(self): def clear_frame(self):
logger.debug("Clearing the birdseye frame") logger.debug("Clearing the birdseye frame")
@ -774,7 +787,7 @@ class Birdseye:
self.broadcaster = BroadcastThread( self.broadcaster = BroadcastThread(
"birdseye", self.converter, websocket_server, stop_event "birdseye", self.converter, websocket_server, stop_event
) )
self.birdseye_manager = BirdsEyeFrameManager(config, stop_event) self.birdseye_manager = BirdsEyeFrameManager(self.config, stop_event)
self.frame_manager = SharedMemoryFrameManager() self.frame_manager = SharedMemoryFrameManager()
self.stop_event = stop_event self.stop_event = stop_event
self.requestor = InterProcessRequestor() self.requestor = InterProcessRequestor()
@ -804,6 +817,16 @@ class Birdseye:
self.birdseye_manager.clear_frame() self.birdseye_manager.clear_frame()
self.__send_new_frame() self.__send_new_frame()
def add_camera(self, camera: str) -> None:
"""Add a camera to the birdseye manager."""
self.birdseye_manager.add_camera(camera)
logger.debug(f"Added camera {camera} to birdseye")
def remove_camera(self, camera: str) -> None:
"""Remove a camera from the birdseye manager."""
self.birdseye_manager.remove_camera(camera)
logger.debug(f"Removed camera {camera} from birdseye")
def write_data( def write_data(
self, self,
camera: str, camera: str,

View File

@ -133,7 +133,7 @@ class OutputProcess(FrigateProcess):
# check if there is an updated config # check if there is an updated config
updates = config_subscriber.check_for_updates() updates = config_subscriber.check_for_updates()
if "add" in updates: if CameraConfigUpdateEnum.add in updates:
for camera in updates["add"]: for camera in updates["add"]:
jsmpeg_cameras[camera] = JsmpegCamera( jsmpeg_cameras[camera] = JsmpegCamera(
cam_config, self.stop_event, websocket_server cam_config, self.stop_event, websocket_server
@ -141,6 +141,12 @@ class OutputProcess(FrigateProcess):
preview_recorders[camera] = PreviewRecorder(cam_config) preview_recorders[camera] = PreviewRecorder(cam_config)
preview_write_times[camera] = 0 preview_write_times[camera] = 0
if (
self.config.birdseye.enabled
and self.config.cameras[camera].birdseye.enabled
):
birdseye.add_camera(camera)
(topic, data) = detection_subscriber.check_for_update(timeout=1) (topic, data) = detection_subscriber.check_for_update(timeout=1)
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()