Files
frigate/frigate/test/test_ffmpeg_presets.py
T
Nick RogersandGitHub c959df32c9 Apple Silicon Macs via lighter: ONNX detector on the Neural Engine and media engine decode (#24453)
* Run ONNX models on a Mac's Neural Engine through lighter's plugin provider

lighter's lighter.sh/ane device places an ONNX Runtime plugin execution
provider in the container. When it is present, the ONNX session setup
registers it once and opens sessions on its Neural Engine device, the same
place CUDA, ROCm and OpenVINO are chosen, so the onnx detector (and any
model that is not pinned to the CPU) runs there with no configuration. The
hardware probe reports it as an onnx unit.

* docs: hardware decode on an Apple Silicon Mac under lighter

A community section on the video decoding page: lighter's lighter.sh/video
device, hwaccel_args -c:v h264_v4l2m2m, and why the Raspberry Pi presets
decode a single-stream camera in software. The detector docs link to it.

* docs: set the lighter decoder per camera when codecs are mixed

* docs: the ONNX detector on a Mac's Neural Engine under lighter

* Format the Neural Engine provider setup

* Fall back to the default providers when the Neural Engine cannot load a model

* Apple Silicon ffmpeg presets for lighter's media engine, recommended when it is present
2026-09-24 15:37:32 -06:00

157 lines
6.1 KiB
Python

import unittest
from frigate.config import FrigateConfig
from frigate.config.camera.ffmpeg import FFMPEG_INPUT_ARGS_DEFAULT
from frigate.ffmpeg_presets import parse_preset_input
class TestFfmpegPresets(unittest.TestCase):
def setUp(self):
self.default_ffmpeg = {
"mqtt": {"host": "mqtt"},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{
"path": "rtsp://10.0.0.1:554/video",
"roles": ["detect"],
}
],
"output_args": {
"detect": "-f rawvideo -pix_fmt yuv420p",
"record": "-f segment -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c copy -an",
},
},
"detect": {
"height": 1080,
"width": 1920,
"fps": 5,
},
"record": {
"enabled": True,
},
"name": "back",
}
},
}
def test_default_ffmpeg(self):
FrigateConfig(**self.default_ffmpeg)
def test_ffmpeg_hwaccel_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = (
"preset-rpi-64-h264"
)
frigate_config = FrigateConfig(**self.default_ffmpeg)
assert "preset-rpi-64-h264" not in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
assert "-c:v:1 h264_v4l2m2m" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
def test_ffmpeg_hwaccel_apple_preset_decodes_every_stream(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = (
"preset-apple-silicon-h265"
)
frigate_config = FrigateConfig(**self.default_ffmpeg)
cmd = " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
assert "-c:v hevc_v4l2m2m" in cmd
assert "-c:v:1" not in cmd
assert "scale=1920:1080" in cmd
def test_ffmpeg_hwaccel_not_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = (
"-other-hwaccel args"
)
frigate_config = FrigateConfig(**self.default_ffmpeg)
assert "-other-hwaccel args" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
def test_ffmpeg_hwaccel_scale_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = (
"preset-nvidia-h264"
)
self.default_ffmpeg["cameras"]["back"]["detect"] = {
"height": 1920,
"width": 2560,
"fps": 10,
}
frigate_config = FrigateConfig(**self.default_ffmpeg)
assert "preset-nvidia-h264" not in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
assert "fps=10,scale_cuda=w=2560:h=1920,hwdownload,format=nv12" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
def test_default_ffmpeg_input_arg_preset(self):
frigate_config = FrigateConfig(**self.default_ffmpeg)
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = (
"preset-rtsp-generic"
)
frigate_preset_config = FrigateConfig(**self.default_ffmpeg)
assert (
# Ignore global and user_agent args in comparison
frigate_preset_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
== frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
)
def test_ffmpeg_input_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = (
"preset-rtmp-generic"
)
frigate_config = FrigateConfig(**self.default_ffmpeg)
assert "preset-rtmp-generic" not in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
assert (" ".join(parse_preset_input("preset-rtmp-generic", 5))) in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
def test_ffmpeg_input_args_as_string(self):
# Strip user_agent args here to avoid handling quoting issues
defaultArgsList = parse_preset_input(FFMPEG_INPUT_ARGS_DEFAULT, 5)[2::]
argsString = " ".join(defaultArgsList) + ' -some "arg with space"'
argsList = defaultArgsList + ["-some", "arg with space"]
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = argsString
frigate_config = FrigateConfig(**self.default_ffmpeg)
assert set(argsList).issubset(
frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
)
def test_ffmpeg_input_not_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = "-some inputs"
frigate_config = FrigateConfig(**self.default_ffmpeg)
assert "-some inputs" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
def test_ffmpeg_output_record_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["output_args"]["record"] = (
"preset-record-generic-audio-aac"
)
frigate_config = FrigateConfig(**self.default_ffmpeg)
assert "preset-record-generic-audio-aac" not in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
assert "-c:v copy -c:a aac" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
def test_ffmpeg_output_record_not_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["output_args"]["record"] = (
"-some output -segment_time 10"
)
frigate_config = FrigateConfig(**self.default_ffmpeg)
assert "-some output" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
)
if __name__ == "__main__":
unittest.main(verbosity=2)