Try using RTSP for restream

This commit is contained in:
Nick Mowen 2022-12-20 13:43:22 -07:00
parent d6731b17a4
commit dcd195ade9
8 changed files with 211 additions and 14 deletions

View File

@ -356,6 +356,9 @@ restream:
enabled: True enabled: True
# Optional: Force audio compatibility with browsers (default: shown below) # Optional: Force audio compatibility with browsers (default: shown below)
force_audio: True 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 # Optional: jsmpeg stream configuration for WebUI
jsmpeg: jsmpeg:
# Optional: Set the height of the jsmpeg stream. (default: 720) # Optional: Set the height of the jsmpeg stream. (default: 720)

View File

@ -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 | | 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 | | 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 | lowest | native | native | yes (depends on audio codec) | yes | requires extra config |
### WebRTC extra configuration: ### WebRTC extra configuration:

View File

@ -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://<frigate_host>:8554/<camera_name>`. 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. Frigate can restream your video feed as an RTSP feed for other applications such as Home Assistant to utilize it at `rtsp://<frigate_host>:8554/<camera_name>`. 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://<frigate_host>:8554/birdseye`. Enabling the restream will cause birdseye to run 24/7 which may increase CPU usage somewhat.
### RTMP (Deprecated) ### 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. 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.

View File

@ -519,6 +519,7 @@ class RestreamConfig(FrigateBaseModel):
force_audio: bool = Field( force_audio: bool = Field(
default=True, title="Force audio compatibility with the browser." default=True, title="Force audio compatibility with the browser."
) )
birdseye: bool = Field(default=False, title="Restream the birdseye feed via RTSP.")
jsmpeg: JsmpegStreamConfig = Field( jsmpeg: JsmpegStreamConfig = Field(
default_factory=JsmpegStreamConfig, title="Jsmpeg Stream Configuration." default_factory=JsmpegStreamConfig, title="Jsmpeg Stream Configuration."
) )

View File

@ -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]: def parse_preset_hardware_acceleration_decode(arg: Any) -> list[str]:
"""Return the correct preset if in preset format otherwise return None.""" """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) return PRESETS_HW_ACCEL_DECODE.get(arg, None)
def parse_preset_hardware_acceleration_scale( def parse_preset_hardware_acceleration_scale(
arg: Any, arg: Any,
detect_args: list[str], detect_args: list[str],

View File

@ -28,10 +28,74 @@ logger = logging.getLogger(__name__)
class FFMpegConverter: class FFMpegConverter:
def __init__(self, in_width, in_height, out_width, out_height, quality): def __init__(
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( 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( self.process = sp.Popen(
ffmpeg_cmd, ffmpeg_cmd,
stdout=sp.PIPE, stdout=sp.PIPE,
@ -386,6 +450,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
config.birdseye.width, config.birdseye.width,
config.birdseye.height, config.birdseye.height,
config.birdseye.quality, config.birdseye.quality,
config.restream.birdseye,
) )
broadcasters["birdseye"] = BroadcastThread( broadcasters["birdseye"] = BroadcastThread(
"birdseye", converters["birdseye"], websocket_server "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 # 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 config.birdseye.enabled and (
if config.birdseye.enabled and any( config.restream.birdseye
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
)
): ):
if birdseye_manager.update( if birdseye_manager.update(
camera, camera,

View File

@ -42,6 +42,11 @@ class RestreamApi:
escape_special_characters(input.path) 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(): for name, path in self.relays.items():
params = {"src": path, "name": name} params = {"src": path, "name": name}
requests.put("http://127.0.0.1:1984/api/streams", params=params) requests.put("http://127.0.0.1:1984/api/streams", params=params)

View File

@ -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 JSMpegPlayer from '../components/JSMpegPlayer';
import Heading from '../components/Heading'; 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() { export default function Birdseye() {
return ( const { data: config } = useSWR('config');
<div className="space-y-4 p-2 px-4">
<Heading size="2xl">Birdseye</Heading> const [viewSource, setViewSource, sourceIsLoaded] = usePersistence('birdseye-source', 'mse');
const sourceValues = ['mse', 'webrtc', 'jsmpeg'];
if (!config || !sourceIsLoaded) {
return <ActivityIndicator />;
}
let player;
if (viewSource == 'mse' && config.restream.birdseye) {
if (videojs.browser.IS_IOS) {
player = (
<Fragment>
<div className="w-5xl text-center text-sm">
MSE is not supported on iOS devices. You'll need to use jsmpeg or webRTC. See the docs for more info.
</div>
</Fragment>
);
} else {
player = (
<Fragment>
<div className="max-w-5xl">
<MsePlayer camera="birdseye" />
</div>
</Fragment>
);
}
} else if (viewSource == 'webrtc' && config.restream.birdseye) {
player = (
<Fragment>
<div className="max-w-5xl">
<WebRtcPlayer camera="birdseye" />
</div>
</Fragment>
);
} else {
player = (
<Fragment>
<div className="max-w-7xl"> <div className="max-w-7xl">
<JSMpegPlayer camera="birdseye" /> <JSMpegPlayer camera="birdseye" />
</div> </div>
</Fragment>
);
}
return (
<div className="space-y-4 p-2 px-4">
<div className="flex justify-between">
<Heading className="p-2" size="2xl">
Birdseye
</Heading>
{config.restream.birdseye && (
<select
className="basis-1/8 cursor-pointer rounded dark:bg-slate-800"
value={viewSource}
onChange={(e) => setViewSource(e.target.value)}
>
{sourceValues.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
)}
</div>
{player}
</div> </div>
); );
} }