forked from unom/punktfunk
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
Reviewed-on: unom/punktfunk#106
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user