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