diff --git a/Cargo.toml b/Cargo.toml index aa1fe22..7fc69ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ ringbuf = "0.1.6" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["audiosessiontypes", "audioclient", "coml2api", "combaseapi", "debug", "devpkey", "handleapi", "ksmedia", "mmdeviceapi", "objbase", "std", "synchapi", "winbase", "winuser"] } asio-sys = { version = "0.1", path = "asio-sys", optional = true } +parking_lot = "0.9" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies] alsa-sys = { version = "0.1", path = "alsa-sys" } diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index f1b69f1..4025a74 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -3,7 +3,7 @@ pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; use std::hash::{Hash, Hasher}; -use std::sync::{Mutex, Arc}; +use std::sync::{Arc}; use BackendSpecificError; use DefaultFormatError; use DeviceNameError; @@ -14,6 +14,7 @@ use SampleRate; use SupportedFormat; use SupportedFormatsError; use super::sys; +use super::parking_lot::Mutex; /// A ASIO Device pub struct Device { @@ -23,7 +24,7 @@ pub struct Device { // Input and/or Output stream. // An driver can only have one of each. // They need to be created at the same time. - pub asio_streams: Arc>>, + pub asio_streams: Arc>, } /// All available devices. @@ -154,7 +155,10 @@ impl Iterator for Devices { Some(name) => match self.asio.load_driver(&name) { Ok(driver) => { let driver = Arc::new(driver); - let asio_streams = Arc::new(Mutex::new(None)); + let asio_streams = Arc::new(Mutex::new(sys::AsioStreams { + input: None, + output: None, + })); return Some(Device { driver, asio_streams }); } Err(_) => continue, diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index e47b81e..f4ac830 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -1,4 +1,5 @@ extern crate asio_sys as sys; +extern crate parking_lot; use { BuildStreamError, diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index dbd99e5..95025b2 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -78,8 +78,8 @@ impl Device { } let num_channels = format.channels.clone(); - let asio_stream = self.get_or_create_input_stream(format)?; - let cpal_num_samples = asio_stream.buffer_size as usize * num_channels as usize; + let buffer_size = self.get_or_create_input_stream(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(); @@ -87,6 +87,7 @@ impl Device { let stream_playing = Arc::new(AtomicBool::new(false)); let playing = Arc::clone(&stream_playing); + let asio_streams = self.asio_streams.clone(); // Set the input callback. // This is most performance critical part of the ASIO bindings. @@ -96,6 +97,13 @@ impl Device { return } + // There is 0% chance of lock contention the host only locks when recreating streams. + let stream_lock = asio_streams.lock(); + let ref asio_stream = match stream_lock.input { + Some(ref asio_stream) => asio_stream, + None => return, + }; + /// 1. Write from the ASIO buffer to the interleaved CPAL buffer. /// 2. Deliver the CPAL buffer to the user callback. unsafe fn process_input_callback( @@ -134,7 +142,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, from_le, std::convert::identity::, @@ -144,7 +152,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, from_be, std::convert::identity::, @@ -158,7 +166,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, std::convert::identity::, @@ -172,7 +180,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, from_le, |s| (s >> 16) as i16, @@ -182,7 +190,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, from_be, |s| (s >> 16) as i16, @@ -195,7 +203,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, |s| s as f32, @@ -235,8 +243,8 @@ impl Device { } let num_channels = format.channels.clone(); - let asio_stream = self.get_or_create_output_stream(format)?; - let cpal_num_samples = asio_stream.buffer_size as usize * num_channels as usize; + let buffer_size = self.get_or_create_output_stream(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(); @@ -245,6 +253,7 @@ impl Device { let stream_playing = Arc::new(AtomicBool::new(false)); let playing = Arc::clone(&stream_playing); + let asio_streams = self.asio_streams.clone(); self.driver.set_callback(move |buffer_index| unsafe { // If not playing, return early. @@ -252,6 +261,13 @@ impl Device { return } + // There is 0% chance of lock contention the host only locks when recreating streams. + let stream_lock = asio_streams.lock(); + let ref asio_stream = match stream_lock.output { + Some(ref asio_stream) => asio_stream, + None => return, + }; + // Silence the ASIO buffer that is about to be used. // // This checks if any other callbacks have already silenced the buffer associated with @@ -325,7 +341,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, to_le, @@ -336,7 +352,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, to_be, @@ -351,7 +367,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, std::convert::identity::, @@ -366,7 +382,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, |s| (s as i32) << 16, to_le, @@ -377,7 +393,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, |s| (s as i32) << 16, to_be, @@ -391,7 +407,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, |s| s as f64, std::convert::identity::, @@ -419,7 +435,7 @@ impl Device { fn get_or_create_input_stream( &self, format: &Format, - ) -> Result { + ) -> Result { match self.default_input_format() { Ok(f) => { let num_asio_channels = f.channels; @@ -428,27 +444,26 @@ impl Device { Err(_) => Err(BuildStreamError::FormatNotSupported), }?; let num_channels = format.channels as usize; - let ref mut streams = *self.asio_streams.lock().unwrap(); - match streams { - Some(streams) => match streams.input.take() { - Some(input) => Ok(input), - None => { - println!("ASIO streams have been already created"); - Err(BuildStreamError::DeviceNotAvailable) - } - }, + let ref mut streams = *self.asio_streams.lock(); + // Either create a stream if thers none or had back the + // size of the current one. + match streams.input { + Some(ref input) => Ok(input.buffer_size as usize), None => { - match self.driver.prepare_input_stream(None, num_channels) { - Ok(mut new_streams) => { - let input = new_streams.input.take().expect("missing input stream"); - *streams = Some(new_streams); - Ok(input) - } - Err(e) => { + let output = streams.output.take(); + self.driver + .prepare_input_stream(output, num_channels) + .map(|new_streams| { + let bs = match new_streams.input { + Some(ref inp) => inp.buffer_size as usize, + None => unreachable!(), + }; + *streams = new_streams; + bs + }).map_err(|ref e| { println!("Error preparing stream: {}", e); - Err(BuildStreamError::DeviceNotAvailable) - } - } + BuildStreamError::DeviceNotAvailable + }) } } } @@ -459,7 +474,7 @@ impl Device { fn get_or_create_output_stream( &self, format: &Format, - ) -> Result { + ) -> Result { match self.default_output_format() { Ok(f) => { let num_asio_channels = f.channels; @@ -468,27 +483,26 @@ impl Device { Err(_) => Err(BuildStreamError::FormatNotSupported), }?; let num_channels = format.channels as usize; - let ref mut streams = *self.asio_streams.lock().unwrap(); - match streams { - Some(streams) => match streams.output.take() { - Some(output) => Ok(output), - None => { - println!("ASIO streams have been already created"); - Err(BuildStreamError::DeviceNotAvailable) - } - }, + let ref mut streams = *self.asio_streams.lock(); + // Either create a stream if thers none or had back the + // size of the current one. + match streams.output { + Some(ref output) => Ok(output.buffer_size as usize), None => { - match self.driver.prepare_output_stream(None, num_channels) { - Ok(mut new_streams) => { - let output = new_streams.output.take().expect("missing output stream"); - *streams = Some(new_streams); - Ok(output) - } - Err(e) => { + let output = streams.output.take(); + self.driver + .prepare_output_stream(output, num_channels) + .map(|new_streams| { + let bs = match new_streams.output { + Some(ref out) => out.buffer_size as usize, + None => unreachable!(), + }; + *streams = new_streams; + bs + }).map_err(|ref e| { println!("Error preparing stream: {}", e); - Err(BuildStreamError::DeviceNotAvailable) - } - } + BuildStreamError::DeviceNotAvailable + }) } } }