From fa6f531e8830c67208c2b752872a15c34442283a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 10 Jun 2025 15:42:36 -0600 Subject: [PATCH] Move camera management fully to separate class --- frigate/app.py | 62 +------------------- frigate/camera/maintainer.py | 107 +++++++++++++++++++++++++++++++++-- frigate/config/updater.py | 1 + 3 files changed, 104 insertions(+), 66 deletions(-) diff --git a/frigate/app.py b/frigate/app.py index 6f7932d33..b9d7cd288 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -36,7 +36,6 @@ from frigate.const import ( FACE_DIR, MODEL_CACHE_DIR, RECORD_DIR, - SHM_FRAMES_VAR, THUMB_DIR, ) from frigate.data_processing.types import DataProcessorMetrics @@ -70,8 +69,7 @@ from frigate.storage import StorageMaintainer from frigate.timeline import TimelineProcessor from frigate.track.object_processing import TrackedObjectProcessor from frigate.util.builtin import empty_and_close_queue -from frigate.util.image import SharedMemoryFrameManager, UntrackedSharedMemory -from frigate.util.object import get_camera_regions_grid +from frigate.util.image import UntrackedSharedMemory from frigate.version import VERSION from frigate.watchdog import FrigateWatchdog @@ -101,8 +99,6 @@ class FrigateApp: self.ptz_metrics: dict[str, PTZMetrics] = {} self.processes: dict[str, int] = {} self.embeddings: Optional[EmbeddingsContext] = None - self.region_grids: dict[str, list[list[dict[str, int]]]] = {} - self.frame_manager = SharedMemoryFrameManager() self.config = config def ensure_dirs(self) -> None: @@ -423,20 +419,6 @@ class FrigateApp: output_processor.start() logger.info(f"Output process started: {output_processor.pid}") - def init_historical_regions(self) -> None: - # delete region grids for removed or renamed cameras - cameras = list(self.config.cameras.keys()) - Regions.delete().where(~(Regions.camera << cameras)).execute() - - # create or update region grids for each camera - for camera in self.config.cameras.values(): - assert camera.name is not None - self.region_grids[camera.name] = get_camera_regions_grid( - camera.name, - camera.detect, - max(self.config.model.width, self.config.model.height), - ) - def start_camera_processor(self) -> None: self.camera_maintainer = CameraMaintainer(self.config, self.stop_event) self.camera_maintainer.start() @@ -499,45 +481,6 @@ class FrigateApp: self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event) self.frigate_watchdog.start() - def shm_frame_count(self) -> int: - total_shm = round(shutil.disk_usage("/dev/shm").total / pow(2, 20), 1) - - # required for log files + nginx cache - min_req_shm = 40 + 10 - - if self.config.birdseye.restream: - min_req_shm += 8 - - available_shm = total_shm - min_req_shm - cam_total_frame_size = 0.0 - - for camera in self.config.cameras.values(): - if camera.enabled and camera.detect.width and camera.detect.height: - cam_total_frame_size += round( - (camera.detect.width * camera.detect.height * 1.5 + 270480) - / 1048576, - 1, - ) - - if cam_total_frame_size == 0.0: - return 0 - - shm_frame_count = min( - int(os.environ.get(SHM_FRAMES_VAR, "50")), - int(available_shm / (cam_total_frame_size)), - ) - - logger.debug( - f"Calculated total camera size {available_shm} / {cam_total_frame_size} :: {shm_frame_count} frames for each camera in SHM" - ) - - if shm_frame_count < 20: - logger.warning( - f"The current SHM size of {total_shm}MB is too small, recommend increasing it to at least {round(min_req_shm + cam_total_frame_size * 20)}MB." - ) - - return shm_frame_count - def init_auth(self) -> None: if self.config.auth.enabled: if User.select().count() == 0: @@ -604,10 +547,8 @@ class FrigateApp: self.init_embeddings_client() self.start_video_output_processor() self.start_ptz_autotracker() - self.init_historical_regions() self.start_detected_frames_processor() self.start_camera_processor() - self.start_camera_capture_processes() self.start_audio_processor() self.start_storage_maintainer() self.start_stats_emitter() @@ -706,7 +647,6 @@ class FrigateApp: self.event_metadata_updater.stop() self.inter_zmq_proxy.stop() - self.frame_manager.cleanup() while len(self.detection_shms) > 0: shm = self.detection_shms.pop() shm.close() diff --git a/frigate/camera/maintainer.py b/frigate/camera/maintainer.py index 739422c1b..1a99bdb22 100644 --- a/frigate/camera/maintainer.py +++ b/frigate/camera/maintainer.py @@ -1,23 +1,107 @@ """Create and maintain camera processes / management.""" import logging +import os +import shutil import threading +from multiprocessing import Queue from multiprocessing.synchronize import Event as MpEvent +from frigate.camera import CameraMetrics, PTZMetrics from frigate.config import FrigateConfig +from frigate.config.updater import GlobalConfigUpdateEnum, GlobalConfigUpdateSubscriber +from frigate.const import SHM_FRAMES_VAR +from frigate.models import Regions from frigate.util import Process as FrigateProcess from frigate.util.builtin import empty_and_close_queue +from frigate.util.image import SharedMemoryFrameManager +from frigate.util.object import get_camera_regions_grid from frigate.video import capture_camera, track_camera logger = logging.getLogger(__name__) - class CameraMaintainer(threading.Thread): - def __init__(self, config: FrigateConfig, stop_event: MpEvent): + def __init__( + self, + config: FrigateConfig, + detection_queue: Queue, + detection_out_events: dict[str, MpEvent], + detected_frames_queue: Queue, + camera_metrics: dict[str, CameraMetrics], + ptz_metrics: dict[str, PTZMetrics], + stop_event: MpEvent, + ): super().__init__(name="camera_processor") self.config = config + self.detection_queue = detection_queue + self.detection_out_events = detection_out_events + self.detected_frames_queue = detected_frames_queue self.stop_event = stop_event + self.camera_metrics = camera_metrics + self.ptz_metrics = ptz_metrics + self.frame_manager = SharedMemoryFrameManager() + self.region_grids: dict[str, list[list[dict[str, int]]]] = {} + self.update_subscriber = GlobalConfigUpdateSubscriber( + [ + GlobalConfigUpdateEnum.add_camera, + GlobalConfigUpdateEnum.debug_camera, + GlobalConfigUpdateEnum.remove_camera, + ] + ) + + def __init_historical_regions(self) -> None: + # delete region grids for removed or renamed cameras + cameras = list(self.config.cameras.keys()) + Regions.delete().where(~(Regions.camera << cameras)).execute() + + # create or update region grids for each camera + for camera in self.config.cameras.values(): + assert camera.name is not None + self.region_grids[camera.name] = get_camera_regions_grid( + camera.name, + camera.detect, + max(self.config.model.width, self.config.model.height), + ) + + def __calculate_shm_frame_count(self) -> int: + total_shm = round(shutil.disk_usage("/dev/shm").total / pow(2, 20), 1) + + # required for log files + nginx cache + min_req_shm = 40 + 10 + + if self.config.birdseye.restream: + min_req_shm += 8 + + available_shm = total_shm - min_req_shm + cam_total_frame_size = 0.0 + + for camera in self.config.cameras.values(): + if camera.enabled and camera.detect.width and camera.detect.height: + cam_total_frame_size += round( + (camera.detect.width * camera.detect.height * 1.5 + 270480) + / 1048576, + 1, + ) + + if cam_total_frame_size == 0.0: + return 0 + + shm_frame_count = min( + int(os.environ.get(SHM_FRAMES_VAR, "50")), + int(available_shm / (cam_total_frame_size)), + ) + + logger.debug( + f"Calculated total camera size {available_shm} / {cam_total_frame_size} :: {shm_frame_count} frames for each camera in SHM" + ) + + if shm_frame_count < 20: + logger.warning( + f"The current SHM size of {total_shm}MB is too small, recommend increasing it to at least {round(min_req_shm + cam_total_frame_size * 20)}MB." + ) + + return shm_frame_count def __start_camera_processors(self) -> None: for name, config in self.config.cameras.items(): @@ -47,7 +131,7 @@ class CameraMaintainer(threading.Thread): logger.info(f"Camera processor started for {name}: {camera_process.pid}") def __start_camera_capture(self) -> None: - shm_frame_count = self.shm_frame_count() + shm_frame_count = self.__calculate_shm_frame_count() for name, config in self.config.cameras.items(): if not self.config.cameras[name].enabled_in_config: @@ -70,11 +154,22 @@ class CameraMaintainer(threading.Thread): logger.info(f"Capture process started for {name}: {capture_process.pid}") def run(self): + self.__init_historical_regions() + # start camera processes self.__start_camera_processors() + self.__start_camera_capture() - while not self.stop_event.is_set(): - pass + while not self.stop_event.wait(1): + updates = self.update_subscriber.check_for_updates() + + for update_type, update_payload in updates: + if update_type == GlobalConfigUpdateEnum.add_camera: + pass + elif update_type == GlobalConfigUpdateEnum.debug_camera: + pass + elif update_type == GlobalConfigUpdateEnum.remove_camera: + pass # ensure the capture processes are done for camera, metrics in self.camera_metrics.items(): @@ -93,3 +188,5 @@ class CameraMaintainer(threading.Thread): camera_process.join() logger.info(f"Closing frame queue for {camera}") empty_and_close_queue(metrics.frame_queue) + + self.frame_manager.cleanup() diff --git a/frigate/config/updater.py b/frigate/config/updater.py index b7ed26114..68e1d1e17 100644 --- a/frigate/config/updater.py +++ b/frigate/config/updater.py @@ -11,6 +11,7 @@ class GlobalConfigUpdateEnum(str, Enum): """Supported global config update types.""" add_camera = "add_camera" + debug_camera = "debug_camera" remove_camera = "remove_camera"