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.