Process subclass for output_frames

This commit is contained in:
George Tsiamasiotis 2024-10-02 12:14:14 +03:00
parent 09256a4cc8
commit 3badc757cc
2 changed files with 139 additions and 153 deletions

View File

@ -13,7 +13,6 @@ from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase from playhouse.sqliteq import SqliteQueueDatabase
from frigate import 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.camera import Camera from frigate.camera.camera import Camera
@ -55,7 +54,7 @@ from frigate.models import (
) )
from frigate.object_detection import ObjectDetectProcess from frigate.object_detection import ObjectDetectProcess
from frigate.object_processing import TrackedObjectProcessor from frigate.object_processing import TrackedObjectProcessor
from frigate.output.output import output_frames from frigate.output.output import OutputProcessor
from frigate.ptz.autotrack import PtzAutoTrackerThread from frigate.ptz.autotrack import PtzAutoTrackerThread
from frigate.ptz.onvif import OnvifController from frigate.ptz.onvif import OnvifController
from frigate.record.cleanup import RecordingCleanup from frigate.record.cleanup import RecordingCleanup
@ -334,15 +333,9 @@ class FrigateApp:
self.detected_frames_processor.start() self.detected_frames_processor.start()
def start_video_output_processor(self) -> None: def start_video_output_processor(self) -> None:
output_processor = util.Process( self.output_processor = OutputProcessor(self.config)
target=output_frames, self.output_processor.start()
name="output_processor", logger.info(f"Output process started: {self.output_processor.pid}")
args=(self.config,),
daemon=True,
)
self.output_processor = output_processor
output_processor.start()
logger.info(f"Output process started: {output_processor.pid}")
def init_cameras(self) -> None: def init_cameras(self) -> None:
for name in self.config.cameras.keys(): for name in self.config.cameras.keys():

View File

@ -1,15 +1,11 @@
"""Handle outputting raw frigate frames""" """Handle outputting raw frigate frames"""
import logging
import multiprocessing as mp
import os import os
import shutil import shutil
import signal
import threading import threading
from typing import Optional from typing import Optional
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
from setproctitle import setproctitle
from ws4py.server.wsgirefserver import ( from ws4py.server.wsgirefserver import (
WebSocketWSGIHandler, WebSocketWSGIHandler,
WebSocketWSGIRequestHandler, WebSocketWSGIRequestHandler,
@ -17,6 +13,7 @@ from ws4py.server.wsgirefserver import (
) )
from ws4py.server.wsgiutils import WebSocketWSGIApplication from ws4py.server.wsgiutils import WebSocketWSGIApplication
from frigate import util
from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum
from frigate.comms.ws import WebSocket from frigate.comms.ws import WebSocket
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
@ -26,23 +23,13 @@ from frigate.output.camera import JsmpegCamera
from frigate.output.preview import PreviewRecorder from frigate.output.preview import PreviewRecorder
from frigate.util.image import SharedMemoryFrameManager from frigate.util.image import SharedMemoryFrameManager
logger = logging.getLogger(__name__)
class OutputProcessor(util.Process):
def __init__(self, config: FrigateConfig):
super().__init__(name="frigate.output", daemon=True)
self.config = config
def output_frames( def run(self):
config: FrigateConfig,
):
threading.current_thread().name = "output"
setproctitle("frigate.output")
stop_event = mp.Event()
def receiveSignal(signalNumber, frame):
stop_event.set()
signal.signal(signal.SIGTERM, receiveSignal)
signal.signal(signal.SIGINT, receiveSignal)
frame_manager = SharedMemoryFrameManager() frame_manager = SharedMemoryFrameManager()
# start a websocket server on 8082 # start a websocket server on 8082
@ -64,22 +51,24 @@ def output_frames(
preview_recorders: dict[str, PreviewRecorder] = {} preview_recorders: dict[str, PreviewRecorder] = {}
preview_write_times: dict[str, float] = {} preview_write_times: dict[str, float] = {}
move_preview_frames("cache") self.move_preview_frames("cache")
for camera, cam_config in config.cameras.items(): for camera, cam_config in self.config.cameras.items():
if not cam_config.enabled: if not cam_config.enabled:
continue continue
jsmpeg_cameras[camera] = JsmpegCamera(cam_config, stop_event, websocket_server) jsmpeg_cameras[camera] = JsmpegCamera(
cam_config, self.stop_event, websocket_server
)
preview_recorders[camera] = PreviewRecorder(cam_config) preview_recorders[camera] = PreviewRecorder(cam_config)
preview_write_times[camera] = 0 preview_write_times[camera] = 0
if config.birdseye.enabled: if self.config.birdseye.enabled:
birdseye = Birdseye(config, stop_event, websocket_server) birdseye = Birdseye(self.config, self.stop_event, websocket_server)
websocket_thread.start() websocket_thread.start()
while not stop_event.is_set(): while not self.stop_event.is_set():
(topic, data) = detection_subscriber.check_for_update(timeout=1) (topic, data) = detection_subscriber.check_for_update(timeout=1)
if not topic: if not topic:
@ -95,22 +84,25 @@ def output_frames(
frame_id = f"{camera}{frame_time}" frame_id = f"{camera}{frame_time}"
frame = frame_manager.get(frame_id, config.cameras[camera].frame_shape_yuv) frame = frame_manager.get(
frame_id, self.config.cameras[camera].frame_shape_yuv
)
if frame is None: if frame is None:
logger.debug(f"Failed to get frame {frame_id} from SHM") self.logger.debug(f"Failed to get frame {frame_id} from SHM")
continue continue
# send camera frame to ffmpeg process if websockets are connected # send camera frame to ffmpeg process if websockets are connected
if any( if any(
ws.environ["PATH_INFO"].endswith(camera) for ws in websocket_server.manager ws.environ["PATH_INFO"].endswith(camera)
for ws in websocket_server.manager
): ):
# write to the converter for the camera if clients are listening to the specific camera # write to the converter for the camera if clients are listening to the specific camera
jsmpeg_cameras[camera].write_frame(frame.tobytes()) jsmpeg_cameras[camera].write_frame(frame.tobytes())
# send output data to birdseye if websocket is connected or restreaming # send output data to birdseye if websocket is connected or restreaming
if config.birdseye.enabled and ( if self.config.birdseye.enabled and (
config.birdseye.restream self.config.birdseye.restream
or any( or any(
ws.environ["PATH_INFO"].endswith("birdseye") ws.environ["PATH_INFO"].endswith("birdseye")
for ws in websocket_server.manager for ws in websocket_server.manager
@ -141,7 +133,7 @@ def output_frames(
frame_manager.close(frame_id) frame_manager.close(frame_id)
move_preview_frames("clips") self.move_preview_frames("clips")
while True: while True:
(topic, data) = detection_subscriber.check_for_update(timeout=0) (topic, data) = detection_subscriber.check_for_update(timeout=0)
@ -158,7 +150,9 @@ def output_frames(
) = data ) = data
frame_id = f"{camera}{frame_time}" frame_id = f"{camera}{frame_time}"
frame = frame_manager.get(frame_id, config.cameras[camera].frame_shape_yuv) frame = frame_manager.get(
frame_id, self.config.cameras[camera].frame_shape_yuv
)
frame_manager.close(frame_id) frame_manager.close(frame_id)
detection_subscriber.stop() detection_subscriber.stop()
@ -177,10 +171,9 @@ def output_frames(
websocket_server.manager.join() websocket_server.manager.join()
websocket_server.shutdown() websocket_server.shutdown()
websocket_thread.join() websocket_thread.join()
logger.info("exiting output process...") self.logger.info("Exiting output process...")
def move_preview_frames(self, loc: str):
def move_preview_frames(loc: str):
preview_holdover = os.path.join(CLIPS_DIR, "preview_restart_cache") preview_holdover = os.path.join(CLIPS_DIR, "preview_restart_cache")
preview_cache = os.path.join(CACHE_DIR, "preview_frames") preview_cache = os.path.join(CACHE_DIR, "preview_frames")
@ -193,4 +186,4 @@ def move_preview_frames(loc: str):
shutil.move(preview_holdover, preview_cache) shutil.move(preview_holdover, preview_cache)
except shutil.Error: except shutil.Error:
logger.error("Failed to restore preview cache.") self.logger.error("Failed to restore preview cache.")