Move camera management fully to separate class

This commit is contained in:
Nicolas Mowen 2025-06-10 15:42:36 -06:00
parent 7085790dcd
commit fa6f531e88
3 changed files with 104 additions and 66 deletions

View File

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

View File

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

View File

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