Allow hardware transcoding on ARM machines

On Linux running on ARM CPUs it is currently not possible to run hardware transcoding even when the hardware is capable. For example, running on a Nvidia DGX Spark (which has NVENC support) does not allow hardware transcoding. I believe the Nvidia drivers are exactly the same API as x86_64 counterparts.

The only thing missing right now is the option to enable hardware transcoding. Seeing as future machines might include ARM processors more often while running modern AMD/Nvidia/Intel GPUs seems like this issue might happen more often in the future.

EDIT: Confirmed to work on Jellyfin, this is the support matrix for NVENC, all formats are supported on the DGX Spark:

Working NVENC/NVDEC hardware transcoding on aarch64 (community plugin, no server-code changes)

If you’re running Plex on an aarch64 box with an NVIDIA GPU (Jetson-class hardware, an aarch64 server with a discrete card, etc.), you’ve probably noticed Plex Transcoder has no NVENC/NVDEC support on that architecture — everything falls back to software x264, even with a GPU sitting right there.

I put together a plugin that adds real hardware decode and encode for that case, using Plex’s existing (undocumented) external-codec loading mechanism — no changes to Plex’s own binaries, no forked builds.

Repo: https://github.com/tpmullan/plex-nvenc-rpc

First release (v0.0.1): https://github.com/tpmullan/plex-nvenc-rpc/releases/tag/v0.0.1

Why it’s a separate process instead of a normal plugin

Plex Transcoder on aarch64 is linked against musl libc, but the NVIDIA driver’s userspace libraries are glibc. A plugin `.so` doesn’t bring its own dynamic linker, so it’s stuck using whatever linker the host process already has — meaning a plugin loaded directly into Transcoder can’t reliably use the NVIDIA driver in-process. I found and fixed several musl/glibc gaps one at a time before concluding that approach doesn’t converge (details in https://github.com/tpmullan/plex-nvenc-rpc/blob/main/docs/history.md).

The plugin that actually loads into Transcoder is deliberately tiny — no FFmpeg linkage, no CUDA code at all. It forks a separate, completely ordinary glibc helper process for every session and talks to it over a small RPC protocol. All the real NVENC/NVDEC/CUDA work happens in that helper, isolated from any musl/glibc concerns

What’s registered

  • h264_nvenc / hevc_nvenc — real hardware encode
  • h264 / hevc (decode) — real hardware decode
  • libx264 — hijacked. Plex’s own encoder-selection decision (made before Transcoder even starts) doesn’t seem to route to a hardware encoder no matter what capability signals the plugin advertises, but it *does* let whichever plugin is registered under a requested name handle it — so this registration always attempts real GPU encode first and transparently falls back to genuine bundled libx264 on CPU if that fails, forwarding the same crf/preset Plex actually requested either way. Never forces GPU unconditionally.

Confirmed via GPU process/memory listings during real, unmodified playback sessions: sustained hardware decode + encode running simultaneously, full video+audio pipeline, 10-bit HEVC content included.

Caveats going in

  • The plugin binary is pinned to one exact Plex FFmpeg build hash (a single constant, EXPECTED_BUILD_HASH in glue.c) and the helper binary to a specific NVIDIA driver/CUDA ABI generation. Different versions need a rebuild — cheap for the plugin (no FFmpeg linkage, few-second recompile), more involved for the helper (needs a real build environment, see the README).
  • 8-bit-only IPC frame format currently (10-bit content gets downsampled before encode).
  • No session-limit awareness between the GPU-first and CPU-fallback paths yet.
  • GPLv2, since it links a real FFmpeg build with libx264 enabled. No Plex source or binaries are contained or linked — see the README’s “Legal” section.

If you want to try it or read the code

Full install instructions (Docker/Kubernetes bind-mount pattern) are in the README. If you’re on a different Plex build hash or driver version than what’s pinned, the fastest path is pointing a coding agent (Claude Code or similar) at the repo and your own container and asking it to adapt the pinned constants and rebuild both binaries — what took a full debugging session to build from scratch should be a much shorter job working from a known-good reference implementation.

Posting this partly hoping it’s useful to someone with the same hardware gap, and partly hoping someone at Plex takes a look — the underlying interface this plugin targets is real and already shipping in the Plex binary; it wouldn’t take much to get equivalent support built in properly for aarch64+NVIDIA. Happy to answer questions about the RPC design or the build process.