fix: add sample rate fallback to ALSA backend CPAL path
When ALSA Direct can't handle a rate (e.g. 176.4kHz) and falls back to CPAL, CPAL also fails because the hardware doesn't support it. Port find_best_fallback_rate from PipeWire backend: queries supported rates via CPAL, picks highest rate in same family (44.1k or 48k) that's <= requested. 176.4kHz on a 96kHz-max DAC now falls back to 88.2kHz and rodio resamples, instead of failing entirely.
This commit is contained in:
1 file changed
+69
-23
@@ -31,6 +31,35 @@ const COMMON_SAMPLE_RATES: &[u32] = &[
|
|||||||
384000, // Ultra high-res
|
384000, // Ultra high-res
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Find the best fallback sample rate in the same family.
|
||||||
|
/// 44.1kHz family: 44100, 88200, 176400, 352800
|
||||||
|
/// 48kHz family: 48000, 96000, 192000, 384000
|
||||||
|
fn find_best_fallback_rate(requested: u32, supported: &[u32]) -> u32 {
|
||||||
|
let is_441_family = requested % 44100 == 0;
|
||||||
|
|
||||||
|
// Find highest supported rate in the same family that's <= requested
|
||||||
|
let mut candidates: Vec<u32> = supported
|
||||||
|
.iter()
|
||||||
|
.filter(|&&r| {
|
||||||
|
if is_441_family {
|
||||||
|
r % 44100 == 0
|
||||||
|
} else {
|
||||||
|
r % 48000 == 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(|&&r| r <= requested)
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
candidates.sort();
|
||||||
|
|
||||||
|
if let Some(&best) = candidates.last() {
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No rate in the same family — use highest supported rate overall
|
||||||
|
supported.iter().copied().max().unwrap_or(48000)
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract supported sample rates from a CPAL device
|
/// Extract supported sample rates from a CPAL device
|
||||||
fn get_supported_sample_rates(device: &rodio::cpal::Device) -> Option<Vec<u32>> {
|
fn get_supported_sample_rates(device: &rodio::cpal::Device) -> Option<Vec<u32>> {
|
||||||
use rodio::cpal::traits::DeviceTrait;
|
use rodio::cpal::traits::DeviceTrait;
|
||||||
@@ -702,19 +731,6 @@ impl AudioBackend for AlsaBackend {
|
|||||||
let device_name = device.name().unwrap_or_else(|_| "unknown".to_string());
|
let device_name = device.name().unwrap_or_else(|_| "unknown".to_string());
|
||||||
log::info!("[ALSA Backend] Using device: {}", device_name);
|
log::info!("[ALSA Backend] Using device: {}", device_name);
|
||||||
|
|
||||||
// Create StreamConfig with requested sample rate
|
|
||||||
let stream_config = StreamConfig {
|
|
||||||
channels: config.channels,
|
|
||||||
sample_rate: config.sample_rate,
|
|
||||||
buffer_size: if config.exclusive_mode {
|
|
||||||
// Smaller buffer for exclusive mode = lower latency
|
|
||||||
BufferSize::Fixed(512)
|
|
||||||
} else {
|
|
||||||
// ~100ms period for fewer CPU wakeups (matches previous vendored cpal tuning)
|
|
||||||
BufferSize::Fixed(config.sample_rate / 10)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if device supports this configuration
|
// Check if device supports this configuration
|
||||||
let supported_configs = device
|
let supported_configs = device
|
||||||
.supported_output_configs()
|
.supported_output_configs()
|
||||||
@@ -737,12 +753,36 @@ impl AudioBackend for AlsaBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found_matching {
|
// If device doesn't support the requested rate, find best fallback
|
||||||
log::warn!(
|
let effective_rate = if !found_matching {
|
||||||
"[ALSA Backend] Device may not support {}Hz, attempting anyway",
|
if let Some(rates) = get_supported_sample_rates(&device) {
|
||||||
|
let fallback = find_best_fallback_rate(config.sample_rate, &rates);
|
||||||
|
log::warn!(
|
||||||
|
"[ALSA Backend] Device doesn't support {}Hz. Supported: {:?}. Falling back to {}Hz (rodio will resample)",
|
||||||
|
config.sample_rate, rates, fallback
|
||||||
|
);
|
||||||
|
fallback
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"[ALSA Backend] Could not determine supported rates, attempting {}Hz anyway",
|
||||||
|
config.sample_rate
|
||||||
|
);
|
||||||
config.sample_rate
|
config.sample_rate
|
||||||
);
|
}
|
||||||
}
|
} else {
|
||||||
|
config.sample_rate
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rebuild StreamConfig with effective rate
|
||||||
|
let stream_config = StreamConfig {
|
||||||
|
channels: config.channels,
|
||||||
|
sample_rate: effective_rate,
|
||||||
|
buffer_size: if config.exclusive_mode {
|
||||||
|
BufferSize::Fixed(512)
|
||||||
|
} else {
|
||||||
|
BufferSize::Fixed(effective_rate / 10)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Create SupportedStreamConfig
|
// Create SupportedStreamConfig
|
||||||
let supported_config = SupportedStreamConfig::new(
|
let supported_config = SupportedStreamConfig::new(
|
||||||
@@ -777,11 +817,17 @@ impl AudioBackend for AlsaBackend {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
log::info!(
|
if effective_rate != config.sample_rate {
|
||||||
"[ALSA Backend] Output stream created successfully at {}Hz (exclusive: {})",
|
log::info!(
|
||||||
config.sample_rate,
|
"[ALSA Backend] Output stream created at {}Hz (resampled from {}Hz, exclusive: {})",
|
||||||
config.exclusive_mode
|
effective_rate, config.sample_rate, config.exclusive_mode
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
log::info!(
|
||||||
|
"[ALSA Backend] Output stream created successfully at {}Hz (exclusive: {})",
|
||||||
|
config.sample_rate, config.exclusive_mode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(mixer_sink)
|
Ok(mixer_sink)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user