From 9e660da433762220a62bb3a70404e6cafaa3b8a3 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Fri, 22 May 2020 13:30:13 +0200 Subject: [PATCH] implemented buffersizes for asio --- asio-sys/src/bindings/errors.rs | 3 ++ asio-sys/src/bindings/mod.rs | 51 +++++++++++++++++++++++++++------ examples/beep.rs | 9 ++++-- examples/enumerate.rs | 1 + examples/feedback.rs | 17 +++++------ src/host/alsa/mod.rs | 9 +++--- src/host/asio/device.rs | 35 ++++++++++++++++++---- src/host/asio/stream.rs | 23 +++++++++++---- src/host/coreaudio/mod.rs | 50 ++++++++++++++++---------------- src/host/wasapi/device.rs | 4 ++- src/lib.rs | 5 +--- 11 files changed, 142 insertions(+), 65 deletions(-) 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..6f54980 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,10 @@ 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 +459,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 +481,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 +514,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 +529,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 +539,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, @@ -536,18 +561,22 @@ impl Driver { /// For this reason we take the output stream if it exists. /// /// `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 +588,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/beep.rs b/examples/beep.rs index ec40eda..6312d25 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -4,7 +4,9 @@ extern crate cpal; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; fn main() -> Result<(), anyhow::Error> { - let host = cpal::default_host(); + //let host = cpal::default_host(); + let host = + cpal::host_from_id(cpal::platform::HostId::Asio).expect("failed to initialise ASIO host"); let device = host .default_output_device() .expect("failed to find a default output device"); @@ -23,6 +25,9 @@ fn run(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyh where T: cpal::Sample, { + let mut config: cpal::StreamConfig = config.clone(); + config.buffer_size = cpal::BufferSize::Fixed(256); + let sample_rate = config.sample_rate.0 as f32; let channels = config.channels as usize; @@ -36,7 +41,7 @@ where let err_fn = |err| eprintln!("an error occurred on stream: {}", err); let stream = device.build_output_stream( - config, + &config, move |data: &mut [T], _: &cpal::OutputCallbackInfo| { write_data(data, channels, &mut next_value) }, 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/examples/feedback.rs b/examples/feedback.rs index a623169..1947266 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -16,8 +16,9 @@ use ringbuf::RingBuffer; const LATENCY_MS: f32 = 150.0; fn main() -> Result<(), anyhow::Error> { - let host = cpal::default_host(); - + //let host = cpal::default_host(); + let host = + cpal::host_from_id(cpal::platform::HostId::Asio).expect("failed to initialise ASIO host"); // Default devices. let input_device = host .default_input_device() @@ -30,8 +31,8 @@ fn main() -> Result<(), anyhow::Error> { // We'll try and use the same configuration between streams to keep it simple. let mut config: cpal::StreamConfig = input_device.default_input_config()?.into(); - config.buffer_size = cpal::BufferSize::Fixed(1024); - + //config.buffer_size = cpal::BufferSize::Fixed(1024); + // Create a delay in case the input and output devices aren't synced. 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; @@ -44,10 +45,10 @@ fn main() -> Result<(), anyhow::Error> { for _ in 0..latency_samples { // The ring buffer has twice as much space as necessary to add latency here, // so this should never fail - producer.push(0.0).unwrap(); + producer.push(0).unwrap(); } - let input_data_fn = move |data: &[f32], _: &cpal::InputCallbackInfo| { + let input_data_fn = move |data: &[i16], _: &cpal::InputCallbackInfo| { println!("data len = {}", data.len()); let mut output_fell_behind = false; for &sample in data { @@ -60,14 +61,14 @@ fn main() -> Result<(), anyhow::Error> { } }; - let output_data_fn = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { + let output_data_fn = move |data: &mut [i16], _: &cpal::OutputCallbackInfo| { let mut input_fell_behind = None; for sample in data { *sample = match consumer.pop() { Ok(s) => s, Err(err) => { input_fell_behind = Some(err); - 0.0 + 0 } }; } diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index f2eb07d..3155bc6 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, - BufferSize, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSizeRange, - SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, + BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, + DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, + SupportedBufferSizeRange, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use std::convert::TryInto; use std::sync::Arc; diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index dee4df3..0d5b9ce 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 SupportedBufferSizeRange; 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,12 @@ 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 = SupportedBufferSizeRange { + min: min as u32, + max: max as u32, + requires_power_of_two: false, + }; // 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 +144,7 @@ impl Device { Ok(SupportedStreamConfig { channels, sample_rate, + buffer_size, sample_format, }) } @@ -137,12 +153,19 @@ 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 = SupportedBufferSizeRange { + min: min as u32, + max: max as u32, + requires_power_of_two: false, + }; 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 c8d2e29..5010901 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -5,9 +5,10 @@ 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, - kAudioDevicePropertyBufferFrameSize, kAudioDevicePropertyBufferFrameSizeRange,kAudioDevicePropertyNominalSampleRate, - kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyStreamFormat, + kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyBufferFrameSize, + kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyDeviceNameCFString, + kAudioDevicePropertyNominalSampleRate, kAudioDevicePropertyScopeOutput, + kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyStreamFormat, kAudioFormatFlagIsFloat, kAudioFormatFlagIsPacked, kAudioFormatLinearPCM, kAudioObjectPropertyElementMaster, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput, kAudioObjectPropertyScopeOutput, @@ -20,10 +21,11 @@ use self::coreaudio::sys::{ }; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, - DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSizeRange, - SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, + BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, + DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, + SupportedBufferSizeRange, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use std::cell::RefCell; use std::ffi::CStr; @@ -667,16 +669,14 @@ impl Device { let asbd = asbd_from_config(config, sample_format); audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?; - // Set the buffersize + // Set the buffersize match config.buffer_size { - BufferSize::Fixed(v) => { - audio_unit.set_property( - kAudioDevicePropertyBufferFrameSize, - scope, - element, - Some(&v), - )? - }, + BufferSize::Fixed(v) => audio_unit.set_property( + kAudioDevicePropertyBufferFrameSize, + scope, + element, + Some(&v), + )?, BufferSize::Default => (), } @@ -751,16 +751,14 @@ impl Device { let asbd = asbd_from_config(config, sample_format); audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?; - // Set the buffersize + // Set the buffersize match config.buffer_size { - BufferSize::Fixed(v) => { - audio_unit.set_property( - kAudioDevicePropertyBufferFrameSize, - scope, - element, - Some(&v), - )? - }, + BufferSize::Fixed(v) => audio_unit.set_property( + kAudioDevicePropertyBufferFrameSize, + scope, + element, + Some(&v), + )?, BufferSize::Default => (), } @@ -900,4 +898,4 @@ fn get_io_buffer_frame_size_range( max: buffer_size_range.mMaximum as u32, requires_power_of_two: false, }) -} \ No newline at end of file +} diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 83a9c77..430aa02 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -319,6 +319,7 @@ unsafe fn format_from_waveformatex_ptr( _ => return None, }; let format = SupportedStreamConfig { + buffer_size: unimplemented!(), channels: (*waveformatex_ptr).nChannels as _, sample_rate: SampleRate((*waveformatex_ptr).nSamplesPerSec), sample_format, @@ -526,7 +527,8 @@ 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())); + unimplemented!(); + //supported_formats.push(SupportedStreamConfigRange::from(format.clone())); } Ok(supported_formats.into_iter()) } diff --git a/src/lib.rs b/src/lib.rs index 351b127..ee12f21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -531,10 +531,7 @@ impl SupportedStreamConfigRange { /// /// **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 { + pub fn with_sample_rate(self, sample_rate: SampleRate) -> SupportedStreamConfig { assert!(self.min_sample_rate <= sample_rate && sample_rate <= self.max_sample_rate); SupportedStreamConfig { channels: self.channels,