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::() + (channels - 1) * mem::size_of::(); 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::(); let padding_size = 10; assert_ne!(mem::size_of::(), 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) { let guard = context.latency_controller.lock().unwrap(); assert_eq!(guard.latency, latency); } // make_silent // ------------------------------------ #[test] fn test_make_silent() { let mut array = allocate_array::(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::()) 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(¶ms).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::::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::::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> { 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::()); 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 = input_devices.iter().cloned().collect(); let output: HashSet = output_devices.iter().cloned().collect(); let union: HashSet = input.union(&output).cloned().collect(); let mut union_devices: Vec = 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 = 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 = 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 = 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, } }