Files
enricobuehler 47d8301aeb feat(host): the presentation clock advanced by a frame's label rather than its length
Admits the 44.1 kHz family and hi-res surround, and fixes the drift that admitting them
turns from latent into live.

`next_pts_ns += frame_us * 1000` advanced the clock by the frame's NOMINAL duration.
That was harmless while every rate divided every rung, and 44 100 Hz divides none of
them: a "5 ms" frame carries 220 samples per channel, which is 4 988 662 ns. The clock
therefore invented 2 267 574 ns every second — 2 272 ppm, 8.2 seconds an hour — and the
re-anchor beside it is `max`, so it only ever corrected FORWARD. A fast clock was never
pulled back, and the A/V sync loop would have chased it forever.

The clock is now a running sample total rather than a sum of per-frame durations, which
accumulates exactly zero where summing floored frames accumulates about a nanosecond a
frame. It lives in a small `PtsClock` because the drift sat inside `audio_thread`, which
no test can reach; as a struct it is provable, and the planted-error run pins every
ladder rung rather than just 5 ms. `advance` folds whole seconds out of the sample count
into the base — exact, since `rate x channels` samples is precisely one second — because
`usize` is 32-bit on some targets and 176.4 kHz 7.1 would wrap it in under an hour.

The frame pacer was changed to agree even though it is cosmetic: it is gated on real data
being available, so a nominal value produced a slot the pacer waited out rather than time
it invented. Two clocks describing the same frame and disagreeing by 0.23 % is the kind
of thing a later reader reconciles in the wrong direction.

The rate gate reads `pcm::rate_is_supported` instead of restating the set. The Windows
48 kHz floor is untouched and never needed touching — the `max` sits inside the
"requested above engine" arm, so a 44 100 request never tripped it. The comment now
separates the two rules that read as one line: refusing to ask for more than the engine
has is the honesty rule for either plane; the 48 kHz floor is for Opus alone, because
libopus accepts 8/12/16/24/48 kHz only.

Surround's `channels != 2` decline is DELETED rather than relaxed. The design called it a
one-line restriction; it was worse than that, because the check ran BEFORE the frame
ladder and overrode the one piece of code that knows the answer. The ladder is
channel-aware, so it decides and `None` declines — and the design's blanket "surround is
out at the default MTU" turns out to be wrong in both directions: 48 kHz 5.1 fits at
1.5 ms and 7.1 at 1 ms, while 16-bit 5.1 fits even at 88.2 and 96 kHz. What is actually
true is that nothing surround fits above 48 kHz in 24-bit, and no 7.1 fits above 48 kHz
at all.

Two stale figures corrected while adjacent: the budget doc claimed a 20 Mbps session
affords the whole stereo ladder (176.4/24 stereo is 8.5 Mbps and wants 33.9), and the
operator-facing decline log quoted 1.5–4.6 Mbps for a plane that now spans 1.4–8.5 in
stereo and 33.9 in 7.1.
2026-08-16 11:52:53 +02:00
..