summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs')
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs1663
1 files changed, 1663 insertions, 0 deletions
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs
new file mode 100644
index 0000000000..4cd86c094e
--- /dev/null
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs
@@ -0,0 +1,1663 @@
+use super::utils::{
+ test_audiounit_get_buffer_frame_size, test_audiounit_scope_is_enabled, test_create_audiounit,
+ test_device_channels_in_scope, test_device_in_scope, test_get_all_devices,
+ test_get_default_audiounit, test_get_default_device, test_get_default_raw_stream,
+ test_get_devices_in_scope, test_get_raw_context, ComponentSubType, DeviceFilter, PropertyScope,
+ Scope,
+};
+use super::*;
+
+// make_sized_audio_channel_layout
+// ------------------------------------
+#[test]
+fn test_make_sized_audio_channel_layout() {
+ for channels in 1..10 {
+ let size = mem::size_of::<AudioChannelLayout>()
+ + (channels - 1) * mem::size_of::<AudioChannelDescription>();
+ let _ = make_sized_audio_channel_layout(size);
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_make_sized_audio_channel_layout_with_wrong_size() {
+ // let _ = make_sized_audio_channel_layout(0);
+ let one_channel_size = mem::size_of::<AudioChannelLayout>();
+ let padding_size = 10;
+ assert_ne!(mem::size_of::<AudioChannelDescription>(), padding_size);
+ let wrong_size = one_channel_size + padding_size;
+ let _ = make_sized_audio_channel_layout(wrong_size);
+}
+
+// active_streams
+// update_latency_by_adding_stream
+// update_latency_by_removing_stream
+// ------------------------------------
+#[test]
+fn test_increase_and_decrease_context_streams() {
+ use std::thread;
+ const STREAMS: u32 = 10;
+
+ let context = AudioUnitContext::new();
+ let context_ptr_value = &context as *const AudioUnitContext as usize;
+
+ let mut join_handles = vec![];
+ for i in 0..STREAMS {
+ join_handles.push(thread::spawn(move || {
+ let context = unsafe { &*(context_ptr_value as *const AudioUnitContext) };
+
+ context.update_latency_by_adding_stream(i)
+ }));
+ }
+ let mut latencies = vec![];
+ for handle in join_handles {
+ latencies.push(handle.join().unwrap());
+ }
+ assert_eq!(context.active_streams(), STREAMS);
+ check_streams(&context, STREAMS);
+
+ check_latency(&context, latencies[0]);
+ for i in 0..latencies.len() - 1 {
+ assert_eq!(latencies[i], latencies[i + 1]);
+ }
+
+ let mut join_handles = vec![];
+ for _ in 0..STREAMS {
+ join_handles.push(thread::spawn(move || {
+ let context = unsafe { &*(context_ptr_value as *const AudioUnitContext) };
+ context.update_latency_by_removing_stream();
+ }));
+ }
+ for handle in join_handles {
+ let _ = handle.join();
+ }
+ check_streams(&context, 0);
+
+ check_latency(&context, None);
+}
+
+fn check_streams(context: &AudioUnitContext, number: u32) {
+ let guard = context.latency_controller.lock().unwrap();
+ assert_eq!(guard.streams, number);
+}
+
+fn check_latency(context: &AudioUnitContext, latency: Option<u32>) {
+ let guard = context.latency_controller.lock().unwrap();
+ assert_eq!(guard.latency, latency);
+}
+
+// make_silent
+// ------------------------------------
+#[test]
+fn test_make_silent() {
+ let mut array = allocate_array::<u32>(10);
+ for data in array.iter_mut() {
+ *data = 0xFFFF;
+ }
+
+ let mut buffer = AudioBuffer::default();
+ buffer.mData = array.as_mut_ptr() as *mut c_void;
+ buffer.mDataByteSize = (array.len() * mem::size_of::<u32>()) as u32;
+ buffer.mNumberChannels = 1;
+
+ audiounit_make_silent(&mut buffer);
+ for data in array {
+ assert_eq!(data, 0);
+ }
+}
+
+// minimum_resampling_input_frames
+// ------------------------------------
+#[test]
+fn test_minimum_resampling_input_frames() {
+ let input_rate = 48000_f64;
+ let output_rate = 44100_f64;
+
+ let frames = 100;
+ let times = input_rate / output_rate;
+ let expected = (frames as f64 * times).ceil() as usize;
+
+ assert_eq!(
+ minimum_resampling_input_frames(input_rate, output_rate, frames),
+ expected
+ );
+}
+
+#[test]
+#[should_panic]
+fn test_minimum_resampling_input_frames_zero_input_rate() {
+ minimum_resampling_input_frames(0_f64, 44100_f64, 1);
+}
+
+#[test]
+#[should_panic]
+fn test_minimum_resampling_input_frames_zero_output_rate() {
+ minimum_resampling_input_frames(48000_f64, 0_f64, 1);
+}
+
+#[test]
+fn test_minimum_resampling_input_frames_equal_input_output_rate() {
+ let frames = 100;
+ assert_eq!(
+ minimum_resampling_input_frames(44100_f64, 44100_f64, frames),
+ frames
+ );
+}
+
+// create_device_info
+// ------------------------------------
+#[test]
+fn test_create_device_info_from_unknown_input_device() {
+ if let Some(default_device_id) = test_get_default_device(Scope::Input) {
+ let default_device = create_device_info(kAudioObjectUnknown, DeviceType::INPUT).unwrap();
+ assert_eq!(default_device.id, default_device_id);
+ assert_eq!(
+ default_device.flags,
+ device_flags::DEV_INPUT | device_flags::DEV_SELECTED_DEFAULT
+ );
+ } else {
+ println!("No input device to perform test.");
+ }
+}
+
+#[test]
+fn test_create_device_info_from_unknown_output_device() {
+ if let Some(default_device_id) = test_get_default_device(Scope::Output) {
+ let default_device = create_device_info(kAudioObjectUnknown, DeviceType::OUTPUT).unwrap();
+ assert_eq!(default_device.id, default_device_id);
+ assert_eq!(
+ default_device.flags,
+ device_flags::DEV_OUTPUT | device_flags::DEV_SELECTED_DEFAULT
+ );
+ } else {
+ println!("No output device to perform test.");
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_set_device_info_to_system_input_device() {
+ let _device = create_device_info(kAudioObjectSystemObject, DeviceType::INPUT);
+}
+
+#[test]
+#[should_panic]
+fn test_set_device_info_to_system_output_device() {
+ let _device = create_device_info(kAudioObjectSystemObject, DeviceType::OUTPUT);
+}
+
+// FIXME: Is it ok to set input device to a nonexistent device ?
+#[ignore]
+#[test]
+#[should_panic]
+fn test_set_device_info_to_nonexistent_input_device() {
+ let nonexistent_id = std::u32::MAX;
+ let _device = create_device_info(nonexistent_id, DeviceType::INPUT);
+}
+
+// FIXME: Is it ok to set output device to a nonexistent device ?
+#[ignore]
+#[test]
+#[should_panic]
+fn test_set_device_info_to_nonexistent_output_device() {
+ let nonexistent_id = std::u32::MAX;
+ let _device = create_device_info(nonexistent_id, DeviceType::OUTPUT);
+}
+
+// add_listener (for default output device)
+// ------------------------------------
+#[test]
+fn test_add_listener_unknown_device() {
+ extern "C" fn callback(
+ _id: AudioObjectID,
+ _number_of_addresses: u32,
+ _addresses: *const AudioObjectPropertyAddress,
+ _data: *mut c_void,
+ ) -> OSStatus {
+ assert!(false, "Should not be called.");
+ kAudioHardwareUnspecifiedError as OSStatus
+ }
+
+ test_get_default_raw_stream(|stream| {
+ let listener = device_property_listener::new(
+ kAudioObjectUnknown,
+ get_property_address(
+ Property::HardwareDefaultOutputDevice,
+ DeviceType::INPUT | DeviceType::OUTPUT,
+ ),
+ callback,
+ );
+ let mut res: OSStatus = 0;
+ stream
+ .queue
+ .run_sync(|| res = stream.add_device_listener(&listener));
+ assert_eq!(res, kAudioHardwareBadObjectError as OSStatus);
+ });
+}
+
+// remove_listener (for default output device)
+// ------------------------------------
+#[test]
+fn test_add_listener_then_remove_system_device() {
+ extern "C" fn callback(
+ _id: AudioObjectID,
+ _number_of_addresses: u32,
+ _addresses: *const AudioObjectPropertyAddress,
+ _data: *mut c_void,
+ ) -> OSStatus {
+ assert!(false, "Should not be called.");
+ kAudioHardwareUnspecifiedError as OSStatus
+ }
+
+ test_get_default_raw_stream(|stream| {
+ let listener = device_property_listener::new(
+ kAudioObjectSystemObject,
+ get_property_address(
+ Property::HardwareDefaultOutputDevice,
+ DeviceType::INPUT | DeviceType::OUTPUT,
+ ),
+ callback,
+ );
+ let mut res: OSStatus = 0;
+ stream
+ .queue
+ .run_sync(|| res = stream.add_device_listener(&listener));
+ assert_eq!(res, NO_ERR);
+ stream
+ .queue
+ .run_sync(|| res = stream.remove_device_listener(&listener));
+ assert_eq!(res, NO_ERR);
+ });
+}
+
+#[test]
+fn test_remove_listener_without_adding_any_listener_before_system_device() {
+ extern "C" fn callback(
+ _id: AudioObjectID,
+ _number_of_addresses: u32,
+ _addresses: *const AudioObjectPropertyAddress,
+ _data: *mut c_void,
+ ) -> OSStatus {
+ assert!(false, "Should not be called.");
+ kAudioHardwareUnspecifiedError as OSStatus
+ }
+
+ test_get_default_raw_stream(|stream| {
+ let listener = device_property_listener::new(
+ kAudioObjectSystemObject,
+ get_property_address(
+ Property::HardwareDefaultOutputDevice,
+ DeviceType::INPUT | DeviceType::OUTPUT,
+ ),
+ callback,
+ );
+ let mut res: OSStatus = 0;
+ stream
+ .queue
+ .run_sync(|| res = stream.remove_device_listener(&listener));
+ assert_eq!(res, NO_ERR);
+ });
+}
+
+#[test]
+fn test_remove_listener_unknown_device() {
+ extern "C" fn callback(
+ _id: AudioObjectID,
+ _number_of_addresses: u32,
+ _addresses: *const AudioObjectPropertyAddress,
+ _data: *mut c_void,
+ ) -> OSStatus {
+ assert!(false, "Should not be called.");
+ kAudioHardwareUnspecifiedError as OSStatus
+ }
+
+ test_get_default_raw_stream(|stream| {
+ let listener = device_property_listener::new(
+ kAudioObjectUnknown,
+ get_property_address(
+ Property::HardwareDefaultOutputDevice,
+ DeviceType::INPUT | DeviceType::OUTPUT,
+ ),
+ callback,
+ );
+ let mut res: OSStatus = 0;
+ stream
+ .queue
+ .run_sync(|| res = stream.remove_device_listener(&listener));
+ assert_eq!(res, kAudioHardwareBadObjectError as OSStatus);
+ });
+}
+
+// get_default_device_id
+// ------------------------------------
+#[test]
+fn test_get_default_device_id() {
+ if test_get_default_device(Scope::Input).is_some() {
+ assert_ne!(
+ get_default_device_id(DeviceType::INPUT).unwrap(),
+ kAudioObjectUnknown,
+ );
+ }
+
+ if test_get_default_device(Scope::Output).is_some() {
+ assert_ne!(
+ get_default_device_id(DeviceType::OUTPUT).unwrap(),
+ kAudioObjectUnknown,
+ );
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_get_default_device_id_with_unknown_type() {
+ assert!(get_default_device_id(DeviceType::UNKNOWN).is_err());
+}
+
+#[test]
+#[should_panic]
+fn test_get_default_device_id_with_inout_type() {
+ assert!(get_default_device_id(DeviceType::INPUT | DeviceType::OUTPUT).is_err());
+}
+
+// convert_channel_layout
+// ------------------------------------
+#[test]
+fn test_convert_channel_layout() {
+ let pairs = [
+ (vec![kAudioObjectUnknown], vec![mixer::Channel::Silence]),
+ (
+ vec![kAudioChannelLabel_Mono],
+ vec![mixer::Channel::FrontCenter],
+ ),
+ (
+ vec![kAudioChannelLabel_Mono, kAudioChannelLabel_LFEScreen],
+ vec![mixer::Channel::FrontCenter, mixer::Channel::LowFrequency],
+ ),
+ (
+ vec![kAudioChannelLabel_Left, kAudioChannelLabel_Right],
+ vec![mixer::Channel::FrontLeft, mixer::Channel::FrontRight],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Unknown,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::Silence,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Unused,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::Silence,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_ForeignLanguage,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::Silence,
+ ],
+ ),
+ // The SMPTE layouts.
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_LFEScreen,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::LowFrequency,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::FrontCenter,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_LFEScreen,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::FrontCenter,
+ mixer::Channel::LowFrequency,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_CenterSurround,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::BackCenter,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_CenterSurround,
+ kAudioChannelLabel_LFEScreen,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::BackCenter,
+ mixer::Channel::LowFrequency,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_CenterSurround,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::FrontCenter,
+ mixer::Channel::BackCenter,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_CenterSurround,
+ kAudioChannelLabel_LFEScreen,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::FrontCenter,
+ mixer::Channel::BackCenter,
+ mixer::Channel::LowFrequency,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_LeftSurroundDirect,
+ kAudioChannelLabel_RightSurroundDirect,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::SideLeft,
+ mixer::Channel::SideRight,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_LeftSurroundDirect,
+ kAudioChannelLabel_RightSurroundDirect,
+ kAudioChannelLabel_LFEScreen,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::SideLeft,
+ mixer::Channel::SideRight,
+ mixer::Channel::LowFrequency,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_LeftSurround,
+ kAudioChannelLabel_RightSurround,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::BackLeft,
+ mixer::Channel::BackRight,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_LeftSurround,
+ kAudioChannelLabel_RightSurround,
+ kAudioChannelLabel_LFEScreen,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::BackLeft,
+ mixer::Channel::BackRight,
+ mixer::Channel::LowFrequency,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_LeftSurroundDirect,
+ kAudioChannelLabel_RightSurroundDirect,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::FrontCenter,
+ mixer::Channel::SideLeft,
+ mixer::Channel::SideRight,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_LeftSurroundDirect,
+ kAudioChannelLabel_RightSurroundDirect,
+ kAudioChannelLabel_LFEScreen,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::FrontCenter,
+ mixer::Channel::SideLeft,
+ mixer::Channel::SideRight,
+ mixer::Channel::LowFrequency,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_LeftSurround,
+ kAudioChannelLabel_RightSurround,
+ kAudioChannelLabel_Center,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::BackLeft,
+ mixer::Channel::BackRight,
+ mixer::Channel::FrontCenter,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_LeftSurround,
+ kAudioChannelLabel_RightSurround,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_LFEScreen,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::BackLeft,
+ mixer::Channel::BackRight,
+ mixer::Channel::FrontCenter,
+ mixer::Channel::LowFrequency,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_LFEScreen,
+ kAudioChannelLabel_CenterSurround,
+ kAudioChannelLabel_LeftSurroundDirect,
+ kAudioChannelLabel_RightSurroundDirect,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::FrontCenter,
+ mixer::Channel::LowFrequency,
+ mixer::Channel::BackCenter,
+ mixer::Channel::SideLeft,
+ mixer::Channel::SideRight,
+ ],
+ ),
+ (
+ vec![
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_LFEScreen,
+ kAudioChannelLabel_LeftSurround,
+ kAudioChannelLabel_RightSurround,
+ kAudioChannelLabel_LeftSurroundDirect,
+ kAudioChannelLabel_RightSurroundDirect,
+ ],
+ vec![
+ mixer::Channel::FrontLeft,
+ mixer::Channel::FrontRight,
+ mixer::Channel::FrontCenter,
+ mixer::Channel::LowFrequency,
+ mixer::Channel::BackLeft,
+ mixer::Channel::BackRight,
+ mixer::Channel::SideLeft,
+ mixer::Channel::SideRight,
+ ],
+ ),
+ ];
+
+ const MAX_CHANNELS: usize = 10;
+ // A Rust mapping structure of the AudioChannelLayout with MAX_CHANNELS channels
+ // https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/CoreAudioTypes.h#L1332
+ #[repr(C)]
+ struct TestLayout {
+ tag: AudioChannelLayoutTag,
+ map: AudioChannelBitmap,
+ number_channel_descriptions: UInt32,
+ channel_descriptions: [AudioChannelDescription; MAX_CHANNELS],
+ }
+
+ impl Default for TestLayout {
+ fn default() -> Self {
+ Self {
+ tag: AudioChannelLayoutTag::default(),
+ map: AudioChannelBitmap::default(),
+ number_channel_descriptions: UInt32::default(),
+ channel_descriptions: [AudioChannelDescription::default(); MAX_CHANNELS],
+ }
+ }
+ }
+
+ let mut layout = TestLayout::default();
+ layout.tag = kAudioChannelLayoutTag_UseChannelDescriptions;
+
+ for (labels, expected_layout) in pairs.iter() {
+ assert!(labels.len() <= MAX_CHANNELS);
+ layout.number_channel_descriptions = labels.len() as u32;
+ for (idx, label) in labels.iter().enumerate() {
+ layout.channel_descriptions[idx].mChannelLabel = *label;
+ }
+ let layout_ref = unsafe { &(*(&layout as *const TestLayout as *const AudioChannelLayout)) };
+ assert_eq!(
+ &audiounit_convert_channel_layout(layout_ref).unwrap(),
+ expected_layout
+ );
+ }
+}
+
+// get_preferred_channel_layout
+// ------------------------------------
+#[test]
+fn test_get_preferred_channel_layout_output() {
+ match test_get_default_audiounit(Scope::Output) {
+ Some(unit) => assert!(!audiounit_get_preferred_channel_layout(unit.get_inner())
+ .unwrap()
+ .is_empty()),
+ None => println!("No output audiounit for test."),
+ }
+}
+
+// get_current_channel_layout
+// ------------------------------------
+#[test]
+fn test_get_current_channel_layout_output() {
+ match test_get_default_audiounit(Scope::Output) {
+ Some(unit) => assert!(!audiounit_get_current_channel_layout(unit.get_inner())
+ .unwrap()
+ .is_empty()),
+ None => println!("No output audiounit for test."),
+ }
+}
+
+// create_stream_description
+// ------------------------------------
+#[test]
+fn test_create_stream_description() {
+ let mut channels = 0;
+ for (bits, format, flags) in [
+ (
+ 16_u32,
+ ffi::CUBEB_SAMPLE_S16LE,
+ kAudioFormatFlagIsSignedInteger,
+ ),
+ (
+ 16_u32,
+ ffi::CUBEB_SAMPLE_S16BE,
+ kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian,
+ ),
+ (32_u32, ffi::CUBEB_SAMPLE_FLOAT32LE, kAudioFormatFlagIsFloat),
+ (
+ 32_u32,
+ ffi::CUBEB_SAMPLE_FLOAT32BE,
+ kAudioFormatFlagIsFloat | kAudioFormatFlagIsBigEndian,
+ ),
+ ]
+ .iter()
+ {
+ let bytes = bits / 8;
+ channels += 1;
+
+ let mut raw = ffi::cubeb_stream_params::default();
+ raw.format = *format;
+ raw.rate = 48_000;
+ raw.channels = channels;
+ raw.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ raw.prefs = ffi::CUBEB_STREAM_PREF_NONE;
+ let params = StreamParams::from(raw);
+ let description = create_stream_description(&params).unwrap();
+ assert_eq!(description.mFormatID, kAudioFormatLinearPCM);
+ assert_eq!(
+ description.mFormatFlags,
+ flags | kLinearPCMFormatFlagIsPacked
+ );
+ assert_eq!(description.mSampleRate as u32, raw.rate);
+ assert_eq!(description.mChannelsPerFrame, raw.channels);
+ assert_eq!(description.mBytesPerFrame, bytes * raw.channels);
+ assert_eq!(description.mFramesPerPacket, 1);
+ assert_eq!(description.mBytesPerPacket, bytes * raw.channels);
+ assert_eq!(description.mReserved, 0);
+ }
+}
+
+// create_blank_audiounit
+// ------------------------------------
+#[test]
+fn test_create_blank_audiounit() {
+ let unit = create_blank_audiounit().unwrap();
+ assert!(!unit.is_null());
+ // Destroy the AudioUnit
+ unsafe {
+ AudioUnitUninitialize(unit);
+ AudioComponentInstanceDispose(unit);
+ }
+}
+
+// enable_audiounit_scope
+// ------------------------------------
+#[test]
+fn test_enable_audiounit_scope() {
+ // It's ok to enable and disable the scopes of input or output
+ // for the unit whose subtype is kAudioUnitSubType_HALOutput
+ // even when there is no available input or output devices.
+ if let Some(unit) = test_create_audiounit(ComponentSubType::HALOutput) {
+ assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true).is_ok());
+ assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false).is_ok());
+ assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true).is_ok());
+ assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false).is_ok());
+ } else {
+ println!("No audiounit to perform test.");
+ }
+}
+
+#[test]
+fn test_enable_audiounit_scope_for_default_output_unit() {
+ if let Some(unit) = test_create_audiounit(ComponentSubType::DefaultOutput) {
+ assert_eq!(
+ enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true).unwrap_err(),
+ kAudioUnitErr_InvalidProperty
+ );
+ assert_eq!(
+ enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false).unwrap_err(),
+ kAudioUnitErr_InvalidProperty
+ );
+ assert_eq!(
+ enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true).unwrap_err(),
+ kAudioUnitErr_InvalidProperty
+ );
+ assert_eq!(
+ enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false).unwrap_err(),
+ kAudioUnitErr_InvalidProperty
+ );
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_enable_audiounit_scope_with_null_unit() {
+ let unit: AudioUnit = ptr::null_mut();
+ assert!(enable_audiounit_scope(unit, DeviceType::INPUT, false).is_err());
+}
+
+// create_audiounit
+// ------------------------------------
+#[test]
+fn test_for_create_audiounit() {
+ let flags_list = [device_flags::DEV_INPUT, device_flags::DEV_OUTPUT];
+
+ let default_input = test_get_default_device(Scope::Input);
+ let default_output = test_get_default_device(Scope::Output);
+
+ for flags in flags_list.iter() {
+ let mut device = device_info::default();
+ device.flags |= *flags;
+
+ // Check the output scope is enabled.
+ if device.flags.contains(device_flags::DEV_OUTPUT) && default_output.is_some() {
+ device.id = default_output.unwrap();
+ let unit = create_audiounit(&device).unwrap();
+ assert!(!unit.is_null());
+ assert!(test_audiounit_scope_is_enabled(unit, Scope::Output));
+
+ // Destroy the AudioUnit.
+ unsafe {
+ AudioUnitUninitialize(unit);
+ AudioComponentInstanceDispose(unit);
+ }
+ }
+
+ // Check the input scope is enabled.
+ if device.flags.contains(device_flags::DEV_INPUT) && default_input.is_some() {
+ let device_id = default_input.unwrap();
+ device.id = device_id;
+ let unit = create_audiounit(&device).unwrap();
+ assert!(!unit.is_null());
+ assert!(test_audiounit_scope_is_enabled(unit, Scope::Input));
+ // Destroy the AudioUnit.
+ unsafe {
+ AudioUnitUninitialize(unit);
+ AudioComponentInstanceDispose(unit);
+ }
+ }
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_create_audiounit_with_unknown_scope() {
+ let device = device_info::default();
+ let _unit = create_audiounit(&device);
+}
+
+// set_buffer_size_sync
+// ------------------------------------
+#[test]
+fn test_set_buffer_size_sync() {
+ test_set_buffer_size_by_scope(Scope::Input);
+ test_set_buffer_size_by_scope(Scope::Output);
+ fn test_set_buffer_size_by_scope(scope: Scope) {
+ let unit = test_get_default_audiounit(scope.clone());
+ if unit.is_none() {
+ println!("No audiounit for {:?}.", scope);
+ return;
+ }
+ let unit = unit.unwrap();
+ let prop_scope = match scope {
+ Scope::Input => PropertyScope::Output,
+ Scope::Output => PropertyScope::Input,
+ };
+ let mut buffer_frames = test_audiounit_get_buffer_frame_size(
+ unit.get_inner(),
+ scope.clone(),
+ prop_scope.clone(),
+ )
+ .unwrap();
+ assert_ne!(buffer_frames, 0);
+ buffer_frames *= 2;
+ assert!(
+ set_buffer_size_sync(unit.get_inner(), scope.clone().into(), buffer_frames).is_ok()
+ );
+ let new_buffer_frames =
+ test_audiounit_get_buffer_frame_size(unit.get_inner(), scope.clone(), prop_scope)
+ .unwrap();
+ assert_eq!(buffer_frames, new_buffer_frames);
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_set_buffer_size_sync_for_input_with_null_input_unit() {
+ test_set_buffer_size_sync_by_scope_with_null_unit(Scope::Input);
+}
+
+#[test]
+#[should_panic]
+fn test_set_buffer_size_sync_for_output_with_null_output_unit() {
+ test_set_buffer_size_sync_by_scope_with_null_unit(Scope::Output);
+}
+
+fn test_set_buffer_size_sync_by_scope_with_null_unit(scope: Scope) {
+ let unit: AudioUnit = ptr::null_mut();
+ assert!(set_buffer_size_sync(unit, scope.into(), 2048).is_err());
+}
+
+// get_volume, set_volume
+// ------------------------------------
+#[test]
+fn test_stream_get_volume() {
+ if let Some(unit) = test_get_default_audiounit(Scope::Output) {
+ let expected_volume: f32 = 0.5;
+ set_volume(unit.get_inner(), expected_volume);
+ assert_eq!(expected_volume, get_volume(unit.get_inner()).unwrap());
+ } else {
+ println!("No output audiounit.");
+ }
+}
+
+// convert_uint32_into_string
+// ------------------------------------
+#[test]
+fn test_convert_uint32_into_string() {
+ let empty = convert_uint32_into_string(0);
+ assert_eq!(empty, CString::default());
+
+ let data: u32 = ('R' as u32) << 24 | ('U' as u32) << 16 | ('S' as u32) << 8 | 'T' as u32;
+ let data_string = convert_uint32_into_string(data);
+ assert_eq!(data_string, CString::new("RUST").unwrap());
+}
+
+// get_channel_count
+// ------------------------------------
+#[test]
+fn test_get_channel_count() {
+ test_channel_count(Scope::Input);
+ test_channel_count(Scope::Output);
+
+ fn test_channel_count(scope: Scope) {
+ if let Some(device) = test_get_default_device(scope.clone()) {
+ let channels = get_channel_count(device, DeviceType::from(scope.clone())).unwrap();
+ assert!(channels > 0);
+ assert_eq!(
+ channels,
+ test_device_channels_in_scope(device, scope).unwrap()
+ );
+ } else {
+ println!("No device for {:?}.", scope);
+ }
+ }
+}
+
+#[test]
+fn test_get_channel_count_of_input_for_a_output_only_deivce() {
+ let devices = test_get_devices_in_scope(Scope::Output);
+ for device in devices {
+ // Skip in-out devices.
+ if test_device_in_scope(device, Scope::Input) {
+ continue;
+ }
+ let count = get_channel_count(device, DeviceType::INPUT).unwrap();
+ assert_eq!(count, 0);
+ }
+}
+
+#[test]
+fn test_get_channel_count_of_output_for_a_input_only_deivce() {
+ let devices = test_get_devices_in_scope(Scope::Input);
+ for device in devices {
+ // Skip in-out devices.
+ if test_device_in_scope(device, Scope::Output) {
+ continue;
+ }
+ let count = get_channel_count(device, DeviceType::OUTPUT).unwrap();
+ assert_eq!(count, 0);
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_get_channel_count_of_unknown_device() {
+ assert!(get_channel_count(kAudioObjectUnknown, DeviceType::OUTPUT).is_err());
+}
+
+#[test]
+fn test_get_channel_count_of_inout_type() {
+ test_channel_count(Scope::Input);
+ test_channel_count(Scope::Output);
+
+ fn test_channel_count(scope: Scope) {
+ if let Some(device) = test_get_default_device(scope.clone()) {
+ assert_eq!(
+ get_channel_count(device, DeviceType::INPUT | DeviceType::OUTPUT),
+ get_channel_count(device, DeviceType::INPUT).map(|c| c + get_channel_count(
+ device,
+ DeviceType::OUTPUT
+ )
+ .unwrap_or(0))
+ );
+ } else {
+ println!("No device for {:?}.", scope);
+ }
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_get_channel_count_of_unknwon_type() {
+ test_channel_count(Scope::Input);
+ test_channel_count(Scope::Output);
+
+ fn test_channel_count(scope: Scope) {
+ if let Some(device) = test_get_default_device(scope.clone()) {
+ assert!(get_channel_count(device, DeviceType::UNKNOWN).is_err());
+ } else {
+ panic!("Panic by default: No device for {:?}.", scope);
+ }
+ }
+}
+
+// get_range_of_sample_rates
+// ------------------------------------
+#[test]
+fn test_get_range_of_sample_rates() {
+ test_get_range_of_sample_rates_in_scope(Scope::Input);
+ test_get_range_of_sample_rates_in_scope(Scope::Output);
+
+ fn test_get_range_of_sample_rates_in_scope(scope: Scope) {
+ if let Some(device) = test_get_default_device(scope.clone()) {
+ let ranges = test_get_available_samplerate_of_device(device);
+ for range in ranges {
+ // Surprisingly, we can get the input/output sample rates from a non-input/non-output device.
+ check_samplerates(range);
+ }
+ } else {
+ println!("No device for {:?}.", scope);
+ }
+ }
+
+ fn test_get_available_samplerate_of_device(id: AudioObjectID) -> Vec<(f64, f64)> {
+ let scopes = [
+ DeviceType::INPUT,
+ DeviceType::OUTPUT,
+ DeviceType::INPUT | DeviceType::OUTPUT,
+ ];
+ let mut ranges = Vec::new();
+ for scope in scopes.iter() {
+ ranges.push(get_range_of_sample_rates(id, *scope).unwrap());
+ }
+ ranges
+ }
+
+ fn check_samplerates((min, max): (f64, f64)) {
+ assert!(min > 0.0);
+ assert!(max > 0.0);
+ assert!(min <= max);
+ }
+}
+
+// get_presentation_latency
+// ------------------------------------
+#[test]
+fn test_get_device_presentation_latency() {
+ test_get_device_presentation_latencies_in_scope(Scope::Input);
+ test_get_device_presentation_latencies_in_scope(Scope::Output);
+
+ fn test_get_device_presentation_latencies_in_scope(scope: Scope) {
+ if let Some(device) = test_get_default_device(scope.clone()) {
+ // TODO: The latencies very from devices to devices. Check nothing here.
+ let latency = get_fixed_latency(device, scope.clone().into());
+ println!(
+ "present latency on the device {} in scope {:?}: {}",
+ device, scope, latency
+ );
+ } else {
+ println!("No device for {:?}.", scope);
+ }
+ }
+}
+
+// get_device_group_id
+// ------------------------------------
+#[test]
+fn test_get_device_group_id() {
+ if let Some(device) = test_get_default_device(Scope::Input) {
+ match get_device_group_id(device, DeviceType::INPUT) {
+ Ok(id) => println!("input group id: {:?}", id),
+ Err(e) => println!("No input group id. Error: {}", e),
+ }
+ } else {
+ println!("No input device.");
+ }
+
+ if let Some(device) = test_get_default_device(Scope::Output) {
+ match get_device_group_id(device, DeviceType::OUTPUT) {
+ Ok(id) => println!("output group id: {:?}", id),
+ Err(e) => println!("No output group id. Error: {}", e),
+ }
+ } else {
+ println!("No output device.");
+ }
+}
+
+#[test]
+fn test_get_same_group_id_for_builtin_device_pairs() {
+ use std::collections::HashMap;
+
+ // These device sources have custom group id. See `get_custom_group_id`.
+ const IMIC: u32 = 0x696D_6963; // "imic"
+ const ISPK: u32 = 0x6973_706B; // "ispk"
+ const EMIC: u32 = 0x656D_6963; // "emic"
+ const HDPN: u32 = 0x6864_706E; // "hdpn"
+ let pairs = [(IMIC, ISPK), (EMIC, HDPN)];
+
+ let mut input_group_ids = HashMap::<u32, String>::new();
+ let input_devices = test_get_devices_in_scope(Scope::Input);
+ for device in input_devices.iter() {
+ match get_device_source(*device, DeviceType::INPUT) {
+ Ok(source) => match get_device_group_id(*device, DeviceType::INPUT) {
+ Ok(id) => assert!(input_group_ids
+ .insert(source, id.into_string().unwrap())
+ .is_none()),
+ Err(e) => assert!(input_group_ids
+ .insert(source, format!("Error {}", e))
+ .is_none()),
+ },
+ _ => {} // do nothing when failing to get source.
+ }
+ }
+
+ let mut output_group_ids = HashMap::<u32, String>::new();
+ let output_devices = test_get_devices_in_scope(Scope::Output);
+ for device in output_devices.iter() {
+ match get_device_source(*device, DeviceType::OUTPUT) {
+ Ok(source) => match get_device_group_id(*device, DeviceType::OUTPUT) {
+ Ok(id) => assert!(output_group_ids
+ .insert(source, id.into_string().unwrap())
+ .is_none()),
+ Err(e) => assert!(output_group_ids
+ .insert(source, format!("Error {}", e))
+ .is_none()),
+ },
+ _ => {} // do nothing when failing to get source.
+ }
+ }
+
+ for (input, output) in pairs.iter() {
+ let input_group_id = input_group_ids.get(input);
+ let output_group_id = output_group_ids.get(output);
+
+ if input_group_id.is_some() && output_group_id.is_some() {
+ assert_eq!(input_group_id, output_group_id);
+ }
+
+ input_group_ids.remove(input);
+ output_group_ids.remove(output);
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_get_device_group_id_by_unknown_device() {
+ assert!(get_device_group_id(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+}
+
+// get_device_label
+// ------------------------------------
+#[test]
+fn test_get_device_label() {
+ if let Some(device) = test_get_default_device(Scope::Input) {
+ let name = get_device_label(device, DeviceType::INPUT).unwrap();
+ println!("input device label: {}", name.into_string());
+ } else {
+ println!("No input device.");
+ }
+
+ if let Some(device) = test_get_default_device(Scope::Output) {
+ let name = get_device_label(device, DeviceType::OUTPUT).unwrap();
+ println!("output device label: {}", name.into_string());
+ } else {
+ println!("No output device.");
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_get_device_label_by_unknown_device() {
+ assert!(get_device_label(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+}
+
+// get_device_global_uid
+// ------------------------------------
+#[test]
+fn test_get_device_global_uid() {
+ // Input device.
+ if let Some(input) = test_get_default_device(Scope::Input) {
+ let uid = get_device_global_uid(input).unwrap();
+ let uid = uid.into_string();
+ assert!(!uid.is_empty());
+ }
+
+ // Output device.
+ if let Some(output) = test_get_default_device(Scope::Output) {
+ let uid = get_device_global_uid(output).unwrap();
+ let uid = uid.into_string();
+ assert!(!uid.is_empty());
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_get_device_global_uid_by_unknwon_device() {
+ // Unknown device.
+ assert!(get_device_global_uid(kAudioObjectUnknown).is_err());
+}
+
+// create_cubeb_device_info
+// destroy_cubeb_device_info
+// ------------------------------------
+#[test]
+fn test_create_cubeb_device_info() {
+ use std::collections::VecDeque;
+
+ test_create_device_from_hwdev_in_scope(Scope::Input);
+ test_create_device_from_hwdev_in_scope(Scope::Output);
+
+ fn test_create_device_from_hwdev_in_scope(scope: Scope) {
+ if let Some(device) = test_get_default_device(scope.clone()) {
+ let is_input = test_device_in_scope(device, Scope::Input);
+ let is_output = test_device_in_scope(device, Scope::Output);
+ let mut results = test_create_device_infos_by_device(device);
+ assert_eq!(results.len(), 2);
+ // Input device type:
+ let input_result = results.pop_front().unwrap();
+ if is_input {
+ let mut input_device_info = input_result.unwrap();
+ check_device_info_by_device(&input_device_info, device, Scope::Input);
+ destroy_cubeb_device_info(&mut input_device_info);
+ } else {
+ assert_eq!(input_result.unwrap_err(), Error::error());
+ }
+ // Output device type:
+ let output_result = results.pop_front().unwrap();
+ if is_output {
+ let mut output_device_info = output_result.unwrap();
+ check_device_info_by_device(&output_device_info, device, Scope::Output);
+ destroy_cubeb_device_info(&mut output_device_info);
+ } else {
+ assert_eq!(output_result.unwrap_err(), Error::error());
+ }
+ } else {
+ println!("No device for {:?}.", scope);
+ }
+ }
+
+ fn test_create_device_infos_by_device(
+ id: AudioObjectID,
+ ) -> VecDeque<std::result::Result<ffi::cubeb_device_info, Error>> {
+ let dev_types = [DeviceType::INPUT, DeviceType::OUTPUT];
+ let mut results = VecDeque::new();
+ for dev_type in dev_types.iter() {
+ results.push_back(create_cubeb_device_info(id, *dev_type));
+ }
+ results
+ }
+
+ fn check_device_info_by_device(info: &ffi::cubeb_device_info, id: AudioObjectID, scope: Scope) {
+ assert!(!info.devid.is_null());
+ assert!(mem::size_of_val(&info.devid) >= mem::size_of::<AudioObjectID>());
+ assert_eq!(info.devid as AudioObjectID, id);
+ assert!(!info.device_id.is_null());
+ assert!(!info.friendly_name.is_null());
+ assert!(!info.group_id.is_null());
+
+ // TODO: Hit a kAudioHardwareUnknownPropertyError for AirPods
+ // assert!(!info.vendor_name.is_null());
+
+ // FIXME: The device is defined to input-only or output-only, but some device is in-out!
+ assert_eq!(info.device_type, DeviceType::from(scope.clone()).bits());
+ assert_eq!(info.state, ffi::CUBEB_DEVICE_STATE_ENABLED);
+ // TODO: The preference is set when the device is default input/output device if the device
+ // info is created from input/output scope. Should the preference be set if the
+ // device is a default input/output device if the device info is created from
+ // output/input scope ? The device may be a in-out device!
+ assert_eq!(info.preferred, get_cubeb_device_pref(id, scope));
+
+ assert_eq!(info.format, ffi::CUBEB_DEVICE_FMT_ALL);
+ assert_eq!(info.default_format, ffi::CUBEB_DEVICE_FMT_F32NE);
+ assert!(info.max_channels > 0);
+ assert!(info.min_rate <= info.max_rate);
+ assert!(info.min_rate <= info.default_rate);
+ assert!(info.default_rate <= info.max_rate);
+
+ assert!(info.latency_lo > 0);
+ assert!(info.latency_hi > 0);
+ assert!(info.latency_lo <= info.latency_hi);
+
+ fn get_cubeb_device_pref(id: AudioObjectID, scope: Scope) -> ffi::cubeb_device_pref {
+ let default_device = test_get_default_device(scope);
+ if default_device.is_some() && default_device.unwrap() == id {
+ ffi::CUBEB_DEVICE_PREF_ALL
+ } else {
+ ffi::CUBEB_DEVICE_PREF_NONE
+ }
+ }
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_create_device_info_by_unknown_device() {
+ assert!(create_cubeb_device_info(kAudioObjectUnknown, DeviceType::OUTPUT).is_err());
+}
+
+#[test]
+fn test_create_device_info_with_unknown_type() {
+ test_create_device_info_with_unknown_type_by_scope(Scope::Input);
+ test_create_device_info_with_unknown_type_by_scope(Scope::Output);
+
+ fn test_create_device_info_with_unknown_type_by_scope(scope: Scope) {
+ if let Some(device) = test_get_default_device(scope.clone()) {
+ assert!(create_cubeb_device_info(device, DeviceType::UNKNOWN).is_err());
+ }
+ }
+}
+
+#[test]
+#[should_panic]
+fn test_device_destroy_empty_device() {
+ let mut device = ffi::cubeb_device_info::default();
+
+ assert!(device.device_id.is_null());
+ assert!(device.group_id.is_null());
+ assert!(device.friendly_name.is_null());
+ assert!(device.vendor_name.is_null());
+
+ // `friendly_name` must be set.
+ destroy_cubeb_device_info(&mut device);
+
+ assert!(device.device_id.is_null());
+ assert!(device.group_id.is_null());
+ assert!(device.friendly_name.is_null());
+ assert!(device.vendor_name.is_null());
+}
+
+#[test]
+fn test_create_device_from_hwdev_with_inout_type() {
+ test_create_device_from_hwdev_with_inout_type_by_scope(Scope::Input);
+ test_create_device_from_hwdev_with_inout_type_by_scope(Scope::Output);
+
+ fn test_create_device_from_hwdev_with_inout_type_by_scope(scope: Scope) {
+ if let Some(device) = test_get_default_device(scope.clone()) {
+ // Get a kAudioHardwareUnknownPropertyError in get_channel_count actually.
+ assert!(
+ create_cubeb_device_info(device, DeviceType::INPUT | DeviceType::OUTPUT).is_err()
+ );
+ } else {
+ println!("No device for {:?}.", scope);
+ }
+ }
+}
+
+// get_devices_of_type
+// ------------------------------------
+#[test]
+fn test_get_devices_of_type() {
+ use std::collections::HashSet;
+
+ let all_devices = audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT);
+ let input_devices = audiounit_get_devices_of_type(DeviceType::INPUT);
+ let output_devices = audiounit_get_devices_of_type(DeviceType::OUTPUT);
+
+ let mut expected_all = test_get_all_devices(DeviceFilter::ExcludeCubebAggregateAndVPIO);
+ expected_all.sort();
+ assert_eq!(all_devices, expected_all);
+ for device in all_devices.iter() {
+ if test_device_in_scope(*device, Scope::Input) {
+ assert!(input_devices.contains(device));
+ }
+ if test_device_in_scope(*device, Scope::Output) {
+ assert!(output_devices.contains(device));
+ }
+ }
+
+ let input: HashSet<AudioObjectID> = input_devices.iter().cloned().collect();
+ let output: HashSet<AudioObjectID> = output_devices.iter().cloned().collect();
+ let union: HashSet<AudioObjectID> = input.union(&output).cloned().collect();
+ let mut union_devices: Vec<AudioObjectID> = union.iter().cloned().collect();
+ union_devices.sort();
+ assert_eq!(all_devices, union_devices);
+}
+
+#[test]
+#[should_panic]
+fn test_get_devices_of_type_unknown() {
+ let no_devs = audiounit_get_devices_of_type(DeviceType::UNKNOWN);
+ assert!(no_devs.is_empty());
+}
+
+// add_devices_changed_listener
+// ------------------------------------
+#[test]
+fn test_add_devices_changed_listener() {
+ use std::collections::HashMap;
+
+ extern "C" fn inout_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+ extern "C" fn in_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+ extern "C" fn out_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+
+ let mut map: HashMap<DeviceType, extern "C" fn(*mut ffi::cubeb, *mut c_void)> = HashMap::new();
+ map.insert(DeviceType::INPUT, in_callback);
+ map.insert(DeviceType::OUTPUT, out_callback);
+ map.insert(DeviceType::INPUT | DeviceType::OUTPUT, inout_callback);
+
+ test_get_raw_context(|context| {
+ for (devtype, callback) in map.iter() {
+ assert!(get_devices_changed_callback(context, Scope::Input).is_none());
+ assert!(get_devices_changed_callback(context, Scope::Output).is_none());
+
+ // Register a callback within a specific scope.
+ assert!(context
+ .add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut())
+ .is_ok());
+
+ if devtype.contains(DeviceType::INPUT) {
+ let cb = get_devices_changed_callback(context, Scope::Input);
+ assert!(cb.is_some());
+ assert_eq!(cb.unwrap(), *callback);
+ } else {
+ let cb = get_devices_changed_callback(context, Scope::Input);
+ assert!(cb.is_none());
+ }
+
+ if devtype.contains(DeviceType::OUTPUT) {
+ let cb = get_devices_changed_callback(context, Scope::Output);
+ assert!(cb.is_some());
+ assert_eq!(cb.unwrap(), *callback);
+ } else {
+ let cb = get_devices_changed_callback(context, Scope::Output);
+ assert!(cb.is_none());
+ }
+
+ // Unregister the callbacks within all scopes.
+ assert!(context
+ .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
+ .is_ok());
+
+ assert!(get_devices_changed_callback(context, Scope::Input).is_none());
+ assert!(get_devices_changed_callback(context, Scope::Output).is_none());
+ }
+ });
+}
+
+#[test]
+#[should_panic]
+fn test_add_devices_changed_listener_in_unknown_scope() {
+ extern "C" fn callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+
+ test_get_raw_context(|context| {
+ let _ = context.add_devices_changed_listener(
+ DeviceType::UNKNOWN,
+ Some(callback),
+ ptr::null_mut(),
+ );
+ });
+}
+
+#[test]
+#[should_panic]
+fn test_add_devices_changed_listener_with_none_callback() {
+ test_get_raw_context(|context| {
+ for devtype in &[DeviceType::INPUT, DeviceType::OUTPUT] {
+ assert!(context
+ .add_devices_changed_listener(*devtype, None, ptr::null_mut())
+ .is_ok());
+ }
+ });
+}
+
+// remove_devices_changed_listener
+// ------------------------------------
+#[test]
+fn test_remove_devices_changed_listener() {
+ use std::collections::HashMap;
+
+ extern "C" fn in_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+ extern "C" fn out_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+
+ let mut map: HashMap<DeviceType, extern "C" fn(*mut ffi::cubeb, *mut c_void)> = HashMap::new();
+ map.insert(DeviceType::INPUT, in_callback);
+ map.insert(DeviceType::OUTPUT, out_callback);
+
+ test_get_raw_context(|context| {
+ for (devtype, _callback) in map.iter() {
+ assert!(get_devices_changed_callback(context, Scope::Input).is_none());
+ assert!(get_devices_changed_callback(context, Scope::Output).is_none());
+
+ // Register callbacks within all scopes.
+ for (scope, listener) in map.iter() {
+ assert!(context
+ .add_devices_changed_listener(*scope, Some(*listener), ptr::null_mut())
+ .is_ok());
+ }
+
+ let input_callback = get_devices_changed_callback(context, Scope::Input);
+ assert!(input_callback.is_some());
+ assert_eq!(
+ input_callback.unwrap(),
+ *(map.get(&DeviceType::INPUT).unwrap())
+ );
+ let output_callback = get_devices_changed_callback(context, Scope::Output);
+ assert!(output_callback.is_some());
+ assert_eq!(
+ output_callback.unwrap(),
+ *(map.get(&DeviceType::OUTPUT).unwrap())
+ );
+
+ // Unregister the callbacks within one specific scopes.
+ assert!(context.remove_devices_changed_listener(*devtype).is_ok());
+
+ if devtype.contains(DeviceType::INPUT) {
+ let cb = get_devices_changed_callback(context, Scope::Input);
+ assert!(cb.is_none());
+ } else {
+ let cb = get_devices_changed_callback(context, Scope::Input);
+ assert!(cb.is_some());
+ assert_eq!(cb.unwrap(), *(map.get(&DeviceType::INPUT).unwrap()));
+ }
+
+ if devtype.contains(DeviceType::OUTPUT) {
+ let cb = get_devices_changed_callback(context, Scope::Output);
+ assert!(cb.is_none());
+ } else {
+ let cb = get_devices_changed_callback(context, Scope::Output);
+ assert!(cb.is_some());
+ assert_eq!(cb.unwrap(), *(map.get(&DeviceType::OUTPUT).unwrap()));
+ }
+
+ // Unregister the callbacks within all scopes.
+ assert!(context
+ .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
+ .is_ok());
+ }
+ });
+}
+
+#[test]
+fn test_remove_devices_changed_listener_without_adding_listeners() {
+ test_get_raw_context(|context| {
+ for devtype in &[
+ DeviceType::INPUT,
+ DeviceType::OUTPUT,
+ DeviceType::INPUT | DeviceType::OUTPUT,
+ ] {
+ assert!(context.remove_devices_changed_listener(*devtype).is_ok());
+ }
+ });
+}
+
+#[test]
+fn test_remove_devices_changed_listener_within_all_scopes() {
+ use std::collections::HashMap;
+
+ extern "C" fn inout_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+ extern "C" fn in_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+ extern "C" fn out_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+
+ let mut map: HashMap<DeviceType, extern "C" fn(*mut ffi::cubeb, *mut c_void)> = HashMap::new();
+ map.insert(DeviceType::INPUT, in_callback);
+ map.insert(DeviceType::OUTPUT, out_callback);
+ map.insert(DeviceType::INPUT | DeviceType::OUTPUT, inout_callback);
+
+ test_get_raw_context(|context| {
+ for (devtype, callback) in map.iter() {
+ assert!(get_devices_changed_callback(context, Scope::Input).is_none());
+ assert!(get_devices_changed_callback(context, Scope::Output).is_none());
+
+ assert!(context
+ .add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut())
+ .is_ok());
+
+ if devtype.contains(DeviceType::INPUT) {
+ let cb = get_devices_changed_callback(context, Scope::Input);
+ assert!(cb.is_some());
+ assert_eq!(cb.unwrap(), *callback);
+ }
+
+ if devtype.contains(DeviceType::OUTPUT) {
+ let cb = get_devices_changed_callback(context, Scope::Output);
+ assert!(cb.is_some());
+ assert_eq!(cb.unwrap(), *callback);
+ }
+
+ assert!(context
+ .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
+ .is_ok());
+
+ assert!(get_devices_changed_callback(context, Scope::Input).is_none());
+ assert!(get_devices_changed_callback(context, Scope::Output).is_none());
+ }
+ });
+}
+
+fn get_devices_changed_callback(
+ context: &AudioUnitContext,
+ scope: Scope,
+) -> ffi::cubeb_device_collection_changed_callback {
+ let devices_guard = context.devices.lock().unwrap();
+ match scope {
+ Scope::Input => devices_guard.input.changed_callback,
+ Scope::Output => devices_guard.output.changed_callback,
+ }
+}