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