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:
parent
972cce0f8c
commit
39ade49347
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue