fix(core): let CODEC_PYROWAVE survive the Welcome decode whitelist
ci / web (push) Successful in 1m8s
ci / docs-site (push) Successful in 1m12s
decky / build-publish (push) Successful in 32s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 37s
apple / swift (push) Successful in 2m34s
ci / bench (push) Successful in 6m53s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10m4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9m10s
arch / build-publish (push) Successful in 13m42s
docker / deploy-docs (push) Successful in 21s
android / android (push) Successful in 14m48s
deb / build-publish (push) Successful in 14m53s
windows-host / package (push) Successful in 15m18s
flatpak / build-publish (push) Failing after 8m14s
ci / rust (push) Successful in 18m28s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m27s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m55s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m20s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m16s
release / apple (push) Successful in 24m45s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m21s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m4s
apple / screenshots (push) Successful in 19m29s

Found by the first live session on .21: the host negotiated PyroWave
and put codec=8 on the wire, but Welcome::decode's codec whitelist
(H264/AV1, else HEVC — the corrupt-byte guard) folded it to HEVC, so
the client opened an FFmpeg HEVC decoder against wavelet AUs. Roundtrip
test now pins the pyrowave byte (and that a genuinely unknown future
bit still folds to the HEVC default).

With the fix the Phase-2 exit session runs END TO END on .21
(host + session client on one box, host capturing the GNOME virtual
display, client presenting into a headless weston):
  negotiated codec=PyroWave (adv 0x0f) → PyroWave encoder (CPU-capture
  path — this box's Mutter+NVIDIA rejects the LINEAR-dmabuf offer) →
  wire → PyroWave decoder on the presenter's device → planar CSC.
  Static desktop: stable 60 fps, e2e 2.1-4.1 ms p50 (p95 <= 6 ms),
  decode 0.2-0.6 ms, vs HEVC/NVENC-direct baseline 2.1 ms — parity at
  idle. Full-window motion: 60 fps at ~80 Mb/s all-intra (HEVC ~7),
  decode still sub-ms, zero decode errors or keyframe-request chatter
  across every run. Deeper motion/loss characterization needs a
  dmabuf-accepting host box (this one is capped by the CPU capture
  path).

Also retires the stale "no shipping client decodes this" wording in
the host encoder/dispatch logs — the negotiation exists now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 09:09:39 +02:00
parent eb8a659319
commit 719b1ef403
4 changed files with 45 additions and 5 deletions
+1
View File
@@ -955,6 +955,7 @@ impl Welcome {
codec: match b.get(66).copied() { codec: match b.get(66).copied() {
Some(CODEC_H264) => CODEC_H264, Some(CODEC_H264) => CODEC_H264,
Some(CODEC_AV1) => CODEC_AV1, Some(CODEC_AV1) => CODEC_AV1,
Some(CODEC_PYROWAVE) => CODEC_PYROWAVE,
_ => CODEC_HEVC, _ => CODEC_HEVC,
}, },
// Optional trailing host-caps byte — absent on an older host → 0 (no gamepad-state // Optional trailing host-caps byte — absent on an older host → 0 (no gamepad-state
+39
View File
@@ -124,6 +124,45 @@ fn codec_negotiation_and_back_compat() {
resolve_codec(CODEC_HEVC | CODEC_PYROWAVE, CODEC_HEVC, CODEC_PYROWAVE), resolve_codec(CODEC_HEVC | CODEC_PYROWAVE, CODEC_HEVC, CODEC_PYROWAVE),
Some(CODEC_HEVC) Some(CODEC_HEVC)
); );
// And the negotiated bit SURVIVES the Welcome wire roundtrip — the decode whitelist
// once folded unknown codec bytes (incl. PyroWave) to HEVC, which sent wavelet AUs
// into an FFmpeg HEVC decoder on the first on-glass run.
let mut pw_w = Welcome::decode(
&Welcome {
abi_version: 2,
udp_port: 1,
mode: Mode {
width: 1280,
height: 720,
refresh_hz: 60,
},
fec: FecConfig {
scheme: FecScheme::Gf16,
fec_percent: 0,
max_data_per_block: 1024,
},
shard_payload: 1024,
encrypt: false,
key: [0; 16],
salt: [0; 4],
frames: 0,
compositor: CompositorPref::Auto,
gamepad: GamepadPref::Auto,
bitrate_kbps: 0,
bit_depth: 8,
color: ColorInfo::SDR_BT709,
chroma_format: CHROMA_IDC_420,
audio_channels: 2,
codec: CODEC_PYROWAVE,
host_caps: 0,
}
.encode(),
)
.unwrap();
assert_eq!(pw_w.codec, CODEC_PYROWAVE);
// A genuinely unknown future bit still folds to the HEVC default.
pw_w.codec = 0x40;
assert_eq!(Welcome::decode(&pw_w.encode()).unwrap().codec, CODEC_HEVC);
// A Hello advertising codecs roundtrips, and the wire form of a codec-only Hello decodes on // A Hello advertising codecs roundtrips, and the wire form of a codec-only Hello decodes on
// a build that ignores the trailing byte (back-compat: extra bytes are skipped). // a build that ignores the trailing byte (back-compat: extra bytes are skipped).
+4 -3
View File
@@ -674,9 +674,10 @@ fn open_video_backend(
{ {
tracing::warn!( tracing::warn!(
?codec, ?codec,
"PUNKTFUNK_ENCODER=pyrowave: EXPERIMENTAL all-intra wavelet stream \ "PUNKTFUNK_ENCODER=pyrowave forces the all-intra wavelet stream \
clients without a PyroWave decoder (all of them until CODEC_PYROWAVE \ regardless of the negotiated codec — only a pyrowave-feature client \
lands) cannot display it" that ALSO preferred CODEC_PYROWAVE can display it (lab override; \
normal sessions negotiate it instead)"
); );
pyrowave::PyroWaveEncoder::open(width, height, fps, bitrate_bps) pyrowave::PyroWaveEncoder::open(width, height, fps, bitrate_bps)
.map(|e| (Box::new(e) as Box<dyn Encoder>, "pyrowave")) .map(|e| (Box::new(e) as Box<dyn Encoder>, "pyrowave"))
@@ -430,8 +430,7 @@ impl PyroWaveEncoder {
gpu = %props.device_name_as_c_str().unwrap_or(c"?").to_string_lossy(), gpu = %props.device_name_as_c_str().unwrap_or(c"?").to_string_lossy(),
mode = %format!("{w}x{h}@{fps}"), mode = %format!("{w}x{h}@{fps}"),
budget_kib = frame_budget / 1024, budget_kib = frame_budget / 1024,
"PyroWave encoder open (intra-only wavelet, BT.709 limited 4:2:0)\ "PyroWave encoder open (intra-only wavelet, BT.709 limited 4:2:0)"
EXPERIMENTAL: no shipping client decodes this until CODEC_PYROWAVE lands"
); );
Ok(Self { Ok(Self {