Merge pull request 'The 272 ms audio buffer was legal: the PipeWire callback filled the buffer ceiling, not the graph's request' (#106) from fix/pw-playback-requested into main
apple / swift (push) Successful in 1m34s
ci / web (push) Successful in 1m3s
ci / bun-nix (push) Successful in 17s
ci / docs-site (push) Successful in 1m15s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m37s
ci / rust-arm64 (push) Successful in 3m1s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 8s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 9s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 13s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 27s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 23s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 17s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 17s
android / android (push) Successful in 5m6s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m43s
apple / screenshots (push) Successful in 6m5s
deb / build-publish (push) Successful in 4m8s
deb / build-publish-host (push) Successful in 3m54s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m9s
deb / build-publish-client-arm64 (push) Successful in 4m36s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m11s
ci / rust (push) Canceled after 2m31s
docker / builders-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
arch / build-publish (push) Successful in 8m34s
flatpak / build-publish (push) Successful in 18m15s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m28s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 22m4s

Reviewed-on: #106
This commit was merged in pull request #106.
This commit is contained in:
2026-08-08 00:12:02 +00:00
2 changed files with 33 additions and 2 deletions
+5 -1
View File
@@ -118,7 +118,11 @@ rand = "0.9"
# need the hidapi driver). Linux links the system SDL3; Windows builds it from source
# (no system SDL3 there — same choice as clients/windows).
[target.'cfg(target_os = "linux")'.dependencies]
pipewire = "0.9"
# `v0_3_49` for `Buffer::requested` — the graph's per-cycle frame ask, without which the
# playback callback can only size writes from the buffer CEILING (quantum-limit, ~170 ms).
# Pure cfg gate; needs libpipewire ≥ 0.3.49 (2022-03) at runtime, which every ship target
# (SteamOS, flatpak runtimes, Arch, Ubuntu ≥ 22.10) clears.
pipewire = { version = "0.9", features = ["v0_3_49"] }
sdl3 = { version = "0.18", features = ["hidapi"] }
# Native VAAPI decode (M6 of the native-decode program): the hand-declared libva buffer
# layouts, the profile/format/surface decisions, the AuPlan → picparams/IQ/slice
+28 -1
View File
@@ -274,14 +274,41 @@ fn pw_thread(
chunk.clear();
let _ = ud.recycle.try_send(chunk);
}
// The graph asks for `requested` frames this cycle (one quantum, after
// rate-matching); the mapped buffer is sized for the WORST case — PipeWire's
// `quantum-limit`, 8192 frames ≈ 170 ms — not for this cycle. Filling to
// capacity queued ~170 ms per buffer downstream of the ring and, worse, taught
// the jitter policy that the device drains 170 ms per callback, which lifted
// the underrun floor (`want` + one frame) above any depth the A/V sync loop is
// allowed to ask for: audio sat a stable ~270 ms late and, by the continuity
// rule, sync was FORBIDDEN from draining it. Capacity is only the ceiling;
// `requested == 0` (no adapter suggestion) falls back to it.
let requested = usize::try_from(buffer.requested()).unwrap_or(0);
let stride = 4 * ud.channels; // F32LE interleaved
let datas = buffer.datas_mut();
if datas.is_empty() {
return;
}
let data = &mut datas[0];
let want_frames = data.data().map(|s| s.len() / stride).unwrap_or(0);
let max_frames = data.data().map(|s| s.len() / stride).unwrap_or(0);
let want_frames = if requested > 0 {
requested.min(max_frames)
} else {
max_frames
};
let want = want_frames * ud.channels;
// Once per stream, in the shape of the host's per-capture-open quantum log:
// whether the graph's request or the buffer ceiling is sizing our writes is
// exactly what an on-glass latency report needs to say.
if ud.callbacks == 0 {
tracing::info!(
requested_frames = requested,
capacity_frames = max_frames,
write_frames = want_frames,
write_ms = want_frames / 48,
"audio playback quantum"
);
}
// A/V sync: take whatever depth the decode thread's sync loop last asked for, and
// publish where the ring actually is so it can measure the result. The policy