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.ptz_metrics,
self.stop_event,
self.metrics_manager,
)
self.camera_maintainer.start()

View File

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

View File

@ -319,35 +319,48 @@ class BirdsEyeFrameManager:
self.frame[:] = self.blank_frame
self.cameras = {}
for camera, settings in self.config.cameras.items():
# precalculate the coordinates for all the channels
y, u1, u2, v1, v2 = get_yuv_crop(
settings.frame_shape_yuv,
(
0,
0,
settings.frame_shape[1],
settings.frame_shape[0],
),
)
self.cameras[camera] = {
"dimensions": [settings.detect.width, settings.detect.height],
"last_active_frame": 0.0,
"current_frame": 0.0,
"layout_frame": 0.0,
"channel_dims": {
"y": y,
"u1": u1,
"u2": u2,
"v1": v1,
"v2": v2,
},
}
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
y, u1, u2, v1, v2 = get_yuv_crop(
settings.frame_shape_yuv,
(
0,
0,
settings.frame_shape[1],
settings.frame_shape[0],
),
)
self.cameras[cam] = {
"dimensions": [
settings.detect.width,
settings.detect.height,
],
"last_active_frame": 0.0,
"current_frame": 0.0,
"layout_frame": 0.0,
"channel_dims": {
"y": y,
"u1": u1,
"u2": u2,
"v1": v1,
"v2": v2,
},
}
def remove_camera(self, cam: str):
"""Remove a camera from self.cameras."""
if cam in self.cameras:
del self.cameras[cam]
def clear_frame(self):
logger.debug("Clearing the birdseye frame")
self.frame[:] = self.blank_frame
@ -774,7 +787,7 @@ class Birdseye:
self.broadcaster = BroadcastThread(
"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.stop_event = stop_event
self.requestor = InterProcessRequestor()
@ -804,6 +817,16 @@ class Birdseye:
self.birdseye_manager.clear_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(
self,
camera: str,

View File

@ -133,7 +133,7 @@ class OutputProcess(FrigateProcess):
# check if there is an updated config
updates = config_subscriber.check_for_updates()
if "add" in updates:
if CameraConfigUpdateEnum.add in updates:
for camera in updates["add"]:
jsmpeg_cameras[camera] = JsmpegCamera(
cam_config, self.stop_event, websocket_server
@ -141,6 +141,12 @@ class OutputProcess(FrigateProcess):
preview_recorders[camera] = PreviewRecorder(cam_config)
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)
now = datetime.datetime.now().timestamp()