pub mod aec;
pub mod input;
pub mod output;
pub mod resample;
pub use input::{list_devices, push_block_drop_count, AudioInputConfig, MicCapture, CAPTURE_RATE};
pub use output::{
push_clip_blocking, AudioOutputConfig, OutputPump, Playback, PlaybackHandle, CLIP_SAMPLE_RATE,
};
pub use resample::{resample_offline, LinearResampler};
use cpal::traits::DeviceTrait;
use crate::error::{AudioError, Result};
pub(crate) fn device_name(device: &cpal::Device) -> Option<String> {
match device.description() {
Ok(desc) => Some(desc.name().to_string()),
Err(_) => {
let name = device.to_string();
(!name.is_empty()).then_some(name)
}
}
}
pub(crate) fn pick_device<I>(
name: Option<&str>,
devices: std::result::Result<I, cpal::Error>,
default: Option<cpal::Device>,
) -> Result<cpal::Device>
where
I: Iterator<Item = cpal::Device>,
{
match name {
Some(name) => Ok(devices
.map_err(|err| AudioError::StreamConfig(err.to_string()))?
.find(|dev| device_name(dev).as_deref() == Some(name))
.ok_or(AudioError::NoDevice)?),
None => Ok(default.ok_or(AudioError::NoDevice)?),
}
}
pub(crate) fn config_ranges<I>(
kind: &str,
configs: std::result::Result<I, cpal::Error>,
) -> Result<Vec<cpal::SupportedStreamConfigRange>>
where
I: Iterator<Item = cpal::SupportedStreamConfigRange>,
{
match configs {
Ok(ranges) => Ok(ranges.collect()),
Err(err) => {
tracing::warn!(%err, "{kind} device config query failed; device unusable");
Err(AudioError::NoDevice.into())
}
}
}
pub(crate) fn negotiate_config(
ranges: &[cpal::SupportedStreamConfigRange],
preferred_rate: u32,
) -> Option<cpal::SupportedStreamConfig> {
for format in [
cpal::SampleFormat::F32,
cpal::SampleFormat::I16,
cpal::SampleFormat::U16,
] {
let mut fallback = None;
for range in ranges
.iter()
.copied()
.filter(|r| r.sample_format() == format)
{
if let Some(cfg) = range.try_with_sample_rate(preferred_rate) {
return Some(cfg);
}
fallback.get_or_insert_with(|| range.with_max_sample_rate());
}
if fallback.is_some() {
return fallback;
}
}
None
}