mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	move birdseye placeholder to output process
This commit is contained in:
		
							parent
							
								
									700f25abc3
								
							
						
					
					
						commit
						8e2ba4a8ea
					
				@ -14,7 +14,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.birdseye import BirdsEyeFrameOutputter
 | 
					 | 
				
			||||||
from frigate.config import FrigateConfig
 | 
					from frigate.config import FrigateConfig
 | 
				
			||||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
 | 
					from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
 | 
				
			||||||
from frigate.edgetpu import EdgeTPUProcess
 | 
					from frigate.edgetpu import EdgeTPUProcess
 | 
				
			||||||
 | 
				
			|||||||
@ -1,145 +0,0 @@
 | 
				
			|||||||
import logging
 | 
					 | 
				
			||||||
import multiprocessing as mp
 | 
					 | 
				
			||||||
import subprocess as sp
 | 
					 | 
				
			||||||
import threading
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import gevent
 | 
					 | 
				
			||||||
import numpy as np
 | 
					 | 
				
			||||||
from flask import (
 | 
					 | 
				
			||||||
    Blueprint,
 | 
					 | 
				
			||||||
    Flask,
 | 
					 | 
				
			||||||
    Response,
 | 
					 | 
				
			||||||
    current_app,
 | 
					 | 
				
			||||||
    jsonify,
 | 
					 | 
				
			||||||
    make_response,
 | 
					 | 
				
			||||||
    request,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from flask_sockets import Sockets
 | 
					 | 
				
			||||||
from gevent import pywsgi
 | 
					 | 
				
			||||||
from geventwebsocket.handler import WebSocketHandler
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from frigate.util import SharedMemoryFrameManager
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# methods for maintaining the birdseyeframe in the object processing thread
 | 
					 | 
				
			||||||
# avoids work when no clients are listening
 | 
					 | 
				
			||||||
class BirdsEyeFrameManager:
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        # self.config = config
 | 
					 | 
				
			||||||
        self.frame_manager = SharedMemoryFrameManager()
 | 
					 | 
				
			||||||
        self._frame_shape = (1080, 1920)
 | 
					 | 
				
			||||||
        self.frame_shape_yuv = (self._frame_shape[0] * 3 // 2, self._frame_shape[1])
 | 
					 | 
				
			||||||
        self.frame_shm = mp.shared_memory.SharedMemory(
 | 
					 | 
				
			||||||
            name=f"birdseye-frame",
 | 
					 | 
				
			||||||
            create=True,
 | 
					 | 
				
			||||||
            size=self.frame_shape_yuv[0] * self.frame_shape_yuv[1],
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.frame = np.ndarray(
 | 
					 | 
				
			||||||
            self.frame_shape_yuv, dtype=np.uint8, buffer=self.frame_shm.buf
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # initialize the frame as black and with the frigate logo
 | 
					 | 
				
			||||||
        self.blank_frame = np.zeros((1080 * 3 // 2, 1920), np.uint8)
 | 
					 | 
				
			||||||
        self.blank_frame[:] = 128
 | 
					 | 
				
			||||||
        self.blank_frame[0:1080, 0:1920] = 16
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.frame[:] = self.blank_frame
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def update_frame(self, camera, object_count, motion_count, frame_time, frame):
 | 
					 | 
				
			||||||
        # determine how many cameras are tracking objects (or recently were)
 | 
					 | 
				
			||||||
        # decide on a layout for the birdseye view (try to avoid too much churn)
 | 
					 | 
				
			||||||
        # calculate position of each camera
 | 
					 | 
				
			||||||
        # calculate resolution of each position in the layout
 | 
					 | 
				
			||||||
        # if layout is changing, wipe the frame black again
 | 
					 | 
				
			||||||
        # For each camera currently tracking objects (alphabetical):
 | 
					 | 
				
			||||||
        #   - resize the current frame and copy into the birdseye view
 | 
					 | 
				
			||||||
        # signal to birdseye process that the frame is ready to send
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.frame[:] = frame
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# separate process for managing the external ffmpeg process and sending frame
 | 
					 | 
				
			||||||
# bytes to ffmpeg
 | 
					 | 
				
			||||||
class BirdsEyeFrameOutputter(threading.Thread):
 | 
					 | 
				
			||||||
    def __init__(self, stop_event):
 | 
					 | 
				
			||||||
        threading.Thread.__init__(self)
 | 
					 | 
				
			||||||
        self.stop_event = stop_event
 | 
					 | 
				
			||||||
        self.frame_shm = mp.shared_memory.SharedMemory(
 | 
					 | 
				
			||||||
            name=f"birdseye-frame",
 | 
					 | 
				
			||||||
            create=False,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def start_ffmpeg(self):
 | 
					 | 
				
			||||||
        ffmpeg_cmd = "ffmpeg -f rawvideo -pix_fmt yuv420p -video_size 1920x1080 -i pipe: -f mpegts -codec:v mpeg1video -b:v 1000k -bf 0 pipe:".split(
 | 
					 | 
				
			||||||
            " "
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.process = sp.Popen(
 | 
					 | 
				
			||||||
            ffmpeg_cmd,
 | 
					 | 
				
			||||||
            stdout=sp.PIPE,
 | 
					 | 
				
			||||||
            # TODO: logging
 | 
					 | 
				
			||||||
            stderr=sp.DEVNULL,
 | 
					 | 
				
			||||||
            stdin=sp.PIPE,
 | 
					 | 
				
			||||||
            start_new_session=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def run(self):
 | 
					 | 
				
			||||||
        self.start_ffmpeg()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while not self.stop_event.wait(1):
 | 
					 | 
				
			||||||
            if self.process.poll() != None:
 | 
					 | 
				
			||||||
                logger.info(f"ffmpeg process is not running. restarting ...")
 | 
					 | 
				
			||||||
                self.start_ffmpeg()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            self.process.stdin.write(self.frame_shm.buf.tobytes())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# separate process for passing jsmpeg packets over websockets
 | 
					 | 
				
			||||||
# signals to the frame manager when a client is listening
 | 
					 | 
				
			||||||
def run_jsmpeg_server():
 | 
					 | 
				
			||||||
    app = Flask(__name__)
 | 
					 | 
				
			||||||
    sockets = Sockets(app)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    http = Blueprint("http", __name__)
 | 
					 | 
				
			||||||
    ws = Blueprint("ws", __name__)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # TODO: add something for notification of subscribers
 | 
					 | 
				
			||||||
    # self.app.frigate_config = frigate_config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    app.register_blueprint(http)
 | 
					 | 
				
			||||||
    sockets.register_blueprint(ws)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    clients = list()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @http.route("/birdseye")
 | 
					 | 
				
			||||||
    def receive_mpegts():
 | 
					 | 
				
			||||||
        chunk_size = 4096
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            chunk = request.stream.read(chunk_size)
 | 
					 | 
				
			||||||
            if len(chunk) == 0:
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
            for client in clients:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    client.send(chunk)
 | 
					 | 
				
			||||||
                except:
 | 
					 | 
				
			||||||
                    logger.debug(
 | 
					 | 
				
			||||||
                        "Removing websocket client due to a closed connection."
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    clients.remove(client)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @ws.route("/birdseye")
 | 
					 | 
				
			||||||
    def echo_socket(socket):
 | 
					 | 
				
			||||||
        # TODO: get reference to
 | 
					 | 
				
			||||||
        # current_app.mqtt_backend.register(socket)
 | 
					 | 
				
			||||||
        clients.append(socket)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while not socket.closed:
 | 
					 | 
				
			||||||
            # Sleep to prevent *constant* context-switches.
 | 
					 | 
				
			||||||
            gevent.sleep(0.1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    server = pywsgi.WSGIServer(("127.0.0.1", 5050), app, handler_class=WebSocketHandler)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        server.serve_forever()
 | 
					 | 
				
			||||||
    except KeyboardInterrupt:
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
@ -17,7 +17,6 @@ import cv2
 | 
				
			|||||||
import matplotlib.pyplot as plt
 | 
					import matplotlib.pyplot as plt
 | 
				
			||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from frigate.birdseye import BirdsEyeFrameManager
 | 
					 | 
				
			||||||
from frigate.config import FrigateConfig, CameraConfig
 | 
					from frigate.config import FrigateConfig, CameraConfig
 | 
				
			||||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
 | 
					from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
 | 
				
			||||||
from frigate.edgetpu import load_labels
 | 
					from frigate.edgetpu import load_labels
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import queue
 | 
				
			|||||||
import signal
 | 
					import signal
 | 
				
			||||||
import subprocess as sp
 | 
					import subprocess as sp
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
 | 
					import numpy as np
 | 
				
			||||||
from multiprocessing import shared_memory
 | 
					from multiprocessing import shared_memory
 | 
				
			||||||
from wsgiref.simple_server import make_server
 | 
					from wsgiref.simple_server import make_server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,6 +66,32 @@ class BroadcastThread(threading.Thread):
 | 
				
			|||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BirdsEyeFrameManager:
 | 
				
			||||||
 | 
					    def __init__(self, height, width):
 | 
				
			||||||
 | 
					        frame_shape = (height, width)
 | 
				
			||||||
 | 
					        yuv_shape = (height * 3 // 2, width)
 | 
				
			||||||
 | 
					        self.frame = np.ndarray(yuv_shape, dtype=np.uint8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # initialize the frame as black and with the frigate logo
 | 
				
			||||||
 | 
					        self.blank_frame = np.zeros(yuv_shape, np.uint8)
 | 
				
			||||||
 | 
					        self.blank_frame[:] = 128
 | 
				
			||||||
 | 
					        self.blank_frame[0 : frame_shape[0], 0 : frame_shape[1]] = 16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.frame[:] = self.blank_frame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update(self, camera, object_count, motion_count, frame_time, frame):
 | 
				
			||||||
 | 
					        # determine how many cameras are tracking objects (or recently were)
 | 
				
			||||||
 | 
					        # decide on a layout for the birdseye view (try to avoid too much churn)
 | 
				
			||||||
 | 
					        # calculate position of each camera
 | 
				
			||||||
 | 
					        # calculate resolution of each position in the layout
 | 
				
			||||||
 | 
					        # if layout is changing, wipe the frame black again
 | 
				
			||||||
 | 
					        # For each camera currently tracking objects (alphabetical):
 | 
				
			||||||
 | 
					        #   - resize the current frame and copy into the birdseye view
 | 
				
			||||||
 | 
					        # signal to birdseye process that the frame is ready to send
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.frame[:] = frame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def output_frames(config, video_output_queue):
 | 
					def output_frames(config, video_output_queue):
 | 
				
			||||||
    threading.current_thread().name = f"output"
 | 
					    threading.current_thread().name = f"output"
 | 
				
			||||||
    setproctitle(f"frigate.output")
 | 
					    setproctitle(f"frigate.output")
 | 
				
			||||||
@ -103,11 +130,18 @@ def output_frames(config, video_output_queue):
 | 
				
			|||||||
            camera, converters[camera], websocket_server
 | 
					            camera, converters[camera], websocket_server
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    converters["birdseye"] = FFMpegConverter(1920, 1080, 640, 320, "1000k")
 | 
				
			||||||
 | 
					    broadcasters["birdseye"] = BroadcastThread(
 | 
				
			||||||
 | 
					        "birdseye", converters["birdseye"], websocket_server
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    websocket_thread.start()
 | 
					    websocket_thread.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for t in broadcasters.values():
 | 
					    for t in broadcasters.values():
 | 
				
			||||||
        t.start()
 | 
					        t.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    birdseye_manager = BirdsEyeFrameManager(1080, 1920)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while not stop_event.is_set():
 | 
					    while not stop_event.is_set():
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            (
 | 
					            (
 | 
				
			||||||
@ -131,6 +165,20 @@ def output_frames(config, video_output_queue):
 | 
				
			|||||||
            # 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
 | 
				
			||||||
            converters[camera].write(frame.tobytes())
 | 
					            converters[camera].write(frame.tobytes())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # update birdseye if websockets are connected
 | 
				
			||||||
 | 
					        if any(
 | 
				
			||||||
 | 
					            ws.environ["PATH_INFO"].endswith("birdseye")
 | 
				
			||||||
 | 
					            for ws in websocket_server.manager
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            birdseye_manager.update(
 | 
				
			||||||
 | 
					                camera,
 | 
				
			||||||
 | 
					                len(current_tracked_objects),
 | 
				
			||||||
 | 
					                len(motion_boxes),
 | 
				
			||||||
 | 
					                frame_time,
 | 
				
			||||||
 | 
					                frame,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            converters["birdseye"].write(birdseye_manager.frame.tobytes())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if camera in previous_frames:
 | 
					        if camera in previous_frames:
 | 
				
			||||||
            frame_manager.delete(previous_frames[camera])
 | 
					            frame_manager.delete(previous_frames[camera])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user