From 423693d14d81506ffae36440073fcbbf66f27504 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 13 Oct 2025 09:59:38 -0600 Subject: [PATCH] Add ability to select GPU for ffmpeg (#20455) * Add ability to set selected GPU for ffmpeg * Cleanup * Cleanup * Improve handling --- docs/docs/configuration/reference.md | 2 + frigate/config/camera/camera.py | 2 + frigate/config/camera/ffmpeg.py | 1 + frigate/ffmpeg_presets.py | 66 +++++++++++++++++++--------- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index 67d2515cb..4933776d2 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -268,6 +268,8 @@ ffmpeg: retry_interval: 10 # Optional: Set tag on HEVC (H.265) recording stream to improve compatibility with Apple players. (default: shown below) apple_compatibility: false + # Optional: Set the index of the GPU to use for hardware acceleration. (default: shown below) + gpu: 0 # Optional: Detect configuration # NOTE: Can be overridden at the camera level diff --git a/frigate/config/camera/camera.py b/frigate/config/camera/camera.py index 821b2a16d..967a69427 100644 --- a/frigate/config/camera/camera.py +++ b/frigate/config/camera/camera.py @@ -235,6 +235,7 @@ class CameraConfig(FrigateBaseModel): self.detect.fps, self.detect.width, self.detect.height, + self.ffmpeg.gpu, ) or ffmpeg_input.hwaccel_args or parse_preset_hardware_acceleration_decode( @@ -242,6 +243,7 @@ class CameraConfig(FrigateBaseModel): self.detect.fps, self.detect.width, self.detect.height, + self.ffmpeg.gpu, ) or camera_arg or [] diff --git a/frigate/config/camera/ffmpeg.py b/frigate/config/camera/ffmpeg.py index dd65fdcd4..2c1e4cdca 100644 --- a/frigate/config/camera/ffmpeg.py +++ b/frigate/config/camera/ffmpeg.py @@ -67,6 +67,7 @@ class FfmpegConfig(FrigateBaseModel): default=False, title="Set tag on HEVC (H.265) recording stream to improve compatibility with Apple players.", ) + gpu: int = Field(default=0, title="GPU index to use for hardware acceleration.") @property def ffmpeg_path(self) -> str: diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index d23c8b372..ff14be2ad 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -23,35 +23,51 @@ logger = logging.getLogger(__name__) class LibvaGpuSelector: "Automatically selects the correct libva GPU." - _selected_gpu = None + _valid_gpus: list[str] | None = None - def get_selected_gpu(self) -> str: - """Get selected libva GPU.""" + def __get_valid_gpus(self) -> None: + """Get valid libva GPUs.""" if not os.path.exists("/dev/dri"): - return "" + self._valid_gpus = [] + return - if self._selected_gpu: - return self._selected_gpu + if self._valid_gpus: + return devices = list(filter(lambda d: d.startswith("render"), os.listdir("/dev/dri"))) if not devices: - return "/dev/dri/renderD128" + self._valid_gpus = ["/dev/dri/renderD128"] + return if len(devices) < 2: - self._selected_gpu = f"/dev/dri/{devices[0]}" - return self._selected_gpu + self._valid_gpus = [f"/dev/dri/{devices[0]}"] + return + self._valid_gpus = [] for device in devices: check = vainfo_hwaccel(device_name=device) logger.debug(f"{device} return vainfo status code: {check.returncode}") if check.returncode == 0: - self._selected_gpu = f"/dev/dri/{device}" - return self._selected_gpu + self._valid_gpus.append(f"/dev/dri/{device}") - return "" + def get_gpu_arg(self, preset: str, gpu: int) -> str: + if "nvidia" in preset: + return f"-hwaccel_device {gpu}" + + if self._valid_gpus is None: + self.__get_valid_gpus() + + if not self._valid_gpus: + return "" + + if gpu <= len(self._valid_gpus): + return f"-hwaccel_device {self._valid_gpus[gpu]}" + else: + logger.warning(f"Invalid GPU index {gpu}, using first valid GPU") + return f"-hwaccel_device {self._valid_gpus[0]}" FPS_VFR_PARAM = "-fps_mode vfr" if LIBAVFORMAT_VERSION_MAJOR >= 59 else "-vsync 2" @@ -63,13 +79,15 @@ _user_agent_args = [ f"FFmpeg Frigate/{VERSION}", ] +# Presets for FFMPEG Stream Decoding (detect role) + PRESETS_HW_ACCEL_DECODE = { "preset-rpi-64-h264": "-c:v:1 h264_v4l2m2m", "preset-rpi-64-h265": "-c:v:1 hevc_v4l2m2m", - 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{' -bsf:v dump_extra' if LIBAVFORMAT_VERSION_MAJOR >= 61 else ''}", # https://trac.ffmpeg.org/ticket/9766#comment:17 - "preset-intel-qsv-h265": f"-load_plugin hevc_hw -hwaccel qsv -qsv_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format qsv{' -bsf:v dump_extra' if LIBAVFORMAT_VERSION_MAJOR >= 61 else ''}", # https://trac.ffmpeg.org/ticket/9766#comment:17 - FFMPEG_HWACCEL_NVIDIA: "-hwaccel cuda -hwaccel_output_format cuda", + FFMPEG_HWACCEL_VAAPI: "-hwaccel_flags allow_profile_mismatch -hwaccel vaapi -hwaccel_device {3} -hwaccel_output_format vaapi", + "preset-intel-qsv-h264": "-hwaccel qsv -qsv_device {3} -hwaccel_output_format qsv -c:v h264_qsv{' -bsf:v dump_extra' if LIBAVFORMAT_VERSION_MAJOR >= 61 else ''}", # https://trac.ffmpeg.org/ticket/9766#comment:17 + "preset-intel-qsv-h265": "-load_plugin hevc_hw -hwaccel qsv -qsv_device {3} -hwaccel_output_format qsv{' -bsf:v dump_extra' if LIBAVFORMAT_VERSION_MAJOR >= 61 else ''}", # https://trac.ffmpeg.org/ticket/9766#comment:17 + FFMPEG_HWACCEL_NVIDIA: "{3} -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}", f"{FFMPEG_HWACCEL_RKMPP}-no-dump_extra": "-hwaccel rkmpp -hwaccel_output_format drm_prime", @@ -97,6 +115,8 @@ PRESETS_HW_ACCEL_DECODE["preset-rk-h265"] = PRESETS_HW_ACCEL_DECODE[ FFMPEG_HWACCEL_RKMPP ] +# Presets for FFMPEG Stream Scaling (detect role) + 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}", @@ -125,13 +145,15 @@ PRESETS_HW_ACCEL_SCALE[f"{FFMPEG_HWACCEL_RKMPP}-no-dump_extra"] = ( PRESETS_HW_ACCEL_SCALE["preset-rk-h264"] = PRESETS_HW_ACCEL_SCALE[FFMPEG_HWACCEL_RKMPP] PRESETS_HW_ACCEL_SCALE["preset-rk-h265"] = PRESETS_HW_ACCEL_SCALE[FFMPEG_HWACCEL_RKMPP] +# Presets for FFMPEG Stream Encoding (birdseye feature) + PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = { "preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m {2}", "preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m {2}", - FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}", + FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}", "preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v main -level:v 4.1 -async_depth:v 1 {2}", - FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", + FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} {3} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", "preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}", "preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile main {2}", FFMPEG_HWACCEL_RKMPP: "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}", @@ -153,6 +175,8 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-rk-h264"] = PRESETS_HW_ACCEL_ENCODE_BIR FFMPEG_HWACCEL_RKMPP ] +# Presets for FFMPEG Stream Encoding (timelapse feature) + PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = { "preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m -pix_fmt yuv420p {2}", "preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m -pix_fmt yuv420p {2}", @@ -190,6 +214,7 @@ def parse_preset_hardware_acceleration_decode( fps: int, width: int, height: int, + gpu: int, ) -> list[str]: """Return the correct preset if in preset format otherwise return None.""" if not isinstance(arg, str): @@ -200,7 +225,8 @@ def parse_preset_hardware_acceleration_decode( if not decode: return None - return decode.format(fps, width, height).split(" ") + gpu_arg = _gpu_selector.get_gpu_arg(arg, gpu) + return decode.format(fps, width, height, gpu_arg).split(" ") def parse_preset_hardware_acceleration_scale( @@ -262,7 +288,7 @@ def parse_preset_hardware_acceleration_encode( ffmpeg_path, input, output, - _gpu_selector.get_selected_gpu(), + _gpu_selector.get_gpu_arg(arg, 0), )