From 1a286c7515fe3ecfb075c9763935d8ed0d50b410 Mon Sep 17 00:00:00 2001 From: George Tsiamasiotis Date: Wed, 25 Sep 2024 12:20:23 +0300 Subject: [PATCH] Try to factor out parts of individual camera management --- frigate/app.py | 131 ++++++++++--------------------- frigate/camera/__init__.py | 68 ---------------- frigate/camera/camera.py | 90 +++++++++++++++++++++ frigate/camera/metrics.py | 68 ++++++++++++++++ frigate/comms/dispatcher.py | 2 +- frigate/events/audio.py | 2 +- frigate/object_detection.py | 3 +- frigate/ptz/autotrack.py | 2 +- frigate/ptz/onvif.py | 2 +- frigate/stats/util.py | 2 +- frigate/track/norfair_tracker.py | 2 +- frigate/types.py | 2 +- frigate/video.py | 2 +- 13 files changed, 210 insertions(+), 166 deletions(-) create mode 100644 frigate/camera/camera.py create mode 100644 frigate/camera/metrics.py diff --git a/frigate/app.py b/frigate/app.py index 85267e01c..3d18a30a7 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -17,7 +17,7 @@ from playhouse.sqliteq import SqliteQueueDatabase import frigate.util as util from frigate.api.auth import hash_password from frigate.api.fastapi_app import create_fastapi_app -from frigate.camera import CameraMetrics, PTZMetrics +from frigate.camera.camera import Camera from frigate.comms.config_updater import ConfigPublisher from frigate.comms.dispatcher import Communicator, Dispatcher from frigate.comms.event_metadata_updater import ( @@ -68,9 +68,7 @@ from frigate.stats.util import stats_init from frigate.storage import StorageMaintainer from frigate.timeline import TimelineProcessor from frigate.util.builtin import empty_and_close_queue -from frigate.util.object import get_camera_regions_grid from frigate.version import VERSION -from frigate.video import capture_camera, track_camera from frigate.watchdog import FrigateWatchdog logger = logging.getLogger(__name__) @@ -82,13 +80,10 @@ class FrigateApp: self.stop_event: MpEvent = mp.Event() self.detection_queue: Queue = mp.Queue() self.detectors: dict[str, ObjectDetectProcess] = {} - self.detection_out_events: dict[str, MpEvent] = {} self.detection_shms: list[mp.shared_memory.SharedMemory] = [] self.log_queue: Queue = mp.Queue() - self.camera_metrics: dict[str, CameraMetrics] = {} - self.ptz_metrics: dict[str, PTZMetrics] = {} + self.cameras: dict[str, Camera] = {} self.processes: dict[str, int] = {} - self.region_grids: dict[str, list[list[dict[str, int]]]] = {} self.config = config def ensure_dirs(self) -> None: @@ -106,16 +101,6 @@ class FrigateApp: else: logger.debug(f"Skipping directory: {d}") - def init_camera_metrics(self) -> None: - # create camera_metrics - for camera_name in self.config.cameras.keys(): - self.camera_metrics[camera_name] = CameraMetrics() - self.ptz_metrics[camera_name] = PTZMetrics( - autotracker_enabled=self.config.cameras[ - camera_name - ].onvif.autotracking.enabled - ) - def init_queues(self) -> None: # Queue for cameras to push tracked objects to self.detected_frames_queue: Queue = mp.Queue( @@ -293,7 +278,10 @@ class FrigateApp: self.inter_zmq_proxy = ZmqProxy() def init_onvif(self) -> None: - self.onvif_controller = OnvifController(self.config, self.ptz_metrics) + self.onvif_controller = OnvifController( + self.config, + {name: camera.ptz_metrics for name, camera in self.cameras.items()}, + ) def init_dispatcher(self) -> None: comms: list[Communicator] = [] @@ -311,14 +299,12 @@ class FrigateApp: self.config, self.inter_config_updater, self.onvif_controller, - self.ptz_metrics, + {name: camera.ptz_metrics for name, camera in self.cameras.items()}, comms, ) def start_detectors(self) -> None: for name in self.config.cameras.keys(): - self.detection_out_events[name] = mp.Event() - try: largest_frame = max( [ @@ -348,7 +334,10 @@ class FrigateApp: self.detectors[name] = ObjectDetectProcess( name, self.detection_queue, - self.detection_out_events, + { + name: camera.detection_out_event + for name, camera in self.cameras.items() + }, detector_config, ) @@ -356,7 +345,7 @@ class FrigateApp: self.ptz_autotracker_thread = PtzAutoTrackerThread( self.config, self.onvif_controller, - self.ptz_metrics, + {name: camera.ptz_metrics for name, camera in self.cameras.items()}, self.dispatcher, self.stop_event, ) @@ -383,64 +372,27 @@ class FrigateApp: output_processor.start() logger.info(f"Output process started: {output_processor.pid}") - def init_historical_regions(self) -> None: + def init_cameras(self) -> None: + for name in self.config.cameras.keys(): + self.cameras[name] = Camera(name, self.config) + + def start_cameras(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(): - self.region_grids[camera.name] = get_camera_regions_grid( - camera.name, - camera.detect, - max(self.config.model.width, self.config.model.height), - ) + shm_frame_count = self.shm_frame_count() - def start_camera_processors(self) -> None: - for name, config in self.config.cameras.items(): - if not self.config.cameras[name].enabled: - logger.info(f"Camera processor not started for disabled camera {name}") - continue + for camera in self.cameras.values(): + camera.init_historical_regions() + camera.start_capture_process(shm_frame_count) + camera.start_process(self.detection_queue, self.detected_frames_queue) - camera_process = util.Process( - target=track_camera, - name=f"camera_processor:{name}", - args=( - name, - config, - self.config.model, - self.config.model.merged_labelmap, - self.detection_queue, - self.detection_out_events[name], - self.detected_frames_queue, - self.camera_metrics[name], - self.ptz_metrics[name], - self.region_grids[name], - ), - daemon=True, - ) - self.camera_metrics[name].process = camera_process - camera_process.start() - logger.info(f"Camera processor started for {name}: {camera_process.pid}") - - def start_camera_capture_processes(self) -> None: - for name, config in self.config.cameras.items(): - if not self.config.cameras[name].enabled: - logger.info(f"Capture process not started for disabled camera {name}") - continue - - capture_process = util.Process( - target=capture_camera, - name=f"camera_capture:{name}", - args=(name, config, self.shm_frame_count(), self.camera_metrics[name]), - ) - capture_process.daemon = True - self.camera_metrics[name].capture_process = capture_process - capture_process.start() - logger.info(f"Capture process started for {name}: {capture_process.pid}") - - def start_audio_processors(self) -> None: - self.audio_process = AudioProcessor(self.config, self.camera_metrics) + def start_audio_processor(self) -> None: + self.audio_process = AudioProcessor( + self.config, + {name: camera.camera_metrics for name, camera in self.cameras.items()}, + ) self.audio_process.start() self.processes["audio_detector"] = self.audio_process.pid or 0 @@ -474,7 +426,10 @@ class FrigateApp: self.stats_emitter = StatsEmitter( self.config, stats_init( - self.config, self.camera_metrics, self.detectors, self.processes + self.config, + {name: camera.camera_metrics for name, camera in self.cameras.items()}, + self.detectors, + self.processes, ), self.stop_event, ) @@ -561,7 +516,6 @@ class FrigateApp: self.config.install() # Start frigate services. - self.init_camera_metrics() self.init_queues() self.init_database() self.init_onvif() @@ -573,14 +527,13 @@ class FrigateApp: self.check_db_data_migrations() self.init_inter_process_communicator() self.init_dispatcher() + self.init_cameras() self.start_detectors() self.start_video_output_processor() self.start_ptz_autotracker() - self.init_historical_regions() self.start_detected_frames_processor() - self.start_camera_processors() - self.start_camera_capture_processes() - self.start_audio_processors() + self.start_cameras() + self.start_audio_processor() self.start_storage_maintainer() self.init_external_event_processor() self.start_stats_emitter() @@ -630,22 +583,22 @@ class FrigateApp: self.audio_process.join() # ensure the capture processes are done - for camera, metrics in self.camera_metrics.items(): - capture_process = metrics.capture_process + for name, camera in self.cameras.items(): + capture_process = camera.camera_metrics.capture_process if capture_process is not None: - logger.info(f"Waiting for capture process for {camera} to stop") + logger.info(f"Waiting for capture process for {name} to stop") capture_process.terminate() capture_process.join() # ensure the camera processors are done - for camera, metrics in self.camera_metrics.items(): - camera_process = metrics.process + for name, camera in self.cameras.items(): + camera_process = camera.camera_metrics.process if camera_process is not None: - logger.info(f"Waiting for process for {camera} to stop") + logger.info(f"Waiting for process for {name} to stop") camera_process.terminate() camera_process.join() - logger.info(f"Closing frame queue for {camera}") - empty_and_close_queue(metrics.frame_queue) + logger.info(f"Closing frame queue for {name}") + empty_and_close_queue(camera.camera_metrics.frame_queue) # ensure the detectors are done for detector in self.detectors.values(): diff --git a/frigate/camera/__init__.py b/frigate/camera/__init__.py index 456751c52..e69de29bb 100644 --- a/frigate/camera/__init__.py +++ b/frigate/camera/__init__.py @@ -1,68 +0,0 @@ -import multiprocessing as mp -from multiprocessing.sharedctypes import Synchronized -from multiprocessing.synchronize import Event -from typing import Optional - - -class CameraMetrics: - camera_fps: Synchronized - detection_fps: Synchronized - detection_frame: Synchronized - process_fps: Synchronized - skipped_fps: Synchronized - read_start: Synchronized - audio_rms: Synchronized - audio_dBFS: Synchronized - - frame_queue: mp.Queue - - process: Optional[mp.Process] - capture_process: Optional[mp.Process] - ffmpeg_pid: Synchronized - - def __init__(self): - self.camera_fps = mp.Value("d", 0) - self.detection_fps = mp.Value("d", 0) - self.detection_frame = mp.Value("d", 0) - self.process_fps = mp.Value("d", 0) - self.skipped_fps = mp.Value("d", 0) - self.read_start = mp.Value("d", 0) - self.audio_rms = mp.Value("d", 0) - self.audio_dBFS = mp.Value("d", 0) - - self.frame_queue = mp.Queue(maxsize=2) - - self.process = None - self.capture_process = None - self.ffmpeg_pid = mp.Value("i", 0) - - -class PTZMetrics: - autotracker_enabled: Synchronized - - start_time: Synchronized - stop_time: Synchronized - frame_time: Synchronized - zoom_level: Synchronized - max_zoom: Synchronized - min_zoom: Synchronized - - tracking_active: Event - motor_stopped: Event - reset: Event - - def __init__(self, *, autotracker_enabled: bool): - self.autotracker_enabled = mp.Value("i", autotracker_enabled) - - self.start_time = mp.Value("d", 0) - self.stop_time = mp.Value("d", 0) - self.frame_time = mp.Value("d", 0) - self.zoom_level = mp.Value("d", 0) - self.max_zoom = mp.Value("d", 0) - self.min_zoom = mp.Value("d", 0) - - self.tracking_active = mp.Event() - self.motor_stopped = mp.Event() - self.reset = mp.Event() - - self.motor_stopped.set() diff --git a/frigate/camera/camera.py b/frigate/camera/camera.py new file mode 100644 index 000000000..041d3d6b2 --- /dev/null +++ b/frigate/camera/camera.py @@ -0,0 +1,90 @@ +import logging +import multiprocessing as mp +from multiprocessing.synchronize import Event + +from frigate import util +from frigate.config import FrigateConfig +from frigate.util.object import get_camera_regions_grid +from frigate.video import capture_camera, track_camera + +from .metrics import CameraMetrics, PTZMetrics + +logger = logging.getLogger(__name__) + + +class Camera: + name: str + config: FrigateConfig + + detection_out_event: Event + region_grid: list[list[dict[str, int]]] + + camera_metrics: CameraMetrics + ptz_metrics: PTZMetrics + + def __init__(self, name: str, config: FrigateConfig): + self.name = name + self.config = config + + self.detection_out_event = mp.Event() + + self.camera_metrics = CameraMetrics() + self.ptz_metrics = PTZMetrics( + autotracker_enabled=self.config.cameras[ + self.name + ].onvif.autotracking.enabled + ) + + def start_process(self, detection_queue: mp.Queue, detected_frames_queue: mp.Queue): + if not self.config.cameras[self.name].enabled: + logger.info(f"Camera processor not started for disabled camera {self.name}") + return + + camera_process = util.Process( + target=track_camera, + name=f"camera_processor:{self.name}", + args=( + self.name, + self.config.cameras[self.name], + self.config.model, + self.config.model.merged_labelmap, + detection_queue, + self.detection_out_event, + detected_frames_queue, + self.camera_metrics, + self.ptz_metrics, + self.region_grid, + ), + daemon=True, + ) + self.camera_metrics.process = camera_process + camera_process.start() + logger.info(f"Camera processor started for {self.name}: {camera_process.pid}") + + def start_capture_process(self, shm_frame_count: int): + if not self.config.cameras[self.name].enabled: + logger.info(f"Capture process not started for disabled camera {self.name}") + return + + capture_process = util.Process( + target=capture_camera, + name=f"camera_capture:{self.name}", + args=( + self.name, + self.config.cameras[self.name], + shm_frame_count, + self.camera_metrics, + ), + ) + capture_process.daemon = True + self.camera_metrics.capture_process = capture_process + capture_process.start() + logger.info(f"Capture process started for {self.name}: {capture_process.pid}") + + def init_historical_regions(self) -> None: + # create or update region grids for each camera + self.region_grid = get_camera_regions_grid( + self.name, + self.config.cameras[self.name].detect, + max(self.config.model.width, self.config.model.height), + ) diff --git a/frigate/camera/metrics.py b/frigate/camera/metrics.py new file mode 100644 index 000000000..456751c52 --- /dev/null +++ b/frigate/camera/metrics.py @@ -0,0 +1,68 @@ +import multiprocessing as mp +from multiprocessing.sharedctypes import Synchronized +from multiprocessing.synchronize import Event +from typing import Optional + + +class CameraMetrics: + camera_fps: Synchronized + detection_fps: Synchronized + detection_frame: Synchronized + process_fps: Synchronized + skipped_fps: Synchronized + read_start: Synchronized + audio_rms: Synchronized + audio_dBFS: Synchronized + + frame_queue: mp.Queue + + process: Optional[mp.Process] + capture_process: Optional[mp.Process] + ffmpeg_pid: Synchronized + + def __init__(self): + self.camera_fps = mp.Value("d", 0) + self.detection_fps = mp.Value("d", 0) + self.detection_frame = mp.Value("d", 0) + self.process_fps = mp.Value("d", 0) + self.skipped_fps = mp.Value("d", 0) + self.read_start = mp.Value("d", 0) + self.audio_rms = mp.Value("d", 0) + self.audio_dBFS = mp.Value("d", 0) + + self.frame_queue = mp.Queue(maxsize=2) + + self.process = None + self.capture_process = None + self.ffmpeg_pid = mp.Value("i", 0) + + +class PTZMetrics: + autotracker_enabled: Synchronized + + start_time: Synchronized + stop_time: Synchronized + frame_time: Synchronized + zoom_level: Synchronized + max_zoom: Synchronized + min_zoom: Synchronized + + tracking_active: Event + motor_stopped: Event + reset: Event + + def __init__(self, *, autotracker_enabled: bool): + self.autotracker_enabled = mp.Value("i", autotracker_enabled) + + self.start_time = mp.Value("d", 0) + self.stop_time = mp.Value("d", 0) + self.frame_time = mp.Value("d", 0) + self.zoom_level = mp.Value("d", 0) + self.max_zoom = mp.Value("d", 0) + self.min_zoom = mp.Value("d", 0) + + self.tracking_active = mp.Event() + self.motor_stopped = mp.Event() + self.reset = mp.Event() + + self.motor_stopped.set() diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index a987f6a38..58b6c97e2 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -6,7 +6,7 @@ import logging from abc import ABC, abstractmethod from typing import Any, Callable, Optional -from frigate.camera import PTZMetrics +from frigate.camera.metrics import PTZMetrics from frigate.comms.config_updater import ConfigPublisher from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.const import ( diff --git a/frigate/events/audio.py b/frigate/events/audio.py index 298fafc78..00974ddd6 100644 --- a/frigate/events/audio.py +++ b/frigate/events/audio.py @@ -12,7 +12,7 @@ import numpy as np import requests import frigate.util as util -from frigate.camera import CameraMetrics +from frigate.camera.metrics import CameraMetrics from frigate.comms.config_updater import ConfigSubscriber from frigate.comms.detections_updater import DetectionPublisher, DetectionTypeEnum from frigate.comms.inter_process import InterProcessRequestor diff --git a/frigate/object_detection.py b/frigate/object_detection.py index eac019a7a..13df5ce78 100644 --- a/frigate/object_detection.py +++ b/frigate/object_detection.py @@ -6,6 +6,7 @@ import queue import signal import threading from abc import ABC, abstractmethod +from multiprocessing.synchronize import Event import numpy as np from setproctitle import setproctitle @@ -79,7 +80,7 @@ class LocalObjectDetector(ObjectDetector): def run_detector( name: str, detection_queue: mp.Queue, - out_events: dict[str, mp.Event], + out_events: dict[str, Event], avg_speed, start, detector_config, diff --git a/frigate/ptz/autotrack.py b/frigate/ptz/autotrack.py index 1dcc8405d..05912a850 100644 --- a/frigate/ptz/autotrack.py +++ b/frigate/ptz/autotrack.py @@ -18,7 +18,7 @@ from norfair.camera_motion import ( TranslationTransformationGetter, ) -from frigate.camera import PTZMetrics +from frigate.camera.metrics import PTZMetrics from frigate.comms.dispatcher import Dispatcher from frigate.config import CameraConfig, FrigateConfig, ZoomingModeEnum from frigate.const import ( diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index bbcdf5188..d3da746f0 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -10,7 +10,7 @@ from onvif import ONVIFCamera, ONVIFError from zeep.exceptions import Fault, TransportError from zeep.transports import Transport -from frigate.camera import PTZMetrics +from frigate.camera.metrics import PTZMetrics from frigate.config import FrigateConfig, ZoomingModeEnum from frigate.util.builtin import find_by_key diff --git a/frigate/stats/util.py b/frigate/stats/util.py index 2a0f251fc..d949ffee9 100644 --- a/frigate/stats/util.py +++ b/frigate/stats/util.py @@ -11,7 +11,7 @@ import psutil import requests from requests.exceptions import RequestException -from frigate.camera import CameraMetrics +from frigate.camera.metrics import CameraMetrics from frigate.config import FrigateConfig from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR from frigate.object_detection import ObjectDetectProcess diff --git a/frigate/track/norfair_tracker.py b/frigate/track/norfair_tracker.py index 99085be4d..4e67b5840 100644 --- a/frigate/track/norfair_tracker.py +++ b/frigate/track/norfair_tracker.py @@ -12,7 +12,7 @@ from norfair import ( ) from norfair.drawing.drawer import Drawer -from frigate.camera import PTZMetrics +from frigate.camera.metrics import PTZMetrics from frigate.config import CameraConfig from frigate.ptz.autotrack import PtzMotionEstimator from frigate.track import ObjectTracker diff --git a/frigate/types.py b/frigate/types.py index 21f55e502..407ce84b5 100644 --- a/frigate/types.py +++ b/frigate/types.py @@ -1,6 +1,6 @@ from typing import TypedDict -from frigate.camera import CameraMetrics +from frigate.camera.metrics import CameraMetrics from frigate.object_detection import ObjectDetectProcess diff --git a/frigate/video.py b/frigate/video.py index 33e71a454..4b397d8d3 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -11,7 +11,7 @@ import time import cv2 from setproctitle import setproctitle -from frigate.camera import CameraMetrics, PTZMetrics +from frigate.camera.metrics import CameraMetrics, PTZMetrics from frigate.comms.config_updater import ConfigSubscriber from frigate.comms.inter_process import InterProcessRequestor from frigate.config import CameraConfig, DetectConfig, ModelConfig