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