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 import frigate.util as util
from frigate.api.auth import hash_password from frigate.api.auth import hash_password
from frigate.api.fastapi_app import create_fastapi_app 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.config_updater import ConfigPublisher
from frigate.comms.dispatcher import Communicator, Dispatcher from frigate.comms.dispatcher import Communicator, Dispatcher
from frigate.comms.event_metadata_updater import ( 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.storage import StorageMaintainer
from frigate.timeline import TimelineProcessor from frigate.timeline import TimelineProcessor
from frigate.util.builtin import empty_and_close_queue 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.version import VERSION
from frigate.video import capture_camera, track_camera
from frigate.watchdog import FrigateWatchdog from frigate.watchdog import FrigateWatchdog
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -82,13 +80,10 @@ class FrigateApp:
self.stop_event: MpEvent = mp.Event() self.stop_event: MpEvent = mp.Event()
self.detection_queue: Queue = mp.Queue() self.detection_queue: Queue = mp.Queue()
self.detectors: dict[str, ObjectDetectProcess] = {} self.detectors: dict[str, ObjectDetectProcess] = {}
self.detection_out_events: dict[str, MpEvent] = {}
self.detection_shms: list[mp.shared_memory.SharedMemory] = [] self.detection_shms: list[mp.shared_memory.SharedMemory] = []
self.log_queue: Queue = mp.Queue() self.log_queue: Queue = mp.Queue()
self.camera_metrics: dict[str, CameraMetrics] = {} self.cameras: dict[str, Camera] = {}
self.ptz_metrics: dict[str, PTZMetrics] = {}
self.processes: dict[str, int] = {} self.processes: dict[str, int] = {}
self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
self.config = config self.config = config
def ensure_dirs(self) -> None: def ensure_dirs(self) -> None:
@ -106,16 +101,6 @@ class FrigateApp:
else: else:
logger.debug(f"Skipping directory: {d}") 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: def init_queues(self) -> None:
# Queue for cameras to push tracked objects to # Queue for cameras to push tracked objects to
self.detected_frames_queue: Queue = mp.Queue( self.detected_frames_queue: Queue = mp.Queue(
@ -293,7 +278,10 @@ class FrigateApp:
self.inter_zmq_proxy = ZmqProxy() self.inter_zmq_proxy = ZmqProxy()
def init_onvif(self) -> None: 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: def init_dispatcher(self) -> None:
comms: list[Communicator] = [] comms: list[Communicator] = []
@ -311,14 +299,12 @@ class FrigateApp:
self.config, self.config,
self.inter_config_updater, self.inter_config_updater,
self.onvif_controller, self.onvif_controller,
self.ptz_metrics, {name: camera.ptz_metrics for name, camera in self.cameras.items()},
comms, comms,
) )
def start_detectors(self) -> None: def start_detectors(self) -> None:
for name in self.config.cameras.keys(): for name in self.config.cameras.keys():
self.detection_out_events[name] = mp.Event()
try: try:
largest_frame = max( largest_frame = max(
[ [
@ -348,7 +334,10 @@ class FrigateApp:
self.detectors[name] = ObjectDetectProcess( self.detectors[name] = ObjectDetectProcess(
name, name,
self.detection_queue, self.detection_queue,
self.detection_out_events, {
name: camera.detection_out_event
for name, camera in self.cameras.items()
},
detector_config, detector_config,
) )
@ -356,7 +345,7 @@ class FrigateApp:
self.ptz_autotracker_thread = PtzAutoTrackerThread( self.ptz_autotracker_thread = PtzAutoTrackerThread(
self.config, self.config,
self.onvif_controller, self.onvif_controller,
self.ptz_metrics, {name: camera.ptz_metrics for name, camera in self.cameras.items()},
self.dispatcher, self.dispatcher,
self.stop_event, self.stop_event,
) )
@ -383,64 +372,27 @@ class FrigateApp:
output_processor.start() output_processor.start()
logger.info(f"Output process started: {output_processor.pid}") 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 # delete region grids for removed or renamed cameras
cameras = list(self.config.cameras.keys()) cameras = list(self.config.cameras.keys())
Regions.delete().where(~(Regions.camera << cameras)).execute() Regions.delete().where(~(Regions.camera << cameras)).execute()
# create or update region grids for each camera shm_frame_count = self.shm_frame_count()
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),
)
def start_camera_processors(self) -> None: for camera in self.cameras.values():
for name, config in self.config.cameras.items(): camera.init_historical_regions()
if not self.config.cameras[name].enabled: camera.start_capture_process(shm_frame_count)
logger.info(f"Camera processor not started for disabled camera {name}") camera.start_process(self.detection_queue, self.detected_frames_queue)
continue
camera_process = util.Process( def start_audio_processor(self) -> None:
target=track_camera, self.audio_process = AudioProcessor(
name=f"camera_processor:{name}", self.config,
args=( {name: camera.camera_metrics for name, camera in self.cameras.items()},
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)
self.audio_process.start() self.audio_process.start()
self.processes["audio_detector"] = self.audio_process.pid or 0 self.processes["audio_detector"] = self.audio_process.pid or 0
@ -474,7 +426,10 @@ class FrigateApp:
self.stats_emitter = StatsEmitter( self.stats_emitter = StatsEmitter(
self.config, self.config,
stats_init( 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, self.stop_event,
) )
@ -561,7 +516,6 @@ class FrigateApp:
self.config.install() self.config.install()
# Start frigate services. # Start frigate services.
self.init_camera_metrics()
self.init_queues() self.init_queues()
self.init_database() self.init_database()
self.init_onvif() self.init_onvif()
@ -573,14 +527,13 @@ class FrigateApp:
self.check_db_data_migrations() self.check_db_data_migrations()
self.init_inter_process_communicator() self.init_inter_process_communicator()
self.init_dispatcher() self.init_dispatcher()
self.init_cameras()
self.start_detectors() self.start_detectors()
self.start_video_output_processor() self.start_video_output_processor()
self.start_ptz_autotracker() self.start_ptz_autotracker()
self.init_historical_regions()
self.start_detected_frames_processor() self.start_detected_frames_processor()
self.start_camera_processors() self.start_cameras()
self.start_camera_capture_processes() self.start_audio_processor()
self.start_audio_processors()
self.start_storage_maintainer() self.start_storage_maintainer()
self.init_external_event_processor() self.init_external_event_processor()
self.start_stats_emitter() self.start_stats_emitter()
@ -630,22 +583,22 @@ class FrigateApp:
self.audio_process.join() self.audio_process.join()
# ensure the capture processes are done # ensure the capture processes are done
for camera, metrics in self.camera_metrics.items(): for name, camera in self.cameras.items():
capture_process = metrics.capture_process capture_process = camera.camera_metrics.capture_process
if capture_process is not None: 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.terminate()
capture_process.join() capture_process.join()
# ensure the camera processors are done # ensure the camera processors are done
for camera, metrics in self.camera_metrics.items(): for name, camera in self.cameras.items():
camera_process = metrics.process camera_process = camera.camera_metrics.process
if camera_process is not None: 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.terminate()
camera_process.join() camera_process.join()
logger.info(f"Closing frame queue for {camera}") logger.info(f"Closing frame queue for {name}")
empty_and_close_queue(metrics.frame_queue) empty_and_close_queue(camera.camera_metrics.frame_queue)
# ensure the detectors are done # ensure the detectors are done
for detector in self.detectors.values(): 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 abc import ABC, abstractmethod
from typing import Any, Callable, Optional 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.comms.config_updater import ConfigPublisher
from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.config import BirdseyeModeEnum, FrigateConfig
from frigate.const import ( from frigate.const import (

View File

@ -12,7 +12,7 @@ import numpy as np
import requests import requests
import frigate.util as util 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.config_updater import ConfigSubscriber
from frigate.comms.detections_updater import DetectionPublisher, DetectionTypeEnum from frigate.comms.detections_updater import DetectionPublisher, DetectionTypeEnum
from frigate.comms.inter_process import InterProcessRequestor from frigate.comms.inter_process import InterProcessRequestor

View File

@ -6,6 +6,7 @@ import queue
import signal import signal
import threading import threading
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from multiprocessing.synchronize import Event
import numpy as np import numpy as np
from setproctitle import setproctitle from setproctitle import setproctitle
@ -79,7 +80,7 @@ class LocalObjectDetector(ObjectDetector):
def run_detector( def run_detector(
name: str, name: str,
detection_queue: mp.Queue, detection_queue: mp.Queue,
out_events: dict[str, mp.Event], out_events: dict[str, Event],
avg_speed, avg_speed,
start, start,
detector_config, detector_config,

View File

@ -18,7 +18,7 @@ from norfair.camera_motion import (
TranslationTransformationGetter, TranslationTransformationGetter,
) )
from frigate.camera import PTZMetrics from frigate.camera.metrics import PTZMetrics
from frigate.comms.dispatcher import Dispatcher from frigate.comms.dispatcher import Dispatcher
from frigate.config import CameraConfig, FrigateConfig, ZoomingModeEnum from frigate.config import CameraConfig, FrigateConfig, ZoomingModeEnum
from frigate.const import ( from frigate.const import (

View File

@ -10,7 +10,7 @@ from onvif import ONVIFCamera, ONVIFError
from zeep.exceptions import Fault, TransportError from zeep.exceptions import Fault, TransportError
from zeep.transports import Transport from zeep.transports import Transport
from frigate.camera import PTZMetrics from frigate.camera.metrics import PTZMetrics
from frigate.config import FrigateConfig, ZoomingModeEnum from frigate.config import FrigateConfig, ZoomingModeEnum
from frigate.util.builtin import find_by_key from frigate.util.builtin import find_by_key

View File

@ -11,7 +11,7 @@ import psutil
import requests import requests
from requests.exceptions import RequestException from requests.exceptions import RequestException
from frigate.camera import CameraMetrics from frigate.camera.metrics import CameraMetrics
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR
from frigate.object_detection import ObjectDetectProcess from frigate.object_detection import ObjectDetectProcess

View File

@ -12,7 +12,7 @@ from norfair import (
) )
from norfair.drawing.drawer import Drawer from norfair.drawing.drawer import Drawer
from frigate.camera import PTZMetrics from frigate.camera.metrics import PTZMetrics
from frigate.config import CameraConfig from frigate.config import CameraConfig
from frigate.ptz.autotrack import PtzMotionEstimator from frigate.ptz.autotrack import PtzMotionEstimator
from frigate.track import ObjectTracker from frigate.track import ObjectTracker

View File

@ -1,6 +1,6 @@
from typing import TypedDict from typing import TypedDict
from frigate.camera import CameraMetrics from frigate.camera.metrics import CameraMetrics
from frigate.object_detection import ObjectDetectProcess from frigate.object_detection import ObjectDetectProcess

View File

@ -11,7 +11,7 @@ import time
import cv2 import cv2
from setproctitle import setproctitle 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.config_updater import ConfigSubscriber
from frigate.comms.inter_process import InterProcessRequestor from frigate.comms.inter_process import InterProcessRequestor
from frigate.config import CameraConfig, DetectConfig, ModelConfig from frigate.config import CameraConfig, DetectConfig, ModelConfig