Repository: blakeblackshear/frigate

Issue:
[HW Accel Support]: ffmpeg presets don't work when device is on /dev/dri/renderD129
### Describe the problem you are having

Using the new `preset-intel-qsv-h264` fails on my machine with 
```ERROR   : Failed to set value '/dev/dri/renderD128' for option 'qsv_device': Input/output error```.

This is likely because the `-qsv_device` ffmpeg option is hardcoded to `/dev/dri/renderD128` here: https://github.com/blakeblackshear/frigate/blob/b6b10e753fa69dd5983761b0063c1227b7bcf997/frigate/ffmpeg_presets.py#L33-L34, but on my system, the Intel iGPU is actually `/dev/dri/renderD129`. 

Removing the `-qsv_device` option from the preset should allow ffmpeg to correctly select the Intel iGPU for QSV. I know this because using `hwaccel_args: -c:v h264_qsv` instead of the preset works fine on my system. 

### Version

0.12.0-66881EB

### Frigate config file

```yaml
cameras:
  Driveway:
    ffmpeg:
      inputs:
        - path: rtsp://192.168.1.202:554/Streaming/Channels/2
          roles:
            - detect
        - path: rtsp://192.168.1.202:554/Streaming/Channels/1
          roles:
            - record
      hwaccel_args: preset-intel-qsv-h264
    detect:
      enabled: True
      width: 704
      height: 480
    record:
      enabled: True
    objects:
      track:
        - person
        - car
```


### docker-compose file or Docker CLI command

```yaml
version: "3.8"
services:
  frigate:
    container_name: frigate
    privileged: true # this may not be necessary for all setups
    restart: unless-stopped
    image: ghcr.io/blakeblackshear/frigate:0.12.0-beta7
    shm_size: "128mb" # update for your cameras based on calculation above
    devices:
#      - /dev/bus/usb:/dev/bus/usb # passes the USB Coral, needs to be modified for other versions
#      - /dev/apex_0:/dev/apex_0 # passes a PCIe Coral, follow driver instructions here https://coral.ai/docs/m2/get-started/#2a-on-linux
      - /dev/dri/renderD128 # for intel hwaccel, needs to be updated for your hardware
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./config.yml:/config/config.yml
#      - /path/to/your/storage:/media/frigate
      - type: bind
        source: /mnt/disks/CCTV
        target: /media/frigate
        bind:
          propagation: slave
      - type: tmpfs # Optional: 1GB of memory, reduces SSD/SD Card wear
        target: /tmp/cache
        tmpfs:
          size: 1000000000
    ports:
      - "5000:5000"
      - "8554:8554" # RTSP feeds
      - "8555:8555/tcp" # WebRTC over tcp
      - "8555:8555/udp" # WebRTC over udp
```


### Relevant log output

```shell
2023-02-06 11:40:42.826378287  [2023-02-06 11:40:42] watchdog.Driveway              ERROR   : Ffmpeg process crashed unexpectedly for Driveway.
2023-02-06 11:40:42.826447820  [2023-02-06 11:40:42] watchdog.Driveway              ERROR   : The following ffmpeg logs include the last 100 lines prior to exit.
2023-02-06 11:40:42.826511890  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : DRM_IOCTL_I915_GEM_APERTURE failed: Invalid argument
2023-02-06 11:40:42.826561490  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : Assuming 131072kB available aperture size.
2023-02-06 11:40:42.826613665  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : May lead to reduced performance or incorrect rendering.
2023-02-06 11:40:42.826659807  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : get chip id failed: -1 [22]
2023-02-06 11:40:42.826709717  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : param: 4, val: 0
2023-02-06 11:40:42.826754771  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : [AVHWDeviceContext @ 0x55cd72b2f480] libva: /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so init failed
2023-02-06 11:40:42.826818262  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : [AVHWDeviceContext @ 0x55cd72b2f480] Failed to initialise VAAPI connection: 18 (invalid parameter).
2023-02-06 11:40:42.826871808  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : Device creation failed: -5.
2023-02-06 11:40:42.826916131  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : Failed to set value '/dev/dri/renderD128' for option 'qsv_device': Input/output error
2023-02-06 11:40:42.826967495  [2023-02-06 11:40:42] ffmpeg.Driveway.detect         ERROR   : Error parsing global options: Input/output error
2023-02-06 11:40:42.833931570  [2023-02-06 11:40:42] watchdog.Driveway              ERROR   : No new recording segments were created for Driveway in the last 120s. restarting the ffmpeg record process...
2023-02-06 11:40:42.834004494  [2023-02-06 11:40:42] watchdog.Driveway              INFO    : Terminating the existing ffmpeg process...
2023-02-06 11:40:42.834064004  [2023-02-06 11:40:42] watchdog.Driveway              INFO    : Waiting for ffmpeg to exit gracefully...
```


### FFprobe output from your camera

```shell
"[\n  {\n    \"return_code\": 0,\n    \"stderr\": {},\n    \"stdout\": {\n      \"programs\": [],\n      \"streams\": [\n        {\n          \"avg_frame_rate\": \"6/1\",\n          \"codec_long_name\": \"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10\",\n          \"height\": 480,\n          \"width\": 704\n        }\n      ]\n    }\n  },\n  {\n    \"return_code\": 0,\n    \"stderr\": {},\n    \"stdout\": {\n      \"programs\": [],\n      \"streams\": [\n        {\n          \"avg_frame_rate\": \"8/1\",\n          \"codec_long_name\": \"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10\",\n          \"height\": 1536,\n          \"width\": 2048\n        }\n      ]\n    }\n  }\n]"
```


### Operating system

UNRAID

### Install method

Docker Compose

### Network connection

Wired

### Camera make and model

Hikvision

### Any other information that may be helpful

_No response_


Discussion:
For the way we are using hwaccel, the device does need to be specified to ffmpeg. We are doing more than just using -c:v qsv. 

We will likely just need to add a config option to set which dri device is used. 
More information: my Intel iGPU is on `renderD129` because I also have an NVIDIA GPU installed that is taking up the `renderD128` spot. 

```
# ls -l /sys/class/drm/renderD*/device/driver
lrwxrwxrwx 1 root root 0 Feb  6 07:46 /sys/class/drm/renderD128/device/driver -> ../../../../bus/pci/drivers/nvidia/
lrwxrwxrwx 1 root root 0 Feb  6 07:46 /sys/class/drm/renderD129/device/driver -> ../../../bus/pci/drivers/i915/
```

Resolving patch:
diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py
--- a/frigate/ffmpeg_presets.py
+++ b/frigate/ffmpeg_presets.py
@@ -1,14 +1,52 @@
 """Handles inserting and maintaining ffmpeg presets."""
 
+import logging
 import os
 
 from typing import Any
 
 from frigate.version import VERSION
 from frigate.const import BTBN_PATH
+from frigate.util import vainfo_hwaccel
+
+
+logger = logging.getLogger(__name__)
+
+
+class LibvaGpuSelector:
+    "Automatically selects the correct libva GPU."
+
+    _selected_gpu = None
+
+    def get_selected_gpu(self) -> str:
+        """Get selected libva GPU."""
+        if not os.path.exists("/dev/dri"):
+            return ""
+
+        if self._selected_gpu:
+            return self._selected_gpu
+
+        devices = list(filter(lambda d: d.startswith("render"), os.listdir("/dev/dri")))
+
+        if len(devices) < 2:
+            self._selected_gpu = "/dev/dri/renderD128"
+            return self._selected_gpu
+
+        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
+
+        return ""
+
 
 TIMEOUT_PARAM = "-timeout" if os.path.exists(BTBN_PATH) else "-stimeout"
 
+_gpu_selector = LibvaGpuSelector()
 _user_agent_args = [
     "-user_agent",
     f"FFmpeg Frigate/{VERSION}",
@@ -23,7 +61,7 @@
         "-hwaccel",
         "vaapi",
         "-hwaccel_device",
-        "/dev/dri/renderD128",
+        _gpu_selector.get_selected_gpu(),
         "-hwaccel_output_format",
         "vaapi",
     ],
@@ -31,7 +69,7 @@
         "-hwaccel",
         "qsv",
         "-qsv_device",
-        "/dev/dri/renderD128",
+        _gpu_selector.get_selected_gpu(),
         "-hwaccel_output_format",
         "qsv",
         "-c:v",
@@ -43,7 +81,7 @@
         "-hwaccel",
         "qsv",
         "-qsv_device",
-        "/dev/dri/renderD128",
+        _gpu_selector.get_selected_gpu(),
         "-hwaccel_output_format",
         "qsv",
         "-c:v",
@@ -95,7 +133,7 @@
 PRESETS_HW_ACCEL_ENCODE = {
     "preset-rpi-32-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -g 50 -bf 0 {1}",
     "preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -g 50 -bf 0 {1}",
-    "preset-vaapi": "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi {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-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}",
@@ -143,6 +181,7 @@ def parse_preset_hardware_acceleration_encode(arg: Any, input: str, output: str)
     return PRESETS_HW_ACCEL_ENCODE.get(arg, PRESETS_HW_ACCEL_ENCODE["default"]).format(
         input,
         output,
+        _gpu_selector.get_selected_gpu(),
     )
 
 
diff --git a/frigate/util.py b/frigate/util.py
--- a/frigate/util.py
+++ b/frigate/util.py
@@ -14,7 +14,7 @@
 from collections import Counter
 from collections.abc import Mapping
 from multiprocessing import shared_memory
-from typing import Any, AnyStr, Tuple
+from typing import Any, AnyStr, Optional, Tuple
 
 import cv2
 import numpy as np
@@ -976,9 +976,13 @@ def ffprobe_stream(path: str) -> sp.CompletedProcess:
     return sp.run(ffprobe_cmd, capture_output=True)
 
 
-def vainfo_hwaccel() -> sp.CompletedProcess:
+def vainfo_hwaccel(device_name: Optional[str] = None) -> sp.CompletedProcess:
     """Run vainfo."""
-    ffprobe_cmd = ["vainfo"]
+    ffprobe_cmd = (
+        ["vainfo"]
+        if not device_name
+        else ["vainfo", "--display", "drm", "--device", f"/dev/dri/{device_name}"]
+    )
     return sp.run(ffprobe_cmd, capture_output=True)
 
 


Test patch: