From 84cec9e935ba13554072ce8c5569e10dec2c67ae Mon Sep 17 00:00:00 2001 From: marthsincemelee Date: Sun, 26 Jul 2026 14:27:30 +0200 Subject: [PATCH] docs: add design spec for Jellyfin hardware transcoding on jupiter Jupiter's Intel iGPU is configured at the OS level but Jellyfin has no access to it, so transcodes run on CPU only and stutter. Co-Authored-By: Claude Sonnet 5 --- .../2026-07-26-jellyfin-hw-transcoding.md | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-26-jellyfin-hw-transcoding.md diff --git a/docs/superpowers/specs/2026-07-26-jellyfin-hw-transcoding.md b/docs/superpowers/specs/2026-07-26-jellyfin-hw-transcoding.md new file mode 100644 index 0000000..af4e585 --- /dev/null +++ b/docs/superpowers/specs/2026-07-26-jellyfin-hw-transcoding.md @@ -0,0 +1,108 @@ +# Intel Quick Sync hardware transcoding for Jellyfin on jupiter + +## Problem + +Jellyfin streams stutter on jupiter whenever a client needs a transcode +(unsupported codec/container, bitrate cap, or a client that can't +direct-play). Transcoding currently runs entirely on CPU. + +jupiter's Intel iGPU is already usable at the OS level: + +- `hardware.graphics.enable = true` with `intel-media-driver` (the `iHD` + VAAPI driver) is configured in + `machines/jupiter/hardware-configuration.nix:20-27`. +- The commented-out `i915.force_probe = "9a49"` kernel param there + corresponds to a Quick-Sync-capable Intel UHD iGPU, confirming the + hardware supports it. + +But `modules/environments/jellyfin/default.nix` never grants the +`jellyfin` systemd service access to `/dev/dri`, so Jellyfin has no path +to the GPU and silently falls back to software transcoding. + +## Goal + +Give the Jellyfin service access to the iGPU's VAAPI render node, so +Quick Sync can be enabled in Jellyfin's own dashboard and transcodes are +offloaded from the CPU. + +## Non-goals + +- Remote/external access or reverse-proxy tuning. +- General CPU/RAM headroom review of jupiter. +- A toggle option (`my.profiles.jellyfin.hardwareAcceleration.enable`) — + jupiter only has the one iGPU, so this is hardcoded on rather than + made configurable. + +## Design + +### NixOS change (declarative) + +In `modules/environments/jellyfin/default.nix`, inside the existing +`config = lib.mkIf cfg.enable { ... }` block, grant the systemd service +supplementary access to the `video` and `render` groups (the groups that +own `/dev/dri/card*` and `/dev/dri/renderD*`): + +```nix +systemd.services.jellyfin.serviceConfig.SupplementaryGroups = [ + "video" + "render" +]; +``` + +This is additive to the existing `systemd.services.jellyfin.after = [ +"network-online.target" ];` block already in the file — both apply to +the same service. + +Also add `libva-utils` to `environment.systemPackages` (or scoped to +this module) so `vainfo` is available on jupiter to verify the driver +loads correctly. + +### Manual step (not declarative) + +Jellyfin stores its transcoding/hardware-acceleration choice in its own +internal `encoding.xml`, which the NixOS module does not expose as an +option. After deploying the Nix change, one-time manual configuration in +the Jellyfin dashboard is required: + +1. **Dashboard → Playback**. +2. Hardware acceleration: **Intel QuickSync (QSV)**. +3. VA-API device: `/dev/dri/renderD128`. +4. Enable hardware decoding for the codecs your library actually uses + (H264 at minimum; HEVC/VP9 depending on iGPU generation). +5. If any HDR content exists in the library, enable tone-mapping — this + is one of the more CPU-expensive operations Quick Sync can offload. + +## Verification + +Build-time (from the Mac, no SSH needed): + +``` +nix eval '.#nixosConfigurations.jupiter.config.systemd.services.jellyfin.serviceConfig.SupplementaryGroups' \ + --extra-experimental-features 'nix-command flakes' +``` + +Expect `[ "video" "render" ]`. + +On jupiter after `sudo nixos-rebuild switch --flake '.#jupiter'`: + +``` +systemctl status jellyfin +journalctl -u jellyfin -n 50 --no-pager +vainfo +``` + +`vainfo` should list the `iHD` driver and print supported VAEntrypoints +(VLD decode / encode profiles for H264/HEVC). + +Functional check: play a file on a client that forces transcoding (or +force it manually via Jellyfin's playback quality setting), then in +Jellyfin's dashboard **Activity/Now Playing** panel confirm the +transcode reason and check that CPU usage on jupiter (`htop`) stays low +during playback rather than pegging a core — Quick Sync offload should +show up as low CPU, some GPU (`intel_gpu_top`) activity instead. + +## Open items + +- Exact supported codec list depends on the iGPU generation (device ID + `9a49`) — confirm via `vainfo` output once run, and enable only the + hardware decode paths it actually reports.