From dcd195ade9805f68a4a0c1a43dfa67c2db8703ed Mon Sep 17 00:00:00 2001 From: Nick Mowen Date: Tue, 20 Dec 2022 13:43:22 -0700 Subject: [PATCH] Try using RTSP for restream --- docs/docs/configuration/index.md | 3 ++ docs/docs/configuration/live.md | 2 +- docs/docs/configuration/restream.md | 8 +++ frigate/config.py | 1 + frigate/ffmpeg_presets.py | 46 +++++++++++++++- frigate/output.py | 83 ++++++++++++++++++++++++++--- frigate/restream.py | 5 ++ web/src/routes/Birdseye.jsx | 77 ++++++++++++++++++++++++-- 8 files changed, 211 insertions(+), 14 deletions(-) diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index fa3a6f2fa..6c31ca514 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -356,6 +356,9 @@ restream: enabled: True # Optional: Force audio compatibility with browsers (default: shown below) force_audio: True + # Optional: Restream birdseye via RTSP (default: shown below) + # NOTE: Enabling this will set birdseye to run 24/7 which may increase CPU usage somewhat. + birdseye: False # Optional: jsmpeg stream configuration for WebUI jsmpeg: # Optional: Set the height of the jsmpeg stream. (default: 720) diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index e537c4ff1..a6c48ce51 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -12,7 +12,7 @@ Live view options can be selected while viewing the live stream. The options are | Source | Latency | Frame Rate | Resolution | Audio | Requires Restream | Other Limitations | | ------ | ------- | -------------------------------------- | -------------- | ---------------------------- | ----------------- | --------------------- | | jsmpeg | low | same as `detect -> fps`, capped at 10 | same as detect | no | no | none | -| mse | low | native | native | yes (depends on audio codec) | yes | none | +| mse | low | native | native | yes (depends on audio codec) | yes | not supported on iOS | | webrtc | lowest | native | native | yes (depends on audio codec) | yes | requires extra config | ### WebRTC extra configuration: diff --git a/docs/docs/configuration/restream.md b/docs/docs/configuration/restream.md index 1f21e8fdc..2a43616db 100644 --- a/docs/docs/configuration/restream.md +++ b/docs/docs/configuration/restream.md @@ -7,6 +7,14 @@ title: Restream Frigate can restream your video feed as an RTSP feed for other applications such as Home Assistant to utilize it at `rtsp://:8554/`. Port 8554 must be open. [This allows you to use a video feed for detection in frigate and Home Assistant live view at the same time without having to make two separate connections to the camera](#reduce-connections-to-camera). The video feed is copied from the original video feed directly to avoid re-encoding. This feed does not include any annotation by Frigate. +#### Force Audio + +Different live view technologies (ex: MSE, WebRTC) support different audio codecs. The `restream -> force_audio` flag tells the restream to make multiple streams available so that all live view technologies are supported. Some camera streams don't work well with this, in which case `restream -> force_audio` should be disabled. + +#### Birdseye Restream + +Birdseye RTSP restream can be enabled at `restream -> birdseye` and accessed at `rtsp://:8554/birdseye`. Enabling the restream will cause birdseye to run 24/7 which may increase CPU usage somewhat. + ### RTMP (Deprecated) In previous Frigate versions RTMP was used for re-streaming. RTMP has disadvantages however including being incompatible with H.265, high bitrates, and certain audio codecs. RTMP is deprecated and it is recommended to move to the new restream role. diff --git a/frigate/config.py b/frigate/config.py index e95e892f3..6b97b2e1a 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -519,6 +519,7 @@ class RestreamConfig(FrigateBaseModel): force_audio: bool = Field( default=True, title="Force audio compatibility with the browser." ) + birdseye: bool = Field(default=False, title="Restream the birdseye feed via RTSP.") jsmpeg: JsmpegStreamConfig = Field( default_factory=JsmpegStreamConfig, title="Jsmpeg Stream Configuration." ) diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index e15730530..938359a5a 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -129,6 +129,51 @@ PRESETS_HW_ACCEL_SCALE = { ], } +PRESETS_HW_ACCEL_ENCODE_TO_RTSP = { + "default": [ + "-c:v", + "libx264", + "-g", + "50", + "-profile:v", + "high", + "-level:v", + "4.1", + "-preset:v", + "superfast", + "-tune:v", + "zerolatency", + ], + "preset-rpi-64-h264": ["-c:v", "h264_v4l2m2m", "-g", "50", "-bf", "0"], + "preset-intel-vaapi": [ + "-c:v", + "h264_vaapi", + "-g", + "50", + "-bf", + "0", + "-profile:v", + "high", + "-level:v", + "4.1", + ], + "preset-intel-qsv-h264": ["-c:v", "h264_qsv"], + "preset-intel-qsv-h265": ["-c:v", "hevc_qsv"], + "preset-nvidia-h264": [ + "-c:v", + "h264_nvenc", + "-g", + "50", + "-profile:v", + "high", + "-level:v", + "auto", + "-preset:v", + "p2", + "-tune:v", + "ll", + ], +} def parse_preset_hardware_acceleration_decode(arg: Any) -> list[str]: """Return the correct preset if in preset format otherwise return None.""" @@ -137,7 +182,6 @@ def parse_preset_hardware_acceleration_decode(arg: Any) -> list[str]: return PRESETS_HW_ACCEL_DECODE.get(arg, None) - def parse_preset_hardware_acceleration_scale( arg: Any, detect_args: list[str], diff --git a/frigate/output.py b/frigate/output.py index 865a8d367..59490c7f8 100644 --- a/frigate/output.py +++ b/frigate/output.py @@ -28,10 +28,74 @@ logger = logging.getLogger(__name__) class FFMpegConverter: - def __init__(self, in_width, in_height, out_width, out_height, quality): - ffmpeg_cmd = f"ffmpeg -f rawvideo -pix_fmt yuv420p -video_size {in_width}x{in_height} -i pipe: -f mpegts -s {out_width}x{out_height} -codec:v mpeg1video -q {quality} -bf 0 pipe:".split( - " " - ) + def __init__( + self, + in_width: int, + in_height: int, + out_width: int, + out_height: int, + quality: int, + birdseye_rtsp: bool = False, + ): + if birdseye_rtsp: + ffmpeg_cmd = [ + "ffmpeg", + "-f", + "rawvideo", + "-pix_fmt", + "yuv420p", + "-video_size", + f"{in_width}x{in_height}", + "-i", + "pipe:", + "-an", + "-f", + "rtp_mpegts", + "-s", + f"{out_width}x{out_height}", + "-codec:v", + "mpeg1video", + "-q", + f"{quality}", + "-bf", + "0", + "rtp://127.0.0.1:1998", + "-f", + "mpegts", + "-s", + f"{out_width}x{out_height}", + "-codec:v", + "mpeg1video", + "-q", + f"{quality}", + "-bf", + "0", + "pipe:", + ] + else: + ffmpeg_cmd = [ + "ffmpeg", + "-f", + "rawvideo", + "-pix_fmt", + "yuv420p", + "-video_size", + f"{in_width}x{in_height}", + "-i", + "pipe:", + "-f", + "mpegts", + "-s", + f"{out_width}x{out_height}", + "-codec:v", + "mpeg1video", + "-q", + f"{quality}", + "-bf", + "0", + "pipe:", + ] + self.process = sp.Popen( ffmpeg_cmd, stdout=sp.PIPE, @@ -386,6 +450,7 @@ def output_frames(config: FrigateConfig, video_output_queue): config.birdseye.width, config.birdseye.height, config.birdseye.quality, + config.restream.birdseye, ) broadcasters["birdseye"] = BroadcastThread( "birdseye", converters["birdseye"], websocket_server @@ -421,10 +486,12 @@ def output_frames(config: FrigateConfig, video_output_queue): # write to the converter for the camera if clients are listening to the specific camera converters[camera].write(frame.tobytes()) - # update birdseye if websockets are connected - if config.birdseye.enabled and any( - ws.environ["PATH_INFO"].endswith("birdseye") - for ws in websocket_server.manager + if config.birdseye.enabled and ( + config.restream.birdseye + or any( + ws.environ["PATH_INFO"].endswith("birdseye") + for ws in websocket_server.manager + ) ): if birdseye_manager.update( camera, diff --git a/frigate/restream.py b/frigate/restream.py index 0d72c3f5a..e7b1add7f 100644 --- a/frigate/restream.py +++ b/frigate/restream.py @@ -42,6 +42,11 @@ class RestreamApi: escape_special_characters(input.path) ) + if self.config.restream.birdseye: + self.relays[ + "birdseye" + ] = "ffmpeg:rtp://127.0.0.1:1998#video=h264#audio=none" + for name, path in self.relays.items(): params = {"src": path, "name": name} requests.put("http://127.0.0.1:1984/api/streams", params=params) diff --git a/web/src/routes/Birdseye.jsx b/web/src/routes/Birdseye.jsx index 75b356054..1097286d3 100644 --- a/web/src/routes/Birdseye.jsx +++ b/web/src/routes/Birdseye.jsx @@ -1,14 +1,83 @@ -import { h } from 'preact'; +import { h, Fragment } from 'preact'; +import { usePersistence } from '../context'; +import ActivityIndicator from '../components/ActivityIndicator'; import JSMpegPlayer from '../components/JSMpegPlayer'; import Heading from '../components/Heading'; +import WebRtcPlayer from '../components/WebRtcPlayer'; +import MsePlayer from '../components/MsePlayer'; +import useSWR from 'swr'; +import videojs from 'video.js'; export default function Birdseye() { + const { data: config } = useSWR('config'); + + const [viewSource, setViewSource, sourceIsLoaded] = usePersistence('birdseye-source', 'mse'); + const sourceValues = ['mse', 'webrtc', 'jsmpeg']; + + if (!config || !sourceIsLoaded) { + return ; + } + + let player; + if (viewSource == 'mse' && config.restream.birdseye) { + if (videojs.browser.IS_IOS) { + player = ( + +
+ MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info. +
+
+ ); + } else { + player = ( + +
+ +
+
+ ); + } + } else if (viewSource == 'webrtc' && config.restream.birdseye) { + player = ( + +
+ +
+
+ ); + } else { + player = ( + +
+ +
+
+ ); + } + return (
- Birdseye -
- +
+ + Birdseye + + + {config.restream.birdseye && ( + + )}
+ + {player}
); }