microduck-gst-plugins/patches/gst-plugins-rs/0001-webrtcsink-no-converter-for-mpph264enc.patch
Pierre Rouanet a9a839f274 mpph264enc could not say constrained-baseline, so H.264 was never offered
webrtcsink's codec discovery builds its encoding chain with no output caps, so `force_profile` is
true and it inserts a capsfilter demanding `profile=constrained-baseline` — WebRTC's interoperable
floor. h264parse strips `alignment`, `stream-format` and `parsed` from a caps query but not
`profile`, so that demand reaches mpph264enc's src pad, whose template listed only
`{ baseline, main, high }`. Empty intersection, GstVideoEncoder's sink getcaps returns nothing,
and the failure surfaces upstream as videorate reporting it "could not transform NV12 ... in
anything we support". Discovery then drops H.264 with a warning nobody was reading, VP8 wins by
default, and the session dies in rtpvp8pay.

The element could always produce constrained-baseline; only its static template denied it.
Measured on an RK3566: `mpph264enc profile=baseline ! h264parse` negotiates
`profile=(string)constrained-baseline` on the parser's src pad, because baseline mode turns CABAC
and 8x8 transform off and MPP emits no FMO, ASO or redundant slices. So this widens the template
by one word rather than claiming something new.

Patches now live in a directory per upstream, applied by an `apply_patches` helper and recorded in
the MANIFEST as `patch <project>/<file>`. Two reasons: a patch aimed at the wrong tree failed the
same way a stale one does, and the MANIFEST's flat `patch <file>` line landed next to whichever
.so happened to precede it, which read as provenance for the wrong plugin.

Assisted-by: Claude:claude-opus-5[1m]
2026-08-25 11:29:02 +02:00

29 lines
1.6 KiB
Diff

--- a/net/webrtc/src/webrtcsink/imp.rs
+++ b/net/webrtc/src/webrtcsink/imp.rs
@@ -685,6 +685,25 @@
ret.add_many([&vapostproc])?;
(vapostproc.clone(), vapostproc)
+ } else if codec
+ .encoder_factory()
+ .is_some_and(|factory| factory.name() == "mpph264enc")
+ {
+ // Rockchip's MPP encoder accepts NV12, I420, YUY2 and a dozen more formats on its
+ // sink pad, and performs any conversion it needs on the SoC's 2D accelerator
+ // (RGA) rather than on the CPU. So unlike the NVIDIA and VA cases above, what it
+ // wants is not a *better* converter but no converter at all: a software
+ // `videoconvert ! videoscale` in front of it is a full CPU pass over every frame,
+ // which on the four Cortex-A55s of an RK3566 is precisely the cost the hardware
+ // encoder exists to avoid.
+ //
+ // The trade-off, stated plainly: without `videoscale` this bin cannot resize, so
+ // the negotiated resolution has to be one the source already produces. That is
+ // true of the robot this is used on, which pins its caps upstream of the tee.
+ let identity = make_element("identity", None)?;
+
+ ret.add_many([&identity])?;
+ (identity.clone(), identity)
} else {
let convert = make_element("videoconvert", None)?;
let scale = make_element("videoscale", None)?;