Revision control

Copy as Markdown

Other Tools

use super::utils::{
test_audiounit_get_buffer_frame_size, test_audiounit_scope_is_enabled, test_create_audiounit,
test_device_channels_in_scope, test_device_in_scope, test_get_all_devices,
test_get_default_audiounit, test_get_default_device, test_get_default_raw_stream,
test_get_devices_in_scope, test_get_raw_context, ComponentSubType, DeviceFilter, PropertyScope,
Scope,
};
use super::*;
// make_sized_audio_channel_layout
// ------------------------------------
#[test]
fn test_make_sized_audio_channel_layout() {
for channels in 1..10 {
let size = mem::size_of::<AudioChannelLayout>()
+ (channels - 1) * mem::size_of::<AudioChannelDescription>();
let _ = make_sized_audio_channel_layout(size);
}
}
#[test]
#[should_panic]
fn test_make_sized_audio_channel_layout_with_wrong_size() {
// let _ = make_sized_audio_channel_layout(0);
let one_channel_size = mem::size_of::<AudioChannelLayout>();
let padding_size = 10;
assert_ne!(mem::size_of::<AudioChannelDescription>(), padding_size);
let wrong_size = one_channel_size + padding_size;
let _ = make_sized_audio_channel_layout(wrong_size);
}
// active_streams
// update_latency_by_adding_stream
// update_latency_by_removing_stream
// ------------------------------------
#[test]
fn test_increase_and_decrease_context_streams() {
use std::thread;
const STREAMS: u32 = 10;
let context = AudioUnitContext::new();
let context_ptr_value = &context as *const AudioUnitContext as usize;
let mut join_handles = vec![];
for i in 0..STREAMS {
join_handles.push(thread::spawn(move || {
let context = unsafe { &*(context_ptr_value as *const AudioUnitContext) };
context.update_latency_by_adding_stream(i)
}));
}
let mut latencies = vec![];
for handle in join_handles {
latencies.push(handle.join().unwrap());
}
assert_eq!(context.active_streams(), STREAMS);
check_streams(&context, STREAMS);
check_latency(&context, 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<u32>) {
let guard = context.latency_controller.lock().unwrap();
assert_eq!(guard.latency, latency);
}
// make_silent
// ------------------------------------
#[test]
fn test_make_silent() {
let mut array = allocate_array::<u32>(10);
for data in array.iter_mut() {
*data = 0xFFFF;
}
let mut buffer = AudioBuffer::default();
buffer.mData = array.as_mut_ptr() as *mut c_void;
buffer.mDataByteSize = (array.len() * mem::size_of::<u32>()) as u32;
buffer.mNumberChannels = 1;
audiounit_make_silent(&mut buffer);
for data in array {
assert_eq!(data, 0);
}
}
// minimum_resampling_input_frames
// ------------------------------------
#[test]
fn test_minimum_resampling_input_frames() {
let input_rate = 48000_f64;
let output_rate = 44100_f64;
let frames = 100;
let times = input_rate / output_rate;
let expected = (frames as f64 * times).ceil() as usize;
assert_eq!(
minimum_resampling_input_frames(input_rate, output_rate, frames),
expected
);
}
#[test]
#[should_panic]
fn test_minimum_resampling_input_frames_zero_input_rate() {
minimum_resampling_input_frames(0_f64, 44100_f64, 1);
}
#[test]
#[should_panic]
fn test_minimum_resampling_input_frames_zero_output_rate() {
minimum_resampling_input_frames(48000_f64, 0_f64, 1);
}
#[test]
fn test_minimum_resampling_input_frames_equal_input_output_rate() {
let frames = 100;
assert_eq!(
minimum_resampling_input_frames(44100_f64, 44100_f64, frames),
frames
);
}
// create_device_info
// ------------------------------------
#[test]
fn test_create_device_info_from_unknown_input_device() {
if let Some(default_device_id) = test_get_default_device(Scope::Input) {
let default_device =
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_set_device_info_to_system_input_device() {
let _device = run_serially_forward_panics(|| {
create_device_info(kAudioObjectSystemObject, DeviceType::INPUT)
});
}
#[test]
#[should_panic]
fn test_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_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_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_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_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
#[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(&params).unwrap();
assert_eq!(description.mFormatID, kAudioFormatLinearPCM);
assert_eq!(
description.mFormatFlags,
flags | kLinearPCMFormatFlagIsPacked
);
assert_eq!(description.mSampleRate as u32, raw.rate);
assert_eq!(description.mChannelsPerFrame, raw.channels);
assert_eq!(description.mBytesPerFrame, bytes * raw.channels);
assert_eq!(description.mFramesPerPacket, 1);
assert_eq!(description.mBytesPerPacket, bytes * raw.channels);
assert_eq!(description.mReserved, 0);
}
}
// create_blank_audiounit
// ------------------------------------
#[test]
fn test_create_blank_audiounit() {
let unit = create_blank_audiounit().unwrap();
assert!(!unit.is_null());
// Destroy the AudioUnit
unsafe {
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
}
}
// enable_audiounit_scope
// ------------------------------------
#[test]
fn test_enable_audiounit_scope() {
// It's ok to enable and disable the scopes of input or output
// for the unit whose subtype is kAudioUnitSubType_HALOutput
// even when there is no available input or output devices.
if let Some(unit) = test_create_audiounit(ComponentSubType::HALOutput) {
assert!(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_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_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_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!(
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_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_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<u32> {
debug_assert_running_serially();
get_devices()
.into_iter()
.filter(|&id| !is_vpio(id))
.filter_map(|id| get_channel_count(id, DeviceType::INPUT).ok())
.collect()
}
#[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());
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<AggregateDevice>,
vpio_mgr: Option<SharedVoiceProcessingUnitManager>,
vpio: Option<OwningHandle<VoiceProcessingUnit>>,
}
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()
});
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::<u32, String>::new();
let input_devices = test_get_devices_in_scope(Scope::Input);
for device in input_devices.iter() {
match run_serially_forward_panics(|| get_device_source(*device, DeviceType::INPUT)) {
Ok(source) => 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()),
},
_ => {} // do nothing when failing to get source.
}
}
let mut output_group_ids = HashMap::<u32, String>::new();
let output_devices = test_get_devices_in_scope(Scope::Output);
for device in output_devices.iter() {
match run_serially_forward_panics(|| get_device_source(*device, DeviceType::OUTPUT)) {
Ok(source) => 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()),
},
_ => {} // 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!(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_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_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;
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);
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(
id: AudioObjectID,
) -> VecDeque<std::result::Result<ffi::cubeb_device_info, Error>> {
let dev_types = [DeviceType::INPUT, DeviceType::OUTPUT];
let mut results = VecDeque::new();
for dev_type in dev_types.iter() {
results.push_back(run_serially_forward_panics(|| {
create_cubeb_device_info(id, *dev_type)
}));
}
results
}
fn check_device_info_by_device(info: &ffi::cubeb_device_info, id: AudioObjectID, scope: Scope) {
assert!(!info.devid.is_null());
assert!(mem::size_of_val(&info.devid) >= mem::size_of::<AudioObjectID>());
assert_eq!(info.devid as AudioObjectID, id);
assert!(!info.device_id.is_null());
assert!(!info.friendly_name.is_null());
assert!(!info.group_id.is_null());
// TODO: Hit a kAudioHardwareUnknownPropertyError for AirPods
// assert!(!info.vendor_name.is_null());
// FIXME: The device is defined to input-only or output-only, but some device is in-out!
assert_eq!(info.device_type, DeviceType::from(scope.clone()).bits());
assert_eq!(info.state, ffi::CUBEB_DEVICE_STATE_ENABLED);
// TODO: The preference is set when the device is default input/output device if the device
// info is created from input/output scope. Should the preference be set if the
// device is a default input/output device if the device info is created from
// output/input scope ? The device may be a in-out device!
assert_eq!(info.preferred, get_cubeb_device_pref(id, scope));
assert_eq!(info.format, ffi::CUBEB_DEVICE_FMT_ALL);
assert_eq!(info.default_format, ffi::CUBEB_DEVICE_FMT_F32NE);
assert!(info.max_channels > 0);
assert!(info.min_rate <= info.max_rate);
assert!(info.min_rate <= info.default_rate);
assert!(info.default_rate <= info.max_rate);
assert!(info.latency_lo > 0);
assert!(info.latency_hi > 0);
assert!(info.latency_lo <= info.latency_hi);
fn get_cubeb_device_pref(id: AudioObjectID, scope: Scope) -> ffi::cubeb_device_pref {
let default_device = test_get_default_device(scope);
if default_device.is_some() && default_device.unwrap() == id {
ffi::CUBEB_DEVICE_PREF_ALL
} else {
ffi::CUBEB_DEVICE_PREF_NONE
}
}
}
}
#[test]
#[should_panic]
fn test_create_device_info_by_unknown_device() {
assert!(create_cubeb_device_info(kAudioObjectUnknown, DeviceType::OUTPUT).is_err());
}
#[test]
fn test_create_device_info_with_unknown_type() {
test_create_device_info_with_unknown_type_by_scope(Scope::Input);
test_create_device_info_with_unknown_type_by_scope(Scope::Output);
fn test_create_device_info_with_unknown_type_by_scope(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
assert!(run_serially_forward_panics(|| 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!(run_serially_forward_panics(|| 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 = 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<AudioObjectID> = input_devices.iter().cloned().collect();
let output: HashSet<AudioObjectID> = output_devices.iter().cloned().collect();
let union: HashSet<AudioObjectID> = input.union(&output).cloned().collect();
let mut union_devices: Vec<AudioObjectID> = union.iter().cloned().collect();
union_devices.sort();
assert_eq!(all_devices, union_devices);
}
#[test]
#[should_panic]
fn test_get_devices_of_type_unknown() {
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<DeviceType, extern "C" fn(*mut ffi::cubeb, *mut c_void)> = HashMap::new();
map.insert(DeviceType::INPUT, in_callback);
map.insert(DeviceType::OUTPUT, out_callback);
map.insert(DeviceType::INPUT | DeviceType::OUTPUT, inout_callback);
test_get_raw_context(|context| {
for (devtype, callback) in map.iter() {
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
// Register a callback within a specific scope.
assert!(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_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!(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_add_devices_changed_listener_in_unknown_scope() {
extern "C" fn callback(_: *mut ffi::cubeb, _: *mut c_void) {}
test_get_raw_context(|context| {
let _ = context.add_devices_changed_listener(
DeviceType::UNKNOWN,
Some(callback),
ptr::null_mut(),
);
});
}
#[test]
#[should_panic]
fn test_add_devices_changed_listener_with_none_callback() {
test_get_raw_context(|context| {
for devtype in &[DeviceType::INPUT, DeviceType::OUTPUT] {
assert!(context
.add_devices_changed_listener(*devtype, None, ptr::null_mut())
.is_ok());
}
});
}
// remove_devices_changed_listener
// ------------------------------------
#[test]
fn test_remove_devices_changed_listener() {
use std::collections::HashMap;
extern "C" fn in_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
extern "C" fn out_callback(_: *mut ffi::cubeb, _: *mut c_void) {}
let mut map: HashMap<DeviceType, extern "C" fn(*mut ffi::cubeb, *mut c_void)> = HashMap::new();
map.insert(DeviceType::INPUT, in_callback);
map.insert(DeviceType::OUTPUT, out_callback);
test_get_raw_context(|context| {
for (devtype, _callback) in map.iter() {
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
// Register callbacks within all scopes.
for (scope, listener) in map.iter() {
assert!(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_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!(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_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!(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<DeviceType, extern "C" fn(*mut ffi::cubeb, *mut c_void)> = HashMap::new();
map.insert(DeviceType::INPUT, in_callback);
map.insert(DeviceType::OUTPUT, out_callback);
map.insert(DeviceType::INPUT | DeviceType::OUTPUT, inout_callback);
test_get_raw_context(|context| {
for (devtype, callback) in map.iter() {
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
assert!(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_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!(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_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());
}