mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Rewrite restream (#5106)
* Tear out restream config * Rework birdseye restream * Create go2rtc config handler * Fix bug * Write start script * Rework style * Fix python run syntax * Output as json instead of yaml * Put old live config back and fix birdseye references * Fix camera webUI * Add frigate env var subsitutions * Fix webui checks * Check keys * Remove unused prest * Fix tests * Update restream docs * Update restream docs * Update live docs * Update camera specific recommendation * Update more docs * add links for the docs Co-authored-by: Felipe Santos <felipecassiors@gmail.com> * Update note about supported audio codecs * Move restream to go2rtc * Docs fixes * Add verification of stream name * Ensure that webUI uses camera name * Update docs to reflect new live stream name * Fix check * Formatting * Remove audio from detect Co-authored-by: Felipe Santos <felipecassiors@gmail.com> * Fix docs * Don't handle env variable substitution * Add go2rtc version * Clarify docs Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
This commit is contained in:
parent
a7751f210b
commit
19afb035ff
@ -4,12 +4,8 @@
|
|||||||
|
|
||||||
set -o errexit -o nounset -o pipefail
|
set -o errexit -o nounset -o pipefail
|
||||||
|
|
||||||
if [[ -f "/config/frigate-go2rtc.yaml" ]]; then
|
raw_config=$(python3 /usr/local/go2rtc/create_config.py)
|
||||||
config_path="/config/frigate-go2rtc.yaml"
|
|
||||||
else
|
|
||||||
config_path="/usr/local/go2rtc/go2rtc.yaml"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Replace the bash process with the go2rtc process, redirecting stderr to stdout
|
# Replace the bash process with the go2rtc process, redirecting stderr to stdout
|
||||||
exec 2>&1
|
exec 2>&1
|
||||||
exec go2rtc -config="${config_path}"
|
exec go2rtc -config="${raw_config}"
|
||||||
|
31
docker/rootfs/usr/local/go2rtc/create_config.py
Normal file
31
docker/rootfs/usr/local/go2rtc/create_config.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"""Creates a go2rtc config file."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
|
||||||
|
|
||||||
|
# Check if we can use .yaml instead of .yml
|
||||||
|
config_file_yaml = config_file.replace(".yml", ".yaml")
|
||||||
|
if os.path.isfile(config_file_yaml):
|
||||||
|
config_file = config_file_yaml
|
||||||
|
|
||||||
|
with open(config_file) as f:
|
||||||
|
raw_config = f.read()
|
||||||
|
|
||||||
|
if config_file.endswith((".yaml", ".yml")):
|
||||||
|
config = yaml.safe_load(raw_config)
|
||||||
|
elif config_file.endswith(".json"):
|
||||||
|
config = json.loads(raw_config)
|
||||||
|
|
||||||
|
go2rtc_config: dict[str, any] = config["go2rtc"]
|
||||||
|
|
||||||
|
if not go2rtc_config.get("log", {}).get("format"):
|
||||||
|
go2rtc_config["log"] = {"format": "text"}
|
||||||
|
|
||||||
|
if not go2rtc_config.get("webrtc", {}).get("candidates", []):
|
||||||
|
go2rtc_config["webrtc"] = {"candidates": ["stun:8555"]}
|
||||||
|
|
||||||
|
print(json.dumps(go2rtc_config))
|
@ -1,6 +0,0 @@
|
|||||||
log:
|
|
||||||
format: text
|
|
||||||
|
|
||||||
webrtc:
|
|
||||||
candidates:
|
|
||||||
- stun:8555
|
|
@ -14,6 +14,12 @@ This page makes use of presets of FFmpeg args. For more information on presets,
|
|||||||
Note that mjpeg cameras require encoding the video into h264 for recording, and restream roles. This will use significantly more CPU than if the cameras supported h264 feeds directly. It is recommended to use the restream role to create an h264 restream and then use that as the source for ffmpeg.
|
Note that mjpeg cameras require encoding the video into h264 for recording, and restream roles. This will use significantly more CPU than if the cameras supported h264 feeds directly. It is recommended to use the restream role to create an h264 restream and then use that as the source for ffmpeg.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
go2rtc:
|
||||||
|
streams:
|
||||||
|
mjpeg_cam: ffmpeg:{your_mjpeg_stream_url}#video=h264#hardware # <- use hardware acceleration to create an h264 stream usable for other components.
|
||||||
|
|
||||||
|
cameras:
|
||||||
|
...
|
||||||
mjpeg_cam:
|
mjpeg_cam:
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
inputs:
|
inputs:
|
||||||
@ -21,12 +27,6 @@ Note that mjpeg cameras require encoding the video into h264 for recording, and
|
|||||||
roles:
|
roles:
|
||||||
- detect
|
- detect
|
||||||
- record
|
- record
|
||||||
- path: {your_mjpeg_stream_url}
|
|
||||||
roles:
|
|
||||||
- restream
|
|
||||||
restream:
|
|
||||||
enabled: true
|
|
||||||
video_encoding: h264
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## JPEG Stream Cameras
|
## JPEG Stream Cameras
|
||||||
|
@ -15,7 +15,6 @@ Each role can only be assigned to one input per camera. The options for roles ar
|
|||||||
| ---------- | ---------------------------------------------------------------------------------------- |
|
| ---------- | ---------------------------------------------------------------------------------------- |
|
||||||
| `detect` | Main feed for object detection |
|
| `detect` | Main feed for object detection |
|
||||||
| `record` | Saves segments of the video feed based on configuration settings. [docs](record.md) |
|
| `record` | Saves segments of the video feed based on configuration settings. [docs](record.md) |
|
||||||
| `restream` | Broadcast as RTSP feed and use the full res stream for live view. [docs](restream.md) |
|
|
||||||
| `rtmp` | Deprecated: Broadcast as an RTMP feed for other services to consume. [docs](restream.md) |
|
| `rtmp` | Deprecated: Broadcast as an RTMP feed for other services to consume. [docs](restream.md) |
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -29,7 +28,7 @@ cameras:
|
|||||||
- path: rtsp://viewer:{FRIGATE_RTSP_PASSWORD}@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
- path: rtsp://viewer:{FRIGATE_RTSP_PASSWORD}@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||||
roles:
|
roles:
|
||||||
- detect
|
- detect
|
||||||
- rtmp
|
- rtmp # <- deprecated, recommend using restream instead
|
||||||
- path: rtsp://viewer:{FRIGATE_RTSP_PASSWORD}@10.0.10.10:554/live
|
- path: rtsp://viewer:{FRIGATE_RTSP_PASSWORD}@10.0.10.10:554/live
|
||||||
roles:
|
roles:
|
||||||
- record
|
- record
|
||||||
|
@ -126,6 +126,9 @@ environment_vars:
|
|||||||
birdseye:
|
birdseye:
|
||||||
# Optional: Enable birdseye view (default: shown below)
|
# Optional: Enable birdseye view (default: shown below)
|
||||||
enabled: True
|
enabled: 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.
|
||||||
|
restream: False
|
||||||
# Optional: Width of the output resolution (default: shown below)
|
# Optional: Width of the output resolution (default: shown below)
|
||||||
width: 1280
|
width: 1280
|
||||||
# Optional: Height of the output resolution (default: shown below)
|
# Optional: Height of the output resolution (default: shown below)
|
||||||
@ -352,32 +355,21 @@ rtmp:
|
|||||||
enabled: False
|
enabled: False
|
||||||
|
|
||||||
# Optional: Restream configuration
|
# Optional: Restream configuration
|
||||||
# NOTE: Can be overridden at the camera level
|
# Uses https://github.com/AlexxIT/go2rtc (v0.1-rc9)
|
||||||
restream:
|
go2rtc:
|
||||||
# Optional: Enable the restream (default: True)
|
|
||||||
enabled: True
|
# Optional: jsmpeg stream configuration for WebUI
|
||||||
# Optional: Set the audio codecs to restream with
|
live:
|
||||||
# possible values are aac, copy, opus. Set to copy
|
# Optional: Set the name of the stream that should be used for live view
|
||||||
# only to avoid transcoding (default: shown below)
|
# in frigate WebUI. (default: name of camera)
|
||||||
audio_encoding:
|
stream_name: camera_name
|
||||||
- aac
|
# Optional: Set the height of the jsmpeg stream. (default: 720)
|
||||||
- opus
|
# This must be less than or equal to the height of the detect stream. Lower resolutions
|
||||||
# Optional: Video encoding to be used. By default the codec will be copied but
|
# reduce bandwidth required for viewing the jsmpeg stream. Width is computed to match known aspect ratio.
|
||||||
# it can be switched to another or an MJPEG stream can be encoded and restreamed
|
height: 720
|
||||||
# as h264 (default: shown below)
|
# Optional: Set the encode quality of the jsmpeg stream (default: shown below)
|
||||||
video_encoding: "copy"
|
# 1 is the highest quality, and 31 is the lowest. Lower quality feeds utilize less CPU resources.
|
||||||
# Optional: Restream birdseye via RTSP (default: shown below)
|
quality: 8
|
||||||
# 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)
|
|
||||||
# This must be less than or equal to the height of the detect stream. Lower resolutions
|
|
||||||
# reduce bandwidth required for viewing the jsmpeg stream. Width is computed to match known aspect ratio.
|
|
||||||
height: 720
|
|
||||||
# Optional: Set the encode quality of the jsmpeg stream (default: shown below)
|
|
||||||
# 1 is the highest quality, and 31 is the lowest. Lower quality feeds utilize less CPU resources.
|
|
||||||
quality: 8
|
|
||||||
|
|
||||||
# Optional: in-feed timestamp style configuration
|
# Optional: in-feed timestamp style configuration
|
||||||
# NOTE: Can be overridden at the camera level
|
# NOTE: Can be overridden at the camera level
|
||||||
|
@ -9,44 +9,93 @@ Frigate has different live view options, some of which require [restream](restre
|
|||||||
|
|
||||||
Live view options can be selected while viewing the live stream. The options are:
|
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 | not supported on iOS, Firefox is h.264 only |
|
| mse | low | native | native | yes (depends on audio codec) | yes | not supported on iOS, Firefox is h.264 only |
|
||||||
| webrtc | lowest | native | native | yes (depends on audio codec) | yes | requires extra config, doesn't support h.265 |
|
| webrtc | lowest | native | native | yes (depends on audio codec) | yes | requires extra config, doesn't support h.265 |
|
||||||
|
|
||||||
|
### Audio Support
|
||||||
|
|
||||||
|
MSE Requires AAC audio, WebRTC requires PCMU/PCMA, or opus audio. If you want to support both MSE and WebRTC then your restream config needs to use ffmpeg to set both.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
go2rtc:
|
||||||
|
streams:
|
||||||
|
test_cam: ffmpeg:rtsp://192.168.1.5:554/live0#video=copy#audio=aac#audio=opus
|
||||||
|
```
|
||||||
|
|
||||||
|
However, chances are that your camera already provides at least one usable audio type, so you just need restream to add the missing one. For example, if your camera outputs audio in AAC format:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
go2rtc:
|
||||||
|
streams:
|
||||||
|
test_cam: ffmpeg:rtsp://192.168.1.5:554/live0#video=copy#audio=copy#audio=opus
|
||||||
|
```
|
||||||
|
|
||||||
|
Which will reuse your camera AAC audio, while also adding one track in OPUS format.
|
||||||
|
|
||||||
|
If your camera uses RTSP and supports the audio type for the live view you want to use, then you can pass the camera stream to go2rtc without ffmpeg.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
go2rtc:
|
||||||
|
streams:
|
||||||
|
test_cam: rtsp://192.168.1.5:554/live0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Stream For Live UI
|
||||||
|
|
||||||
|
There may be some cameras that you would prefer to use the sub stream for live view, but the main stream for recording. This can be done via `live -> stream_name`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
go2rtc:
|
||||||
|
streams:
|
||||||
|
test_cam: ffmpeg:rtsp://192.168.1.5:554/live0#video=copy#audio=aac#audio=opus
|
||||||
|
test_cam_sub: ffmpeg:rtsp://192.168.1.5:554/substream#video=copy#audio=aac#audio=opus
|
||||||
|
|
||||||
|
cameras:
|
||||||
|
test_cam:
|
||||||
|
ffmpeg:
|
||||||
|
output_args:
|
||||||
|
record: preset-record-generic-audio-copy
|
||||||
|
inputs:
|
||||||
|
- path: rtsp://127.0.0.1:8554/test_cam?video=copy&audio=aac # <--- the name here must match the name of the camera in restream
|
||||||
|
input_args: preset-rtsp-restream
|
||||||
|
roles:
|
||||||
|
- record
|
||||||
|
- path: rtsp://127.0.0.1:8554/test_cam_sub?video=copy # <--- the name here must match the name of the camera_sub in restream
|
||||||
|
input_args: preset-rtsp-restream
|
||||||
|
roles:
|
||||||
|
- detect
|
||||||
|
live:
|
||||||
|
stream_name: test_cam_sub
|
||||||
|
```
|
||||||
|
|
||||||
### WebRTC extra configuration:
|
### WebRTC extra configuration:
|
||||||
|
|
||||||
WebRTC works by creating a TCP or UDP connection on port `8555`. However, it requires additional configuration:
|
WebRTC works by creating a TCP or UDP connection on port `8555`. However, it requires additional configuration:
|
||||||
|
|
||||||
* For external access, over the internet, setup your router to forward port `8555` to port `8555` on the Frigate device, for both TCP and UDP.
|
- For external access, over the internet, setup your router to forward port `8555` to port `8555` on the Frigate device, for both TCP and UDP.
|
||||||
* For internal/local access, you will need to use a custom go2rtc config:
|
- For internal/local access, you will need to use a custom go2rtc config:
|
||||||
1. Create your own go2rtc config, based on [Frigate's internal go2rtc config](https://github.com/blakeblackshear/frigate/blob/dev/docker/rootfs/usr/local/go2rtc/go2rtc.yaml).
|
|
||||||
2. Add your internal IP to the list of `candidates`. Here is an example, assuming that `192.168.1.10` is the local IP of the device running Frigate:
|
|
||||||
|
|
||||||
```yaml title="/config/frigate-go2rtc.yaml"
|
1. Add your internal IP to the list of `candidates`. Here is an example, assuming that `192.168.1.10` is the local IP of the device running Frigate:
|
||||||
log:
|
|
||||||
format: text
|
|
||||||
|
|
||||||
webrtc:
|
```yaml
|
||||||
candidates:
|
go2rtc:
|
||||||
- 192.168.1.10:8555
|
streams:
|
||||||
- stun:8555
|
test_cam: ...
|
||||||
```
|
webrtc:
|
||||||
|
candidates:
|
||||||
3. Mount this config file at `/config/frigate-go2rtc.yaml`. Here is an example, if you run Frigate through docker-compose:
|
- 192.168.1.10:8555
|
||||||
|
- stun:8555
|
||||||
```yaml title="docker-compose.yaml"
|
```
|
||||||
volumes:
|
|
||||||
- /path/to/your/go2rtc.yaml:/config/frigate-go2rtc.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
|
|
||||||
If you are having difficulties getting WebRTC to work and you are running Frigate with docker, you may want to try changing the container network mode:
|
If you are having difficulties getting WebRTC to work and you are running Frigate with docker, you may want to try changing the container network mode:
|
||||||
|
|
||||||
* `network: host`, in this mode you don't need to forward any ports. The services inside of the Frigate container will have full access to the network interfaces of your host machine as if they were running natively and not in a container. Any port conflicts will need to be resolved. This network mode is recommended by go2rtc, but we recommend you only use it if necessary.
|
- `network: host`, in this mode you don't need to forward any ports. The services inside of the Frigate container will have full access to the network interfaces of your host machine as if they were running natively and not in a container. Any port conflicts will need to be resolved. This network mode is recommended by go2rtc, but we recommend you only use it if necessary.
|
||||||
* `network: bridge` creates a virtual network interface for the container, and the container will have full access to it. You also don't need to forward any ports, however, the IP for accessing Frigate locally will differ from the IP of the host machine. Your router will see Frigate as if it was a new device connected in the network.
|
- `network: bridge` creates a virtual network interface for the container, and the container will have full access to it. You also don't need to forward any ports, however, the IP for accessing Frigate locally will differ from the IP of the host machine. Your router will see Frigate as if it was a new device connected in the network.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
@ -7,29 +7,11 @@ 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.
|
||||||
|
|
||||||
#### Copy Audio
|
Frigate uses [go2rtc](https://github.com/AlexxIT/go2rtc) to provide its restream and MSE/WebRTC capabilities. The go2rtc config is hosted at the `go2rtc` in the config, see [go2rtc docs](https://github.com/AlexxIT/go2rtc#configuration) for more advanced configurations and features.
|
||||||
|
|
||||||
Different live view technologies (ex: MSE, WebRTC) support different audio codecs. The `restream -> audio_encoding` field 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 -> audio_encoding` should be set to `copy` only.
|
|
||||||
|
|
||||||
#### Birdseye Restream
|
#### 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.
|
Birdseye RTSP restream can be enabled at `birdseye -> restream` and accessed at `rtsp://<frigate_host>:8554/birdseye`. Enabling the restream will cause birdseye to run 24/7 which may increase CPU usage somewhat.
|
||||||
|
|
||||||
#### Changing Restream Codec {#changing-restream-codec}
|
|
||||||
|
|
||||||
Generally it is recommended to let the codec from the camera be copied. But there may be some cases where h265 needs to be transcoded as h264 or an MJPEG stream can be encoded and restreamed as h264. In this case the encoding will need to be set, if any hardware acceleration presets are set then that will be used to encode the stream.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
ffmpeg:
|
|
||||||
hwaccel_args: your-hwaccel-preset # <- highly recommended so the GPU is used
|
|
||||||
|
|
||||||
cameras:
|
|
||||||
mjpeg_cam:
|
|
||||||
ffmpeg:
|
|
||||||
...
|
|
||||||
restream:
|
|
||||||
video_encoding: h264
|
|
||||||
```
|
|
||||||
|
|
||||||
### RTMP (Deprecated)
|
### RTMP (Deprecated)
|
||||||
|
|
||||||
@ -44,20 +26,21 @@ Some cameras only support one active connection or you may just want to have a s
|
|||||||
One connection is made to the camera. One for the restream, `detect` and `record` connect to the restream.
|
One connection is made to the camera. One for the restream, `detect` and `record` connect to the restream.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
go2rtc:
|
||||||
|
streams:
|
||||||
|
test_cam: ffmpeg:rtsp://192.168.1.5:554/live0#video=copy#audio=aac#audio=opus
|
||||||
|
|
||||||
cameras:
|
cameras:
|
||||||
test_cam:
|
test_cam:
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
output_args:
|
output_args:
|
||||||
record: preset-record-audio-copy
|
record: preset-record-generic-audio-copy
|
||||||
inputs:
|
inputs:
|
||||||
- path: rtsp://127.0.0.1:8554/test_cam?video=copy&audio=aac # <--- the name here must match the name of the camera
|
- path: rtsp://127.0.0.1:8554/test_cam?video=copy&audio=aac # <--- the name here must match the name of the camera in restream
|
||||||
input_args: preset-rtsp-restream
|
input_args: preset-rtsp-restream
|
||||||
roles:
|
roles:
|
||||||
- record
|
- record
|
||||||
- detect
|
- detect
|
||||||
- path: rtsp://192.168.1.5:554/live0 # <--- 1 connection to camera stream
|
|
||||||
roles:
|
|
||||||
- restream
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### With Sub Stream
|
### With Sub Stream
|
||||||
@ -65,20 +48,23 @@ cameras:
|
|||||||
Two connections are made to the camera. One for the sub stream, one for the restream, `record` connects to the restream.
|
Two connections are made to the camera. One for the sub stream, one for the restream, `record` connects to the restream.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
go2rtc:
|
||||||
|
streams:
|
||||||
|
test_cam: ffmpeg:rtsp://192.168.1.5:554/live0#video=copy#audio=aac#audio=opus
|
||||||
|
test_cam_sub: ffmpeg:rtsp://192.168.1.5:554/substream#video=copy#audio=aac#audio=opus
|
||||||
|
|
||||||
cameras:
|
cameras:
|
||||||
test_cam:
|
test_cam:
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
output_args:
|
output_args:
|
||||||
record: preset-record-audio-copy
|
record: preset-record-generic-audio-copy
|
||||||
inputs:
|
inputs:
|
||||||
- path: rtsp://127.0.0.1:8554/test_cam?video=copy&audio=aac # <--- the name here must match the name of the camera
|
- path: rtsp://127.0.0.1:8554/test_cam?video=copy&audio=aac # <--- the name here must match the name of the camera in restream
|
||||||
input_args: preset-rtsp-restream
|
input_args: preset-rtsp-restream
|
||||||
roles:
|
roles:
|
||||||
- record
|
- record
|
||||||
- path: rtsp://192.168.1.5:554/stream # <--- camera high res stream
|
- path: rtsp://127.0.0.1:8554/test_cam_sub?video=copy&audio=aac # <--- the name here must match the name of the camera_sub in restream
|
||||||
roles:
|
input_args: preset-rtsp-restream
|
||||||
- restream
|
|
||||||
- path: rtsp://192.168.1.5:554/substream # <--- camera sub stream
|
|
||||||
roles:
|
roles:
|
||||||
- detect
|
- detect
|
||||||
```
|
```
|
||||||
|
@ -14,7 +14,7 @@ By default, Frigate removes audio from recordings to reduce the likelihood of fa
|
|||||||
```yaml title="frigate.yml"
|
```yaml title="frigate.yml"
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
output_args:
|
output_args:
|
||||||
record: preset-record-generic-audio
|
record: preset-record-generic-audio-aac
|
||||||
```
|
```
|
||||||
|
|
||||||
### My mjpeg stream or snapshots look green and crazy
|
### My mjpeg stream or snapshots look green and crazy
|
||||||
@ -25,7 +25,7 @@ This almost always means that the width/height defined for your camera are not c
|
|||||||
|
|
||||||
### I can't view events or recordings in the Web UI.
|
### I can't view events or recordings in the Web UI.
|
||||||
|
|
||||||
Ensure your cameras send h264 encoded video, or [transcode them](/configuration/restream.md#changing-restream-codec).
|
Ensure your cameras send h264 encoded video, or [transcode them](/configuration/restream.md).
|
||||||
|
|
||||||
### "[mov,mp4,m4a,3gp,3g2,mj2 @ 0x5639eeb6e140] moov atom not found"
|
### "[mov,mp4,m4a,3gp,3g2,mj2 @ 0x5639eeb6e140] moov atom not found"
|
||||||
|
|
||||||
|
@ -344,6 +344,7 @@ class BirdseyeModeEnum(str, Enum):
|
|||||||
|
|
||||||
class BirdseyeConfig(FrigateBaseModel):
|
class BirdseyeConfig(FrigateBaseModel):
|
||||||
enabled: bool = Field(default=True, title="Enable birdseye view.")
|
enabled: bool = Field(default=True, title="Enable birdseye view.")
|
||||||
|
restream: bool = Field(default=False, title="Restream birdseye via RTSP.")
|
||||||
width: int = Field(default=1280, title="Birdseye width.")
|
width: int = Field(default=1280, title="Birdseye width.")
|
||||||
height: int = Field(default=720, title="Birdseye height.")
|
height: int = Field(default=720, title="Birdseye height.")
|
||||||
quality: int = Field(
|
quality: int = Field(
|
||||||
@ -405,7 +406,6 @@ class FfmpegConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
class CameraRoleEnum(str, Enum):
|
class CameraRoleEnum(str, Enum):
|
||||||
record = "record"
|
record = "record"
|
||||||
restream = "restream"
|
|
||||||
rtmp = "rtmp"
|
rtmp = "rtmp"
|
||||||
detect = "detect"
|
detect = "detect"
|
||||||
|
|
||||||
@ -519,39 +519,15 @@ class RtmpConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(default=False, title="RTMP restreaming enabled.")
|
enabled: bool = Field(default=False, title="RTMP restreaming enabled.")
|
||||||
|
|
||||||
|
|
||||||
class JsmpegStreamConfig(FrigateBaseModel):
|
class CameraLiveConfig(FrigateBaseModel):
|
||||||
height: int = Field(default=720, title="Live camera view height.")
|
stream_name: str = Field(default="", title="Name of restream to use as live view.")
|
||||||
quality: int = Field(default=8, ge=1, le=31, title="Live camera view quality.")
|
height: int = Field(default=720, title="Live camera view height")
|
||||||
|
quality: int = Field(default=8, ge=1, le=31, title="Live camera view quality")
|
||||||
|
|
||||||
|
|
||||||
class RestreamVideoCodecEnum(str, Enum):
|
class RestreamConfig(BaseModel):
|
||||||
copy = "copy"
|
class Config:
|
||||||
h264 = "h264"
|
extra = Extra.allow
|
||||||
h265 = "h265"
|
|
||||||
|
|
||||||
|
|
||||||
class RestreamAudioCodecEnum(str, Enum):
|
|
||||||
aac = "aac"
|
|
||||||
copy = "copy"
|
|
||||||
opus = "opus"
|
|
||||||
|
|
||||||
|
|
||||||
class RestreamConfig(FrigateBaseModel):
|
|
||||||
enabled: bool = Field(default=True, title="Restreaming enabled.")
|
|
||||||
audio_encoding: list[RestreamAudioCodecEnum] = Field(
|
|
||||||
default=[RestreamAudioCodecEnum.aac, RestreamAudioCodecEnum.opus],
|
|
||||||
title="Codecs to supply for audio.",
|
|
||||||
)
|
|
||||||
video_encoding: RestreamVideoCodecEnum = Field(
|
|
||||||
default=RestreamVideoCodecEnum.copy, title="Method for encoding the restream."
|
|
||||||
)
|
|
||||||
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."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CameraUiConfig(FrigateBaseModel):
|
class CameraUiConfig(FrigateBaseModel):
|
||||||
@ -578,8 +554,8 @@ class CameraConfig(FrigateBaseModel):
|
|||||||
rtmp: RtmpConfig = Field(
|
rtmp: RtmpConfig = Field(
|
||||||
default_factory=RtmpConfig, title="RTMP restreaming configuration."
|
default_factory=RtmpConfig, title="RTMP restreaming configuration."
|
||||||
)
|
)
|
||||||
restream: RestreamConfig = Field(
|
live: CameraLiveConfig = Field(
|
||||||
default_factory=RestreamConfig, title="Restreaming configuration."
|
default_factory=CameraLiveConfig, title="Live playback settings."
|
||||||
)
|
)
|
||||||
snapshots: SnapshotsConfig = Field(
|
snapshots: SnapshotsConfig = Field(
|
||||||
default_factory=SnapshotsConfig, title="Snapshot configuration."
|
default_factory=SnapshotsConfig, title="Snapshot configuration."
|
||||||
@ -621,7 +597,6 @@ class CameraConfig(FrigateBaseModel):
|
|||||||
config["ffmpeg"]["inputs"][0]["roles"] = [
|
config["ffmpeg"]["inputs"][0]["roles"] = [
|
||||||
"record",
|
"record",
|
||||||
"detect",
|
"detect",
|
||||||
"restream",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if has_rtmp:
|
if has_rtmp:
|
||||||
@ -758,9 +733,17 @@ def verify_config_roles(camera_config: CameraConfig) -> None:
|
|||||||
f"Camera {camera_config.name} has rtmp enabled, but rtmp is not assigned to an input."
|
f"Camera {camera_config.name} has rtmp enabled, but rtmp is not assigned to an input."
|
||||||
)
|
)
|
||||||
|
|
||||||
if camera_config.restream.enabled and not "restream" in assigned_roles:
|
|
||||||
raise ValueError(
|
def verify_valid_live_stream_name(
|
||||||
f"Camera {camera_config.name} has restream enabled, but restream is not assigned to an input."
|
frigate_config: FrigateConfig, camera_config: CameraConfig
|
||||||
|
) -> None:
|
||||||
|
"""Verify that a restream exists to use for live view."""
|
||||||
|
if (
|
||||||
|
camera_config.live.stream_name
|
||||||
|
not in frigate_config.go2rtc.dict().get("streams", {}).keys()
|
||||||
|
):
|
||||||
|
return ValueError(
|
||||||
|
f"No restream with name {camera_config.live.stream_name} exists for camera {camera_config.name}."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -854,7 +837,10 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
rtmp: RtmpConfig = Field(
|
rtmp: RtmpConfig = Field(
|
||||||
default_factory=RtmpConfig, title="Global RTMP restreaming configuration."
|
default_factory=RtmpConfig, title="Global RTMP restreaming configuration."
|
||||||
)
|
)
|
||||||
restream: RestreamConfig = Field(
|
live: CameraLiveConfig = Field(
|
||||||
|
default_factory=CameraLiveConfig, title="Live playback settings."
|
||||||
|
)
|
||||||
|
go2rtc: RestreamConfig = Field(
|
||||||
default_factory=RestreamConfig, title="Global restream configuration."
|
default_factory=RestreamConfig, title="Global restream configuration."
|
||||||
)
|
)
|
||||||
birdseye: BirdseyeConfig = Field(
|
birdseye: BirdseyeConfig = Field(
|
||||||
@ -895,7 +881,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
"record": ...,
|
"record": ...,
|
||||||
"snapshots": ...,
|
"snapshots": ...,
|
||||||
"rtmp": ...,
|
"rtmp": ...,
|
||||||
"restream": ...,
|
"live": ...,
|
||||||
"objects": ...,
|
"objects": ...,
|
||||||
"motion": ...,
|
"motion": ...,
|
||||||
"detect": ...,
|
"detect": ...,
|
||||||
@ -968,7 +954,12 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
**camera_config.motion.dict(exclude_unset=True),
|
**camera_config.motion.dict(exclude_unset=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Set live view stream if none is set
|
||||||
|
if not camera_config.live.stream_name:
|
||||||
|
camera_config.live.stream_name = name
|
||||||
|
|
||||||
verify_config_roles(camera_config)
|
verify_config_roles(camera_config)
|
||||||
|
verify_valid_live_stream_name(config, camera_config)
|
||||||
verify_old_retain_config(camera_config)
|
verify_old_retain_config(camera_config)
|
||||||
verify_recording_retention(camera_config)
|
verify_recording_retention(camera_config)
|
||||||
verify_recording_segments_setup_with_reasonable_time(camera_config)
|
verify_recording_segments_setup_with_reasonable_time(camera_config)
|
||||||
|
@ -102,17 +102,6 @@ PRESETS_HW_ACCEL_ENCODE = {
|
|||||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {1}",
|
"default": "ffmpeg -hide_banner {0} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {1}",
|
||||||
}
|
}
|
||||||
|
|
||||||
PRESETS_HW_ACCEL_GO2RTC_ENGINE = {
|
|
||||||
"preset-rpi-32-h264": "v4l2m2m",
|
|
||||||
"preset-rpi-64-h264": "v4l2m2m",
|
|
||||||
"preset-intel-vaapi": "vaapi",
|
|
||||||
"preset-intel-qsv-h264": "vaapi", # go2rtc doesn't support qsv
|
|
||||||
"preset-intel-qsv-h265": "vaapi",
|
|
||||||
"preset-amd-vaapi": "vaapi",
|
|
||||||
"preset-nvidia-h264": "cuda",
|
|
||||||
"preset-nvidia-h265": "cuda",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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."""
|
||||||
@ -156,14 +145,6 @@ def parse_preset_hardware_acceleration_encode(arg: Any, input: str, output: str)
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_preset_hardware_acceleration_go2rtc_engine(arg: Any) -> list[str]:
|
|
||||||
"""Return the correct engine for the preset otherwise returns None."""
|
|
||||||
if not isinstance(arg, str):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return PRESETS_HW_ACCEL_GO2RTC_ENGINE.get(arg)
|
|
||||||
|
|
||||||
|
|
||||||
PRESETS_INPUT = {
|
PRESETS_INPUT = {
|
||||||
"preset-http-jpeg-generic": _user_agent_args
|
"preset-http-jpeg-generic": _user_agent_args
|
||||||
+ [
|
+ [
|
||||||
|
@ -415,15 +415,15 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
|||||||
|
|
||||||
for camera, cam_config in config.cameras.items():
|
for camera, cam_config in config.cameras.items():
|
||||||
width = int(
|
width = int(
|
||||||
cam_config.restream.jsmpeg.height
|
cam_config.live.height
|
||||||
* (cam_config.frame_shape[1] / cam_config.frame_shape[0])
|
* (cam_config.frame_shape[1] / cam_config.frame_shape[0])
|
||||||
)
|
)
|
||||||
converters[camera] = FFMpegConverter(
|
converters[camera] = FFMpegConverter(
|
||||||
cam_config.frame_shape[1],
|
cam_config.frame_shape[1],
|
||||||
cam_config.frame_shape[0],
|
cam_config.frame_shape[0],
|
||||||
width,
|
width,
|
||||||
cam_config.restream.jsmpeg.height,
|
cam_config.live.height,
|
||||||
cam_config.restream.jsmpeg.quality,
|
cam_config.live.quality,
|
||||||
)
|
)
|
||||||
broadcasters[camera] = BroadcastThread(
|
broadcasters[camera] = BroadcastThread(
|
||||||
camera, converters[camera], websocket_server
|
camera, converters[camera], websocket_server
|
||||||
@ -436,7 +436,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,
|
config.birdseye.restream,
|
||||||
)
|
)
|
||||||
broadcasters["birdseye"] = BroadcastThread(
|
broadcasters["birdseye"] = BroadcastThread(
|
||||||
"birdseye", converters["birdseye"], websocket_server
|
"birdseye", converters["birdseye"], websocket_server
|
||||||
@ -449,7 +449,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
|||||||
|
|
||||||
birdseye_manager = BirdsEyeFrameManager(config, frame_manager)
|
birdseye_manager = BirdsEyeFrameManager(config, frame_manager)
|
||||||
|
|
||||||
if config.restream.birdseye:
|
if config.birdseye.restream:
|
||||||
birdseye_buffer = frame_manager.create(
|
birdseye_buffer = frame_manager.create(
|
||||||
"birdseye",
|
"birdseye",
|
||||||
birdseye_manager.yuv_shape[0] * birdseye_manager.yuv_shape[1],
|
birdseye_manager.yuv_shape[0] * birdseye_manager.yuv_shape[1],
|
||||||
@ -479,7 +479,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
|||||||
converters[camera].write(frame.tobytes())
|
converters[camera].write(frame.tobytes())
|
||||||
|
|
||||||
if config.birdseye.enabled and (
|
if config.birdseye.enabled and (
|
||||||
config.restream.birdseye
|
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
|
||||||
@ -494,7 +494,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
|||||||
):
|
):
|
||||||
frame_bytes = birdseye_manager.frame.tobytes()
|
frame_bytes = birdseye_manager.frame.tobytes()
|
||||||
|
|
||||||
if config.restream.birdseye:
|
if config.birdseye.restream:
|
||||||
birdseye_buffer[:] = frame_bytes
|
birdseye_buffer[:] = frame_bytes
|
||||||
|
|
||||||
converters["birdseye"].write(frame_bytes)
|
converters["birdseye"].write(frame_bytes)
|
||||||
|
@ -4,42 +4,15 @@
|
|||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from typing import Optional
|
from frigate.config import FrigateConfig
|
||||||
|
|
||||||
from frigate.config import FrigateConfig, RestreamAudioCodecEnum, RestreamVideoCodecEnum
|
|
||||||
from frigate.const import BIRDSEYE_PIPE
|
from frigate.const import BIRDSEYE_PIPE
|
||||||
from frigate.ffmpeg_presets import (
|
from frigate.ffmpeg_presets import (
|
||||||
parse_preset_hardware_acceleration_encode,
|
parse_preset_hardware_acceleration_encode,
|
||||||
parse_preset_hardware_acceleration_go2rtc_engine,
|
|
||||||
)
|
)
|
||||||
from frigate.util import escape_special_characters
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_manual_go2rtc_stream(
|
|
||||||
camera_url: str,
|
|
||||||
aCodecs: list[RestreamAudioCodecEnum],
|
|
||||||
vCodec: RestreamVideoCodecEnum,
|
|
||||||
engine: Optional[str],
|
|
||||||
) -> str:
|
|
||||||
"""Get a manual stream for go2rtc."""
|
|
||||||
stream = f"ffmpeg:{camera_url}"
|
|
||||||
|
|
||||||
for aCodec in aCodecs:
|
|
||||||
stream += f"#audio={aCodec}"
|
|
||||||
|
|
||||||
if vCodec == RestreamVideoCodecEnum.copy:
|
|
||||||
stream += "#video=copy"
|
|
||||||
else:
|
|
||||||
stream += f"#video={vCodec}"
|
|
||||||
|
|
||||||
if engine:
|
|
||||||
stream += f"#hardware={engine}"
|
|
||||||
|
|
||||||
return stream
|
|
||||||
|
|
||||||
|
|
||||||
class RestreamApi:
|
class RestreamApi:
|
||||||
"""Control go2rtc relay API."""
|
"""Control go2rtc relay API."""
|
||||||
|
|
||||||
@ -50,34 +23,7 @@ class RestreamApi:
|
|||||||
"""Add cameras to go2rtc."""
|
"""Add cameras to go2rtc."""
|
||||||
self.relays: dict[str, str] = {}
|
self.relays: dict[str, str] = {}
|
||||||
|
|
||||||
for cam_name, camera in self.config.cameras.items():
|
if self.config.birdseye.restream:
|
||||||
if not camera.restream.enabled:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for input in camera.ffmpeg.inputs:
|
|
||||||
if "restream" in input.roles:
|
|
||||||
if (
|
|
||||||
input.path.startswith("rtsp")
|
|
||||||
and camera.restream.video_encoding
|
|
||||||
== RestreamVideoCodecEnum.copy
|
|
||||||
and camera.restream.audio_encoding
|
|
||||||
== [RestreamAudioCodecEnum.copy]
|
|
||||||
):
|
|
||||||
self.relays[
|
|
||||||
cam_name
|
|
||||||
] = f"{escape_special_characters(input.path)}#backchannel=0"
|
|
||||||
else:
|
|
||||||
# go2rtc only supports rtsp for direct relay, otherwise ffmpeg is used
|
|
||||||
self.relays[cam_name] = get_manual_go2rtc_stream(
|
|
||||||
escape_special_characters(input.path),
|
|
||||||
camera.restream.audio_encoding,
|
|
||||||
camera.restream.video_encoding,
|
|
||||||
parse_preset_hardware_acceleration_go2rtc_engine(
|
|
||||||
self.config.ffmpeg.hwaccel_args
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.config.restream.birdseye:
|
|
||||||
self.relays[
|
self.relays[
|
||||||
"birdseye"
|
"birdseye"
|
||||||
] = f"exec:{parse_preset_hardware_acceleration_encode(self.config.ffmpeg.hwaccel_args, f'-f rawvideo -pix_fmt yuv420p -video_size {self.config.birdseye.width}x{self.config.birdseye.height} -r 10 -i {BIRDSEYE_PIPE}', '-rtsp_transport tcp -f rtsp {output}')}"
|
] = f"exec:{parse_preset_hardware_acceleration_encode(self.config.ffmpeg.hwaccel_args, f'-f rawvideo -pix_fmt yuv420p -video_size {self.config.birdseye.width}x{self.config.birdseye.height} -r 10 -i {BIRDSEYE_PIPE}', '-rtsp_transport tcp -f rtsp {output}')}"
|
||||||
|
@ -621,7 +621,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"path": "rtsp://10.0.0.1:554/video",
|
"path": "rtsp://10.0.0.1:554/video",
|
||||||
"roles": ["detect", "rtmp", "restream"],
|
"roles": ["detect", "rtmp"],
|
||||||
},
|
},
|
||||||
{"path": "rtsp://10.0.0.1:554/record", "roles": ["record"]},
|
{"path": "rtsp://10.0.0.1:554/record", "roles": ["record"]},
|
||||||
]
|
]
|
||||||
@ -883,7 +883,6 @@ class TestConfig(unittest.TestCase):
|
|||||||
|
|
||||||
config = {
|
config = {
|
||||||
"mqtt": {"host": "mqtt"},
|
"mqtt": {"host": "mqtt"},
|
||||||
"restream": {"enabled": False},
|
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"back": {
|
"back": {
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
@ -1096,30 +1095,6 @@ class TestConfig(unittest.TestCase):
|
|||||||
assert runtime_config.cameras["back"].snapshots.height == 150
|
assert runtime_config.cameras["back"].snapshots.height == 150
|
||||||
assert runtime_config.cameras["back"].snapshots.enabled
|
assert runtime_config.cameras["back"].snapshots.enabled
|
||||||
|
|
||||||
def test_global_restream(self):
|
|
||||||
|
|
||||||
config = {
|
|
||||||
"mqtt": {"host": "mqtt"},
|
|
||||||
"restream": {"enabled": True},
|
|
||||||
"cameras": {
|
|
||||||
"back": {
|
|
||||||
"ffmpeg": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"path": "rtsp://10.0.0.1:554/video",
|
|
||||||
"roles": ["detect"],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
frigate_config = FrigateConfig(**config)
|
|
||||||
assert config == frigate_config.dict(exclude_unset=True)
|
|
||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
|
||||||
assert runtime_config.cameras["back"].restream.enabled
|
|
||||||
|
|
||||||
def test_global_rtmp_disabled(self):
|
def test_global_rtmp_disabled(self):
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
@ -1166,56 +1141,6 @@ class TestConfig(unittest.TestCase):
|
|||||||
runtime_config = frigate_config.runtime_config
|
runtime_config = frigate_config.runtime_config
|
||||||
assert not runtime_config.cameras["back"].rtmp.enabled
|
assert not runtime_config.cameras["back"].rtmp.enabled
|
||||||
|
|
||||||
def test_default_restream(self):
|
|
||||||
|
|
||||||
config = {
|
|
||||||
"mqtt": {"host": "mqtt"},
|
|
||||||
"cameras": {
|
|
||||||
"back": {
|
|
||||||
"ffmpeg": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"path": "rtsp://10.0.0.1:554/video",
|
|
||||||
"roles": ["detect"],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
frigate_config = FrigateConfig(**config)
|
|
||||||
assert config == frigate_config.dict(exclude_unset=True)
|
|
||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
|
||||||
assert runtime_config.cameras["back"].restream.enabled
|
|
||||||
|
|
||||||
def test_global_restream_merge(self):
|
|
||||||
|
|
||||||
config = {
|
|
||||||
"mqtt": {"host": "mqtt"},
|
|
||||||
"restream": {"enabled": False},
|
|
||||||
"cameras": {
|
|
||||||
"back": {
|
|
||||||
"ffmpeg": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"path": "rtsp://10.0.0.1:554/video",
|
|
||||||
"roles": ["detect"],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"restream": {
|
|
||||||
"enabled": True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
frigate_config = FrigateConfig(**config)
|
|
||||||
assert config == frigate_config.dict(exclude_unset=True)
|
|
||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
|
||||||
assert runtime_config.cameras["back"].restream.enabled
|
|
||||||
|
|
||||||
def test_global_rtmp_merge(self):
|
def test_global_rtmp_merge(self):
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
@ -1247,7 +1172,6 @@ class TestConfig(unittest.TestCase):
|
|||||||
|
|
||||||
config = {
|
config = {
|
||||||
"mqtt": {"host": "mqtt"},
|
"mqtt": {"host": "mqtt"},
|
||||||
"restream": {"enabled": False},
|
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"back": {
|
"back": {
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
@ -1275,7 +1199,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
|
|
||||||
config = {
|
config = {
|
||||||
"mqtt": {"host": "mqtt"},
|
"mqtt": {"host": "mqtt"},
|
||||||
"restream": {"jsmpeg": {"quality": 4}},
|
"live": {"quality": 4},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"back": {
|
"back": {
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
@ -1293,7 +1217,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
assert config == frigate_config.dict(exclude_unset=True)
|
assert config == frigate_config.dict(exclude_unset=True)
|
||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
runtime_config = frigate_config.runtime_config
|
||||||
assert runtime_config.cameras["back"].restream.jsmpeg.quality == 4
|
assert runtime_config.cameras["back"].live.quality == 4
|
||||||
|
|
||||||
def test_default_live(self):
|
def test_default_live(self):
|
||||||
|
|
||||||
@ -1316,13 +1240,13 @@ class TestConfig(unittest.TestCase):
|
|||||||
assert config == frigate_config.dict(exclude_unset=True)
|
assert config == frigate_config.dict(exclude_unset=True)
|
||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
runtime_config = frigate_config.runtime_config
|
||||||
assert runtime_config.cameras["back"].restream.jsmpeg.quality == 8
|
assert runtime_config.cameras["back"].live.quality == 8
|
||||||
|
|
||||||
def test_global_live_merge(self):
|
def test_global_live_merge(self):
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"mqtt": {"host": "mqtt"},
|
"mqtt": {"host": "mqtt"},
|
||||||
"restream": {"jsmpeg": {"quality": 4, "height": 480}},
|
"live": {"quality": 4, "height": 480},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"back": {
|
"back": {
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
@ -1333,10 +1257,8 @@ class TestConfig(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"restream": {
|
"live": {
|
||||||
"jsmpeg": {
|
"quality": 7,
|
||||||
"quality": 7,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1345,8 +1267,8 @@ class TestConfig(unittest.TestCase):
|
|||||||
assert config == frigate_config.dict(exclude_unset=True)
|
assert config == frigate_config.dict(exclude_unset=True)
|
||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
runtime_config = frigate_config.runtime_config
|
||||||
assert runtime_config.cameras["back"].restream.jsmpeg.quality == 7
|
assert runtime_config.cameras["back"].live.quality == 7
|
||||||
assert runtime_config.cameras["back"].restream.jsmpeg.height == 480
|
assert runtime_config.cameras["back"].live.height == 480
|
||||||
|
|
||||||
def test_global_timestamp_style(self):
|
def test_global_timestamp_style(self):
|
||||||
|
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
"""Test restream.py."""
|
|
||||||
|
|
||||||
from unittest import TestCase, main
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from frigate.config import FrigateConfig
|
|
||||||
from frigate.restream import RestreamApi
|
|
||||||
|
|
||||||
|
|
||||||
class TestRestream(TestCase):
|
|
||||||
def setUp(self) -> None:
|
|
||||||
"""Setup the tests."""
|
|
||||||
self.config = {
|
|
||||||
"mqtt": {"host": "mqtt"},
|
|
||||||
"restream": {"enabled": False},
|
|
||||||
"cameras": {
|
|
||||||
"back": {
|
|
||||||
"ffmpeg": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"path": "rtsp://10.0.0.1:554/video",
|
|
||||||
"roles": ["detect", "restream"],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"restream": {
|
|
||||||
"enabled": True,
|
|
||||||
"audio_encoding": ["copy"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"front": {
|
|
||||||
"ffmpeg": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"path": "http://10.0.0.1:554/video/stream",
|
|
||||||
"roles": ["detect", "restream"],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"restream": {
|
|
||||||
"enabled": True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch("frigate.restream.requests")
|
|
||||||
def test_rtsp_stream(
|
|
||||||
self, mock_request
|
|
||||||
) -> None: # need to ensure restream doesn't try to call API
|
|
||||||
"""Test that the normal rtsp stream is sent plainly."""
|
|
||||||
frigate_config = FrigateConfig(**self.config)
|
|
||||||
restream = RestreamApi(frigate_config)
|
|
||||||
restream.add_cameras()
|
|
||||||
assert restream.relays["back"].startswith("rtsp")
|
|
||||||
|
|
||||||
@patch("frigate.restream.requests")
|
|
||||||
def test_http_stream(
|
|
||||||
self, mock_request
|
|
||||||
) -> None: # need to ensure restream doesn't try to call API
|
|
||||||
"""Test that the http stream is sent via ffmpeg."""
|
|
||||||
frigate_config = FrigateConfig(**self.config)
|
|
||||||
restream = RestreamApi(frigate_config)
|
|
||||||
restream.add_cameras()
|
|
||||||
assert not restream.relays["front"].startswith("rtsp")
|
|
||||||
|
|
||||||
@patch("frigate.restream.requests")
|
|
||||||
def test_restream_codec_change(
|
|
||||||
self, mock_request
|
|
||||||
) -> None: # need to ensure restream doesn't try to call API
|
|
||||||
"""Test that the http stream is sent via ffmpeg."""
|
|
||||||
self.config["cameras"]["front"]["restream"]["video_encoding"] = "h265"
|
|
||||||
self.config["ffmpeg"] = {"hwaccel_args": "preset-nvidia-h264"}
|
|
||||||
frigate_config = FrigateConfig(**self.config)
|
|
||||||
restream = RestreamApi(frigate_config)
|
|
||||||
restream.add_cameras()
|
|
||||||
assert "#hardware=cuda" in restream.relays["front"]
|
|
||||||
assert "#video=h265" in restream.relays["front"]
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(verbosity=2)
|
|
@ -18,7 +18,7 @@ export default function Birdseye() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let player;
|
let player;
|
||||||
if (viewSource == 'mse' && config.restream.birdseye) {
|
if (viewSource == 'mse' && config.birdseye.restream) {
|
||||||
if ('MediaSource' in window) {
|
if ('MediaSource' in window) {
|
||||||
player = (
|
player = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@ -36,7 +36,7 @@ export default function Birdseye() {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (viewSource == 'webrtc' && config.restream.birdseye) {
|
} else if (viewSource == 'webrtc' && config.birdseye.restream) {
|
||||||
player = (
|
player = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="max-w-5xl">
|
<div className="max-w-5xl">
|
||||||
@ -61,7 +61,7 @@ export default function Birdseye() {
|
|||||||
Birdseye
|
Birdseye
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
{config.restream.birdseye && (
|
{config.birdseye.restream && (
|
||||||
<select
|
<select
|
||||||
className="basis-1/8 cursor-pointer rounded dark:bg-slate-800"
|
className="basis-1/8 cursor-pointer rounded dark:bg-slate-800"
|
||||||
value={viewSource}
|
value={viewSource}
|
||||||
|
@ -25,14 +25,15 @@ export default function Camera({ camera }) {
|
|||||||
const [viewMode, setViewMode] = useState('live');
|
const [viewMode, setViewMode] = useState('live');
|
||||||
|
|
||||||
const cameraConfig = config?.cameras[camera];
|
const cameraConfig = config?.cameras[camera];
|
||||||
|
const restreamEnabled = cameraConfig && Object.keys(config.go2rtc.streams).includes(cameraConfig.live.stream_name);
|
||||||
const jsmpegWidth = cameraConfig
|
const jsmpegWidth = cameraConfig
|
||||||
? Math.round(cameraConfig.restream.jsmpeg.height * (cameraConfig.detect.width / cameraConfig.detect.height))
|
? Math.round(cameraConfig.live.height * (cameraConfig.detect.width / cameraConfig.detect.height))
|
||||||
: 0;
|
: 0;
|
||||||
const [viewSource, setViewSource, sourceIsLoaded] = usePersistence(
|
const [viewSource, setViewSource, sourceIsLoaded] = usePersistence(
|
||||||
`${camera}-source`,
|
`${camera}-source`,
|
||||||
getDefaultLiveMode(config, cameraConfig)
|
getDefaultLiveMode(config, cameraConfig)
|
||||||
);
|
);
|
||||||
const sourceValues = cameraConfig && cameraConfig.restream.enabled ? ['mse', 'webrtc', 'jsmpeg'] : ['jsmpeg'];
|
const sourceValues = restreamEnabled ? ['mse', 'webrtc', 'jsmpeg'] : ['jsmpeg'];
|
||||||
const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject);
|
const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject);
|
||||||
|
|
||||||
const handleSetOption = useCallback(
|
const handleSetOption = useCallback(
|
||||||
@ -106,7 +107,7 @@ export default function Camera({ camera }) {
|
|||||||
|
|
||||||
let player;
|
let player;
|
||||||
if (viewMode === 'live') {
|
if (viewMode === 'live') {
|
||||||
if (viewSource == 'mse' && cameraConfig.restream.enabled) {
|
if (viewSource == 'mse' && restreamEnabled) {
|
||||||
if ('MediaSource' in window) {
|
if ('MediaSource' in window) {
|
||||||
player = (
|
player = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@ -124,7 +125,7 @@ export default function Camera({ camera }) {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (viewSource == 'webrtc' && cameraConfig.restream.enabled) {
|
} else if (viewSource == 'webrtc' && restreamEnabled) {
|
||||||
player = (
|
player = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="max-w-5xl">
|
<div className="max-w-5xl">
|
||||||
@ -136,7 +137,7 @@ export default function Camera({ camera }) {
|
|||||||
player = (
|
player = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div>
|
<div>
|
||||||
<JSMpegPlayer camera={camera} width={jsmpegWidth} height={cameraConfig.restream.jsmpeg.height} />
|
<JSMpegPlayer camera={camera} width={jsmpegWidth} height={cameraConfig.live.height} />
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
@ -200,9 +201,9 @@ export default function Camera({ camera }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultLiveMode(config, cameraConfig) {
|
function getDefaultLiveMode(config, cameraConfig, restreamEnabled) {
|
||||||
if (cameraConfig) {
|
if (cameraConfig) {
|
||||||
if (cameraConfig.restream.enabled) {
|
if (restreamEnabled) {
|
||||||
return config.ui.live_mode;
|
return config.ui.live_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user