feat(protocol,clients): codec preference negotiation + Linux client decodes per Welcome (Phase 2a)

Adds a client-selectable **preferred codec** and wires the core + ABI + probe + Linux client to
negotiate and decode it. (Windows/Apple/Android follow in 2b.)

**Core:**
- `Hello.preferred_codec` (a single CODEC_* bit, 0 = auto) — a soft hint appended after
  `video_codecs`. `resolve_codec(client, host, preferred)` now honors the preference when the host
  can also emit it, else falls back to precedence (HEVC > AV1 > H.264). Roundtrip + preference tests.
- `NativeClient::connect` takes `video_codecs` + `preferred_codec`; `NativeClient.codec` exposes the
  resolved `Welcome.codec`.
- ABI: `punktfunk_connect_ex7` (adds the two codec params; `ex6` delegates to it advertising
  HEVC-only) + `punktfunk_connection_codec` getter + `PUNKTFUNK_CODEC_{H264,HEVC,AV1}` constants
  (drift-guarded against the wire values). Header regenerated.

**Host:** passes `hello.preferred_codec` into `resolve_codec`.

**probe:** `--codec h264|hevc|av1|auto` sets the preference (still advertises it can decode all
three); the dump extension already follows the resolved codec.

**Linux client:** advertises the codecs FFmpeg can actually decode (`decodable_codecs()`), threads
the user's `codec` setting as the preference, and builds the decoder — both the software and VAAPI
paths, plus the mid-session VAAPI→software demotion — from the negotiated `Welcome.codec` instead of
hardcoding HEVC. New "Video codec" dropdown in Preferences (Automatic/HEVC/H.264/AV1).

Live-validated on the dev box: probe `--codec hevc` against a software (H.264-only) host resolves to
H.264 (graceful soft-preference fallback), no failure. clippy + core (57) + host (133) tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 00:13:26 +00:00
parent ffc0b07b46
commit 12843fe253
36 changed files with 529 additions and 144 deletions
+9 -2
View File
@@ -663,8 +663,9 @@ async fn serve_session(
// client's advertised codecs). A GPU-less software host emits H.264, so an HEVC-only client
// shares nothing with it → refuse honestly rather than send a stream it can't decode.
let host_codecs = crate::encode::Codec::host_wire_caps();
let codec_bit = punktfunk_core::quic::resolve_codec(hello.video_codecs, host_codecs)
.ok_or_else(|| {
let codec_bit =
punktfunk_core::quic::resolve_codec(hello.video_codecs, host_codecs, hello.preferred_codec)
.ok_or_else(|| {
anyhow!(
"no shared video codec: client advertised 0x{:02x}, host can emit 0x{:02x} \
(a software-encode host produces H.264 — the client must advertise CODEC_H264)",
@@ -3976,6 +3977,8 @@ mod tests {
0,
0, // video_caps
2, // audio_channels (stereo)
0, // video_codecs (0 → HEVC-only)
0, // preferred_codec (auto)
None, // launch
None, // pin: TOFU — the operator's approval (not a PIN) authorizes this client
Some((cert, key)),
@@ -4036,6 +4039,8 @@ mod tests {
0,
0, // video_caps
2, // audio_channels (stereo)
0, // video_codecs
0, // preferred_codec
None, // launch
None,
None,
@@ -4062,6 +4067,8 @@ mod tests {
0,
0, // video_caps
2, // audio_channels (stereo)
0, // video_codecs
0, // preferred_codec
None, // launch
Some(host_fp),
Some((cert.clone(), key.clone())),