1 use super::*;
2 
3 // Common Utils
4 // ------------------------------------------------------------------------------------------------
5 #[derive(Clone, Debug, PartialEq)]
6 pub enum Scope {
7     Input,
8     Output,
9 }
10 
11 impl From<Scope> for DeviceType {
from(scope: Scope) -> Self12     fn from(scope: Scope) -> Self {
13         match scope {
14             Scope::Input => DeviceType::INPUT,
15             Scope::Output => DeviceType::OUTPUT,
16         }
17     }
18 }
19 
20 #[derive(Clone)]
21 pub enum PropertyScope {
22     Input,
23     Output,
24 }
25 
test_get_default_device(scope: Scope) -> Option<AudioObjectID>26 pub fn test_get_default_device(scope: Scope) -> Option<AudioObjectID> {
27     let address = AudioObjectPropertyAddress {
28         mSelector: match scope {
29             Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
30             Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
31         },
32         mScope: kAudioObjectPropertyScopeGlobal,
33         mElement: kAudioObjectPropertyElementMaster,
34     };
35 
36     let mut devid: AudioObjectID = kAudioObjectUnknown;
37     let mut size = mem::size_of::<AudioObjectID>();
38     let status = unsafe {
39         AudioObjectGetPropertyData(
40             kAudioObjectSystemObject,
41             &address,
42             0,
43             ptr::null(),
44             &mut size as *mut usize as *mut UInt32,
45             &mut devid as *mut AudioObjectID as *mut c_void,
46         )
47     };
48     if status != NO_ERR || devid == kAudioObjectUnknown {
49         return None;
50     }
51     Some(devid)
52 }
53 
54 // TODO: Create a GetProperty trait and add a default implementation for it, then implement it
55 //       for TestAudioUnit so the member method like `get_buffer_frame_size` can reuse the trait
56 //       method get_property_data.
57 #[derive(Debug)]
58 pub struct TestAudioUnit(AudioUnit);
59 
60 impl TestAudioUnit {
new(unit: AudioUnit) -> Self61     fn new(unit: AudioUnit) -> Self {
62         assert!(!unit.is_null());
63         Self(unit)
64     }
get_inner(&self) -> AudioUnit65     pub fn get_inner(&self) -> AudioUnit {
66         self.0
67     }
get_buffer_frame_size( &self, scope: Scope, prop_scope: PropertyScope, ) -> std::result::Result<u32, OSStatus>68     pub fn get_buffer_frame_size(
69         &self,
70         scope: Scope,
71         prop_scope: PropertyScope,
72     ) -> std::result::Result<u32, OSStatus> {
73         test_audiounit_get_buffer_frame_size(self.0, scope, prop_scope)
74     }
75 }
76 
77 impl Drop for TestAudioUnit {
drop(&mut self)78     fn drop(&mut self) {
79         unsafe {
80             AudioUnitUninitialize(self.0);
81             AudioComponentInstanceDispose(self.0);
82         }
83     }
84 }
85 
86 // TODO: 1. Return Result with custom errors.
87 //       2. Allow to create a in-out unit.
test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit>88 pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> {
89     let device = test_get_default_device(scope.clone());
90     let unit = test_create_audiounit(ComponentSubType::HALOutput);
91     if device.is_none() || unit.is_none() {
92         return None;
93     }
94     let unit = unit.unwrap();
95     let device = device.unwrap();
96     match scope {
97         Scope::Input => {
98             if test_enable_audiounit_in_scope(unit.get_inner(), Scope::Input, true).is_err()
99                 || test_enable_audiounit_in_scope(unit.get_inner(), Scope::Output, false).is_err()
100             {
101                 return None;
102             }
103         }
104         Scope::Output => {
105             if test_enable_audiounit_in_scope(unit.get_inner(), Scope::Input, false).is_err()
106                 || test_enable_audiounit_in_scope(unit.get_inner(), Scope::Output, true).is_err()
107             {
108                 return None;
109             }
110         }
111     }
112 
113     let status = unsafe {
114         AudioUnitSetProperty(
115             unit.get_inner(),
116             kAudioOutputUnitProperty_CurrentDevice,
117             kAudioUnitScope_Global,
118             0, // Global bus
119             &device as *const AudioObjectID as *const c_void,
120             mem::size_of::<AudioObjectID>() as u32,
121         )
122     };
123     if status == NO_ERR {
124         Some(unit)
125     } else {
126         None
127     }
128 }
129 
130 pub enum ComponentSubType {
131     HALOutput,
132     DefaultOutput,
133 }
134 
135 // TODO: Return Result with custom errors.
136 // Surprisingly the AudioUnit can be created even when there is no any device on the platform,
137 // no matter its subtype is HALOutput or DefaultOutput.
test_create_audiounit(unit_type: ComponentSubType) -> Option<TestAudioUnit>138 pub fn test_create_audiounit(unit_type: ComponentSubType) -> Option<TestAudioUnit> {
139     let desc = AudioComponentDescription {
140         componentType: kAudioUnitType_Output,
141         componentSubType: match unit_type {
142             ComponentSubType::HALOutput => kAudioUnitSubType_HALOutput,
143             ComponentSubType::DefaultOutput => kAudioUnitSubType_DefaultOutput,
144         },
145         componentManufacturer: kAudioUnitManufacturer_Apple,
146         componentFlags: 0,
147         componentFlagsMask: 0,
148     };
149     let comp = unsafe { AudioComponentFindNext(ptr::null_mut(), &desc) };
150     if comp.is_null() {
151         return None;
152     }
153     let mut unit: AudioUnit = ptr::null_mut();
154     let status = unsafe { AudioComponentInstanceNew(comp, &mut unit) };
155     // TODO: Is unit possible to be null when no error returns ?
156     if status != NO_ERR || unit.is_null() {
157         None
158     } else {
159         Some(TestAudioUnit::new(unit))
160     }
161 }
162 
test_enable_audiounit_in_scope( unit: AudioUnit, scope: Scope, enable: bool, ) -> std::result::Result<(), OSStatus>163 fn test_enable_audiounit_in_scope(
164     unit: AudioUnit,
165     scope: Scope,
166     enable: bool,
167 ) -> std::result::Result<(), OSStatus> {
168     assert!(!unit.is_null());
169     let (scope, element) = match scope {
170         Scope::Input => (kAudioUnitScope_Input, AU_IN_BUS),
171         Scope::Output => (kAudioUnitScope_Output, AU_OUT_BUS),
172     };
173     let on_off: u32 = if enable { 1 } else { 0 };
174     let status = unsafe {
175         AudioUnitSetProperty(
176             unit,
177             kAudioOutputUnitProperty_EnableIO,
178             scope,
179             element,
180             &on_off as *const u32 as *const c_void,
181             mem::size_of::<u32>() as u32,
182         )
183     };
184     if status == NO_ERR {
185         Ok(())
186     } else {
187         Err(status)
188     }
189 }
190 
test_get_default_source_name(scope: Scope) -> Option<String>191 pub fn test_get_default_source_name(scope: Scope) -> Option<String> {
192     if let Some(source) = test_get_default_source_data(scope) {
193         Some(u32_to_string(source))
194     } else {
195         None
196     }
197 }
198 
test_get_default_source_data(scope: Scope) -> Option<u32>199 pub fn test_get_default_source_data(scope: Scope) -> Option<u32> {
200     let device = test_get_default_device(scope.clone());
201     if device.is_none() {
202         return None;
203     }
204 
205     let device = device.unwrap();
206     let address = AudioObjectPropertyAddress {
207         mSelector: kAudioDevicePropertyDataSource,
208         mScope: match scope {
209             Scope::Input => kAudioDevicePropertyScopeInput,
210             Scope::Output => kAudioDevicePropertyScopeOutput,
211         },
212         mElement: kAudioObjectPropertyElementMaster,
213     };
214     let mut size = mem::size_of::<u32>();
215     let mut data: u32 = 0;
216 
217     let status = unsafe {
218         AudioObjectGetPropertyData(
219             device,
220             &address,
221             0,
222             ptr::null(),
223             &mut size as *mut usize as *mut u32,
224             &mut data as *mut u32 as *mut c_void,
225         )
226     };
227 
228     // TODO: Can data be 0 when no error returns ?
229     if status == NO_ERR && data > 0 {
230         Some(data)
231     } else {
232         None
233     }
234 }
235 
u32_to_string(data: u32) -> String236 fn u32_to_string(data: u32) -> String {
237     // Reverse 0xWXYZ into 0xZYXW.
238     let mut buffer = [b'\x00'; 4]; // 4 bytes for u32.
239     buffer[0] = (data >> 24) as u8;
240     buffer[1] = (data >> 16) as u8;
241     buffer[2] = (data >> 8) as u8;
242     buffer[3] = (data) as u8;
243     String::from_utf8_lossy(&buffer).to_string()
244 }
245 
test_get_all_devices() -> Vec<AudioObjectID>246 pub fn test_get_all_devices() -> Vec<AudioObjectID> {
247     let mut devices = Vec::new();
248     let address = AudioObjectPropertyAddress {
249         mSelector: kAudioHardwarePropertyDevices,
250         mScope: kAudioObjectPropertyScopeGlobal,
251         mElement: kAudioObjectPropertyElementMaster,
252     };
253     let mut size: usize = 0;
254     let status = unsafe {
255         AudioObjectGetPropertyDataSize(
256             kAudioObjectSystemObject,
257             &address,
258             0,
259             ptr::null(),
260             &mut size as *mut usize as *mut u32,
261         )
262     };
263     // size will be 0 if there is no device at all.
264     if status != NO_ERR || size == 0 {
265         return devices;
266     }
267     assert_eq!(size % mem::size_of::<AudioObjectID>(), 0);
268     let elements = size / mem::size_of::<AudioObjectID>();
269     devices.resize(elements, kAudioObjectUnknown);
270     let status = unsafe {
271         AudioObjectGetPropertyData(
272             kAudioObjectSystemObject,
273             &address,
274             0,
275             ptr::null(),
276             &mut size as *mut usize as *mut u32,
277             devices.as_mut_ptr() as *mut c_void,
278         )
279     };
280     if status != NO_ERR {
281         devices.clear();
282         return devices;
283     }
284     for device in devices.iter() {
285         assert_ne!(*device, kAudioObjectUnknown);
286     }
287     devices
288 }
289 
test_get_devices_in_scope(scope: Scope) -> Vec<AudioObjectID>290 pub fn test_get_devices_in_scope(scope: Scope) -> Vec<AudioObjectID> {
291     let mut devices = test_get_all_devices();
292     devices.retain(|device| test_device_in_scope(*device, scope.clone()));
293     devices
294 }
295 
test_print_devices_in_scope(devices: &Vec<AudioObjectID>, scope: Scope)296 fn test_print_devices_in_scope(devices: &Vec<AudioObjectID>, scope: Scope) {
297     println!(
298         "\n{:?} devices\n\
299          --------------------",
300         scope
301     );
302     for device in devices {
303         let info = TestDeviceInfo::new(*device, scope.clone());
304         print_info(&info);
305     }
306     println!("");
307 
308     fn print_info(info: &TestDeviceInfo) {
309         println!("{:>4}: {}\n\tuid: {}", info.id, info.label, info.uid);
310     }
311 }
312 
313 #[derive(Debug)]
314 struct TestDeviceInfo {
315     id: AudioObjectID,
316     label: String,
317     uid: String,
318 }
319 impl TestDeviceInfo {
new(id: AudioObjectID, scope: Scope) -> Self320     fn new(id: AudioObjectID, scope: Scope) -> Self {
321         Self {
322             id,
323             label: Self::get_label(id, scope.clone()),
324             uid: Self::get_uid(id, scope),
325         }
326     }
327 
get_label(id: AudioObjectID, scope: Scope) -> String328     fn get_label(id: AudioObjectID, scope: Scope) -> String {
329         match get_device_uid(id, scope.into()) {
330             Ok(uid) => uid.into_string(),
331             Err(status) => format!("Unknow. Error: {}", status).to_string(),
332         }
333     }
334 
get_uid(id: AudioObjectID, scope: Scope) -> String335     fn get_uid(id: AudioObjectID, scope: Scope) -> String {
336         match get_device_label(id, scope.into()) {
337             Ok(label) => label.into_string(),
338             Err(status) => format!("Unknown. Error: {}", status).to_string(),
339         }
340     }
341 }
342 
test_device_channels_in_scope( id: AudioObjectID, scope: Scope, ) -> std::result::Result<u32, OSStatus>343 pub fn test_device_channels_in_scope(
344     id: AudioObjectID,
345     scope: Scope,
346 ) -> std::result::Result<u32, OSStatus> {
347     let address = AudioObjectPropertyAddress {
348         mSelector: kAudioDevicePropertyStreamConfiguration,
349         mScope: match scope {
350             Scope::Input => kAudioDevicePropertyScopeInput,
351             Scope::Output => kAudioDevicePropertyScopeOutput,
352         },
353         mElement: kAudioObjectPropertyElementMaster,
354     };
355     let mut size: usize = 0;
356     let status = unsafe {
357         AudioObjectGetPropertyDataSize(
358             id,
359             &address,
360             0,
361             ptr::null(),
362             &mut size as *mut usize as *mut u32,
363         )
364     };
365     if status != NO_ERR {
366         return Err(status);
367     }
368     if size == 0 {
369         return Ok(0);
370     }
371     let byte_len = size / mem::size_of::<u8>();
372     let mut bytes = vec![0u8; byte_len];
373     let status = unsafe {
374         AudioObjectGetPropertyData(
375             id,
376             &address,
377             0,
378             ptr::null(),
379             &mut size as *mut usize as *mut u32,
380             bytes.as_mut_ptr() as *mut c_void,
381         )
382     };
383     if status != NO_ERR {
384         return Err(status);
385     }
386     let buf_list = unsafe { &*(bytes.as_mut_ptr() as *mut AudioBufferList) };
387     let buf_len = buf_list.mNumberBuffers as usize;
388     if buf_len == 0 {
389         return Ok(0);
390     }
391     let buf_ptr = buf_list.mBuffers.as_ptr() as *const AudioBuffer;
392     let buffers = unsafe { slice::from_raw_parts(buf_ptr, buf_len) };
393     let mut channels: u32 = 0;
394     for buffer in buffers {
395         channels += buffer.mNumberChannels;
396     }
397     Ok(channels)
398 }
399 
test_device_in_scope(id: AudioObjectID, scope: Scope) -> bool400 pub fn test_device_in_scope(id: AudioObjectID, scope: Scope) -> bool {
401     let channels = test_device_channels_in_scope(id, scope);
402     channels.is_ok() && channels.unwrap() > 0
403 }
404 
test_get_all_onwed_devices(id: AudioDeviceID) -> Vec<AudioObjectID>405 pub fn test_get_all_onwed_devices(id: AudioDeviceID) -> Vec<AudioObjectID> {
406     assert_ne!(id, kAudioObjectUnknown);
407 
408     let address = AudioObjectPropertyAddress {
409         mSelector: kAudioObjectPropertyOwnedObjects,
410         mScope: kAudioObjectPropertyScopeGlobal,
411         mElement: kAudioObjectPropertyElementMaster,
412     };
413 
414     let qualifier_data_size = mem::size_of::<AudioObjectID>();
415     let class_id: AudioClassID = kAudioSubDeviceClassID;
416     let qualifier_data = &class_id;
417     let mut size: usize = 0;
418 
419     unsafe {
420         assert_eq!(
421             AudioObjectGetPropertyDataSize(
422                 id,
423                 &address,
424                 qualifier_data_size as u32,
425                 qualifier_data as *const u32 as *const c_void,
426                 &mut size as *mut usize as *mut u32
427             ),
428             NO_ERR
429         );
430     }
431     assert_ne!(size, 0);
432 
433     let elements = size / mem::size_of::<AudioObjectID>();
434     let mut devices: Vec<AudioObjectID> = allocate_array(elements);
435 
436     unsafe {
437         assert_eq!(
438             AudioObjectGetPropertyData(
439                 id,
440                 &address,
441                 qualifier_data_size as u32,
442                 qualifier_data as *const u32 as *const c_void,
443                 &mut size as *mut usize as *mut u32,
444                 devices.as_mut_ptr() as *mut c_void
445             ),
446             NO_ERR
447         );
448     }
449 
450     devices
451 }
452 
test_get_master_device(id: AudioObjectID) -> String453 pub fn test_get_master_device(id: AudioObjectID) -> String {
454     assert_ne!(id, kAudioObjectUnknown);
455 
456     let address = AudioObjectPropertyAddress {
457         mSelector: kAudioAggregateDevicePropertyMasterSubDevice,
458         mScope: kAudioObjectPropertyScopeGlobal,
459         mElement: kAudioObjectPropertyElementMaster,
460     };
461 
462     let mut master: CFStringRef = ptr::null_mut();
463     let mut size = mem::size_of::<CFStringRef>();
464     assert_eq!(
465         audio_object_get_property_data(id, &address, &mut size, &mut master),
466         NO_ERR
467     );
468     assert!(!master.is_null());
469 
470     let master = StringRef::new(master as _);
471     master.into_string()
472 }
473 
test_get_drift_compensations(id: AudioObjectID) -> std::result::Result<u32, OSStatus>474 pub fn test_get_drift_compensations(id: AudioObjectID) -> std::result::Result<u32, OSStatus> {
475     let address = AudioObjectPropertyAddress {
476         mSelector: kAudioSubDevicePropertyDriftCompensation,
477         mScope: kAudioObjectPropertyScopeGlobal,
478         mElement: kAudioObjectPropertyElementMaster,
479     };
480     let mut size = mem::size_of::<u32>();
481     let mut compensation = u32::max_value();
482     let status = unsafe {
483         AudioObjectGetPropertyData(
484             id,
485             &address,
486             0,
487             ptr::null(),
488             &mut size as *mut usize as *mut u32,
489             &mut compensation as *mut u32 as *mut c_void,
490         )
491     };
492     if status == NO_ERR {
493         Ok(compensation)
494     } else {
495         Err(status)
496     }
497 }
498 
test_audiounit_scope_is_enabled(unit: AudioUnit, scope: Scope) -> bool499 pub fn test_audiounit_scope_is_enabled(unit: AudioUnit, scope: Scope) -> bool {
500     assert!(!unit.is_null());
501     let mut has_io: UInt32 = 0;
502     let (scope, element) = match scope {
503         Scope::Input => (kAudioUnitScope_Input, AU_IN_BUS),
504         Scope::Output => (kAudioUnitScope_Output, AU_OUT_BUS),
505     };
506     let mut size = mem::size_of::<UInt32>();
507     assert_eq!(
508         audio_unit_get_property(
509             unit,
510             kAudioOutputUnitProperty_HasIO,
511             scope,
512             element,
513             &mut has_io,
514             &mut size
515         ),
516         NO_ERR
517     );
518     has_io != 0
519 }
520 
test_audiounit_get_buffer_frame_size( unit: AudioUnit, scope: Scope, prop_scope: PropertyScope, ) -> std::result::Result<u32, OSStatus>521 pub fn test_audiounit_get_buffer_frame_size(
522     unit: AudioUnit,
523     scope: Scope,
524     prop_scope: PropertyScope,
525 ) -> std::result::Result<u32, OSStatus> {
526     let element = match scope {
527         Scope::Input => AU_IN_BUS,
528         Scope::Output => AU_OUT_BUS,
529     };
530     let prop_scope = match prop_scope {
531         PropertyScope::Input => kAudioUnitScope_Input,
532         PropertyScope::Output => kAudioUnitScope_Output,
533     };
534     let mut buffer_frames: u32 = 0;
535     let mut size = mem::size_of::<u32>();
536     let status = unsafe {
537         AudioUnitGetProperty(
538             unit,
539             kAudioDevicePropertyBufferFrameSize,
540             prop_scope,
541             element,
542             &mut buffer_frames as *mut u32 as *mut c_void,
543             &mut size as *mut usize as *mut u32,
544         )
545     };
546     if status == NO_ERR {
547         Ok(buffer_frames)
548     } else {
549         Err(status)
550     }
551 }
552 
553 // Surprisingly it's ok to set
554 //   1. a unknown device
555 //   2. a non-input/non-output device
556 //   3. the current default input/output device
557 // as the new default input/output device by apple's API. We need to check the above things by ourselves.
test_set_default_device( device: AudioObjectID, scope: Scope, ) -> std::result::Result<bool, OSStatus>558 pub fn test_set_default_device(
559     device: AudioObjectID,
560     scope: Scope,
561 ) -> std::result::Result<bool, OSStatus> {
562     let default = test_get_default_device(scope.clone());
563     if default.is_none() {
564         return Ok(false);
565     }
566     let default = default.unwrap();
567     if default == device || !test_device_in_scope(device, scope.clone()) {
568         return Ok(false);
569     }
570     let address = AudioObjectPropertyAddress {
571         mSelector: match scope {
572             Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
573             Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
574         },
575         mScope: kAudioObjectPropertyScopeGlobal,
576         mElement: kAudioObjectPropertyElementMaster,
577     };
578     let size = mem::size_of::<AudioObjectID>();
579     let status = unsafe {
580         AudioObjectSetPropertyData(
581             kAudioObjectSystemObject,
582             &address,
583             0,
584             ptr::null(),
585             size as u32,
586             &device as *const AudioObjectID as *const c_void,
587         )
588     };
589     if status == NO_ERR {
590         Ok(true)
591     } else {
592         Err(status)
593     }
594 }
595 
596 pub struct TestDeviceSwitcher {
597     scope: Scope,
598     devices: Vec<AudioObjectID>,
599     current_device_index: usize,
600 }
601 
602 impl TestDeviceSwitcher {
new(scope: Scope) -> Self603     pub fn new(scope: Scope) -> Self {
604         let devices = test_get_devices_in_scope(scope.clone());
605         let current = test_get_default_device(scope.clone()).unwrap();
606         let index = devices
607             .iter()
608             .position(|device| *device == current)
609             .unwrap();
610         test_print_devices_in_scope(&devices, scope.clone());
611         Self {
612             scope: scope,
613             devices: devices,
614             current_device_index: index,
615         }
616     }
617 
next(&mut self)618     pub fn next(&mut self) {
619         let current = self.devices[self.current_device_index];
620         let next_index = (self.current_device_index + 1) % self.devices.len();
621         let next = self.devices[next_index];
622         println!(
623             "Switch device for {:?}: {} -> {}",
624             self.scope, current, next
625         );
626         assert!(self.set_device(next).unwrap());
627         self.current_device_index = next_index;
628     }
629 
set_device(&self, device: AudioObjectID) -> std::result::Result<bool, OSStatus>630     fn set_device(&self, device: AudioObjectID) -> std::result::Result<bool, OSStatus> {
631         test_set_default_device(device, self.scope.clone())
632     }
633 }
634 
test_create_device_change_listener<F>(scope: Scope, listener: F) -> TestPropertyListener<F> where F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,635 pub fn test_create_device_change_listener<F>(scope: Scope, listener: F) -> TestPropertyListener<F>
636 where
637     F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
638 {
639     let address = AudioObjectPropertyAddress {
640         mSelector: match scope {
641             Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
642             Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
643         },
644         mScope: kAudioObjectPropertyScopeGlobal,
645         mElement: kAudioObjectPropertyElementMaster,
646     };
647     TestPropertyListener::new(kAudioObjectSystemObject, address, listener)
648 }
649 
650 pub struct TestPropertyListener<F>
651 where
652     F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
653 {
654     device: AudioObjectID,
655     property: AudioObjectPropertyAddress,
656     callback: F,
657 }
658 
659 impl<F> TestPropertyListener<F>
660 where
661     F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
662 {
new(device: AudioObjectID, property: AudioObjectPropertyAddress, callback: F) -> Self663     pub fn new(device: AudioObjectID, property: AudioObjectPropertyAddress, callback: F) -> Self {
664         Self {
665             device,
666             property,
667             callback,
668         }
669     }
670 
start(&self) -> std::result::Result<(), OSStatus>671     pub fn start(&self) -> std::result::Result<(), OSStatus> {
672         let status = unsafe {
673             AudioObjectAddPropertyListener(
674                 self.device,
675                 &self.property,
676                 Some(Self::render),
677                 self as *const Self as *mut c_void,
678             )
679         };
680         if status == NO_ERR {
681             Ok(())
682         } else {
683             Err(status)
684         }
685     }
686 
stop(&self) -> std::result::Result<(), OSStatus>687     pub fn stop(&self) -> std::result::Result<(), OSStatus> {
688         let status = unsafe {
689             AudioObjectRemovePropertyListener(
690                 self.device,
691                 &self.property,
692                 Some(Self::render),
693                 self as *const Self as *mut c_void,
694             )
695         };
696         if status == NO_ERR {
697             Ok(())
698         } else {
699             Err(status)
700         }
701     }
702 
render( id: AudioObjectID, number_of_addresses: u32, addresses: *const AudioObjectPropertyAddress, data: *mut c_void, ) -> OSStatus703     extern "C" fn render(
704         id: AudioObjectID,
705         number_of_addresses: u32,
706         addresses: *const AudioObjectPropertyAddress,
707         data: *mut c_void,
708     ) -> OSStatus {
709         let listener = unsafe { &*(data as *mut Self) };
710         assert_eq!(id, listener.device);
711         let addrs = unsafe { slice::from_raw_parts(addresses, number_of_addresses as usize) };
712         (listener.callback)(addrs)
713     }
714 }
715 
716 impl<F> Drop for TestPropertyListener<F>
717 where
718     F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
719 {
drop(&mut self)720     fn drop(&mut self) {
721         self.stop();
722     }
723 }
724 
725 // TODO: It doesn't work if default input or output is an aggregate device! Probably we need to do
726 //       the same thing as what audiounit_set_aggregate_sub_device_list does.
727 #[derive(Debug)]
728 pub struct TestDevicePlugger {
729     scope: Scope,
730     plugin_id: AudioObjectID,
731     device_id: AudioObjectID,
732 }
733 
734 impl TestDevicePlugger {
new(scope: Scope) -> std::result::Result<Self, OSStatus>735     pub fn new(scope: Scope) -> std::result::Result<Self, OSStatus> {
736         let plugin_id = Self::get_system_plugin_id()?;
737         Ok(Self {
738             scope,
739             plugin_id,
740             device_id: kAudioObjectUnknown,
741         })
742     }
743 
get_device_id(&self) -> AudioObjectID744     pub fn get_device_id(&self) -> AudioObjectID {
745         self.device_id
746     }
747 
plug(&mut self) -> std::result::Result<(), OSStatus>748     pub fn plug(&mut self) -> std::result::Result<(), OSStatus> {
749         self.device_id = self.create_aggregate_device()?;
750         Ok(())
751     }
752 
unplug(&mut self) -> std::result::Result<(), OSStatus>753     pub fn unplug(&mut self) -> std::result::Result<(), OSStatus> {
754         self.destroy_aggregate_device()
755     }
756 
is_plugging(&self) -> bool757     fn is_plugging(&self) -> bool {
758         self.device_id != kAudioObjectUnknown
759     }
760 
destroy_aggregate_device(&mut self) -> std::result::Result<(), OSStatus>761     fn destroy_aggregate_device(&mut self) -> std::result::Result<(), OSStatus> {
762         assert_ne!(self.plugin_id, kAudioObjectUnknown);
763         assert_ne!(self.device_id, kAudioObjectUnknown);
764 
765         let address = AudioObjectPropertyAddress {
766             mSelector: kAudioPlugInDestroyAggregateDevice,
767             mScope: kAudioObjectPropertyScopeGlobal,
768             mElement: kAudioObjectPropertyElementMaster,
769         };
770 
771         let mut size: usize = 0;
772         let status = unsafe {
773             AudioObjectGetPropertyDataSize(
774                 self.plugin_id,
775                 &address,
776                 0,
777                 ptr::null(),
778                 &mut size as *mut usize as *mut u32,
779             )
780         };
781         if status != NO_ERR {
782             return Err(status);
783         }
784         assert_ne!(size, 0);
785 
786         let status = unsafe {
787             // This call can simulate removing a device.
788             AudioObjectGetPropertyData(
789                 self.plugin_id,
790                 &address,
791                 0,
792                 ptr::null(),
793                 &mut size as *mut usize as *mut u32,
794                 &mut self.device_id as *mut AudioDeviceID as *mut c_void,
795             )
796         };
797         if status == NO_ERR {
798             self.device_id = kAudioObjectUnknown;
799             Ok(())
800         } else {
801             Err(status)
802         }
803     }
804 
create_aggregate_device(&self) -> std::result::Result<AudioObjectID, OSStatus>805     fn create_aggregate_device(&self) -> std::result::Result<AudioObjectID, OSStatus> {
806         use std::time::{SystemTime, UNIX_EPOCH};
807 
808         const TEST_AGGREGATE_DEVICE_NAME: &str = "TestAggregateDevice";
809 
810         assert_ne!(self.plugin_id, kAudioObjectUnknown);
811 
812         let sub_devices = Self::get_sub_devices(self.scope.clone());
813         if sub_devices.is_none() {
814             return Err(kAudioCodecUnspecifiedError as OSStatus);
815         }
816         let sub_devices = sub_devices.unwrap();
817 
818         let address = AudioObjectPropertyAddress {
819             mSelector: kAudioPlugInCreateAggregateDevice,
820             mScope: kAudioObjectPropertyScopeGlobal,
821             mElement: kAudioObjectPropertyElementMaster,
822         };
823 
824         let mut size: usize = 0;
825         let status = unsafe {
826             AudioObjectGetPropertyDataSize(
827                 self.plugin_id,
828                 &address,
829                 0,
830                 ptr::null(),
831                 &mut size as *mut usize as *mut u32,
832             )
833         };
834         if status != NO_ERR {
835             return Err(status);
836         }
837         assert_ne!(size, 0);
838 
839         let sys_time = SystemTime::now();
840         let time_id = sys_time.duration_since(UNIX_EPOCH).unwrap().as_nanos();
841         let device_name = format!("{}_{}", TEST_AGGREGATE_DEVICE_NAME, time_id);
842         let device_uid = format!("org.mozilla.{}", device_name);
843 
844         let mut device_id = kAudioObjectUnknown;
845         let status = unsafe {
846             let device_dict = CFDictionaryCreateMutable(
847                 kCFAllocatorDefault,
848                 0,
849                 &kCFTypeDictionaryKeyCallBacks,
850                 &kCFTypeDictionaryValueCallBacks,
851             );
852 
853             // Set the name of this device.
854             let device_name = cfstringref_from_string(&device_name);
855             CFDictionaryAddValue(
856                 device_dict,
857                 cfstringref_from_static_string(AGGREGATE_DEVICE_NAME_KEY) as *const c_void,
858                 device_name as *const c_void,
859             );
860             CFRelease(device_name as *const c_void);
861 
862             // Set the uid of this device.
863             let device_uid = cfstringref_from_string(&device_uid);
864             CFDictionaryAddValue(
865                 device_dict,
866                 cfstringref_from_static_string(AGGREGATE_DEVICE_UID_KEY) as *const c_void,
867                 device_uid as *const c_void,
868             );
869             CFRelease(device_uid as *const c_void);
870 
871             // This device is private to the process creating it.
872             let private_value: i32 = 1;
873             let device_private_key = CFNumberCreate(
874                 kCFAllocatorDefault,
875                 i64::from(kCFNumberIntType),
876                 &private_value as *const i32 as *const c_void,
877             );
878             CFDictionaryAddValue(
879                 device_dict,
880                 cfstringref_from_static_string(AGGREGATE_DEVICE_PRIVATE_KEY) as *const c_void,
881                 device_private_key as *const c_void,
882             );
883             CFRelease(device_private_key as *const c_void);
884 
885             // Set this device to be a stacked aggregate (i.e. multi-output device).
886             let stacked_value: i32 = 0; // 1 for normal aggregate device.
887             let device_stacked_key = CFNumberCreate(
888                 kCFAllocatorDefault,
889                 i64::from(kCFNumberIntType),
890                 &stacked_value as *const i32 as *const c_void,
891             );
892             CFDictionaryAddValue(
893                 device_dict,
894                 cfstringref_from_static_string(AGGREGATE_DEVICE_STACKED_KEY) as *const c_void,
895                 device_stacked_key as *const c_void,
896             );
897             CFRelease(device_stacked_key as *const c_void);
898 
899             // Set sub devices for this device.
900             CFDictionaryAddValue(
901                 device_dict,
902                 cfstringref_from_static_string(AGGREGATE_DEVICE_SUB_DEVICE_LIST_KEY)
903                     as *const c_void,
904                 sub_devices as *const c_void,
905             );
906             CFRelease(sub_devices as *const c_void);
907 
908             // This call can simulate adding a device.
909             let status = AudioObjectGetPropertyData(
910                 self.plugin_id,
911                 &address,
912                 mem::size_of_val(&device_dict) as u32,
913                 &device_dict as *const CFMutableDictionaryRef as *const c_void,
914                 &mut size as *mut usize as *mut u32,
915                 &mut device_id as *mut AudioDeviceID as *mut c_void,
916             );
917             CFRelease(device_dict as *const c_void);
918             status
919         };
920         if status == NO_ERR {
921             assert_ne!(device_id, kAudioObjectUnknown);
922             Ok(device_id)
923         } else {
924             Err(status)
925         }
926     }
927 
get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus>928     fn get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus> {
929         let address = AudioObjectPropertyAddress {
930             mSelector: kAudioHardwarePropertyPlugInForBundleID,
931             mScope: kAudioObjectPropertyScopeGlobal,
932             mElement: kAudioObjectPropertyElementMaster,
933         };
934 
935         let mut size: usize = 0;
936         let status = unsafe {
937             AudioObjectGetPropertyDataSize(
938                 kAudioObjectSystemObject,
939                 &address,
940                 0,
941                 ptr::null(),
942                 &mut size as *mut usize as *mut u32,
943             )
944         };
945         if status != NO_ERR {
946             return Err(status);
947         }
948         assert_ne!(size, 0);
949 
950         let mut plugin_id = kAudioObjectUnknown;
951         let mut in_bundle_ref = cfstringref_from_static_string("com.apple.audio.CoreAudio");
952         let mut translation_value = AudioValueTranslation {
953             mInputData: &mut in_bundle_ref as *mut CFStringRef as *mut c_void,
954             mInputDataSize: mem::size_of::<CFStringRef>() as u32,
955             mOutputData: &mut plugin_id as *mut AudioObjectID as *mut c_void,
956             mOutputDataSize: mem::size_of::<AudioObjectID>() as u32,
957         };
958         assert_eq!(size, mem::size_of_val(&translation_value));
959 
960         let status = unsafe {
961             let status = AudioObjectGetPropertyData(
962                 kAudioObjectSystemObject,
963                 &address,
964                 0,
965                 ptr::null(),
966                 &mut size as *mut usize as *mut u32,
967                 &mut translation_value as *mut AudioValueTranslation as *mut c_void,
968             );
969             CFRelease(in_bundle_ref as *const c_void);
970             status
971         };
972         if status == NO_ERR {
973             assert_ne!(plugin_id, kAudioObjectUnknown);
974             Ok(plugin_id)
975         } else {
976             Err(status)
977         }
978     }
979 
980     // TODO: This doesn't work as what we expect when the default deivce in the scope is an
981     //       aggregate device. We should get the list of all the active sub devices and put
982     //       them into the array, if the device is an aggregate device. See the code in
983     //       AggregateDevice::get_sub_devices and audiounit_set_aggregate_sub_device_list.
get_sub_devices(scope: Scope) -> Option<CFArrayRef>984     fn get_sub_devices(scope: Scope) -> Option<CFArrayRef> {
985         let device = test_get_default_device(scope);
986         if device.is_none() {
987             return None;
988         }
989         let device = device.unwrap();
990         let uid = get_device_global_uid(device);
991         if uid.is_err() {
992             return None;
993         }
994         let uid = uid.unwrap();
995         unsafe {
996             let list = CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks);
997             let sub_device_dict = CFDictionaryCreateMutable(
998                 ptr::null(),
999                 0,
1000                 &kCFTypeDictionaryKeyCallBacks,
1001                 &kCFTypeDictionaryValueCallBacks,
1002             );
1003             CFDictionaryAddValue(
1004                 sub_device_dict,
1005                 cfstringref_from_static_string(SUB_DEVICE_UID_KEY) as *const c_void,
1006                 uid.get_raw() as *const c_void,
1007             );
1008             CFArrayAppendValue(list, sub_device_dict as *const c_void);
1009             CFRelease(sub_device_dict as *const c_void);
1010             Some(list)
1011         }
1012     }
1013 }
1014 
1015 impl Drop for TestDevicePlugger {
drop(&mut self)1016     fn drop(&mut self) {
1017         if self.is_plugging() {
1018             self.unplug();
1019         }
1020     }
1021 }
1022 
1023 // Test Templates
1024 // ------------------------------------------------------------------------------------------------
test_ops_context_operation<F>(name: &'static str, operation: F) where F: FnOnce(*mut ffi::cubeb),1025 pub fn test_ops_context_operation<F>(name: &'static str, operation: F)
1026 where
1027     F: FnOnce(*mut ffi::cubeb),
1028 {
1029     let name_c_string = CString::new(name).expect("Failed to create context name");
1030     let mut context = ptr::null_mut::<ffi::cubeb>();
1031     assert_eq!(
1032         unsafe { OPS.init.unwrap()(&mut context, name_c_string.as_ptr()) },
1033         ffi::CUBEB_OK
1034     );
1035     assert!(!context.is_null());
1036     operation(context);
1037     unsafe { OPS.destroy.unwrap()(context) }
1038 }
1039 
1040 // The in-out stream initializeed with different device will create an aggregate_device and
1041 // result in firing device-collection-changed callbacks. Run in-out streams with tests
1042 // capturing device-collection-changed callbacks may cause troubles.
test_ops_stream_operation<F>( name: &'static str, input_device: ffi::cubeb_devid, input_stream_params: *mut ffi::cubeb_stream_params, output_device: ffi::cubeb_devid, output_stream_params: *mut ffi::cubeb_stream_params, latency_frames: u32, data_callback: ffi::cubeb_data_callback, state_callback: ffi::cubeb_state_callback, user_ptr: *mut c_void, operation: F, ) where F: FnOnce(*mut ffi::cubeb_stream),1043 pub fn test_ops_stream_operation<F>(
1044     name: &'static str,
1045     input_device: ffi::cubeb_devid,
1046     input_stream_params: *mut ffi::cubeb_stream_params,
1047     output_device: ffi::cubeb_devid,
1048     output_stream_params: *mut ffi::cubeb_stream_params,
1049     latency_frames: u32,
1050     data_callback: ffi::cubeb_data_callback,
1051     state_callback: ffi::cubeb_state_callback,
1052     user_ptr: *mut c_void,
1053     operation: F,
1054 ) where
1055     F: FnOnce(*mut ffi::cubeb_stream),
1056 {
1057     test_ops_context_operation("context: stream operation", |context_ptr| {
1058         // Do nothing if there is no input/output device to perform input/output tests.
1059         if !input_stream_params.is_null() && test_get_default_device(Scope::Input).is_none() {
1060             println!("No input device to perform input tests for \"{}\".", name);
1061             return;
1062         }
1063 
1064         if !output_stream_params.is_null() && test_get_default_device(Scope::Output).is_none() {
1065             println!("No output device to perform output tests for \"{}\".", name);
1066             return;
1067         }
1068 
1069         let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
1070         let stream_name = CString::new(name).expect("Failed to create stream name");
1071         assert_eq!(
1072             unsafe {
1073                 OPS.stream_init.unwrap()(
1074                     context_ptr,
1075                     &mut stream,
1076                     stream_name.as_ptr(),
1077                     input_device,
1078                     input_stream_params,
1079                     output_device,
1080                     output_stream_params,
1081                     latency_frames,
1082                     data_callback,
1083                     state_callback,
1084                     user_ptr,
1085                 )
1086             },
1087             ffi::CUBEB_OK
1088         );
1089         assert!(!stream.is_null());
1090         operation(stream);
1091         unsafe {
1092             OPS.stream_destroy.unwrap()(stream);
1093         }
1094     });
1095 }
1096 
test_get_raw_context<F>(operation: F) where F: FnOnce(&mut AudioUnitContext),1097 pub fn test_get_raw_context<F>(operation: F)
1098 where
1099     F: FnOnce(&mut AudioUnitContext),
1100 {
1101     let mut context = AudioUnitContext::new();
1102     operation(&mut context);
1103 }
1104 
test_get_default_raw_stream<F>(operation: F) where F: FnOnce(&mut AudioUnitStream),1105 pub fn test_get_default_raw_stream<F>(operation: F)
1106 where
1107     F: FnOnce(&mut AudioUnitStream),
1108 {
1109     test_get_raw_stream(ptr::null_mut(), None, None, 0, operation);
1110 }
1111 
test_get_raw_stream<F>( user_ptr: *mut c_void, data_callback: ffi::cubeb_data_callback, state_callback: ffi::cubeb_state_callback, latency_frames: u32, operation: F, ) where F: FnOnce(&mut AudioUnitStream),1112 fn test_get_raw_stream<F>(
1113     user_ptr: *mut c_void,
1114     data_callback: ffi::cubeb_data_callback,
1115     state_callback: ffi::cubeb_state_callback,
1116     latency_frames: u32,
1117     operation: F,
1118 ) where
1119     F: FnOnce(&mut AudioUnitStream),
1120 {
1121     let mut context = AudioUnitContext::new();
1122 
1123     // Add a stream to the context since we are about to create one.
1124     // AudioUnitStream::drop() will check the context has at least one stream.
1125     let global_latency_frames = context.update_latency_by_adding_stream(latency_frames);
1126 
1127     let mut stream = AudioUnitStream::new(
1128         &mut context,
1129         user_ptr,
1130         data_callback,
1131         state_callback,
1132         global_latency_frames.unwrap(),
1133     );
1134     stream.core_stream_data = CoreStreamData::new(&stream, None, None);
1135 
1136     operation(&mut stream);
1137 }
1138 
test_get_stream_with_default_callbacks_by_type<F>( name: &'static str, stm_type: StreamType, input_device: Option<AudioObjectID>, output_device: Option<AudioObjectID>, data: *mut c_void, operation: F, ) where F: FnOnce(&mut AudioUnitStream),1139 pub fn test_get_stream_with_default_callbacks_by_type<F>(
1140     name: &'static str,
1141     stm_type: StreamType,
1142     input_device: Option<AudioObjectID>,
1143     output_device: Option<AudioObjectID>,
1144     data: *mut c_void,
1145     operation: F,
1146 ) where
1147     F: FnOnce(&mut AudioUnitStream),
1148 {
1149     let mut input_params = get_dummy_stream_params(Scope::Input);
1150     let mut output_params = get_dummy_stream_params(Scope::Output);
1151 
1152     let in_params = if stm_type.contains(StreamType::INPUT) {
1153         &mut input_params as *mut ffi::cubeb_stream_params
1154     } else {
1155         ptr::null_mut()
1156     };
1157     let out_params = if stm_type.contains(StreamType::OUTPUT) {
1158         &mut output_params as *mut ffi::cubeb_stream_params
1159     } else {
1160         ptr::null_mut()
1161     };
1162     let in_device = if let Some(id) = input_device {
1163         id as ffi::cubeb_devid
1164     } else {
1165         ptr::null_mut()
1166     };
1167     let out_device = if let Some(id) = output_device {
1168         id as ffi::cubeb_devid
1169     } else {
1170         ptr::null_mut()
1171     };
1172 
1173     test_ops_stream_operation_with_default_callbacks(
1174         name,
1175         in_device,
1176         in_params,
1177         out_device,
1178         out_params,
1179         data,
1180         |stream| {
1181             let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
1182             operation(stm);
1183         },
1184     );
1185 }
1186 
1187 bitflags! {
1188     pub struct StreamType: u8 {
1189         const INPUT = 0b01;
1190         const OUTPUT = 0b10;
1191         const DUPLEX = Self::INPUT.bits | Self::OUTPUT.bits;
1192     }
1193 }
1194 
get_dummy_stream_params(scope: Scope) -> ffi::cubeb_stream_params1195 fn get_dummy_stream_params(scope: Scope) -> ffi::cubeb_stream_params {
1196     // The stream format for input and output must be same.
1197     const STREAM_FORMAT: u32 = ffi::CUBEB_SAMPLE_FLOAT32NE;
1198 
1199     // Make sure the parameters meet the requirements of AudioUnitContext::stream_init
1200     // (in the comments).
1201     let mut stream_params = ffi::cubeb_stream_params::default();
1202     stream_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
1203     let (format, rate, channels, layout) = match scope {
1204         Scope::Input => (STREAM_FORMAT, 48000, 1, ffi::CUBEB_LAYOUT_MONO),
1205         Scope::Output => (STREAM_FORMAT, 44100, 2, ffi::CUBEB_LAYOUT_STEREO),
1206     };
1207     stream_params.format = format;
1208     stream_params.rate = rate;
1209     stream_params.channels = channels;
1210     stream_params.layout = layout;
1211     stream_params
1212 }
1213 
test_ops_stream_operation_with_default_callbacks<F>( name: &'static str, input_device: ffi::cubeb_devid, input_stream_params: *mut ffi::cubeb_stream_params, output_device: ffi::cubeb_devid, output_stream_params: *mut ffi::cubeb_stream_params, data: *mut c_void, operation: F, ) where F: FnOnce(*mut ffi::cubeb_stream),1214 fn test_ops_stream_operation_with_default_callbacks<F>(
1215     name: &'static str,
1216     input_device: ffi::cubeb_devid,
1217     input_stream_params: *mut ffi::cubeb_stream_params,
1218     output_device: ffi::cubeb_devid,
1219     output_stream_params: *mut ffi::cubeb_stream_params,
1220     data: *mut c_void,
1221     operation: F,
1222 ) where
1223     F: FnOnce(*mut ffi::cubeb_stream),
1224 {
1225     test_ops_stream_operation(
1226         name,
1227         input_device,
1228         input_stream_params,
1229         output_device,
1230         output_stream_params,
1231         4096, // TODO: Get latency by get_min_latency instead ?
1232         Some(data_callback),
1233         Some(state_callback),
1234         data,
1235         operation,
1236     );
1237 
1238     extern "C" fn state_callback(
1239         stream: *mut ffi::cubeb_stream,
1240         _user_ptr: *mut c_void,
1241         state: ffi::cubeb_state,
1242     ) {
1243         assert!(!stream.is_null());
1244         assert_ne!(state, ffi::CUBEB_STATE_ERROR);
1245     }
1246 
1247     extern "C" fn data_callback(
1248         stream: *mut ffi::cubeb_stream,
1249         _user_ptr: *mut c_void,
1250         _input_buffer: *const c_void,
1251         output_buffer: *mut c_void,
1252         nframes: i64,
1253     ) -> i64 {
1254         assert!(!stream.is_null());
1255 
1256         // Feed silence data to output buffer
1257         if !output_buffer.is_null() {
1258             let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
1259             let channels = stm.core_stream_data.output_stream_params.channels();
1260             let samples = nframes as usize * channels as usize;
1261             let sample_size = cubeb_sample_size(stm.core_stream_data.output_stream_params.format());
1262             unsafe {
1263                 ptr::write_bytes(output_buffer, 0, samples * sample_size);
1264             }
1265         }
1266 
1267         nframes
1268     }
1269 }
1270