diff --git a/examples/beep.rs b/examples/beep.rs index 9df4f2b..60ec8aa 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -8,23 +8,23 @@ fn main() -> Result<(), anyhow::Error> { let device = host .default_output_device() .expect("failed to find a default output device"); - let format = device.default_output_format()?; + let config = device.default_output_config()?; - match format.data_type { - cpal::SampleFormat::F32 => run::(&device, &format.shape())?, - cpal::SampleFormat::I16 => run::(&device, &format.shape())?, - cpal::SampleFormat::U16 => run::(&device, &format.shape())?, + match config.sample_format() { + cpal::SampleFormat::F32 => run::(&device, &config.into())?, + cpal::SampleFormat::I16 => run::(&device, &config.into())?, + cpal::SampleFormat::U16 => run::(&device, &config.into())?, } Ok(()) } -fn run(device: &cpal::Device, shape: &cpal::Shape) -> Result<(), anyhow::Error> +fn run(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error> where T: cpal::Sample, { - let sample_rate = shape.sample_rate.0 as f32; - let channels = shape.channels as usize; + let sample_rate = config.sample_rate.0 as f32; + let channels = config.channels as usize; // Produce a sinusoid of maximum amplitude. let mut sample_clock = 0f32; @@ -36,7 +36,7 @@ where let err_fn = |err| eprintln!("an error occurred on stream: {}", err); let stream = device.build_output_stream( - shape, + config, move |data: &mut [T]| write_data(data, channels, &mut next_value), err_fn, )?; diff --git a/examples/enumerate.rs b/examples/enumerate.rs index 56542eb..d599790 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -21,48 +21,48 @@ fn main() -> Result<(), anyhow::Error> { for (device_index, device) in devices.enumerate() { println!(" {}. \"{}\"", device_index + 1, device.name()?); - // Input formats - if let Ok(fmt) = device.default_input_format() { - println!(" Default input stream format:\n {:?}", fmt); + // Input configs + if let Ok(conf) = device.default_input_config() { + println!(" Default input stream config:\n {:?}", conf); } - let mut input_formats = match device.supported_input_formats() { + let mut input_configs = match device.supported_input_configs() { Ok(f) => f.peekable(), Err(e) => { println!("Error: {:?}", e); continue; } }; - if input_formats.peek().is_some() { - println!(" All supported input stream formats:"); - for (format_index, format) in input_formats.enumerate() { + if input_configs.peek().is_some() { + println!(" All supported input stream configs:"); + for (config_index, config) in input_configs.enumerate() { println!( " {}.{}. {:?}", device_index + 1, - format_index + 1, - format + config_index + 1, + config ); } } - // Output formats - if let Ok(fmt) = device.default_output_format() { - println!(" Default output stream format:\n {:?}", fmt); + // Output configs + if let Ok(conf) = device.default_output_config() { + println!(" Default output stream config:\n {:?}", conf); } - let mut output_formats = match device.supported_output_formats() { + let mut output_configs = match device.supported_output_configs() { Ok(f) => f.peekable(), Err(e) => { println!("Error: {:?}", e); continue; } }; - if output_formats.peek().is_some() { - println!(" All supported output stream formats:"); - for (format_index, format) in output_formats.enumerate() { + if output_configs.peek().is_some() { + println!(" All supported output stream configs:"); + for (config_index, config) in output_configs.enumerate() { println!( " {}.{}. {:?}", device_index + 1, - format_index + 1, - format + config_index + 1, + config ); } } diff --git a/examples/feedback.rs b/examples/feedback.rs index 06d3970..2601136 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -1,7 +1,7 @@ //! Feeds back the input stream directly into the output stream. //! -//! Assumes that the input and output devices can use the same stream format and that they support -//! the f32 sample format. +//! Assumes that the input and output devices can use the same stream configuration and that they +//! support the f32 sample format. //! //! Uses a delay of `LATENCY_MS` milliseconds in case the default input and output streams are not //! precisely synchronised. @@ -28,12 +28,12 @@ fn main() -> Result<(), anyhow::Error> { println!("Using default input device: \"{}\"", input_device.name()?); println!("Using default output device: \"{}\"", output_device.name()?); - // We'll try and use the same format between streams to keep it simple - let shape = input_device.default_input_format()?.shape(); + // We'll try and use the same configuration between streams to keep it simple. + let config: cpal::StreamConfig = input_device.default_input_config()?.into(); // Create a delay in case the input and output devices aren't synced. - let latency_frames = (LATENCY_MS / 1_000.0) * shape.sample_rate.0 as f32; - let latency_samples = latency_frames as usize * shape.channels as usize; + let latency_frames = (LATENCY_MS / 1_000.0) * config.sample_rate.0 as f32; + let latency_samples = latency_frames as usize * config.channels as usize; // The buffer to share samples let ring = RingBuffer::new(latency_samples * 2); @@ -80,10 +80,10 @@ fn main() -> Result<(), anyhow::Error> { // Build streams. println!( "Attempting to build both streams with f32 samples and `{:?}`.", - shape + config ); - let input_stream = input_device.build_input_stream(&shape, input_data_fn, err_fn)?; - let output_stream = output_device.build_output_stream(&shape, output_data_fn, err_fn)?; + let input_stream = input_device.build_input_stream(&config, input_data_fn, err_fn)?; + let output_stream = output_device.build_output_stream(&config, output_data_fn, err_fn)?; println!("Successfully built streams."); // Play the streams. diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 96a8b93..223a153 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -1,4 +1,4 @@ -//! Records a WAV file (roughly 3 seconds long) using the default input device and format. +//! Records a WAV file (roughly 3 seconds long) using the default input device and config. //! //! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav". @@ -15,18 +15,18 @@ fn main() -> Result<(), anyhow::Error> { // Use the default host for working with audio devices. let host = cpal::default_host(); - // Setup the default input device and stream with the default input format. + // Setup the default input device and stream with the default input config. let device = host .default_input_device() .expect("Failed to get default input device"); println!("Default input device: {}", device.name()?); - let format = device - .default_input_format() - .expect("Failed to get default input format"); - println!("Default input format: {:?}", format); + let config = device + .default_input_config() + .expect("Failed to get default input config"); + println!("Default input config: {:?}", config); // The WAV file we're recording to. const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav"); - let spec = wav_spec_from_format(&format); + let spec = wav_spec_from_config(&config); let writer = hound::WavWriter::create(PATH, spec)?; let writer = Arc::new(Mutex::new(Some(writer))); @@ -40,19 +40,19 @@ fn main() -> Result<(), anyhow::Error> { eprintln!("an error occurred on stream: {}", err); }; - let stream = match format.data_type { + let stream = match config.sample_format() { cpal::SampleFormat::F32 => device.build_input_stream( - &format.shape(), + &config.into(), move |data| write_input_data::(data, &writer_2), err_fn, )?, cpal::SampleFormat::I16 => device.build_input_stream( - &format.shape(), + &config.into(), move |data| write_input_data::(data, &writer_2), err_fn, )?, cpal::SampleFormat::U16 => device.build_input_stream( - &format.shape(), + &config.into(), move |data| write_input_data::(data, &writer_2), err_fn, )?, @@ -76,12 +76,12 @@ fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat { } } -fn wav_spec_from_format(format: &cpal::Format) -> hound::WavSpec { +fn wav_spec_from_config(config: &cpal::SupportedStreamConfig) -> hound::WavSpec { hound::WavSpec { - channels: format.channels as _, - sample_rate: format.sample_rate.0 as _, - bits_per_sample: (format.data_type.sample_size() * 8) as _, - sample_format: sample_format(format.data_type), + channels: config.channels() as _, + sample_rate: config.sample_rate().0 as _, + bits_per_sample: (config.sample_format().sample_size() * 8) as _, + sample_format: sample_format(config.sample_format()), } } diff --git a/src/error.rs b/src/error.rs index 0a87bbe..f404f29 100644 --- a/src/error.rs +++ b/src/error.rs @@ -47,7 +47,7 @@ pub enum DeviceNameError { /// Error that can happen when enumerating the list of supported formats. #[derive(Debug, Error)] -pub enum SupportedFormatsError { +pub enum SupportedStreamConfigsError { /// The device no longer exists. This can happen if the device is disconnected while the /// program is running. #[error("The requested device is no longer available. For example, it has been unplugged.")] @@ -67,7 +67,7 @@ pub enum SupportedFormatsError { /// May occur when attempting to request the default input or output stream format from a `Device`. #[derive(Debug, Error)] -pub enum DefaultFormatError { +pub enum DefaultStreamConfigError { /// The device no longer exists. This can happen if the device is disconnected while the /// program is running. #[error("The requested device is no longer available. For example, it has been unplugged.")] @@ -90,9 +90,9 @@ pub enum BuildStreamError { /// program is running. #[error("The requested device is no longer available. For example, it has been unplugged.")] DeviceNotAvailable, - /// The required format is not supported. - #[error("The requested stream format is not supported by the device.")] - FormatNotSupported, + /// The specified stream configuration is not supported. + #[error("The requested stream configuration is not supported by the device.")] + StreamConfigNotSupported, /// We called something the C-Layer did not understand /// /// On ALSA device functions called with a feature they do not support will yield this. E.g. diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index d556359..42f5660 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -2,9 +2,10 @@ extern crate alsa_sys as alsa; extern crate libc; use crate::{ - BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultFormatError, - DeviceNameError, DevicesError, Format, PauseStreamError, PlayStreamError, SampleFormat, - SampleRate, StreamError, SupportedFormat, SupportedFormatsError, + BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, + DeviceNameError, DevicesError, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, + StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use std::sync::Arc; use std::thread::{self, JoinHandle}; @@ -14,8 +15,8 @@ use traits::{DeviceTrait, HostTrait, StreamTrait}; pub use self::enumerate::{default_input_device, default_output_device, Devices}; -pub type SupportedInputFormats = VecIntoIter; -pub type SupportedOutputFormats = VecIntoIter; +pub type SupportedInputConfigs = VecIntoIter; +pub type SupportedOutputConfigs = VecIntoIter; mod enumerate; @@ -52,37 +53,38 @@ impl HostTrait for Host { } impl DeviceTrait for Device { - type SupportedInputFormats = SupportedInputFormats; - type SupportedOutputFormats = SupportedOutputFormats; + type SupportedInputConfigs = SupportedInputConfigs; + type SupportedOutputConfigs = SupportedOutputConfigs; type Stream = Stream; fn name(&self) -> Result { Device::name(self) } - fn supported_input_formats( + fn supported_input_configs( &self, - ) -> Result { - Device::supported_input_formats(self) + ) -> Result { + Device::supported_input_configs(self) } - fn supported_output_formats( + fn supported_output_configs( &self, - ) -> Result { - Device::supported_output_formats(self) + ) -> Result { + Device::supported_output_configs(self) } - fn default_input_format(&self) -> Result { - Device::default_input_format(self) + fn default_input_config(&self) -> Result { + Device::default_input_config(self) } - fn default_output_format(&self) -> Result { - Device::default_output_format(self) + fn default_output_config(&self) -> Result { + Device::default_output_config(self) } fn build_input_stream_raw( &self, - format: &Format, + conf: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -90,14 +92,16 @@ impl DeviceTrait for Device { D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let stream_inner = self.build_stream_inner(format, alsa::SND_PCM_STREAM_CAPTURE)?; + let stream_inner = + self.build_stream_inner(conf, sample_format, alsa::SND_PCM_STREAM_CAPTURE)?; let stream = Stream::new_input(Arc::new(stream_inner), data_callback, error_callback); Ok(stream) } fn build_output_stream_raw( &self, - format: &Format, + conf: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -105,7 +109,8 @@ impl DeviceTrait for Device { D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let stream_inner = self.build_stream_inner(format, alsa::SND_PCM_STREAM_PLAYBACK)?; + let stream_inner = + self.build_stream_inner(conf, sample_format, alsa::SND_PCM_STREAM_PLAYBACK)?; let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback); Ok(stream) } @@ -161,7 +166,8 @@ pub struct Device(String); impl Device { fn build_stream_inner( &self, - format: &Format, + conf: &StreamConfig, + sample_format: SampleFormat, stream_type: alsa::snd_pcm_stream_t, ) -> Result { let name = ffi::CString::new(self.0.clone()).expect("unable to clone device"); @@ -185,13 +191,13 @@ impl Device { }; let can_pause = unsafe { let hw_params = HwParams::alloc(); - set_hw_params_from_format(handle, &hw_params, format) + set_hw_params_from_format(handle, &hw_params, conf, sample_format) .map_err(|description| BackendSpecificError { description })?; alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1 }; let (buffer_len, period_len) = unsafe { - set_sw_params_from_format(handle, format) + set_sw_params_from_format(handle, conf) .map_err(|description| BackendSpecificError { description })? }; @@ -213,9 +219,9 @@ impl Device { let stream_inner = StreamInner { channel: handle, - sample_format: format.data_type, + sample_format, num_descriptors, - num_channels: format.channels as u16, + num_channels: conf.channels as u16, buffer_len, period_len, can_pause, @@ -235,10 +241,10 @@ impl Device { Ok(self.0.clone()) } - unsafe fn supported_formats( + unsafe fn supported_configs( &self, stream_t: alsa::snd_pcm_stream_t, - ) -> Result, SupportedFormatsError> { + ) -> Result, SupportedStreamConfigsError> { let mut handle = ptr::null_mut(); let device_name = match ffi::CString::new(&self.0[..]) { Ok(name) => name, @@ -256,8 +262,8 @@ impl Device { alsa::SND_PCM_NONBLOCK, ) { -2 | - -16 /* determined empirically */ => return Err(SupportedFormatsError::DeviceNotAvailable), - -22 => return Err(SupportedFormatsError::InvalidArgument), + -16 /* determined empirically */ => return Err(SupportedStreamConfigsError::DeviceNotAvailable), + -22 => return Err(SupportedStreamConfigsError::InvalidArgument), e => if let Err(description) = check_errors(e) { let err = BackendSpecificError { description }; return Err(err.into()) @@ -402,14 +408,14 @@ impl Device { let mut output = Vec::with_capacity( supported_formats.len() * supported_channels.len() * sample_rates.len(), ); - for &data_type in supported_formats.iter() { + for &sample_format in supported_formats.iter() { for channels in supported_channels.iter() { for &(min_rate, max_rate) in sample_rates.iter() { - output.push(SupportedFormat { + output.push(SupportedStreamConfigRange { channels: channels.clone(), min_sample_rate: SampleRate(min_rate as u32), max_sample_rate: SampleRate(max_rate as u32), - data_type: data_type, + sample_format: sample_format, }); } } @@ -420,31 +426,35 @@ impl Device { Ok(output.into_iter()) } - fn supported_input_formats(&self) -> Result { - unsafe { self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE) } + fn supported_input_configs( + &self, + ) -> Result { + unsafe { self.supported_configs(alsa::SND_PCM_STREAM_CAPTURE) } } - fn supported_output_formats(&self) -> Result { - unsafe { self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK) } + fn supported_output_configs( + &self, + ) -> Result { + unsafe { self.supported_configs(alsa::SND_PCM_STREAM_PLAYBACK) } } // ALSA does not offer default stream formats, so instead we compare all supported formats by - // the `SupportedFormat::cmp_default_heuristics` order and select the greatest. - fn default_format( + // the `SupportedStreamConfigRange::cmp_default_heuristics` order and select the greatest. + fn default_config( &self, stream_t: alsa::snd_pcm_stream_t, - ) -> Result { + ) -> Result { let mut formats: Vec<_> = unsafe { - match self.supported_formats(stream_t) { - Err(SupportedFormatsError::DeviceNotAvailable) => { - return Err(DefaultFormatError::DeviceNotAvailable); + match self.supported_configs(stream_t) { + Err(SupportedStreamConfigsError::DeviceNotAvailable) => { + return Err(DefaultStreamConfigError::DeviceNotAvailable); } - Err(SupportedFormatsError::InvalidArgument) => { + Err(SupportedStreamConfigsError::InvalidArgument) => { // this happens sometimes when querying for input and output capabilities but // the device supports only one - return Err(DefaultFormatError::StreamTypeNotSupported); + return Err(DefaultStreamConfigError::StreamTypeNotSupported); } - Err(SupportedFormatsError::BackendSpecific { err }) => { + Err(SupportedStreamConfigsError::BackendSpecific { err }) => { return Err(err.into()); } Ok(fmts) => fmts.collect(), @@ -464,16 +474,16 @@ impl Device { } Ok(format) } - None => Err(DefaultFormatError::StreamTypeNotSupported), + None => Err(DefaultStreamConfigError::StreamTypeNotSupported), } } - fn default_input_format(&self) -> Result { - self.default_format(alsa::SND_PCM_STREAM_CAPTURE) + fn default_input_config(&self) -> Result { + self.default_config(alsa::SND_PCM_STREAM_CAPTURE) } - fn default_output_format(&self) -> Result { - self.default_format(alsa::SND_PCM_STREAM_PLAYBACK) + fn default_output_config(&self) -> Result { + self.default_config(alsa::SND_PCM_STREAM_PLAYBACK) } } @@ -900,7 +910,8 @@ fn get_available_samples(stream: &StreamInner) -> Result Result<(), String> { if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) { return Err(format!("errors on pcm handle: {}", e)); @@ -913,14 +924,14 @@ unsafe fn set_hw_params_from_format( return Err(format!("handle not acessible: {}", e)); } - let data_type = if cfg!(target_endian = "big") { - match format.data_type { + let sample_format = if cfg!(target_endian = "big") { + match sample_format { SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_BE, SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_BE, SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_BE, } } else { - match format.data_type { + match sample_format { SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_LE, SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_LE, SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_LE, @@ -930,14 +941,14 @@ unsafe fn set_hw_params_from_format( if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format( pcm_handle, hw_params.0, - data_type, + sample_format, )) { return Err(format!("format could not be set: {}", e)); } if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate( pcm_handle, hw_params.0, - format.sample_rate.0 as libc::c_uint, + config.sample_rate.0 as libc::c_uint, 0, )) { return Err(format!("sample rate could not be set: {}", e)); @@ -945,7 +956,7 @@ unsafe fn set_hw_params_from_format( if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels( pcm_handle, hw_params.0, - format.channels as libc::c_uint, + config.channels as libc::c_uint, )) { return Err(format!("channel count could not be set: {}", e)); } @@ -969,7 +980,7 @@ unsafe fn set_hw_params_from_format( unsafe fn set_sw_params_from_format( pcm_handle: *mut alsa::snd_pcm_t, - format: &Format, + config: &StreamConfig, ) -> Result<(usize, usize), String> { let mut sw_params = ptr::null_mut(); // TODO: RAII if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) { @@ -1005,8 +1016,8 @@ unsafe fn set_sw_params_from_format( )) { return Err(format!("snd_pcm_sw_params_set_avail_min failed: {}", e)); } - let buffer = buffer as usize * format.channels as usize; - let period = period as usize * format.channels as usize; + let buffer = buffer as usize * config.channels as usize; + let period = period as usize * config.channels as usize; (buffer, period) }; diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index b0b6532..dee4df3 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -1,20 +1,20 @@ use std; -pub type SupportedInputFormats = std::vec::IntoIter; -pub type SupportedOutputFormats = std::vec::IntoIter; +pub type SupportedInputConfigs = std::vec::IntoIter; +pub type SupportedOutputConfigs = std::vec::IntoIter; use super::parking_lot::Mutex; use super::sys; use std::hash::{Hash, Hasher}; use std::sync::Arc; use BackendSpecificError; -use DefaultFormatError; +use DefaultStreamConfigError; use DeviceNameError; use DevicesError; -use Format; use SampleFormat; use SampleRate; -use SupportedFormat; -use SupportedFormatsError; +use SupportedStreamConfig; +use SupportedStreamConfigRange; +use SupportedStreamConfigsError; /// A ASIO Device pub struct Device { @@ -52,52 +52,21 @@ impl Device { Ok(self.driver.name().to_string()) } - /// Gets the supported input formats. + /// Gets the supported input configs. /// TODO currently only supports the default. - /// Need to find all possible formats. - pub fn supported_input_formats(&self) -> Result { - // Retrieve the default format for the total supported channels and supported sample - // format. - let mut f = match self.default_input_format() { - Err(_) => return Err(SupportedFormatsError::DeviceNotAvailable), - Ok(f) => f, - }; - - // Collect a format for every combination of supported sample rate and number of channels. - let mut supported_formats = vec![]; - for &rate in ::COMMON_SAMPLE_RATES { - if !self - .driver - .can_sample_rate(rate.0.into()) - .ok() - .unwrap_or(false) - { - continue; - } - for channels in 1..f.channels + 1 { - f.channels = channels; - f.sample_rate = rate; - supported_formats.push(SupportedFormat::from(f.clone())); - } - } - Ok(supported_formats.into_iter()) - } - - /// Gets the supported output formats. - /// TODO currently only supports the default. - /// Need to find all possible formats. - pub fn supported_output_formats( + /// Need to find all possible configs. + pub fn supported_input_configs( &self, - ) -> Result { - // Retrieve the default format for the total supported channels and supported sample + ) -> Result { + // Retrieve the default config for the total supported channels and supported sample // format. - let mut f = match self.default_output_format() { - Err(_) => return Err(SupportedFormatsError::DeviceNotAvailable), + let mut f = match self.default_input_config() { + Err(_) => return Err(SupportedStreamConfigsError::DeviceNotAvailable), Ok(f) => f, }; - // Collect a format for every combination of supported sample rate and number of channels. - let mut supported_formats = vec![]; + // Collect a config for every combination of supported sample rate and number of channels. + let mut supported_configs = vec![]; for &rate in ::COMMON_SAMPLE_RATES { if !self .driver @@ -110,38 +79,71 @@ impl Device { for channels in 1..f.channels + 1 { f.channels = channels; f.sample_rate = rate; - supported_formats.push(SupportedFormat::from(f.clone())); + supported_configs.push(SupportedStreamConfigRange::from(f.clone())); } } - Ok(supported_formats.into_iter()) + Ok(supported_configs.into_iter()) } - /// Returns the default input format - pub fn default_input_format(&self) -> Result { - let channels = self.driver.channels().map_err(default_format_err)?.ins as u16; - let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_format_err)? as _); + /// Gets the supported output configs. + /// TODO currently only supports the default. + /// Need to find all possible configs. + pub fn supported_output_configs( + &self, + ) -> Result { + // Retrieve the default config for the total supported channels and supported sample + // format. + let mut f = match self.default_output_config() { + Err(_) => return Err(SupportedStreamConfigsError::DeviceNotAvailable), + Ok(f) => f, + }; + + // Collect a config for every combination of supported sample rate and number of channels. + let mut supported_configs = vec![]; + for &rate in ::COMMON_SAMPLE_RATES { + if !self + .driver + .can_sample_rate(rate.0.into()) + .ok() + .unwrap_or(false) + { + continue; + } + for channels in 1..f.channels + 1 { + f.channels = channels; + f.sample_rate = rate; + supported_configs.push(SupportedStreamConfigRange::from(f.clone())); + } + } + Ok(supported_configs.into_iter()) + } + + /// Returns the default input config + pub fn default_input_config(&self) -> Result { + let channels = self.driver.channels().map_err(default_config_err)?.ins as u16; + let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_config_err)? as _); // Map th ASIO sample type to a CPAL sample type - let data_type = self.driver.input_data_type().map_err(default_format_err)?; - let data_type = - convert_data_type(&data_type).ok_or(DefaultFormatError::StreamTypeNotSupported)?; - Ok(Format { + let data_type = self.driver.input_data_type().map_err(default_config_err)?; + let sample_format = convert_data_type(&data_type) + .ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?; + Ok(SupportedStreamConfig { channels, sample_rate, - data_type, + sample_format, }) } - /// Returns the default output format - pub fn default_output_format(&self) -> Result { - let channels = self.driver.channels().map_err(default_format_err)?.outs as u16; - let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_format_err)? as _); - let data_type = self.driver.output_data_type().map_err(default_format_err)?; - let data_type = - convert_data_type(&data_type).ok_or(DefaultFormatError::StreamTypeNotSupported)?; - Ok(Format { + /// Returns the default output config + pub fn default_output_config(&self) -> Result { + let channels = self.driver.channels().map_err(default_config_err)?.outs as u16; + let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_config_err)? as _); + let data_type = self.driver.output_data_type().map_err(default_config_err)?; + let sample_format = convert_data_type(&data_type) + .ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?; + Ok(SupportedStreamConfig { channels, sample_rate, - data_type, + sample_format, }) } } @@ -202,12 +204,12 @@ pub(crate) fn convert_data_type(ty: &sys::AsioSampleType) -> Option DefaultFormatError { +fn default_config_err(e: sys::AsioError) -> DefaultStreamConfigError { match e { sys::AsioError::NoDrivers | sys::AsioError::HardwareMalfunction => { - DefaultFormatError::DeviceNotAvailable + DefaultStreamConfigError::DeviceNotAvailable } - sys::AsioError::NoRate => DefaultFormatError::StreamTypeNotSupported, + sys::AsioError::NoRate => DefaultStreamConfigError::StreamTypeNotSupported, err => { let description = format!("{}", err); BackendSpecificError { description }.into() diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 24713a0..a885db6 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -2,12 +2,13 @@ extern crate asio_sys as sys; extern crate parking_lot; use crate::{ - BuildStreamError, Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - PauseStreamError, PlayStreamError, StreamError, SupportedFormatsError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, + PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, + SupportedStreamConfig, SupportedStreamConfigsError, }; use traits::{DeviceTrait, HostTrait, StreamTrait}; -pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats}; +pub use self::device::{Device, Devices, SupportedInputConfigs, SupportedOutputConfigs}; pub use self::stream::Stream; use std::sync::Arc; @@ -53,37 +54,38 @@ impl HostTrait for Host { } impl DeviceTrait for Device { - type SupportedInputFormats = SupportedInputFormats; - type SupportedOutputFormats = SupportedOutputFormats; + type SupportedInputConfigs = SupportedInputConfigs; + type SupportedOutputConfigs = SupportedOutputConfigs; type Stream = Stream; fn name(&self) -> Result { Device::name(self) } - fn supported_input_formats( + fn supported_input_configs( &self, - ) -> Result { - Device::supported_input_formats(self) + ) -> Result { + Device::supported_input_configs(self) } - fn supported_output_formats( + fn supported_output_configs( &self, - ) -> Result { - Device::supported_output_formats(self) + ) -> Result { + Device::supported_output_configs(self) } - fn default_input_format(&self) -> Result { - Device::default_input_format(self) + fn default_input_config(&self) -> Result { + Device::default_input_config(self) } - fn default_output_format(&self) -> Result { - Device::default_output_format(self) + fn default_output_config(&self) -> Result { + Device::default_output_config(self) } fn build_input_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -91,12 +93,13 @@ impl DeviceTrait for Device { D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Device::build_input_stream_raw(self, format, data_callback, error_callback) + Device::build_input_stream_raw(self, config, sample_format, data_callback, error_callback) } fn build_output_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -104,7 +107,7 @@ impl DeviceTrait for Device { D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Device::build_output_stream_raw(self, format, data_callback, error_callback) + Device::build_output_stream_raw(self, config, sample_format, data_callback, error_callback) } } diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index e938b5f..33f7974 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -10,12 +10,13 @@ use std::sync::Arc; use BackendSpecificError; use BuildStreamError; use Data; -use Format; use PauseStreamError; use PlayStreamError; use Sample; use SampleFormat; +use StreamConfig; use StreamError; +use SupportedStreamConfig; /// Sample types whose constant silent value is known. trait Silence { @@ -59,7 +60,8 @@ impl Stream { impl Device { pub fn build_input_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, mut data_callback: D, _error_callback: E, ) -> Result @@ -70,18 +72,18 @@ impl Device { let stream_type = self.driver.input_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. - let data_type = super::device::convert_data_type(&stream_type) - .ok_or(BuildStreamError::FormatNotSupported)?; - if format.data_type != data_type { - return Err(BuildStreamError::FormatNotSupported); + let expected_sample_format = super::device::convert_data_type(&stream_type) + .ok_or(BuildStreamError::StreamConfigNotSupported)?; + if sample_format != expected_sample_format { + return Err(BuildStreamError::StreamConfigNotSupported); } - let num_channels = format.channels.clone(); - let buffer_size = self.get_or_create_input_stream(format)?; + let num_channels = config.channels.clone(); + let buffer_size = self.get_or_create_input_stream(config, sample_format)?; let cpal_num_samples = buffer_size * num_channels as usize; // Create the buffer depending on the size of the data type. - let len_bytes = cpal_num_samples * data_type.sample_size(); + let len_bytes = cpal_num_samples * sample_format.sample_size(); let mut interleaved = vec![0u8; len_bytes]; let stream_playing = Arc::new(AtomicBool::new(false)); @@ -134,7 +136,7 @@ impl Device { callback(&data); } - match (&stream_type, data_type) { + match (&stream_type, sample_format) { (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => { process_input_callback::( &mut data_callback, @@ -225,7 +227,8 @@ impl Device { pub fn build_output_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, mut data_callback: D, _error_callback: E, ) -> Result @@ -236,18 +239,18 @@ impl Device { let stream_type = self.driver.output_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. - let data_type = super::device::convert_data_type(&stream_type) - .ok_or(BuildStreamError::FormatNotSupported)?; - if format.data_type != data_type { - return Err(BuildStreamError::FormatNotSupported); + let expected_sample_format = super::device::convert_data_type(&stream_type) + .ok_or(BuildStreamError::StreamConfigNotSupported)?; + if sample_format != expected_sample_format { + return Err(BuildStreamError::StreamConfigNotSupported); } - let num_channels = format.channels.clone(); - let buffer_size = self.get_or_create_output_stream(format)?; + let num_channels = config.channels.clone(); + let buffer_size = self.get_or_create_output_stream(config, sample_format)?; let cpal_num_samples = buffer_size * num_channels as usize; // Create buffers depending on data type. - let len_bytes = cpal_num_samples * data_type.sample_size(); + let len_bytes = cpal_num_samples * sample_format.sample_size(); let mut interleaved = vec![0u8; len_bytes]; let mut silence_asio_buffer = SilenceAsioBuffer::default(); @@ -336,7 +339,7 @@ impl Device { } } - match (data_type, &stream_type) { + match (sample_format, &stream_type) { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => { process_output_callback::( &mut data_callback, @@ -436,15 +439,19 @@ impl Device { /// If there is no existing ASIO Input Stream it will be created. /// /// On success, the buffer size of the stream is returned. - fn get_or_create_input_stream(&self, format: &Format) -> Result { - match self.default_input_format() { + fn get_or_create_input_stream( + &self, + config: &StreamConfig, + sample_format: SampleFormat, + ) -> Result { + match self.default_input_config() { Ok(f) => { let num_asio_channels = f.channels; - check_format(&self.driver, format, num_asio_channels) + check_config(&self.driver, config, sample_format, num_asio_channels) } - Err(_) => Err(BuildStreamError::FormatNotSupported), + Err(_) => Err(BuildStreamError::StreamConfigNotSupported), }?; - let num_channels = format.channels as usize; + let num_channels = config.channels as usize; let ref mut streams = *self.asio_streams.lock(); // Either create a stream if thers none or had back the // size of the current one. @@ -473,15 +480,19 @@ impl Device { /// Create a new CPAL Output Stream. /// /// If there is no existing ASIO Output Stream it will be created. - fn get_or_create_output_stream(&self, format: &Format) -> Result { - match self.default_output_format() { + fn get_or_create_output_stream( + &self, + config: &StreamConfig, + sample_format: SampleFormat, + ) -> Result { + match self.default_output_config() { Ok(f) => { let num_asio_channels = f.channels; - check_format(&self.driver, format, num_asio_channels) + check_config(&self.driver, config, sample_format, num_asio_channels) } - Err(_) => Err(BuildStreamError::FormatNotSupported), + Err(_) => Err(BuildStreamError::StreamConfigNotSupported), }?; - let num_channels = format.channels as usize; + let num_channels = config.channels as usize; let ref mut streams = *self.asio_streams.lock(); // Either create a stream if thers none or had back the // size of the current one. @@ -570,19 +581,19 @@ impl AsioSample for f64 { } } -/// Check whether or not the desired format is supported by the stream. +/// Check whether or not the desired config is supported by the stream. /// /// Checks sample rate, data type and then finally the number of channels. -fn check_format( +fn check_config( driver: &sys::Driver, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, num_asio_channels: u16, ) -> Result<(), BuildStreamError> { - let Format { + let StreamConfig { channels, sample_rate, - data_type, - } = format; + } = config; // Try and set the sample rate to what the user selected. let sample_rate = sample_rate.0.into(); if sample_rate != driver.sample_rate().map_err(build_stream_err)? { @@ -594,16 +605,16 @@ fn check_format( .set_sample_rate(sample_rate) .map_err(build_stream_err)?; } else { - return Err(BuildStreamError::FormatNotSupported); + return Err(BuildStreamError::StreamConfigNotSupported); } } // unsigned formats are not supported by asio - match data_type { + match sample_format { SampleFormat::I16 | SampleFormat::F32 => (), - SampleFormat::U16 => return Err(BuildStreamError::FormatNotSupported), + SampleFormat::U16 => return Err(BuildStreamError::StreamConfigNotSupported), } if *channels > num_asio_channels { - return Err(BuildStreamError::FormatNotSupported); + return Err(BuildStreamError::StreamConfigNotSupported); } Ok(()) } diff --git a/src/host/coreaudio/enumerate.rs b/src/host/coreaudio/enumerate.rs index 6762665..bdfd5d0 100644 --- a/src/host/coreaudio/enumerate.rs +++ b/src/host/coreaudio/enumerate.rs @@ -9,7 +9,7 @@ use super::Device; use std::mem; use std::ptr::null; use std::vec::IntoIter as VecIntoIter; -use {BackendSpecificError, DevicesError, SupportedFormat}; +use {BackendSpecificError, DevicesError, SupportedStreamConfigRange}; unsafe fn audio_devices() -> Result, OSStatus> { let property_address = AudioObjectPropertyAddress { @@ -143,5 +143,5 @@ pub fn default_output_device() -> Option { Some(device) } -pub type SupportedInputFormats = VecIntoIter; -pub type SupportedOutputFormats = VecIntoIter; +pub type SupportedInputConfigs = VecIntoIter; +pub type SupportedOutputConfigs = VecIntoIter; diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index cefcf88..e3a699f 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -20,9 +20,10 @@ use self::coreaudio::sys::{ }; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultFormatError, - DeviceNameError, DevicesError, Format, PauseStreamError, PlayStreamError, SampleFormat, - SampleRate, StreamError, SupportedFormat, SupportedFormatsError, + BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, + DeviceNameError, DevicesError, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, + StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use std::cell::RefCell; use std::ffi::CStr; @@ -37,8 +38,8 @@ use std::time::Duration; mod enumerate; pub use self::enumerate::{ - default_input_device, default_output_device, Devices, SupportedInputFormats, - SupportedOutputFormats, + default_input_device, default_output_device, Devices, SupportedInputConfigs, + SupportedOutputConfigs, }; /// Coreaudio host, the default host on macOS and iOS. @@ -74,37 +75,38 @@ impl HostTrait for Host { } impl DeviceTrait for Device { - type SupportedInputFormats = SupportedInputFormats; - type SupportedOutputFormats = SupportedOutputFormats; + type SupportedInputConfigs = SupportedInputConfigs; + type SupportedOutputConfigs = SupportedOutputConfigs; type Stream = Stream; fn name(&self) -> Result { Device::name(self) } - fn supported_input_formats( + fn supported_input_configs( &self, - ) -> Result { - Device::supported_input_formats(self) + ) -> Result { + Device::supported_input_configs(self) } - fn supported_output_formats( + fn supported_output_configs( &self, - ) -> Result { - Device::supported_output_formats(self) + ) -> Result { + Device::supported_output_configs(self) } - fn default_input_format(&self) -> Result { - Device::default_input_format(self) + fn default_input_config(&self) -> Result { + Device::default_input_config(self) } - fn default_output_format(&self) -> Result { - Device::default_output_format(self) + fn default_output_config(&self) -> Result { + Device::default_output_config(self) } fn build_input_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -112,12 +114,13 @@ impl DeviceTrait for Device { D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Device::build_input_stream_raw(self, format, data_callback, error_callback) + Device::build_input_stream_raw(self, config, sample_format, data_callback, error_callback) } fn build_output_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -125,7 +128,7 @@ impl DeviceTrait for Device { D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Device::build_output_stream_raw(self, format, data_callback, error_callback) + Device::build_output_stream_raw(self, config, sample_format, data_callback, error_callback) } } @@ -165,11 +168,11 @@ impl Device { Ok(c_str.to_string_lossy().into_owned()) } - // Logic re-used between `supported_input_formats` and `supported_output_formats`. - fn supported_formats( + // Logic re-used between `supported_input_configs` and `supported_output_configs`. + fn supported_configs( &self, scope: AudioObjectPropertyScope, - ) -> Result { + ) -> Result { let mut property_address = AudioObjectPropertyAddress { mSelector: kAudioDevicePropertyStreamConfiguration, mScope: scope, @@ -255,11 +258,11 @@ impl Device { // Collect the supported formats for the device. let mut fmts = vec![]; for range in ranges { - let fmt = SupportedFormat { + let fmt = SupportedStreamConfigRange { channels: n_channels as ChannelCount, min_sample_rate: SampleRate(range.mMinimum as _), max_sample_rate: SampleRate(range.mMaximum as _), - data_type: sample_format, + sample_format: sample_format, }; fmts.push(fmt); } @@ -268,19 +271,25 @@ impl Device { } } - fn supported_input_formats(&self) -> Result { - self.supported_formats(kAudioObjectPropertyScopeInput) + fn supported_input_configs( + &self, + ) -> Result { + self.supported_configs(kAudioObjectPropertyScopeInput) } - fn supported_output_formats(&self) -> Result { - self.supported_formats(kAudioObjectPropertyScopeOutput) + fn supported_output_configs( + &self, + ) -> Result { + self.supported_configs(kAudioObjectPropertyScopeOutput) } - fn default_format( + fn default_config( &self, scope: AudioObjectPropertyScope, - ) -> Result { - fn default_format_error_from_os_status(status: OSStatus) -> Result<(), DefaultFormatError> { + ) -> Result { + fn default_config_error_from_os_status( + status: OSStatus, + ) -> Result<(), DefaultStreamConfigError> { let err = match coreaudio::Error::from_os_status(status) { Err(err) => err, Ok(_) => return Ok(()), @@ -291,10 +300,10 @@ impl Device { ) | coreaudio::Error::AudioCodec(_) | coreaudio::Error::AudioFormat(_) => { - Err(DefaultFormatError::StreamTypeNotSupported) + Err(DefaultStreamConfigError::StreamTypeNotSupported) } coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::NoConnection) => { - Err(DefaultFormatError::DeviceNotAvailable) + Err(DefaultStreamConfigError::DeviceNotAvailable) } err => { let description = format!("{}", std::error::Error::description(&err)); @@ -321,7 +330,7 @@ impl Device { &data_size as *const _ as *mut _, &asbd as *const _ as *mut _, ); - default_format_error_from_os_status(status)?; + default_config_error_from_os_status(status)?; let sample_format = { let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag( @@ -330,7 +339,7 @@ impl Device { ); let flags = match audio_format { Some(coreaudio::audio_unit::AudioFormat::LinearPCM(flags)) => flags, - _ => return Err(DefaultFormatError::StreamTypeNotSupported), + _ => return Err(DefaultStreamConfigError::StreamTypeNotSupported), }; let maybe_sample_format = coreaudio::audio_unit::SampleFormat::from_flags_and_bytes_per_frame( @@ -340,25 +349,25 @@ impl Device { match maybe_sample_format { Some(coreaudio::audio_unit::SampleFormat::F32) => SampleFormat::F32, Some(coreaudio::audio_unit::SampleFormat::I16) => SampleFormat::I16, - _ => return Err(DefaultFormatError::StreamTypeNotSupported), + _ => return Err(DefaultStreamConfigError::StreamTypeNotSupported), } }; - let format = Format { + let config = SupportedStreamConfig { sample_rate: SampleRate(asbd.mSampleRate as _), channels: asbd.mChannelsPerFrame as _, - data_type: sample_format, + sample_format: sample_format, }; - Ok(format) + Ok(config) } } - fn default_input_format(&self) -> Result { - self.default_format(kAudioObjectPropertyScopeInput) + fn default_input_config(&self) -> Result { + self.default_config(kAudioObjectPropertyScopeInput) } - fn default_output_format(&self) -> Result { - self.default_format(kAudioObjectPropertyScopeOutput) + fn default_output_config(&self) -> Result { + self.default_config(kAudioObjectPropertyScopeOutput) } } @@ -389,22 +398,24 @@ impl From for BuildStreamError { | coreaudio::Error::NoKnownSubtype | coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) | coreaudio::Error::AudioCodec(_) - | coreaudio::Error::AudioFormat(_) => BuildStreamError::FormatNotSupported, + | coreaudio::Error::AudioFormat(_) => BuildStreamError::StreamConfigNotSupported, _ => BuildStreamError::DeviceNotAvailable, } } } // Create a coreaudio AudioStreamBasicDescription from a CPAL Format. -fn asbd_from_format(format: &Format) -> AudioStreamBasicDescription { - let n_channels = format.channels as usize; - let sample_rate = format.sample_rate.0; - let bytes_per_channel = format.data_type.sample_size(); +fn asbd_from_config( + config: &StreamConfig, + sample_format: SampleFormat, +) -> AudioStreamBasicDescription { + let n_channels = config.channels as usize; + let sample_rate = config.sample_rate.0; + let bytes_per_channel = sample_format.sample_size(); let bits_per_channel = bytes_per_channel * 8; let bytes_per_frame = n_channels * bytes_per_channel; let frames_per_packet = 1; let bytes_per_packet = frames_per_packet * bytes_per_frame; - let sample_format = format.data_type; let format_flags = match sample_format { SampleFormat::F32 => (kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked) as u32, _ => kAudioFormatFlagIsPacked as u32, @@ -469,7 +480,8 @@ fn audio_unit_from_device(device: &Device, input: bool) -> Result( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, mut data_callback: D, _error_callback: E, ) -> Result @@ -502,7 +514,7 @@ impl Device { coreaudio::Error::from_os_status(status)?; // If the requested sample rate is different to the device sample rate, update the device. - if sample_rate as u32 != format.sample_rate.0 { + if sample_rate as u32 != config.sample_rate.0 { // Get available sample rate ranges. property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; let data_size = 0u32; @@ -530,12 +542,12 @@ impl Device { let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges); // Now that we have the available ranges, pick the one matching the desired rate. - let sample_rate = format.sample_rate.0; + let sample_rate = config.sample_rate.0; let maybe_index = ranges.iter().position(|r| { r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate }); let range_index = match maybe_index { - None => return Err(BuildStreamError::FormatNotSupported), + None => return Err(BuildStreamError::StreamConfigNotSupported), Some(i) => i, }; @@ -619,12 +631,11 @@ impl Device { let mut audio_unit = audio_unit_from_device(self, true)?; // Set the stream in interleaved mode. - let asbd = asbd_from_format(format); + let asbd = asbd_from_config(config, sample_format); audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?; // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. - let sample_format = format.data_type; let bytes_per_channel = sample_format.sample_size(); type Args = render_callback::Args; audio_unit.set_input_callback(move |args: Args| unsafe { @@ -657,7 +668,8 @@ impl Device { fn build_output_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, mut data_callback: D, _error_callback: E, ) -> Result @@ -672,12 +684,11 @@ impl Device { let element = Element::Output; // Set the stream in interleaved mode. - let asbd = asbd_from_format(format); + let asbd = asbd_from_config(config, sample_format); audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?; // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. - let sample_format = format.data_type; let bytes_per_channel = sample_format.sample_size(); type Args = render_callback::Args; audio_unit.set_render_callback(move |args: Args| unsafe { diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 1b2d797..999746c 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -8,9 +8,9 @@ use stdweb::web::TypedArray; use stdweb::Reference; use crate::{ - BuildStreamError, Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - PauseStreamError, PlayStreamError, SampleFormat, StreamError, SupportedFormat, - SupportedFormatsError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, + PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, + SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; use traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -37,8 +37,8 @@ pub struct Stream { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StreamId(usize); -pub type SupportedInputFormats = ::std::vec::IntoIter; -pub type SupportedOutputFormats = ::std::vec::IntoIter; +pub type SupportedInputConfigs = ::std::vec::IntoIter; +pub type SupportedOutputConfigs = ::std::vec::IntoIter; impl Host { pub fn new() -> Result { @@ -60,12 +60,16 @@ impl Device { } #[inline] - fn supported_input_formats(&self) -> Result { + fn supported_input_configs( + &self, + ) -> Result { unimplemented!(); } #[inline] - fn supported_output_formats(&self) -> Result { + fn supported_output_configs( + &self, + ) -> Result { // TODO: right now cpal's API doesn't allow flexibility here // "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if // this ever becomes more flexible, don't forget to change that @@ -74,25 +78,25 @@ impl Device { // // UPDATE: We can do this now. Might be best to use `crate::COMMON_SAMPLE_RATES` and // filter out those that lay outside the range specified above. - Ok(vec![SupportedFormat { + Ok(vec![SupportedStreamConfigRange { channels: 2, min_sample_rate: ::SampleRate(44100), max_sample_rate: ::SampleRate(44100), - data_type: ::SampleFormat::F32, + sample_format: ::SampleFormat::F32, }] .into_iter()) } - fn default_input_format(&self) -> Result { + fn default_input_config(&self) -> Result { unimplemented!(); } - fn default_output_format(&self) -> Result { - // TODO: because it is hard coded, see supported_output_formats. - Ok(Format { + fn default_output_config(&self) -> Result { + // TODO: because it is hard coded, see supported_output_configs. + Ok(SupportedStreamConfig { channels: 2, sample_rate: ::SampleRate(44100), - data_type: ::SampleFormat::F32, + sample_format: ::SampleFormat::F32, }) } } @@ -120,37 +124,38 @@ impl HostTrait for Host { } impl DeviceTrait for Device { - type SupportedInputFormats = SupportedInputFormats; - type SupportedOutputFormats = SupportedOutputFormats; + type SupportedInputConfigs = SupportedInputConfigs; + type SupportedOutputConfigs = SupportedOutputConfigs; type Stream = Stream; fn name(&self) -> Result { Device::name(self) } - fn supported_input_formats( + fn supported_input_configs( &self, - ) -> Result { - Device::supported_input_formats(self) + ) -> Result { + Device::supported_input_configs(self) } - fn supported_output_formats( + fn supported_output_configs( &self, - ) -> Result { - Device::supported_output_formats(self) + ) -> Result { + Device::supported_output_configs(self) } - fn default_input_format(&self) -> Result { - Device::default_input_format(self) + fn default_input_config(&self) -> Result { + Device::default_input_config(self) } - fn default_output_format(&self) -> Result { - Device::default_output_format(self) + fn default_output_config(&self) -> Result { + Device::default_output_config(self) } fn build_input_stream_raw( &self, - _format: &Format, + _config: &StreamConfig, + _sample_format: SampleFormat, _data_callback: D, _error_callback: E, ) -> Result @@ -163,7 +168,8 @@ impl DeviceTrait for Device { fn build_output_stream_raw( &self, - format: &Format, + _config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -172,7 +178,7 @@ impl DeviceTrait for Device { E: FnMut(StreamError) + Send + 'static, { assert_eq!( - format.data_type, + sample_format, SampleFormat::F32, "emscripten backend currently only supports `f32` data", ); diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 2cdbff2..1424ca8 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -1,6 +1,7 @@ use crate::{ - BuildStreamError, Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - PauseStreamError, PlayStreamError, StreamError, SupportedFormat, SupportedFormatsError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, + PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, + SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; use traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -15,8 +16,8 @@ pub struct Host; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Stream; -pub struct SupportedInputFormats; -pub struct SupportedOutputFormats; +pub struct SupportedInputConfigs; +pub struct SupportedOutputConfigs; impl Host { #[allow(dead_code)] @@ -32,8 +33,8 @@ impl Devices { } impl DeviceTrait for Device { - type SupportedInputFormats = SupportedInputFormats; - type SupportedOutputFormats = SupportedOutputFormats; + type SupportedInputConfigs = SupportedInputConfigs; + type SupportedOutputConfigs = SupportedOutputConfigs; type Stream = Stream; #[inline] @@ -42,28 +43,33 @@ impl DeviceTrait for Device { } #[inline] - fn supported_input_formats(&self) -> Result { + fn supported_input_configs( + &self, + ) -> Result { unimplemented!() } #[inline] - fn supported_output_formats(&self) -> Result { + fn supported_output_configs( + &self, + ) -> Result { unimplemented!() } #[inline] - fn default_input_format(&self) -> Result { + fn default_input_config(&self) -> Result { unimplemented!() } #[inline] - fn default_output_format(&self) -> Result { + fn default_output_config(&self) -> Result { unimplemented!() } fn build_input_stream_raw( &self, - _format: &Format, + _config: &StreamConfig, + _sample_format: SampleFormat, _data_callback: D, _error_callback: E, ) -> Result @@ -77,7 +83,8 @@ impl DeviceTrait for Device { /// Create an output stream. fn build_output_stream_raw( &self, - _format: &Format, + _config: &StreamConfig, + _sample_format: SampleFormat, _data_callback: D, _error_callback: E, ) -> Result @@ -129,20 +136,20 @@ impl Iterator for Devices { } } -impl Iterator for SupportedInputFormats { - type Item = SupportedFormat; +impl Iterator for SupportedInputConfigs { + type Item = SupportedStreamConfigRange; #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { None } } -impl Iterator for SupportedOutputFormats { - type Item = SupportedFormat; +impl Iterator for SupportedOutputConfigs { + type Item = SupportedStreamConfigRange; #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { None } } diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 69c82f2..f1e1e1b 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,6 +1,7 @@ use crate::{ - BackendSpecificError, Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - SampleFormat, SampleRate, SupportedFormat, SupportedFormatsError, COMMON_SAMPLE_RATES, + BackendSpecificError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, + SampleFormat, SampleRate, StreamConfig, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, COMMON_SAMPLE_RATES, }; use std; use std::ffi::OsString; @@ -49,8 +50,8 @@ use super::{ }; use crate::{traits::DeviceTrait, BuildStreamError, StreamError}; -pub type SupportedInputFormats = std::vec::IntoIter; -pub type SupportedOutputFormats = std::vec::IntoIter; +pub type SupportedInputConfigs = std::vec::IntoIter; +pub type SupportedOutputConfigs = std::vec::IntoIter; /// Wrapper because of that stupid decision to remove `Send` and `Sync` from raw pointers. #[derive(Copy, Clone)] @@ -67,37 +68,38 @@ pub struct Device { } impl DeviceTrait for Device { - type SupportedInputFormats = SupportedInputFormats; - type SupportedOutputFormats = SupportedOutputFormats; + type SupportedInputConfigs = SupportedInputConfigs; + type SupportedOutputConfigs = SupportedOutputConfigs; type Stream = Stream; fn name(&self) -> Result { Device::name(self) } - fn supported_input_formats( + fn supported_input_configs( &self, - ) -> Result { - Device::supported_input_formats(self) + ) -> Result { + Device::supported_input_configs(self) } - fn supported_output_formats( + fn supported_output_configs( &self, - ) -> Result { - Device::supported_output_formats(self) + ) -> Result { + Device::supported_output_configs(self) } - fn default_input_format(&self) -> Result { - Device::default_input_format(self) + fn default_input_config(&self) -> Result { + Device::default_input_config(self) } - fn default_output_format(&self) -> Result { - Device::default_output_format(self) + fn default_output_config(&self) -> Result { + Device::default_output_config(self) } fn build_input_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -105,7 +107,7 @@ impl DeviceTrait for Device { D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let stream_inner = self.build_input_stream_raw_inner(format)?; + let stream_inner = self.build_input_stream_raw_inner(config, sample_format)?; Ok(Stream::new_input( stream_inner, data_callback, @@ -115,7 +117,8 @@ impl DeviceTrait for Device { fn build_output_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -123,7 +126,7 @@ impl DeviceTrait for Device { D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let stream_inner = self.build_output_stream_raw_inner(format)?; + let stream_inner = self.build_output_stream_raw_inner(config, sample_format)?; Ok(Stream::new_output( stream_inner, data_callback, @@ -214,7 +217,7 @@ unsafe fn data_flow_from_immendpoint(endpoint: *const IMMEndpoint) -> EDataFlow pub unsafe fn is_format_supported( client: *const IAudioClient, waveformatex_ptr: *const mmreg::WAVEFORMATEX, -) -> Result { +) -> Result { /* // `IsFormatSupported` checks whether the format is supported and fills // a `WAVEFORMATEX` @@ -241,7 +244,7 @@ pub unsafe fn is_format_supported( }, (winerror::S_FALSE, _) => { (*audio_client).Release(); - return Err(BuildStreamError::FormatNotSupported); + return Err(BuildStreamError::StreamConfigNotSupported); }, (_, Ok(())) => (), }; @@ -258,7 +261,7 @@ pub unsafe fn is_format_supported( // has been found, but not an exact match) so we also treat this as unsupported. match (result, check_result(result)) { (_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - Err(SupportedFormatsError::DeviceNotAvailable) + Err(SupportedStreamConfigsError::DeviceNotAvailable) } (_, Err(_)) => Ok(false), (winerror::S_FALSE, _) => Ok(false), @@ -291,11 +294,11 @@ pub unsafe fn is_format_supported( // Get a cpal Format from a WAVEFORMATEX. unsafe fn format_from_waveformatex_ptr( waveformatex_ptr: *const mmreg::WAVEFORMATEX, -) -> Option { +) -> Option { fn cmp_guid(a: &GUID, b: &GUID) -> bool { a.Data1 == b.Data1 && a.Data2 == b.Data2 && a.Data3 == b.Data3 && a.Data4 == b.Data4 } - let data_type = match ( + let sample_format = match ( (*waveformatex_ptr).wBitsPerSample, (*waveformatex_ptr).wFormatTag, ) { @@ -315,10 +318,10 @@ unsafe fn format_from_waveformatex_ptr( // Unknown data format returned by GetMixFormat. _ => return None, }; - let format = Format { + let format = SupportedStreamConfig { channels: (*waveformatex_ptr).nChannels as _, sample_rate: SampleRate((*waveformatex_ptr).nSamplesPerSec), - data_type, + sample_format, }; Some(format) } @@ -433,7 +436,7 @@ impl Device { // number of channels seems to be supported. Any more or less returns an invalid // parameter error. Thus we just assume that the default number of channels is the only // number supported. - fn supported_formats(&self) -> Result { + fn supported_formats(&self) -> Result { // initializing COM because we call `CoTaskMemFree` to release the format. com::com_initialized(); @@ -441,7 +444,7 @@ impl Device { let lock = match self.ensure_future_audio_client() { Ok(lock) => lock, Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - return Err(SupportedFormatsError::DeviceNotAvailable) + return Err(SupportedStreamConfigsError::DeviceNotAvailable) } Err(e) => { let description = format!("{}", e); @@ -457,7 +460,7 @@ impl Device { match check_result((*client).GetMixFormat(&mut default_waveformatex_ptr.0)) { Ok(()) => (), Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - return Err(SupportedFormatsError::DeviceNotAvailable); + return Err(SupportedStreamConfigsError::DeviceNotAvailable); } Err(e) => { let description = format!("{}", e); @@ -514,7 +517,8 @@ impl Device { Some(fmt) => fmt, None => { let description = - "could not create a `cpal::Format` from a `WAVEFORMATEX`".to_string(); + "could not create a `cpal::SupportedStreamConfig` from a `WAVEFORMATEX`" + .to_string(); let err = BackendSpecificError { description }; return Err(err.into()); } @@ -522,13 +526,15 @@ impl Device { let mut supported_formats = Vec::with_capacity(supported_sample_rates.len()); for rate in supported_sample_rates { format.sample_rate = SampleRate(rate as _); - supported_formats.push(SupportedFormat::from(format.clone())); + supported_formats.push(SupportedStreamConfigRange::from(format.clone())); } Ok(supported_formats.into_iter()) } } - pub fn supported_input_formats(&self) -> Result { + pub fn supported_input_configs( + &self, + ) -> Result { if self.data_flow() == eCapture { self.supported_formats() // If it's an output device, assume no input formats. @@ -537,9 +543,9 @@ impl Device { } } - pub fn supported_output_formats( + pub fn supported_output_configs( &self, - ) -> Result { + ) -> Result { if self.data_flow() == eRender { self.supported_formats() // If it's an input device, assume no output formats. @@ -552,14 +558,14 @@ impl Device { // processor to mix them together. // // One format is guaranteed to be supported, the one returned by `GetMixFormat`. - fn default_format(&self) -> Result { + fn default_format(&self) -> Result { // initializing COM because we call `CoTaskMemFree` com::com_initialized(); let lock = match self.ensure_future_audio_client() { Ok(lock) => lock, Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - return Err(DefaultFormatError::DeviceNotAvailable) + return Err(DefaultStreamConfigError::DeviceNotAvailable) } Err(e) => { let description = format!("{}", e); @@ -573,7 +579,7 @@ impl Device { let mut format_ptr = WaveFormatExPtr(ptr::null_mut()); match check_result((*client).GetMixFormat(&mut format_ptr.0)) { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - return Err(DefaultFormatError::DeviceNotAvailable); + return Err(DefaultStreamConfigError::DeviceNotAvailable); } Err(e) => { let description = format!("{}", e); @@ -584,7 +590,7 @@ impl Device { }; format_from_waveformatex_ptr(format_ptr.0) - .ok_or(DefaultFormatError::StreamTypeNotSupported) + .ok_or(DefaultStreamConfigError::StreamTypeNotSupported) } } @@ -593,26 +599,27 @@ impl Device { endpoint.data_flow() } - pub fn default_input_format(&self) -> Result { + pub fn default_input_config(&self) -> Result { if self.data_flow() == eCapture { self.default_format() } else { - Err(DefaultFormatError::StreamTypeNotSupported) + Err(DefaultStreamConfigError::StreamTypeNotSupported) } } - pub fn default_output_format(&self) -> Result { + pub fn default_output_config(&self) -> Result { let data_flow = self.data_flow(); if data_flow == eRender { self.default_format() } else { - Err(DefaultFormatError::StreamTypeNotSupported) + Err(DefaultStreamConfigError::StreamTypeNotSupported) } } pub(crate) fn build_input_stream_raw_inner( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, ) -> Result { unsafe { // Making sure that COM is initialized. @@ -634,13 +641,13 @@ impl Device { // Computing the format and initializing the device. let waveformatex = { - let format_attempt = format_to_waveformatextensible(format) - .ok_or(BuildStreamError::FormatNotSupported)?; + let format_attempt = config_to_waveformatextensible(config, sample_format) + .ok_or(BuildStreamError::StreamConfigNotSupported)?; let share_mode = AUDCLNT_SHAREMODE_SHARED; // Ensure the format is supported. match super::device::is_format_supported(audio_client, &format_attempt.Format) { - Ok(false) => return Err(BuildStreamError::FormatNotSupported), + Ok(false) => return Err(BuildStreamError::StreamConfigNotSupported), Err(_) => return Err(BuildStreamError::DeviceNotAvailable), _ => (), } @@ -749,14 +756,15 @@ impl Device { playing: false, max_frames_in_buffer, bytes_per_frame: waveformatex.nBlockAlign, - sample_format: format.data_type, + sample_format, }) } } pub(crate) fn build_output_stream_raw_inner( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, ) -> Result { unsafe { // Making sure that COM is initialized. @@ -778,13 +786,13 @@ impl Device { // Computing the format and initializing the device. let waveformatex = { - let format_attempt = format_to_waveformatextensible(format) - .ok_or(BuildStreamError::FormatNotSupported)?; + let format_attempt = config_to_waveformatextensible(config, sample_format) + .ok_or(BuildStreamError::StreamConfigNotSupported)?; let share_mode = AUDCLNT_SHAREMODE_SHARED; // Ensure the format is supported. match super::device::is_format_supported(audio_client, &format_attempt.Format) { - Ok(false) => return Err(BuildStreamError::FormatNotSupported), + Ok(false) => return Err(BuildStreamError::StreamConfigNotSupported), Err(_) => return Err(BuildStreamError::DeviceNotAvailable), _ => (), } @@ -893,7 +901,7 @@ impl Device { playing: false, max_frames_in_buffer, bytes_per_frame: waveformatex.nBlockAlign, - sample_format: format.data_type, + sample_format, }) } } @@ -1141,19 +1149,22 @@ pub fn default_output_device() -> Option { // Turns a `Format` into a `WAVEFORMATEXTENSIBLE`. // // Returns `None` if the WAVEFORMATEXTENSIBLE does not support the given format. -fn format_to_waveformatextensible(format: &Format) -> Option { - let format_tag = match format.data_type { +fn config_to_waveformatextensible( + config: &StreamConfig, + sample_format: SampleFormat, +) -> Option { + let format_tag = match sample_format { SampleFormat::I16 => mmreg::WAVE_FORMAT_PCM, SampleFormat::F32 => mmreg::WAVE_FORMAT_EXTENSIBLE, SampleFormat::U16 => return None, }; - let channels = format.channels as WORD; - let sample_rate = format.sample_rate.0 as DWORD; - let sample_bytes = format.data_type.sample_size() as WORD; + let channels = config.channels as WORD; + let sample_rate = config.sample_rate.0 as DWORD; + let sample_bytes = sample_format.sample_size() as WORD; let avg_bytes_per_sec = u32::from(channels) * sample_rate * u32::from(sample_bytes); let block_align = channels * sample_bytes; let bits_per_sample = 8 * sample_bytes; - let cb_size = match format.data_type { + let cb_size = match sample_format { SampleFormat::I16 => 0, SampleFormat::F32 => { let extensible_size = mem::size_of::(); @@ -1177,7 +1188,7 @@ fn format_to_waveformatextensible(format: &Format) -> Option ksmedia::KSDATAFORMAT_SUBTYPE_PCM, SampleFormat::F32 => ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, SampleFormat::U16 => return None, diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index 7fb6038..abf00a2 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -1,8 +1,8 @@ extern crate winapi; pub use self::device::{ - default_input_device, default_output_device, Device, Devices, SupportedInputFormats, - SupportedOutputFormats, + default_input_device, default_output_device, Device, Devices, SupportedInputConfigs, + SupportedOutputConfigs, }; pub use self::stream::Stream; use self::winapi::um::winnt::HRESULT; diff --git a/src/lib.rs b/src/lib.rs index f05cb7e..abea8ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,24 +31,25 @@ //! let device = host.default_output_device().expect("no output device available"); //! ``` //! -//! Before we can create a stream, we must decide what the format of the audio samples is going to -//! be. You can query all the supported formats with the `supported_input_formats()` and -//! `supported_output_formats()` methods. These produce a list of `SupportedFormat` structs which -//! can later be turned into actual `Format` structs. If you don't want to query the list of -//! formats, you can also build your own `Format` manually, but doing so could lead to an error -//! when building the stream if the format is not supported by the device. +//! Before we can create a stream, we must decide what the configuration of the audio stream is +//! going to be. You can query all the supported configurations with the +//! `supported_input_configs()` and `supported_output_configs()` methods. These produce a list of +//! `SupportedStreamConfigRange` structs which can later be turned into actual +//! `SupportedStreamConfig` structs. If you don't want to query the list of configs, you can also +//! build your own `StreamConfig` manually, but doing so could lead to an error when building the +//! stream if the config is not supported by the device. //! -//! > **Note**: the `supported_formats()` method could return an error for example if the device -//! > has been disconnected. +//! > **Note**: the `supported_input/output_configs()` methods could return an error for example if +//! > the device has been disconnected. //! //! ```no_run //! use cpal::traits::{DeviceTrait, HostTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); -//! let mut supported_formats_range = device.supported_output_formats() -//! .expect("error while querying formats"); -//! let format = supported_formats_range.next() -//! .expect("no supported format?!") +//! let mut supported_configs_range = device.supported_output_configs() +//! .expect("error while querying configs"); +//! let supported_config = supported_configs_range.next() +//! .expect("no supported config?!") //! .with_max_sample_rate(); //! ``` //! @@ -59,9 +60,9 @@ //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); -//! # let shape = device.default_output_format().unwrap().shape(); +//! # let config = device.default_output_config().unwrap().into(); //! let stream = device.build_output_stream( -//! &shape, +//! &config, //! move |data: &mut [f32]| { //! // react to stream events and read or write stream data here. //! }, @@ -91,13 +92,14 @@ //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); -//! # let format = device.default_output_format().unwrap(); +//! # let supported_config = device.default_output_config().unwrap(); //! let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err); -//! let shape = format.shape(); -//! let stream = match format.data_type { -//! SampleFormat::F32 => device.build_output_stream(&shape, write_silence::, err_fn), -//! SampleFormat::I16 => device.build_output_stream(&shape, write_silence::, err_fn), -//! SampleFormat::U16 => device.build_output_stream(&shape, write_silence::, err_fn), +//! let sample_format = supported_config.sample_format(); +//! let config = supported_config.into(); +//! let stream = match sample_format { +//! SampleFormat::F32 => device.build_output_stream(&config, write_silence::, err_fn), +//! SampleFormat::I16 => device.build_output_stream(&config, write_silence::, err_fn), +//! SampleFormat::U16 => device.build_output_stream(&config, write_silence::, err_fn), //! }.unwrap(); //! //! fn write_silence(data: &mut [T]) { @@ -114,10 +116,12 @@ //! # use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); -//! # let format = device.default_output_format().unwrap(); +//! # let supported_config = device.default_output_config().unwrap(); +//! # let sample_format = supported_config.sample_format(); +//! # let config = supported_config.into(); //! # let data_fn = move |_data: &mut cpal::Data| {}; //! # let err_fn = move |_err| {}; -//! # let stream = device.build_output_stream_raw(&format, data_fn, err_fn).unwrap(); +//! # let stream = device.build_output_stream_raw(&config, sample_format, data_fn, err_fn).unwrap(); //! stream.play().unwrap(); //! ``` //! @@ -128,10 +132,12 @@ //! # use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); -//! # let format = device.default_output_format().unwrap(); +//! # let supported_config = device.default_output_config().unwrap(); +//! # let sample_format = supported_config.sample_format(); +//! # let config = supported_config.into(); //! # let data_fn = move |_data: &mut cpal::Data| {}; //! # let err_fn = move |_err| {}; -//! # let stream = device.build_output_stream_raw(&format, data_fn, err_fn).unwrap(); +//! # let stream = device.build_output_stream_raw(&config, sample_format, data_fn, err_fn).unwrap(); //! stream.pause().unwrap(); //! ``` @@ -149,7 +155,7 @@ extern crate thiserror; pub use error::*; pub use platform::{ available_hosts, default_host, host_from_id, Device, Devices, Host, HostId, Stream, - SupportedInputFormats, SupportedOutputFormats, ALL_HOSTS, + SupportedInputConfigs, SupportedOutputConfigs, ALL_HOSTS, }; pub use samples_formats::{Sample, SampleFormat}; @@ -172,56 +178,35 @@ pub type ChannelCount = u16; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct SampleRate(pub u32); -/// The format of an input or output audio stream. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Format { - pub channels: ChannelCount, - pub sample_rate: SampleRate, - pub data_type: SampleFormat, -} - -impl Format { - /// Construct a format having a particular `shape` and `data_type`. - pub fn with_shape(shape: &Shape, data_type: SampleFormat) -> Self { - Self { - channels: shape.channels, - sample_rate: shape.sample_rate, - data_type, - } - } - - /// Extract aspects of the format independent of the data type. - pub fn shape(&self) -> Shape { - self.clone().into() - } -} - -/// The properties of an input or output audio stream excluding its data type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Shape { +/// The set of parameters used to describe how to open a stream. +/// +/// The sample format is omitted in favour of using a sample type. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StreamConfig { pub channels: ChannelCount, pub sample_rate: SampleRate, } -impl From for Shape { - fn from(x: Format) -> Self { - Self { - channels: x.channels, - sample_rate: x.sample_rate, - } - } -} - -/// Describes a range of supported stream formats. +/// Describes a range of supported stream configurations, retrieved via the +/// `Device::supported_input/output_configs` method. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct SupportedFormat { - pub channels: ChannelCount, +pub struct SupportedStreamConfigRange { + pub(crate) channels: ChannelCount, /// Minimum value for the samples rate of the supported formats. - pub min_sample_rate: SampleRate, + pub(crate) min_sample_rate: SampleRate, /// Maximum value for the samples rate of the supported formats. - pub max_sample_rate: SampleRate, + pub(crate) max_sample_rate: SampleRate, /// Type of data expected by the device. - pub data_type: SampleFormat, + pub(crate) sample_format: SampleFormat, +} + +/// Describes a single supported stream configuration, retrieved via either a +/// `SupportedStreamConfigRange` instance or one of the `Device::default_input/output_config` methods. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SupportedStreamConfig { + channels: ChannelCount, + sample_rate: SampleRate, + sample_format: SampleFormat, } /// A buffer of dynamically typed audio data, passed to raw stream callbacks. @@ -235,6 +220,27 @@ pub struct Data { sample_format: SampleFormat, } +impl SupportedStreamConfig { + pub fn channels(&self) -> ChannelCount { + self.channels + } + + pub fn sample_rate(&self) -> SampleRate { + self.sample_rate + } + + pub fn sample_format(&self) -> SampleFormat { + self.sample_format + } + + pub fn config(&self) -> StreamConfig { + StreamConfig { + channels: self.channels, + sample_rate: self.sample_rate, + } + } +} + impl Data { // Internal constructor for host implementations to use. // @@ -328,25 +334,54 @@ impl Data { } } -impl SupportedFormat { - /// Turns this `SupportedFormat` into a `Format` corresponding to the maximum samples rate. - #[inline] - pub fn with_max_sample_rate(self) -> Format { - Format { +impl SupportedStreamConfigRange { + pub fn channels(&self) -> ChannelCount { + self.channels + } + + pub fn min_sample_rate(&self) -> SampleRate { + self.min_sample_rate + } + + pub fn max_sample_rate(&self) -> SampleRate { + self.max_sample_rate + } + + pub fn sample_format(&self) -> SampleFormat { + self.sample_format + } + + /// Retrieve a `SupportedStreamConfig` with the given sample rate. + /// + /// **panic!**s if the given `sample_rate` is outside the range specified within this + /// `SupportedStreamConfigRange` instance. + pub fn with_sample_rate(self, sample_rate: SampleRate) -> SupportedStreamConfig { + assert!(sample_rate <= self.min_sample_rate && sample_rate <= self.max_sample_rate); + SupportedStreamConfig { channels: self.channels, - sample_rate: self.max_sample_rate, - data_type: self.data_type, + sample_format: self.sample_format, + sample_rate, } } - /// A comparison function which compares two `SupportedFormat`s in terms of their priority of + /// Turns this `SupportedStreamConfigRange` into a `SupportedStreamConfig` corresponding to the maximum samples rate. + #[inline] + pub fn with_max_sample_rate(self) -> SupportedStreamConfig { + SupportedStreamConfig { + channels: self.channels, + sample_rate: self.max_sample_rate, + sample_format: self.sample_format, + } + } + + /// A comparison function which compares two `SupportedStreamConfigRange`s in terms of their priority of /// use as a default stream format. /// /// Some backends do not provide a default stream format for their audio devices. In these /// cases, CPAL attempts to decide on a reasonable default format for the user. To do this we /// use the "greatest" of all supported stream formats when compared with this method. /// - /// Formats are prioritised by the following heuristics: + /// SupportedStreamConfigs are prioritised by the following heuristics: /// /// **Channels**: /// @@ -382,17 +417,17 @@ impl SupportedFormat { return cmp_channels; } - let cmp_f32 = (self.data_type == F32).cmp(&(other.data_type == F32)); + let cmp_f32 = (self.sample_format == F32).cmp(&(other.sample_format == F32)); if cmp_f32 != Equal { return cmp_f32; } - let cmp_i16 = (self.data_type == I16).cmp(&(other.data_type == I16)); + let cmp_i16 = (self.sample_format == I16).cmp(&(other.sample_format == I16)); if cmp_i16 != Equal { return cmp_i16; } - let cmp_u16 = (self.data_type == U16).cmp(&(other.data_type == U16)); + let cmp_u16 = (self.sample_format == U16).cmp(&(other.sample_format == U16)); if cmp_u16 != Equal { return cmp_u16; } @@ -410,14 +445,20 @@ impl SupportedFormat { } } -impl From for SupportedFormat { +impl From for StreamConfig { + fn from(conf: SupportedStreamConfig) -> Self { + conf.config() + } +} + +impl From for SupportedStreamConfigRange { #[inline] - fn from(format: Format) -> SupportedFormat { - SupportedFormat { + fn from(format: SupportedStreamConfig) -> SupportedStreamConfigRange { + SupportedStreamConfigRange { channels: format.channels, min_sample_rate: format.sample_rate, max_sample_rate: format.sample_rate, - data_type: format.data_type, + sample_format: format.sample_format, } } } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 6296ce5..cd5f529 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -27,8 +27,8 @@ pub use self::platform_impl::*; // } // ``` // -// And so on for Device, Devices, EventLoop, Host, StreamId, SupportedInputFormats, -// SupportedOutputFormats and all their necessary trait implementations. +// And so on for Device, Devices, EventLoop, Host, StreamId, SupportedInputConfigs, +// SupportedOutputConfigs and all their necessary trait implementations. // ``` macro_rules! impl_platform_host { ($($HostVariant:ident $host_mod:ident $host_name:literal),*) => { @@ -67,13 +67,13 @@ macro_rules! impl_platform_host { // TODO: Confirm this and add more specific detail and references. pub struct Stream(StreamInner, crate::platform::NotSendSyncAcrossAllPlatforms); - /// The **SupportedInputFormats** iterator associated with the platform's dynamically + /// The **SupportedInputConfigs** iterator associated with the platform's dynamically /// dispatched **Host** type. - pub struct SupportedInputFormats(SupportedInputFormatsInner); + pub struct SupportedInputConfigs(SupportedInputConfigsInner); - /// The **SupportedOutputFormats** iterator associated with the platform's dynamically + /// The **SupportedOutputConfigs** iterator associated with the platform's dynamically /// dispatched **Host** type. - pub struct SupportedOutputFormats(SupportedOutputFormatsInner); + pub struct SupportedOutputConfigs(SupportedOutputConfigsInner); /// Unique identifier for available hosts on the platform. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] @@ -107,15 +107,15 @@ macro_rules! impl_platform_host { )* } - enum SupportedInputFormatsInner { + enum SupportedInputConfigsInner { $( - $HostVariant(crate::host::$host_mod::SupportedInputFormats), + $HostVariant(crate::host::$host_mod::SupportedInputConfigs), )* } - enum SupportedOutputFormatsInner { + enum SupportedOutputConfigsInner { $( - $HostVariant(crate::host::$host_mod::SupportedOutputFormats), + $HostVariant(crate::host::$host_mod::SupportedOutputConfigs), )* } @@ -162,13 +162,13 @@ macro_rules! impl_platform_host { } } - impl Iterator for SupportedInputFormats { - type Item = crate::SupportedFormat; + impl Iterator for SupportedInputConfigs { + type Item = crate::SupportedStreamConfigRange; fn next(&mut self) -> Option { match self.0 { $( - SupportedInputFormatsInner::$HostVariant(ref mut s) => s.next(), + SupportedInputConfigsInner::$HostVariant(ref mut s) => s.next(), )* } } @@ -176,19 +176,19 @@ macro_rules! impl_platform_host { fn size_hint(&self) -> (usize, Option) { match self.0 { $( - SupportedInputFormatsInner::$HostVariant(ref d) => d.size_hint(), + SupportedInputConfigsInner::$HostVariant(ref d) => d.size_hint(), )* } } } - impl Iterator for SupportedOutputFormats { - type Item = crate::SupportedFormat; + impl Iterator for SupportedOutputConfigs { + type Item = crate::SupportedStreamConfigRange; fn next(&mut self) -> Option { match self.0 { $( - SupportedOutputFormatsInner::$HostVariant(ref mut s) => s.next(), + SupportedOutputConfigsInner::$HostVariant(ref mut s) => s.next(), )* } } @@ -196,15 +196,15 @@ macro_rules! impl_platform_host { fn size_hint(&self) -> (usize, Option) { match self.0 { $( - SupportedOutputFormatsInner::$HostVariant(ref d) => d.size_hint(), + SupportedOutputConfigsInner::$HostVariant(ref d) => d.size_hint(), )* } } } impl crate::traits::DeviceTrait for Device { - type SupportedInputFormats = SupportedInputFormats; - type SupportedOutputFormats = SupportedOutputFormats; + type SupportedInputConfigs = SupportedInputConfigs; + type SupportedOutputConfigs = SupportedOutputConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -215,49 +215,50 @@ macro_rules! impl_platform_host { } } - fn supported_input_formats(&self) -> Result { + fn supported_input_configs(&self) -> Result { match self.0 { $( DeviceInner::$HostVariant(ref d) => { - d.supported_input_formats() - .map(SupportedInputFormatsInner::$HostVariant) - .map(SupportedInputFormats) + d.supported_input_configs() + .map(SupportedInputConfigsInner::$HostVariant) + .map(SupportedInputConfigs) } )* } } - fn supported_output_formats(&self) -> Result { + fn supported_output_configs(&self) -> Result { match self.0 { $( DeviceInner::$HostVariant(ref d) => { - d.supported_output_formats() - .map(SupportedOutputFormatsInner::$HostVariant) - .map(SupportedOutputFormats) + d.supported_output_configs() + .map(SupportedOutputConfigsInner::$HostVariant) + .map(SupportedOutputConfigs) } )* } } - fn default_input_format(&self) -> Result { + fn default_input_config(&self) -> Result { match self.0 { $( - DeviceInner::$HostVariant(ref d) => d.default_input_format(), + DeviceInner::$HostVariant(ref d) => d.default_input_config(), )* } } - fn default_output_format(&self) -> Result { + fn default_output_config(&self) -> Result { match self.0 { $( - DeviceInner::$HostVariant(ref d) => d.default_output_format(), + DeviceInner::$HostVariant(ref d) => d.default_output_config(), )* } } fn build_input_stream_raw( &self, - format: &crate::Format, + config: &crate::StreamConfig, + sample_format: crate::SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -267,7 +268,13 @@ macro_rules! impl_platform_host { { match self.0 { $( - DeviceInner::$HostVariant(ref d) => d.build_input_stream_raw(format, data_callback, error_callback) + DeviceInner::$HostVariant(ref d) => d + .build_input_stream_raw( + config, + sample_format, + data_callback, + error_callback, + ) .map(StreamInner::$HostVariant) .map(Stream::from), )* @@ -276,7 +283,8 @@ macro_rules! impl_platform_host { fn build_output_stream_raw( &self, - format: &crate::Format, + config: &crate::StreamConfig, + sample_format: crate::SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -286,7 +294,13 @@ macro_rules! impl_platform_host { { match self.0 { $( - DeviceInner::$HostVariant(ref d) => d.build_output_stream_raw(format, data_callback, error_callback) + DeviceInner::$HostVariant(ref d) => d + .build_output_stream_raw( + config, + sample_format, + data_callback, + error_callback, + ) .map(StreamInner::$HostVariant) .map(Stream::from), )* @@ -436,8 +450,8 @@ macro_rules! impl_platform_host { mod platform_impl { pub use crate::host::alsa::{ Device as AlsaDevice, Devices as AlsaDevices, Host as AlsaHost, Stream as AlsaStream, - SupportedInputFormats as AlsaSupportedInputFormats, - SupportedOutputFormats as AlsaSupportedOutputFormats, + SupportedInputConfigs as AlsaSupportedInputConfigs, + SupportedOutputConfigs as AlsaSupportedOutputConfigs, }; impl_platform_host!(Alsa alsa "ALSA"); @@ -454,8 +468,8 @@ mod platform_impl { mod platform_impl { pub use crate::host::coreaudio::{ Device as CoreAudioDevice, Devices as CoreAudioDevices, Host as CoreAudioHost, - Stream as CoreAudioStream, SupportedInputFormats as CoreAudioSupportedInputFormats, - SupportedOutputFormats as CoreAudioSupportedOutputFormats, + Stream as CoreAudioStream, SupportedInputConfigs as CoreAudioSupportedInputConfigs, + SupportedOutputConfigs as CoreAudioSupportedOutputConfigs, }; impl_platform_host!(CoreAudio coreaudio "CoreAudio"); @@ -472,8 +486,8 @@ mod platform_impl { mod platform_impl { pub use crate::host::emscripten::{ Device as EmscriptenDevice, Devices as EmscriptenDevices, Host as EmscriptenHost, - Stream as EmscriptenStream, SupportedInputFormats as EmscriptenSupportedInputFormats, - SupportedOutputFormats as EmscriptenSupportedOutputFormats, + Stream as EmscriptenStream, SupportedInputConfigs as EmscriptenSupportedInputConfigs, + SupportedOutputConfigs as EmscriptenSupportedOutputConfigs, }; impl_platform_host!(Emscripten emscripten "Emscripten"); @@ -491,13 +505,13 @@ mod platform_impl { #[cfg(feature = "asio")] pub use crate::host::asio::{ Device as AsioDevice, Devices as AsioDevices, Host as AsioHost, Stream as AsioStream, - SupportedInputFormats as AsioSupportedInputFormats, - SupportedOutputFormats as AsioSupportedOutputFormats, + SupportedInputConfigs as AsioSupportedInputConfigs, + SupportedOutputConfigs as AsioSupportedOutputConfigs, }; pub use crate::host::wasapi::{ Device as WasapiDevice, Devices as WasapiDevices, Host as WasapiHost, - Stream as WasapiStream, SupportedInputFormats as WasapiSupportedInputFormats, - SupportedOutputFormats as WasapiSupportedOutputFormats, + Stream as WasapiStream, SupportedInputConfigs as WasapiSupportedInputConfigs, + SupportedOutputConfigs as WasapiSupportedOutputConfigs, }; #[cfg(feature = "asio")] @@ -526,8 +540,8 @@ mod platform_impl { mod platform_impl { pub use crate::host::null::{ Device as NullDevice, Devices as NullDevices, EventLoop as NullEventLoop, Host as NullHost, - StreamId as NullStreamId, SupportedInputFormats as NullSupportedInputFormats, - SupportedOutputFormats as NullSupportedOutputFormats, + StreamId as NullStreamId, SupportedInputConfigs as NullSupportedInputConfigs, + SupportedOutputConfigs as NullSupportedOutputConfigs, }; impl_platform_host!(Null null "Null"); diff --git a/src/traits.rs b/src/traits.rs index 7ab61b7..ef94aa7 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,9 +1,9 @@ //! The suite of traits allowing CPAL to abstract over hosts, devices, event loops and stream IDs. use { - BuildStreamError, Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - InputDevices, OutputDevices, PauseStreamError, PlayStreamError, Sample, Shape, StreamError, - SupportedFormat, SupportedFormatsError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, InputDevices, + OutputDevices, PauseStreamError, PlayStreamError, Sample, SampleFormat, StreamConfig, + StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; /// A **Host** provides access to the available audio devices on the system. @@ -56,7 +56,7 @@ pub trait HostTrait { fn input_devices(&self) -> Result, DevicesError> { fn supports_input(device: &D) -> bool { device - .supported_input_formats() + .supported_input_configs() .map(|mut iter| iter.next().is_some()) .unwrap_or(false) } @@ -70,7 +70,7 @@ pub trait HostTrait { fn output_devices(&self) -> Result, DevicesError> { fn supports_output(device: &D) -> bool { device - .supported_output_formats() + .supported_output_configs() .map(|mut iter| iter.next().is_some()) .unwrap_or(false) } @@ -84,9 +84,9 @@ pub trait HostTrait { /// methods that involve a device return a `Result` allowing the user to handle this case. pub trait DeviceTrait { /// The iterator type yielding supported input stream formats. - type SupportedInputFormats: Iterator; + type SupportedInputConfigs: Iterator; /// The iterator type yielding supported output stream formats. - type SupportedOutputFormats: Iterator; + type SupportedOutputConfigs: Iterator; /// The stream type created by `build_input_stream_raw` and `build_output_stream_raw`. type Stream: StreamTrait; @@ -96,26 +96,27 @@ pub trait DeviceTrait { /// An iterator yielding formats that are supported by the backend. /// /// Can return an error if the device is no longer valid (eg. it has been disconnected). - fn supported_input_formats(&self) - -> Result; + fn supported_input_configs( + &self, + ) -> Result; /// An iterator yielding output stream formats that are supported by the device. /// /// Can return an error if the device is no longer valid (eg. it has been disconnected). - fn supported_output_formats( + fn supported_output_configs( &self, - ) -> Result; + ) -> Result; /// The default input stream format for the device. - fn default_input_format(&self) -> Result; + fn default_input_config(&self) -> Result; /// The default output stream format for the device. - fn default_output_format(&self) -> Result; + fn default_output_config(&self) -> Result; /// Create an input stream. fn build_input_stream( &self, - shape: &Shape, + config: &StreamConfig, mut data_callback: D, error_callback: E, ) -> Result @@ -125,7 +126,8 @@ pub trait DeviceTrait { E: FnMut(StreamError) + Send + 'static, { self.build_input_stream_raw( - &Format::with_shape(shape, T::FORMAT), + config, + T::FORMAT, move |data| { data_callback( data.as_slice() @@ -139,7 +141,7 @@ pub trait DeviceTrait { /// Create an output stream. fn build_output_stream( &self, - shape: &Shape, + config: &StreamConfig, mut data_callback: D, error_callback: E, ) -> Result @@ -149,7 +151,8 @@ pub trait DeviceTrait { E: FnMut(StreamError) + Send + 'static, { self.build_output_stream_raw( - &Format::with_shape(shape, T::FORMAT), + config, + T::FORMAT, move |data| { data_callback( data.as_slice_mut() @@ -163,7 +166,8 @@ pub trait DeviceTrait { /// Create a dynamically typed input stream. fn build_input_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result @@ -174,7 +178,8 @@ pub trait DeviceTrait { /// Create a dynamically typed output stream. fn build_output_stream_raw( &self, - format: &Format, + config: &StreamConfig, + sample_format: SampleFormat, data_callback: D, error_callback: E, ) -> Result