fix(audio): fallback to plughw instead of CPAL when DAC rate unsupported (#288)
When /proc/asound reports that the selected hw: device does not natively support the track's sample rate (e.g., 96 kHz track on a 48 kHz-only Vanatoo Transparent Zero), the previous code returned None from try_create_direct_stream. The caller then dispatched to CPAL's create_output_stream, where the CPAL ALSA host could not locate the device by its /proc/asound name and failed with the misleading error 'Device not found'. The same function already has a plughw: fallback path further down that wraps the hw device with ALSA's plug plugin — the latter resamples in ALSA space and is the correct behavior for rate mismatches. This change makes the /proc/asound pre-check set a local flag that skips the hw attempt, falling through to the existing plughw branch instead of returning None. Sound now comes out. Bit-perfect is lost (by necessity) and the mode reported back to the player is PluginFallback, which lets the UI surface the degradation (wired up in Fix 4). cc
This commit is contained in:
1 file changed
+23
-11
@@ -564,33 +564,45 @@ impl AlsaBackend {
|
||||
// before opening the PCM device. This avoids the "device busy" issue
|
||||
// where ALSA Direct opens the device, fails to configure the rate, and
|
||||
// leaves the device in a state CPAL can't open afterwards.
|
||||
//
|
||||
// When the rate is unsupported, we skip the hw: attempt and fall through
|
||||
// to the plughw: path below, which lets ALSA auto-resample in kernel/
|
||||
// userspace. Falling back to CPAL is not an option because CPAL's device
|
||||
// enumeration does not reliably expose raw hw: devices by the same name
|
||||
// the UI stored (front:CARD=X,DEV=Y vs hw:CARD=X,DEV=Y), and the lookup
|
||||
// fails with a misleading "Device not found" error. See issue #288.
|
||||
let mut hw_rate_unsupported = false;
|
||||
if let Some(card_name) = extract_card_name_from_device(device_id) {
|
||||
if let Some(hw_rates) = get_hw_supported_rates(&card_name) {
|
||||
if !hw_rates.contains(&config.sample_rate) {
|
||||
log::info!(
|
||||
"[ALSA Backend] Hardware rates for '{}': {:?}. {}Hz not supported, skipping ALSA Direct",
|
||||
"[ALSA Backend] Hardware rates for '{}': {:?}. {}Hz not supported natively — falling back to plughw for software resample",
|
||||
card_name, hw_rates, config.sample_rate
|
||||
);
|
||||
return None;
|
||||
hw_rate_unsupported = true;
|
||||
} else {
|
||||
log::info!(
|
||||
"[ALSA Backend] Hardware confirms support for {}Hz (card '{}', rates: {:?})",
|
||||
config.sample_rate,
|
||||
card_name,
|
||||
hw_rates
|
||||
);
|
||||
}
|
||||
log::info!(
|
||||
"[ALSA Backend] Hardware confirms support for {}Hz (card '{}', rates: {:?})",
|
||||
config.sample_rate,
|
||||
card_name,
|
||||
hw_rates
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Respect ALSA plugin selection from settings
|
||||
// Respect ALSA plugin selection from settings. When /proc/asound already
|
||||
// told us the hw device won't accept the rate, skip straight to plughw
|
||||
// even if the user prefers Hw — plughw with resample is the only path
|
||||
// that produces sound.
|
||||
let try_hw_first = match config.alsa_plugin {
|
||||
Some(AlsaPlugin::Hw) => true,
|
||||
Some(AlsaPlugin::Hw) => !hw_rate_unsupported,
|
||||
Some(AlsaPlugin::PlugHw) => false, // Skip hw, go directly to plughw
|
||||
Some(AlsaPlugin::Pcm) => {
|
||||
log::info!("[ALSA Backend] PCM mode selected, not using direct ALSA");
|
||||
return None; // Use CPAL instead
|
||||
}
|
||||
None => true, // Default: try hw first
|
||||
None => !hw_rate_unsupported, // Default: try hw first if rate is supported
|
||||
};
|
||||
|
||||
if try_hw_first {
|
||||
|
||||
Reference in new issue
Block a user