fix(audio): reset clock.force-rate on stop for all forced sessions (#263 leak)

QBZ forces the global PipeWire clock for any non-locked PipeWire stream, but the
stop/pause-suspend reset was gated on pw_force_bitperfect — which is only set in
DAC-passthrough mode. So a plain PipeWire user (no passthrough) forced the graph
clock on play and never reset it, leaving the whole graph force-clocked after
stopping (a stale clock.force-rate that resamples every other app).

Track whether QBZ actually wrote the force (CLOCK_FORCE_APPLIED) and make
reset_pipewire_clock self-gating: it only resets a force WE applied, never
clobbering another app's forced rate. The player's two reset call sites (Stop,
pause-suspend) now call it unconditionally.

Verified on hardware (Cambridge USB DAC, PipeWire + passthrough off): a 192kHz
track forces clock.force-rate=192000; on pause-suspend the clock returns to 0
(previously stayed stuck at 192000).

cc
This commit is contained in:
vicrodh committed 2026-05-31 09:57:56 -06:00
1 parent d19312fe17
commit b139392b9b
2 files changed
+31 -19

No files matched your search

+11 -19
View File
@@ -2591,17 +2591,13 @@ impl Player {
*pause_suspend_deadline = Some(
Instant::now() + Duration::from_millis(PAUSE_SUSPEND_DELAY_MS),
);
// Reset PipeWire clock if bit-perfect was active
// Reset the PipeWire clock if WE forced it. The call is
// self-gating (reset_pipewire_clock no-ops unless QBZ set
// the force), so it is now unconditional: the previous
// pw_force_bitperfect gate missed plain (no-passthrough)
// PipeWire users and leaked a forced rate after stop (#263).
#[cfg(target_os = "linux")]
if thread_settings
.lock()
.ok()
.map(|s| s.pw_force_bitperfect)
.unwrap_or(false)
{
qbz_audio::pipewire_backend::PipeWireBackend::reset_pipewire_clock(
);
}
qbz_audio::pipewire_backend::PipeWireBackend::reset_pipewire_clock();
log::info!("Audio thread: stopped");
}
AudioCommand::SetVolume(volume) => {
@@ -3228,16 +3224,12 @@ impl Player {
}
drop(stream_opt.take());
pause_suspend_deadline = None;
// Reset PipeWire clock if bit-perfect was active
// Reset the PipeWire clock if WE forced it (self-gating;
// see reset_pipewire_clock). Unconditional now — the
// previous pw_force_bitperfect gate leaked a forced rate
// for plain (no-passthrough) PipeWire users (#263).
#[cfg(target_os = "linux")]
if thread_settings
.lock()
.ok()
.map(|s| s.pw_force_bitperfect)
.unwrap_or(false)
{
qbz_audio::pipewire_backend::PipeWireBackend::reset_pipewire_clock();
}
qbz_audio::pipewire_backend::PipeWireBackend::reset_pipewire_clock();
log::info!("Audio thread: suspended stream after pause");
continue;
}