From 79cba8347ec725a63f534c72b220dc862a39f9c9 Mon Sep 17 00:00:00 2001 From: User Joe <5625795+jtrotsky@users.noreply.github.com> Date: Wed, 8 Jul 2026 08:36:39 +1200 Subject: [PATCH] immich hardware transcoding --- docs/guides/hardware-transcoding.md | 104 ++++++++++++++++++++++++++++ scripts/templates/zensical.toml.j2 | 1 + 2 files changed, 105 insertions(+) create mode 100644 docs/guides/hardware-transcoding.md diff --git a/docs/guides/hardware-transcoding.md b/docs/guides/hardware-transcoding.md new file mode 100644 index 0000000..83f633b --- /dev/null +++ b/docs/guides/hardware-transcoding.md @@ -0,0 +1,104 @@ +--- +title: "Hardware Transcoding (VAAPI) on FreeBSD Containers" +description: "Enable Intel iGPU hardware video transcoding in FreeBSD jails and podman containers: DRM driver setup, devfs rules for /dev/dri passthrough, and app configuration for Jellyfin and Immich." +--- + +# Hardware Transcoding (VAAPI) + +Intel iGPUs can transcode video in hardware on FreeBSD via VAAPI. Media images +(Jellyfin, Emby, Immich) ship ffmpeg with VAAPI support and the Intel iHD media +driver (`libva-intel-media-driver`), so the only work is on the host: load the +GPU driver and expose the render node to the container. + +Measured on an Intel N150 (Twin Lake): 1080p30 H.264 → HEVC at ~90 fps +(3× realtime) using ~2% of the CPU time a software encode needs — where the +same box cannot even software-encode 1080p30 HEVC in realtime. + +## 1. Host: DRM driver and firmware + +Install the DRM kernel module and the GPU firmware for your iGPU generation, +then load it: + +```sh +pkg install drm-latest-kmod gpu-firmware-intel-kmod-alderlake gpu-firmware-intel-kmod-tigerlake +kldload i915kms +``` + +!!! danger "Install firmware before loading the module" + Loading `i915kms` without the matching GuC/HuC firmware packages present + can panic the machine. Install the firmware first, and only add + `i915kms` to `kld_list` in `rc.conf` once a manual load has proven stable. + +Newer chips (e.g. Twin Lake / N150, PCI ID `0x46d4`) need `drm-latest-kmod`; +`drm-66-kmod` does not know them. Verify the load with `ls /dev/dri/` — you +should see `renderD128`. + +## 2. Expose the render node + +### Classic jails + +Add a devfs ruleset to `/etc/devfs.rules`: + +``` +[devfsrules_jails_gpu=61182] +add include $devfsrules_hide_all +add include $devfsrules_unhide_basic +add include $devfsrules_unhide_login +add path 'bpf*' unhide +add path 'dri' unhide +add path 'dri/*' unhide mode 0666 +add path 'drm*' unhide mode 0666 +``` + +Configure the jail with `devfs_ruleset = 61182`. + +### Podman + +Pass the device through (CLI `--device /dev/dri/renderD128`, or in compose): + +```yaml +services: + jellyfin: + devices: + - /dev/dri/renderD128 +``` + +!!! warning "podman devfs bug — post-start fix required" + Current podman on FreeBSD unhides the device node inside the container's + devfs but not its parent `drm` directory, so the node stays unreachable. + Until this is fixed upstream, apply the missing rules after the container + starts (as root, using the container's name): + + ```sh + ROOT=$(podman mount ) + devfs -m "$ROOT/dev" rule apply path drm unhide + devfs -m "$ROOT/dev" rule apply path 'drm/*' unhide mode 0666 + devfs -m "$ROOT/dev" rule apply path dri unhide + devfs -m "$ROOT/dev" rule apply path 'dri/*' unhide + podman unmount + ``` + + `mode 0666` matters: daemonless images run as the non-root `bsd` user, and + podman's own device rule creates the node root-only (0600). + +## 3. Configure the application + +| App | Setting | +|-----|---------| +| Jellyfin | Dashboard → Playback → Transcoding → Hardware acceleration: **VAAPI**, device `/dev/dri/renderD128` | +| Immich | Administration → Settings → Video Transcoding → Hardware Acceleration: **VAAPI** | + +Use VAAPI, not QSV — the QSV stack is Linux-only. + +## 4. Verify + +Inside the container (images that ship `libva-utils`): + +```sh +podman exec vainfo --display drm --device /dev/dri/renderD128 +``` + +A working stack prints the iHD driver version and a list of +`VAProfile…/VAEntrypoint…` lines; `VAEntrypointEncSliceLP` entries are the +low-power hardware encoders. If `vainfo` fails to open the device, re-check +the devfs rules and node permissions (`ls -la /dev/drm/` inside the container). diff --git a/scripts/templates/zensical.toml.j2 b/scripts/templates/zensical.toml.j2 index bde1323..9f0e9bc 100644 --- a/scripts/templates/zensical.toml.j2 +++ b/scripts/templates/zensical.toml.j2 @@ -66,6 +66,7 @@ nav = [ { "Networking" = "guides/networking.md" }, { "ZFS Storage" = "guides/zfs.md" }, { "ocijail Patch" = "guides/ocijail-patch.md" }, + { "Hardware Transcoding" = "guides/hardware-transcoding.md" }, { "Operations" = "guides/operations.md" }, { "Contributing" = [ { "Quickstart" = "guides/contributing-quickstart.md" },