use std::ffi::c_char; 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_panic_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, Some(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_panic_minimum_resampling_input_frames_zero_input_rate() { minimum_resampling_input_frames(0_f64, 44100_f64, 1); } #[test] #[should_panic] fn test_panic_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 = run_serially(|| 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 = run_serially(|| 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_panic_set_device_info_to_system_input_device() { let _device = run_serially_forward_panics(|| { create_device_info(kAudioObjectSystemObject, DeviceType::INPUT) }); } #[test] #[should_panic] fn test_panic_set_device_info_to_system_output_device() { let _device = run_serially_forward_panics(|| { create_device_info(kAudioObjectSystemObject, DeviceType::OUTPUT) }); } // FIXME: Is it ok to set input device to a nonexistent device ? #[ignore] #[test] #[should_panic] fn test_panic_set_device_info_to_nonexistent_input_device() { let nonexistent_id = std::u32::MAX; let _device = run_serially_forward_panics(|| 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_panic_set_device_info_to_nonexistent_output_device() { let nonexistent_id = std::u32::MAX; let _device = run_serially_forward_panics(|| 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 res = stream .queue .run_sync(|| stream.add_device_listener(&listener)) .unwrap(); 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 res = stream .queue .run_sync(|| stream.add_device_listener(&listener)) .unwrap(); assert_eq!(res, NO_ERR); let res = stream .queue .run_sync(|| stream.remove_device_listener(&listener)) .unwrap(); 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 res = stream .queue .run_sync(|| stream.remove_device_listener(&listener)) .unwrap(); 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 res = stream .queue .run_sync(|| stream.remove_device_listener(&listener)) .unwrap(); 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!( run_serially(|| get_default_device_id(DeviceType::INPUT)).unwrap(), kAudioObjectUnknown, ); } if test_get_default_device(Scope::Output).is_some() { assert_ne!( run_serially(|| get_default_device_id(DeviceType::OUTPUT)).unwrap(), kAudioObjectUnknown, ); } } #[test] #[should_panic] fn test_panic_get_default_device_id_with_unknown_type() { assert!(run_serially_forward_panics(|| get_default_device_id(DeviceType::UNKNOWN)).is_err()); } #[test] #[should_panic] fn test_panic_get_default_device_id_with_inout_type() { assert!(run_serially_forward_panics(|| get_default_device_id( DeviceType::INPUT | DeviceType::OUTPUT )) .is_err()); } // convert_channel_layout // ------------------------------------ #[test] fn test_convert_channel_layout() { let pairs = [ ( 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::Discrete, ], ), ( 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!(!run_serially(|| 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!( !run_serially_forward_panics(|| 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!(run_serially_forward_panics(|| enable_audiounit_scope( unit.get_inner(), DeviceType::OUTPUT, true )) .is_ok()); assert!(run_serially_forward_panics(|| enable_audiounit_scope( unit.get_inner(), DeviceType::OUTPUT, false )) .is_ok()); assert!(run_serially_forward_panics(|| enable_audiounit_scope( unit.get_inner(), DeviceType::INPUT, true )) .is_ok()); assert!(run_serially_forward_panics(|| 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!( run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true)) .unwrap_err(), kAudioUnitErr_InvalidProperty ); assert_eq!( run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false)) .unwrap_err(), kAudioUnitErr_InvalidProperty ); assert_eq!( run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true)) .unwrap_err(), kAudioUnitErr_InvalidProperty ); assert_eq!( run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false)) .unwrap_err(), kAudioUnitErr_InvalidProperty ); } } #[test] #[should_panic] fn test_panic_enable_audiounit_scope_with_null_unit() { let unit: AudioUnit = ptr::null_mut(); assert!( run_serially_forward_panics(|| 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 = run_serially(|| create_audiounit(&device).unwrap()); assert!(!unit.is_null()); assert!(test_audiounit_scope_is_enabled(unit, Scope::Output)); // Destroy the AudioUnit. run_serially(|| 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 = run_serially(|| create_audiounit(&device).unwrap()); assert!(!unit.is_null()); assert!(test_audiounit_scope_is_enabled(unit, Scope::Input)); // Destroy the AudioUnit. run_serially(|| unsafe { AudioUnitUninitialize(unit); AudioComponentInstanceDispose(unit); }); } } } #[test] #[should_panic] fn test_panic_create_audiounit_with_unknown_scope() { let device = device_info::default(); let _unit = run_serially_forward_panics(|| 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!(run_serially(|| 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_panic_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_panic_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!( run_serially_forward_panics(|| 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; run_serially(|| set_volume(unit.get_inner(), expected_volume)); assert_eq!( expected_volume, run_serially(|| 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 = run_serially(|| 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 = run_serially(|| 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 = run_serially(|| get_channel_count(device, DeviceType::OUTPUT)).unwrap(); assert_eq!(count, 0); } } #[test] #[should_panic] fn test_panic_get_channel_count_of_unknown_device() { assert!(run_serially_forward_panics(|| 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()) { run_serially_forward_panics(|| { 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_panic_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!(run_serially_forward_panics(|| get_channel_count( device, DeviceType::UNKNOWN ) .is_err())); } else { panic!("Panic by default: No device for {:?}.", scope); } } } fn is_vpio(id: AudioObjectID) -> bool { debug_assert_running_serially(); get_device_global_uid(id) .map(|uid| uid.into_string()) .map(|uid| uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME)) .unwrap_or(false) } fn get_nonvpio_input_channel_counts() -> Vec { debug_assert_running_serially(); get_devices() .into_iter() .filter(|&id| !is_vpio(id)) .filter_map(|id| get_channel_count(id, DeviceType::INPUT).ok()) .collect() } extern "C" { fn sysctlbyname( name: *const c_char, oldp: *mut std::os::raw::c_void, oldlenp: *mut libc::size_t, newp: *mut std::os::raw::c_void, newlen: libc::size_t, ) -> std::os::raw::c_int; } fn get_sysctl_string(name: &str) -> Option { let name_cstr = std::ffi::CString::new(name).ok()?; let mut size: libc::size_t = 0; unsafe { if sysctlbyname( name_cstr.as_ptr(), std::ptr::null_mut(), &mut size, std::ptr::null_mut(), 0, ) != 0 { return None; } let mut buffer = vec![0u8; size]; if sysctlbyname( name_cstr.as_ptr(), buffer.as_mut_ptr() as *mut _, &mut size, std::ptr::null_mut(), 0, ) != 0 { return None; } buffer.truncate(size - 1); String::from_utf8(buffer).ok() } } fn is_m4_macbook() -> bool { if let Some(model) = get_sysctl_string("hw.model") { // Apple Silicon devices aren't e.g. MacBookProXX,Y, they are MacXX,Y return model.starts_with("Mac16,"); // All M4-based computers } false } #[test] #[ignore] fn test_get_channel_count_of_input_devices_with_vpio() { let non_vpio_channel_counts = run_serially_forward_panics(get_nonvpio_input_channel_counts); let queue = Queue::new_with_target( "test_get_channel_count_of_input_devices_with_vpio", get_serial_queue_singleton(), ); let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone()); let _vpio = queue.run_sync(|| shared.take_or_create()).unwrap().unwrap(); let vpio_channel_counts = run_serially_forward_panics(get_nonvpio_input_channel_counts); // https://github.com/mozilla/cubeb-coreaudio-rs/issues/255 // Maybe M3 also ? Sometimes this passes, but it's fairly inconsistent. if !is_m4_macbook() { assert_eq!(non_vpio_channel_counts, vpio_channel_counts); } } #[test] #[ignore] fn test_get_channel_count_of_input_devices_with_aggregate_device_and_vpio() { let input_device = test_get_default_device(Scope::Input); let output_device = test_get_default_device(Scope::Output); if input_device.is_none() || output_device.is_none() || input_device == output_device { println!("No input or output device to create an aggregate device."); return; } #[derive(Default)] struct State { aggr: Option, vpio_mgr: Option, vpio: Option>, } impl Drop for State { fn drop(&mut self) { let mut aggr = self.aggr.take(); let mut vpio = self.vpio.take(); run_serially_forward_panics(move || { aggr.take(); vpio.take(); }); } } let state = Arc::new(Mutex::new(State::default())); // Set up an AggregateDevice with input and output. let initial_channel_counts = run_serially_forward_panics(get_nonvpio_input_channel_counts); let s1 = state.clone(); let aggr_channel_counts = run_serially_forward_panics(|| { let mut state = s1.lock().unwrap(); state.aggr = Some(AggregateDevice::new(input_device.unwrap(), output_device.unwrap()).unwrap()); get_nonvpio_input_channel_counts() }); assert_eq!(initial_channel_counts.len() + 1, aggr_channel_counts.len()); let queue = Queue::new_with_target( "test_get_channel_count_of_input_devices_with_aggregate_device_and_vpio", get_serial_queue_singleton(), ); { let mut s = state.lock().unwrap(); s.vpio_mgr = Some(SharedVoiceProcessingUnitManager::new(queue.clone())); } let s2 = state.clone(); let aggr_vpio_channel_counts = run_serially_forward_panics(|| { let mut state = s2.lock().unwrap(); state.vpio = state.vpio_mgr.as_mut().map(|m| m.take_or_create().unwrap()); get_nonvpio_input_channel_counts() }); // https://github.com/mozilla/cubeb-coreaudio-rs/issues/255 if !is_m4_macbook() { assert_eq!(aggr_channel_counts, aggr_vpio_channel_counts); } } // 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( run_serially_forward_panics(|| 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 = run_serially_forward_panics(|| 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 run_serially_forward_panics(|| 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 run_serially_forward_panics(|| 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() { if let Ok(source) = run_serially_forward_panics(|| get_device_source(*device, DeviceType::INPUT)) { match run_serially_forward_panics(|| 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()), } } // else 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() { if let Ok(source) = run_serially_forward_panics(|| get_device_source(*device, DeviceType::OUTPUT)) { match run_serially_forward_panics(|| 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()), } } // else 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_panic_get_device_group_id_by_unknown_device() { assert!(run_serially_forward_panics(|| 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 = run_serially_forward_panics(|| 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 = run_serially_forward_panics(|| get_device_label(device, DeviceType::OUTPUT)).unwrap(); println!("output device label: {}", name.into_string()); } else { println!("No output device."); } } #[test] #[should_panic] fn test_panic_get_device_label_by_unknown_device() { assert!(run_serially_forward_panics(|| 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 = run_serially_forward_panics(|| 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 = run_serially_forward_panics(|| get_device_global_uid(output)).unwrap(); let uid = uid.into_string(); assert!(!uid.is_empty()); } } #[test] #[should_panic] fn test_panic_get_device_global_uid_by_unknwon_device() { // Unknown device. assert!(run_serially_forward_panics(|| 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; let intern = Arc::new(Mutex::new(intern::Intern::new())); test_create_device_from_hwdev_in_scope(&intern, Scope::Input); test_create_device_from_hwdev_in_scope(&intern, Scope::Output); fn test_create_device_from_hwdev_in_scope(intern: &Arc>, 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(intern, 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); run_serially(|| 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); run_serially(|| 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( intern: &Arc>, 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(run_serially_forward_panics(|| { create_cubeb_device_info(intern, 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_ne!(info.devid, info.device_id as _); 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_panic_create_device_info_by_unknown_device() { let intern = Arc::new(Mutex::new(intern::Intern::new())); assert!(create_cubeb_device_info(&intern, 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) { let intern = Arc::new(Mutex::new(intern::Intern::new())); if let Some(device) = test_get_default_device(scope.clone()) { assert!(run_serially_forward_panics(|| create_cubeb_device_info( &intern, device, DeviceType::UNKNOWN )) .is_err()); } } } #[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()) { let intern = Arc::new(Mutex::new(intern::Intern::new())); // Get a kAudioHardwareUnknownPropertyError in get_channel_count actually. assert!(run_serially_forward_panics(|| create_cubeb_device_info( &intern, 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 = run_serially_forward_panics(|| { audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT) }); let input_devices = run_serially_forward_panics(|| audiounit_get_devices_of_type(DeviceType::INPUT)); let output_devices = run_serially_forward_panics(|| 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_panic_get_devices_of_type_unknown() { run_serially_forward_panics(|| { 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!(run_serially(|| { 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!(std::ptr::fn_addr_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!(std::ptr::fn_addr_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!(run_serially(|| { 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_panic_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_panic_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!(run_serially(|| 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!(std::ptr::fn_addr_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!(std::ptr::fn_addr_eq( output_callback.unwrap(), *(map.get(&DeviceType::OUTPUT).unwrap()) )); // Unregister the callbacks within one specific scopes. assert!(run_serially(|| 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!(std::ptr::fn_addr_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!(std::ptr::fn_addr_eq( cb.unwrap(), *(map.get(&DeviceType::OUTPUT).unwrap()) )); } // Unregister the callbacks within all scopes. assert!(run_serially( || 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!(run_serially(|| 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!(run_serially(|| 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!(std::ptr::fn_addr_eq(cb.unwrap(), *callback)); } if devtype.contains(DeviceType::OUTPUT) { let cb = get_devices_changed_callback(context, Scope::Output); assert!(cb.is_some()); assert!(std::ptr::fn_addr_eq(cb.unwrap(), *callback)); } assert!(run_serially( || 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, } } // SharedVoiceProcessingUnitManager // ------------------------------------ #[test] fn test_shared_voice_processing_unit() { let queue = Queue::new_with_target( "test_shared_voice_processing_unit", get_serial_queue_singleton(), ); let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone()); let r1 = queue.run_sync(|| shared.take()).unwrap(); assert!(r1.is_err()); let r2 = queue.run_sync(|| shared.take_or_create()).unwrap(); assert!(r2.is_ok()); { let _handle = r2.unwrap(); let r3 = queue.run_sync(|| shared.take()).unwrap(); assert!(r3.is_err()); } let r4 = queue.run_sync(|| shared.take()).unwrap(); assert!(r4.is_ok()); } #[test] #[should_panic] fn test_panic_shared_voice_processing_unit_bad_release_order() { let queue = Queue::new_with_target( "test_shared_voice_processing_unit_bad_release_order", get_serial_queue_singleton(), ); let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone()); let r1 = queue.run_sync(|| shared.take()).unwrap(); assert!(r1.is_ok()); drop(shared); run_serially_forward_panics(|| drop(r1)); } #[test] fn test_shared_voice_processing_multiple_units() { let queue = Queue::new_with_target( "test_shared_voice_processing_multiple_units", get_serial_queue_singleton(), ); let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone()); let r1 = queue.run_sync(|| shared.take_or_create()).unwrap(); assert!(r1.is_ok()); let r2 = queue.run_sync(|| shared.take_or_create()).unwrap(); assert!(r2.is_ok()); { let _handle1 = r1.unwrap(); let _handle2 = r2.unwrap(); let r3 = queue.run_sync(|| shared.take()).unwrap(); assert!(r3.is_err()); } let r1 = queue.run_sync(|| shared.take()).unwrap(); assert!(r1.is_ok()); let r2 = queue.run_sync(|| shared.take()).unwrap(); assert!(r2.is_ok()); let r3 = queue.run_sync(|| shared.take()).unwrap(); assert!(r3.is_err()); } #[test] fn test_shared_voice_processing_release_on_idle() { let queue = Queue::new_with_target( "test_shared_voice_processing_release_on_idle", get_serial_queue_singleton(), ); let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout( queue.clone(), Duration::from_millis(0), ); let r = queue.run_sync(|| shared.take_or_create()).unwrap(); assert!(r.is_ok()); { let _handle = r.unwrap(); } queue.run_sync(|| {}); let r = queue.run_sync(|| shared.take()).unwrap(); assert!(r.is_err()); } #[test] fn test_shared_voice_processing_no_release_on_outstanding() { let queue = Queue::new_with_target( "test_shared_voice_processing_no_release_on_outstanding", get_serial_queue_singleton(), ); let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout( queue.clone(), Duration::from_millis(0), ); let r1 = queue.run_sync(|| shared.take_or_create()).unwrap(); assert!(r1.is_ok()); let r2 = queue.run_sync(|| shared.take_or_create()).unwrap(); assert!(r2.is_ok()); { let _handle1 = r1.unwrap(); } queue.run_sync(|| {}); let r1 = queue.run_sync(|| shared.take()).unwrap(); assert!(r1.is_ok()); } #[test] fn test_shared_voice_processing_release_on_idle_cancel_on_take() { let queue = Queue::new_with_target( "test_shared_voice_processing_release_on_idle_cancel_on_take", get_serial_queue_singleton(), ); let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout( queue.clone(), Duration::from_millis(0), ); let r1 = queue.run_sync(|| shared.take_or_create()).unwrap(); assert!(r1.is_ok()); let r2 = queue.run_sync(|| shared.take_or_create()).unwrap(); assert!(r2.is_ok()); let r1 = queue .run_sync(|| { { let _handle1 = r1.unwrap(); let _handle2 = r2.unwrap(); } shared.take() }) .unwrap(); assert!(r1.is_ok()); queue.run_sync(|| {}); let r2 = queue.run_sync(|| shared.take()).unwrap(); assert!(r2.is_ok()); }