Try to factor out parts of individual camera management

This commit is contained in:
George Tsiamasiotis 2024-09-25 12:20:23 +03:00
parent 9616d9a524
commit 1a286c7515
13 changed files with 210 additions and 166 deletions

View File

@ -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():

View File

@ -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()

90
frigate/camera/camera.py Normal file
View File

@ -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),
)

68
frigate/camera/metrics.py Normal file
View File

@ -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()

View File

@ -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 (

View File

@ -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

View File

@ -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,

View File

@ -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 (

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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