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:
parent
8b4ebeffff
commit
c432f2b18d
|
@ -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.
|
||||||
return Err(LoadDriverError::DriverAlreadyExists);
|
if let Some(driver) = self.loaded_driver() {
|
||||||
|
if driver.name() == driver_name {
|
||||||
|
return Ok(driver);
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue