AV1 decoded on the D3D11VA rung because 31 is not a level, and two host warnings that named the wrong subsystem #205

Merged
enricobuehler merged 3 commits from worktree-av1-level-sentinel into main 2026-08-13 19:35:41 +00:00
Owner

Three fixes off one field report (RTX 5060 Windows client + its Linux/NVENC host, 4K120). Client log and host log both attached to the investigation; the host log turned out to close the loop on the client bug and to expose two diagnostics of our own that misdirect.

1. pf-vkdecode: AV1's "maximum parameters" level is not a level above the ceiling

Every AV1 session demoted to D3D11VA with outside device caps: stream level (seq_level_idx 31) above the device's maxLevel (AV1 Std level 23), while the hardware decoded the stream trivially on the slower rung it fell through to.

seq_level_idx is a 5-bit field. Annex A defines 0…23 (levels 2.0…7.3), reserves 24…30, and makes 31 the "maximum parameters" level — the spec's own way of saying the bitstream is not constrained to any level. StdVideoAV1Level stops at 7.3 = 23, so 31 has no Std code point and the index-coded comparison that holds across 0…23 says nothing here: 31 > 23 is true even of a device that decodes everything AV1 can name, which is what makes it useless as a capability test.

a02014ec fixed the H.264/H.265 half of exactly this and deliberately left AV1 alone, on the premise that "no over-declaration has been seen in the field". The reporter's own log from that same day already showed otherwise.

The host log closes the loop: it is our own Linux direct-SDK NVENC (codec=av1_nvenc). We write no AV1 level because nvenc_core.rs cannot — for AV1 the level enum's 0 is LEVEL 2.0, not autoselect as it is for HEVC — so NVENC's preset default ships, and that default is the sentinel.

Unlike H.265 there is nothing to clamp: StdVideoAV1SequenceHeader carries no level field, so the declaration never reaches the driver. Warn once and proceed.

2. host/audio: stop warning that "the stream will click" when there is no stream

Ten the audio encode thread could not keep up — captured audio was DROPPED warnings, worst reading dropped_chunks=11251. Not one wanted sample was lost.

PipeWire negotiated a 128-frame quantum ⇒ 375 chunks/s ⇒ a 30 s window is exactly 11250, so those were 100 % drop rates at peak_db=-120.0 (digital silence). All ten straddled a session boundary, and dropped_chunks / 375 matches the seconds with no live session in each window to within a fraction of a second.

The capturer is host-lifetime — the planes park it between sessions rather than dropping it — but the consumer is the per-session encode thread and the hand-off channel is a bounded sync_channel(64). So ~170 ms after a session ends it is full and stays full. The counter now only counts while a session is actually reading.

Both backends: the parking call sites are platform-independent, so the WASAPI half had the identical defect (it had no idle at all, and gains one). Only the Linux half has field evidence.

3. host: a 2 s keyframe cadence is the client's flush cooldown, not display churn

The recovery-cadence detector warned that recoveries were METRONOMIC and pointed at display-topology churn, display-poller software and virtual-display timing — at period_s=2.0, which is punktfunk_core::client::FLUSH_COOLDOWN. A client shedding a standing receive queue is rate-limited to one flush+keyframe per cooldown, so it asks at exactly that spacing for as long as it stays behind. Perfect periodicity argues for a fixed software cooldown, not against it.

In this case the host was blameless and the chain ran the other way — and it was fix 1 three layers upstream: AV1 refused → demoted to a slower decode rung → could not hold 4K120 → standing queue. The detector now routes on the shared constant (FLUSH_COOLDOWN is pub for this, documented as such) so the two cannot drift.

Verification

  • pf-vkdecode: 195/195 lib tests (incl. a new one pinning the sentinel arithmetic), fmt, clippy clean.
  • punktfunk-host on linux/amd64 (container): clippy --all-targets -D warnings clean; cargo test native::stream::tests 18/18 incl. the new case, which derives its inputs from FLUSH_COOLDOWN so it survives a retune.
  • punktfunk-host on the Windows CI runner: cargo check clean — after cargo clean -p to defeat the scp-mtime staleness trap, so the Compiling punktfunk-host line is real and not a vacuous pass.
  • cargo fmt --all --check clean.

Not verified

Fix 1 has no on-glass verification. There is no RTX 5060 here, and the reporter's box is the only one that has produced a seq_level_idx 31 stream. The unit test pins the arithmetic, not the decode. Fixes 2 and 3 are log/reporting behaviour and are type- and test-checked on both platforms, but neither has been observed live either.

⚠ Merging moves main while the 0.28.0 release PR (#203) is open.

Three fixes off one field report (RTX 5060 Windows client + its Linux/NVENC host, 4K120). Client log and host log both attached to the investigation; the host log turned out to close the loop on the client bug and to expose two diagnostics of our own that misdirect. ## 1. `pf-vkdecode`: AV1's "maximum parameters" level is not a level above the ceiling Every AV1 session demoted to D3D11VA with `outside device caps: stream level (seq_level_idx 31) above the device's maxLevel (AV1 Std level 23)`, while the hardware decoded the stream trivially on the slower rung it fell through to. `seq_level_idx` is a 5-bit field. Annex A defines 0…23 (levels 2.0…7.3), reserves 24…30, and makes **31 the "maximum parameters" level — the spec's own way of saying the bitstream is not constrained to any level**. `StdVideoAV1Level` stops at 7.3 = 23, so 31 has no Std code point and the index-coded comparison that holds across 0…23 says nothing here: `31 > 23` is true even of a device that decodes everything AV1 can name, which is what makes it useless as a capability test. `a02014ec` fixed the H.264/H.265 half of exactly this and deliberately left AV1 alone, on the premise that "no over-declaration has been seen in the field". The reporter's own log from that same day already showed otherwise. The host log closes the loop: it is **our own `Linux direct-SDK NVENC`** (`codec=av1_nvenc`). We write no AV1 level because `nvenc_core.rs` cannot — for AV1 the level enum's `0` is LEVEL 2.0, not autoselect as it is for HEVC — so NVENC's preset default ships, and that default is the sentinel. Unlike H.265 there is nothing to clamp: `StdVideoAV1SequenceHeader` carries no level field, so the declaration never reaches the driver. Warn once and proceed. ## 2. `host/audio`: stop warning that "the stream will click" when there is no stream Ten `the audio encode thread could not keep up — captured audio was DROPPED` warnings, worst reading `dropped_chunks=11251`. Not one wanted sample was lost. PipeWire negotiated a 128-frame quantum ⇒ 375 chunks/s ⇒ a 30 s window is exactly 11250, so those were **100 % drop rates at `peak_db=-120.0`** (digital silence). All ten straddled a session boundary, and `dropped_chunks / 375` matches the seconds with **no live session** in each window to within a fraction of a second. The capturer is host-lifetime — the planes *park* it between sessions rather than dropping it — but the consumer is the per-session encode thread and the hand-off channel is a bounded `sync_channel(64)`. So ~170 ms after a session ends it is full and stays full. The counter now only counts while a session is actually reading. Both backends: the parking call sites are platform-independent, so the WASAPI half had the identical defect (it had no `idle` at all, and gains one). Only the Linux half has field evidence. ## 3. `host`: a 2 s keyframe cadence is the client's flush cooldown, not display churn The recovery-cadence detector warned that recoveries were `METRONOMIC` and pointed at display-topology churn, display-poller software and virtual-display timing — at `period_s=2.0`, which is `punktfunk_core::client::FLUSH_COOLDOWN`. A client shedding a standing receive queue is rate-limited to one flush+keyframe per cooldown, so it asks at exactly that spacing for as long as it stays behind. Perfect periodicity argues *for* a fixed software cooldown, not against it. In this case the host was blameless and the chain ran the other way — and it was **fix 1** three layers upstream: AV1 refused → demoted to a slower decode rung → could not hold 4K120 → standing queue. The detector now routes on the shared constant (`FLUSH_COOLDOWN` is `pub` for this, documented as such) so the two cannot drift. ## Verification - `pf-vkdecode`: 195/195 lib tests (incl. a new one pinning the sentinel arithmetic), fmt, clippy clean. - `punktfunk-host` on **linux/amd64** (container): `clippy --all-targets -D warnings` clean; `cargo test native::stream::tests` 18/18 incl. the new case, which derives its inputs from `FLUSH_COOLDOWN` so it survives a retune. - `punktfunk-host` on the **Windows CI runner**: `cargo check` clean — after `cargo clean -p` to defeat the scp-mtime staleness trap, so the `Compiling punktfunk-host` line is real and not a vacuous pass. - `cargo fmt --all --check` clean. ## Not verified **Fix 1 has no on-glass verification.** There is no RTX 5060 here, and the reporter's box is the only one that has produced a `seq_level_idx 31` stream. The unit test pins the arithmetic, not the decode. Fixes 2 and 3 are log/reporting behaviour and are type- and test-checked on both platforms, but neither has been observed live either. ⚠ Merging moves `main` while the 0.28.0 release PR (#203) is open.
enricobuehler added 3 commits 2026-08-13 19:01:49 +00:00
A 2026-08-13 field report from the same RTX 5060 client as a02014ec: every AV1
session demoted to D3D11VA with "outside device caps: stream level
(seq_level_idx 31) above the device's maxLevel (AV1 Std level 23)" — 4K120,
NVIDIA, the hardware decoding the stream trivially on the D3D11VA rung it fell
through to. a02014ec fixed the H.264/H.265 half of exactly this and left AV1
alone on the premise that "no over-declaration has been seen in the field";
the reporter's own log from that same day already showed otherwise.

seq_level_idx is a 5-bit field. Annex A defines 0…23 (levels 2.0…7.3),
reserves 24…30, and makes 31 the "maximum parameters" level — the spec's own
way of saying the bitstream is NOT constrained to a level. StdVideoAV1Level
stops at 7.3 = 23, so 31 has no Std code point and the index-coded comparison
that holds across 0…23 says nothing here: 31 > 23 is true even of a device
that decodes everything AV1 can name, which is what makes it useless as a
capability test. We write no AV1 level on any host encode path, so whichever
sentinel the vendor's encoder defaults to is what the client must accept.

So the gate warns once and proceeds, like its H.265 sibling. Unlike H.265
there is nothing to clamp: StdVideoAV1SequenceHeader carries no level field,
so the declaration never reaches the driver and cannot be invalid usage. The
stream's real demands stay enforced where they are physical facts — coded
extent and DPB depth, both checked at session build.

Not verified on glass: no RTX 5060 here, and the reporter's box is the only
one that has produced a seq_level_idx 31 stream. The unit test pins the
arithmetic that made the refusal look reasonable.
A 2026-08-13 field host log carried ten "the audio encode thread could not keep
up — captured audio was DROPPED" warnings, the worst reading
dropped_chunks=11251. That reads like catastrophic audio loss. It was not: not
one sample anybody wanted was lost.

PipeWire negotiated a 128-frame quantum, so the plane produces 48000/128 = 375
chunks/s and a 30 s stats window holds exactly 11250 — those windows were a
100 % drop rate, at peak_db=-120.0 (digital silence). Every one of the ten
straddled a session boundary, and across all of them dropped_chunks/375 matches
the seconds with NO live session in that window to within a fraction of a
second (3890/375 = 10.4 s against a 10.5 s gap; 3616/375 = 9.6 s against 9.8 s).

The capturer is host-lifetime: the native and gamestream planes PARK it between
sessions (`AudioCapturer::idle`) rather than dropping it, but the consumer is
the per-session encode thread. The hand-off channel is a bounded
sync_channel(64), so ~170 ms after a session ends it is full and every
try_send fails for as long as the host sits idle — counted as the encode thread
falling behind, and reported with a sentence about a stream that does not
exist. It is the worst kind of false alarm: it names a real failure mode, in a
subsystem with real open audio work, at a volume that demands attention.

So the drop counter now only counts while a session is actually reading, via an
`active` flag shared with the capture thread and toggled by the same
open/drain/idle/Drop transitions that already own the routing claim. A full
channel under a live consumer still means exactly what it used to.

Both backends: the parking call sites are platform-independent, so the WASAPI
half had the identical defect (it had no `idle` at all, and gains one). Only
the Linux half has field evidence.

Verified: punktfunk-host clippy --all-targets -D warnings clean on
linux/amd64 (container) and cargo check clean on the Windows CI runner.
fix(host): a 2 s keyframe cadence is the client's flush cooldown, not display churn
ci / bun-nix (pull_request) Successful in 45s
apple / swift (pull_request) Successful in 2m12s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 5m52s
ci / web (pull_request) Successful in 6m40s
ci / rust-arm64 (pull_request) Successful in 6m54s
android / android (pull_request) Successful in 7m42s
ci / rust (pull_request) Successful in 12m59s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 2m55s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m28s
8216f1d92d
The host's recovery-cadence detector warns that "client keyframe recoveries are
METRONOMIC — a periodic host/display disturbance (display-topology churn,
display-poller software, virtual-display timing) is the likely cause, not
random network loss". In a 2026-08-13 field log it fired at period_s=2.0 and
sent the investigation at three innocent host subsystems.

2.0 s is `punktfunk_core::client::FLUSH_COOLDOWN`. The client's receive-backlog
guard sheds a standing queue with a flush plus a keyframe request and is
rate-limited to one per cooldown, so a client that cannot sustain the stream
asks for a keyframe at EXACTLY that spacing for as long as it stays behind —
the constant's own doc says it "degrades into a periodic skip + a logged
warning", which is the behaviour the detector then read as physical. Perfect
periodicity argues FOR a fixed software cooldown, not against it.

In the field case the host was blameless and the chain ran the other way: the
client refused the negotiated codec on its Vulkan rung, demoted to a slower
decode path, could not hold 4K120 there, and built the standing queue. Three
layers between the symptom the host reported and the cause.

So the detector now routes: a period on the client's cooldown names the client
and says where to look in ITS log (`receive backlog stopped draining`, and a
demoted decode rung); anything else keeps the display-disturbance wording it
had. The comparison reads FLUSH_COOLDOWN itself — now `pub` for exactly this,
documented as such — rather than a copy of the number, so the two cannot drift.
±10 % absorbs scheduling jitter and the request's trip without being wide
enough to swallow the disturbance cadences the other branch exists to report.

Verified: 18/18 native::stream::tests on linux/amd64 (container), including the
new case, which derives its inputs from FLUSH_COOLDOWN so it survives a retune;
clippy --all-targets -D warnings clean; cargo check clean on the Windows CI
runner.
enricobuehler merged commit 99c245520c into main 2026-08-13 19:35:41 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#205