diff --git a/asio-sys/src/bindings/errors.rs b/asio-sys/src/bindings/errors.rs index 8c1e367..aa78fd2 100644 --- a/asio-sys/src/bindings/errors.rs +++ b/asio-sys/src/bindings/errors.rs @@ -19,6 +19,7 @@ pub enum AsioError { HardwareStuck, NoRate, ASE_NoMemory, + InvalidBufferSize, UnknownError, } @@ -63,6 +64,7 @@ impl fmt::Display for AsioError { "sample clock or rate cannot be determined or is not present" ), AsioError::ASE_NoMemory => write!(f, "not enough memory for completing the request"), + AsioError::InvalidBufferSize => write!(f, "buffersize out of range for device"), AsioError::UnknownError => write!(f, "Error not in SDK"), } } @@ -94,6 +96,7 @@ impl Error for AsioError { AsioError::HardwareStuck => "hardware is not running when sample position is inquired", AsioError::NoRate => "sample clock or rate cannot be determined or is not present", AsioError::ASE_NoMemory => "not enough memory for completing the request", + AsioError::InvalidBufferSize => "buffersize out of range for device", AsioError::UnknownError => "Error not in SDK", } } diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index f0ba084..36b2dee 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -385,6 +385,14 @@ impl Driver { Ok(channel) } + /// Get the min and max supported buffersize of the driver. + pub fn buffersize_range(&self) -> Result<(c_long, c_long), AsioError> { + let buffer_sizes = asio_get_buffer_sizes()?; + let min = buffer_sizes.min; + let max = buffer_sizes.max; + Ok((min, max)) + } + /// Get current sample rate of the driver. pub fn sample_rate(&self) -> Result { let mut rate: c_double = 0.0; @@ -431,8 +439,14 @@ impl Driver { /// /// This will destroy any already allocated buffers. /// - /// The preferred buffer size from ASIO is used. - fn create_buffers(&self, buffer_infos: &mut [AsioBufferInfo]) -> Result { + /// If buffersize is None then the preferred buffer size from ASIO is used, + /// otherwise the desired buffersize is used if the requeted size is within + /// the range of accepted buffersizes for the device. + fn create_buffers( + &self, + buffer_infos: &mut [AsioBufferInfo], + buffer_size: Option, + ) -> Result { let num_channels = buffer_infos.len(); // To pass as ai::ASIOCallbacks @@ -449,6 +463,17 @@ impl Driver { ); } + let buffer_size = match buffer_size { + Some(v) => { + if v <= buffer_sizes.max { + v + } else { + return Err(AsioError::InvalidBufferSize); + } + } + None => buffer_sizes.pref, + }; + // Ensure the driver is in the `Initialized` state. if let DriverState::Running = *state { state.stop()?; @@ -460,23 +485,27 @@ impl Driver { asio_result!(ai::ASIOCreateBuffers( buffer_infos.as_mut_ptr() as *mut _, num_channels as i32, - buffer_sizes.pref, + buffer_size, &mut callbacks as *mut _ as *mut _, ))?; } *state = DriverState::Prepared; - Ok(buffer_sizes.pref) + Ok(buffer_size) } /// Creates the streams. /// + /// `buffer_size` sets the desired buffer_size. If None is passed in, then the + /// default buffersize for the device is used. + /// /// Both input and output streams need to be created together as a single slice of /// `ASIOBufferInfo`. fn create_streams( &self, mut input_buffer_infos: Vec, mut output_buffer_infos: Vec, + buffer_size: Option, ) -> Result { let (input, output) = match ( input_buffer_infos.is_empty(), @@ -489,7 +518,7 @@ impl Driver { let mut all_buffer_infos = input_buffer_infos; all_buffer_infos.append(&mut output_buffer_infos); // Create the buffers. On success, split the output and input again. - let buffer_size = self.create_buffers(&mut all_buffer_infos)?; + let buffer_size = self.create_buffers(&mut all_buffer_infos, buffer_size)?; let output_buffer_infos = all_buffer_infos.split_off(split_point); let input_buffer_infos = all_buffer_infos; let input = Some(AsioStream { @@ -504,7 +533,7 @@ impl Driver { } // Just input (false, true) => { - let buffer_size = self.create_buffers(&mut input_buffer_infos)?; + let buffer_size = self.create_buffers(&mut input_buffer_infos, buffer_size)?; let input = Some(AsioStream { buffer_infos: input_buffer_infos, buffer_size, @@ -514,7 +543,7 @@ impl Driver { } // Just output (true, false) => { - let buffer_size = self.create_buffers(&mut output_buffer_infos)?; + let buffer_size = self.create_buffers(&mut output_buffer_infos, buffer_size)?; let input = None; let output = Some(AsioStream { buffer_infos: output_buffer_infos, @@ -537,17 +566,21 @@ impl Driver { /// /// `num_channels` is the desired number of input channels. /// + /// `buffer_size` sets the desired buffer_size. If None is passed in, then the + /// default buffersize for the device is used. + /// /// This returns a full AsioStreams with both input and output if output was active. pub fn prepare_input_stream( &self, output: Option, num_channels: usize, + buffer_size: Option, ) -> Result { let input_buffer_infos = prepare_buffer_infos(true, num_channels); let output_buffer_infos = output .map(|output| output.buffer_infos) .unwrap_or_else(Vec::new); - self.create_streams(input_buffer_infos, output_buffer_infos) + self.create_streams(input_buffer_infos, output_buffer_infos, buffer_size) } /// Prepare the output stream. @@ -559,17 +592,21 @@ impl Driver { /// /// `num_channels` is the desired number of output channels. /// + /// `buffer_size` sets the desired buffer_size. If None is passed in, then the + /// default buffersize for the device is used. + /// /// This returns a full AsioStreams with both input and output if input was active. pub fn prepare_output_stream( &self, input: Option, num_channels: usize, + buffer_size: Option, ) -> Result { let input_buffer_infos = input .map(|input| input.buffer_infos) .unwrap_or_else(Vec::new); let output_buffer_infos = prepare_buffer_infos(false, num_channels); - self.create_streams(input_buffer_infos, output_buffer_infos) + self.create_streams(input_buffer_infos, output_buffer_infos, buffer_size) } /// Releases buffers allocations. diff --git a/examples/enumerate.rs b/examples/enumerate.rs index d599790..911c646 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -11,6 +11,7 @@ fn main() -> Result<(), anyhow::Error> { for host_id in available_hosts { println!("{}", host_id.name()); let host = cpal::host_from_id(host_id)?; + let default_in = host.default_input_device().map(|e| e.name().unwrap()); let default_out = host.default_output_device().map(|e| e.name().unwrap()); println!(" Default Input Device:\n {:?}", default_in); diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index bce4549..65490d0 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -3,10 +3,11 @@ extern crate libc; use self::alsa::poll::Descriptors; use crate::{ - BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, - DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError, + BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, + DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, + SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use std::convert::TryInto; use std::sync::Arc; @@ -339,6 +340,14 @@ impl Device { }) .collect::>(); + let min_buffer_size = hw_params.get_buffer_size_min()?; + let max_buffer_size = hw_params.get_buffer_size_max()?; + + let buffer_size_range = SupportedBufferSize::Range { + min: min_buffer_size as u32, + max: max_buffer_size as u32, + }; + let mut output = Vec::with_capacity( supported_formats.len() * supported_channels.len() * sample_rates.len(), ); @@ -349,6 +358,7 @@ impl Device { channels: channels.clone(), min_sample_rate: SampleRate(min_rate as u32), max_sample_rate: SampleRate(max_rate as u32), + buffer_size: buffer_size_range.clone(), sample_format: sample_format, }); } @@ -869,7 +879,7 @@ fn set_hw_params_from_format<'a>( config: &StreamConfig, sample_format: SampleFormat, ) -> Result, BackendSpecificError> { - let mut hw_params = alsa::pcm::HwParams::any(pcm_handle)?; + let hw_params = alsa::pcm::HwParams::any(pcm_handle)?; hw_params.set_access(alsa::pcm::Access::RWInterleaved)?; let sample_format = if cfg!(target_endian = "big") { @@ -890,11 +900,14 @@ fn set_hw_params_from_format<'a>( hw_params.set_rate(config.sample_rate.0, alsa::ValueOr::Nearest)?; hw_params.set_channels(config.channels as u32)?; - // If this isn't set manually a overlarge buffer may be used causing audio delay - let mut hw_params_copy = hw_params.clone(); - if let Err(_) = hw_params.set_buffer_time_near(100_000, alsa::ValueOr::Nearest) { - // Swap out the params with errors for a snapshot taken before the error was introduced. - mem::swap(&mut hw_params_copy, &mut hw_params); + match config.buffer_size { + BufferSize::Fixed(v) => hw_params.set_buffer_size(v as i64)?, + BufferSize::Default => { + // These values together represent a moderate latency and wakeup interval. + // Without them we are at the mercy of the device + hw_params.set_period_time_near(25_000, alsa::ValueOr::Nearest)?; + hw_params.set_buffer_time_near(100_000, alsa::ValueOr::Nearest)?; + } } pcm_handle.hw_params(&hw_params)?; diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index dee4df3..1a1a1aa 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -12,6 +12,7 @@ use DeviceNameError; use DevicesError; use SampleFormat; use SampleRate; +use SupportedBufferSize; use SupportedStreamConfig; use SupportedStreamConfigRange; use SupportedStreamConfigsError; @@ -77,9 +78,13 @@ impl Device { continue; } for channels in 1..f.channels + 1 { - f.channels = channels; - f.sample_rate = rate; - supported_configs.push(SupportedStreamConfigRange::from(f.clone())); + supported_configs.push(SupportedStreamConfigRange { + channels, + min_sample_rate: rate, + max_sample_rate: rate, + buffer_size: f.buffer_size.clone(), + sample_format: f.sample_format.clone(), + }) } } Ok(supported_configs.into_iter()) @@ -110,9 +115,13 @@ impl Device { continue; } for channels in 1..f.channels + 1 { - f.channels = channels; - f.sample_rate = rate; - supported_configs.push(SupportedStreamConfigRange::from(f.clone())); + supported_configs.push(SupportedStreamConfigRange { + channels, + min_sample_rate: rate, + max_sample_rate: rate, + buffer_size: f.buffer_size.clone(), + sample_format: f.sample_format.clone(), + }) } } Ok(supported_configs.into_iter()) @@ -122,6 +131,11 @@ impl Device { 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 _); + let (min, max) = self.driver.buffersize_range().map_err(default_config_err)?; + let buffer_size = SupportedBufferSize::Range { + min: min as u32, + max: max as u32, + }; // Map th ASIO sample type to a CPAL sample type let data_type = self.driver.input_data_type().map_err(default_config_err)?; let sample_format = convert_data_type(&data_type) @@ -129,6 +143,7 @@ impl Device { Ok(SupportedStreamConfig { channels, sample_rate, + buffer_size, sample_format, }) } @@ -137,12 +152,18 @@ impl Device { 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 (min, max) = self.driver.buffersize_range().map_err(default_config_err)?; + let buffer_size = SupportedBufferSize::Range { + min: min as u32, + max: max as u32, + }; 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, + buffer_size, sample_format, }) } diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index e8fbc2d..9035045 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -5,9 +5,9 @@ use self::num_traits::PrimInt; use super::parking_lot::Mutex; use super::Device; use crate::{ - BackendSpecificError, BuildStreamError, Data, InputCallbackInfo, OutputCallbackInfo, - PauseStreamError, PlayStreamError, Sample, SampleFormat, StreamConfig, StreamError, - SupportedStreamConfig, + BackendSpecificError, BufferSize, BuildStreamError, Data, InputCallbackInfo, + OutputCallbackInfo, PauseStreamError, PlayStreamError, Sample, SampleFormat, StreamConfig, + StreamError, }; use std; use std::sync::atomic::{AtomicBool, Ordering}; @@ -482,6 +482,12 @@ impl Device { }?; let num_channels = config.channels as usize; let ref mut streams = *self.asio_streams.lock(); + + let buffer_size = match config.buffer_size { + BufferSize::Fixed(v) => Some(v as i32), + BufferSize::Default => None, + }; + // Either create a stream if thers none or had back the // size of the current one. match streams.input { @@ -489,7 +495,7 @@ impl Device { None => { let output = streams.output.take(); self.driver - .prepare_input_stream(output, num_channels) + .prepare_input_stream(output, num_channels, buffer_size) .map(|new_streams| { let bs = match new_streams.input { Some(ref inp) => inp.buffer_size as usize, @@ -523,6 +529,12 @@ impl Device { }?; let num_channels = config.channels as usize; let ref mut streams = *self.asio_streams.lock(); + + let buffer_size = match config.buffer_size { + BufferSize::Fixed(v) => Some(v as i32), + BufferSize::Default => None, + }; + // Either create a stream if thers none or had back the // size of the current one. match streams.output { @@ -530,7 +542,7 @@ impl Device { None => { let output = streams.output.take(); self.driver - .prepare_output_stream(output, num_channels) + .prepare_output_stream(output, num_channels, buffer_size) .map(|new_streams| { let bs = match new_streams.output { Some(ref out) => out.buffer_size as usize, @@ -645,6 +657,7 @@ fn check_config( let StreamConfig { channels, sample_rate, + buffer_size, } = config; // Try and set the sample rate to what the user selected. let sample_rate = sample_rate.0.into(); diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index 970e155..eb74a96 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -5,7 +5,8 @@ use self::core_foundation_sys::string::{CFStringGetCString, CFStringGetCStringPt use self::coreaudio::audio_unit::render_callback::{self, data}; use self::coreaudio::audio_unit::{AudioUnit, Element, Scope}; use self::coreaudio::sys::{ - kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyDeviceNameCFString, + kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyBufferFrameSize, + kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyDeviceNameCFString, kAudioDevicePropertyNominalSampleRate, kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyStreamFormat, kAudioFormatFlagIsFloat, kAudioFormatFlagIsPacked, kAudioFormatLinearPCM, @@ -20,10 +21,11 @@ use self::coreaudio::sys::{ }; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, - DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError, + BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, + DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, + SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use std::cell::RefCell; use std::ffi::CStr; @@ -276,6 +278,9 @@ impl Device { let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _; let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges); + let audio_unit = audio_unit_from_device(self, true)?; + let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?; + // Collect the supported formats for the device. let mut fmts = vec![]; for range in ranges { @@ -283,6 +288,7 @@ impl Device { channels: n_channels as ChannelCount, min_sample_rate: SampleRate(range.mMinimum as _), max_sample_rate: SampleRate(range.mMaximum as _), + buffer_size: buffer_size.clone(), sample_format: sample_format, }; fmts.push(fmt); @@ -374,9 +380,13 @@ impl Device { } }; + let audio_unit = audio_unit_from_device(self, true)?; + let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?; + let config = SupportedStreamConfig { sample_rate: SampleRate(asbd.mSampleRate as _), channels: asbd.mChannelsPerFrame as _, + buffer_size: buffer_size, sample_format: sample_format, }; Ok(config) @@ -426,6 +436,24 @@ impl From for BuildStreamError { } } +impl From for SupportedStreamConfigsError { + fn from(err: coreaudio::Error) -> SupportedStreamConfigsError { + let description = format!("{}", err); + let err = BackendSpecificError { description }; + // Check for possible DeviceNotAvailable variant + SupportedStreamConfigsError::BackendSpecific { err } + } +} + +impl From for DefaultStreamConfigError { + fn from(err: coreaudio::Error) -> DefaultStreamConfigError { + let description = format!("{}", err); + let err = BackendSpecificError { description }; + // Check for possible DeviceNotAvailable variant + DefaultStreamConfigError::BackendSpecific { err } + } +} + // Create a coreaudio AudioStreamBasicDescription from a CPAL Format. fn asbd_from_config( config: &StreamConfig, @@ -656,6 +684,29 @@ impl Device { let asbd = asbd_from_config(config, sample_format); audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?; + // Set the buffersize + match config.buffer_size { + BufferSize::Fixed(v) => { + let buffer_size_range = get_io_buffer_frame_size_range(&audio_unit)?; + match buffer_size_range { + SupportedBufferSize::Range { min, max } => { + if v >= min && v <= max { + audio_unit.set_property( + kAudioDevicePropertyBufferFrameSize, + scope, + element, + Some(&v), + )? + } else { + return Err(BuildStreamError::StreamConfigNotSupported); + } + } + SupportedBufferSize::Unknown => (), + } + } + BufferSize::Default => (), + } + // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. let bytes_per_channel = sample_format.sample_size(); @@ -727,6 +778,29 @@ impl Device { let asbd = asbd_from_config(config, sample_format); audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?; + // Set the buffersize + match config.buffer_size { + BufferSize::Fixed(v) => { + let buffer_size_range = get_io_buffer_frame_size_range(&audio_unit)?; + match buffer_size_range { + SupportedBufferSize::Range { min, max } => { + if v >= min && v <= max { + audio_unit.set_property( + kAudioDevicePropertyBufferFrameSize, + scope, + element, + Some(&v), + )? + } else { + return Err(BuildStreamError::StreamConfigNotSupported); + } + } + SupportedBufferSize::Unknown => (), + } + } + BufferSize::Default => (), + } + // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. let bytes_per_channel = sample_format.sample_size(); @@ -848,3 +922,18 @@ fn check_os_status(os_status: OSStatus) -> Result<(), BackendSpecificError> { } } } + +fn get_io_buffer_frame_size_range( + audio_unit: &AudioUnit, +) -> Result { + let buffer_size_range: AudioValueRange = audio_unit.get_property( + kAudioDevicePropertyBufferFrameSizeRange, + Scope::Global, + Element::Output, + )?; + + Ok(SupportedBufferSize::Range { + min: buffer_size_range.mMinimum as u32, + max: buffer_size_range.mMaximum as u32, + }) +} diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index de5ac0a..dbf5078 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -8,10 +8,10 @@ use stdweb::web::TypedArray; use stdweb::Reference; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, + BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, - StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, - SupportedStreamConfigsError, + SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError, }; use traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -41,6 +41,16 @@ pub struct StreamId(usize); pub type SupportedInputConfigs = ::std::vec::IntoIter; pub type SupportedOutputConfigs = ::std::vec::IntoIter; +const MIN_CHANNELS: u16 = 1; +const MAX_CHANNELS: u16 = 32; +const MIN_SAMPLE_RATE: SampleRate = SampleRate(8_000); +const MAX_SAMPLE_RATE: SampleRate = SampleRate(96_000); +const DEFAULT_SAMPLE_RATE: SampleRate = SampleRate(44_100); +const MIN_BUFFER_SIZE: u32 = 1; +const MAX_BUFFER_SIZE: u32 = std::u32::MAX; +const DEFAULT_BUFFER_SIZE: usize = 2048; +const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; + impl Host { pub fn new() -> Result { stdweb::initialize(); @@ -71,21 +81,20 @@ impl Device { 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 - // According to https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createBuffer - // browsers must support 1 to 32 channels at leats and 8,000 Hz to 96,000 Hz. - // - // 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![SupportedStreamConfigRange { - channels: 2, - min_sample_rate: ::SampleRate(44100), - max_sample_rate: ::SampleRate(44100), - sample_format: ::SampleFormat::F32, - }] - .into_iter()) + let buffer_size = SupportedBufferSize::Range { + min: MIN_BUFFER_SIZE, + max: MAX_BUFFER_SIZE, + }; + let configs: Vec<_> = (MIN_CHANNELS..=MAX_CHANNELS) + .map(|channels| SupportedStreamConfigRange { + channels, + min_sample_rate: MIN_SAMPLE_RATE, + max_sample_rate: MAX_SAMPLE_RATE, + buffer_size: buffer_size.clone(), + sample_format: SUPPORTED_SAMPLE_FORMAT, + }) + .collect(); + Ok(configs.into_iter()) } fn default_input_config(&self) -> Result { @@ -93,12 +102,15 @@ impl Device { } fn default_output_config(&self) -> Result { - // TODO: because it is hard coded, see supported_output_configs. - Ok(SupportedStreamConfig { - channels: 2, - sample_rate: ::SampleRate(44100), - sample_format: ::SampleFormat::F32, - }) + const EXPECT: &str = "expected at least one valid webaudio stream config"; + let mut configs: Vec<_> = self.supported_output_configs().expect(EXPECT).collect(); + configs.sort_by(|a, b| a.cmp_default_heuristics(b)); + let config = configs + .into_iter() + .next() + .expect(EXPECT) + .with_sample_rate(DEFAULT_SAMPLE_RATE); + Ok(config) } } @@ -169,7 +181,7 @@ impl DeviceTrait for Device { fn build_output_stream_raw( &self, - _config: &StreamConfig, + config: &StreamConfig, sample_format: SampleFormat, data_callback: D, error_callback: E, @@ -178,11 +190,20 @@ impl DeviceTrait for Device { D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - assert_eq!( - sample_format, - SampleFormat::F32, - "emscripten backend currently only supports `f32` data", - ); + if !valid_config(config, sample_format) { + return Err(BuildStreamError::StreamConfigNotSupported); + } + + let buffer_size_frames = match config.buffer_size { + BufferSize::Fixed(v) => { + if v == 0 { + return Err(BuildStreamError::StreamConfigNotSupported); + } else { + v as usize + } + } + BufferSize::Default => DEFAULT_BUFFER_SIZE, + }; // Create the stream. let audio_ctxt_ref = js!(return new AudioContext()).into_reference().unwrap(); @@ -199,7 +220,14 @@ impl DeviceTrait for Device { // See also: The call to `set_timeout` at the end of the `audio_callback_fn` which creates // the loop. set_timeout( - || audio_callback_fn::(user_data_ptr as *mut c_void), + || { + audio_callback_fn::( + user_data_ptr as *mut c_void, + config, + sample_format, + buffer_size_frames, + ) + }, 10, ); @@ -223,12 +251,18 @@ impl StreamTrait for Stream { // The first argument of the callback function (a `void*`) is a casted pointer to `self` // and to the `callback` parameter that was passed to `run`. -fn audio_callback_fn(user_data_ptr: *mut c_void) -where +fn audio_callback_fn( + user_data_ptr: *mut c_void, + config: &StreamConfig, + sample_format: SampleFormat, + buffer_size_frames: usize, +) where D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - const SAMPLE_RATE: usize = 44100; + let num_channels = config.channels as usize; + let sample_rate = config.sample_rate.0; + let buffer_size_samples = buffer_size_frames * num_channels; unsafe { let user_data_ptr2 = user_data_ptr as *mut (&Stream, D, E); @@ -237,12 +271,11 @@ where let audio_ctxt = &stream.audio_ctxt_ref; // TODO: We should be re-using a buffer. - let mut temporary_buffer = vec![0.0; SAMPLE_RATE * 2 / 3]; + let mut temporary_buffer = vec![0f32; buffer_size_samples]; { let len = temporary_buffer.len(); let data = temporary_buffer.as_mut_ptr() as *mut (); - let sample_format = SampleFormat::F32; let mut data = Data::from_parts(data, len, sample_format); let now_secs: f64 = js!(@{audio_ctxt}.getOutputTimestamp().currentTime) @@ -253,7 +286,7 @@ where // we estimate based on buffer size instead. Probably should use this, but it's only // supported by firefox (2020-04-28). // let latency_secs: f64 = js!(@{audio_ctxt}.outputLatency).try_into().unwrap(); - let buffer_duration = frames_to_duration(len, SAMPLE_RATE); + let buffer_duration = frames_to_duration(len, sample_rate as usize); let playback = callback .add(buffer_duration) .expect("`playback` occurs beyond representation supported by `StreamInstant`"); @@ -273,19 +306,19 @@ where typed_array }; - let num_channels = 2u32; // TODO: correct value debug_assert_eq!(temporary_buffer.len() % num_channels as usize, 0); js!( var src_buffer = new Float32Array(@{typed_array}.buffer); var context = @{audio_ctxt}; - var buf_len = @{temporary_buffer.len() as u32}; - var num_channels = @{num_channels}; + var buffer_size_frames = @{buffer_size_frames as u32}; + var num_channels = @{num_channels as u32}; + var sample_rate = sample_rate; - var buffer = context.createBuffer(num_channels, buf_len / num_channels, 44100); + var buffer = context.createBuffer(num_channels, buffer_size_frames, sample_rate); for (var channel = 0; channel < num_channels; ++channel) { var buffer_content = buffer.getChannelData(channel); - for (var i = 0; i < buf_len / num_channels; ++i) { + for (var i = 0; i < buffer_size_frames; ++i) { buffer_content[i] = src_buffer[i * num_channels + channel]; } } @@ -299,7 +332,10 @@ where // TODO: handle latency better ; right now we just use setInterval with the amount of sound // data that is in each buffer ; this is obviously bad, and also the schedule is too tight // and there may be underflows - set_timeout(|| audio_callback_fn::(user_data_ptr), 330); + set_timeout( + || audio_callback_fn::(user_data_ptr, config, sample_format, buffer_size_frames), + buffer_size_frames as u32 * 1000 / sample_rate, + ); } } @@ -348,6 +384,15 @@ fn is_webaudio_available() -> bool { .unwrap() } +// Whether or not the given stream configuration is valid for building a stream. +fn valid_config(conf: &StreamConfig, sample_format: SampleFormat) -> bool { + conf.channels <= MAX_CHANNELS + && conf.channels >= MIN_CHANNELS + && conf.sample_rate <= MAX_SAMPLE_RATE + && conf.sample_rate >= MIN_SAMPLE_RATE + && sample_format == SUPPORTED_SAMPLE_FORMAT +} + // Convert the given duration in frames at the given sample rate to a `std::time::Duration`. fn frames_to_duration(frames: usize, rate: usize) -> std::time::Duration { let secsf = frames as f64 / rate as f64; diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 83a9c77..6173b82 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,8 +1,8 @@ use crate::{ - BackendSpecificError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, - InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, - SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, - COMMON_SAMPLE_RATES, + BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError, + DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, + SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, COMMON_SAMPLE_RATES, }; use std; use std::ffi::OsString; @@ -27,6 +27,7 @@ use super::winapi::shared::mmreg; use super::winapi::shared::winerror; use super::winapi::shared::wtypes; use super::winapi::Interface; + // https://msdn.microsoft.com/en-us/library/cc230355.aspx use super::winapi::um::audioclient::{ self, IAudioClient, IID_IAudioClient, AUDCLNT_E_DEVICE_INVALIDATED, @@ -318,9 +319,11 @@ unsafe fn format_from_waveformatex_ptr( // Unknown data format returned by GetMixFormat. _ => return None, }; + let format = SupportedStreamConfig { channels: (*waveformatex_ptr).nChannels as _, sample_rate: SampleRate((*waveformatex_ptr).nSamplesPerSec), + buffer_size: SupportedBufferSize::Unknown, sample_format, }; Some(format) @@ -513,7 +516,7 @@ impl Device { // TODO: Test the different sample formats? // Create the supported formats. - let mut format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0) { + let format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0) { Some(fmt) => fmt, None => { let description = @@ -525,8 +528,13 @@ 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(SupportedStreamConfigRange::from(format.clone())); + supported_formats.push(SupportedStreamConfigRange { + channels: format.channels.clone(), + min_sample_rate: SampleRate(rate as _), + max_sample_rate: SampleRate(rate as _), + buffer_size: format.buffer_size.clone(), + sample_format: format.sample_format.clone(), + }) } Ok(supported_formats.into_iter()) } @@ -639,6 +647,16 @@ impl Device { } }; + match config.buffer_size { + BufferSize::Fixed(_) => { + // TO DO: We need IAudioClient3 to get buffersize ranges first + // Otherwise the supported ranges are unknown. In the mean time + // the smallest buffersize is selected and used. + return Err(BuildStreamError::StreamConfigNotSupported); + } + BufferSize::Default => (), + }; + // Computing the format and initializing the device. let waveformatex = { let format_attempt = config_to_waveformatextensible(config, sample_format) @@ -791,6 +809,16 @@ impl Device { } }; + match config.buffer_size { + BufferSize::Fixed(_) => { + // TO DO: We need IAudioClient3 to get buffersize ranges first + // Otherwise the supported ranges are unknown. In the mean time + // the smallest buffersize is selected and used. + return Err(BuildStreamError::StreamConfigNotSupported); + } + BufferSize::Default => (), + }; + // Computing the format and initializing the device. let waveformatex = { let format_attempt = config_to_waveformatextensible(config, sample_format) @@ -813,6 +841,7 @@ impl Device { &format_attempt.Format, ptr::null(), ); + match check_result(hresult) { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index 94818a9..f58f18c 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -7,10 +7,10 @@ use self::wasm_bindgen::prelude::*; use self::wasm_bindgen::JsCast; use self::web_sys::{AudioContext, AudioContextOptions}; use crate::{ - BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, - DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, - SampleFormat, SampleRate, StreamConfig, StreamError, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError, + BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, + DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, + PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, + SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; use std::ops::DerefMut; use std::sync::{Arc, Mutex, RwLock}; @@ -39,6 +39,9 @@ const MAX_CHANNELS: u16 = 32; const MIN_SAMPLE_RATE: SampleRate = SampleRate(8_000); const MAX_SAMPLE_RATE: SampleRate = SampleRate(96_000); const DEFAULT_SAMPLE_RATE: SampleRate = SampleRate(44_100); +const MIN_BUFFER_SIZE: u32 = 1; +const MAX_BUFFER_SIZE: u32 = std::u32::MAX; +const DEFAULT_BUFFER_SIZE: usize = 2048; const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; impl Host { @@ -93,11 +96,16 @@ impl Device { fn supported_output_configs( &self, ) -> Result { + let buffer_size = SupportedBufferSize::Range { + min: MIN_BUFFER_SIZE, + max: MAX_BUFFER_SIZE, + }; let configs: Vec<_> = (MIN_CHANNELS..=MAX_CHANNELS) .map(|channels| SupportedStreamConfigRange { channels, min_sample_rate: MIN_SAMPLE_RATE, max_sample_rate: MAX_SAMPLE_RATE, + buffer_size: buffer_size.clone(), sample_format: SUPPORTED_SAMPLE_FORMAT, }) .collect(); @@ -190,11 +198,20 @@ impl DeviceTrait for Device { } let n_channels = config.channels as usize; - // Use a buffer period of 1/3s for this early proof of concept. - // TODO: Change this to the requested buffer size when updating for the buffer size API. - let buffer_size_frames = (config.sample_rate.0 as f64 / 3.0).round() as usize; + + let buffer_size_frames = match config.buffer_size { + BufferSize::Fixed(v) => { + if v == 0 { + return Err(BuildStreamError::StreamConfigNotSupported); + } else { + v as usize + } + } + BufferSize::Default => DEFAULT_BUFFER_SIZE, + }; let buffer_size_samples = buffer_size_frames * n_channels; let buffer_time_step_secs = buffer_time_step_secs(buffer_size_frames, config.sample_rate); + let data_callback = Arc::new(Mutex::new(Box::new(data_callback))); // Create the WebAudio stream. diff --git a/src/lib.rs b/src/lib.rs index 1c3e2c8..917cb6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,6 +180,22 @@ pub type ChannelCount = u16; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct SampleRate(pub u32); +/// The desired number of frames for the hardware buffer. +pub type FrameCount = u32; + +/// The buffer size used by the device. +/// +/// Default is used when no specific buffer size is set and uses the default +/// behavior of the given host. Note, the default buffer size may be surprisingly +/// large, leading to latency issues. If low latency is desired, Fixed(BufferSize) +/// should be used in accordance with the SupportedBufferSize range produced by +/// the SupportedStreamConfig API. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum BufferSize { + Default, + Fixed(FrameCount), +} + /// The set of parameters used to describe how to open a stream. /// /// The sample format is omitted in favour of using a sample type. @@ -187,6 +203,19 @@ pub struct SampleRate(pub u32); pub struct StreamConfig { pub channels: ChannelCount, pub sample_rate: SampleRate, + pub buffer_size: BufferSize, +} + +/// Describes the minimum and maximum supported buffer size for the device +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum SupportedBufferSize { + Range { + min: FrameCount, + max: FrameCount, + }, + /// In the case that the platform provides no way of getting the default + /// buffersize before starting a stream. + Unknown, } /// Describes a range of supported stream configurations, retrieved via the @@ -198,6 +227,8 @@ pub struct SupportedStreamConfigRange { pub(crate) min_sample_rate: SampleRate, /// Maximum value for the samples rate of the supported formats. pub(crate) max_sample_rate: SampleRate, + /// Buffersize ranges supported by the device + pub(crate) buffer_size: SupportedBufferSize, /// Type of data expected by the device. pub(crate) sample_format: SampleFormat, } @@ -208,6 +239,7 @@ pub struct SupportedStreamConfigRange { pub struct SupportedStreamConfig { channels: ChannelCount, sample_rate: SampleRate, + buffer_size: SupportedBufferSize, sample_format: SampleFormat, } @@ -289,6 +321,10 @@ impl SupportedStreamConfig { self.sample_rate } + pub fn buffer_size(&self) -> &SupportedBufferSize { + &self.buffer_size + } + pub fn sample_format(&self) -> SampleFormat { self.sample_format } @@ -297,6 +333,7 @@ impl SupportedStreamConfig { StreamConfig { channels: self.channels, sample_rate: self.sample_rate, + buffer_size: BufferSize::Default, } } } @@ -492,11 +529,15 @@ impl SupportedStreamConfigRange { self.max_sample_rate } + pub fn buffer_size(&self) -> &SupportedBufferSize { + &self.buffer_size + } + pub fn sample_format(&self) -> SampleFormat { self.sample_format } - /// Retrieve a `SupportedStreamConfig` with the given sample rate. + /// Retrieve a `SupportedStreamConfig` with the given sample rate and buffer size. /// /// **panic!**s if the given `sample_rate` is outside the range specified within this /// `SupportedStreamConfigRange` instance. @@ -504,8 +545,9 @@ impl SupportedStreamConfigRange { assert!(self.min_sample_rate <= sample_rate && sample_rate <= self.max_sample_rate); SupportedStreamConfig { channels: self.channels, + sample_rate: self.max_sample_rate, sample_format: self.sample_format, - sample_rate, + buffer_size: self.buffer_size, } } @@ -516,6 +558,7 @@ impl SupportedStreamConfigRange { channels: self.channels, sample_rate: self.max_sample_rate, sample_format: self.sample_format, + buffer_size: self.buffer_size, } } @@ -596,18 +639,6 @@ impl From for StreamConfig { } } -impl From for SupportedStreamConfigRange { - #[inline] - fn from(format: SupportedStreamConfig) -> SupportedStreamConfigRange { - SupportedStreamConfigRange { - channels: format.channels, - min_sample_rate: format.sample_rate, - max_sample_rate: format.sample_rate, - sample_format: format.sample_format, - } - } -} - // If a backend does not provide an API for retrieving supported formats, we query it with a bunch // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. //