1 use super::*;
2 use std::time::{SystemTime, UNIX_EPOCH};
3 
4 const APPLE_EVENT_TIMEOUT: OSStatus = -1712;
5 pub const DRIFT_COMPENSATION: u32 = 1;
6 
7 #[derive(Debug)]
8 pub struct AggregateDevice {
9     plugin_id: AudioObjectID,
10     device_id: AudioObjectID,
11     input_id: AudioObjectID,
12     output_id: AudioObjectID,
13 }
14 
15 impl AggregateDevice {
16     // Aggregate Device is a virtual audio interface which utilizes inputs and outputs
17     // of one or more physical audio interfaces. It is possible to use the clock of
18     // one of the devices as a master clock for all the combined devices and enable
19     // drift compensation for the devices that are not designated clock master.
20     //
21     // Creating a new aggregate device programmatically requires [0][1]:
22     // 1. Locate the base plug-in ("com.apple.audio.CoreAudio")
23     // 2. Create a dictionary that describes the aggregate device
24     //    (don't add sub-devices in that step, prone to fail [0])
25     // 3. Ask the base plug-in to create the aggregate device (blank)
26     // 4. Add the array of sub-devices.
27     // 5. Set the master device (1st output device in our case)
28     // 6. Enable drift compensation for the non-master devices
29     //
30     // [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html
31     // [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html
32     // [2] CoreAudio.framework/Headers/AudioHardware.h
new( input_id: AudioObjectID, output_id: AudioObjectID, ) -> std::result::Result<Self, OSStatus>33     pub fn new(
34         input_id: AudioObjectID,
35         output_id: AudioObjectID,
36     ) -> std::result::Result<Self, OSStatus> {
37         let plugin_id = Self::get_system_plugin_id()?;
38         let device_id = Self::create_blank_device_sync(plugin_id)?;
39         Self::set_sub_devices_sync(device_id, input_id, output_id)?;
40         Self::set_master_device(device_id)?;
41         Self::activate_clock_drift_compensation(device_id)?;
42         Self::workaround_for_airpod(device_id, input_id, output_id)?;
43         cubeb_log!(
44             "Add devices input {} and output {} into an aggregate device {}",
45             input_id,
46             output_id,
47             device_id
48         );
49         Ok(Self {
50             plugin_id,
51             device_id,
52             input_id,
53             output_id,
54         })
55     }
56 
get_device_id(&self) -> AudioObjectID57     pub fn get_device_id(&self) -> AudioObjectID {
58         self.device_id
59     }
60 
61     // The following APIs are set to `pub` for testing purpose.
get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus>62     pub fn get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus> {
63         let address = AudioObjectPropertyAddress {
64             mSelector: kAudioHardwarePropertyPlugInForBundleID,
65             mScope: kAudioObjectPropertyScopeGlobal,
66             mElement: kAudioObjectPropertyElementMaster,
67         };
68 
69         let mut size: usize = 0;
70         let status =
71             audio_object_get_property_data_size(kAudioObjectSystemObject, &address, &mut size);
72         if status != NO_ERR {
73             return Err(status);
74         }
75         assert_ne!(size, 0);
76 
77         let mut plugin_id = kAudioObjectUnknown;
78         let mut in_bundle_ref = cfstringref_from_static_string("com.apple.audio.CoreAudio");
79         let mut translation_value = AudioValueTranslation {
80             mInputData: &mut in_bundle_ref as *mut CFStringRef as *mut c_void,
81             mInputDataSize: mem::size_of::<CFStringRef>() as u32,
82             mOutputData: &mut plugin_id as *mut AudioObjectID as *mut c_void,
83             mOutputDataSize: mem::size_of::<AudioObjectID>() as u32,
84         };
85         assert_eq!(size, mem::size_of_val(&translation_value));
86 
87         let status = audio_object_get_property_data(
88             kAudioObjectSystemObject,
89             &address,
90             &mut size,
91             &mut translation_value,
92         );
93         unsafe {
94             CFRelease(in_bundle_ref as *const c_void);
95         }
96         if status == NO_ERR {
97             assert_ne!(plugin_id, kAudioObjectUnknown);
98             Ok(plugin_id)
99         } else {
100             Err(status)
101         }
102     }
103 
create_blank_device_sync( plugin_id: AudioObjectID, ) -> std::result::Result<AudioObjectID, OSStatus>104     pub fn create_blank_device_sync(
105         plugin_id: AudioObjectID,
106     ) -> std::result::Result<AudioObjectID, OSStatus> {
107         let waiting_time = Duration::new(5, 0);
108 
109         let condvar_pair = Arc::new((Mutex::new(Vec::<AudioObjectID>::new()), Condvar::new()));
110         let mut cloned_condvar_pair = condvar_pair.clone();
111         let data_ptr = &mut cloned_condvar_pair as *mut Arc<(Mutex<Vec<AudioObjectID>>, Condvar)>;
112 
113         let address = get_property_address(
114             Property::HardwareDevices,
115             DeviceType::INPUT | DeviceType::OUTPUT,
116         );
117         assert_eq!(
118             audio_object_add_property_listener(
119                 kAudioObjectSystemObject,
120                 &address,
121                 devices_changed_callback,
122                 data_ptr as *mut c_void,
123             ),
124             NO_ERR
125         );
126 
127         let _teardown = finally(|| {
128             assert_eq!(
129                 audio_object_remove_property_listener(
130                     kAudioObjectSystemObject,
131                     &address,
132                     devices_changed_callback,
133                     data_ptr as *mut c_void,
134                 ),
135                 NO_ERR
136             );
137         });
138 
139         let device = Self::create_blank_device(plugin_id)?;
140 
141         // Wait until the aggregate is created.
142         let &(ref lock, ref cvar) = &*condvar_pair;
143         let devices = lock.lock().unwrap();
144         if !devices.contains(&device) {
145             let (devs, timeout_res) = cvar.wait_timeout(devices, waiting_time).unwrap();
146             if timeout_res.timed_out() {
147                 cubeb_log!(
148                     "Time out for waiting the creation of aggregate device {}!",
149                     device
150                 );
151             }
152             if !devs.contains(&device) {
153                 return Err(APPLE_EVENT_TIMEOUT);
154             }
155         }
156 
157         extern "C" fn devices_changed_callback(
158             id: AudioObjectID,
159             _number_of_addresses: u32,
160             _addresses: *const AudioObjectPropertyAddress,
161             data: *mut c_void,
162         ) -> OSStatus {
163             assert_eq!(id, kAudioObjectSystemObject);
164             let pair = unsafe { &mut *(data as *mut Arc<(Mutex<Vec<AudioObjectID>>, Condvar)>) };
165             let &(ref lock, ref cvar) = &**pair;
166             let mut devices = lock.lock().unwrap();
167             *devices = audiounit_get_devices();
168             cvar.notify_one();
169             NO_ERR
170         }
171 
172         Ok(device)
173     }
174 
create_blank_device( plugin_id: AudioObjectID, ) -> std::result::Result<AudioObjectID, OSStatus>175     pub fn create_blank_device(
176         plugin_id: AudioObjectID,
177     ) -> std::result::Result<AudioObjectID, OSStatus> {
178         assert_ne!(plugin_id, kAudioObjectUnknown);
179 
180         let address = AudioObjectPropertyAddress {
181             mSelector: kAudioPlugInCreateAggregateDevice,
182             mScope: kAudioObjectPropertyScopeGlobal,
183             mElement: kAudioObjectPropertyElementMaster,
184         };
185 
186         let mut size: usize = 0;
187         let status = audio_object_get_property_data_size(plugin_id, &address, &mut size);
188         if status != NO_ERR {
189             return Err(status);
190         }
191         assert_ne!(size, 0);
192 
193         let sys_time = SystemTime::now();
194         let time_id = sys_time.duration_since(UNIX_EPOCH).unwrap().as_nanos();
195         let device_name = format!("{}_{}", PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
196         let device_uid = format!("org.mozilla.{}", device_name);
197 
198         let mut device_id = kAudioObjectUnknown;
199         let status = unsafe {
200             let device_dict = CFMutableDictRef::default();
201 
202             // Set the name of the device.
203             let device_name = cfstringref_from_string(&device_name);
204             device_dict.add_value(
205                 cfstringref_from_static_string(AGGREGATE_DEVICE_NAME_KEY) as *const c_void,
206                 device_name as *const c_void,
207             );
208             CFRelease(device_name as *const c_void);
209 
210             // Set the uid of the device.
211             let device_uid = cfstringref_from_string(&device_uid);
212             device_dict.add_value(
213                 cfstringref_from_static_string(AGGREGATE_DEVICE_UID_KEY) as *const c_void,
214                 device_uid as *const c_void,
215             );
216             CFRelease(device_uid as *const c_void);
217 
218             // Make the device private to the process creating it.
219             let private_value: i32 = 1;
220             let device_private_key = CFNumberCreate(
221                 kCFAllocatorDefault,
222                 i64::from(kCFNumberIntType),
223                 &private_value as *const i32 as *const c_void,
224             );
225             device_dict.add_value(
226                 cfstringref_from_static_string(AGGREGATE_DEVICE_PRIVATE_KEY) as *const c_void,
227                 device_private_key as *const c_void,
228             );
229             CFRelease(device_private_key as *const c_void);
230 
231             // Set the device to a stacked aggregate (i.e. multi-output device).
232             let stacked_value: i32 = 0; // 1 for normal aggregate device.
233             let device_stacked_key = CFNumberCreate(
234                 kCFAllocatorDefault,
235                 i64::from(kCFNumberIntType),
236                 &stacked_value as *const i32 as *const c_void,
237             );
238             device_dict.add_value(
239                 cfstringref_from_static_string(AGGREGATE_DEVICE_STACKED_KEY) as *const c_void,
240                 device_stacked_key as *const c_void,
241             );
242             CFRelease(device_stacked_key as *const c_void);
243 
244             // This call will fire `audiounit_collection_changed_callback` indirectly!
245             audio_object_get_property_data_with_qualifier(
246                 plugin_id,
247                 &address,
248                 mem::size_of_val(&device_dict),
249                 &device_dict,
250                 &mut size,
251                 &mut device_id,
252             )
253         };
254         if status == NO_ERR {
255             assert_ne!(device_id, kAudioObjectUnknown);
256             Ok(device_id)
257         } else {
258             Err(status)
259         }
260     }
261 
set_sub_devices_sync( device_id: AudioDeviceID, input_id: AudioDeviceID, output_id: AudioDeviceID, ) -> std::result::Result<(), OSStatus>262     pub fn set_sub_devices_sync(
263         device_id: AudioDeviceID,
264         input_id: AudioDeviceID,
265         output_id: AudioDeviceID,
266     ) -> std::result::Result<(), OSStatus> {
267         let address = AudioObjectPropertyAddress {
268             mSelector: kAudioAggregateDevicePropertyFullSubDeviceList,
269             mScope: kAudioObjectPropertyScopeGlobal,
270             mElement: kAudioObjectPropertyElementMaster,
271         };
272 
273         let waiting_time = Duration::new(5, 0);
274 
275         let condvar_pair = Arc::new((Mutex::new(AudioObjectID::default()), Condvar::new()));
276         let mut cloned_condvar_pair = condvar_pair.clone();
277         let data_ptr = &mut cloned_condvar_pair as *mut Arc<(Mutex<AudioObjectID>, Condvar)>;
278 
279         assert_eq!(
280             audio_object_add_property_listener(
281                 device_id,
282                 &address,
283                 devices_changed_callback,
284                 data_ptr as *mut c_void,
285             ),
286             NO_ERR
287         );
288 
289         let _teardown = finally(|| {
290             assert_eq!(
291                 audio_object_remove_property_listener(
292                     device_id,
293                     &address,
294                     devices_changed_callback,
295                     data_ptr as *mut c_void,
296                 ),
297                 NO_ERR
298             );
299         });
300 
301         Self::set_sub_devices(device_id, input_id, output_id)?;
302 
303         // Wait until the sub devices are added.
304         let &(ref lock, ref cvar) = &*condvar_pair;
305         let device = lock.lock().unwrap();
306         if *device != device_id {
307             let (dev, timeout_res) = cvar.wait_timeout(device, waiting_time).unwrap();
308             if timeout_res.timed_out() {
309                 cubeb_log!(
310                     "Time out for waiting for adding devices({}, {}) to aggregate device {}!",
311                     input_id,
312                     output_id,
313                     device_id
314                 );
315             }
316             if *dev != device_id {
317                 return Err(APPLE_EVENT_TIMEOUT);
318             }
319         }
320 
321         extern "C" fn devices_changed_callback(
322             id: AudioObjectID,
323             _number_of_addresses: u32,
324             _addresses: *const AudioObjectPropertyAddress,
325             data: *mut c_void,
326         ) -> OSStatus {
327             let pair = unsafe { &mut *(data as *mut Arc<(Mutex<AudioObjectID>, Condvar)>) };
328             let &(ref lock, ref cvar) = &**pair;
329             let mut device = lock.lock().unwrap();
330             *device = id;
331             cvar.notify_one();
332             NO_ERR
333         }
334 
335         Ok(())
336     }
337 
set_sub_devices( device_id: AudioDeviceID, input_id: AudioDeviceID, output_id: AudioDeviceID, ) -> std::result::Result<(), OSStatus>338     pub fn set_sub_devices(
339         device_id: AudioDeviceID,
340         input_id: AudioDeviceID,
341         output_id: AudioDeviceID,
342     ) -> std::result::Result<(), OSStatus> {
343         assert_ne!(device_id, kAudioObjectUnknown);
344         assert_ne!(input_id, kAudioObjectUnknown);
345         assert_ne!(output_id, kAudioObjectUnknown);
346         assert_ne!(input_id, output_id);
347 
348         let output_sub_devices = Self::get_sub_devices(output_id)?;
349         let input_sub_devices = Self::get_sub_devices(input_id)?;
350 
351         unsafe {
352             let sub_devices = CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks);
353             // The order of the items in the array is significant and is used to determine the order of the streams
354             // of the AudioAggregateDevice.
355             for device in output_sub_devices {
356                 let uid = get_device_global_uid(device)?;
357                 CFArrayAppendValue(sub_devices, uid.get_raw() as *const c_void);
358             }
359 
360             for device in input_sub_devices {
361                 let uid = get_device_global_uid(device)?;
362                 CFArrayAppendValue(sub_devices, uid.get_raw() as *const c_void);
363             }
364 
365             let address = AudioObjectPropertyAddress {
366                 mSelector: kAudioAggregateDevicePropertyFullSubDeviceList,
367                 mScope: kAudioObjectPropertyScopeGlobal,
368                 mElement: kAudioObjectPropertyElementMaster,
369             };
370 
371             let size = mem::size_of::<CFMutableArrayRef>();
372             let status = audio_object_set_property_data(device_id, &address, size, &sub_devices);
373             CFRelease(sub_devices as *const c_void);
374             if status == NO_ERR {
375                 Ok(())
376             } else {
377                 Err(status)
378             }
379         }
380     }
381 
get_sub_devices( device_id: AudioDeviceID, ) -> std::result::Result<Vec<AudioObjectID>, OSStatus>382     pub fn get_sub_devices(
383         device_id: AudioDeviceID,
384     ) -> std::result::Result<Vec<AudioObjectID>, OSStatus> {
385         assert_ne!(device_id, kAudioObjectUnknown);
386 
387         let mut sub_devices = Vec::new();
388         let address = AudioObjectPropertyAddress {
389             mSelector: kAudioAggregateDevicePropertyActiveSubDeviceList,
390             mScope: kAudioObjectPropertyScopeGlobal,
391             mElement: kAudioObjectPropertyElementMaster,
392         };
393         let mut size: usize = 0;
394         let rv = audio_object_get_property_data_size(device_id, &address, &mut size);
395 
396         if rv == kAudioHardwareUnknownPropertyError as OSStatus {
397             // Return a vector containing the device itself if the device has no sub devices.
398             sub_devices.push(device_id);
399             return Ok(sub_devices);
400         } else if rv != NO_ERR {
401             return Err(rv);
402         }
403 
404         assert_ne!(size, 0);
405 
406         let count = size / mem::size_of::<AudioObjectID>();
407         sub_devices = allocate_array(count);
408         let rv = audio_object_get_property_data(
409             device_id,
410             &address,
411             &mut size,
412             sub_devices.as_mut_ptr(),
413         );
414 
415         if rv == NO_ERR {
416             Ok(sub_devices)
417         } else {
418             Err(rv)
419         }
420     }
421 
set_master_device(device_id: AudioDeviceID) -> std::result::Result<(), OSStatus>422     pub fn set_master_device(device_id: AudioDeviceID) -> std::result::Result<(), OSStatus> {
423         assert_ne!(device_id, kAudioObjectUnknown);
424         let address = AudioObjectPropertyAddress {
425             mSelector: kAudioAggregateDevicePropertyMasterSubDevice,
426             mScope: kAudioObjectPropertyScopeGlobal,
427             mElement: kAudioObjectPropertyElementMaster,
428         };
429 
430         // Master become the 1st output sub device
431         let output_device_id = audiounit_get_default_device_id(DeviceType::OUTPUT);
432         assert_ne!(output_device_id, kAudioObjectUnknown);
433         let output_sub_devices = Self::get_sub_devices(output_device_id)?;
434         assert!(!output_sub_devices.is_empty());
435         let master_sub_device_uid = get_device_global_uid(output_sub_devices[0]).unwrap();
436         let master_sub_device = master_sub_device_uid.get_raw();
437         let size = mem::size_of::<CFStringRef>();
438         let status = audio_object_set_property_data(device_id, &address, size, &master_sub_device);
439         if status == NO_ERR {
440             Ok(())
441         } else {
442             Err(status)
443         }
444     }
445 
activate_clock_drift_compensation( device_id: AudioObjectID, ) -> std::result::Result<(), OSStatus>446     pub fn activate_clock_drift_compensation(
447         device_id: AudioObjectID,
448     ) -> std::result::Result<(), OSStatus> {
449         assert_ne!(device_id, kAudioObjectUnknown);
450         let address = AudioObjectPropertyAddress {
451             mSelector: kAudioObjectPropertyOwnedObjects,
452             mScope: kAudioObjectPropertyScopeGlobal,
453             mElement: kAudioObjectPropertyElementMaster,
454         };
455 
456         let qualifier_data_size = mem::size_of::<AudioObjectID>();
457         let class_id: AudioClassID = kAudioSubDeviceClassID;
458         let qualifier_data = &class_id;
459 
460         let mut size: usize = 0;
461         let status = audio_object_get_property_data_size_with_qualifier(
462             device_id,
463             &address,
464             qualifier_data_size,
465             qualifier_data,
466             &mut size,
467         );
468         if status != NO_ERR {
469             return Err(status);
470         }
471         assert!(size > 0);
472         let subdevices_num = size / mem::size_of::<AudioObjectID>();
473         assert!(
474             subdevices_num >= 2,
475             "We should have at least one input and one output device."
476         );
477         let mut sub_devices: Vec<AudioObjectID> = allocate_array(subdevices_num);
478         let status = audio_object_get_property_data_with_qualifier(
479             device_id,
480             &address,
481             qualifier_data_size,
482             qualifier_data,
483             &mut size,
484             sub_devices.as_mut_ptr(),
485         );
486         if status != NO_ERR {
487             return Err(status);
488         }
489 
490         let address = AudioObjectPropertyAddress {
491             mSelector: kAudioSubDevicePropertyDriftCompensation,
492             mScope: kAudioObjectPropertyScopeGlobal,
493             mElement: kAudioObjectPropertyElementMaster,
494         };
495 
496         // Start from the second device since the first is the master clock
497         for device in &sub_devices[1..] {
498             let status = audio_object_set_property_data(
499                 *device,
500                 &address,
501                 mem::size_of::<u32>(),
502                 &DRIFT_COMPENSATION,
503             );
504             if status != NO_ERR {
505                 cubeb_log!(
506                     "Failed to set drift compensation for {}. Ignore it.",
507                     device
508                 );
509             }
510         }
511 
512         Ok(())
513     }
514 
destroy_device( plugin_id: AudioObjectID, mut device_id: AudioDeviceID, ) -> std::result::Result<(), OSStatus>515     pub fn destroy_device(
516         plugin_id: AudioObjectID,
517         mut device_id: AudioDeviceID,
518     ) -> std::result::Result<(), OSStatus> {
519         assert_ne!(plugin_id, kAudioObjectUnknown);
520         assert_ne!(device_id, kAudioObjectUnknown);
521 
522         let address = AudioObjectPropertyAddress {
523             mSelector: kAudioPlugInDestroyAggregateDevice,
524             mScope: kAudioObjectPropertyScopeGlobal,
525             mElement: kAudioObjectPropertyElementMaster,
526         };
527 
528         let mut size: usize = 0;
529         let status = audio_object_get_property_data_size(plugin_id, &address, &mut size);
530         if status != NO_ERR {
531             return Err(status);
532         }
533         assert!(size > 0);
534 
535         let status = audio_object_get_property_data(plugin_id, &address, &mut size, &mut device_id);
536         if status == NO_ERR {
537             Ok(())
538         } else {
539             Err(status)
540         }
541     }
542 
workaround_for_airpod( device_id: AudioDeviceID, input_id: AudioDeviceID, output_id: AudioDeviceID, ) -> std::result::Result<(), OSStatus>543     pub fn workaround_for_airpod(
544         device_id: AudioDeviceID,
545         input_id: AudioDeviceID,
546         output_id: AudioDeviceID,
547     ) -> std::result::Result<(), OSStatus> {
548         assert_ne!(device_id, kAudioObjectUnknown);
549         assert_ne!(input_id, kAudioObjectUnknown);
550         assert_ne!(output_id, kAudioObjectUnknown);
551         assert_ne!(input_id, output_id);
552 
553         let label = get_device_label(input_id, DeviceType::INPUT)?;
554         let input_label = label.into_string();
555 
556         let label = get_device_label(output_id, DeviceType::OUTPUT)?;
557         let output_label = label.into_string();
558 
559         if input_label.contains("AirPods") && output_label.contains("AirPods") {
560             let input_rate =
561                 get_device_sample_rate(input_id, DeviceType::INPUT | DeviceType::OUTPUT)?;
562             cubeb_log!(
563                 "The nominal rate of the input device {}: {}",
564                 input_id,
565                 input_rate
566             );
567 
568             let output_rate =
569                 match get_device_sample_rate(output_id, DeviceType::INPUT | DeviceType::OUTPUT) {
570                     Ok(rate) => format!("{}", rate),
571                     Err(e) => format!("Error {}", e),
572                 };
573             cubeb_log!(
574                 "The nominal rate of the output device {}: {}",
575                 output_id,
576                 output_rate
577             );
578 
579             let addr = AudioObjectPropertyAddress {
580                 mSelector: kAudioDevicePropertyNominalSampleRate,
581                 mScope: kAudioObjectPropertyScopeGlobal,
582                 mElement: kAudioObjectPropertyElementMaster,
583             };
584 
585             let status = audio_object_set_property_data(
586                 device_id,
587                 &addr,
588                 mem::size_of::<f64>(),
589                 &input_rate,
590             );
591             if status != NO_ERR {
592                 return Err(status);
593             }
594         }
595 
596         Ok(())
597     }
598 }
599 
600 impl Default for AggregateDevice {
default() -> Self601     fn default() -> Self {
602         Self {
603             plugin_id: kAudioObjectUnknown,
604             device_id: kAudioObjectUnknown,
605             input_id: kAudioObjectUnknown,
606             output_id: kAudioObjectUnknown,
607         }
608     }
609 }
610 
611 impl Drop for AggregateDevice {
drop(&mut self)612     fn drop(&mut self) {
613         if self.plugin_id != kAudioObjectUnknown && self.device_id != kAudioObjectUnknown {
614             if let Err(r) = Self::destroy_device(self.plugin_id, self.device_id) {
615                 cubeb_log!(
616                     "Failed to destroyed aggregate device {}. Error: {}",
617                     self.device_id,
618                     r
619                 );
620             } else {
621                 cubeb_log!("Destroyed aggregate device {}", self.device_id);
622             }
623         }
624     }
625 }
626