fix(player): rebuild output stream on sample-rate change for all backends (#449)
The stream-recreate decision only forced a rebuild on a decoded-format change
for bit-perfect backends (DAC passthrough, ALSA Direct, CoreAudio exclusive).
The default/PipeWire path relied on Stop dropping the stream to get a fresh one
at the new rate; once that drop was deferred to avoid a track-change click
(e93fcaec), the PipeWire node stayed locked to the first track's rate for all
subsequent tracks — a regression vs 1.2.10. Make any decoded-format change
rebuild the stream on every backend; same-rate tracks still reuse it, so the
click fix is preserved.
Verified on hardware via pw-top: PipeWire without passthrough now switches rate
per track; PipeWire with passthrough and ALSA Direct unchanged (no regression).
cc
This commit is contained in:
1 file changed
+27
-16
@@ -642,10 +642,11 @@ struct StreamRecreateDecision {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read settings once and evaluate every condition that forces a stream
|
/// Read settings once and evaluate every condition that forces a stream
|
||||||
/// rebuild: format change on backends that demand bit-perfect output (DAC
|
/// rebuild: any decoded-format change (sample rate / channels — the output
|
||||||
/// passthrough, ALSA Direct, CoreAudio Exclusive) and CoreAudio shared-mode
|
/// stream must follow the track's native rate on every backend, #449) and
|
||||||
/// rate drift (CPAL caches the device rate at open time, so when the OS
|
/// CoreAudio shared-mode rate drift (CPAL caches the device rate at open
|
||||||
/// nominal rate moves we must rebuild or playback runs at the wrong speed).
|
/// time, so when the OS nominal rate moves we must rebuild or playback runs
|
||||||
|
/// at the wrong speed).
|
||||||
///
|
///
|
||||||
/// `current_track_sample_rate` / `current_track_channels` describe the last
|
/// `current_track_sample_rate` / `current_track_channels` describe the last
|
||||||
/// *decoded source* format and are compared to the incoming `sample_rate` /
|
/// *decoded source* format and are compared to the incoming `sample_rate` /
|
||||||
@@ -732,16 +733,20 @@ fn evaluate_stream_recreate(
|
|||||||
fn compute_needs_new_stream(
|
fn compute_needs_new_stream(
|
||||||
has_stream: bool,
|
has_stream: bool,
|
||||||
format_changed: bool,
|
format_changed: bool,
|
||||||
dac_passthrough: bool,
|
_dac_passthrough: bool,
|
||||||
using_alsa_direct: bool,
|
_using_alsa_direct: bool,
|
||||||
using_coreaudio_exclusive: bool,
|
_using_coreaudio_exclusive: bool,
|
||||||
coreaudio_shared_rate_mismatch: bool,
|
coreaudio_shared_rate_mismatch: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
!has_stream
|
// A decoded-format change (sample rate or channel count) requires a fresh
|
||||||
|| (dac_passthrough && format_changed)
|
// output stream on EVERY backend so the device follows the track's native
|
||||||
|| (using_alsa_direct && format_changed)
|
// rate (#449). The bit-perfect flags used to gate this, which was correct
|
||||||
|| (using_coreaudio_exclusive && format_changed)
|
// only while Stop dropped the stream; once that drop was deferred to avoid
|
||||||
|| coreaudio_shared_rate_mismatch
|
// a track-change click (e93fcaec), the default/PipeWire path stopped
|
||||||
|
// switching rates and stayed locked to the first track. Same-rate tracks
|
||||||
|
// keep reusing the stream (format_changed == false), preserving the click
|
||||||
|
// fix.
|
||||||
|
!has_stream || format_changed || coreaudio_shared_rate_mismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to create output stream using the backend system (if configured)
|
/// Try to create output stream using the backend system (if configured)
|
||||||
@@ -3668,10 +3673,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_change_alone_does_not_force_rebuild_on_default_backend() {
|
fn format_change_on_default_backend_rebuilds_for_native_rate() {
|
||||||
// Rodio handles sample-rate conversion; a format change without a
|
// #449 regression guard: a decoded sample-rate/channel change must
|
||||||
// bit-perfect backend does not require a new stream.
|
// rebuild the output stream on EVERY backend, not just the bit-perfect
|
||||||
assert!(!compute_needs_new_stream(
|
// ones, so the device follows the track's native rate. 1.2.10 got this
|
||||||
|
// "for free" because Stop dropped the stream; once that drop was
|
||||||
|
// deferred to avoid a track-change click (e93fcaec), reusing the stream
|
||||||
|
// on the default/PipeWire backend left the node locked to the first
|
||||||
|
// track's rate for every subsequent track. Same-rate tracks still
|
||||||
|
// reuse (format_changed == false), preserving the click fix.
|
||||||
|
assert!(compute_needs_new_stream(
|
||||||
true, true, false, false, false, false
|
true, true, false, false, false, false
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user