From cba5b76bb3896a780f68b71cc1c0e31172b5b3e3 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:17:59 -0600 Subject: [PATCH] fix(audio): fallback to plughw instead of CPAL when DAC rate unsupported (#288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/qbz-audio/src/alsa_backend.rs | 34 +++++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/qbz-audio/src/alsa_backend.rs b/crates/qbz-audio/src/alsa_backend.rs index f7e7976af..f73f40d50 100644 --- a/crates/qbz-audio/src/alsa_backend.rs +++ b/crates/qbz-audio/src/alsa_backend.rs @@ -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 {