Accelerated Jellyfin
turing-rk1/jellyfin-forky and turing-rk1/jellyfin-trixie build a Turing RK1
image running the Jellyfin media server, transcoding video on the board’s
hardware encoder.
boot2deb build turing-rk1/jellyfin-forky
Flash it the way you would any other RK1 image — see Turing RK1. Jellyfin comes up on port 8096 with the transcode settings already filled in. There is nothing to configure to get hardware encoding. There is one setting to leave alone, described below.
What is accelerated, and what is not
Encoding runs on the VEPU580. Decoding and scaling run on the CPU.
That is the whole shape of it, and it is not the shape Jellyfin’s dashboard implies, so it is worth being plain about. Jellyfin offers an “Enable hardware decoding for” list alongside the hardware-encoding switch. On this image that list is deliberately empty.
The reason is that the two halves of Rockchip’s stack are in different states on a
mainline kernel. The *_rkmpp encoders talk to mpp_service and work. The
*_rkmpp decoders expect an MPP decode client, and mainline does not provide
one — rkvdec is a V4L2 stateless driver instead. The decoders are still compiled
in, so they appear in ffmpeg -hwaccels, and Jellyfin’s capability probe reads
exactly that list and concludes hardware decoding is available. It is not.
Turning a codec on in that list makes Jellyfin emit -hwaccel rkmpp. That hwaccel
finds no MPP decoder, and FFmpeg decodes in software without saying so, at the same CPU
cost as no hwaccel at all. The list buys nothing and hides what is actually running.
So: leave Playback → Transcoding → Enable hardware decoding for empty. Everything else in that page is yours to tune.
On this board that is also the faster arrangement rather than a concession. RGA scaling only pays for itself on frames already held in an MPP context, which on a software-decode path they never are. The round trip to and from the 2D engine costs more than swscale saves. Eight Cortex cores decode and scale, and the encoder — the part that actually would not keep up in software — is in hardware.
FFmpeg on this image can decode in hardware, with -hwaccel v4l2request. The
stock Jellyfin server cannot ask for it, because its acceleration type is a fixed
list with no v4l2request in it. These two recipes install the stock server.
Hardware decode as well builds one that can.
What the image sets up for you
| Setting | Value | Why |
|---|---|---|
| FFmpeg path | /opt/ffmpeg-rk/bin/ffmpeg | The only build here that can reach the VEPU580 |
| Hardware acceleration | rkmpp | The only type that reaches h264_rkmpp / hevc_rkmpp |
| Hardware encoding | on | |
| Hardware decoding | (empty) | See above — leave it empty |
| Tone mapping | off | This FFmpeg is built without OpenCL |
These are written to /etc/jellyfin/encoding.xml before first boot. They are
starting values: Jellyfin rewrites that file on every start. From first boot
onward the dashboard governs every row but the FFmpeg path, which it only displays
(see below). To change the defaults for the next image,
edit features/jellyfin-rockchip/overlay-pre/etc/jellyfin/encoding.xml in your
config tree.
HEVC output is left off, as it is in stock Jellyfin. Whether your clients can play
HEVC is a fact about your household, not about the board. hevc_rkmpp is there and
works if you turn it on.
Jellyfin’s bundled FFmpeg is not installed
The image installs jellyfin-server and jellyfin-web, not the jellyfin
metapackage, so jellyfin-ffmpeg never lands. It would be a second complete FFmpeg
that cannot reach the hardware, and it pulls its own pocket’s library versions in
behind it.
The consequence is worth knowing: there is no fallback encoder, and on this
application no encoder means no server. Jellyfin validates the FFmpeg path during
startup and exits if the binary does not run — it does not start with transcoding
switched off. So if you point it at a path that does not exist, the service dies at
boot. Check with journalctl -u jellyfin. The giveaway is
Failed to find valid ffmpeg. If you want the bundled build available as a safety
net, add jellyfin-ffmpeg8 to a copy of the jellyfin feature’s package list.
Change the path in encoding.xml, not on the command line. The image ships a
jellyfin.service drop-in that clears the --ffmpeg= argument Debian normally
passes, so Jellyfin reads the path from <EncoderAppPath> in
/etc/jellyfin/encoding.xml. The dashboard’s FFmpeg path field shows that path
but cannot edit it, because Jellyfin rejects a change to it made through its API.
Edit the file with the server stopped:
sudo systemctl stop jellyfin
sudoedit /etc/jellyfin/encoding.xml
sudo systemctl start jellyfin
A path put back on the command line, by editing /etc/default/jellyfin-encoder,
outranks the file without saying so.
Keeping it updated
Jellyfin’s own apt repository stays configured on the running system, since the
image writes its sources.list.d entry and keyring. apt upgrade therefore picks
up Jellyfin releases the ordinary way. Debian’s mirrors are there too. Nothing about
this image requires a reflash to take a security update to the server.
A build installs whichever release that repository serves at build time. The solved
manifest published beside the image, <recipe>.pkgs.lock, records the version. A
Jellyfin major release can migrate the database with no path back, so back up
/var/lib/jellyfin and /etc/jellyfin before an apt upgrade that crosses one.
The exception is ffmpeg-rk. It is built from source, pinned by commit in the
recipe’s lock, and comes from no repository, so apt upgrade will never move it.
It is also the component that parses untrusted media. Moving it means re-pinning and
rebuilding:
boot2deb outdated turing-rk1/jellyfin-forky # what has moved upstream
boot2deb update turing-rk1/jellyfin-forky # re-pin
boot2deb build turing-rk1/jellyfin-forky
Hardware decode as well
The jellyfin-v4l2request feature builds the Jellyfin server from source with a
patch series that lets decode and encode name different stacks. It seeds the pairing:
decode on the stateless decoders through -hwaccel v4l2request, encode on the VEPU580.
boot2deb update turing-rk1/forky+media-accel-rockchip+jellyfin+jellyfin-v4l2request+vulkan+avs-decode
boot2deb build turing-rk1/forky+media-accel-rockchip+jellyfin+jellyfin-v4l2request+vulkan+avs-decode --image-size 3G
It takes the place of jellyfin-rockchip in the selection, since both seed
encoding.xml. What changes against the table above:
| Setting | Value |
|---|---|
| Hardware decoding type | v4l2request |
| Hardware encoding type | rkmpp |
| Hardware decoding | H.264, HEVC, VP9, at 8 and 10 bits |
H.264, HEVC and VP9 all decode on rkvdec, the decoder with the widest frame-size
range for them, which is the one FFmpeg tries first. A 10-bit frame is unpacked
from the decoder’s packed NV15 layout on the CPU, which is most of what a 10-bit
transcode costs. A 4K HEVC Main 10 stream transcodes to 1080p at 1.6x realtime, on a
quarter of the CPU that software decode takes. Two at once hold just under realtime
each. The dashboard has no field for the two types. They live in
/etc/jellyfin/encoding.xml, and setting the acceleration type in the dashboard leaves
them as they are.
The server is held at its release. The built jellyfin-server carries an epoch, and
depends on the jellyfin-web of its own release, so apt upgrade moves neither. The
Jellyfin repository stays configured for everything else it serves. A newer Jellyfin is
a change to the app’s ref in features/jellyfin-v4l2request.toml, then an update and
a build. See features/jellyfin-v4l2request/README.md for how the build works.
Where the media lives
The recipes declare no data volume, on purpose. An RK1 running Jellyfin might keep
its library on an M.2 disk, an external drive, or network storage, and the recipe
cannot know which. To attach one, add the data-volume feature and a
[[data_volumes]] block to your own copy of the recipe. See
Data volumes.
Checking it is working
Play something that must be transcoded, such as a file in a codec the client cannot take, or one with subtitles burned in. Then look at the FFmpeg command Jellyfin logged:
sudo grep -h "ffmpeg" /var/log/jellyfin/*.log | tail -1
You want to see /opt/ffmpeg-rk/bin/ffmpeg, -c:v hevc_rkmpp or h264_rkmpp, and
no -hwaccel rkmpp. If -hwaccel rkmpp is there, a codec got re-enabled in the
hardware-decoding list.
Every run also prints mpp_platform: client N driver is not ready! for a handful of
N. That is normal: libmpp probes for vendor client types a mainline kernel does not
have. The one the encoders need, RKVENC, is present. RKVDEC (9) and JPEG_DEC (13) are
among the absent ones, which is why the *_rkmpp decoders never open.
Status
Neither recipe is validated, and the gap is Jellyfin rather than the hardware
underneath it.
The transcode path itself is measured on a boot2deb-built RK1 image. h264_rkmpp and
hevc_rkmpp produce correct streams: every frame of a 90-frame clip in both codecs,
from software frames and through hwupload alike. That was verified against a stock
FFmpeg on another machine, rather than against the build that produced them.
Hardware decode through -hwaccel v4l2request cuts decode CPU cost by 53x at 1080p
and by 70-162x at 4K, and HEVC decode is bit-exact against software.
Driving that path from Jellyfin has been measured on the board too. A patched
server carrying a v4l2request acceleration type played files through the API on
2026-09-02. It emitted exactly the accelerated commands, with no software fallback.
The two recipes install the stock server, so what ships there is the encoder half.
The jellyfin-v4l2request build ships both. See the
support matrix for what each recipe has been taken
through.