use cpal::{
traits::{DeviceTrait, HostTrait},
BufferSize, DefaultStreamConfigError, DeviceNameError, DevicesError, SampleRate, StreamConfig,
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigsError,
};
use crate::standalone_processor::StandaloneOptions;
#[derive(Clone, Copy)]
pub enum AudioIOMode {
Input,
Output,
}
fn list_devices<Host: HostTrait>(
host: &Host,
mode: AudioIOMode,
) -> Result<impl Iterator<Item = Host::Device>, DevicesError> {
match mode {
AudioIOMode::Input => host.input_devices(),
AudioIOMode::Output => host.output_devices(),
}
}
fn supported_configs(
device: &impl DeviceTrait,
mode: AudioIOMode,
) -> Result<Vec<cpal::SupportedStreamConfigRange>, cpal::SupportedStreamConfigsError> {
match mode {
AudioIOMode::Input => device.supported_input_configs().map(|i| i.collect()),
AudioIOMode::Output => device.supported_output_configs().map(|i| i.collect()),
}
}
fn device_name(options: &StandaloneOptions, mode: AudioIOMode) -> Option<&String> {
match mode {
AudioIOMode::Input => options.input_device.as_ref(),
AudioIOMode::Output => options.output_device.as_ref(),
}
}
fn default_device<Host: HostTrait>(host: &Host, mode: AudioIOMode) -> Option<Host::Device> {
match mode {
AudioIOMode::Input => host.default_input_device(),
AudioIOMode::Output => host.default_output_device(),
}
}
fn default_config(
device: &impl DeviceTrait,
mode: AudioIOMode,
) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
match mode {
AudioIOMode::Input => device.default_input_config(),
AudioIOMode::Output => device.default_output_config(),
}
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigureDeviceError {
#[error("Missing device: {0:?}")]
MissingDevice(Option<String>),
#[error("Supported streams listing error: {0:?}")]
SupportedStreamConfig(#[from] SupportedStreamConfigsError),
#[error("Default stream config error: {0:?}")]
DefaultStreamConfig(#[from] DefaultStreamConfigError),
#[error("Name error: {0:?}")]
NameError(#[from] DeviceNameError),
}
fn configure_device<Host: HostTrait>(
host: &Host,
options: &StandaloneOptions,
mode: AudioIOMode,
buffer_size: usize,
sample_rate: usize,
) -> Result<(Host::Device, StreamConfig), ConfigureDeviceError> {
let device_name = device_name(options, mode);
log::debug!(
"Device name option provided = device_name={:?}",
device_name
);
let device = device_name
.and_then(|device_name| {
let mut devices = list_devices(host, mode).map(Some).unwrap_or(None)?;
devices.find(|device| matches!(device.name(), Ok(name) if &name == device_name))
})
.map(Ok)
.unwrap_or_else(|| {
log::debug!("No name provided, using default device");
default_device(host, mode)
.map(Ok)
.unwrap_or_else(|| Err(ConfigureDeviceError::MissingDevice(device_name.cloned())))
})?;
log::debug!("Found device device_name={:?}", device.name()?);
log::debug!("Listing supported configs");
let supported_configs = supported_configs(&device, mode)?;
let mut supports_stereo = false;
let mut supports_buffer_size = false;
let mut supports_sample_rate = false;
for config in supported_configs {
log::debug!(" Supported config: {:?}", config);
if config.channels() > 1 {
supports_stereo = true;
}
if let SupportedBufferSize::Range { min, max } = config.buffer_size() {
let buffer_size = buffer_size as u32;
if buffer_size >= *min && buffer_size <= *max {
supports_buffer_size = true;
}
}
let sample_rate = sample_rate as u32;
if config.min_sample_rate().0 >= sample_rate && config.max_sample_rate().0 <= sample_rate {
supports_sample_rate = true;
}
}
log::debug!("Negotiating default configuration");
let config = default_config(&device, mode)?;
let mut config: StreamConfig = config.into();
config.channels = if supports_stereo { 2 } else { 1 };
config.sample_rate = if supports_sample_rate {
SampleRate(sample_rate as u32)
} else {
config.sample_rate
};
config.buffer_size = if supports_buffer_size {
BufferSize::Fixed(buffer_size as u32)
} else {
config.buffer_size
};
#[cfg(target_os = "ios")]
{
config.buffer_size = BufferSize::Default;
}
Ok((device, config))
}
pub fn configure_input_device<Host: HostTrait>(
host: &Host,
options: &StandaloneOptions,
buffer_size: usize,
sample_rate: usize,
) -> Result<(Host::Device, StreamConfig), ConfigureDeviceError> {
log::debug!("Negotiating input configuration");
let (input_device, input_config) =
configure_device(host, options, AudioIOMode::Input, buffer_size, sample_rate)?;
log::info!(
"Using input name={} sample_rate={} buffer_size={:?}",
input_device.name()?,
sample_rate,
input_config.buffer_size
);
Ok((input_device, input_config))
}
pub fn configure_output_device<Host: HostTrait>(
host: Host,
options: &StandaloneOptions,
buffer_size: usize,
sample_rate: usize,
) -> Result<(Host::Device, StreamConfig), ConfigureDeviceError> {
log::debug!("Negotiating output configuration");
let (output_device, output_config) = configure_device(
&host,
options,
AudioIOMode::Output,
buffer_size,
sample_rate,
)?;
log::info!(
"Using output name={} sample_rate={} buffer_size={:?}",
output_device.name()?,
sample_rate,
output_config.buffer_size
);
Ok((output_device, output_config))
}
#[cfg(test)]
mod test {
use crate::standalone_cpal::mock_cpal::vec_iterator::VecIterator;
use crate::standalone_cpal::mock_cpal::*;
use super::*;
#[test]
fn test_none_device_name() {
let options = StandaloneOptions::default();
assert!(device_name(&options, AudioIOMode::Input).is_none());
assert!(device_name(&options, AudioIOMode::Output).is_none());
}
#[test]
fn test_device_name_for_input() {
let options = StandaloneOptions {
input_device: Some("input-name".to_string()),
..StandaloneOptions::default()
};
let name = device_name(&options, AudioIOMode::Input);
assert_eq!(name.unwrap(), "input-name");
}
#[test]
fn test_device_name_for_output() {
let options = StandaloneOptions {
output_device: Some("output-name".to_string()),
..StandaloneOptions::default()
};
let name = device_name(&options, AudioIOMode::Output);
assert_eq!(name.unwrap(), "output-name");
}
#[test]
fn test_list_devices_calls_host_input_devices() {
let mut host = MockHost::default();
host.expect_input_devices().returning(|| {
let mock_devices = vec![MockDevice::default()];
Ok(VecIterator::from(mock_devices).filter(|_| true))
});
let result = list_devices(&host, AudioIOMode::Input);
assert!(result.is_ok());
}
#[test]
fn test_list_devices_calls_host_output_devices() {
let mut host = MockHost::default();
host.expect_output_devices().returning(|| {
let mock_devices = vec![MockDevice::default()];
Ok(VecIterator::from(mock_devices).filter(|_| true))
});
let result = list_devices(&host, AudioIOMode::Output);
assert!(result.is_ok());
}
#[test]
fn supported_configs_works_for_input_devices() {
let mut device = MockDevice::default();
device
.expect_supported_input_configs()
.returning(|| Ok(VecIterator::from(vec![])));
let result = supported_configs(&device, AudioIOMode::Input);
assert!(result.is_ok());
}
#[test]
fn supported_configs_works_for_output_devices() {
let mut device = MockDevice::default();
device
.expect_supported_output_configs()
.returning(|| Ok(VecIterator::from(vec![])));
let result = supported_configs(&device, AudioIOMode::Output);
assert!(result.is_ok());
}
}