Pierre Rouanet b81b50199a Patch webrtcsink to put no converter in front of mpph264enc
`make_converter_for_video_caps` builds the chain webrtcsink inserts ahead of an
encoder it selected, special-casing hardware it knows — NVMM, D3D11, CUDA, GL,
VA, and on main also v4l2h264enc — and falling back to software
`videoconvert ! videoscale` for anything else. Rockchip's MPP encoder takes
NV12, I420, YUY2 and more directly and converts on the SoC's 2D accelerator, so
the fallback adds a full CPU pass over every frame to do work the hardware was
going to do anyway, on the four A55s robotd's 50 Hz loop shares.

The reason this matters more than CPU: the robot currently avoids the whole
question by pre-encoding and handing webrtcsink finished H.264. That works, and
it means webrtcsink cannot reach the encoder — so congestion control cannot
adapt the bitrate to the link, and a peer's PLI cannot produce a keyframe, which
leaves a viewer that lost one broken until the next periodic GOP. Letting
webrtcsink own the encoder fixes both. This patch is what makes that affordable.

**This repository is no longer patch-free, and says so.** MPL-2.0 asks that
modifications be identifiable, so the README states it, patches/README.md gives
each patch's reasoning, and build.sh records every applied patch in the release
MANIFEST beside the upstream ref. `git apply --check` runs first, so a patch that
stops applying fails the build naming itself rather than yielding a plugin
quietly missing the change it was carried for.

The trade-off is written down rather than glossed: without videoscale the bin
cannot resize, so the negotiated resolution must be one the source produces.
True on this robot, which pins its caps upstream of the tee — and the reason
upstream may want RGA-backed scaling instead of nothing before taking it. The
v4l2h264enc arm on main is the same shape for another hardware encoder, so the
precedent exists, and if it lands this file is deleted at the next bump.

Verified to apply cleanly against a real 0.15.3 checkout.

Assisted-by: Claude:claude-opus-5[1m] shellcheck
2026-08-25 10:43:05 +02:00

122 lines
6.5 KiB
Markdown

# microduck-gst-plugins
Prebuilt aarch64 GStreamer plugins for the micro duck robot, built in CI from pinned upstream
sources and attached to a release.
Two plugins, for two unrelated reasons. Neither is packaged anywhere we can install from.
| plugin | provides | why it is here |
|---|---|---|
| `libgstrockchipmpp.so` | `mpph264enc`, `mpph265enc`, `mppjpegenc`, `mppvp8enc`, `mppvideodec`, `mppjpegdec` | Debian ships no Rockchip encoder in any suite. Radxa's own `gstreamer1.0-rockchip1_1.14-4` does contain them, so this build is about the pin, dropping `libx11-6`, and riding along with the plugin below — see [below](#the-permission-trap-that-hid-all-of-this). |
| `libgstrswebrtc.so`, `libgstrsrtp.so` | `webrtcsink`, `webrtcsrc`, `rsrtp*` | `gstreamer1.0-plugins-rs` does not exist in **any** Debian suite — not trixie, backports, sid or experimental. Patched; see [`patches/`](patches/). |
`webrtcbin` is **not** here: it comes from `gstreamer1.0-plugins-bad` in Debian and needs no
build.
## Why a repository of its own
The robot's daemon is cross-compiled from a developer's machine with `cargo-zigbuild`, and its one
C dependency is already the documented cost of doing that. GStreamer would be a much larger second
one — a cross sysroot or x86 multiarch, either of which links against an approximation of the
target.
So these are built **natively on an arm64 runner, in a `debian:trixie` container**, which is the
robot's own userland. Nothing is cross-compiled and nothing is approximated. arm64 runners are
free on public repositories, which is one reason this repository is public.
The other reason matters more: a release asset here is fetched by a robot during provisioning and
by the updater's `preinstall` hook, and that hook runs with a cleared environment and no token. A
private repository would break it. This is the same arrangement the daemon already relies on for
ONNX Runtime, which comes from a public `microsoft/onnxruntime` release.
Building rather than taking a third-party binary also buys one concrete thing beyond provenance:
`rkximage` and `kmssrc`, the X11 and KMS *sinks* in the same source tree, are **disabled**. A
headless robot has no use for either, and they are why the prebuilt Radxa deb depends on
`libx11-6`.
## The permission trap that hid all of this
`/dev/mpp_service` arrives as `0600 root:root`, and **an MPP GStreamer plugin registers its
decoders unconditionally but probes MPP before registering its encoders.** With the node
unreadable the probe fails and the encoders are silently omitted — no error, no log line.
That one cause produced four separate misleading results while this was being worked out:
- `mpi_enc_test` wrote an empty file and **exited 0**.
- Radxa's `1.14-4` looked decode-only. It is not; `strings` on its `.so` lists every encoder.
- A third-party `1.14-8` deb installed cleanly and still showed no `mpph264enc`.
- This repository's own CI build shows only `mppjpegdec` and `mppvideodec`, because a container
has no `/dev/mpp_service` either. **That is expected, not a failed build.**
So: a plugin that lists only decoders is evidence about the *device node*, not about the plugin. A
non-root process needs a udev rule giving the node a group — mode `0660`, group `video` — and only
then does `gst-inspect-1.0 mpph264enc` mean anything.
## Consuming a release
```
tar -xzf microduck-gst-plugins-<version>-aarch64.tar.gz
```
Put the `.so` files anywhere and point `GST_PLUGIN_PATH` at it — `/usr/local/lib/gstreamer-1.0`
on a robot, which is deliberately **not** the distro's plugin directory, so an `apt` operation can
never quietly replace or remove them.
```
GST_PLUGIN_PATH=/usr/local/lib/gstreamer-1.0 gst-inspect-1.0 mpph264enc
```
Verify the tarball against the `.sha256` beside it before unpacking. **Pin a version**; do not
follow "latest". Two provisioning runs a day apart that produce different plugins, with nothing
recording which, is an unreproducible media bug waiting to happen.
### Runtime dependencies
The plugins link against libraries a robot needs installed:
- `librockchip-mpp1` and `librga2` — from Radxa's pool, at the versions in
[`pins.env`](pins.env). Not in Debian.
- `libgstreamer1.0-0`, `libgstreamer-plugins-base1.0-0`, `libglib2.0-0`, `libdrm2` — Debian.
`mpph264enc` also needs **read/write access to `/dev/mpp_service`** — see
[the permission trap](#the-permission-trap-that-hid-all-of-this), which is the single most
misleading thing about this stack.
## Bumping a pin
Edit [`pins.env`](pins.env), commit, tag `vN`, push the tag. The release workflow builds and
attaches the tarball, with the manifest as the release notes so a release always says which
upstream commits it came from.
`workflow_dispatch` builds without cutting a release — worth using, because a workflow that only
ever runs on a tag is one you discover is broken at the moment you need it.
## Licences and source
These are binaries built from other people's source, so where that source is matters:
- **`gstreamer-rockchip`** is LGPL. Built from
[`JeffyCN/mirrors`](https://github.com/JeffyCN/mirrors) on the `gstreamer-rockchip` branch, at
the commit in `pins.env` and recorded in every release's `MANIFEST`.
`rockchip-linux/gstreamer-rockchip`, which every published deb names as its homepage, is a 404;
`JeffyCN/mirrors` is the live mirror under the same maintainer.
- **`gst-plugins-rs`** is MPL-2.0. Built from
[the upstream repository](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs) at the tag in
`pins.env`.
**`gst-plugins-rs` is patched, and that matters for more than tidiness.** MPL-2.0 asks that
modifications be identifiable, so it is stated here, listed in [`patches/`](patches/) with what
each one buys and how it ends, and recorded in every release's `MANIFEST` — a release names the
upstream ref *and* every patch applied over it. `gstreamer-rockchip` is unmodified.
There is one patch today: `webrtcsink` inserts a software `videoconvert ! videoscale` in front of
any encoder it does not recognise, and `mpph264enc` converts on the SoC's 2D accelerator instead —
so a CPU pass over every frame is added to work the hardware was going to do anyway, on cores the
robot's control loop shares. [`patches/README.md`](patches/README.md) has the reasoning, the
trade-off it accepts, and the route upstream that would delete it.
Together the ref and the patch list are both the licence answer and the reason a media bug found on
a robot can be traced to a specific build.
This repository's own build scripts are Apache-2.0, matching the daemon.