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.
558 // This function returns an Ok containing the previous default device id on success.
559 // Otherwise, it returns an Err containing the error code with OSStatus type
test_set_default_device( device: AudioObjectID, scope: Scope, ) -> std::result::Result<AudioObjectID, OSStatus>560 pub fn test_set_default_device(
561     device: AudioObjectID,
562     scope: Scope,
563 ) -> std::result::Result<AudioObjectID, OSStatus> {
564     assert!(test_device_in_scope(device, scope.clone()));
565     let default = test_get_default_device(scope.clone()).unwrap();
566     if default == device {
567         // Do nothing if device is already the default device
568         return Ok(device);
569     }
570 
571     let address = AudioObjectPropertyAddress {
572         mSelector: match scope {
573             Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
574             Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
575         },
576         mScope: kAudioObjectPropertyScopeGlobal,
577         mElement: kAudioObjectPropertyElementMaster,
578     };
579     let size = mem::size_of::<AudioObjectID>();
580     let status = unsafe {
581         AudioObjectSetPropertyData(
582             kAudioObjectSystemObject,
583             &address,
584             0,
585             ptr::null(),
586             size as u32,
587             &device as *const AudioObjectID as *const c_void,
588         )
589     };
590     if status == NO_ERR {
591         Ok(default)
592     } else {
593         Err(status)
594     }
595 }
596 
597 pub struct TestDeviceSwitcher {
598     scope: Scope,
599     devices: Vec<AudioObjectID>,
600     current_device_index: usize,
601 }
602 
603 impl TestDeviceSwitcher {
new(scope: Scope) -> Self604     pub fn new(scope: Scope) -> Self {
605         let devices = test_get_devices_in_scope(scope.clone());
606         let current = test_get_default_device(scope.clone()).unwrap();
607         let index = devices
608             .iter()
609             .position(|device| *device == current)
610             .unwrap();
611         test_print_devices_in_scope(&devices, scope.clone());
612         Self {
613             scope: scope,
614             devices: devices,
615             current_device_index: index,
616         }
617     }
618 
next(&mut self)619     pub fn next(&mut self) {
620         let current = self.devices[self.current_device_index];
621         let next_index = (self.current_device_index + 1) % self.devices.len();
622         let next = self.devices[next_index];
623         println!(
624             "Switch device for {:?}: {} -> {}",
625             self.scope, current, next
626         );
627         let prev = self.set_device(next).unwrap();
628         assert_eq!(prev, current);
629         self.current_device_index = next_index;
630     }
631 
set_device(&self, device: AudioObjectID) -> std::result::Result<AudioObjectID, OSStatus>632     fn set_device(&self, device: AudioObjectID) -> std::result::Result<AudioObjectID, OSStatus> {
633         test_set_default_device(device, self.scope.clone())
634     }
635 }
636 
test_create_device_change_listener<F>(scope: Scope, listener: F) -> TestPropertyListener<F> where F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,637 pub fn test_create_device_change_listener<F>(scope: Scope, listener: F) -> TestPropertyListener<F>
638 where
639     F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
640 {
641     let address = AudioObjectPropertyAddress {
642         mSelector: match scope {
643             Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
644             Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
645         },
646         mScope: kAudioObjectPropertyScopeGlobal,
647         mElement: kAudioObjectPropertyElementMaster,
648     };
649     TestPropertyListener::new(kAudioObjectSystemObject, address, listener)
650 }
651 
652 pub struct TestPropertyListener<F>
653 where
654     F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
655 {
656     device: AudioObjectID,
657     property: AudioObjectPropertyAddress,
658     callback: F,
659 }
660 
661 impl<F> TestPropertyListener<F>
662 where
663     F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
664 {
new(device: AudioObjectID, property: AudioObjectPropertyAddress, callback: F) -> Self665     pub fn new(device: AudioObjectID, property: AudioObjectPropertyAddress, callback: F) -> Self {
666         Self {
667             device,
668             property,
669             callback,
670         }
671     }
672 
start(&self) -> std::result::Result<(), OSStatus>673     pub fn start(&self) -> std::result::Result<(), OSStatus> {
674         let status = unsafe {
675             AudioObjectAddPropertyListener(
676                 self.device,
677                 &self.property,
678                 Some(Self::render),
679                 self as *const Self as *mut c_void,
680             )
681         };
682         if status == NO_ERR {
683             Ok(())
684         } else {
685             Err(status)
686         }
687     }
688 
stop(&self) -> std::result::Result<(), OSStatus>689     pub fn stop(&self) -> std::result::Result<(), OSStatus> {
690         let status = unsafe {
691             AudioObjectRemovePropertyListener(
692                 self.device,
693                 &self.property,
694                 Some(Self::render),
695                 self as *const Self as *mut c_void,
696             )
697         };
698         if status == NO_ERR {
699             Ok(())
700         } else {
701             Err(status)
702         }
703     }
704 
render( id: AudioObjectID, number_of_addresses: u32, addresses: *const AudioObjectPropertyAddress, data: *mut c_void, ) -> OSStatus705     extern "C" fn render(
706         id: AudioObjectID,
707         number_of_addresses: u32,
708         addresses: *const AudioObjectPropertyAddress,
709         data: *mut c_void,
710     ) -> OSStatus {
711         let listener = unsafe { &*(data as *mut Self) };
712         assert_eq!(id, listener.device);
713         let addrs = unsafe { slice::from_raw_parts(addresses, number_of_addresses as usize) };
714         (listener.callback)(addrs)
715     }
716 }
717 
718 impl<F> Drop for TestPropertyListener<F>
719 where
720     F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
721 {
drop(&mut self)722     fn drop(&mut self) {
723         self.stop();
724     }
725 }
726 
727 // TODO: It doesn't work if default input or output is an aggregate device! Probably we need to do
728 //       the same thing as what audiounit_set_aggregate_sub_device_list does.
729 #[derive(Debug)]
730 pub struct TestDevicePlugger {
731     scope: Scope,
732     plugin_id: AudioObjectID,
733     device_id: AudioObjectID,
734 }
735 
736 impl TestDevicePlugger {
new(scope: Scope) -> std::result::Result<Self, OSStatus>737     pub fn new(scope: Scope) -> std::result::Result<Self, OSStatus> {
738         let plugin_id = Self::get_system_plugin_id()?;
739         Ok(Self {
740             scope,
741             plugin_id,
742             device_id: kAudioObjectUnknown,
743         })
744     }
745 
get_device_id(&self) -> AudioObjectID746     pub fn get_device_id(&self) -> AudioObjectID {
747         self.device_id
748     }
749 
plug(&mut self) -> std::result::Result<(), OSStatus>750     pub fn plug(&mut self) -> std::result::Result<(), OSStatus> {
751         self.device_id = self.create_aggregate_device()?;
752         Ok(())
753     }
754 
unplug(&mut self) -> std::result::Result<(), OSStatus>755     pub fn unplug(&mut self) -> std::result::Result<(), OSStatus> {
756         self.destroy_aggregate_device()
757     }
758 
is_plugging(&self) -> bool759     fn is_plugging(&self) -> bool {
760         self.device_id != kAudioObjectUnknown
761     }
762 
destroy_aggregate_device(&mut self) -> std::result::Result<(), OSStatus>763     fn destroy_aggregate_device(&mut self) -> std::result::Result<(), OSStatus> {
764         assert_ne!(self.plugin_id, kAudioObjectUnknown);
765         assert_ne!(self.device_id, kAudioObjectUnknown);
766 
767         let address = AudioObjectPropertyAddress {
768             mSelector: kAudioPlugInDestroyAggregateDevice,
769             mScope: kAudioObjectPropertyScopeGlobal,
770             mElement: kAudioObjectPropertyElementMaster,
771         };
772 
773         let mut size: usize = 0;
774         let status = unsafe {
775             AudioObjectGetPropertyDataSize(
776                 self.plugin_id,
777                 &address,
778                 0,
779                 ptr::null(),
780                 &mut size as *mut usize as *mut u32,
781             )
782         };
783         if status != NO_ERR {
784             return Err(status);
785         }
786         assert_ne!(size, 0);
787 
788         let status = unsafe {
789             // This call can simulate removing a device.
790             AudioObjectGetPropertyData(
791                 self.plugin_id,
792                 &address,
793                 0,
794                 ptr::null(),
795                 &mut size as *mut usize as *mut u32,
796                 &mut self.device_id as *mut AudioDeviceID as *mut c_void,
797             )
798         };
799         if status == NO_ERR {
800             self.device_id = kAudioObjectUnknown;
801             Ok(())
802         } else {
803             Err(status)
804         }
805     }
806 
create_aggregate_device(&self) -> std::result::Result<AudioObjectID, OSStatus>807     fn create_aggregate_device(&self) -> std::result::Result<AudioObjectID, OSStatus> {
808         use std::time::{SystemTime, UNIX_EPOCH};
809 
810         const TEST_AGGREGATE_DEVICE_NAME: &str = "TestAggregateDevice";
811 
812         assert_ne!(self.plugin_id, kAudioObjectUnknown);
813 
814         let sub_devices = Self::get_sub_devices(self.scope.clone());
815         if sub_devices.is_none() {
816             return Err(kAudioCodecUnspecifiedError as OSStatus);
817         }
818         let sub_devices = sub_devices.unwrap();
819 
820         let address = AudioObjectPropertyAddress {
821             mSelector: kAudioPlugInCreateAggregateDevice,
822             mScope: kAudioObjectPropertyScopeGlobal,
823             mElement: kAudioObjectPropertyElementMaster,
824         };
825 
826         let mut size: usize = 0;
827         let status = unsafe {
828             AudioObjectGetPropertyDataSize(
829                 self.plugin_id,
830                 &address,
831                 0,
832                 ptr::null(),
833                 &mut size as *mut usize as *mut u32,
834             )
835         };
836         if status != NO_ERR {
837             return Err(status);
838         }
839         assert_ne!(size, 0);
840 
841         let sys_time = SystemTime::now();
842         let time_id = sys_time.duration_since(UNIX_EPOCH).unwrap().as_nanos();
843         let device_name = format!("{}_{}", TEST_AGGREGATE_DEVICE_NAME, time_id);
844         let device_uid = format!("org.mozilla.{}", device_name);
845 
846         let mut device_id = kAudioObjectUnknown;
847         let status = unsafe {
848             let device_dict = CFDictionaryCreateMutable(
849                 kCFAllocatorDefault,
850                 0,
851                 &kCFTypeDictionaryKeyCallBacks,
852                 &kCFTypeDictionaryValueCallBacks,
853             );
854 
855             // Set the name of this device.
856             let device_name = cfstringref_from_string(&device_name);
857             CFDictionaryAddValue(
858                 device_dict,
859                 cfstringref_from_static_string(AGGREGATE_DEVICE_NAME_KEY) as *const c_void,
860                 device_name as *const c_void,
861             );
862             CFRelease(device_name as *const c_void);
863 
864             // Set the uid of this device.
865             let device_uid = cfstringref_from_string(&device_uid);
866             CFDictionaryAddValue(
867                 device_dict,
868                 cfstringref_from_static_string(AGGREGATE_DEVICE_UID_KEY) as *const c_void,
869                 device_uid as *const c_void,
870             );
871             CFRelease(device_uid as *const c_void);
872 
873             // This device is private to the process creating it.
874             let private_value: i32 = 1;
875             let device_private_key = CFNumberCreate(
876                 kCFAllocatorDefault,
877                 i64::from(kCFNumberIntType),
878                 &private_value as *const i32 as *const c_void,
879             );
880             CFDictionaryAddValue(
881                 device_dict,
882                 cfstringref_from_static_string(AGGREGATE_DEVICE_PRIVATE_KEY) as *const c_void,
883                 device_private_key as *const c_void,
884             );
885             CFRelease(device_private_key as *const c_void);
886 
887             // Set this device to be a stacked aggregate (i.e. multi-output device).
888             let stacked_value: i32 = 0; // 1 for normal aggregate device.
889             let device_stacked_key = CFNumberCreate(
890                 kCFAllocatorDefault,
891                 i64::from(kCFNumberIntType),
892                 &stacked_value as *const i32 as *const c_void,
893             );
894             CFDictionaryAddValue(
895                 device_dict,
896                 cfstringref_from_static_string(AGGREGATE_DEVICE_STACKED_KEY) as *const c_void,
897                 device_stacked_key as *const c_void,
898             );
899             CFRelease(device_stacked_key as *const c_void);
900 
901             // Set sub devices for this device.
902             CFDictionaryAddValue(
903                 device_dict,
904                 cfstringref_from_static_string(AGGREGATE_DEVICE_SUB_DEVICE_LIST_KEY)
905                     as *const c_void,
906                 sub_devices as *const c_void,
907             );
908             CFRelease(sub_devices as *const c_void);
909 
910             // This call can simulate adding a device.
911             let status = AudioObjectGetPropertyData(
912                 self.plugin_id,
913                 &address,
914                 mem::size_of_val(&device_dict) as u32,
915                 &device_dict as *const CFMutableDictionaryRef as *const c_void,
916                 &mut size as *mut usize as *mut u32,
917                 &mut device_id as *mut AudioDeviceID as *mut c_void,
918             );
919             CFRelease(device_dict as *const c_void);
920             status
921         };
922         if status == NO_ERR {
923             assert_ne!(device_id, kAudioObjectUnknown);
924             Ok(device_id)
925         } else {
926             Err(status)
927         }
928     }
929 
get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus>930     fn get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus> {
931         let address = AudioObjectPropertyAddress {
932             mSelector: kAudioHardwarePropertyPlugInForBundleID,
933             mScope: kAudioObjectPropertyScopeGlobal,
934             mElement: kAudioObjectPropertyElementMaster,
935         };
936 
937         let mut size: usize = 0;
938         let status = unsafe {
939             AudioObjectGetPropertyDataSize(
940                 kAudioObjectSystemObject,
941                 &address,
942                 0,
943                 ptr::null(),
944                 &mut size as *mut usize as *mut u32,
945             )
946         };
947         if status != NO_ERR {
948             return Err(status);
949         }
950         assert_ne!(size, 0);
951 
952         let mut plugin_id = kAudioObjectUnknown;
953         let mut in_bundle_ref = cfstringref_from_static_string("com.apple.audio.CoreAudio");
954         let mut translation_value = AudioValueTranslation {
955             mInputData: &mut in_bundle_ref as *mut CFStringRef as *mut c_void,
956             mInputDataSize: mem::size_of::<CFStringRef>() as u32,
957             mOutputData: &mut plugin_id as *mut AudioObjectID as *mut c_void,
958             mOutputDataSize: mem::size_of::<AudioObjectID>() as u32,
959         };
960         assert_eq!(size, mem::size_of_val(&translation_value));
961 
962         let status = unsafe {
963             let status = AudioObjectGetPropertyData(
964                 kAudioObjectSystemObject,
965                 &address,
966                 0,
967                 ptr::null(),
968                 &mut size as *mut usize as *mut u32,
969                 &mut translation_value as *mut AudioValueTranslation as *mut c_void,
970             );
971             CFRelease(in_bundle_ref as *const c_void);
972             status
973         };
974         if status == NO_ERR {
975             assert_ne!(plugin_id, kAudioObjectUnknown);
976             Ok(plugin_id)
977         } else {
978             Err(status)
979         }
980     }
981 
982     // TODO: This doesn't work as what we expect when the default deivce in the scope is an
983     //       aggregate device. We should get the list of all the active sub devices and put
984     //       them into the array, if the device is an aggregate device. See the code in
985     //       AggregateDevice::get_sub_devices and audiounit_set_aggregate_sub_device_list.
get_sub_devices(scope: Scope) -> Option<CFArrayRef>986     fn get_sub_devices(scope: Scope) -> Option<CFArrayRef> {
987         let device = test_get_default_device(scope);
988         if device.is_none() {
989             return None;
990         }
991         let device = device.unwrap();
992         let uid = get_device_global_uid(device);
993         if uid.is_err() {
994             return None;
995         }
996         let uid = uid.unwrap();
997         unsafe {
998             let list = CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks);
999             let sub_device_dict = CFDictionaryCreateMutable(
1000                 ptr::null(),
1001                 0,
1002                 &kCFTypeDictionaryKeyCallBacks,
1003                 &kCFTypeDictionaryValueCallBacks,
1004             );
1005             CFDictionaryAddValue(
1006                 sub_device_dict,
1007                 cfstringref_from_static_string(SUB_DEVICE_UID_KEY) as *const c_void,
1008                 uid.get_raw() as *const c_void,
1009             );
1010             CFArrayAppendValue(list, sub_device_dict as *const c_void);
1011             CFRelease(sub_device_dict as *const c_void);
1012             Some(list)
1013         }
1014     }
1015 }
1016 
1017 impl Drop for TestDevicePlugger {
drop(&mut self)1018     fn drop(&mut self) {
1019         if self.is_plugging() {
1020             self.unplug();
1021         }
1022     }
1023 }
1024 
1025 // Test Templates
1026 // ------------------------------------------------------------------------------------------------
test_ops_context_operation<F>(name: &'static str, operation: F) where F: FnOnce(*mut ffi::cubeb),1027 pub fn test_ops_context_operation<F>(name: &'static str, operation: F)
1028 where
1029     F: FnOnce(*mut ffi::cubeb),
1030 {
1031     let name_c_string = CString::new(name).expect("Failed to create context name");
1032     let mut context = ptr::null_mut::<ffi::cubeb>();
1033     assert_eq!(
1034         unsafe { OPS.init.unwrap()(&mut context, name_c_string.as_ptr()) },
1035         ffi::CUBEB_OK
1036     );
1037     assert!(!context.is_null());
1038     operation(context);
1039     unsafe { OPS.destroy.unwrap()(context) }
1040 }
1041 
1042 // The in-out stream initializeed with different device will create an aggregate_device and
1043 // result in firing device-collection-changed callbacks. Run in-out streams with tests
1044 // 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),1045 pub fn test_ops_stream_operation<F>(
1046     name: &'static str,
1047     input_device: ffi::cubeb_devid,
1048     input_stream_params: *mut ffi::cubeb_stream_params,
1049     output_device: ffi::cubeb_devid,
1050     output_stream_params: *mut ffi::cubeb_stream_params,
1051     latency_frames: u32,
1052     data_callback: ffi::cubeb_data_callback,
1053     state_callback: ffi::cubeb_state_callback,
1054     user_ptr: *mut c_void,
1055     operation: F,
1056 ) where
1057     F: FnOnce(*mut ffi::cubeb_stream),
1058 {
1059     test_ops_context_operation("context: stream operation", |context_ptr| {
1060         // Do nothing if there is no input/output device to perform input/output tests.
1061         if !input_stream_params.is_null() && test_get_default_device(Scope::Input).is_none() {
1062             println!("No input device to perform input tests for \"{}\".", name);
1063             return;
1064         }
1065 
1066         if !output_stream_params.is_null() && test_get_default_device(Scope::Output).is_none() {
1067             println!("No output device to perform output tests for \"{}\".", name);
1068             return;
1069         }
1070 
1071         let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
1072         let stream_name = CString::new(name).expect("Failed to create stream name");
1073         assert_eq!(
1074             unsafe {
1075                 OPS.stream_init.unwrap()(
1076                     context_ptr,
1077                     &mut stream,
1078                     stream_name.as_ptr(),
1079                     input_device,
1080                     input_stream_params,
1081                     output_device,
1082                     output_stream_params,
1083                     latency_frames,
1084                     data_callback,
1085                     state_callback,
1086                     user_ptr,
1087                 )
1088             },
1089             ffi::CUBEB_OK
1090         );
1091         assert!(!stream.is_null());
1092         operation(stream);
1093         unsafe {
1094             OPS.stream_destroy.unwrap()(stream);
1095         }
1096     });
1097 }
1098 
test_get_raw_context<F>(operation: F) where F: FnOnce(&mut AudioUnitContext),1099 pub fn test_get_raw_context<F>(operation: F)
1100 where
1101     F: FnOnce(&mut AudioUnitContext),
1102 {
1103     let mut context = AudioUnitContext::new();
1104     operation(&mut context);
1105 }
1106 
test_get_default_raw_stream<F>(operation: F) where F: FnOnce(&mut AudioUnitStream),1107 pub fn test_get_default_raw_stream<F>(operation: F)
1108 where
1109     F: FnOnce(&mut AudioUnitStream),
1110 {
1111     test_get_raw_stream(ptr::null_mut(), None, None, 0, operation);
1112 }
1113 
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),1114 fn test_get_raw_stream<F>(
1115     user_ptr: *mut c_void,
1116     data_callback: ffi::cubeb_data_callback,
1117     state_callback: ffi::cubeb_state_callback,
1118     latency_frames: u32,
1119     operation: F,
1120 ) where
1121     F: FnOnce(&mut AudioUnitStream),
1122 {
1123     let mut context = AudioUnitContext::new();
1124 
1125     // Add a stream to the context since we are about to create one.
1126     // AudioUnitStream::drop() will check the context has at least one stream.
1127     let global_latency_frames = context.update_latency_by_adding_stream(latency_frames);
1128 
1129     let mut stream = AudioUnitStream::new(
1130         &mut context,
1131         user_ptr,
1132         data_callback,
1133         state_callback,
1134         global_latency_frames.unwrap(),
1135     );
1136     stream.core_stream_data = CoreStreamData::new(&stream, None, None);
1137 
1138     operation(&mut stream);
1139 }
1140 
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),1141 pub fn test_get_stream_with_default_callbacks_by_type<F>(
1142     name: &'static str,
1143     stm_type: StreamType,
1144     input_device: Option<AudioObjectID>,
1145     output_device: Option<AudioObjectID>,
1146     data: *mut c_void,
1147     operation: F,
1148 ) where
1149     F: FnOnce(&mut AudioUnitStream),
1150 {
1151     let mut input_params = get_dummy_stream_params(Scope::Input);
1152     let mut output_params = get_dummy_stream_params(Scope::Output);
1153 
1154     let in_params = if stm_type.contains(StreamType::INPUT) {
1155         &mut input_params as *mut ffi::cubeb_stream_params
1156     } else {
1157         ptr::null_mut()
1158     };
1159     let out_params = if stm_type.contains(StreamType::OUTPUT) {
1160         &mut output_params as *mut ffi::cubeb_stream_params
1161     } else {
1162         ptr::null_mut()
1163     };
1164     let in_device = if let Some(id) = input_device {
1165         id as ffi::cubeb_devid
1166     } else {
1167         ptr::null_mut()
1168     };
1169     let out_device = if let Some(id) = output_device {
1170         id as ffi::cubeb_devid
1171     } else {
1172         ptr::null_mut()
1173     };
1174 
1175     test_ops_stream_operation_with_default_callbacks(
1176         name,
1177         in_device,
1178         in_params,
1179         out_device,
1180         out_params,
1181         data,
1182         |stream| {
1183             let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
1184             operation(stm);
1185         },
1186     );
1187 }
1188 
1189 bitflags! {
1190     pub struct StreamType: u8 {
1191         const INPUT = 0b01;
1192         const OUTPUT = 0b10;
1193         const DUPLEX = Self::INPUT.bits | Self::OUTPUT.bits;
1194     }
1195 }
1196 
get_dummy_stream_params(scope: Scope) -> ffi::cubeb_stream_params1197 fn get_dummy_stream_params(scope: Scope) -> ffi::cubeb_stream_params {
1198     // The stream format for input and output must be same.
1199     const STREAM_FORMAT: u32 = ffi::CUBEB_SAMPLE_FLOAT32NE;
1200 
1201     // Make sure the parameters meet the requirements of AudioUnitContext::stream_init
1202     // (in the comments).
1203     let mut stream_params = ffi::cubeb_stream_params::default();
1204     stream_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
1205     let (format, rate, channels, layout) = match scope {
1206         Scope::Input => (STREAM_FORMAT, 48000, 1, ffi::CUBEB_LAYOUT_MONO),
1207         Scope::Output => (STREAM_FORMAT, 44100, 2, ffi::CUBEB_LAYOUT_STEREO),
1208     };
1209     stream_params.format = format;
1210     stream_params.rate = rate;
1211     stream_params.channels = channels;
1212     stream_params.layout = layout;
1213     stream_params
1214 }
1215 
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),1216 fn test_ops_stream_operation_with_default_callbacks<F>(
1217     name: &'static str,
1218     input_device: ffi::cubeb_devid,
1219     input_stream_params: *mut ffi::cubeb_stream_params,
1220     output_device: ffi::cubeb_devid,
1221     output_stream_params: *mut ffi::cubeb_stream_params,
1222     data: *mut c_void,
1223     operation: F,
1224 ) where
1225     F: FnOnce(*mut ffi::cubeb_stream),
1226 {
1227     test_ops_stream_operation(
1228         name,
1229         input_device,
1230         input_stream_params,
1231         output_device,
1232         output_stream_params,
1233         4096, // TODO: Get latency by get_min_latency instead ?
1234         Some(data_callback),
1235         Some(state_callback),
1236         data,
1237         operation,
1238     );
1239 
1240     extern "C" fn state_callback(
1241         stream: *mut ffi::cubeb_stream,
1242         _user_ptr: *mut c_void,
1243         state: ffi::cubeb_state,
1244     ) {
1245         assert!(!stream.is_null());
1246         assert_ne!(state, ffi::CUBEB_STATE_ERROR);
1247     }
1248 
1249     extern "C" fn data_callback(
1250         stream: *mut ffi::cubeb_stream,
1251         _user_ptr: *mut c_void,
1252         _input_buffer: *const c_void,
1253         output_buffer: *mut c_void,
1254         nframes: i64,
1255     ) -> i64 {
1256         assert!(!stream.is_null());
1257 
1258         // Feed silence data to output buffer
1259         if !output_buffer.is_null() {
1260             let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
1261             let channels = stm.core_stream_data.output_stream_params.channels();
1262             let samples = nframes as usize * channels as usize;
1263             let sample_size = cubeb_sample_size(stm.core_stream_data.output_stream_params.format());
1264             unsafe {
1265                 ptr::write_bytes(output_buffer, 0, samples * sample_size);
1266             }
1267         }
1268 
1269         nframes
1270     }
1271 }
1272