feat(audio): use device default sample rate and always downsample (#1084)

* feat(audio): use device default sample rate and always downsample

instead of forcing the microphone to open at 16kHz (which can cause
issues with bluetooth codecs, some ALSA drivers, and other devices
that advertise 16kHz support but produce suboptimal audio), use the
device's native/default sample rate and let the existing FrameResampler
downsample to 16kHz for the whisper pipeline.

the resampling infrastructure (rubato FftFixedIn) already exists in
run_consumer() and short-circuits when in_hz == out_hz, so devices
that natively default to 16kHz are unaffected.

* fix: graceful fallback when supported_input_configs fails

some ALSA/PipeWire drivers support default_input_config but have
a broken supported_input_configs implementation. fall back to the
default config instead of propagating the error. also add a warn
log when no config matches the device's default rate.
This commit is contained in:
Viren Mohindra 2026-03-18 20:53:03 -04:00 committed by GitHub
parent d33535cfd3
commit 0b3322faf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -282,13 +282,26 @@ impl AudioRecorder {
fn get_preferred_config(
device: &cpal::Device,
) -> Result<cpal::SupportedStreamConfig, Box<dyn std::error::Error>> {
let supported_configs = device.supported_input_configs()?;
// Use the device's native/default sample rate and let the FrameResampler
// in run_consumer() downsample to 16kHz. This avoids forcing hardware into
// a non-native rate which can cause issues on some devices (Bluetooth
// codecs, certain ALSA drivers, etc.).
let default_config = device.default_input_config()?;
let target_rate = default_config.sample_rate();
// Try to find the best sample format at the device's default rate
let supported_configs = match device.supported_input_configs() {
Ok(configs) => configs,
Err(e) => {
log::warn!("Could not enumerate input configs ({e}), using device default");
return Ok(default_config);
}
};
let mut best_config: Option<cpal::SupportedStreamConfigRange> = None;
// Try to find a config that supports 16kHz, prioritizing better formats
for config_range in supported_configs {
if config_range.min_sample_rate().0 <= constants::WHISPER_SAMPLE_RATE
&& config_range.max_sample_rate().0 >= constants::WHISPER_SAMPLE_RATE
if config_range.min_sample_rate() <= target_rate
&& config_range.max_sample_rate() >= target_rate
{
match best_config {
None => best_config = Some(config_range),
@ -310,11 +323,15 @@ impl AudioRecorder {
}
if let Some(config) = best_config {
return Ok(config.with_sample_rate(cpal::SampleRate(constants::WHISPER_SAMPLE_RATE)));
return Ok(config.with_sample_rate(target_rate));
}
// If no config supports 16kHz, fall back to default
Ok(device.default_input_config()?)
// Fall back to device default if no config matched (exotic/virtual devices)
log::warn!(
"No supported config matched device default rate {:?}, using default config",
target_rate
);
Ok(default_config)
}
}