Update asio-sys to allow for having multiple handles to the same driver

ASIO has a limitation where it only supports loading a single audio
driver at a time. This fixes a common error where CPAL users would
request both the default input device and output device in separate
`load_driver` calls. Now, `load_driver` will return another handle to
the existing driver if the existing driver has the same name.
This commit is contained in:
mitchmindtree 2019-07-01 16:13:14 +02:00
parent 8b4ebeffff
commit c432f2b18d
2 changed files with 130 additions and 69 deletions

View File

@ -7,9 +7,7 @@ use self::errors::{AsioError, AsioErrorWrapper, LoadDriverError};
use std::ffi::CStr; use std::ffi::CStr;
use std::ffi::CString; use std::ffi::CString;
use std::os::raw::{c_char, c_double, c_long, c_void}; use std::os::raw::{c_char, c_double, c_long, c_void};
use std::sync::atomic::{self, AtomicBool}; use std::sync::{Arc, Mutex, Weak};
use std::sync::{Arc, Mutex};
use std;
// Bindings import // Bindings import
use self::asio_import as ai; use self::asio_import as ai;
@ -22,21 +20,44 @@ pub struct Asio {
// Keeps track of whether or not a driver is already loaded. // Keeps track of whether or not a driver is already loaded.
// //
// This is necessary as ASIO only supports one `Driver` at a time. // This is necessary as ASIO only supports one `Driver` at a time.
driver_loaded: Arc<AtomicBool>, loaded_driver: Mutex<Weak<DriverInner>>,
} }
/// A handle to a single ASIO driver. /// A handle to a single ASIO driver.
/// ///
/// Creating an instance of this type loads and initialises the driver. /// Creating an instance of this type loads and initialises the driver.
/// ///
/// Dropping the instance will dispose of any resources and de-initialise the driver. /// Dropping all `Driver` instances will automatically dispose of any resources and de-initialise
#[derive(Debug)] /// the driver.
#[derive(Clone, Debug)]
pub struct Driver { pub struct Driver {
inner: Arc<DriverInner>,
}
// Contains the state associated with a `Driver`.
//
// This state may be shared between multiple `Driver` handles representing the same underlying
// driver. Only when the last `Driver` is dropped will the `Drop` implementation for this type run
// and the necessary driver resources will be de-allocated and unloaded.
//
// The same could be achieved by returning an `Arc<Driver>` from the `Host::load_driver` API,
// however the `DriverInner` abstraction is required in order to allow for the `Driver::destroy`
// method to exist safely. By wrapping the `Arc<DriverInner>` in the `Driver` type, we can make
// sure the user doesn't `try_unwrap` the `Arc` and invalidate the `Asio` instance's weak pointer.
// This would allow for instantiation of a separate driver before the existing one is destroyed,
// which is disallowed by ASIO.
#[derive(Debug)]
struct DriverInner {
state: Mutex<DriverState>, state: Mutex<DriverState>,
// A flag that is set to `false` when the `Driver` is dropped. // The unique name associated with this driver.
name: String,
// Track whether or not the driver has been destroyed.
// //
// This lets the `Asio` handle know that a new driver can be loaded. // This allows for the user to manually destroy the driver and handle any errors if they wish.
loaded: Arc<AtomicBool>, //
// In the case that the driver has been manually destroyed this flag will be set to `true`
// indicating to the `drop` implementation that there is nothing to be done.
destroyed: bool,
} }
/// All possible states of an ASIO `Driver` instance. /// All possible states of an ASIO `Driver` instance.
@ -168,8 +189,8 @@ lazy_static! {
impl Asio { impl Asio {
/// Initialise the ASIO API. /// Initialise the ASIO API.
pub fn new() -> Self { pub fn new() -> Self {
let driver_loaded = Arc::new(AtomicBool::new(false)); let loaded_driver = Mutex::new(Weak::new());
Asio { driver_loaded } Asio { loaded_driver }
} }
/// Returns the name for each available driver. /// Returns the name for each available driver.
@ -204,14 +225,18 @@ impl Asio {
} }
} }
/// Whether or not a driver has already been loaded by this process. /// If a driver has already been loaded, this will return that driver.
///
/// Returns `None` if no driver is currently loaded.
/// ///
/// This can be useful to check before calling `load_driver` as ASIO only supports loading a /// This can be useful to check before calling `load_driver` as ASIO only supports loading a
/// single driver at a time. /// single driver at a time.
/// pub fn loaded_driver(&self) -> Option<Driver> {
/// Uses the given atomic ordering to access the atomic boolean used to track driver loading. self.loaded_driver
pub fn is_driver_loaded(&self, ord: atomic::Ordering) -> bool { .lock()
self.driver_loaded.load(ord) .expect("failed to acquire loaded driver lock")
.upgrade()
.map(|inner| Driver { inner })
} }
/// Load a driver from the given name. /// Load a driver from the given name.
@ -221,14 +246,20 @@ impl Asio {
/// ///
/// NOTE: Despite many requests from users, ASIO only supports loading a single driver at a /// NOTE: Despite many requests from users, ASIO only supports loading a single driver at a
/// time. Calling this method while a previously loaded `Driver` instance exists will result in /// time. Calling this method while a previously loaded `Driver` instance exists will result in
/// an error. /// an error. That said, if this method is called with the name of a driver that has already
/// been loaded, that driver will be returned successfully.
pub fn load_driver(&self, driver_name: &str) -> Result<Driver, LoadDriverError> { pub fn load_driver(&self, driver_name: &str) -> Result<Driver, LoadDriverError> {
if self.driver_loaded.load(atomic::Ordering::SeqCst) { // Check whether or not a driver is already loaded.
if let Some(driver) = self.loaded_driver() {
if driver.name() == driver_name {
return Ok(driver);
} else {
return Err(LoadDriverError::DriverAlreadyExists); return Err(LoadDriverError::DriverAlreadyExists);
} }
}
// Make owned CString to send to load driver // Make owned CString to send to load driver
let my_driver_name = CString::new(driver_name) let driver_name_cstring = CString::new(driver_name)
.expect("failed to create `CString` from driver name"); .expect("failed to create `CString` from driver name");
let mut driver_info = ai::ASIODriverInfo { let mut driver_info = ai::ASIODriverInfo {
_bindgen_opaque_blob: [0u32; 43], _bindgen_opaque_blob: [0u32; 43],
@ -236,15 +267,18 @@ impl Asio {
unsafe { unsafe {
// TODO: Check that a driver of the same name does not already exist? // TODO: Check that a driver of the same name does not already exist?
match ai::load_asio_driver(my_driver_name.as_ptr() as *mut i8) { match ai::load_asio_driver(driver_name_cstring.as_ptr() as *mut i8) {
false => Err(LoadDriverError::LoadDriverFailed), false => Err(LoadDriverError::LoadDriverFailed),
true => { true => {
// Initialize ASIO. // Initialize ASIO.
asio_result!(ai::ASIOInit(&mut driver_info))?; asio_result!(ai::ASIOInit(&mut driver_info))?;
self.driver_loaded.store(true, atomic::Ordering::SeqCst);
let loaded = self.driver_loaded.clone();
let state = Mutex::new(DriverState::Initialized); let state = Mutex::new(DriverState::Initialized);
let driver = Driver { state, loaded }; let name = driver_name.to_string();
let destroyed = false;
let inner = Arc::new(DriverInner { name, state, destroyed });
*self.loaded_driver.lock().expect("failed to acquire loaded driver lock") =
Arc::downgrade(&inner);
let driver = Driver { inner };
Ok(driver) Ok(driver)
} }
} }
@ -261,6 +295,11 @@ impl BufferCallback {
} }
impl Driver { impl Driver {
/// The name used to uniquely identify this driver.
pub fn name(&self) -> &str {
&self.inner.name
}
/// Returns the number of input and output channels available on the driver. /// Returns the number of input and output channels available on the driver.
pub fn channels(&self) -> Result<Channels, AsioError> { pub fn channels(&self) -> Result<Channels, AsioError> {
let mut ins: c_long = 0; let mut ins: c_long = 0;
@ -365,10 +404,10 @@ impl Driver {
); );
} }
if let DriverState::Running = self.state() { if let DriverState::Running = self.inner.state() {
self.stop()?; self.stop()?;
} }
if let DriverState::Prepared = self.state() { if let DriverState::Prepared = self.inner.state() {
self.dispose_buffers()?; self.dispose_buffers()?;
} }
@ -379,7 +418,7 @@ impl Driver {
callbacks as *mut _, callbacks as *mut _,
))?; ))?;
} }
self.set_state(DriverState::Prepared); self.inner.set_state(DriverState::Prepared);
Ok(pref_b_size) Ok(pref_b_size)
} }
@ -519,31 +558,13 @@ impl Driver {
self.create_streams(streams) self.create_streams(streams)
} }
fn state(&self) -> DriverState {
*self.state.lock().expect("failed to lock `DriverState`")
}
fn set_state(&self, state: DriverState) {
*self.state.lock().expect("failed to lock `DriverState`") = state;
}
/// Releases buffers allocations. /// Releases buffers allocations.
/// ///
/// This will `stop` the stream if the driver is `Running`. /// This will `stop` the stream if the driver is `Running`.
/// ///
/// No-op if no buffers are allocated. /// No-op if no buffers are allocated.
pub fn dispose_buffers(&self) -> Result<(), AsioError> { pub fn dispose_buffers(&self) -> Result<(), AsioError> {
if let DriverState::Initialized = self.state() { self.inner.dispose_buffers_inner()
return Ok(());
}
if let DriverState::Running = self.state() {
self.stop()?;
}
unsafe {
asio_result!(ai::ASIODisposeBuffers())?;
}
self.set_state(DriverState::Initialized);
Ok(())
} }
/// Starts ASIO streams playing. /// Starts ASIO streams playing.
@ -554,13 +575,13 @@ impl Driver {
/// ///
/// No-op if already `Running`. /// No-op if already `Running`.
pub fn start(&self) -> Result<(), AsioError> { pub fn start(&self) -> Result<(), AsioError> {
if let DriverState::Running = self.state() { if let DriverState::Running = self.inner.state() {
return Ok(()); return Ok(());
} }
unsafe { unsafe {
asio_result!(ai::ASIOStart())?; asio_result!(ai::ASIOStart())?;
} }
self.set_state(DriverState::Running); self.inner.set_state(DriverState::Running);
Ok(()) Ok(())
} }
@ -571,13 +592,7 @@ impl Driver {
/// If the state was `Running` and the stream is stopped successfully, the driver will be in /// If the state was `Running` and the stream is stopped successfully, the driver will be in
/// the `Prepared` state. /// the `Prepared` state.
pub fn stop(&self) -> Result<(), AsioError> { pub fn stop(&self) -> Result<(), AsioError> {
if let DriverState::Running = self.state() { self.inner.stop_inner()
unsafe {
asio_result!(ai::ASIOStop())?;
}
self.set_state(DriverState::Prepared);
}
Ok(())
} }
/// Adds a callback to the list of active callbacks. /// Adds a callback to the list of active callbacks.
@ -593,17 +608,65 @@ impl Driver {
/// 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
/// any associated resources. /// any associated resources.
pub fn destroy(mut self) -> Result<(), AsioError> { ///
self.destroy_inner() /// Returns `Ok(true)` if the driver was successfully destroyed.
///
/// Returns `Ok(false)` if the driver was not destroyed because another handle to the driver
/// still exists.
///
/// Returns `Err` if some switching driver states failed or if ASIO returned an error on exit.
pub fn destroy(self) -> Result<bool, AsioError> {
let Driver { inner } = self;
match Arc::try_unwrap(inner) {
Err(_) => Ok(false),
Ok(mut inner) => {
inner.destroy_inner()?;
Ok(true)
}
}
}
}
impl DriverInner {
fn state(&self) -> DriverState {
*self.state.lock().expect("failed to lock `DriverState`")
}
fn set_state(&self, state: DriverState) {
*self.state.lock().expect("failed to lock `DriverState`") = state;
}
fn stop_inner(&self) -> Result<(), AsioError> {
if let DriverState::Running = self.state() {
unsafe {
asio_result!(ai::ASIOStop())?;
}
self.set_state(DriverState::Prepared);
}
Ok(())
}
fn dispose_buffers_inner(&self) -> Result<(), AsioError> {
if let DriverState::Initialized = self.state() {
return Ok(());
}
if let DriverState::Running = self.state() {
self.stop_inner()?;
}
unsafe {
asio_result!(ai::ASIODisposeBuffers())?;
}
self.set_state(DriverState::Initialized);
Ok(())
} }
fn destroy_inner(&mut self) -> Result<(), AsioError> { fn destroy_inner(&mut self) -> Result<(), AsioError> {
// Drop back through the driver state machine one state at a time. // Drop back through the driver state machine one state at a time.
if let DriverState::Running = self.state() { if let DriverState::Running = self.state() {
self.stop().expect("failed to stop ASIO driver"); self.stop_inner()?;
} }
if let DriverState::Prepared = self.state() { if let DriverState::Prepared = self.state() {
self.dispose_buffers().expect("failed to dispose buffers of ASIO driver"); self.dispose_buffers_inner()?;
} }
unsafe { unsafe {
asio_result!(ai::ASIOExit())?; asio_result!(ai::ASIOExit())?;
@ -615,16 +678,16 @@ impl Driver {
bcs.clear(); bcs.clear();
} }
// Indicate to the // Signal that the driver has been destroyed.
self.loaded.store(false, atomic::Ordering::SeqCst); self.destroyed = true;
Ok(()) Ok(())
} }
} }
impl Drop for Driver { impl Drop for DriverInner {
fn drop(&mut self) { fn drop(&mut self) {
if self.loaded.load(atomic::Ordering::SeqCst) { if self.destroyed {
// We probably shouldn't `panic!` in the destructor? We also shouldn't ignore errors // We probably shouldn't `panic!` in the destructor? We also shouldn't ignore errors
// though either. // though either.
self.destroy_inner().ok(); self.destroy_inner().ok();

View File

@ -20,8 +20,6 @@ use super::sys;
pub struct Device { pub struct Device {
/// The drivers for this device /// The drivers for this device
pub driver: Arc<sys::Driver>, pub driver: Arc<sys::Driver>,
/// The name of this device
pub name: String,
} }
/// All available devices /// All available devices
@ -32,7 +30,7 @@ pub struct Devices {
impl PartialEq for Device { impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.name == other.name self.driver.name() == other.driver.name()
} }
} }
@ -40,13 +38,13 @@ impl Eq for Device {}
impl Hash for Device { impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state); self.driver.name().hash(state);
} }
} }
impl Device { impl Device {
pub fn name(&self) -> Result<String, DeviceNameError> { pub fn name(&self) -> Result<String, DeviceNameError> {
Ok(self.name.clone()) Ok(self.driver.name().to_string())
} }
/// Gets the supported input formats. /// Gets the supported input formats.
@ -148,7 +146,7 @@ impl Iterator for Devices {
loop { loop {
match self.drivers.next() { match self.drivers.next() {
Some(name) => match self.asio.load_driver(&name) { Some(name) => match self.asio.load_driver(&name) {
Ok(driver) => return Some(Device { driver: Arc::new(driver), name }), Ok(driver) => return Some(Device { driver: Arc::new(driver) }),
Err(_) => continue, Err(_) => continue,
} }
None => return None, None => return None,