Bug 1530715 - P24: Create an AggregateDevice module. r=padenot

Using an aggregate-device struct to operate all the settings for the
aggregate device is a way to make the code clearer. In addition, we can
avoid calling some aggregate-device APIs by borrowing the
AudioUnitStream as a mutable. It will help to avoid potential
borrowing-twice issues in the later mutex replacement.

Differential Revision: https://phabricator.services.mozilla.com/D34057

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Chun-Min Chang 2019-07-10 08:06:54 +00:00
Родитель 4c2db1a9e9
Коммит 3aa1456189
3 изменённых файлов: 656 добавлений и 716 удалений

Просмотреть файл

@ -3,4 +3,4 @@ git repository using the update.sh script.
The cubeb-coreaudio-rs git repository is: https://github.com/ChunMinChang/cubeb-coreaudio-rs
The git commit ID used was 883d9919065abde325502a65ae265998ed046b91 (2019-06-21 15:51:40 -0700)
The git commit ID used was c7f9d00909ef52b4390d70c81d3b36e8001b2386 (2019-06-21 15:51:40 -0700)

Просмотреть файл

@ -0,0 +1,630 @@
use super::*;
use std::time::{SystemTime, UNIX_EPOCH};
const APPLE_EVENT_TIMEOUT: OSStatus = -1712;
#[derive(Debug)]
pub struct AggregateDevice {
plugin_id: AudioObjectID,
device_id: AudioObjectID,
input_id: AudioObjectID,
output_id: AudioObjectID,
}
impl AggregateDevice {
// Aggregate Device is a virtual audio interface which utilizes inputs and outputs
// of one or more physical audio interfaces. It is possible to use the clock of
// one of the devices as a master clock for all the combined devices and enable
// drift compensation for the devices that are not designated clock master.
//
// Creating a new aggregate device programmatically requires [0][1]:
// 1. Locate the base plug-in ("com.apple.audio.CoreAudio")
// 2. Create a dictionary that describes the aggregate device
// (don't add sub-devices in that step, prone to fail [0])
// 3. Ask the base plug-in to create the aggregate device (blank)
// 4. Add the array of sub-devices.
// 5. Set the master device (1st output device in our case)
// 6. Enable drift compensation for the non-master devices
//
// [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html
// [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html
// [2] CoreAudio.framework/Headers/AudioHardware.h
pub fn new(
input_id: AudioObjectID,
output_id: AudioObjectID,
) -> std::result::Result<Self, OSStatus> {
let plugin_id = Self::get_system_plugin_id()?;
let device_id = Self::create_blank_device_sync(plugin_id)?;
Self::set_sub_devices_sync(device_id, input_id, output_id)?;
Self::set_master_device(device_id)?;
Self::activate_clock_drift_compensation(device_id)?;
Self::workaround_for_airpod(device_id, input_id, output_id)?;
cubeb_log!(
"Add devices input {} and output {} into an aggregate device {}",
input_id,
output_id,
device_id
);
Ok(Self {
plugin_id,
device_id,
input_id,
output_id,
})
}
pub fn get_device_id(&self) -> AudioObjectID {
self.device_id
}
// The following APIs are set to `pub` for testing purpose.
pub fn get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus> {
let address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyPlugInForBundleID,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
let status =
audio_object_get_property_data_size(kAudioObjectSystemObject, &address, &mut size);
if status != NO_ERR {
return Err(status);
}
assert_ne!(size, 0);
let mut plugin_id = kAudioObjectUnknown;
let mut in_bundle_ref = cfstringref_from_static_string("com.apple.audio.CoreAudio");
let mut translation_value = AudioValueTranslation {
mInputData: &mut in_bundle_ref as *mut CFStringRef as *mut c_void,
mInputDataSize: mem::size_of::<CFStringRef>() as u32,
mOutputData: &mut plugin_id as *mut AudioObjectID as *mut c_void,
mOutputDataSize: mem::size_of::<AudioObjectID>() as u32,
};
assert_eq!(size, mem::size_of_val(&translation_value));
let status = audio_object_get_property_data(
kAudioObjectSystemObject,
&address,
&mut size,
&mut translation_value,
);
unsafe {
CFRelease(in_bundle_ref as *const c_void);
}
if status == NO_ERR {
assert_ne!(plugin_id, kAudioObjectUnknown);
Ok(plugin_id)
} else {
Err(status)
}
}
pub fn create_blank_device_sync(
plugin_id: AudioObjectID,
) -> std::result::Result<AudioObjectID, OSStatus> {
let waiting_time = Duration::new(5, 0);
let condvar_pair = Arc::new((Mutex::new(Vec::<AudioObjectID>::new()), Condvar::new()));
let mut cloned_condvar_pair = condvar_pair.clone();
let data_ptr = &mut cloned_condvar_pair as *mut Arc<(Mutex<Vec<AudioObjectID>>, Condvar)>;
assert_eq!(
audio_object_add_property_listener(
kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
devices_changed_callback,
data_ptr as *mut c_void,
),
NO_ERR
);
let _teardown = finally(|| {
assert_eq!(
audio_object_remove_property_listener(
kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
devices_changed_callback,
data_ptr as *mut c_void,
),
NO_ERR
);
});
let device = Self::create_blank_device(plugin_id)?;
// Wait until the aggregate is created.
let &(ref lock, ref cvar) = &*condvar_pair;
let devices = lock.lock().unwrap();
if !devices.contains(&device) {
let (devs, timeout_res) = cvar.wait_timeout(devices, waiting_time).unwrap();
if timeout_res.timed_out() {
cubeb_log!(
"Time out for waiting the creation of aggregate device {}!",
device
);
}
if !devs.contains(&device) {
return Err(APPLE_EVENT_TIMEOUT);
}
}
extern "C" fn devices_changed_callback(
id: AudioObjectID,
_number_of_addresses: u32,
_addresses: *const AudioObjectPropertyAddress,
data: *mut c_void,
) -> OSStatus {
assert_eq!(id, kAudioObjectSystemObject);
let pair = unsafe { &mut *(data as *mut Arc<(Mutex<Vec<AudioObjectID>>, Condvar)>) };
let &(ref lock, ref cvar) = &**pair;
let mut devices = lock.lock().unwrap();
*devices = audiounit_get_devices();
cvar.notify_one();
NO_ERR
}
Ok(device)
}
pub fn create_blank_device(
plugin_id: AudioObjectID,
) -> std::result::Result<AudioObjectID, OSStatus> {
assert_ne!(plugin_id, kAudioObjectUnknown);
let address = AudioObjectPropertyAddress {
mSelector: kAudioPlugInCreateAggregateDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
let status = audio_object_get_property_data_size(plugin_id, &address, &mut size);
if status != NO_ERR {
return Err(status);
}
assert_ne!(size, 0);
let sys_time = SystemTime::now();
let time_id = sys_time.duration_since(UNIX_EPOCH).unwrap().as_nanos();
let device_name = format!("{}_{}", PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
let device_uid = format!("org.mozilla.{}", device_name);
let mut device_id = kAudioObjectUnknown;
let status = unsafe {
let device_dict = CFMutableDictRef::default();
// Set the name of the device.
let device_name = cfstringref_from_string(&device_name);
device_dict.add_value(
cfstringref_from_static_string(AGGREGATE_DEVICE_NAME_KEY) as *const c_void,
device_name as *const c_void,
);
CFRelease(device_name as *const c_void);
// Set the uid of the device.
let device_uid = cfstringref_from_string(&device_uid);
device_dict.add_value(
cfstringref_from_static_string(AGGREGATE_DEVICE_UID_KEY) as *const c_void,
device_uid as *const c_void,
);
CFRelease(device_uid as *const c_void);
// Make the device private to the process creating it.
let private_value: i32 = 1;
let device_private_key = CFNumberCreate(
kCFAllocatorDefault,
i64::from(kCFNumberIntType),
&private_value as *const i32 as *const c_void,
);
device_dict.add_value(
cfstringref_from_static_string(AGGREGATE_DEVICE_PRIVATE_KEY) as *const c_void,
device_private_key as *const c_void,
);
CFRelease(device_private_key as *const c_void);
// Set the device to a stacked aggregate (i.e. multi-output device).
let stacked_value: i32 = 0; // 1 for normal aggregate device.
let device_stacked_key = CFNumberCreate(
kCFAllocatorDefault,
i64::from(kCFNumberIntType),
&stacked_value as *const i32 as *const c_void,
);
device_dict.add_value(
cfstringref_from_static_string(AGGREGATE_DEVICE_STACKED_KEY) as *const c_void,
device_stacked_key as *const c_void,
);
CFRelease(device_stacked_key as *const c_void);
// This call will fire `audiounit_collection_changed_callback` indirectly!
let status = audio_object_get_property_data_with_qualifier(
plugin_id,
&address,
mem::size_of_val(&device_dict),
&device_dict,
&mut size,
&mut device_id,
);
status
};
if status == NO_ERR {
assert_ne!(device_id, kAudioObjectUnknown);
Ok(device_id)
} else {
Err(status)
}
}
pub fn set_sub_devices_sync(
device_id: AudioDeviceID,
input_id: AudioDeviceID,
output_id: AudioDeviceID,
) -> std::result::Result<(), OSStatus> {
let address = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyFullSubDeviceList,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let waiting_time = Duration::new(5, 0);
let condvar_pair = Arc::new((Mutex::new(AudioObjectID::default()), Condvar::new()));
let mut cloned_condvar_pair = condvar_pair.clone();
let data_ptr = &mut cloned_condvar_pair as *mut Arc<(Mutex<AudioObjectID>, Condvar)>;
assert_eq!(
audio_object_add_property_listener(
device_id,
&address,
devices_changed_callback,
data_ptr as *mut c_void,
),
NO_ERR
);
let _teardown = finally(|| {
assert_eq!(
audio_object_remove_property_listener(
device_id,
&address,
devices_changed_callback,
data_ptr as *mut c_void,
),
NO_ERR
);
});
Self::set_sub_devices(device_id, input_id, output_id)?;
// Wait until the sub devices are added.
let &(ref lock, ref cvar) = &*condvar_pair;
let device = lock.lock().unwrap();
if *device != device_id {
let (dev, timeout_res) = cvar.wait_timeout(device, waiting_time).unwrap();
if timeout_res.timed_out() {
cubeb_log!(
"Time out for waiting for adding devices({}, {}) to aggregate device {}!",
input_id,
output_id,
device_id
);
}
if *dev != device_id {
return Err(APPLE_EVENT_TIMEOUT);
}
}
extern "C" fn devices_changed_callback(
id: AudioObjectID,
_number_of_addresses: u32,
_addresses: *const AudioObjectPropertyAddress,
data: *mut c_void,
) -> OSStatus {
let pair = unsafe { &mut *(data as *mut Arc<(Mutex<AudioObjectID>, Condvar)>) };
let &(ref lock, ref cvar) = &**pair;
let mut device = lock.lock().unwrap();
*device = id;
cvar.notify_one();
NO_ERR
}
Ok(())
}
pub fn set_sub_devices(
device_id: AudioDeviceID,
input_id: AudioDeviceID,
output_id: AudioDeviceID,
) -> std::result::Result<(), OSStatus> {
assert_ne!(device_id, kAudioObjectUnknown);
assert_ne!(input_id, kAudioObjectUnknown);
assert_ne!(output_id, kAudioObjectUnknown);
assert_ne!(input_id, output_id);
let output_sub_devices = audiounit_get_sub_devices(output_id);
let input_sub_devices = audiounit_get_sub_devices(input_id);
unsafe {
let sub_devices = CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks);
// The order of the items in the array is significant and is used to determine the order of the streams
// of the AudioAggregateDevice.
for device in output_sub_devices {
let uid = get_device_name(device);
assert!(!uid.is_null());
CFArrayAppendValue(sub_devices, uid as *const c_void);
CFRelease(uid as *const c_void);
}
for device in input_sub_devices {
let uid = get_device_name(device);
assert!(!uid.is_null());
CFArrayAppendValue(sub_devices, uid as *const c_void);
CFRelease(uid as *const c_void);
}
let address = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyFullSubDeviceList,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let size = mem::size_of::<CFMutableArrayRef>();
let status = audio_object_set_property_data(device_id, &address, size, &sub_devices);
CFRelease(sub_devices as *const c_void);
if status == NO_ERR {
Ok(())
} else {
Err(status)
}
}
}
pub fn set_master_device(device_id: AudioDeviceID) -> std::result::Result<(), OSStatus> {
assert_ne!(device_id, kAudioObjectUnknown);
let address = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyMasterSubDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
// Master become the 1st output sub device
let output_device_id = audiounit_get_default_device_id(DeviceType::OUTPUT);
assert_ne!(output_device_id, kAudioObjectUnknown);
let output_sub_devices = audiounit_get_sub_devices(output_device_id);
assert!(!output_sub_devices.is_empty());
let master_sub_device = get_device_name(output_sub_devices[0]);
let size = mem::size_of::<CFStringRef>();
let status = audio_object_set_property_data(device_id, &address, size, &master_sub_device);
if !master_sub_device.is_null() {
unsafe {
CFRelease(master_sub_device as *const c_void);
}
}
if status == NO_ERR {
Ok(())
} else {
Err(status)
}
}
pub fn activate_clock_drift_compensation(
device_id: AudioObjectID,
) -> std::result::Result<(), OSStatus> {
assert_ne!(device_id, kAudioObjectUnknown);
let address = AudioObjectPropertyAddress {
mSelector: kAudioObjectPropertyOwnedObjects,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let qualifier_data_size = mem::size_of::<AudioObjectID>();
let class_id: AudioClassID = kAudioSubDeviceClassID;
let qualifier_data = &class_id;
let mut size: usize = 0;
let status = audio_object_get_property_data_size_with_qualifier(
device_id,
&address,
qualifier_data_size,
qualifier_data,
&mut size,
);
if status != NO_ERR {
return Err(status);
}
assert!(size > 0);
let subdevices_num = size / mem::size_of::<AudioObjectID>();
assert!(
subdevices_num >= 2,
"We should have at least one input and one output device."
);
let mut sub_devices: Vec<AudioObjectID> = allocate_array(subdevices_num);
let status = audio_object_get_property_data_with_qualifier(
device_id,
&address,
qualifier_data_size,
qualifier_data,
&mut size,
sub_devices.as_mut_ptr(),
);
if status != NO_ERR {
return Err(status);
}
let address = AudioObjectPropertyAddress {
mSelector: kAudioSubDevicePropertyDriftCompensation,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
// Start from the second device since the first is the master clock
for device in &sub_devices[1..] {
let drift_compensation_value: u32 = 1;
let status = audio_object_set_property_data(
*device,
&address,
mem::size_of::<u32>(),
&drift_compensation_value,
);
if status != NO_ERR {
cubeb_log!(
"Failed to set drift compensation for {}. Ignore it.",
device
);
}
}
Ok(())
}
pub fn destroy_device(
plugin_id: AudioObjectID,
mut device_id: AudioDeviceID,
) -> std::result::Result<(), OSStatus> {
assert_ne!(plugin_id, kAudioObjectUnknown);
assert_ne!(device_id, kAudioObjectUnknown);
let address = AudioObjectPropertyAddress {
mSelector: kAudioPlugInDestroyAggregateDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
let status = audio_object_get_property_data_size(plugin_id, &address, &mut size);
if status != NO_ERR {
return Err(status);
}
assert!(size > 0);
let status = audio_object_get_property_data(plugin_id, &address, &mut size, &mut device_id);
if status == NO_ERR {
Ok(())
} else {
Err(status)
}
}
pub fn workaround_for_airpod(
device_id: AudioDeviceID,
input_id: AudioDeviceID,
output_id: AudioDeviceID,
) -> std::result::Result<(), OSStatus> {
assert_ne!(device_id, kAudioObjectUnknown);
assert_ne!(input_id, kAudioObjectUnknown);
assert_ne!(output_id, kAudioObjectUnknown);
assert_ne!(input_id, output_id);
let mut input_device_info = ffi::cubeb_device_info::default();
audiounit_create_device_from_hwdev(&mut input_device_info, input_id, DeviceType::INPUT);
let mut output_device_info = ffi::cubeb_device_info::default();
audiounit_create_device_from_hwdev(&mut output_device_info, output_id, DeviceType::OUTPUT);
let input_name_str = unsafe {
CString::from_raw(input_device_info.friendly_name as *mut c_char)
.into_string()
.expect("Fail to convert input name from CString into String")
};
input_device_info.friendly_name = ptr::null();
let output_name_str = unsafe {
CString::from_raw(output_device_info.friendly_name as *mut c_char)
.into_string()
.expect("Fail to convert output name from CString into String")
};
output_device_info.friendly_name = ptr::null();
let _teardown = finally(|| {
// Retrieve the rest lost memory.
// No need to retrieve the memory of {input,output}_device_info.friendly_name
// since they are already retrieved/retaken above.
assert!(input_device_info.friendly_name.is_null());
audiounit_device_destroy(&mut input_device_info);
assert!(output_device_info.friendly_name.is_null());
audiounit_device_destroy(&mut output_device_info);
});
if input_name_str.contains("AirPods") && output_name_str.contains("AirPods") {
let mut input_min_rate = 0;
let mut input_max_rate = 0;
let mut input_nominal_rate = 0;
audiounit_get_available_samplerate(
input_id,
kAudioObjectPropertyScopeGlobal,
&mut input_min_rate,
&mut input_max_rate,
&mut input_nominal_rate,
);
cubeb_log!(
"Input device {}, name: {}, min: {}, max: {}, nominal rate: {}",
input_id,
input_name_str,
input_min_rate,
input_max_rate,
input_nominal_rate
);
let mut output_min_rate = 0;
let mut output_max_rate = 0;
let mut output_nominal_rate = 0;
audiounit_get_available_samplerate(
output_id,
kAudioObjectPropertyScopeGlobal,
&mut output_min_rate,
&mut output_max_rate,
&mut output_nominal_rate,
);
cubeb_log!(
"Output device {}, name: {}, min: {}, max: {}, nominal rate: {}",
output_id,
output_name_str,
output_min_rate,
output_max_rate,
output_nominal_rate
);
let rate = f64::from(input_nominal_rate);
let addr = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyNominalSampleRate,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let status =
audio_object_set_property_data(device_id, &addr, mem::size_of::<f64>(), &rate);
if status != NO_ERR {
return Err(status);
}
}
Ok(())
}
}
impl Default for AggregateDevice {
fn default() -> Self {
Self {
plugin_id: kAudioObjectUnknown,
device_id: kAudioObjectUnknown,
input_id: kAudioObjectUnknown,
output_id: kAudioObjectUnknown,
}
}
}
impl Drop for AggregateDevice {
fn drop(&mut self) {
if self.plugin_id != kAudioObjectUnknown && self.device_id != kAudioObjectUnknown {
if let Err(r) = Self::destroy_device(self.plugin_id, self.device_id) {
cubeb_log!(
"Failed to destroyed aggregate device {}. Error: {}",
self.device_id,
r
);
} else {
cubeb_log!("Destroyed aggregate device {}", self.device_id);
}
}
}
}

Просмотреть файл

@ -8,12 +8,14 @@
extern crate coreaudio_sys_utils;
extern crate libc;
mod aggregate_device;
mod auto_array;
mod auto_release;
mod owned_critical_section;
mod property_address;
mod utils;
use self::aggregate_device::*;
use self::auto_array::*;
use self::auto_release::*;
use self::coreaudio_sys_utils::aggregate_device::*;
@ -41,7 +43,7 @@ use std::ptr;
use std::slice;
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, AtomicU64, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::time::Duration;
const NO_ERR: OSStatus = 0;
@ -1025,211 +1027,6 @@ fn audiounit_get_sub_devices(device_id: AudioDeviceID) -> Vec<AudioObjectID> {
sub_devices
}
fn audiounit_create_blank_aggregate_device(
plugin_id: &mut AudioObjectID,
aggregate_device_id: &mut AudioDeviceID,
) -> Result<()> {
let address_plugin_bundle_id = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyPlugInForBundleID,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
let mut r = audio_object_get_property_data_size(
kAudioObjectSystemObject,
&address_plugin_bundle_id,
&mut size,
);
if r != NO_ERR {
cubeb_log!(
"AudioObjectGetPropertyDataSize/kAudioHardwarePropertyPlugInForBundleID, rv={}",
r
);
return Err(Error::error());
}
assert_ne!(size, 0);
let mut in_bundle_ref = cfstringref_from_static_string("com.apple.audio.CoreAudio");
let mut translation_value = AudioValueTranslation {
mInputData: &mut in_bundle_ref as *mut CFStringRef as *mut c_void,
mInputDataSize: mem::size_of_val(&in_bundle_ref) as u32,
mOutputData: plugin_id as *mut AudioObjectID as *mut c_void,
mOutputDataSize: mem::size_of_val(plugin_id) as u32,
};
r = audio_object_get_property_data(
kAudioObjectSystemObject,
&address_plugin_bundle_id,
&mut size,
&mut translation_value,
);
if r != NO_ERR {
cubeb_log!(
"AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, rv={}",
r
);
return Err(Error::error());
}
assert_ne!(*plugin_id, kAudioObjectUnknown);
let create_aggregate_device_address = AudioObjectPropertyAddress {
mSelector: kAudioPlugInCreateAggregateDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
r = audio_object_get_property_data_size(
*plugin_id,
&create_aggregate_device_address,
&mut size,
);
if r != NO_ERR {
cubeb_log!(
"AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, rv={}",
r
);
return Err(Error::error());
}
assert_ne!(size, 0);
let sys_time = SystemTime::now();
let time_id = sys_time.duration_since(UNIX_EPOCH).unwrap().as_nanos();
let device_name = format!("{}_{}", PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
let device_uid = format!("org.mozilla.{}", device_name);
unsafe {
let aggregate_device_dict = CFMutableDictRef::default();
let aggregate_device_name = cfstringref_from_string(&device_name);
aggregate_device_dict.add_value(
cfstringref_from_static_string(AGGREGATE_DEVICE_NAME_KEY),
aggregate_device_name,
);
CFRelease(aggregate_device_name as *const c_void);
let aggregate_device_uid = cfstringref_from_string(&device_uid);
aggregate_device_dict.add_value(
cfstringref_from_static_string(AGGREGATE_DEVICE_UID_KEY),
aggregate_device_uid,
);
CFRelease(aggregate_device_uid as *const c_void);
let private_value: i32 = 1;
let aggregate_device_private_key = CFNumberCreate(
kCFAllocatorDefault,
i64::from(kCFNumberIntType),
&private_value as *const i32 as *const c_void,
);
aggregate_device_dict.add_value(
cfstringref_from_static_string(AGGREGATE_DEVICE_PRIVATE_KEY),
aggregate_device_private_key,
);
CFRelease(aggregate_device_private_key as *const c_void);
let stacked_value: i32 = 0;
let aggregate_device_stacked_key = CFNumberCreate(
kCFAllocatorDefault,
i64::from(kCFNumberIntType),
&stacked_value as *const i32 as *const c_void,
);
aggregate_device_dict.add_value(
cfstringref_from_static_string(AGGREGATE_DEVICE_STACKED_KEY),
aggregate_device_stacked_key,
);
CFRelease(aggregate_device_stacked_key as *const c_void);
// This call will fire `audiounit_collection_changed_callback` indirectly!
r = audio_object_get_property_data_with_qualifier(
*plugin_id,
&create_aggregate_device_address,
mem::size_of_val(&aggregate_device_dict),
&aggregate_device_dict,
&mut size,
aggregate_device_id,
);
if r != NO_ERR {
cubeb_log!(
"AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv={}",
r
);
return Err(Error::error());
}
assert_ne!(*aggregate_device_id, kAudioObjectUnknown);
cubeb_log!("New aggregate device {}", *aggregate_device_id);
}
Ok(())
}
fn audiounit_create_blank_aggregate_device_sync(
plugin_id: &mut AudioObjectID,
aggregate_device_id: &mut AudioDeviceID,
) -> Result<()> {
let waiting_time = Duration::new(5, 0);
let condvar_pair = Arc::new((Mutex::new(Vec::<AudioObjectID>::new()), Condvar::new()));
let mut cloned_condvar_pair = condvar_pair.clone();
let data_ptr = &mut cloned_condvar_pair;
assert_eq!(
audio_object_add_property_listener(
kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
devices_changed_callback,
data_ptr,
),
NO_ERR
);
let _teardown = finally(|| {
assert_eq!(
audio_object_remove_property_listener(
kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
devices_changed_callback,
data_ptr,
),
NO_ERR
);
});
audiounit_create_blank_aggregate_device(plugin_id, aggregate_device_id)?;
// Wait until the aggregate is created.
let &(ref lock, ref cvar) = &*condvar_pair;
let devices = lock.lock().unwrap();
if !devices.contains(aggregate_device_id) {
let (devs, timeout_res) = cvar.wait_timeout(devices, waiting_time).unwrap();
if timeout_res.timed_out() {
cubeb_log!(
"Time out for waiting the creation of aggregate device {}!",
aggregate_device_id
);
}
if !devs.contains(aggregate_device_id) {
return Err(Error::device_unavailable());
}
}
extern "C" fn devices_changed_callback(
id: AudioObjectID,
_number_of_addresses: u32,
_addresses: *const AudioObjectPropertyAddress,
data: *mut c_void,
) -> OSStatus {
assert_eq!(id, kAudioObjectSystemObject);
let pair = unsafe { &mut *(data as *mut Arc<(Mutex<Vec<AudioObjectID>>, Condvar)>) };
let &(ref lock, ref cvar) = &**pair;
let mut devices = lock.lock().unwrap();
*devices = audiounit_get_devices();
cvar.notify_one();
NO_ERR
}
Ok(())
}
fn get_device_name(id: AudioDeviceID) -> CFStringRef {
let mut size = mem::size_of::<CFStringRef>();
let mut uiname: CFStringRef = ptr::null();
@ -1246,322 +1043,6 @@ fn get_device_name(id: AudioDeviceID) -> CFStringRef {
}
}
fn audiounit_set_aggregate_sub_device_list(
aggregate_device_id: AudioDeviceID,
input_device_id: AudioDeviceID,
output_device_id: AudioDeviceID,
) -> Result<()> {
assert_ne!(aggregate_device_id, kAudioObjectUnknown);
assert_ne!(input_device_id, kAudioObjectUnknown);
assert_ne!(output_device_id, kAudioObjectUnknown);
assert_ne!(input_device_id, output_device_id);
cubeb_log!(
"Add devices input {} and output {} into aggregate device {}",
input_device_id,
output_device_id,
aggregate_device_id
);
let output_sub_devices = audiounit_get_sub_devices(output_device_id);
let input_sub_devices = audiounit_get_sub_devices(input_device_id);
unsafe {
let aggregate_sub_devices_array =
CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks);
// The order of the items in the array is significant and is used to determine the order of the streams
// of the AudioAggregateDevice.
for device in output_sub_devices {
let strref = get_device_name(device);
if strref.is_null() {
CFRelease(aggregate_sub_devices_array as *const c_void);
return Err(Error::error());
}
CFArrayAppendValue(aggregate_sub_devices_array, strref as *const c_void);
CFRelease(strref as *const c_void);
}
for device in input_sub_devices {
let strref = get_device_name(device);
if strref.is_null() {
CFRelease(aggregate_sub_devices_array as *const c_void);
return Err(Error::error());
}
CFArrayAppendValue(aggregate_sub_devices_array, strref as *const c_void);
CFRelease(strref as *const c_void);
}
let aggregate_sub_device_list = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyFullSubDeviceList,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let size = mem::size_of::<CFMutableArrayRef>();
let rv = audio_object_set_property_data(
aggregate_device_id,
&aggregate_sub_device_list,
size,
&aggregate_sub_devices_array,
);
CFRelease(aggregate_sub_devices_array as *const c_void);
if rv != NO_ERR {
cubeb_log!(
"AudioObjectSetPropertyData/kAudioAggregateDevicePropertyFullSubDeviceList, rv={}",
rv
);
return Err(Error::error());
}
}
Ok(())
}
fn audiounit_set_aggregate_sub_device_list_sync(
aggregate_device_id: AudioDeviceID,
input_device_id: AudioDeviceID,
output_device_id: AudioDeviceID,
) -> Result<()> {
let address = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyFullSubDeviceList,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let waiting_time = Duration::new(5, 0);
let condvar_pair = Arc::new((Mutex::new(AudioObjectID::default()), Condvar::new()));
let mut cloned_condvar_pair = condvar_pair.clone();
let data_ptr = &mut cloned_condvar_pair;
assert_eq!(
audio_object_add_property_listener(
aggregate_device_id,
&address,
devices_changed_callback,
data_ptr,
),
NO_ERR
);
let _teardown = finally(|| {
assert_eq!(
audio_object_remove_property_listener(
aggregate_device_id,
&address,
devices_changed_callback,
data_ptr,
),
NO_ERR
);
});
audiounit_set_aggregate_sub_device_list(
aggregate_device_id,
input_device_id,
output_device_id,
)?;
// Wait until the sub devices are added.
let &(ref lock, ref cvar) = &*condvar_pair;
let device = lock.lock().unwrap();
if *device != aggregate_device_id {
let (dev, timeout_res) = cvar.wait_timeout(device, waiting_time).unwrap();
if timeout_res.timed_out() {
cubeb_log!(
"Time out for waiting the devices({}, {}) adding!",
input_device_id,
output_device_id
);
}
if *dev != aggregate_device_id {
return Err(Error::device_unavailable());
}
}
extern "C" fn devices_changed_callback(
id: AudioObjectID,
_number_of_addresses: u32,
_addresses: *const AudioObjectPropertyAddress,
data: *mut c_void,
) -> OSStatus {
let pair = unsafe { &mut *(data as *mut Arc<(Mutex<AudioObjectID>, Condvar)>) };
let &(ref lock, ref cvar) = &**pair;
let mut device = lock.lock().unwrap();
*device = id;
cvar.notify_one();
NO_ERR
}
Ok(())
}
fn audiounit_set_master_aggregate_device(aggregate_device_id: AudioDeviceID) -> Result<()> {
assert_ne!(aggregate_device_id, kAudioObjectUnknown);
let master_aggregate_sub_device = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyMasterSubDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
// Master become the 1st output sub device
let output_device_id = audiounit_get_default_device_id(DeviceType::OUTPUT);
assert_ne!(output_device_id, kAudioObjectUnknown);
let output_sub_devices = audiounit_get_sub_devices(output_device_id);
assert!(!output_sub_devices.is_empty());
let master_sub_device = get_device_name(output_sub_devices[0]);
let size = mem::size_of::<CFStringRef>();
let rv = audio_object_set_property_data(
aggregate_device_id,
&master_aggregate_sub_device,
size,
&master_sub_device,
);
if !master_sub_device.is_null() {
unsafe {
CFRelease(master_sub_device as *const c_void);
}
}
if rv != NO_ERR {
cubeb_log!(
"AudioObjectSetPropertyData/kAudioAggregateDevicePropertyMasterSubDevice, rv={}",
rv
);
return Err(Error::error());
}
Ok(())
}
fn audiounit_activate_clock_drift_compensation(aggregate_device_id: AudioDeviceID) -> Result<()> {
assert_ne!(aggregate_device_id, kAudioObjectUnknown);
let address_owned = AudioObjectPropertyAddress {
mSelector: kAudioObjectPropertyOwnedObjects,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let qualifier_data_size = mem::size_of::<AudioObjectID>();
let class_id: AudioClassID = kAudioSubDeviceClassID;
let qualifier_data = &class_id;
let mut size: usize = 0;
let mut rv = audio_object_get_property_data_size_with_qualifier(
aggregate_device_id,
&address_owned,
qualifier_data_size,
qualifier_data,
&mut size,
);
if rv != NO_ERR {
cubeb_log!(
"AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, rv={}",
rv
);
return Err(Error::error());
}
assert!(
size > 0,
"The sub devices of the aggregate device have not been added yet."
);
let subdevices_num = size / mem::size_of::<AudioObjectID>();
let mut sub_devices: Vec<AudioObjectID> = allocate_array(subdevices_num);
rv = audio_object_get_property_data_with_qualifier(
aggregate_device_id,
&address_owned,
qualifier_data_size,
qualifier_data,
&mut size,
sub_devices.as_mut_ptr(),
);
if rv != NO_ERR {
cubeb_log!(
"AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv={}",
rv
);
return Err(Error::error());
}
let address_drift = AudioObjectPropertyAddress {
mSelector: kAudioSubDevicePropertyDriftCompensation,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
assert!(
subdevices_num >= 2,
"We should have at least 2 devices for the aggregate device."
);
// Start from the second device since the first is the master clock
for device in &sub_devices[1..] {
let drift_compensation_value: u32 = 1;
rv = audio_object_set_property_data(
*device,
&address_drift,
mem::size_of::<u32>(),
&drift_compensation_value,
);
if rv != NO_ERR {
cubeb_log!(
"AudioObjectSetPropertyData/kAudioSubDevicePropertyDriftCompensation, rv={}",
rv
);
return Ok(());
}
}
Ok(())
}
fn audiounit_destroy_aggregate_device(
plugin_id: AudioObjectID,
aggregate_device_id: &mut AudioDeviceID,
) -> Result<()> {
assert_ne!(plugin_id, kAudioObjectUnknown);
assert_ne!(*aggregate_device_id, kAudioObjectUnknown);
let destroy_aggregate_device_addr = AudioObjectPropertyAddress {
mSelector: kAudioPlugInDestroyAggregateDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
let mut rv =
audio_object_get_property_data_size(plugin_id, &destroy_aggregate_device_addr, &mut size);
if rv != NO_ERR {
cubeb_log!(
"AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, rv={}",
rv
);
return Err(Error::error());
}
assert!(size > 0);
rv = audio_object_get_property_data(
plugin_id,
&destroy_aggregate_device_addr,
&mut size,
aggregate_device_id,
);
if rv != NO_ERR {
cubeb_log!(
"AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv={}",
rv
);
return Err(Error::error());
}
cubeb_log!("Destroyed aggregate device {}", *aggregate_device_id);
*aggregate_device_id = kAudioObjectUnknown;
Ok(())
}
fn create_audiounit(device: &device_info) -> Result<AudioUnit> {
assert!(device
.flags
@ -2961,8 +2442,6 @@ struct AudioUnitStream<'ctx> {
resampler: AutoRelease<ffi::cubeb_resampler>,
// This is true if a device change callback is currently running.
switching_device: AtomicBool,
aggregate_device_id: AudioDeviceID, // the aggregate device id
plugin_id: AudioObjectID, // used to create aggregate device
// Mixer interface
mixer: AutoRelease<ffi::cubeb_mixer>,
// Buffer where remixing/resampling will occur when upmixing is required
@ -2975,6 +2454,7 @@ struct AudioUnitStream<'ctx> {
input_alive_listener: Option<device_property_listener>,
input_source_listener: Option<device_property_listener>,
output_source_listener: Option<device_property_listener>,
aggregate_device: AggregateDevice,
}
impl<'ctx> AudioUnitStream<'ctx> {
@ -3028,8 +2508,6 @@ impl<'ctx> AudioUnitStream<'ctx> {
panning: atomic::Atomic::new(0.0_f32),
resampler: AutoRelease::new(ptr::null_mut(), ffi::cubeb_resampler_destroy),
switching_device: AtomicBool::new(false),
aggregate_device_id: kAudioObjectUnknown,
plugin_id: kAudioObjectUnknown,
mixer: AutoRelease::new(ptr::null_mut(), ffi::cubeb_mixer_destroy),
temp_buffer: Vec::new(),
temp_buffer_size: 0,
@ -3038,6 +2516,7 @@ impl<'ctx> AudioUnitStream<'ctx> {
input_alive_listener: None,
input_source_listener: None,
output_source_listener: None,
aggregate_device: AggregateDevice::default(),
}
}
@ -3526,176 +3005,6 @@ impl<'ctx> AudioUnitStream<'ctx> {
);
}
fn workaround_for_airpod(&self) {
assert_ne!(self.input_device.id, self.output_device.id);
assert_ne!(self.aggregate_device_id, kAudioObjectUnknown);
let mut input_device_info = ffi::cubeb_device_info::default();
assert_ne!(self.input_device.id, kAudioObjectUnknown);
audiounit_create_device_from_hwdev(
&mut input_device_info,
self.input_device.id,
DeviceType::INPUT,
);
let mut output_device_info = ffi::cubeb_device_info::default();
assert_ne!(self.output_device.id, kAudioObjectUnknown);
audiounit_create_device_from_hwdev(
&mut output_device_info,
self.output_device.id,
DeviceType::OUTPUT,
);
let input_name_str = unsafe {
CString::from_raw(input_device_info.friendly_name as *mut c_char)
.into_string()
.expect("Fail to convert input name from CString into String")
};
input_device_info.friendly_name = ptr::null();
let output_name_str = unsafe {
CString::from_raw(output_device_info.friendly_name as *mut c_char)
.into_string()
.expect("Fail to convert output name from CString into String")
};
output_device_info.friendly_name = ptr::null();
if input_name_str.contains("AirPods") && output_name_str.contains("AirPods") {
let mut input_min_rate = 0;
let mut input_max_rate = 0;
let mut input_nominal_rate = 0;
audiounit_get_available_samplerate(
self.input_device.id,
kAudioObjectPropertyScopeGlobal,
&mut input_min_rate,
&mut input_max_rate,
&mut input_nominal_rate,
);
cubeb_log!(
"({:p}) Input device {}, name: {}, min: {}, max: {}, nominal rate: {}",
self as *const AudioUnitStream,
self.input_device.id,
input_name_str,
input_min_rate,
input_max_rate,
input_nominal_rate
);
let mut output_min_rate = 0;
let mut output_max_rate = 0;
let mut output_nominal_rate = 0;
audiounit_get_available_samplerate(
self.output_device.id,
kAudioObjectPropertyScopeGlobal,
&mut output_min_rate,
&mut output_max_rate,
&mut output_nominal_rate,
);
cubeb_log!(
"({:p}) Output device {}, name: {}, min: {}, max: {}, nominal rate: {}",
self as *const AudioUnitStream,
self.output_device.id,
output_name_str,
output_min_rate,
output_max_rate,
output_nominal_rate
);
let rate = f64::from(input_nominal_rate);
let addr = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyNominalSampleRate,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let rv = audio_object_set_property_data(
self.aggregate_device_id,
&addr,
mem::size_of::<f64>(),
&rate,
);
if rv != NO_ERR {
cubeb_log!("Non fatal error, AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, rv={}", rv);
}
}
// Retrieve the rest lost memory.
// No need to retrieve the memory of {input,output}_device_info.friendly_name
// since they are already retrieved/retaken above.
assert!(input_device_info.friendly_name.is_null());
audiounit_device_destroy(&mut input_device_info);
assert!(output_device_info.friendly_name.is_null());
audiounit_device_destroy(&mut output_device_info);
}
// Aggregate Device is a virtual audio interface which utilizes inputs and outputs
// of one or more physical audio interfaces. It is possible to use the clock of
// one of the devices as a master clock for all the combined devices and enable
// drift compensation for the devices that are not designated clock master.
//
// Creating a new aggregate device programmatically requires [0][1]:
// 1. Locate the base plug-in ("com.apple.audio.CoreAudio")
// 2. Create a dictionary that describes the aggregate device
// (don't add sub-devices in that step, prone to fail [0])
// 3. Ask the base plug-in to create the aggregate device (blank)
// 4. Add the array of sub-devices.
// 5. Set the master device (1st output device in our case)
// 6. Enable drift compensation for the non-master devices
//
// [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html
// [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html
// [2] CoreAudio.framework/Headers/AudioHardware.h
fn create_aggregate_device(&mut self) -> Result<()> {
if let Err(r) = audiounit_create_blank_aggregate_device_sync(
&mut self.plugin_id,
&mut self.aggregate_device_id,
) {
cubeb_log!(
"({:p}) Failed to create blank aggregate device",
self as *const AudioUnitStream
);
return Err(r);
}
// The aggregate device may not be created at this point!
// It's better to listen the system devices changing to make sure it's added.
if let Err(r) = audiounit_set_aggregate_sub_device_list_sync(
self.aggregate_device_id,
self.input_device.id,
self.output_device.id,
) {
cubeb_log!(
"({:p}) Failed to set aggregate sub-device list",
self as *const AudioUnitStream
);
audiounit_destroy_aggregate_device(self.plugin_id, &mut self.aggregate_device_id);
return Err(r);
}
if let Err(r) = audiounit_set_master_aggregate_device(self.aggregate_device_id) {
cubeb_log!(
"({:p}) Failed to set master sub-device for aggregate device",
self as *const AudioUnitStream
);
audiounit_destroy_aggregate_device(self.plugin_id, &mut self.aggregate_device_id);
return Err(r);
}
if let Err(r) = audiounit_activate_clock_drift_compensation(self.aggregate_device_id) {
cubeb_log!(
"({:p}) Failed to activate clock drift compensation for aggregate device",
self as *const AudioUnitStream
);
audiounit_destroy_aggregate_device(self.plugin_id, &mut self.aggregate_device_id);
return Err(r);
}
self.workaround_for_airpod();
Ok(())
}
fn configure_input(&mut self) -> Result<()> {
assert!(!self.input_unit.is_null());
@ -4009,21 +3318,25 @@ impl<'ctx> AudioUnitStream<'ctx> {
let mut out_dev_info = self.output_device.clone();
if self.has_input() && self.has_output() && self.input_device.id != self.output_device.id {
if self.create_aggregate_device().is_err() {
self.aggregate_device_id = kAudioObjectUnknown;
cubeb_log!(
"({:p}) Create aggregate devices failed.",
self as *const AudioUnitStream
);
// !!!NOTE: It is not necessary to return here. If it does not
// return it will fallback to the old implementation. The intention
// is to investigate how often it fails. I plan to remove
// it after a couple of weeks.
} else {
in_dev_info.id = self.aggregate_device_id;
out_dev_info.id = self.aggregate_device_id;
in_dev_info.flags = device_flags::DEV_INPUT;
out_dev_info.flags = device_flags::DEV_OUTPUT;
match AggregateDevice::new(self.input_device.id, self.output_device.id) {
Ok(device) => {
in_dev_info.id = device.get_device_id();
out_dev_info.id = device.get_device_id();
in_dev_info.flags = device_flags::DEV_INPUT;
out_dev_info.flags = device_flags::DEV_OUTPUT;
self.aggregate_device = device;
}
Err(status) => {
cubeb_log!(
"({:p}) Create aggregate devices failed. Error: {}",
self as *const AudioUnitStream,
status
);
// !!!NOTE: It is not necessary to return here. If it does not
// return it will fallback to the old implementation. The intention
// is to investigate how often it fails. I plan to remove
// it after a couple of weeks.
}
}
}
@ -4182,10 +3495,7 @@ impl<'ctx> AudioUnitStream<'ctx> {
self.resampler.reset(ptr::null_mut());
self.mixer.reset(ptr::null_mut());
if self.aggregate_device_id != kAudioObjectUnknown {
audiounit_destroy_aggregate_device(self.plugin_id, &mut self.aggregate_device_id);
self.aggregate_device_id = kAudioObjectUnknown;
}
self.aggregate_device = AggregateDevice::default();
}
fn destroy_internal(&mut self) {