audio_processor_standalone/standalone_cpal/
options.rs1use cpal::{
29    traits::{DeviceTrait, HostTrait},
30    BufferSize, DefaultStreamConfigError, DeviceNameError, DevicesError, SampleRate, StreamConfig,
31    SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigsError,
32};
33
34use crate::standalone_processor::StandaloneOptions;
35
36#[derive(Clone, Copy)]
37pub enum AudioIOMode {
38    Input,
39    Output,
40}
41
42fn list_devices<Host: HostTrait>(
43    host: &Host,
44    mode: AudioIOMode,
45) -> Result<impl Iterator<Item = Host::Device>, DevicesError> {
46    match mode {
47        AudioIOMode::Input => host.input_devices(),
48        AudioIOMode::Output => host.output_devices(),
49    }
50}
51
52fn supported_configs(
53    device: &impl DeviceTrait,
54    mode: AudioIOMode,
55) -> Result<Vec<cpal::SupportedStreamConfigRange>, cpal::SupportedStreamConfigsError> {
56    match mode {
57        AudioIOMode::Input => device.supported_input_configs().map(|i| i.collect()),
58        AudioIOMode::Output => device.supported_output_configs().map(|i| i.collect()),
59    }
60}
61
62fn device_name(options: &StandaloneOptions, mode: AudioIOMode) -> Option<&String> {
63    match mode {
64        AudioIOMode::Input => options.input_device.as_ref(),
65        AudioIOMode::Output => options.output_device.as_ref(),
66    }
67}
68
69fn default_device<Host: HostTrait>(host: &Host, mode: AudioIOMode) -> Option<Host::Device> {
70    match mode {
71        AudioIOMode::Input => host.default_input_device(),
72        AudioIOMode::Output => host.default_output_device(),
73    }
74}
75
76fn default_config(
77    device: &impl DeviceTrait,
78    mode: AudioIOMode,
79) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
80    match mode {
81        AudioIOMode::Input => device.default_input_config(),
82        AudioIOMode::Output => device.default_output_config(),
83    }
84}
85
86#[derive(Debug, thiserror::Error)]
87pub enum ConfigureDeviceError {
88    #[error("Missing device: {0:?}")]
89    MissingDevice(Option<String>),
90    #[error("Supported streams listing error: {0:?}")]
91    SupportedStreamConfig(#[from] SupportedStreamConfigsError),
92    #[error("Default stream config error: {0:?}")]
93    DefaultStreamConfig(#[from] DefaultStreamConfigError),
94    #[error("Name error: {0:?}")]
95    NameError(#[from] DeviceNameError),
96}
97
98fn configure_device<Host: HostTrait>(
99    host: &Host,
100    options: &StandaloneOptions,
101    mode: AudioIOMode,
102    buffer_size: usize,
103    sample_rate: usize,
104) -> Result<(Host::Device, StreamConfig), ConfigureDeviceError> {
105    let device_name = device_name(options, mode);
106    log::debug!(
107        "Device name option provided = device_name={:?}",
108        device_name
109    );
110    let device = device_name
111        .and_then(|device_name| {
112            let mut devices = list_devices(host, mode).map(Some).unwrap_or(None)?;
113            devices.find(|device| matches!(device.name(), Ok(name) if &name == device_name))
114        })
115        .map(Ok)
116        .unwrap_or_else(|| {
117            log::debug!("No name provided, using default device");
118            default_device(host, mode)
119                .map(Ok)
120                .unwrap_or_else(|| Err(ConfigureDeviceError::MissingDevice(device_name.cloned())))
121        })?;
122
123    log::debug!("Found device device_name={:?}", device.name()?);
124
125    log::debug!("Listing supported configs");
126    let supported_configs = supported_configs(&device, mode)?;
127    let mut supports_stereo = false;
128    let mut supports_buffer_size = false;
129    let mut supports_sample_rate = false;
130    for config in supported_configs {
131        log::debug!("  Supported config: {:?}", config);
132        if config.channels() > 1 {
133            supports_stereo = true;
134        }
135        if let SupportedBufferSize::Range { min, max } = config.buffer_size() {
136            let buffer_size = buffer_size as u32;
137            if buffer_size >= *min && buffer_size <= *max {
138                supports_buffer_size = true;
139            }
140        }
141        let sample_rate = sample_rate as u32;
142        if config.min_sample_rate().0 >= sample_rate && config.max_sample_rate().0 <= sample_rate {
143            supports_sample_rate = true;
144        }
145    }
146
147    log::debug!("Negotiating default configuration");
148    let config = default_config(&device, mode)?;
149    let mut config: StreamConfig = config.into();
150    config.channels = if supports_stereo { 2 } else { 1 };
151    config.sample_rate = if supports_sample_rate {
152        SampleRate(sample_rate as u32)
153    } else {
154        config.sample_rate
155    };
156    config.buffer_size = if supports_buffer_size {
157        BufferSize::Fixed(buffer_size as u32)
158    } else {
159        config.buffer_size
160    };
161
162    #[cfg(target_os = "ios")]
163    {
164        config.buffer_size = BufferSize::Default;
165    }
166
167    Ok((device, config))
168}
169
170pub fn configure_input_device<Host: HostTrait>(
171    host: &Host,
172    options: &StandaloneOptions,
173    buffer_size: usize,
174    sample_rate: usize,
175) -> Result<(Host::Device, StreamConfig), ConfigureDeviceError> {
176    log::debug!("Negotiating input configuration");
177    let (input_device, input_config) =
178        configure_device(host, options, AudioIOMode::Input, buffer_size, sample_rate)?;
179    log::info!(
180        "Using input name={} sample_rate={} buffer_size={:?}",
181        input_device.name()?,
182        sample_rate,
183        input_config.buffer_size
184    );
185    Ok((input_device, input_config))
186}
187
188pub fn configure_output_device<Host: HostTrait>(
189    host: Host,
190    options: &StandaloneOptions,
191    buffer_size: usize,
192    sample_rate: usize,
193) -> Result<(Host::Device, StreamConfig), ConfigureDeviceError> {
194    log::debug!("Negotiating output configuration");
195    let (output_device, output_config) = configure_device(
196        &host,
197        options,
198        AudioIOMode::Output,
199        buffer_size,
200        sample_rate,
201    )?;
202    log::info!(
203        "Using output name={} sample_rate={} buffer_size={:?}",
204        output_device.name()?,
205        sample_rate,
206        output_config.buffer_size
207    );
208    Ok((output_device, output_config))
209}
210
211#[cfg(test)]
212mod test {
213    use crate::standalone_cpal::mock_cpal::vec_iterator::VecIterator;
214    use crate::standalone_cpal::mock_cpal::*;
215
216    use super::*;
217
218    #[test]
219    fn test_none_device_name() {
220        let options = StandaloneOptions::default();
221        assert!(device_name(&options, AudioIOMode::Input).is_none());
222        assert!(device_name(&options, AudioIOMode::Output).is_none());
223    }
224
225    #[test]
226    fn test_device_name_for_input() {
227        let options = StandaloneOptions {
228            input_device: Some("input-name".to_string()),
229            ..StandaloneOptions::default()
230        };
231        let name = device_name(&options, AudioIOMode::Input);
232        assert_eq!(name.unwrap(), "input-name");
233    }
234
235    #[test]
236    fn test_device_name_for_output() {
237        let options = StandaloneOptions {
238            output_device: Some("output-name".to_string()),
239            ..StandaloneOptions::default()
240        };
241        let name = device_name(&options, AudioIOMode::Output);
242        assert_eq!(name.unwrap(), "output-name");
243    }
244
245    #[test]
246    fn test_list_devices_calls_host_input_devices() {
247        let mut host = MockHost::default();
248        host.expect_input_devices().returning(|| {
249            let mock_devices = vec![MockDevice::default()];
250
251            Ok(VecIterator::from(mock_devices).filter(|_| true))
252        });
253        let result = list_devices(&host, AudioIOMode::Input);
254        assert!(result.is_ok());
255    }
256
257    #[test]
258    fn test_list_devices_calls_host_output_devices() {
259        let mut host = MockHost::default();
260        host.expect_output_devices().returning(|| {
261            let mock_devices = vec![MockDevice::default()];
262
263            Ok(VecIterator::from(mock_devices).filter(|_| true))
264        });
265        let result = list_devices(&host, AudioIOMode::Output);
266        assert!(result.is_ok());
267    }
268
269    #[test]
270    fn supported_configs_works_for_input_devices() {
271        let mut device = MockDevice::default();
272        device
273            .expect_supported_input_configs()
274            .returning(|| Ok(VecIterator::from(vec![])));
275
276        let result = supported_configs(&device, AudioIOMode::Input);
277        assert!(result.is_ok());
278    }
279
280    #[test]
281    fn supported_configs_works_for_output_devices() {
282        let mut device = MockDevice::default();
283        device
284            .expect_supported_output_configs()
285            .returning(|| Ok(VecIterator::from(vec![])));
286
287        let result = supported_configs(&device, AudioIOMode::Output);
288        assert!(result.is_ok());
289    }
290}