RAII cleanup wthin the ASIO backend

- Shares the `Device`'s `driver` and `asio_streams` `Arc`s with the
`Stream`s to ensure they remain valid if the `Host` or `Device` are
dropped early.
- Ensures that a stream's callback is removed upon `Drop`.
This commit is contained in:
mitchmindtree 2019-12-15 19:50:09 +01:00
parent 972cce0f8c
commit 39ade49347
2 changed files with 53 additions and 13 deletions

View File

@ -235,6 +235,9 @@ struct BufferSizes {
grans: c_long, grans: c_long,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CallbackId(usize);
lazy_static! { lazy_static! {
/// A global way to access all the callbacks. /// 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. /// 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. /// The indices are how we match a callback with a stream.
static ref BUFFER_CALLBACK: Mutex<Vec<Option<BufferCallback>>> = Mutex::new(Vec::new()); static ref BUFFER_CALLBACK: Mutex<Vec<(CallbackId, BufferCallback)>> = Mutex::new(Vec::new());
} }
impl Asio { impl Asio {
@ -589,12 +592,26 @@ impl Driver {
/// Adds a callback to the list of active callbacks. /// Adds a callback to the list of active callbacks.
/// ///
/// The given function receives the index of the buffer currently ready for processing. /// The given function receives the index of the buffer currently ready for processing.
pub fn set_callback<F>(&self, callback: F) ///
/// Returns an ID uniquely associated with the given callback so that it may be removed later.
pub fn add_callback<F>(&self, callback: F) -> CallbackId
where where
F: 'static + FnMut(i32) + Send, F: 'static + FnMut(i32) + Send,
{ {
let mut bc = BUFFER_CALLBACK.lock().unwrap(); 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 /// Consumes and destroys the `Driver`, stopping the streams if they are running and releasing
@ -863,11 +880,9 @@ extern "C" fn buffer_switch_time_info(
) -> *mut ai::ASIOTime { ) -> *mut ai::ASIOTime {
// This lock is probably unavoidable, but locks in the audio stream are not great. // This lock is probably unavoidable, but locks in the audio stream are not great.
let mut bcs = BUFFER_CALLBACK.lock().unwrap(); let mut bcs = BUFFER_CALLBACK.lock().unwrap();
for mut bc in bcs.iter_mut() { for &mut (_, ref mut bc) in bcs.iter_mut() {
if let Some(ref mut bc) = bc {
bc.run(double_buffer_index); bc.run(double_buffer_index);
} }
}
time time
} }

View File

@ -6,6 +6,7 @@ use super::Device;
use std; use std;
use std::sync::atomic::{Ordering, AtomicBool}; use std::sync::atomic::{Ordering, AtomicBool};
use std::sync::Arc; use std::sync::Arc;
use super::parking_lot::Mutex;
use BackendSpecificError; use BackendSpecificError;
use BuildStreamError; use BuildStreamError;
use Format; use Format;
@ -41,6 +42,10 @@ struct SilenceAsioBuffer {
pub struct Stream { pub struct Stream {
playing: Arc<AtomicBool>, playing: Arc<AtomicBool>,
// Ensure the `Driver` does not terminate until the last stream is dropped.
driver: Arc<sys::Driver>,
asio_streams: Arc<Mutex<sys::AsioStreams>>,
callback_id: sys::CallbackId,
} }
impl Stream { impl Stream {
@ -55,8 +60,6 @@ impl Stream {
} }
} }
// TODO: drop implementation
impl Device { impl Device {
pub fn build_input_stream<D, E>( pub fn build_input_stream<D, E>(
&self, &self,
@ -91,7 +94,7 @@ impl Device {
// Set the input callback. // Set the input callback.
// This is most performance critical part of the ASIO bindings. // 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 not playing return early.
if !playing.load(Ordering::SeqCst) { if !playing.load(Ordering::SeqCst) {
return return
@ -217,10 +220,18 @@ impl Device {
} }
}); });
let driver = self.driver.clone();
let asio_streams = self.asio_streams.clone();
// Immediately start the device? // Immediately start the device?
self.driver.start().map_err(build_stream_err)?; 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<D, E>( pub fn build_output_stream<D, E>(
@ -255,7 +266,7 @@ impl Device {
let playing = Arc::clone(&stream_playing); let playing = Arc::clone(&stream_playing);
let asio_streams = self.asio_streams.clone(); 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 not playing, return early.
if !playing.load(Ordering::SeqCst) { if !playing.load(Ordering::SeqCst) {
return return
@ -421,10 +432,18 @@ impl Device {
} }
}); });
let driver = self.driver.clone();
let asio_streams = self.asio_streams.clone();
// Immediately start the device? // Immediately start the device?
self.driver.start().map_err(build_stream_err)?; 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. /// 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 { impl Silence for i16 {
const SILENCE: Self = 0; const SILENCE: Self = 0;
} }