diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 6896b60..eee5c8b 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -235,6 +235,9 @@ struct BufferSizes { grans: c_long, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CallbackId(usize); + lazy_static! { /// A global way to access all the callbacks. /// @@ -244,7 +247,7 @@ lazy_static! { /// Options are used so that when a callback is removed we don't change the Vec indices. /// /// The indices are how we match a callback with a stream. - static ref BUFFER_CALLBACK: Mutex>> = Mutex::new(Vec::new()); + static ref BUFFER_CALLBACK: Mutex> = Mutex::new(Vec::new()); } impl Asio { @@ -589,12 +592,26 @@ impl Driver { /// Adds a callback to the list of active callbacks. /// /// The given function receives the index of the buffer currently ready for processing. - pub fn set_callback(&self, callback: F) + /// + /// Returns an ID uniquely associated with the given callback so that it may be removed later. + pub fn add_callback(&self, callback: F) -> CallbackId where F: 'static + FnMut(i32) + Send, { let mut bc = BUFFER_CALLBACK.lock().unwrap(); - bc.push(Some(BufferCallback(Box::new(callback)))); + let id = bc + .last() + .map(|&(id, _)| CallbackId(id.0.checked_add(1).expect("stream ID overflowed"))) + .unwrap_or(CallbackId(0)); + let cb = BufferCallback(Box::new(callback)); + bc.push((id, cb)); + id + } + + /// Remove the callback with the given ID. + pub fn remove_callback(&self, rem_id: CallbackId) { + let mut bc = BUFFER_CALLBACK.lock().unwrap(); + bc.retain(|&(id, _)| id != rem_id); } /// Consumes and destroys the `Driver`, stopping the streams if they are running and releasing @@ -863,10 +880,8 @@ extern "C" fn buffer_switch_time_info( ) -> *mut ai::ASIOTime { // This lock is probably unavoidable, but locks in the audio stream are not great. let mut bcs = BUFFER_CALLBACK.lock().unwrap(); - for mut bc in bcs.iter_mut() { - if let Some(ref mut bc) = bc { - bc.run(double_buffer_index); - } + for &mut (_, ref mut bc) in bcs.iter_mut() { + bc.run(double_buffer_index); } time } diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 95025b2..fb18e70 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -6,6 +6,7 @@ use super::Device; use std; use std::sync::atomic::{Ordering, AtomicBool}; use std::sync::Arc; +use super::parking_lot::Mutex; use BackendSpecificError; use BuildStreamError; use Format; @@ -41,6 +42,10 @@ struct SilenceAsioBuffer { pub struct Stream { playing: Arc, + // Ensure the `Driver` does not terminate until the last stream is dropped. + driver: Arc, + asio_streams: Arc>, + callback_id: sys::CallbackId, } impl Stream { @@ -55,8 +60,6 @@ impl Stream { } } -// TODO: drop implementation - impl Device { pub fn build_input_stream( &self, @@ -91,7 +94,7 @@ impl Device { // Set the input callback. // This is most performance critical part of the ASIO bindings. - self.driver.set_callback(move |buffer_index| unsafe { + let callback_id = self.driver.add_callback(move |buffer_index| unsafe { // If not playing return early. if !playing.load(Ordering::SeqCst) { return @@ -217,10 +220,18 @@ impl Device { } }); + let driver = self.driver.clone(); + let asio_streams = self.asio_streams.clone(); + // Immediately start the device? self.driver.start().map_err(build_stream_err)?; - Ok(Stream { playing: stream_playing }) + Ok(Stream { + playing: stream_playing, + driver, + asio_streams, + callback_id, + }) } pub fn build_output_stream( @@ -255,7 +266,7 @@ impl Device { let playing = Arc::clone(&stream_playing); let asio_streams = self.asio_streams.clone(); - self.driver.set_callback(move |buffer_index| unsafe { + let callback_id = self.driver.add_callback(move |buffer_index| unsafe { // If not playing, return early. if !playing.load(Ordering::SeqCst) { return @@ -421,10 +432,18 @@ impl Device { } }); + let driver = self.driver.clone(); + let asio_streams = self.asio_streams.clone(); + // Immediately start the device? self.driver.start().map_err(build_stream_err)?; - Ok(Stream { playing: stream_playing }) + Ok(Stream { + playing: stream_playing, + driver, + asio_streams, + callback_id, + }) } /// Create a new CPAL Input Stream. @@ -508,6 +527,12 @@ impl Device { } } +impl Drop for Stream { + fn drop(&mut self) { + self.driver.remove_callback(self.callback_id); + } +} + impl Silence for i16 { const SILENCE: Self = 0; }