intel graphics in the server
I use jellyfin as my media server software. Many of my video files are high-bitrate, HDR, and HEVC-encoded, which is fine if you’re directly streaming on a local network to a device which can handle all the above qualities. This isn’t the usual case though, so most streams have to be transcoded by jellyfin.
Without a graphics card or other hardware video processor, jellyfin uses software encoders like x264 to do this transcoding. This works fine in most small cases, except for one really annoying nit: HDR video doesn’t get tone mapped to SDR, so any viewers not supporting it, including the web viewer, show a low-contrast washed-out mess.
Jellyfin’s developers have stated that software tonemapping is not planned, so I went looking for a cheap graphics card to fix this. Most cases where transcoding is needed only require AVC (H.264) output and OpenCL support for the tone mapping, so any consumer GPU from the past decade probably would have worked.
Unfortunately, my 2U chassis doesn’t have a horizontal slot nor the extra power-supply cables for a full-height PCIe card. Old nvidia workstation or server cards could fit and are pretty cheap, and this was the option I was considering before I found the Arc A310.
Low-profile consumer graphics cards are not very common, which makes them expensive compared to used full-size cards. Luckily, the A310 is a very low-end card, so even the low-profile version was competitive in price with used cards of the same size. It also was low-powered enough to skip the 6/8-pin PCIe power receptor and only use the PCIe slot for power. It wasn’t completely as cheap as going for an older workstation card, but I decided that the extra compute headroom for tonemapping and the newer codec support (mostly AV1) would be worth the price. Intel’s encoders also seem about as good as NVidia’s for this use-case (from a very brief search).
Installing the new card was easy enough, but not completely seamless. The first boot after slotting it in, I realized I needed a newer kernel: the current LTS (6.1) didn’t have full support for this generation of GPUs, so I switched to the latest kernel. The nixos builds of FFmpeg default to building with the obsolete intel Media SDK (MFX) instead of oneVPL, so I needed to override the package to support my card:
let
jellyfin-ffmpeg-overlay = (final: prev: {
jellyfin-ffmpeg = prev.jellyfin-ffmpeg.override {
ffmpeg_6-full = prev.ffmpeg_6-full.override {
withMfx = false;
withVpl = true;
};
};
});
unstable = (import <unstable> {
config.allowUnfree = true;
overlays = [ jellyfin-ffmpeg-overlay ];
});
in
services.jellyfin = {
enable = true;
package = unstable.jellyfin;
};
The VPL backend was still too new for the 23.11 stable nixpkgs, so I pulled it in from unstable. Finally, I had hardware transcoding working, but I hadn’t yet tried to enable tonemapping.
I decided to use jellyfin’s OpenCL tonemapping instead of the full-hardware intel VPP mapper for more format support and tuning options, but couldn’t seem to get OpenCL to work. A bug in the intel OpenCL driver prevented the card from being presented to applications at all, though a temporary fix from setting two environment variables exists:
environment.variables = {
NEOReadDebugKeys = "1";
OverrideGpuAddressSpace = "48";
};
The fixed driver hasn’t yet landed in unstable nixpkgs but should be available soon.
Since my initial setup, the transcoding system has consistently worked well. Many of the first hurdles are obvious results of picking a freshly-introduced software and hardware ecosystem, with intel’s drivers not yet fully stable, and their software shifting around often. Despite that, the results of efficient transcoding and better viewer support was worth it to me.
{ config, pkgs, ... }:
let
jellyfin-ffmpeg-overlay = (final: prev: {
jellyfin-ffmpeg = prev.jellyfin-ffmpeg.override {
ffmpeg_6-full = prev.ffmpeg_6-full.override {
withMfx = false;
withVpl = true;
};
};
});
unstable = (import <unstable> {
config.allowUnfree = true;
overlays = [ jellyfin-ffmpeg-overlay ];
});
in
{
# TODO remove when LTS supports DG2 series
boot.kernelPackages = pkgs.linuxPackages_latest;
# TODO https://github.com/intel/compute-runtime/issues/710
environment.variables = {
NEOReadDebugKeys = "1";
OverrideGpuAddressSpace = "48";
};
hardware.opengl = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver
intel-compute-runtime
unstable.onevpl-intel-gpu
];
};
services.jellyfin = {
enable = true;
package = unstable.jellyfin;
};
}