Files
esh-pfi-infrastructure/docs/runbooks/plex-arc-vaapi-jammy.md
T
vh 88e171bea6 fix(esh): Plex hardware transcoding on the Arc A580, and the two ways it hid
Plex never hardware-transcoded on esh-pve-nas LXC 105 despite correct passthrough,
cgroups, group membership, authenticated HuC firmware, a lifetime Plex Pass,
HardwareAcceleratedCodecs=1, and the Arc already selected as HardwareDevicePath.

Root cause sat below all of that: intel-media-va-driver 22.3.1 (Apr 2023, stock
jammy) predates Arc/DG2 support and exports only __vaDriverInit_1_14, against the
libva 2.22 that Plex bundles and loads via RPATH rather than the system one. A
half-finished prior attempt at the same fix was also present -- libva and libva-drm
hand-installed at 2.22 with libva-x11 left at 2.14, breaking every X11 VA-API
consumer on va_fool_postp.

Fixed with Intel's client-GPU repo: iHD 24.3.4 (__vaDriverInit_1_22, an exact ABI
match) plus a consistent libva 2.22.0.2-87 set, which also brings the orphaned
manual install back under dpkg. The repo track is rolling, so the six packages are
pinned in /etc/apt/preferences.d/intel-gpu-pin and apt-mark held; verified by a
simulated upgrade that moves 152 packages and touches none of them.

Two findings worth more than the fix:

pct snapshot refuses on a guest with a bind mount and still exits 0, so a script
guarding a change with it proceeds without the rollback point it believes it has.
The ZFS dataset snapshot is the working path, verified by reading it back.

A synthetic Plex Transcoder invocation is not a valid test of Plex's transcode
path. Plex bundles its own libc among 61 libraries; the harness produced three
distinct failure modes that were artifacts of not reproducing that runtime, and it
failed identically before and after a fix that worked. With no positive control its
negatives carried no information. Only a forced transcode settles it, and PASS is
recognisable by Plex naming the device. The original empty decoder/encoder line was
an absence of evidence rather than evidence of failure -- TranscodeSession was 0.

Jellyfin LXC 107 has the same stale stack and the same Arc available; left alone
per the operator, and it ships its own ffmpeg so this may not transfer verbatim.
2026-09-11 22:03:52 -07:00

5.5 KiB

Plex hardware transcoding on the Arc A580 (esh-pve-nas, LXC 105)

Fixed 2026-09-11. Plex never hardware-transcoded on the Arc A580 despite every setting being correct. The fault was one layer below the settings, and the whole diagnosis is reusable for any Intel-GPU VA-API consumer on Ubuntu 22.04.

The symptom, and why it misleads

Plex logged, on every transcode decision:

TPU: hardware transcoding: final decoder: , final encoder:

Both fields empty. That reads like "hardware transcoding failed", but on its own it is also what you get when nothing transcoded at all — and TranscodeSession count was zero, so the log was not evidence of failure. Twenty of those lines landed inside one second on one thread: a capability probe loop, not twenty sessions.

Do not diagnose this from configuration. Every one of these read correct while hardware transcoding was dead:

checked state
Arc A580 present, DMC/GuC/HuC firmware HuC "authenticated for all workloads"
LXC passthrough: both render nodes + cgroup allows
plex user in video(44) + render(104)
Plex 1.43.2, lifetime Plex Pass
HardwareAcceleratedCodecs=1 (via API, not the file)
HardwareDevicePath = …@0000:03:00.0 (the Arc) already selected

HardwareAcceleratedCodecs is absent from Preferences.xml when enabled — Plex only persists non-defaults. Read it from the API, never from the file:

curl -s "http://127.0.0.1:32400/:/prefs?X-Plex-Token=$TOKEN"

Root cause

intel-media-va-driver 22.3.1 (Apr 2023, stock jammy) — predates Arc/DG2 support entirely, and exports only __vaDriverInit_1_14 against Plex's bundled libva 2.22 (/usr/lib/plexmediaserver/lib/libva.so.2, loaded via RPATH, not the system one).

Compounding it: someone had previously hand-installed libva 2.22 over the packaged 2.14 and left libva-x11 behind at 2.14, so vainfo died with undefined symbol: va_fool_postp — a half-finished run at this same fix.

The fix

# 1. Snapshot. `pct snapshot` REFUSES on a guest with a bind mount (mp0: /tank/media)
#    AND STILL EXITS 0 — snapshot the ZFS dataset directly and read it back.
zfs snapshot nvme/subvol-105-disk-0@pre-vaapi-20260911
zfs list -t snapshot nvme/subvol-105-disk-0@pre-vaapi-20260911   # VERIFY, don't trust rc

# 2. Intel client-GPU repo (inside LXC 105)
curl -fsS https://repositories.intel.com/gpu/intel-graphics.key \
  | gpg --yes --dearmor -o /usr/share/keyrings/intel-graphics.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel-graphics.gpg] \
https://repositories.intel.com/gpu/ubuntu jammy client" \
  > /etc/apt/sources.list.d/intel-gpu-jammy.list
apt-get update

# 3. Consistent set — the driver AND all four libva packages together
apt-get install -y intel-media-va-driver-non-free libva2 libva-drm2 libva-x11-2

# 4. Pinned + held, because `jammy client` is a ROLLING track
#    /etc/apt/preferences.d/intel-gpu-pin  +  apt-mark hold

systemctl restart plexmediaserver

Landed: iHD 24.3.4 (__vaDriverInit_1_22), libva set 2.22.0.2-87, libigdgmm12 22.5.2 — and the orphaned manual libva is now dpkg-owned.

Verification — behaviour, not config

A synthetic Plex Transcoder invocation is not a valid test. Running it from a shell produced three different failure modes (unknown libva error, then a libstdc++ __wmemmove_chk relocation error) that were artifacts of not reproducing Plex's bundled Conan runtime — Plex ships its own libc among 61 bundled libraries. It failed identically before and after a fix that worked. No positive control existed, so its negatives were worthless.

Force a real transcode and read Plex's own log:

TOKEN=$(sed -n 's/.*PlexOnlineToken="\([^"]*\)".*/\1/p' \
  "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Preferences.xml")
KEY=<a ratingKey from /library/sections/<n>/all>
curl -s -o /dev/null "http://127.0.0.1:32400/video/:/transcode/universal/start.m3u8\
?path=%2Flibrary%2Fmetadata%2F$KEY&mediaIndex=0&partIndex=0&protocol=hls\
&directPlay=0&directStream=0&videoQuality=20&maxVideoBitrate=1500\
&X-Plex-Token=$TOKEN&X-Plex-Client-Identifier=vaapi-verify&session=vaapiverify1"
sleep 12
grep -i "hardware transcoding: testing API" "…/Logs/Plex Media Server.log" | tail -3
curl -s -o /dev/null "http://127.0.0.1:32400/video/:/transcode/universal/stop\
?session=vaapiverify1&X-Plex-Token=$TOKEN"

PASS looks like this — the device is named, and both encoder and decoder are tested:

Codecs: testing h264_vaapi (encoder)
Codecs: hardware transcoding: testing API vaapi for device '/dev/dri/renderD129' (Intel DG2 [Arc A580])
Codecs: testing h264 (decoder) with hwdevice vaapi

followed by [FFMPEG] - Format 0x… -> bgra surface enumeration, which only occurs after a successful vaInitialize. FAIL is the empty final decoder: , final encoder:.

vainfo --display drm --device /dev/dri/renderD129 is a useful secondary check (it now reports iHD 24.3.4 with H.264/HEVC VLD and EncSliceLP) but it exercises the system libva, not Plex's bundled one — so it can pass while Plex fails.

Rollback

pct stop 105
zfs rollback nvme/subvol-105-disk-0@pre-vaapi-20260911
pct start 105

Not done

  • Jellyfin (LXC 107) on the same host has the same stale stack and the same Arc available. Left alone 2026-09-11 — operator: not actively used. It ships its own ffmpeg, so this fix may not transfer verbatim.
  • LXC 105 has 152 packages pending upgrade unrelated to this work.