Add ability to select GPU for ffmpeg (#20455)

* Add ability to set selected GPU for ffmpeg

* Cleanup

* Cleanup

* Improve handling
This commit is contained in:
Nicolas Mowen 2025-10-13 09:59:38 -06:00 committed by GitHub
parent 6e10fc21c3
commit 423693d14d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 20 deletions

View File

@ -268,6 +268,8 @@ ffmpeg:
retry_interval: 10 retry_interval: 10
# Optional: Set tag on HEVC (H.265) recording stream to improve compatibility with Apple players. (default: shown below) # Optional: Set tag on HEVC (H.265) recording stream to improve compatibility with Apple players. (default: shown below)
apple_compatibility: false apple_compatibility: false
# Optional: Set the index of the GPU to use for hardware acceleration. (default: shown below)
gpu: 0
# Optional: Detect configuration # Optional: Detect configuration
# NOTE: Can be overridden at the camera level # NOTE: Can be overridden at the camera level

View File

@ -235,6 +235,7 @@ class CameraConfig(FrigateBaseModel):
self.detect.fps, self.detect.fps,
self.detect.width, self.detect.width,
self.detect.height, self.detect.height,
self.ffmpeg.gpu,
) )
or ffmpeg_input.hwaccel_args or ffmpeg_input.hwaccel_args
or parse_preset_hardware_acceleration_decode( or parse_preset_hardware_acceleration_decode(
@ -242,6 +243,7 @@ class CameraConfig(FrigateBaseModel):
self.detect.fps, self.detect.fps,
self.detect.width, self.detect.width,
self.detect.height, self.detect.height,
self.ffmpeg.gpu,
) )
or camera_arg or camera_arg
or [] or []

View File

@ -67,6 +67,7 @@ class FfmpegConfig(FrigateBaseModel):
default=False, default=False,
title="Set tag on HEVC (H.265) recording stream to improve compatibility with Apple players.", 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 @property
def ffmpeg_path(self) -> str: def ffmpeg_path(self) -> str:

View File

@ -23,36 +23,52 @@ logger = logging.getLogger(__name__)
class LibvaGpuSelector: class LibvaGpuSelector:
"Automatically selects the correct libva GPU." "Automatically selects the correct libva GPU."
_selected_gpu = None _valid_gpus: list[str] | None = None
def get_selected_gpu(self) -> str: def __get_valid_gpus(self) -> None:
"""Get selected libva GPU.""" """Get valid libva GPUs."""
if not os.path.exists("/dev/dri"): if not os.path.exists("/dev/dri"):
return "" self._valid_gpus = []
return
if self._selected_gpu: if self._valid_gpus:
return self._selected_gpu return
devices = list(filter(lambda d: d.startswith("render"), os.listdir("/dev/dri"))) devices = list(filter(lambda d: d.startswith("render"), os.listdir("/dev/dri")))
if not devices: if not devices:
return "/dev/dri/renderD128" self._valid_gpus = ["/dev/dri/renderD128"]
return
if len(devices) < 2: if len(devices) < 2:
self._selected_gpu = f"/dev/dri/{devices[0]}" self._valid_gpus = [f"/dev/dri/{devices[0]}"]
return self._selected_gpu return
self._valid_gpus = []
for device in devices: for device in devices:
check = vainfo_hwaccel(device_name=device) check = vainfo_hwaccel(device_name=device)
logger.debug(f"{device} return vainfo status code: {check.returncode}") logger.debug(f"{device} return vainfo status code: {check.returncode}")
if check.returncode == 0: if check.returncode == 0:
self._selected_gpu = f"/dev/dri/{device}" self._valid_gpus.append(f"/dev/dri/{device}")
return self._selected_gpu
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 "" 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" FPS_VFR_PARAM = "-fps_mode vfr" if LIBAVFORMAT_VERSION_MAJOR >= 59 else "-vsync 2"
TIMEOUT_PARAM = "-timeout" if LIBAVFORMAT_VERSION_MAJOR >= 59 else "-stimeout" TIMEOUT_PARAM = "-timeout" if LIBAVFORMAT_VERSION_MAJOR >= 59 else "-stimeout"
@ -63,13 +79,15 @@ _user_agent_args = [
f"FFmpeg Frigate/{VERSION}", f"FFmpeg Frigate/{VERSION}",
] ]
# Presets for FFMPEG Stream Decoding (detect role)
PRESETS_HW_ACCEL_DECODE = { PRESETS_HW_ACCEL_DECODE = {
"preset-rpi-64-h264": "-c:v:1 h264_v4l2m2m", "preset-rpi-64-h264": "-c:v:1 h264_v4l2m2m",
"preset-rpi-64-h265": "-c:v:1 hevc_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", FFMPEG_HWACCEL_VAAPI: "-hwaccel_flags allow_profile_mismatch -hwaccel vaapi -hwaccel_device {3} -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-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": 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 "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: "-hwaccel cuda -hwaccel_output_format cuda", FFMPEG_HWACCEL_NVIDIA: "{3} -hwaccel cuda -hwaccel_output_format cuda",
"preset-jetson-h264": "-c:v h264_nvmpi -resize {1}x{2}", "preset-jetson-h264": "-c:v h264_nvmpi -resize {1}x{2}",
"preset-jetson-h265": "-c:v hevc_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", 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 FFMPEG_HWACCEL_RKMPP
] ]
# Presets for FFMPEG Stream Scaling (detect role)
PRESETS_HW_ACCEL_SCALE = { PRESETS_HW_ACCEL_SCALE = {
"preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}", "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-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-h264"] = PRESETS_HW_ACCEL_SCALE[FFMPEG_HWACCEL_RKMPP]
PRESETS_HW_ACCEL_SCALE["preset-rk-h265"] = 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 = { PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
"preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m {2}", "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}", "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-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}", "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-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}", "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}", 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 FFMPEG_HWACCEL_RKMPP
] ]
# Presets for FFMPEG Stream Encoding (timelapse feature)
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = { PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
"preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m -pix_fmt yuv420p {2}", "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}", "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, fps: int,
width: int, width: int,
height: int, height: int,
gpu: int,
) -> list[str]: ) -> list[str]:
"""Return the correct preset if in preset format otherwise return None.""" """Return the correct preset if in preset format otherwise return None."""
if not isinstance(arg, str): if not isinstance(arg, str):
@ -200,7 +225,8 @@ def parse_preset_hardware_acceleration_decode(
if not decode: if not decode:
return None 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( def parse_preset_hardware_acceleration_scale(
@ -262,7 +288,7 @@ def parse_preset_hardware_acceleration_encode(
ffmpeg_path, ffmpeg_path,
input, input,
output, output,
_gpu_selector.get_selected_gpu(), _gpu_selector.get_gpu_arg(arg, 0),
) )