mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-30 13:48:07 +02: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