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, FACE_DIR,
MODEL_CACHE_DIR, MODEL_CACHE_DIR,
RECORD_DIR, RECORD_DIR,
SHM_FRAMES_VAR,
THUMB_DIR, THUMB_DIR,
) )
from frigate.data_processing.types import DataProcessorMetrics from frigate.data_processing.types import DataProcessorMetrics
@ -70,8 +69,7 @@ from frigate.storage import StorageMaintainer
from frigate.timeline import TimelineProcessor from frigate.timeline import TimelineProcessor
from frigate.track.object_processing import TrackedObjectProcessor from frigate.track.object_processing import TrackedObjectProcessor
from frigate.util.builtin import empty_and_close_queue from frigate.util.builtin import empty_and_close_queue
from frigate.util.image import SharedMemoryFrameManager, UntrackedSharedMemory from frigate.util.image import UntrackedSharedMemory
from frigate.util.object import get_camera_regions_grid
from frigate.version import VERSION from frigate.version import VERSION
from frigate.watchdog import FrigateWatchdog from frigate.watchdog import FrigateWatchdog
@ -101,8 +99,6 @@ class FrigateApp:
self.ptz_metrics: dict[str, PTZMetrics] = {} self.ptz_metrics: dict[str, PTZMetrics] = {}
self.processes: dict[str, int] = {} self.processes: dict[str, int] = {}
self.embeddings: Optional[EmbeddingsContext] = None self.embeddings: Optional[EmbeddingsContext] = None
self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
self.frame_manager = SharedMemoryFrameManager()
self.config = config self.config = config
def ensure_dirs(self) -> None: def ensure_dirs(self) -> None:
@ -423,20 +419,6 @@ 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:
# 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: def start_camera_processor(self) -> None:
self.camera_maintainer = CameraMaintainer(self.config, self.stop_event) self.camera_maintainer = CameraMaintainer(self.config, self.stop_event)
self.camera_maintainer.start() self.camera_maintainer.start()
@ -499,45 +481,6 @@ class FrigateApp:
self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event) self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event)
self.frigate_watchdog.start() 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: def init_auth(self) -> None:
if self.config.auth.enabled: if self.config.auth.enabled:
if User.select().count() == 0: if User.select().count() == 0:
@ -604,10 +547,8 @@ class FrigateApp:
self.init_embeddings_client() self.init_embeddings_client()
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_processor() self.start_camera_processor()
self.start_camera_capture_processes()
self.start_audio_processor() self.start_audio_processor()
self.start_storage_maintainer() self.start_storage_maintainer()
self.start_stats_emitter() self.start_stats_emitter()
@ -706,7 +647,6 @@ class FrigateApp:
self.event_metadata_updater.stop() self.event_metadata_updater.stop()
self.inter_zmq_proxy.stop() self.inter_zmq_proxy.stop()
self.frame_manager.cleanup()
while len(self.detection_shms) > 0: while len(self.detection_shms) > 0:
shm = self.detection_shms.pop() shm = self.detection_shms.pop()
shm.close() shm.close()

View File

@ -1,23 +1,107 @@
"""Create and maintain camera processes / management.""" """Create and maintain camera processes / management."""
import logging import logging
import os
import shutil
import threading import threading
from multiprocessing import Queue
from multiprocessing.synchronize import Event as MpEvent from multiprocessing.synchronize import Event as MpEvent
from frigate.camera import CameraMetrics, PTZMetrics
from frigate.config import FrigateConfig 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 import Process as FrigateProcess
from frigate.util.builtin import empty_and_close_queue 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 from frigate.video import capture_camera, track_camera
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class CameraMaintainer(threading.Thread): 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") super().__init__(name="camera_processor")
self.config = config 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.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: def __start_camera_processors(self) -> None:
for name, config in self.config.cameras.items(): 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}") logger.info(f"Camera processor started for {name}: {camera_process.pid}")
def __start_camera_capture(self) -> None: 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(): for name, config in self.config.cameras.items():
if not self.config.cameras[name].enabled_in_config: 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}") logger.info(f"Capture process started for {name}: {capture_process.pid}")
def run(self): def run(self):
self.__init_historical_regions()
# start camera processes # start camera processes
self.__start_camera_processors() self.__start_camera_processors()
self.__start_camera_capture()
while not self.stop_event.is_set(): while not self.stop_event.wait(1):
pass 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 # ensure the capture processes are done
for camera, metrics in self.camera_metrics.items(): for camera, metrics in self.camera_metrics.items():
@ -93,3 +188,5 @@ class CameraMaintainer(threading.Thread):
camera_process.join() camera_process.join()
logger.info(f"Closing frame queue for {camera}") logger.info(f"Closing frame queue for {camera}")
empty_and_close_queue(metrics.frame_queue) 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.""" """Supported global config update types."""
add_camera = "add_camera" add_camera = "add_camera"
debug_camera = "debug_camera"
remove_camera = "remove_camera" remove_camera = "remove_camera"