diff --git a/frigate/config.py b/frigate/config.py index 3f68f302a..98d4e87fb 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -39,7 +39,7 @@ from frigate.util.builtin import ( load_config_with_no_duplicates, ) from frigate.util.image import create_mask -from frigate.util.services import get_video_properties +from frigate.util.services import auto_detect_hwaccel, get_video_properties logger = logging.getLogger(__name__) @@ -599,8 +599,8 @@ class FfmpegConfig(FrigateBaseModel): global_args: Union[str, List[str]] = Field( default=FFMPEG_GLOBAL_ARGS_DEFAULT, title="Global FFmpeg arguments." ) - hwaccel_args: Union[str, List[str]] = Field( - default_factory=list, title="FFmpeg hardware acceleration arguments." + hwaccel_args: Optional[Union[str, List[str]]] = Field( + default=None, title="FFmpeg hardware acceleration arguments." ) input_args: Union[str, List[str]] = Field( default=FFMPEG_INPUT_ARGS_DEFAULT, title="FFmpeg input arguments." @@ -1097,6 +1097,10 @@ class FrigateConfig(FrigateBaseModel): elif config.objects.filters[attribute].min_score == 0.5: config.objects.filters[attribute].min_score = 0.7 + # auto detect hwaccel args + if config.ffmpeg.hwaccel_args is None: + config.ffmpeg.hwaccel_args = auto_detect_hwaccel() + # Global config to propagate down to camera level global_config = config.dict( include={ diff --git a/frigate/const.py b/frigate/const.py index d00221341..97e33b689 100644 --- a/frigate/const.py +++ b/frigate/const.py @@ -35,6 +35,11 @@ AUDIO_MAX_BIT_RANGE = 32768.0 AUDIO_SAMPLE_RATE = 16000 AUDIO_MIN_CONFIDENCE = 0.5 +# Ffmpeg Presets + +FFMPEG_HWACCEL_NVIDIA = "preset-nvidia" +FFMPEG_HWACCEL_VAAPI = "preset-vaapi" + # Regex Consts REGEX_CAMERA_NAME = r"^[a-zA-Z0-9_-]+$" diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 82ca92dda..96314e6a5 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -5,6 +5,7 @@ import os from enum import Enum from typing import Any +from frigate.const import FFMPEG_HWACCEL_NVIDIA, FFMPEG_HWACCEL_VAAPI from frigate.util.services import vainfo_hwaccel from frigate.version import VERSION @@ -62,55 +63,72 @@ _user_agent_args = [ PRESETS_HW_ACCEL_DECODE = { "preset-rpi-64-h264": "-c:v:1 h264_v4l2m2m", "preset-rpi-64-h265": "-c:v:1 hevc_v4l2m2m", - "preset-vaapi": f"-hwaccel_flags allow_profile_mismatch -hwaccel vaapi -hwaccel_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format vaapi", + FFMPEG_HWACCEL_VAAPI: f"-hwaccel_flags allow_profile_mismatch -hwaccel vaapi -hwaccel_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format vaapi", "preset-intel-qsv-h264": f"-hwaccel qsv -qsv_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format qsv -c:v h264_qsv", "preset-intel-qsv-h265": f"-load_plugin hevc_hw -hwaccel qsv -qsv_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format qsv -c:v hevc_qsv", - "preset-nvidia-h264": "-hwaccel cuda -hwaccel_output_format cuda", - "preset-nvidia-h265": "-hwaccel cuda -hwaccel_output_format cuda", - "preset-nvidia-mjpeg": "-hwaccel cuda -hwaccel_output_format cuda", + FFMPEG_HWACCEL_NVIDIA: "-hwaccel cuda -hwaccel_output_format cuda", "preset-jetson-h264": "-c:v h264_nvmpi -resize {1}x{2}", "preset-jetson-h265": "-c:v hevc_nvmpi -resize {1}x{2}", "preset-rk-h264": "-c:v h264_rkmpp_decoder", "preset-rk-h265": "-c:v hevc_rkmpp_decoder", } +PRESETS_HW_ACCEL_DECODE["preset-nvidia-h264"] = PRESETS_HW_ACCEL_DECODE[ + FFMPEG_HWACCEL_NVIDIA +] +PRESETS_HW_ACCEL_DECODE["preset-nvidia-h265"] = PRESETS_HW_ACCEL_DECODE[ + FFMPEG_HWACCEL_NVIDIA +] +PRESETS_HW_ACCEL_DECODE["preset-nvidia-mjpeg"] = PRESETS_HW_ACCEL_DECODE[ + FFMPEG_HWACCEL_NVIDIA +] PRESETS_HW_ACCEL_SCALE = { "preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}", "preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}", - "preset-vaapi": "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", + FFMPEG_HWACCEL_VAAPI: "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", "preset-intel-qsv-h264": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", "preset-intel-qsv-h265": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", - "preset-nvidia-h264": "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", - "preset-nvidia-h265": "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", + FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", "preset-jetson-h264": "-r {0}", # scaled in decoder "preset-jetson-h265": "-r {0}", # scaled in decoder "preset-rk-h264": "-r {0} -vf fps={0},scale={1}:{2}", "preset-rk-h265": "-r {0} -vf fps={0},scale={1}:{2}", "default": "-r {0} -vf fps={0},scale={1}:{2}", } +PRESETS_HW_ACCEL_SCALE["preset-nvidia-h264"] = PRESETS_HW_ACCEL_SCALE[ + FFMPEG_HWACCEL_NVIDIA +] +PRESETS_HW_ACCEL_SCALE["preset-nvidia-h265"] = PRESETS_HW_ACCEL_SCALE[ + FFMPEG_HWACCEL_NVIDIA +] PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = { "preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m {1}", "preset-rpi-64-h265": "ffmpeg -hide_banner {0} -c:v hevc_v4l2m2m {1}", - "preset-vaapi": "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {2} {0} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {1}", + FFMPEG_HWACCEL_VAAPI: "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {2} {0} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {1}", "preset-intel-qsv-h264": "ffmpeg -hide_banner {0} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {1}", "preset-intel-qsv-h265": "ffmpeg -hide_banner {0} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {1}", - "preset-nvidia-h264": "ffmpeg -hide_banner {0} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {1}", - "preset-nvidia-h265": "ffmpeg -hide_banner {0} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {1}", + FFMPEG_HWACCEL_NVIDIA: "ffmpeg -hide_banner {0} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {1}", "preset-jetson-h264": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}", "preset-jetson-h265": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}", "preset-rk-h264": "ffmpeg -hide_banner {0} -c:v h264_rkmpp_encoder -profile high {1}", "preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp_encoder -profile high {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_ENCODE_BIRDSEYE[ + "preset-nvidia-h264" +] = PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA] +PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[ + "preset-nvidia-h265" +] = PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA] PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = { "preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -pix_fmt yuv420p {1}", "preset-rpi-64-h265": "ffmpeg -hide_banner {0} -c:v hevc_v4l2m2m -pix_fmt yuv420p {1}", - "preset-vaapi": "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {2} {0} -c:v h264_vaapi {1}", + FFMPEG_HWACCEL_VAAPI: "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {2} {0} -c:v h264_vaapi {1}", "preset-intel-qsv-h264": "ffmpeg -hide_banner {0} -c:v h264_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {1}", "preset-intel-qsv-h265": "ffmpeg -hide_banner {0} -c:v hevc_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {1}", - "preset-nvidia-h264": "ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {0} -c:v h264_nvenc {1}", + FFMPEG_HWACCEL_NVIDIA: "ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {0} -c:v h264_nvenc {1}", "preset-nvidia-h265": "ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {0} -c:v hevc_nvenc {1}", "preset-jetson-h264": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}", "preset-jetson-h265": "ffmpeg -hide_banner {0} -c:v hevc_nvmpi -profile high {1}", @@ -118,6 +136,9 @@ PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = { "preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp_encoder -profile high {1}", "default": "ffmpeg -hide_banner {0} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {1}", } +PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[ + "preset-nvidia-h264" +] = PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[FFMPEG_HWACCEL_NVIDIA] # encoding of previews is only done on CPU due to comparable encode times and better quality from libx264 PRESETS_HW_ACCEL_ENCODE_PREVIEW = { diff --git a/frigate/util/services.py b/frigate/util/services.py index 28421cef3..55ca7ac57 100644 --- a/frigate/util/services.py +++ b/frigate/util/services.py @@ -13,7 +13,9 @@ from typing import Optional import cv2 import psutil import py3nvml.py3nvml as nvml +import requests +from frigate.const import FFMPEG_HWACCEL_NVIDIA, FFMPEG_HWACCEL_VAAPI from frigate.util.builtin import escape_special_characters logger = logging.getLogger(__name__) @@ -371,6 +373,36 @@ def vainfo_hwaccel(device_name: Optional[str] = None) -> sp.CompletedProcess: return sp.run(ffprobe_cmd, capture_output=True) +def auto_detect_hwaccel() -> str: + """Detect hwaccel args by default.""" + try: + cuda = False + vaapi = False + resp = requests.get("http://192.168.50.106:1984/api/ffmpeg/hardware", timeout=3) + + if resp.status_code == 200: + data: dict[str, list[dict[str, str]]] = resp.json() + for source in data.get("sources", []): + if "cuda" in source.get("url", "") and source.get("name") == "OK": + cuda = True + + if "vaapi" in source.get("url", "") and source.get("name") == "OK": + vaapi = True + except requests.RequestException: + pass + + if cuda: + logger.info("Automatically detected nvidia hwaccel for video decoding") + return FFMPEG_HWACCEL_NVIDIA + + if vaapi: + logger.info("Automatically detected vaapi hwaccel for video decoding") + return FFMPEG_HWACCEL_VAAPI + + logger.warning("Did not detect hwaccel, using a GPU for accelerated video decoding is highly recommended") + return "" + + async def get_video_properties(url, get_duration=False) -> dict[str, any]: async def calculate_duration(video: Optional[any]) -> float: duration = None