diff options
Diffstat (limited to '')
9 files changed, 1921 insertions, 596 deletions
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs index 1d3c341ae8..6c5e494059 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs @@ -3,6 +3,8 @@ use super::utils::{ test_get_drift_compensations, test_get_master_device, DeviceFilter, Scope, }; use super::*; +use std::iter::zip; +use std::panic; // AggregateDevice::set_sub_devices // ------------------------------------ @@ -19,21 +21,27 @@ fn test_aggregate_set_sub_devices_for_an_unknown_aggregate_device() { let default_input = default_input.unwrap(); let default_output = default_output.unwrap(); assert!( - AggregateDevice::set_sub_devices(kAudioObjectUnknown, default_input, default_output) - .is_err() + run_serially_forward_panics(|| AggregateDevice::set_sub_devices( + kAudioObjectUnknown, + default_input, + default_output + )) + .is_err() ); } #[test] #[should_panic] fn test_aggregate_set_sub_devices_for_unknown_devices() { - // If aggregate device id is kAudioObjectUnknown, we are unable to set device list. - assert!(AggregateDevice::set_sub_devices( - kAudioObjectUnknown, - kAudioObjectUnknown, - kAudioObjectUnknown - ) - .is_err()); + run_serially_forward_panics(|| { + // If aggregate device id is kAudioObjectUnknown, we are unable to set device list. + assert!(AggregateDevice::set_sub_devices( + kAudioObjectUnknown, + kAudioObjectUnknown, + kAudioObjectUnknown + ) + .is_err()); + }); } // AggregateDevice::get_sub_devices @@ -48,7 +56,13 @@ fn test_aggregate_get_sub_devices() { // containing `device` itself if it's not an aggregate device. This test assumes devices // is not an empty aggregate device (Test will panic when calling get_sub_devices with // an empty aggregate device). - let sub_devices = AggregateDevice::get_sub_devices(device).unwrap(); + println!( + "get_sub_devices({}={})", + device, + run_serially_forward_panics(|| get_device_uid(device)) + ); + let sub_devices = + run_serially_forward_panics(|| AggregateDevice::get_sub_devices(device).unwrap()); // TODO: If the device is a blank aggregate device, then the assertion fails! assert!(!sub_devices.is_empty()); } @@ -57,8 +71,10 @@ fn test_aggregate_get_sub_devices() { #[test] #[should_panic] fn test_aggregate_get_sub_devices_for_a_unknown_device() { - let devices = AggregateDevice::get_sub_devices(kAudioObjectUnknown).unwrap(); - assert!(devices.is_empty()); + run_serially_forward_panics(|| { + let devices = AggregateDevice::get_sub_devices(kAudioObjectUnknown).unwrap(); + assert!(devices.is_empty()); + }); } // AggregateDevice::set_master_device @@ -66,7 +82,11 @@ fn test_aggregate_get_sub_devices_for_a_unknown_device() { #[test] #[should_panic] fn test_aggregate_set_master_device_for_an_unknown_aggregate_device() { - assert!(AggregateDevice::set_master_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err()); + run_serially_forward_panics(|| { + assert!( + AggregateDevice::set_master_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err() + ); + }); } // AggregateDevice::activate_clock_drift_compensation @@ -74,7 +94,9 @@ fn test_aggregate_set_master_device_for_an_unknown_aggregate_device() { #[test] #[should_panic] fn test_aggregate_activate_clock_drift_compensation_for_an_unknown_aggregate_device() { - assert!(AggregateDevice::activate_clock_drift_compensation(kAudioObjectUnknown).is_err()); + run_serially_forward_panics(|| { + assert!(AggregateDevice::activate_clock_drift_compensation(kAudioObjectUnknown).is_err()); + }); } // AggregateDevice::destroy_device @@ -82,60 +104,55 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_unknown_aggregate_dev #[test] #[should_panic] fn test_aggregate_destroy_device_for_unknown_plugin_and_aggregate_devices() { - assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err()) + run_serially_forward_panics(|| { + assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err()) + }); } #[test] #[should_panic] fn test_aggregate_destroy_aggregate_device_for_a_unknown_aggregate_device() { - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - assert!(AggregateDevice::destroy_device(plugin, kAudioObjectUnknown).is_err()); + run_serially_forward_panics(|| { + let plugin = AggregateDevice::get_system_plugin_id().unwrap(); + assert!(AggregateDevice::destroy_device(plugin, kAudioObjectUnknown).is_err()); + }); } -// Default Ignored Tests -// ================================================================================================ -// The following tests that calls `AggregateDevice::create_blank_device` are marked `ignore` by -// default since the device-collection-changed callbacks will be fired upon -// `AggregateDevice::create_blank_device` is called (it will plug a new device in system!). -// Some tests rely on the device-collection-changed callbacks in a certain way. The callbacks -// fired from a unexpected `AggregateDevice::create_blank_device` will break those tests. - // AggregateDevice::create_blank_device_sync // ------------------------------------ #[test] -#[ignore] fn test_aggregate_create_blank_device() { // TODO: Test this when there is no available devices. - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); + let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap(); + let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap(); let devices = test_get_all_devices(DeviceFilter::IncludeAll); let device = devices.into_iter().find(|dev| dev == &device).unwrap(); - let uid = get_device_global_uid(device).unwrap().into_string(); + let uid = run_serially(|| get_device_global_uid(device).unwrap().into_string()); assert!(uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME)); - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok()); } // AggregateDevice::get_sub_devices // ------------------------------------ #[test] -#[ignore] #[should_panic] fn test_aggregate_get_sub_devices_for_blank_aggregate_devices() { - // TODO: Test this when there is no available devices. - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - // There is no sub device in a blank aggregate device! - // AggregateDevice::get_sub_devices guarantees returning a non-empty devices vector, so - // the following call will panic! - let sub_devices = AggregateDevice::get_sub_devices(device).unwrap(); - assert!(sub_devices.is_empty()); - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + run_serially_forward_panics(|| { + // TODO: Test this when there is no available devices. + let plugin = AggregateDevice::get_system_plugin_id().unwrap(); + let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); + // There is no sub device in a blank aggregate device! + // AggregateDevice::get_sub_devices guarantees returning a non-empty devices vector, so + // the following call will panic! + let sub_devices = AggregateDevice::get_sub_devices(device).unwrap(); + assert!(sub_devices.is_empty()); + assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + }); } // AggregateDevice::set_sub_devices_sync // ------------------------------------ #[test] -#[ignore] fn test_aggregate_set_sub_devices() { let input_device = test_get_default_device(Scope::Input); let output_device = test_get_default_device(Scope::Output); @@ -147,13 +164,20 @@ fn test_aggregate_set_sub_devices() { let input_device = input_device.unwrap(); let output_device = output_device.unwrap(); - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok()); - - let sub_devices = AggregateDevice::get_sub_devices(device).unwrap(); - let input_sub_devices = AggregateDevice::get_sub_devices(input_device).unwrap(); - let output_sub_devices = AggregateDevice::get_sub_devices(output_device).unwrap(); + let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap(); + let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap(); + assert!(run_serially(|| AggregateDevice::set_sub_devices_sync( + device, + input_device, + output_device + )) + .is_ok()); + + let sub_devices = run_serially(|| AggregateDevice::get_sub_devices(device)).unwrap(); + let input_sub_devices = + run_serially(|| AggregateDevice::get_sub_devices(input_device)).unwrap(); + let output_sub_devices = + run_serially(|| AggregateDevice::get_sub_devices(output_device)).unwrap(); // TODO: There may be overlapping devices between input_sub_devices and output_sub_devices, // but now AggregateDevice::set_sub_devices will add them directly. @@ -168,10 +192,10 @@ fn test_aggregate_set_sub_devices() { assert!(sub_devices.contains(dev)); } - let onwed_devices = test_get_all_onwed_devices(device); - let onwed_device_uids = get_device_uids(&onwed_devices); - let input_sub_device_uids = get_device_uids(&input_sub_devices); - let output_sub_device_uids = get_device_uids(&output_sub_devices); + let onwed_devices = run_serially(|| test_get_all_onwed_devices(device)); + let onwed_device_uids = run_serially(|| get_device_uids(&onwed_devices)); + let input_sub_device_uids = run_serially(|| get_device_uids(&input_sub_devices)); + let output_sub_device_uids = run_serially(|| get_device_uids(&output_sub_devices)); for uid in &input_sub_device_uids { assert!(onwed_device_uids.contains(uid)); } @@ -179,11 +203,10 @@ fn test_aggregate_set_sub_devices() { assert!(onwed_device_uids.contains(uid)); } - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok()); } #[test] -#[ignore] #[should_panic] fn test_aggregate_set_sub_devices_for_unknown_input_devices() { let output_device = test_get_default_device(Scope::Output); @@ -192,16 +215,19 @@ fn test_aggregate_set_sub_devices_for_unknown_input_devices() { } let output_device = output_device.unwrap(); - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); + run_serially_forward_panics(|| { + let plugin = AggregateDevice::get_system_plugin_id().unwrap(); + let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - assert!(AggregateDevice::set_sub_devices(device, kAudioObjectUnknown, output_device).is_err()); + assert!( + AggregateDevice::set_sub_devices(device, kAudioObjectUnknown, output_device).is_err() + ); - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + }); } #[test] -#[ignore] #[should_panic] fn test_aggregate_set_sub_devices_for_unknown_output_devices() { let input_device = test_get_default_device(Scope::Input); @@ -210,12 +236,16 @@ fn test_aggregate_set_sub_devices_for_unknown_output_devices() { } let input_device = input_device.unwrap(); - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); + run_serially_forward_panics(|| { + let plugin = AggregateDevice::get_system_plugin_id().unwrap(); + let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - assert!(AggregateDevice::set_sub_devices(device, input_device, kAudioObjectUnknown).is_err()); + assert!( + AggregateDevice::set_sub_devices(device, input_device, kAudioObjectUnknown).is_err() + ); - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + }); } fn get_device_uids(devices: &Vec<AudioObjectID>) -> Vec<String> { @@ -228,7 +258,6 @@ fn get_device_uids(devices: &Vec<AudioObjectID>) -> Vec<String> { // AggregateDevice::set_master_device // ------------------------------------ #[test] -#[ignore] fn test_aggregate_set_master_device() { let input_device = test_get_default_device(Scope::Input); let output_device = test_get_default_device(Scope::Output); @@ -240,22 +269,28 @@ fn test_aggregate_set_master_device() { let input_device = input_device.unwrap(); let output_device = output_device.unwrap(); - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok()); - assert!(AggregateDevice::set_master_device(device, output_device).is_ok()); - - // Check if master is set to the first sub device of the default output device. - let first_output_sub_device_uid = - get_device_uid(AggregateDevice::get_sub_devices(device).unwrap()[0]); - let master_device_uid = test_get_master_device(device); + let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap(); + let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap(); + assert!(run_serially(|| AggregateDevice::set_sub_devices_sync( + device, + input_device, + output_device + )) + .is_ok()); + assert!(run_serially(|| AggregateDevice::set_master_device(device, output_device)).is_ok()); + + let output_sub_devices = + run_serially(|| AggregateDevice::get_sub_devices(output_device)).unwrap(); + let first_output_sub_device_uid = run_serially(|| get_device_uid(output_sub_devices[0])); + + // Check that the first sub device of the output device is set as master device. + let master_device_uid = run_serially(|| test_get_master_device(device)); assert_eq!(first_output_sub_device_uid, master_device_uid); - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok()); } #[test] -#[ignore] fn test_aggregate_set_master_device_for_a_blank_aggregate_device() { let output_device = test_get_default_device(Scope::Output); if output_device.is_none() { @@ -263,9 +298,11 @@ fn test_aggregate_set_master_device_for_a_blank_aggregate_device() { return; } - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - assert!(AggregateDevice::set_master_device(device, output_device.unwrap()).is_ok()); + let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap(); + let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap(); + assert!( + run_serially(|| AggregateDevice::set_master_device(device, output_device.unwrap())).is_ok() + ); // TODO: it's really weird the aggregate device actually own nothing // but its master device can be set successfully! @@ -275,17 +312,16 @@ fn test_aggregate_set_master_device_for_a_blank_aggregate_device() { // The CFStringRef of the master device returned from `test_get_master_device` is actually // non-null. - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok()); } fn get_device_uid(id: AudioObjectID) -> String { - get_device_global_uid(id).unwrap().into_string() + get_device_global_uid(id).map_or(String::new(), |uid| uid.into_string()) } // AggregateDevice::activate_clock_drift_compensation // ------------------------------------ #[test] -#[ignore] fn test_aggregate_activate_clock_drift_compensation() { let input_device = test_get_default_device(Scope::Input); let output_device = test_get_default_device(Scope::Output); @@ -297,27 +333,40 @@ fn test_aggregate_activate_clock_drift_compensation() { let input_device = input_device.unwrap(); let output_device = output_device.unwrap(); - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok()); - assert!(AggregateDevice::set_master_device(device, output_device).is_ok()); - assert!(AggregateDevice::activate_clock_drift_compensation(device).is_ok()); + let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap(); + let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap(); + assert!(run_serially(|| AggregateDevice::set_sub_devices_sync( + device, + input_device, + output_device + )) + .is_ok()); + assert!(run_serially(|| AggregateDevice::set_master_device(device, output_device)).is_ok()); + assert!(run_serially(|| AggregateDevice::activate_clock_drift_compensation(device)).is_ok()); // Check the compensations. - let devices = test_get_all_onwed_devices(device); - let compensations = get_drift_compensations(&devices); + let devices = run_serially(|| test_get_all_onwed_devices(device)); + let compensations = run_serially(|| get_drift_compensations(&devices)); + let master_device_uid = run_serially(|| test_get_master_device(device)); assert!(!compensations.is_empty()); assert_eq!(devices.len(), compensations.len()); - for (i, compensation) in compensations.iter().enumerate() { - assert_eq!(*compensation, if i == 0 { 0 } else { DRIFT_COMPENSATION }); + for (device, compensation) in zip(devices, compensations) { + let uid = get_device_uid(device); + assert_eq!( + compensation, + if uid == master_device_uid { + 0 + } else { + DRIFT_COMPENSATION + } + ); } - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok()); } #[test] -#[ignore] fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_without_master_device() { let input_device = test_get_default_device(Scope::Input); @@ -330,25 +379,32 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_with let input_device = input_device.unwrap(); let output_device = output_device.unwrap(); - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok()); - - // TODO: Is the master device the first output sub device by default if we - // don't set that ? Is it because we add the output sub device list - // before the input's one ? (See implementation of - // AggregateDevice::set_sub_devices). - let first_output_sub_device_uid = - get_device_uid(AggregateDevice::get_sub_devices(output_device).unwrap()[0]); - let master_device_uid = test_get_master_device(device); - assert_eq!(first_output_sub_device_uid, master_device_uid); + let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap(); + let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap(); + assert!(run_serially(|| AggregateDevice::set_sub_devices_sync( + device, + input_device, + output_device + )) + .is_ok()); + + // The master device is by default the first sub device in the list. + // This happens to be the first sub device of the input device, see implementation of + // AggregateDevice::set_sub_devices. + let first_input_sub_device_uid = + run_serially(|| get_device_uid(AggregateDevice::get_sub_devices(input_device).unwrap()[0])); + let first_sub_device_uid = + run_serially(|| get_device_uid(AggregateDevice::get_sub_devices(device).unwrap()[0])); + assert_eq!(first_input_sub_device_uid, first_sub_device_uid); + let master_device_uid = run_serially(|| test_get_master_device(device)); + assert_eq!(first_sub_device_uid, master_device_uid); // Compensate the drift directly without setting master device. - assert!(AggregateDevice::activate_clock_drift_compensation(device).is_ok()); + assert!(run_serially(|| AggregateDevice::activate_clock_drift_compensation(device)).is_ok()); // Check the compensations. - let devices = test_get_all_onwed_devices(device); - let compensations = get_drift_compensations(&devices); + let devices = run_serially(|| test_get_all_onwed_devices(device)); + let compensations = run_serially(|| get_drift_compensations(&devices)); assert!(!compensations.is_empty()); assert_eq!(devices.len(), compensations.len()); @@ -356,25 +412,26 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_with assert_eq!(*compensation, if i == 0 { 0 } else { DRIFT_COMPENSATION }); } - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok()); } #[test] #[should_panic] -#[ignore] fn test_aggregate_activate_clock_drift_compensation_for_a_blank_aggregate_device() { - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); + run_serially_forward_panics(|| { + let plugin = AggregateDevice::get_system_plugin_id().unwrap(); + let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - let sub_devices = AggregateDevice::get_sub_devices(device).unwrap(); - assert!(sub_devices.is_empty()); - let onwed_devices = test_get_all_onwed_devices(device); - assert!(onwed_devices.is_empty()); + let sub_devices = AggregateDevice::get_sub_devices(device).unwrap(); + assert!(sub_devices.is_empty()); + let onwed_devices = test_get_all_onwed_devices(device); + assert!(onwed_devices.is_empty()); - // Get a panic since no sub devices to be set compensation. - assert!(AggregateDevice::activate_clock_drift_compensation(device).is_err()); + // Get a panic since no sub devices to be set compensation. + assert!(AggregateDevice::activate_clock_drift_compensation(device).is_err()); - assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + assert!(AggregateDevice::destroy_device(plugin, device).is_ok()); + }); } fn get_drift_compensations(devices: &Vec<AudioObjectID>) -> Vec<u32> { @@ -391,10 +448,56 @@ fn get_drift_compensations(devices: &Vec<AudioObjectID>) -> Vec<u32> { // AggregateDevice::destroy_device // ------------------------------------ #[test] -#[ignore] #[should_panic] fn test_aggregate_destroy_aggregate_device_for_a_unknown_plugin_device() { - let plugin = AggregateDevice::get_system_plugin_id().unwrap(); - let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); - assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, device).is_err()); + run_serially_forward_panics(|| { + let plugin = AggregateDevice::get_system_plugin_id().unwrap(); + let device = AggregateDevice::create_blank_device_sync(plugin).unwrap(); + assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, device).is_err()); + }); +} + +// AggregateDevice::new +// ------------------------------------ +#[test] +fn test_aggregate_new() { + 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; + } + + run_serially_forward_panics(|| { + let input_device = input_device.unwrap(); + let output_device = output_device.unwrap(); + + let aggr = AggregateDevice::new(input_device, output_device).unwrap(); + + // Check main device + let output_sub_devices = AggregateDevice::get_sub_devices(output_device).unwrap(); + let first_output_sub_device_uid = get_device_uid(output_sub_devices[0]); + let master_device_uid = test_get_master_device(aggr.get_device_id()); + assert_eq!(first_output_sub_device_uid, master_device_uid); + + // Check drift compensation + let devices = test_get_all_onwed_devices(aggr.get_device_id()); + let compensations = get_drift_compensations(&devices); + assert!(!compensations.is_empty()); + assert_eq!(devices.len(), compensations.len()); + + let device_uids = devices.iter().map(|&id| get_device_uid(id)); + for (uid, compensation) in zip(device_uids, compensations) { + assert_eq!( + compensation, + if uid == master_device_uid { + 0 + } else { + DRIFT_COMPENSATION + }, + "Unexpected drift value for device with uid {}", + uid + ); + } + }); } diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs index 4cd86c094e..5ce2374a3e 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs @@ -56,7 +56,7 @@ fn test_increase_and_decrease_context_streams() { assert_eq!(context.active_streams(), STREAMS); check_streams(&context, STREAMS); - check_latency(&context, latencies[0]); + check_latency(&context, Some(latencies[0])); for i in 0..latencies.len() - 1 { assert_eq!(latencies[i], latencies[i + 1]); } @@ -149,7 +149,8 @@ fn test_minimum_resampling_input_frames_equal_input_output_rate() { #[test] fn test_create_device_info_from_unknown_input_device() { if let Some(default_device_id) = test_get_default_device(Scope::Input) { - let default_device = create_device_info(kAudioObjectUnknown, DeviceType::INPUT).unwrap(); + 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, @@ -163,7 +164,8 @@ fn test_create_device_info_from_unknown_input_device() { #[test] fn test_create_device_info_from_unknown_output_device() { if let Some(default_device_id) = test_get_default_device(Scope::Output) { - let default_device = create_device_info(kAudioObjectUnknown, DeviceType::OUTPUT).unwrap(); + 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, @@ -177,13 +179,17 @@ fn test_create_device_info_from_unknown_output_device() { #[test] #[should_panic] fn test_set_device_info_to_system_input_device() { - let _device = create_device_info(kAudioObjectSystemObject, DeviceType::INPUT); + 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 = create_device_info(kAudioObjectSystemObject, DeviceType::OUTPUT); + let _device = run_serially_forward_panics(|| { + create_device_info(kAudioObjectSystemObject, DeviceType::OUTPUT) + }); } // FIXME: Is it ok to set input device to a nonexistent device ? @@ -192,7 +198,8 @@ fn test_set_device_info_to_system_output_device() { #[should_panic] fn test_set_device_info_to_nonexistent_input_device() { let nonexistent_id = std::u32::MAX; - let _device = create_device_info(nonexistent_id, DeviceType::INPUT); + 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 ? @@ -201,7 +208,8 @@ fn test_set_device_info_to_nonexistent_input_device() { #[should_panic] fn test_set_device_info_to_nonexistent_output_device() { let nonexistent_id = std::u32::MAX; - let _device = create_device_info(nonexistent_id, DeviceType::OUTPUT); + let _device = + run_serially_forward_panics(|| create_device_info(nonexistent_id, DeviceType::OUTPUT)); } // add_listener (for default output device) @@ -227,10 +235,10 @@ fn test_add_listener_unknown_device() { ), callback, ); - let mut res: OSStatus = 0; - stream + let res = stream .queue - .run_sync(|| res = stream.add_device_listener(&listener)); + .run_sync(|| stream.add_device_listener(&listener)) + .unwrap(); assert_eq!(res, kAudioHardwareBadObjectError as OSStatus); }); } @@ -258,14 +266,15 @@ fn test_add_listener_then_remove_system_device() { ), callback, ); - let mut res: OSStatus = 0; - stream + let res = stream .queue - .run_sync(|| res = stream.add_device_listener(&listener)); + .run_sync(|| stream.add_device_listener(&listener)) + .unwrap(); assert_eq!(res, NO_ERR); - stream + let res = stream .queue - .run_sync(|| res = stream.remove_device_listener(&listener)); + .run_sync(|| stream.remove_device_listener(&listener)) + .unwrap(); assert_eq!(res, NO_ERR); }); } @@ -291,10 +300,10 @@ fn test_remove_listener_without_adding_any_listener_before_system_device() { ), callback, ); - let mut res: OSStatus = 0; - stream + let res = stream .queue - .run_sync(|| res = stream.remove_device_listener(&listener)); + .run_sync(|| stream.remove_device_listener(&listener)) + .unwrap(); assert_eq!(res, NO_ERR); }); } @@ -320,10 +329,10 @@ fn test_remove_listener_unknown_device() { ), callback, ); - let mut res: OSStatus = 0; - stream + let res = stream .queue - .run_sync(|| res = stream.remove_device_listener(&listener)); + .run_sync(|| stream.remove_device_listener(&listener)) + .unwrap(); assert_eq!(res, kAudioHardwareBadObjectError as OSStatus); }); } @@ -334,14 +343,14 @@ fn test_remove_listener_unknown_device() { fn test_get_default_device_id() { if test_get_default_device(Scope::Input).is_some() { assert_ne!( - get_default_device_id(DeviceType::INPUT).unwrap(), + run_serially(|| get_default_device_id(DeviceType::INPUT)).unwrap(), kAudioObjectUnknown, ); } if test_get_default_device(Scope::Output).is_some() { assert_ne!( - get_default_device_id(DeviceType::OUTPUT).unwrap(), + run_serially(|| get_default_device_id(DeviceType::OUTPUT)).unwrap(), kAudioObjectUnknown, ); } @@ -350,13 +359,16 @@ fn test_get_default_device_id() { #[test] #[should_panic] fn test_get_default_device_id_with_unknown_type() { - assert!(get_default_device_id(DeviceType::UNKNOWN).is_err()); + 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!(get_default_device_id(DeviceType::INPUT | DeviceType::OUTPUT).is_err()); + assert!(run_serially_forward_panics(|| get_default_device_id( + DeviceType::INPUT | DeviceType::OUTPUT + )) + .is_err()); } // convert_channel_layout @@ -724,9 +736,11 @@ fn test_convert_channel_layout() { #[test] fn test_get_preferred_channel_layout_output() { match test_get_default_audiounit(Scope::Output) { - Some(unit) => assert!(!audiounit_get_preferred_channel_layout(unit.get_inner()) - .unwrap() - .is_empty()), + Some(unit) => assert!(!run_serially(|| audiounit_get_preferred_channel_layout( + unit.get_inner() + )) + .unwrap() + .is_empty()), None => println!("No output audiounit for test."), } } @@ -736,9 +750,15 @@ fn test_get_preferred_channel_layout_output() { #[test] fn test_get_current_channel_layout_output() { match test_get_default_audiounit(Scope::Output) { - Some(unit) => assert!(!audiounit_get_current_channel_layout(unit.get_inner()) - .unwrap() - .is_empty()), + 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."), } } @@ -814,10 +834,30 @@ fn test_enable_audiounit_scope() { // for the unit whose subtype is kAudioUnitSubType_HALOutput // even when there is no available input or output devices. if let Some(unit) = test_create_audiounit(ComponentSubType::HALOutput) { - assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true).is_ok()); - assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false).is_ok()); - assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true).is_ok()); - assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false).is_ok()); + 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."); } @@ -827,19 +867,23 @@ fn test_enable_audiounit_scope() { fn test_enable_audiounit_scope_for_default_output_unit() { if let Some(unit) = test_create_audiounit(ComponentSubType::DefaultOutput) { assert_eq!( - enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true).unwrap_err(), + run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true)) + .unwrap_err(), kAudioUnitErr_InvalidProperty ); assert_eq!( - enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false).unwrap_err(), + run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false)) + .unwrap_err(), kAudioUnitErr_InvalidProperty ); assert_eq!( - enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true).unwrap_err(), + run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true)) + .unwrap_err(), kAudioUnitErr_InvalidProperty ); assert_eq!( - enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false).unwrap_err(), + run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false)) + .unwrap_err(), kAudioUnitErr_InvalidProperty ); } @@ -849,7 +893,10 @@ fn test_enable_audiounit_scope_for_default_output_unit() { #[should_panic] fn test_enable_audiounit_scope_with_null_unit() { let unit: AudioUnit = ptr::null_mut(); - assert!(enable_audiounit_scope(unit, DeviceType::INPUT, false).is_err()); + assert!( + run_serially_forward_panics(|| enable_audiounit_scope(unit, DeviceType::INPUT, false)) + .is_err() + ); } // create_audiounit @@ -868,29 +915,29 @@ fn test_for_create_audiounit() { // Check the output scope is enabled. if device.flags.contains(device_flags::DEV_OUTPUT) && default_output.is_some() { device.id = default_output.unwrap(); - let unit = create_audiounit(&device).unwrap(); + let unit = run_serially(|| create_audiounit(&device).unwrap()); assert!(!unit.is_null()); assert!(test_audiounit_scope_is_enabled(unit, Scope::Output)); // Destroy the AudioUnit. - unsafe { + 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 = create_audiounit(&device).unwrap(); + let unit = run_serially(|| create_audiounit(&device).unwrap()); assert!(!unit.is_null()); assert!(test_audiounit_scope_is_enabled(unit, Scope::Input)); // Destroy the AudioUnit. - unsafe { + run_serially(|| unsafe { AudioUnitUninitialize(unit); AudioComponentInstanceDispose(unit); - } + }); } } } @@ -899,7 +946,7 @@ fn test_for_create_audiounit() { #[should_panic] fn test_create_audiounit_with_unknown_scope() { let device = device_info::default(); - let _unit = create_audiounit(&device); + let _unit = run_serially_forward_panics(|| create_audiounit(&device)); } // set_buffer_size_sync @@ -927,9 +974,12 @@ fn test_set_buffer_size_sync() { .unwrap(); assert_ne!(buffer_frames, 0); buffer_frames *= 2; - assert!( - set_buffer_size_sync(unit.get_inner(), scope.clone().into(), buffer_frames).is_ok() - ); + 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(); @@ -951,7 +1001,9 @@ fn test_set_buffer_size_sync_for_output_with_null_output_unit() { fn test_set_buffer_size_sync_by_scope_with_null_unit(scope: Scope) { let unit: AudioUnit = ptr::null_mut(); - assert!(set_buffer_size_sync(unit, scope.into(), 2048).is_err()); + assert!( + run_serially_forward_panics(|| set_buffer_size_sync(unit, scope.into(), 2048)).is_err() + ); } // get_volume, set_volume @@ -960,8 +1012,11 @@ fn test_set_buffer_size_sync_by_scope_with_null_unit(scope: Scope) { fn test_stream_get_volume() { if let Some(unit) = test_get_default_audiounit(Scope::Output) { let expected_volume: f32 = 0.5; - set_volume(unit.get_inner(), expected_volume); - assert_eq!(expected_volume, get_volume(unit.get_inner()).unwrap()); + 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."); } @@ -988,7 +1043,9 @@ fn test_get_channel_count() { fn test_channel_count(scope: Scope) { if let Some(device) = test_get_default_device(scope.clone()) { - let channels = get_channel_count(device, DeviceType::from(scope.clone())).unwrap(); + let channels = + run_serially(|| get_channel_count(device, DeviceType::from(scope.clone()))) + .unwrap(); assert!(channels > 0); assert_eq!( channels, @@ -1008,7 +1065,7 @@ fn test_get_channel_count_of_input_for_a_output_only_deivce() { if test_device_in_scope(device, Scope::Input) { continue; } - let count = get_channel_count(device, DeviceType::INPUT).unwrap(); + let count = run_serially(|| get_channel_count(device, DeviceType::INPUT)).unwrap(); assert_eq!(count, 0); } } @@ -1021,7 +1078,7 @@ fn test_get_channel_count_of_output_for_a_input_only_deivce() { if test_device_in_scope(device, Scope::Output) { continue; } - let count = get_channel_count(device, DeviceType::OUTPUT).unwrap(); + let count = run_serially(|| get_channel_count(device, DeviceType::OUTPUT)).unwrap(); assert_eq!(count, 0); } } @@ -1029,7 +1086,11 @@ fn test_get_channel_count_of_output_for_a_input_only_deivce() { #[test] #[should_panic] fn test_get_channel_count_of_unknown_device() { - assert!(get_channel_count(kAudioObjectUnknown, DeviceType::OUTPUT).is_err()); + assert!(run_serially_forward_panics(|| get_channel_count( + kAudioObjectUnknown, + DeviceType::OUTPUT + )) + .is_err()); } #[test] @@ -1039,14 +1100,16 @@ fn test_get_channel_count_of_inout_type() { fn test_channel_count(scope: Scope) { if let Some(device) = test_get_default_device(scope.clone()) { - assert_eq!( - get_channel_count(device, DeviceType::INPUT | DeviceType::OUTPUT), - get_channel_count(device, DeviceType::INPUT).map(|c| c + get_channel_count( - device, - DeviceType::OUTPUT - ) - .unwrap_or(0)) - ); + 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); } @@ -1095,7 +1158,9 @@ fn test_get_range_of_sample_rates() { ]; let mut ranges = Vec::new(); for scope in scopes.iter() { - ranges.push(get_range_of_sample_rates(id, *scope).unwrap()); + ranges.push( + run_serially_forward_panics(|| get_range_of_sample_rates(id, *scope)).unwrap(), + ); } ranges } @@ -1117,7 +1182,7 @@ fn test_get_device_presentation_latency() { fn test_get_device_presentation_latencies_in_scope(scope: Scope) { if let Some(device) = test_get_default_device(scope.clone()) { // TODO: The latencies very from devices to devices. Check nothing here. - let latency = get_fixed_latency(device, scope.clone().into()); + let latency = run_serially(|| get_fixed_latency(device, scope.clone().into())); println!( "present latency on the device {} in scope {:?}: {}", device, scope, latency @@ -1133,7 +1198,7 @@ fn test_get_device_presentation_latency() { #[test] fn test_get_device_group_id() { if let Some(device) = test_get_default_device(Scope::Input) { - match get_device_group_id(device, DeviceType::INPUT) { + match run_serially(|| get_device_group_id(device, DeviceType::INPUT)) { Ok(id) => println!("input group id: {:?}", id), Err(e) => println!("No input group id. Error: {}", e), } @@ -1142,7 +1207,7 @@ fn test_get_device_group_id() { } if let Some(device) = test_get_default_device(Scope::Output) { - match get_device_group_id(device, DeviceType::OUTPUT) { + match run_serially(|| get_device_group_id(device, DeviceType::OUTPUT)) { Ok(id) => println!("output group id: {:?}", id), Err(e) => println!("No output group id. Error: {}", e), } @@ -1165,8 +1230,8 @@ fn test_get_same_group_id_for_builtin_device_pairs() { let mut input_group_ids = HashMap::<u32, String>::new(); let input_devices = test_get_devices_in_scope(Scope::Input); for device in input_devices.iter() { - match get_device_source(*device, DeviceType::INPUT) { - Ok(source) => match get_device_group_id(*device, DeviceType::INPUT) { + match run_serially(|| get_device_source(*device, DeviceType::INPUT)) { + Ok(source) => match run_serially(|| get_device_group_id(*device, DeviceType::INPUT)) { Ok(id) => assert!(input_group_ids .insert(source, id.into_string().unwrap()) .is_none()), @@ -1181,8 +1246,8 @@ fn test_get_same_group_id_for_builtin_device_pairs() { let mut output_group_ids = HashMap::<u32, String>::new(); let output_devices = test_get_devices_in_scope(Scope::Output); for device in output_devices.iter() { - match get_device_source(*device, DeviceType::OUTPUT) { - Ok(source) => match get_device_group_id(*device, DeviceType::OUTPUT) { + match run_serially(|| get_device_source(*device, DeviceType::OUTPUT)) { + Ok(source) => match run_serially(|| get_device_group_id(*device, DeviceType::OUTPUT)) { Ok(id) => assert!(output_group_ids .insert(source, id.into_string().unwrap()) .is_none()), @@ -1210,7 +1275,11 @@ fn test_get_same_group_id_for_builtin_device_pairs() { #[test] #[should_panic] fn test_get_device_group_id_by_unknown_device() { - assert!(get_device_group_id(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_group_id( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_label @@ -1218,14 +1287,14 @@ fn test_get_device_group_id_by_unknown_device() { #[test] fn test_get_device_label() { if let Some(device) = test_get_default_device(Scope::Input) { - let name = get_device_label(device, DeviceType::INPUT).unwrap(); + let name = run_serially(|| get_device_label(device, DeviceType::INPUT)).unwrap(); println!("input device label: {}", name.into_string()); } else { println!("No input device."); } if let Some(device) = test_get_default_device(Scope::Output) { - let name = get_device_label(device, DeviceType::OUTPUT).unwrap(); + let name = run_serially(|| get_device_label(device, DeviceType::OUTPUT)).unwrap(); println!("output device label: {}", name.into_string()); } else { println!("No output device."); @@ -1235,7 +1304,11 @@ fn test_get_device_label() { #[test] #[should_panic] fn test_get_device_label_by_unknown_device() { - assert!(get_device_label(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_label( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_global_uid @@ -1244,14 +1317,14 @@ fn test_get_device_label_by_unknown_device() { fn test_get_device_global_uid() { // Input device. if let Some(input) = test_get_default_device(Scope::Input) { - let uid = get_device_global_uid(input).unwrap(); + let uid = run_serially(|| get_device_global_uid(input)).unwrap(); let uid = uid.into_string(); assert!(!uid.is_empty()); } // Output device. if let Some(output) = test_get_default_device(Scope::Output) { - let uid = get_device_global_uid(output).unwrap(); + let uid = run_serially(|| get_device_global_uid(output)).unwrap(); let uid = uid.into_string(); assert!(!uid.is_empty()); } @@ -1285,7 +1358,7 @@ fn test_create_cubeb_device_info() { if is_input { let mut input_device_info = input_result.unwrap(); check_device_info_by_device(&input_device_info, device, Scope::Input); - destroy_cubeb_device_info(&mut input_device_info); + run_serially(|| destroy_cubeb_device_info(&mut input_device_info)); } else { assert_eq!(input_result.unwrap_err(), Error::error()); } @@ -1294,7 +1367,7 @@ fn test_create_cubeb_device_info() { if is_output { let mut output_device_info = output_result.unwrap(); check_device_info_by_device(&output_device_info, device, Scope::Output); - destroy_cubeb_device_info(&mut output_device_info); + run_serially(|| destroy_cubeb_device_info(&mut output_device_info)); } else { assert_eq!(output_result.unwrap_err(), Error::error()); } @@ -1309,7 +1382,7 @@ fn test_create_cubeb_device_info() { let dev_types = [DeviceType::INPUT, DeviceType::OUTPUT]; let mut results = VecDeque::new(); for dev_type in dev_types.iter() { - results.push_back(create_cubeb_device_info(id, *dev_type)); + results.push_back(run_serially(|| create_cubeb_device_info(id, *dev_type))); } results } @@ -1369,7 +1442,9 @@ fn test_create_device_info_with_unknown_type() { fn test_create_device_info_with_unknown_type_by_scope(scope: Scope) { if let Some(device) = test_get_default_device(scope.clone()) { - assert!(create_cubeb_device_info(device, DeviceType::UNKNOWN).is_err()); + assert!( + run_serially(|| create_cubeb_device_info(device, DeviceType::UNKNOWN)).is_err() + ); } } } @@ -1401,9 +1476,11 @@ fn test_create_device_from_hwdev_with_inout_type() { fn test_create_device_from_hwdev_with_inout_type_by_scope(scope: Scope) { if let Some(device) = test_get_default_device(scope.clone()) { // Get a kAudioHardwareUnknownPropertyError in get_channel_count actually. - assert!( - create_cubeb_device_info(device, DeviceType::INPUT | DeviceType::OUTPUT).is_err() - ); + assert!(run_serially(|| create_cubeb_device_info( + device, + DeviceType::INPUT | DeviceType::OUTPUT + )) + .is_err()); } else { println!("No device for {:?}.", scope); } @@ -1416,9 +1493,10 @@ fn test_create_device_from_hwdev_with_inout_type() { fn test_get_devices_of_type() { use std::collections::HashSet; - let all_devices = audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT); - let input_devices = audiounit_get_devices_of_type(DeviceType::INPUT); - let output_devices = audiounit_get_devices_of_type(DeviceType::OUTPUT); + let all_devices = + run_serially(|| audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT)); + let input_devices = run_serially(|| audiounit_get_devices_of_type(DeviceType::INPUT)); + let output_devices = run_serially(|| audiounit_get_devices_of_type(DeviceType::OUTPUT)); let mut expected_all = test_get_all_devices(DeviceFilter::ExcludeCubebAggregateAndVPIO); expected_all.sort(); @@ -1443,8 +1521,10 @@ fn test_get_devices_of_type() { #[test] #[should_panic] fn test_get_devices_of_type_unknown() { - let no_devs = audiounit_get_devices_of_type(DeviceType::UNKNOWN); - assert!(no_devs.is_empty()); + run_serially_forward_panics(|| { + let no_devs = audiounit_get_devices_of_type(DeviceType::UNKNOWN); + assert!(no_devs.is_empty()); + }); } // add_devices_changed_listener @@ -1468,9 +1548,10 @@ fn test_add_devices_changed_listener() { assert!(get_devices_changed_callback(context, Scope::Output).is_none()); // Register a callback within a specific scope. - assert!(context - .add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut()) - .is_ok()); + 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); @@ -1491,9 +1572,10 @@ fn test_add_devices_changed_listener() { } // Unregister the callbacks within all scopes. - assert!(context - .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT) - .is_ok()); + 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()); @@ -1547,9 +1629,12 @@ fn test_remove_devices_changed_listener() { // Register callbacks within all scopes. for (scope, listener) in map.iter() { - assert!(context - .add_devices_changed_listener(*scope, Some(*listener), ptr::null_mut()) - .is_ok()); + 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); @@ -1566,7 +1651,7 @@ fn test_remove_devices_changed_listener() { ); // Unregister the callbacks within one specific scopes. - assert!(context.remove_devices_changed_listener(*devtype).is_ok()); + 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); @@ -1587,9 +1672,10 @@ fn test_remove_devices_changed_listener() { } // Unregister the callbacks within all scopes. - assert!(context - .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT) - .is_ok()); + assert!(run_serially( + || context.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT) + ) + .is_ok()); } }); } @@ -1602,7 +1688,7 @@ fn test_remove_devices_changed_listener_without_adding_listeners() { DeviceType::OUTPUT, DeviceType::INPUT | DeviceType::OUTPUT, ] { - assert!(context.remove_devices_changed_listener(*devtype).is_ok()); + assert!(run_serially(|| context.remove_devices_changed_listener(*devtype)).is_ok()); } }); } @@ -1625,9 +1711,12 @@ fn test_remove_devices_changed_listener_within_all_scopes() { assert!(get_devices_changed_callback(context, Scope::Input).is_none()); assert!(get_devices_changed_callback(context, Scope::Output).is_none()); - assert!(context - .add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut()) - .is_ok()); + 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); @@ -1641,9 +1730,10 @@ fn test_remove_devices_changed_listener_within_all_scopes() { assert_eq!(cb.unwrap(), *callback); } - assert!(context - .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT) - .is_ok()); + 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()); @@ -1661,3 +1751,135 @@ fn get_devices_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()); +} diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs index c27dada7ad..1201c446ea 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs @@ -21,7 +21,7 @@ use super::utils::{ test_set_default_device, Scope, StreamType, TestDevicePlugger, TestDeviceSwitcher, }; use super::*; -use std::sync::{LockResult, MutexGuard, WaitTimeoutResult}; +use std::sync::{LockResult, WaitTimeoutResult}; // Switch default devices used by the active streams, to test stream reinitialization // ================================================================================================ @@ -49,19 +49,21 @@ fn test_switch_device_in_scope(scope: Scope) { let notifier = Arc::new(Notifier::new(0)); let also_notifier = notifier.clone(); - let listener = test_create_device_change_listener(scope.clone(), move |_addresses| { - let mut cnt = notifier.lock().unwrap(); - *cnt += 1; - notifier.notify(cnt); - NO_ERR + let listener = run_serially(|| { + test_create_device_change_listener(scope.clone(), move |_addresses| { + let mut cnt = notifier.lock().unwrap(); + *cnt += 1; + notifier.notify(cnt); + NO_ERR + }) }); - listener.start(); + run_serially(|| listener.start()); let changed_watcher = Watcher::new(&also_notifier); test_get_started_stream_in_scope(scope.clone(), move |_stream| loop { - let mut guard = changed_watcher.lock().unwrap(); - let start_cnt = guard.clone(); + let start_cnt = changed_watcher.lock().unwrap().clone(); device_switcher.next(); + let mut guard = changed_watcher.lock().unwrap(); guard = changed_watcher .wait_while(guard, |cnt| *cnt == start_cnt) .unwrap(); @@ -709,11 +711,7 @@ fn test_unplug_a_device_on_an_active_stream( state_callback, device_changed_callback, |stream| { - stream.start(); - - let changed_watcher = Watcher::new(¬ifier); - let mut data_guard = notifier.lock().unwrap(); - assert_eq!(data_guard.states.last().unwrap(), &ffi::CUBEB_STATE_STARTED); + assert_eq!(stream.start(), Ok(())); println!( "Stream runs on the device {} for {:?}", @@ -722,13 +720,20 @@ fn test_unplug_a_device_on_an_active_stream( ); let dev = plugger.get_device_id(); - let start_changed_count = data_guard.changed_count.clone(); + let start_changed_count = { + let guard = notifier.lock().unwrap(); + assert_eq!(guard.states.last().unwrap(), &ffi::CUBEB_STATE_STARTED); + guard.changed_count.clone() + }; assert!(plugger.unplug().is_ok()); + let changed_watcher = Watcher::new(¬ifier); + if set_device_to_default { // The stream will be reinitialized if it follows the default input or output device. println!("Waiting for default device to change and reinit"); + let mut data_guard = notifier.lock().unwrap(); data_guard = changed_watcher .wait_while(data_guard, |data| { data.changed_count == start_changed_count @@ -740,6 +745,7 @@ fn test_unplug_a_device_on_an_active_stream( // stream can be dropped immediately before device-changed callback // so we only check the states if we wait for it explicitly. println!("Waiting for non-default device to enter error state"); + let mut data_guard = notifier.lock().unwrap(); let (new_guard, timeout_res) = changed_watcher .wait_timeout_while(data_guard, Duration::from_millis(wait_up_to_ms), |data| { data.states.last().unwrap_or(&ffi::CUBEB_STATE_STARTED) diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs index 8277a7642d..a974aee64b 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs @@ -7,14 +7,14 @@ use super::*; fn test_get_device_uid() { // Input device. if let Some(input) = test_get_default_device(Scope::Input) { - let uid = get_device_uid(input, DeviceType::INPUT).unwrap(); + let uid = run_serially(|| get_device_uid(input, DeviceType::INPUT)).unwrap(); let uid = uid.into_string(); assert!(!uid.is_empty()); } // Output device. if let Some(output) = test_get_default_device(Scope::Output) { - let uid = get_device_uid(output, DeviceType::OUTPUT).unwrap(); + let uid = run_serially(|| get_device_uid(output, DeviceType::OUTPUT)).unwrap(); let uid = uid.into_string(); assert!(!uid.is_empty()); } @@ -24,7 +24,10 @@ fn test_get_device_uid() { #[should_panic] fn test_get_device_uid_by_unknwon_device() { // Unknown device. - assert!(get_device_uid(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!( + run_serially_forward_panics(|| get_device_uid(kAudioObjectUnknown, DeviceType::INPUT)) + .is_err() + ); } // get_device_model_uid @@ -33,7 +36,7 @@ fn test_get_device_uid_by_unknwon_device() { #[test] fn test_get_device_model_uid() { if let Some(device) = test_get_default_device(Scope::Input) { - match get_device_model_uid(device, DeviceType::INPUT) { + match run_serially(|| get_device_model_uid(device, DeviceType::INPUT)) { Ok(uid) => println!("input model uid: {}", uid.into_string()), Err(e) => println!("No input model uid. Error: {}", e), } @@ -42,7 +45,7 @@ fn test_get_device_model_uid() { } if let Some(device) = test_get_default_device(Scope::Output) { - match get_device_model_uid(device, DeviceType::OUTPUT) { + match run_serially(|| get_device_model_uid(device, DeviceType::OUTPUT)) { Ok(uid) => println!("output model uid: {}", uid.into_string()), Err(e) => println!("No output model uid. Error: {}", e), } @@ -54,7 +57,11 @@ fn test_get_device_model_uid() { #[test] #[should_panic] fn test_get_device_model_uid_by_unknown_device() { - assert!(get_device_model_uid(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_model_uid( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_transport_type @@ -62,7 +69,7 @@ fn test_get_device_model_uid_by_unknown_device() { #[test] fn test_get_device_transport_type() { if let Some(device) = test_get_default_device(Scope::Input) { - match get_device_transport_type(device, DeviceType::INPUT) { + match run_serially(|| get_device_transport_type(device, DeviceType::INPUT)) { Ok(trans_type) => println!( "input transport type: {:X}, {:?}", trans_type, @@ -75,7 +82,7 @@ fn test_get_device_transport_type() { } if let Some(device) = test_get_default_device(Scope::Output) { - match get_device_transport_type(device, DeviceType::OUTPUT) { + match run_serially(|| get_device_transport_type(device, DeviceType::OUTPUT)) { Ok(trans_type) => println!( "output transport type: {:X}, {:?}", trans_type, @@ -91,7 +98,11 @@ fn test_get_device_transport_type() { #[test] #[should_panic] fn test_get_device_transport_type_by_unknown_device() { - assert!(get_device_transport_type(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_transport_type( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_source @@ -100,7 +111,7 @@ fn test_get_device_transport_type_by_unknown_device() { #[test] fn test_get_device_source() { if let Some(device) = test_get_default_device(Scope::Input) { - match get_device_source(device, DeviceType::INPUT) { + match run_serially(|| get_device_source(device, DeviceType::INPUT)) { Ok(source) => println!( "input source: {:X}, {:?}", source, @@ -113,7 +124,7 @@ fn test_get_device_source() { } if let Some(device) = test_get_default_device(Scope::Output) { - match get_device_source(device, DeviceType::OUTPUT) { + match run_serially(|| get_device_source(device, DeviceType::OUTPUT)) { Ok(source) => println!( "output source: {:X}, {:?}", source, @@ -129,7 +140,11 @@ fn test_get_device_source() { #[test] #[should_panic] fn test_get_device_source_by_unknown_device() { - assert!(get_device_source(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_source( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_source_name @@ -137,7 +152,7 @@ fn test_get_device_source_by_unknown_device() { #[test] fn test_get_device_source_name() { if let Some(device) = test_get_default_device(Scope::Input) { - match get_device_source_name(device, DeviceType::INPUT) { + match run_serially(|| get_device_source_name(device, DeviceType::INPUT)) { Ok(name) => println!("input: {}", name.into_string()), Err(e) => println!("No input data source name. Error: {}", e), } @@ -146,7 +161,7 @@ fn test_get_device_source_name() { } if let Some(device) = test_get_default_device(Scope::Output) { - match get_device_source_name(device, DeviceType::OUTPUT) { + match run_serially(|| get_device_source_name(device, DeviceType::OUTPUT)) { Ok(name) => println!("output: {}", name.into_string()), Err(e) => println!("No output data source name. Error: {}", e), } @@ -158,7 +173,11 @@ fn test_get_device_source_name() { #[test] #[should_panic] fn test_get_device_source_name_by_unknown_device() { - assert!(get_device_source_name(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_source_name( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_name @@ -166,14 +185,14 @@ fn test_get_device_source_name_by_unknown_device() { #[test] fn test_get_device_name() { if let Some(device) = test_get_default_device(Scope::Input) { - let name = get_device_name(device, DeviceType::INPUT).unwrap(); + let name = run_serially(|| get_device_name(device, DeviceType::INPUT)).unwrap(); println!("input device name: {}", name.into_string()); } else { println!("No input device."); } if let Some(device) = test_get_default_device(Scope::Output) { - let name = get_device_name(device, DeviceType::OUTPUT).unwrap(); + let name = run_serially(|| get_device_name(device, DeviceType::OUTPUT).unwrap()); println!("output device name: {}", name.into_string()); } else { println!("No output device."); @@ -183,7 +202,11 @@ fn test_get_device_name() { #[test] #[should_panic] fn test_get_device_name_by_unknown_device() { - assert!(get_device_name(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_name( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_manufacturer @@ -193,7 +216,7 @@ fn test_get_device_manufacturer() { if let Some(device) = test_get_default_device(Scope::Input) { // Some devices like AirPods cannot get the vendor info so we print the error directly. // TODO: Replace `map` and `unwrap_or_else` by `map_or_else` - let name = get_device_manufacturer(device, DeviceType::INPUT) + let name = run_serially(|| get_device_manufacturer(device, DeviceType::INPUT)) .map(|name| name.into_string()) .unwrap_or_else(|e| format!("Error: {}", e)); println!("input device vendor: {}", name); @@ -204,9 +227,10 @@ fn test_get_device_manufacturer() { if let Some(device) = test_get_default_device(Scope::Output) { // Some devices like AirPods cannot get the vendor info so we print the error directly. // TODO: Replace `map` and `unwrap_or_else` by `map_or_else` - let name = get_device_manufacturer(device, DeviceType::OUTPUT) - .map(|name| name.into_string()) - .unwrap_or_else(|e| format!("Error: {}", e)); + let name = + run_serially_forward_panics(|| get_device_manufacturer(device, DeviceType::OUTPUT)) + .map(|name| name.into_string()) + .unwrap_or_else(|e| format!("Error: {}", e)); println!("output device vendor: {}", name); } else { println!("No output device."); @@ -216,7 +240,11 @@ fn test_get_device_manufacturer() { #[test] #[should_panic] fn test_get_device_manufacturer_by_unknown_device() { - assert!(get_device_manufacturer(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_manufacturer( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_buffer_frame_size_range @@ -224,7 +252,8 @@ fn test_get_device_manufacturer_by_unknown_device() { #[test] fn test_get_device_buffer_frame_size_range() { if let Some(device) = test_get_default_device(Scope::Input) { - let range = get_device_buffer_frame_size_range(device, DeviceType::INPUT).unwrap(); + let range = + run_serially(|| get_device_buffer_frame_size_range(device, DeviceType::INPUT)).unwrap(); println!( "range of input buffer frame size: {}-{}", range.mMinimum, range.mMaximum @@ -234,7 +263,8 @@ fn test_get_device_buffer_frame_size_range() { } if let Some(device) = test_get_default_device(Scope::Output) { - let range = get_device_buffer_frame_size_range(device, DeviceType::OUTPUT).unwrap(); + let range = run_serially(|| get_device_buffer_frame_size_range(device, DeviceType::OUTPUT)) + .unwrap(); println!( "range of output buffer frame size: {}-{}", range.mMinimum, range.mMaximum @@ -247,7 +277,13 @@ fn test_get_device_buffer_frame_size_range() { #[test] #[should_panic] fn test_get_device_buffer_frame_size_range_by_unknown_device() { - assert!(get_device_buffer_frame_size_range(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!( + run_serially_forward_panics(|| get_device_buffer_frame_size_range( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err() + ); } // get_device_latency @@ -255,14 +291,14 @@ fn test_get_device_buffer_frame_size_range_by_unknown_device() { #[test] fn test_get_device_latency() { if let Some(device) = test_get_default_device(Scope::Input) { - let latency = get_device_latency(device, DeviceType::INPUT).unwrap(); + let latency = run_serially(|| get_device_latency(device, DeviceType::INPUT)).unwrap(); println!("latency of input device: {}", latency); } else { println!("No input device."); } if let Some(device) = test_get_default_device(Scope::Output) { - let latency = get_device_latency(device, DeviceType::OUTPUT).unwrap(); + let latency = run_serially(|| get_device_latency(device, DeviceType::OUTPUT)).unwrap(); println!("latency of output device: {}", latency); } else { println!("No output device."); @@ -272,7 +308,11 @@ fn test_get_device_latency() { #[test] #[should_panic] fn test_get_device_latency_by_unknown_device() { - assert!(get_device_latency(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_latency( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_streams @@ -280,7 +320,7 @@ fn test_get_device_latency_by_unknown_device() { #[test] fn test_get_device_streams() { if let Some(device) = test_get_default_device(Scope::Input) { - let streams = get_device_streams(device, DeviceType::INPUT).unwrap(); + let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap(); println!("streams on the input device: {:?}", streams); assert!(!streams.is_empty()); } else { @@ -288,7 +328,7 @@ fn test_get_device_streams() { } if let Some(device) = test_get_default_device(Scope::Output) { - let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap(); + let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap(); println!("streams on the output device: {:?}", streams); assert!(!streams.is_empty()); } else { @@ -299,7 +339,11 @@ fn test_get_device_streams() { #[test] #[should_panic] fn test_get_device_streams_by_unknown_device() { - assert!(get_device_streams(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_streams( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_device_sample_rate @@ -307,14 +351,14 @@ fn test_get_device_streams_by_unknown_device() { #[test] fn test_get_device_sample_rate() { if let Some(device) = test_get_default_device(Scope::Input) { - let rate = get_device_sample_rate(device, DeviceType::INPUT).unwrap(); + let rate = run_serially(|| get_device_sample_rate(device, DeviceType::INPUT)).unwrap(); println!("input sample rate: {}", rate); } else { println!("No input device."); } if let Some(device) = test_get_default_device(Scope::Output) { - let rate = get_device_sample_rate(device, DeviceType::OUTPUT).unwrap(); + let rate = run_serially(|| get_device_sample_rate(device, DeviceType::OUTPUT).unwrap()); println!("output sample rate: {}", rate); } else { println!("No output device."); @@ -324,7 +368,11 @@ fn test_get_device_sample_rate() { #[test] #[should_panic] fn test_get_device_sample_rate_by_unknown_device() { - assert!(get_device_sample_rate(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!(run_serially_forward_panics(|| get_device_sample_rate( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err()); } // get_ranges_of_device_sample_rate @@ -332,14 +380,16 @@ fn test_get_device_sample_rate_by_unknown_device() { #[test] fn test_get_ranges_of_device_sample_rate() { if let Some(device) = test_get_default_device(Scope::Input) { - let ranges = get_ranges_of_device_sample_rate(device, DeviceType::INPUT).unwrap(); + let ranges = + run_serially(|| get_ranges_of_device_sample_rate(device, DeviceType::INPUT)).unwrap(); println!("ranges of input sample rate: {:?}", ranges); } else { println!("No input device."); } if let Some(device) = test_get_default_device(Scope::Output) { - let ranges = get_ranges_of_device_sample_rate(device, DeviceType::OUTPUT).unwrap(); + let ranges = + run_serially(|| get_ranges_of_device_sample_rate(device, DeviceType::OUTPUT)).unwrap(); println!("ranges of output sample rate: {:?}", ranges); } else { println!("No output device."); @@ -349,7 +399,13 @@ fn test_get_ranges_of_device_sample_rate() { #[test] #[should_panic] fn test_get_ranges_of_device_sample_rate_by_unknown_device() { - assert!(get_ranges_of_device_sample_rate(kAudioObjectUnknown, DeviceType::INPUT).is_err()); + assert!( + run_serially_forward_panics(|| get_ranges_of_device_sample_rate( + kAudioObjectUnknown, + DeviceType::INPUT + )) + .is_err() + ); } // get_stream_latency @@ -357,9 +413,9 @@ fn test_get_ranges_of_device_sample_rate_by_unknown_device() { #[test] fn test_get_stream_latency() { if let Some(device) = test_get_default_device(Scope::Input) { - let streams = get_device_streams(device, DeviceType::INPUT).unwrap(); + let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap(); for stream in streams { - let latency = get_stream_latency(stream).unwrap(); + let latency = run_serially(|| get_stream_latency(stream)).unwrap(); println!("latency of the input stream {} is {}", stream, latency); } } else { @@ -367,9 +423,9 @@ fn test_get_stream_latency() { } if let Some(device) = test_get_default_device(Scope::Output) { - let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap(); + let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap(); for stream in streams { - let latency = get_stream_latency(stream).unwrap(); + let latency = run_serially(|| get_stream_latency(stream)).unwrap(); println!("latency of the output stream {} is {}", stream, latency); } } else { @@ -388,10 +444,10 @@ fn test_get_stream_latency_by_unknown_device() { #[test] fn test_get_stream_virtual_format() { if let Some(device) = test_get_default_device(Scope::Input) { - let streams = get_device_streams(device, DeviceType::INPUT).unwrap(); + let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap(); let formats = streams .iter() - .map(|s| get_stream_virtual_format(*s)) + .map(|s| run_serially(|| get_stream_virtual_format(*s))) .collect::<Vec<std::result::Result<AudioStreamBasicDescription, OSStatus>>>(); println!("input stream formats: {:?}", formats); assert!(!formats.is_empty()); @@ -400,10 +456,10 @@ fn test_get_stream_virtual_format() { } if let Some(device) = test_get_default_device(Scope::Output) { - let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap(); + let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap(); let formats = streams .iter() - .map(|s| get_stream_virtual_format(*s)) + .map(|s| run_serially(|| get_stream_virtual_format(*s))) .collect::<Vec<std::result::Result<AudioStreamBasicDescription, OSStatus>>>(); println!("output stream formats: {:?}", formats); assert!(!formats.is_empty()); @@ -415,7 +471,9 @@ fn test_get_stream_virtual_format() { #[test] #[should_panic] fn test_get_stream_virtual_format_by_unknown_stream() { - assert!(get_stream_virtual_format(kAudioObjectUnknown).is_err()); + assert!( + run_serially_forward_panics(|| get_stream_virtual_format(kAudioObjectUnknown)).is_err() + ); } // get_stream_terminal_type @@ -442,25 +500,21 @@ fn test_get_stream_terminal_type() { } } if let Some(device) = test_get_default_device(Scope::Input) { - let streams = get_device_streams(device, DeviceType::INPUT).unwrap(); - for stream in streams { - assert_eq!( - terminal_type_to_device_type(get_stream_terminal_type(stream).unwrap()), - Some(DeviceType::INPUT) - ); - } + let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap(); + assert!(streams.iter().any(|&s| { + terminal_type_to_device_type(run_serially(|| get_stream_terminal_type(s)).unwrap()) + == Some(DeviceType::INPUT) + })); } else { println!("No input device."); } if let Some(device) = test_get_default_device(Scope::Output) { - let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap(); - for stream in streams { - assert_eq!( - terminal_type_to_device_type(get_stream_terminal_type(stream).unwrap()), - Some(DeviceType::OUTPUT) - ); - } + let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap(); + assert!(streams.iter().any(|&s| { + terminal_type_to_device_type(run_serially(|| get_stream_terminal_type(s)).unwrap()) + == Some(DeviceType::OUTPUT) + })); } else { println!("No output device."); } diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs index 340fec002d..aa15b7428b 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs @@ -2,10 +2,12 @@ extern crate itertools; use self::itertools::iproduct; use super::utils::{ - get_devices_info_in_scope, noop_data_callback, test_device_channels_in_scope, - test_get_default_device, test_ops_context_operation, test_ops_stream_operation, Scope, + draining_data_callback, get_devices_info_in_scope, noop_data_callback, + test_device_channels_in_scope, test_get_default_device, test_ops_context_operation, + test_ops_stream_operation, test_ops_stream_operation_on_context, Scope, }; use super::*; +use std::thread; // Context Operations // ------------------------------------------------------------------------------------------------ @@ -368,9 +370,6 @@ fn test_ops_context_register_device_collection_changed() { #[test] fn test_ops_context_register_device_collection_changed_with_a_duplex_stream() { - use std::thread; - use std::time::Duration; - extern "C" fn callback(_: *mut ffi::cubeb, got_called_ptr: *mut c_void) { let got_called = unsafe { &mut *(got_called_ptr as *mut bool) }; *got_called = true; @@ -667,14 +666,20 @@ fn test_ops_context_stream_init_channel_rate_combinations() { ffi::CUBEB_OK ); assert!(!stream.is_null()); + + unsafe { OPS.stream_destroy.unwrap()(stream) }; } }); } // Stream Operations // ------------------------------------------------------------------------------------------------ -fn test_default_output_stream_operation<F>(name: &'static str, operation: F) -where +fn test_default_output_stream_operation_on_context_with_callback<F>( + name: &'static str, + context_ptr: *mut ffi::cubeb, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where F: FnOnce(*mut ffi::cubeb_stream), { // Make sure the parameters meet the requirements of AudioUnitContext::stream_init @@ -686,24 +691,53 @@ where output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE; - test_ops_stream_operation( + test_ops_stream_operation_on_context( name, + context_ptr, ptr::null_mut(), // Use default input device. ptr::null_mut(), // No input parameters. ptr::null_mut(), // Use default output device. &mut output_params, 4096, // TODO: Get latency by get_min_latency instead ? - Some(noop_data_callback), + data_callback, None, // No state callback. ptr::null_mut(), // No user data pointer. operation, ); } -fn test_default_duplex_stream_operation<F>(name: &'static str, operation: F) +fn test_default_output_stream_operation_with_callback<F>( + name: &'static str, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_ops_context_operation("context: default output stream operation", |context_ptr| { + test_default_output_stream_operation_on_context_with_callback( + name, + context_ptr, + data_callback, + operation, + ); + }); +} + +fn test_default_output_stream_operation<F>(name: &'static str, operation: F) where F: FnOnce(*mut ffi::cubeb_stream), { + test_default_output_stream_operation_with_callback(name, Some(noop_data_callback), operation); +} + +fn test_default_duplex_stream_operation_on_context_with_callback<F>( + name: &'static str, + context_ptr: *mut ffi::cubeb, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ // Make sure the parameters meet the requirements of AudioUnitContext::stream_init // (in the comments). let mut input_params = ffi::cubeb_stream_params::default(); @@ -720,24 +754,53 @@ where output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE; - test_ops_stream_operation( + test_ops_stream_operation_on_context( name, + context_ptr, ptr::null_mut(), // Use default input device. &mut input_params, ptr::null_mut(), // Use default output device. &mut output_params, 4096, // TODO: Get latency by get_min_latency instead ? - Some(noop_data_callback), + data_callback, None, // No state callback. ptr::null_mut(), // No user data pointer. operation, ); } -fn test_stereo_input_duplex_stream_operation<F>(name: &'static str, operation: F) +fn test_default_duplex_stream_operation_with_callback<F>( + name: &'static str, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_ops_context_operation("context: default duplex stream operation", |context_ptr| { + test_default_duplex_stream_operation_on_context_with_callback( + name, + context_ptr, + data_callback, + operation, + ); + }); +} + +fn test_default_duplex_stream_operation<F>(name: &'static str, operation: F) where F: FnOnce(*mut ffi::cubeb_stream), { + test_default_duplex_stream_operation_with_callback(name, Some(noop_data_callback), operation); +} + +fn test_stereo_input_duplex_stream_operation_on_context_with_callback<F>( + name: &'static str, + context_ptr: *mut ffi::cubeb, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ let mut input_devices = get_devices_info_in_scope(Scope::Input); input_devices.retain(|d| test_device_channels_in_scope(d.id, Scope::Input).unwrap_or(0) >= 2); if input_devices.is_empty() { @@ -759,24 +822,138 @@ where output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE; - test_ops_stream_operation( + test_ops_stream_operation_on_context( name, + context_ptr, input_devices[0].id as ffi::cubeb_devid, &mut input_params, ptr::null_mut(), // Use default output device. &mut output_params, 4096, // TODO: Get latency by get_min_latency instead ? + data_callback, + None, // No state callback. + ptr::null_mut(), // No user data pointer. + operation, + ); +} + +fn test_stereo_input_duplex_stream_operation_with_callback<F>( + name: &'static str, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_ops_context_operation( + "context: stereo input duplex stream operation", + |context_ptr| { + test_stereo_input_duplex_stream_operation_on_context_with_callback( + name, + context_ptr, + data_callback, + operation, + ); + }, + ); +} + +fn test_stereo_input_duplex_stream_operation<F>(name: &'static str, operation: F) +where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_stereo_input_duplex_stream_operation_with_callback( + name, Some(noop_data_callback), + operation, + ); +} + +fn test_default_input_voice_stream_operation_on_context_with_callback<F>( + name: &'static str, + context_ptr: *mut ffi::cubeb, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + // Make sure the parameters meet the requirements of AudioUnitContext::stream_init + // (in the comments). + let mut input_params = ffi::cubeb_stream_params::default(); + input_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE; + input_params.rate = 44100; + input_params.channels = 1; + input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; + input_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE; + + test_ops_stream_operation_on_context( + name, + context_ptr, + ptr::null_mut(), // Use default input device. + &mut input_params, + ptr::null_mut(), // Use default output device. + ptr::null_mut(), // No output parameters. + 4096, // TODO: Get latency by get_min_latency instead ? + data_callback, None, // No state callback. ptr::null_mut(), // No user data pointer. operation, ); } -fn test_default_duplex_voice_stream_operation<F>(name: &'static str, operation: F) +fn test_default_input_voice_stream_operation_on_context<F>( + name: &'static str, + context_ptr: *mut ffi::cubeb, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_default_input_voice_stream_operation_on_context_with_callback( + name, + context_ptr, + Some(noop_data_callback), + operation, + ); +} + +fn test_default_input_voice_stream_operation_with_callback<F>( + name: &'static str, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_ops_context_operation( + "context: default input voice stream operation", + |context_ptr| { + test_default_input_voice_stream_operation_on_context_with_callback( + name, + context_ptr, + data_callback, + operation, + ); + }, + ); +} + +fn test_default_input_voice_stream_operation<F>(name: &'static str, operation: F) where F: FnOnce(*mut ffi::cubeb_stream), { + test_default_input_voice_stream_operation_with_callback( + name, + Some(noop_data_callback), + operation, + ); +} + +fn test_default_duplex_voice_stream_operation_on_context_with_callback<F>( + name: &'static str, + context_ptr: *mut ffi::cubeb, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ // Make sure the parameters meet the requirements of AudioUnitContext::stream_init // (in the comments). let mut input_params = ffi::cubeb_stream_params::default(); @@ -793,20 +970,64 @@ where output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED; output_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE; - test_ops_stream_operation( + test_ops_stream_operation_on_context( name, + context_ptr, ptr::null_mut(), // Use default input device. &mut input_params, ptr::null_mut(), // Use default output device. &mut output_params, 4096, // TODO: Get latency by get_min_latency instead ? - Some(noop_data_callback), + data_callback, None, // No state callback. ptr::null_mut(), // No user data pointer. operation, ); } +fn test_default_duplex_voice_stream_operation_on_context<F>( + name: &'static str, + context_ptr: *mut ffi::cubeb, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_default_duplex_voice_stream_operation_on_context_with_callback( + name, + context_ptr, + Some(noop_data_callback), + operation, + ); +} + +fn test_default_duplex_voice_stream_operation_with_callback<F>( + name: &'static str, + data_callback: ffi::cubeb_data_callback, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_ops_context_operation("context: duplex voice stream operation", |context_ptr| { + test_default_duplex_voice_stream_operation_on_context_with_callback( + name, + context_ptr, + data_callback, + operation, + ); + }); +} + +fn test_default_duplex_voice_stream_operation<F>(name: &'static str, operation: F) +where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_default_duplex_voice_stream_operation_with_callback( + name, + Some(noop_data_callback), + operation, + ); +} + fn test_stereo_input_duplex_voice_stream_operation<F>(name: &'static str, operation: F) where F: FnOnce(*mut ffi::cubeb_stream), @@ -866,6 +1087,18 @@ fn test_ops_stream_stop() { } #[test] +fn test_ops_stream_drain() { + test_default_output_stream_operation_with_callback( + "stream: drain", + Some(draining_data_callback), + |stream| { + assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + thread::sleep(Duration::from_millis(10)); + }, + ); +} + +#[test] fn test_ops_stream_position() { test_default_output_stream_operation("stream: position", |stream| { let mut position = u64::max_value(); @@ -987,17 +1220,72 @@ fn test_ops_stereo_input_duplex_stream_stop() { } #[test] -fn test_ops_duplex_voice_stream_init_and_destroy() { - test_default_duplex_voice_stream_operation( - "duplex voice stream: init and destroy", - |_stream| {}, +fn test_ops_stereo_input_duplex_stream_drain() { + test_stereo_input_duplex_stream_operation_with_callback( + "stereo-input duplex stream: drain", + Some(draining_data_callback), + |stream| { + assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + thread::sleep(Duration::from_millis(10)); + }, ); } #[test] +fn test_ops_input_voice_stream_init_and_destroy() { + test_default_input_voice_stream_operation("input voice stream: init and destroy", |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); + }); +} + +#[test] +fn test_ops_input_voice_stream_start() { + test_default_input_voice_stream_operation("input voice stream: start", |stream| { + assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); + }); +} + +#[test] +fn test_ops_input_voice_stream_stop() { + test_default_input_voice_stream_operation("input voice stream: stop", |stream| { + assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); + }); +} + +#[test] +fn test_ops_duplex_voice_stream_init_and_destroy() { + test_default_duplex_voice_stream_operation("duplex voice stream: init and destroy", |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); + }); +} + +#[test] fn test_ops_duplex_voice_stream_start() { test_default_duplex_voice_stream_operation("duplex voice stream: start", |stream| { assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); }); } @@ -1005,21 +1293,334 @@ fn test_ops_duplex_voice_stream_start() { fn test_ops_duplex_voice_stream_stop() { test_default_duplex_voice_stream_operation("duplex voice stream: stop", |stream| { assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); + }); +} + +#[test] +fn test_ops_duplex_voice_stream_drain() { + test_default_duplex_voice_stream_operation_with_callback( + "duplex voice stream: drain", + Some(draining_data_callback), + |stream| { + assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); + thread::sleep(Duration::from_millis(10)); + }, + ); +} + +#[test] +#[ignore] +fn test_ops_timing_sensitive_multiple_voice_stream_init_and_destroy() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } + let start = Instant::now(); + let mut t1 = start; + let mut t2 = start; + let mut t3 = start; + let mut t4 = start; + let mut t5 = start; + let mut t6 = start; + let mut t7 = start; + let mut t8 = start; + let mut t9 = start; + let mut t10 = start; + test_ops_context_operation("multiple duplex voice streams", |context_ptr| { + // First stream uses vpio, creates the shared vpio unit. + test_default_duplex_voice_stream_operation_on_context( + "multiple voice streams: stream 1, duplex", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + + // Two concurrent vpio streams are supported. + test_default_input_voice_stream_operation_on_context( + "multiple voice streams: stream 2, input-only", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + + // Three concurrent vpio streams are supported. + test_default_duplex_voice_stream_operation_on_context( + "multiple voice streams: stream 3, duplex", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + }, + ); + }, + ); + }, + ); + t1 = Instant::now(); + // Fourth stream uses vpio, allows reuse of one already created. + test_default_duplex_voice_stream_operation_on_context( + "multiple voice streams: stream 4, duplex", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + t2 = Instant::now(); + + // Fifth stream uses vpio, allows reuse of one already created. + test_default_duplex_voice_stream_operation_on_context( + "multiple voice streams: stream 5, duplex", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + t3 = Instant::now(); + + // Sixth stream uses vpio, allows reuse of one already created. + test_default_input_voice_stream_operation_on_context( + "multiple voice streams: stream 6, input-only", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + t4 = Instant::now(); + + // Seventh stream uses vpio, but is created anew. + test_default_input_voice_stream_operation_on_context( + "multiple voice streams: stream 7, input-only", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + t5 = Instant::now(); + }, + ); + t6 = Instant::now(); + }, + ); + t7 = Instant::now(); + }, + ); + t8 = Instant::now(); + }, + ); + t9 = Instant::now(); + }); + t10 = Instant::now(); + + let reuse_vpio_1 = t2 - t1; + let reuse_vpio_2 = t3 - t2; + let reuse_vpio_3 = t4 - t3; + let create_standalone_vpio = t5 - t4; + assert!( + create_standalone_vpio > reuse_vpio_1 * 2, + "Failed create_standalone_vpio={}s > reuse_vpio_1={}s * 2", + create_standalone_vpio.as_secs_f32(), + reuse_vpio_1.as_secs_f32() + ); + assert!( + create_standalone_vpio > reuse_vpio_2 * 2, + "Failed create_standalone_vpio={}s > reuse_vpio_2={}s * 2", + create_standalone_vpio.as_secs_f32(), + reuse_vpio_2.as_secs_f32() + ); + assert!( + create_standalone_vpio > reuse_vpio_3 * 2, + "Failed create_standalone_vpio={}s > reuse_vpio_3={}s * 2", + create_standalone_vpio.as_secs_f32(), + reuse_vpio_3.as_secs_f32() + ); + + let recycle_vpio_1 = t6 - t5; + let recycle_vpio_2 = t7 - t6; + let recycle_vpio_3 = t8 - t7; + let recycle_vpio_4 = t9 - t8; + let dispose_vpios = t10 - t9; + assert!( + dispose_vpios > recycle_vpio_1 * 2, + "Failed dispose_vpios={}s > recycle_vpio_1 ={}s * 2", + dispose_vpios.as_secs_f32(), + recycle_vpio_1.as_secs_f32() + ); + assert!( + dispose_vpios > recycle_vpio_2 * 2, + "Failed dispose_vpios={}s > recycle_vpio_2 ={}s * 2", + dispose_vpios.as_secs_f32(), + recycle_vpio_2.as_secs_f32() + ); + assert!( + dispose_vpios > recycle_vpio_3 * 2, + "Failed dispose_vpios={}s > recycle_vpio_3 ={}s * 2", + dispose_vpios.as_secs_f32(), + recycle_vpio_3.as_secs_f32() + ); + assert!( + dispose_vpios > recycle_vpio_4 * 2, + "Failed dispose_vpios={}s > recycle_vpio_4 ={}s * 2", + dispose_vpios.as_secs_f32(), + recycle_vpio_4.as_secs_f32() + ); +} + +#[test] +#[ignore] +fn test_ops_timing_sensitive_multiple_duplex_voice_stream_start() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } + test_ops_context_operation("multiple duplex voice streams", |context_ptr| { + let start = Instant::now(); + // First stream uses vpio, creates the shared vpio unit. + test_default_duplex_voice_stream_operation_on_context( + "multiple duplex voice streams: stream 1", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + }, + ); + let d1 = start.elapsed(); + // Second stream uses vpio, allows reuse of the one already created. + test_default_duplex_voice_stream_operation_on_context( + "multiple duplex voice streams: stream 2", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + }, + ); + let d2 = start.elapsed() - d1; + // d1 being significantly longer than d2 is proof we reuse vpio. + assert!( + d1 > d2 * 2, + "Failed d1={}s > d2={}s * s", + d1.as_secs_f32(), + d2.as_secs_f32() + ); + }); +} + +#[test] +#[ignore] +fn test_ops_timing_sensitive_multiple_duplex_voice_stream_params() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } + test_ops_context_operation("multiple duplex voice streams with params", |context_ptr| { + let start = Instant::now(); + // First stream uses vpio, creates the shared vpio unit. + test_default_duplex_voice_stream_operation_on_context( + "multiple duplex voice streams: stream 1", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + assert_eq!( + unsafe { + OPS.stream_set_input_processing_params.unwrap()( + stream, + ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION + | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION, + ) + }, + ffi::CUBEB_OK + ); + assert_eq!( + unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) }, + ffi::CUBEB_OK + ); + }, + ); + let d1 = start.elapsed(); + // Second stream uses vpio, allows reuse of the one already created. + test_default_duplex_voice_stream_operation_on_context( + "multiple duplex voice streams: stream 2", + context_ptr, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); + let queue = stm.queue.clone(); + // Test that input processing params does not carry over when reusing vpio. + let mut bypass: u32 = 0; + let r = queue + .run_sync(|| { + audio_unit_get_property( + stm.core_stream_data.input_unit, + kAUVoiceIOProperty_BypassVoiceProcessing, + kAudioUnitScope_Global, + AU_IN_BUS, + &mut bypass, + &mut mem::size_of::<u32>(), + ) + }) + .unwrap(); + assert_eq!(r, NO_ERR); + assert_eq!(bypass, 1); + + // Test that input mute state does not carry over when reusing vpio. + let mut mute: u32 = 0; + let r = queue + .run_sync(|| { + audio_unit_get_property( + stm.core_stream_data.input_unit, + kAUVoiceIOProperty_MuteOutput, + kAudioUnitScope_Global, + AU_IN_BUS, + &mut mute, + &mut mem::size_of::<u32>(), + ) + }) + .unwrap(); + assert_eq!(r, NO_ERR); + assert_eq!(mute, 0); + }, + ); + let d2 = start.elapsed() - d1; + // d1 being significantly longer than d2 is proof we reuse vpio. + assert!( + d1 > d2 * 2, + "Failed d1={}s > d2={}s * 2", + d1.as_secs_f32(), + d2.as_secs_f32() + ); }); } #[test] fn test_ops_duplex_voice_stream_set_input_mute() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } test_default_duplex_voice_stream_operation("duplex voice stream: mute", |stream| { assert_eq!( unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) }, ffi::CUBEB_OK ); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); }); } #[test] fn test_ops_duplex_voice_stream_set_input_mute_before_start() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } test_default_duplex_voice_stream_operation( "duplex voice stream: mute before start", |stream| { @@ -1028,12 +1629,18 @@ fn test_ops_duplex_voice_stream_set_input_mute_before_start() { ffi::CUBEB_OK ); assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); }, ); } #[test] fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } test_default_duplex_voice_stream_operation( "duplex voice stream: mute before start with reinit", |stream| { @@ -1045,6 +1652,7 @@ fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() { // Hacky cast, but testing this here was simplest for now. let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); stm.reinit_async(); let queue = stm.queue.clone(); let mut mute_after_reinit = false; @@ -1068,17 +1676,27 @@ fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() { #[test] fn test_ops_duplex_voice_stream_set_input_mute_after_start() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } test_default_duplex_voice_stream_operation("duplex voice stream: mute after start", |stream| { assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); assert_eq!( unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) }, ffi::CUBEB_OK ); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); }); } #[test] fn test_ops_duplex_voice_stream_set_input_processing_params() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } test_default_duplex_voice_stream_operation("duplex voice stream: processing", |stream| { let params: ffi::cubeb_input_processing_params = ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION @@ -1088,11 +1706,17 @@ fn test_ops_duplex_voice_stream_set_input_processing_params() { unsafe { OPS.stream_set_input_processing_params.unwrap()(stream, params) }, ffi::CUBEB_OK ); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); }); } #[test] fn test_ops_duplex_voice_stream_set_input_processing_params_before_start() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } test_default_duplex_voice_stream_operation( "duplex voice stream: processing before start", |stream| { @@ -1105,12 +1729,18 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_before_start() { ffi::CUBEB_OK ); assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); }, ); } #[test] fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_reinit() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } test_default_duplex_voice_stream_operation( "duplex voice stream: processing before start with reinit", |stream| { @@ -1126,6 +1756,7 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_re // Hacky cast, but testing this here was simplest for now. let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); stm.reinit_async(); let queue = stm.queue.clone(); let mut params_after_reinit: ffi::cubeb_input_processing_params = @@ -1170,9 +1801,15 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_re #[test] fn test_ops_duplex_voice_stream_set_input_processing_params_after_start() { + if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY { + // We disable VPIO on Monterey. + return; + } test_default_duplex_voice_stream_operation( "duplex voice stream: processing after start", |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert!(stm.core_stream_data.using_voice_processing_unit()); assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); let params: ffi::cubeb_input_processing_params = ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION @@ -1190,7 +1827,13 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_after_start() { fn test_ops_stereo_input_duplex_voice_stream_init_and_destroy() { test_stereo_input_duplex_voice_stream_operation( "stereo-input duplex voice stream: init and destroy", - |_stream| {}, + |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); + }, ); } @@ -1199,6 +1842,11 @@ fn test_ops_stereo_input_duplex_voice_stream_start() { test_stereo_input_duplex_voice_stream_operation( "stereo-input duplex voice stream: start", |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); }, ); @@ -1209,6 +1857,11 @@ fn test_ops_stereo_input_duplex_voice_stream_stop() { test_stereo_input_duplex_voice_stream_operation( "stereo-input duplex voice stream: stop", |stream| { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + assert_eq!( + stm.core_stream_data.using_voice_processing_unit(), + macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY + ); assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK); }, ); diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs index b2b2241cc9..3bf96c65c6 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs @@ -4,7 +4,6 @@ use super::utils::{ }; use super::*; use std::io; -use std::sync::atomic::AtomicBool; #[ignore] #[test] @@ -153,25 +152,84 @@ fn test_device_collection_change() { let _ = std::io::stdin().read_line(&mut input); } +struct StreamData { + stream_ptr: *mut ffi::cubeb_stream, + enable_loopback: AtomicBool, +} + +impl StreamData { + fn new() -> Self { + Self { + stream_ptr: ptr::null_mut(), + enable_loopback: AtomicBool::new(false), + } + } +} + +struct StreamsData { + streams: Vec<StreamData>, + current_idx: Option<usize>, +} + +impl StreamsData { + fn new() -> Self { + Self { + streams: Vec::new(), + current_idx: None, + } + } + + fn len(&self) -> usize { + self.streams.len() + } + + fn current_mut(&mut self) -> &mut StreamData { + &mut self.streams[self.current_idx.unwrap()] + } + + fn current(&self) -> &StreamData { + &self.streams[self.current_idx.unwrap()] + } + + fn select(&mut self, idx: usize) { + assert!(idx < self.len()); + self.current_idx = Some(idx); + } + + fn push(&mut self, stream: StreamData) { + self.streams.push(stream) + } +} + #[ignore] #[test] fn test_stream_tester() { test_ops_context_operation("context: stream tester", |context_ptr| { - let mut stream_ptr: *mut ffi::cubeb_stream = ptr::null_mut(); - let enable_loopback = AtomicBool::new(false); + let mut input_prefs = StreamPrefs::NONE; + let mut output_prefs = StreamPrefs::NONE; + let mut streams = StreamsData::new(); loop { println!( - "commands:\n\ + "Current stream: {} (of {}). Commands:\n\ \t'q': quit\n\ + \t'b': change current stream\n\ + \t'i': set input stream prefs to be used for creating streams\n\ + \t'o': set output stream prefs to be used for creating streams\n\ \t'c': create a stream\n\ - \t'd': destroy a stream\n\ - \t's': start the created stream\n\ - \t't': stop the created stream\n\ + Commands on the current stream:\n\ + \t'd': destroy\n\ + \t's': start\n\ + \t't': stop\n\ \t'r': register a device changed callback\n\ \t'l': set loopback (DUPLEX-only)\n\ \t'v': set volume\n\ \t'm': set input mute\n\ - \t'p': set input processing" + \t'p': set input processing", + streams + .current_idx + .map(|i| format!("{}", i + 1 as usize)) + .unwrap_or(String::from("N/A")), + streams.len(), ); let mut command = String::new(); @@ -181,66 +239,130 @@ fn test_stream_tester() { match command.as_str() { "q" => { println!("Quit."); - destroy_stream(&mut stream_ptr); + for mut stream in streams.streams { + if !stream.stream_ptr.is_null() { + destroy_stream(&mut stream); + } + } break; } - "c" => create_stream(&mut stream_ptr, context_ptr, &enable_loopback), - "d" => destroy_stream(&mut stream_ptr), - "s" => start_stream(stream_ptr), - "t" => stop_stream(stream_ptr), - "r" => register_device_change_callback(stream_ptr), - "l" => set_loopback(stream_ptr, &enable_loopback), - "v" => set_volume(stream_ptr), - "m" => set_input_mute(stream_ptr), - "p" => set_input_processing(stream_ptr), - x => println!("Unknown command: {}", x), + "i" => set_prefs(&mut input_prefs), + "o" => set_prefs(&mut output_prefs), + "c" => create_stream(context_ptr, &mut streams, input_prefs, output_prefs), + _ if streams.current_idx.is_none() => { + println!("There are no streams! Create a stream first.") + } + cmd => match cmd { + "b" => select_stream(&mut streams), + "d" => destroy_stream(streams.current_mut()), + "s" => start_stream(streams.current()), + "t" => stop_stream(streams.current()), + "r" => register_device_change_callback(streams.current()), + "l" => set_loopback(streams.current()), + "v" => set_volume(streams.current()), + "m" => set_input_mute(streams.current()), + "p" => set_input_processing(streams.current()), + x => println!("Unknown command: {}", x), + }, } } }); - fn start_stream(stream_ptr: *mut ffi::cubeb_stream) { - if stream_ptr.is_null() { + fn set_prefs(prefs: &mut StreamPrefs) { + let mut done = false; + while !done { + println!( + "Current prefs: {:?}\nSelect action:\n\ + \t1) Set None\n\ + \t2) Toggle Loopback\n\ + \t3) Toggle Disable Device Switching\n\ + \t4) Toggle Voice\n\ + \t5) Set All\n\ + \t0) Done", + prefs + ); + let mut input = String::new(); + let _ = io::stdin().read_line(&mut input); + assert_eq!(input.pop().unwrap(), '\n'); + match input.as_str() { + "1" => *prefs = StreamPrefs::NONE, + "2" => prefs.toggle(StreamPrefs::LOOPBACK), + "3" => prefs.toggle(StreamPrefs::DISABLE_DEVICE_SWITCHING), + "4" => prefs.toggle(StreamPrefs::VOICE), + "5" => *prefs = StreamPrefs::all(), + "0" => done = true, + _ => println!("Invalid action. Select again.\n"), + } + } + } + + fn select_stream(streams: &mut StreamsData) { + let num_streams = streams.len(); + let current_idx = streams.current_idx.unwrap(); + println!( + "Current stream is {}. Select stream 1 to {} on which to apply commands:", + current_idx + 1 as usize, + num_streams + ); + let mut selection: Option<usize> = None; + while selection.is_none() { + let mut input = String::new(); + let _ = io::stdin().read_line(&mut input); + assert_eq!(input.pop().unwrap(), '\n'); + selection = match input.parse::<usize>() { + Ok(i) if (1..=num_streams).contains((&i).into()) => Some(i), + _ => { + println!("Invalid stream. Select again.\n"); + None + } + } + } + streams.select(selection.unwrap() - 1) + } + + fn start_stream(stream: &StreamData) { + if stream.stream_ptr.is_null() { println!("No stream can start."); return; } assert_eq!( - unsafe { OPS.stream_start.unwrap()(stream_ptr) }, + unsafe { OPS.stream_start.unwrap()(stream.stream_ptr) }, ffi::CUBEB_OK ); - println!("Stream {:p} started.", stream_ptr); + println!("Stream {:p} started.", stream.stream_ptr); } - fn stop_stream(stream_ptr: *mut ffi::cubeb_stream) { - if stream_ptr.is_null() { + fn stop_stream(stream: &StreamData) { + if stream.stream_ptr.is_null() { println!("No stream can stop."); return; } assert_eq!( - unsafe { OPS.stream_stop.unwrap()(stream_ptr) }, + unsafe { OPS.stream_stop.unwrap()(stream.stream_ptr) }, ffi::CUBEB_OK ); - println!("Stream {:p} stopped.", stream_ptr); + println!("Stream {:p} stopped.", stream.stream_ptr); } - fn set_volume(stream_ptr: *mut ffi::cubeb_stream) { - if stream_ptr.is_null() { + fn set_volume(stream: &StreamData) { + if stream.stream_ptr.is_null() { println!("No stream can set volume."); return; } const VOL: f32 = 0.5; assert_eq!( - unsafe { OPS.stream_set_volume.unwrap()(stream_ptr, VOL) }, + unsafe { OPS.stream_set_volume.unwrap()(stream.stream_ptr, VOL) }, ffi::CUBEB_OK ); - println!("Set stream {:p} volume to {}", stream_ptr, VOL); + println!("Set stream {:p} volume to {}", stream.stream_ptr, VOL); } - fn set_loopback(stream_ptr: *mut ffi::cubeb_stream, enable_loopback: &AtomicBool) { - if stream_ptr.is_null() { + fn set_loopback(stream: &StreamData) { + if stream.stream_ptr.is_null() { println!("No stream can set loopback."); return; } - let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) }; + let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) }; if !stm.core_stream_data.has_input() || !stm.core_stream_data.has_output() { println!("Duplex stream needed to set loopback"); return; @@ -261,20 +383,20 @@ fn test_stream_tester() { } } let loopback = loopback.unwrap(); - enable_loopback.store(loopback, Ordering::SeqCst); + stream.enable_loopback.store(loopback, Ordering::SeqCst); println!( "Loopback {} for stream {:p}", if loopback { "enabled" } else { "disabled" }, - stream_ptr + stream.stream_ptr ); } - fn set_input_mute(stream_ptr: *mut ffi::cubeb_stream) { - if stream_ptr.is_null() { + fn set_input_mute(stream: &StreamData) { + if stream.stream_ptr.is_null() { println!("No stream can set input mute."); return; } - let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) }; + let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) }; if !stm.core_stream_data.has_input() { println!("Input stream needed to set loopback"); return; @@ -295,7 +417,7 @@ fn test_stream_tester() { } } let mute = mute.unwrap(); - let res = unsafe { OPS.stream_set_input_mute.unwrap()(stream_ptr, mute.into()) }; + let res = unsafe { OPS.stream_set_input_mute.unwrap()(stream.stream_ptr, mute.into()) }; println!( "{} set stream {:p} input {}", if res == ffi::CUBEB_OK { @@ -303,17 +425,17 @@ fn test_stream_tester() { } else { "Failed to" }, - stream_ptr, + stream.stream_ptr, if mute { "mute" } else { "unmute" } ); } - fn set_input_processing(stream_ptr: *mut ffi::cubeb_stream) { - if stream_ptr.is_null() { + fn set_input_processing(stream: &StreamData) { + if stream.stream_ptr.is_null() { println!("No stream can set input processing."); return; } - let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) }; + let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) }; if !stm.core_stream_data.using_voice_processing_unit() { println!("Duplex stream with voice processing needed to set input processing params"); return; @@ -338,6 +460,23 @@ fn test_stream_tester() { params.set(InputProcessingParams::ECHO_CANCELLATION, true); params.set(InputProcessingParams::NOISE_SUPPRESSION, true); } + let mut agc = u32::from(false); + let mut size: usize = mem::size_of::<u32>(); + assert_eq!( + audio_unit_get_property( + stm.core_stream_data.input_unit, + kAUVoiceIOProperty_VoiceProcessingEnableAGC, + kAudioUnitScope_Global, + AU_IN_BUS, + &mut agc, + &mut size, + ), + NO_ERR + ); + assert_eq!(size, mem::size_of::<u32>()); + if agc == 1 { + params.set(InputProcessingParams::AUTOMATIC_GAIN_CONTROL, true); + } } let mut done = false; while !done { @@ -367,8 +506,9 @@ fn test_stream_tester() { _ => println!("Invalid action. Select again.\n"), } } - let res = - unsafe { OPS.stream_set_input_processing_params.unwrap()(stream_ptr, params.bits()) }; + let res = unsafe { + OPS.stream_set_input_processing_params.unwrap()(stream.stream_ptr, params.bits()) + }; println!( "{} set stream {:p} input processing params to {:?}", if res == ffi::CUBEB_OK { @@ -376,48 +516,62 @@ fn test_stream_tester() { } else { "Failed to" }, - stream_ptr, + stream.stream_ptr, params, ); } - fn register_device_change_callback(stream_ptr: *mut ffi::cubeb_stream) { + fn register_device_change_callback(stream: &StreamData) { extern "C" fn callback(user_ptr: *mut c_void) { println!("user pointer @ {:p}", user_ptr); assert!(user_ptr.is_null()); } - if stream_ptr.is_null() { + if stream.stream_ptr.is_null() { println!("No stream for registering the callback."); return; } assert_eq!( unsafe { - OPS.stream_register_device_changed_callback.unwrap()(stream_ptr, Some(callback)) + OPS.stream_register_device_changed_callback.unwrap()( + stream.stream_ptr, + Some(callback), + ) }, ffi::CUBEB_OK ); - println!("Stream {:p} now has a device change callback.", stream_ptr); + println!( + "Stream {:p} now has a device change callback.", + stream.stream_ptr + ); } - fn destroy_stream(stream_ptr: &mut *mut ffi::cubeb_stream) { - if stream_ptr.is_null() { + fn destroy_stream(stream: &mut StreamData) { + if stream.stream_ptr.is_null() { println!("No need to destroy stream."); return; } unsafe { - OPS.stream_destroy.unwrap()(*stream_ptr); + OPS.stream_destroy.unwrap()((*stream).stream_ptr); } - println!("Stream {:p} destroyed.", *stream_ptr); - *stream_ptr = ptr::null_mut(); + println!("Stream {:p} destroyed.", stream.stream_ptr); + stream.stream_ptr = ptr::null_mut(); } fn create_stream( - stream_ptr: &mut *mut ffi::cubeb_stream, context_ptr: *mut ffi::cubeb, - enable_loopback: &AtomicBool, + streams: &mut StreamsData, + input_prefs: StreamPrefs, + output_prefs: StreamPrefs, ) { - if !stream_ptr.is_null() { + if streams.len() == 0 || !streams.current().stream_ptr.is_null() { + println!("Allocating stream {}.", streams.len() + 1); + streams.push(StreamData::new()); + streams.select(streams.len() - 1); + } + + let stream = streams.current_mut(); + if !stream.stream_ptr.is_null() { println!("Stream has been created."); return; } @@ -486,8 +640,8 @@ fn test_stream_tester() { } }; - let mut input_params = get_dummy_stream_params(Scope::Input); - let mut output_params = get_dummy_stream_params(Scope::Output); + let mut input_params = get_dummy_stream_params(Scope::Input, input_prefs); + let mut output_params = get_dummy_stream_params(Scope::Output, output_prefs); let (input_device, input_stream_params) = if stream_type.contains(StreamType::INPUT) { ( @@ -519,7 +673,7 @@ fn test_stream_tester() { unsafe { OPS.stream_init.unwrap()( context_ptr, - stream_ptr, + &mut stream.stream_ptr, stream_name.as_ptr(), input_device as ffi::cubeb_devid, input_stream_params, @@ -528,13 +682,13 @@ fn test_stream_tester() { 4096, // latency Some(data_callback), Some(state_callback), - enable_loopback as *const AtomicBool as *mut c_void, // user pointer + &stream.enable_loopback as *const AtomicBool as *mut c_void, // user pointer ) }, ffi::CUBEB_OK ); - assert!(!stream_ptr.is_null()); - println!("Stream {:p} created.", *stream_ptr); + assert!(!stream.stream_ptr.is_null()); + println!("Stream {:p} created.", stream.stream_ptr); extern "C" fn state_callback( stream: *mut ffi::cubeb_stream, @@ -592,14 +746,14 @@ fn test_stream_tester() { nframes } - fn get_dummy_stream_params(scope: Scope) -> ffi::cubeb_stream_params { + fn get_dummy_stream_params(scope: Scope, prefs: StreamPrefs) -> ffi::cubeb_stream_params { // The stream format for input and output must be same. const STREAM_FORMAT: u32 = ffi::CUBEB_SAMPLE_FLOAT32NE; // Make sure the parameters meet the requirements of AudioUnitContext::stream_init // (in the comments). let mut stream_params = ffi::cubeb_stream_params::default(); - stream_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE; + stream_params.prefs = prefs.bits(); let (format, rate, channels, layout) = match scope { Scope::Input => (STREAM_FORMAT, 48000, 1, ffi::CUBEB_LAYOUT_MONO), Scope::Output => (STREAM_FORMAT, 44100, 2, ffi::CUBEB_LAYOUT_STEREO), diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs index 16063d0011..be5edb1919 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs @@ -537,14 +537,16 @@ fn test_set_buffer_frame_size_in_parallel_in_scope(scope: Scope) { units.push(test_get_default_audiounit(scope.clone()).unwrap()); let unit_value = units.last().unwrap().get_inner() as usize; join_handles.push(thread::spawn(move || { - let status = audio_unit_set_property( - unit_value as AudioUnit, - kAudioDevicePropertyBufferFrameSize, - unit_scope, - unit_element, - &latency_frames, - mem::size_of::<u32>(), - ); + let status = run_serially(|| { + audio_unit_set_property( + unit_value as AudioUnit, + kAudioDevicePropertyBufferFrameSize, + unit_scope, + unit_element, + &latency_frames, + mem::size_of::<u32>(), + ) + }); (latency_frames, status) })); } diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs index 42cb9ee997..5f54286153 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs @@ -1,6 +1,6 @@ use super::utils::{test_get_default_device, test_ops_stream_operation, Scope}; use super::*; -use std::sync::atomic::{AtomicI64, Ordering}; +use std::sync::atomic::AtomicI64; #[test] fn test_dial_tone() { @@ -202,7 +202,7 @@ fn test_dial_tone() { for data in buffer.iter_mut() { let t1 = (2.0 * PI * 350.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin(); let t2 = (2.0 * PI * 440.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin(); - *data = f32_to_i16_sample(0.5 * (t1 + t2)); + *data = f32_to_i16_sample(0.45 * (t1 + t2)); closure.phase += 1; } diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs index ef07aeeeb4..6fb6d38fb3 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs @@ -25,6 +25,29 @@ pub extern "C" fn noop_data_callback( nframes } +pub extern "C" fn draining_data_callback( + stream: *mut ffi::cubeb_stream, + _user_ptr: *mut c_void, + _input_buffer: *const c_void, + output_buffer: *mut c_void, + nframes: i64, +) -> i64 { + assert!(!stream.is_null()); + + // Feed silence data to output buffer + if !output_buffer.is_null() { + let stm = unsafe { &mut *(stream as *mut AudioUnitStream) }; + let channels = stm.core_stream_data.output_stream_params.channels(); + let samples = nframes as usize * channels as usize; + let sample_size = cubeb_sample_size(stm.core_stream_data.output_stream_params.format()); + unsafe { + ptr::write_bytes(output_buffer, 0, samples * sample_size); + } + } + + nframes - 1 +} + #[derive(Clone, Debug, PartialEq)] pub enum Scope { Input, @@ -47,31 +70,35 @@ pub enum PropertyScope { } pub fn test_get_default_device(scope: Scope) -> Option<AudioObjectID> { - let address = AudioObjectPropertyAddress { - mSelector: match scope { - Scope::Input => kAudioHardwarePropertyDefaultInputDevice, - Scope::Output => kAudioHardwarePropertyDefaultOutputDevice, - }, - mScope: kAudioObjectPropertyScopeGlobal, - mElement: kAudioObjectPropertyElementMaster, - }; + debug_assert_not_running_serially(); + run_serially_forward_panics(|| { + let address = AudioObjectPropertyAddress { + mSelector: match scope { + Scope::Input => kAudioHardwarePropertyDefaultInputDevice, + Scope::Output => kAudioHardwarePropertyDefaultOutputDevice, + }, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; - let mut devid: AudioObjectID = kAudioObjectUnknown; - let mut size = mem::size_of::<AudioObjectID>(); - let status = unsafe { - AudioObjectGetPropertyData( - kAudioObjectSystemObject, - &address, - 0, - ptr::null(), - &mut size as *mut usize as *mut UInt32, - &mut devid as *mut AudioObjectID as *mut c_void, - ) - }; - if status != NO_ERR || devid == kAudioObjectUnknown { - return None; - } - Some(devid) + let mut devid: AudioObjectID = kAudioObjectUnknown; + let mut size = mem::size_of::<AudioObjectID>(); + let status = unsafe { + AudioObjectGetPropertyData( + kAudioObjectSystemObject, + &address, + 0, + ptr::null(), + &mut size as *mut usize as *mut UInt32, + &mut devid as *mut AudioObjectID as *mut c_void, + ) + }; + + if status != NO_ERR || devid == kAudioObjectUnknown { + return None; + } + Some(devid) + }) } // TODO: Create a GetProperty trait and add a default implementation for it, then implement it @@ -99,16 +126,17 @@ impl TestAudioUnit { impl Drop for TestAudioUnit { fn drop(&mut self) { - unsafe { + run_serially_forward_panics(|| unsafe { AudioUnitUninitialize(self.0); AudioComponentInstanceDispose(self.0); - } + }); } } // TODO: 1. Return Result with custom errors. // 2. Allow to create a in-out unit. pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> { + debug_assert_not_running_serially(); let device = test_get_default_device(scope.clone()); let unit = test_create_audiounit(ComponentSubType::HALOutput); if device.is_none() || unit.is_none() { @@ -133,7 +161,7 @@ pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> { } } - let status = unsafe { + let status = run_serially(|| unsafe { AudioUnitSetProperty( unit.get_inner(), kAudioOutputUnitProperty_CurrentDevice, @@ -142,7 +170,7 @@ pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> { &device as *const AudioObjectID as *const c_void, mem::size_of::<AudioObjectID>() as u32, ) - }; + }); if status == NO_ERR { Some(unit) } else { @@ -159,6 +187,7 @@ pub enum ComponentSubType { // Surprisingly the AudioUnit can be created even when there is no any device on the platform, // no matter its subtype is HALOutput or DefaultOutput. pub fn test_create_audiounit(unit_type: ComponentSubType) -> Option<TestAudioUnit> { + debug_assert_not_running_serially(); let desc = AudioComponentDescription { componentType: kAudioUnitType_Output, componentSubType: match unit_type { @@ -169,12 +198,12 @@ pub fn test_create_audiounit(unit_type: ComponentSubType) -> Option<TestAudioUni componentFlags: 0, componentFlagsMask: 0, }; - let comp = unsafe { AudioComponentFindNext(ptr::null_mut(), &desc) }; + let comp = run_serially(|| unsafe { AudioComponentFindNext(ptr::null_mut(), &desc) }); if comp.is_null() { return None; } let mut unit: AudioUnit = ptr::null_mut(); - let status = unsafe { AudioComponentInstanceNew(comp, &mut unit) }; + let status = run_serially(|| unsafe { AudioComponentInstanceNew(comp, &mut unit) }); // TODO: Is unit possible to be null when no error returns ? if status != NO_ERR || unit.is_null() { None @@ -188,13 +217,14 @@ fn test_enable_audiounit_in_scope( scope: Scope, enable: bool, ) -> std::result::Result<(), OSStatus> { + debug_assert_not_running_serially(); assert!(!unit.is_null()); let (scope, element) = match scope { Scope::Input => (kAudioUnitScope_Input, AU_IN_BUS), Scope::Output => (kAudioUnitScope_Output, AU_OUT_BUS), }; let on_off: u32 = if enable { 1 } else { 0 }; - let status = unsafe { + let status = run_serially(|| unsafe { AudioUnitSetProperty( unit, kAudioOutputUnitProperty_EnableIO, @@ -203,7 +233,7 @@ fn test_enable_audiounit_in_scope( &on_off as *const u32 as *const c_void, mem::size_of::<u32>() as u32, ) - }; + }); if status == NO_ERR { Ok(()) } else { @@ -216,72 +246,80 @@ pub enum DeviceFilter { IncludeAll, } pub fn test_get_all_devices(filter: DeviceFilter) -> Vec<AudioObjectID> { - let mut devices = Vec::new(); - let address = AudioObjectPropertyAddress { - mSelector: kAudioHardwarePropertyDevices, - mScope: kAudioObjectPropertyScopeGlobal, - mElement: kAudioObjectPropertyElementMaster, - }; - let mut size: usize = 0; - let status = unsafe { - AudioObjectGetPropertyDataSize( - kAudioObjectSystemObject, - &address, - 0, - ptr::null(), - &mut size as *mut usize as *mut u32, - ) - }; - // size will be 0 if there is no device at all. - if status != NO_ERR || size == 0 { - return devices; - } - assert_eq!(size % mem::size_of::<AudioObjectID>(), 0); - let elements = size / mem::size_of::<AudioObjectID>(); - devices.resize(elements, kAudioObjectUnknown); - let status = unsafe { - AudioObjectGetPropertyData( - kAudioObjectSystemObject, - &address, - 0, - ptr::null(), - &mut size as *mut usize as *mut u32, - devices.as_mut_ptr() as *mut c_void, - ) - }; - if status != NO_ERR { - devices.clear(); - return devices; - } - for device in devices.iter() { - assert_ne!(*device, kAudioObjectUnknown); - } + debug_assert_not_running_serially(); + // To avoid races, the devices getter and the device name filtering have + // to run in the same serial task. If not, a device may exist when the + // getter runs but not when getting its uid. + run_serially_forward_panics(|| { + let mut devices = Vec::new(); + let address = AudioObjectPropertyAddress { + mSelector: kAudioHardwarePropertyDevices, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; + let mut size: usize = 0; + let status = unsafe { + AudioObjectGetPropertyDataSize( + kAudioObjectSystemObject, + &address, + 0, + ptr::null(), + &mut size as *mut usize as *mut u32, + ) + }; + // size will be 0 if there is no device at all. + if status != NO_ERR || size == 0 { + return devices; + } + assert_eq!(size % mem::size_of::<AudioObjectID>(), 0); + let elements = size / mem::size_of::<AudioObjectID>(); + devices.resize(elements, kAudioObjectUnknown); + let status = unsafe { + AudioObjectGetPropertyData( + kAudioObjectSystemObject, + &address, + 0, + ptr::null(), + &mut size as *mut usize as *mut u32, + devices.as_mut_ptr() as *mut c_void, + ) + }; + if status != NO_ERR { + devices.clear(); + return devices; + } + for device in devices.iter() { + assert_ne!(*device, kAudioObjectUnknown); + } - match filter { - DeviceFilter::ExcludeCubebAggregateAndVPIO => { - devices.retain(|&device| { - if let Ok(uid) = get_device_global_uid(device) { - let uid = uid.into_string(); - !uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME) - && !uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME) - } else { - true - } - }); + match filter { + DeviceFilter::ExcludeCubebAggregateAndVPIO => { + devices.retain(|&device| { + if let Ok(uid) = get_device_global_uid(device) { + let uid = uid.into_string(); + !uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME) + && !uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME) + } else { + true + } + }); + } + _ => {} } - _ => {} - } - devices + devices + }) } pub fn test_get_devices_in_scope(scope: Scope) -> Vec<AudioObjectID> { + debug_assert_not_running_serially(); let mut devices = test_get_all_devices(DeviceFilter::ExcludeCubebAggregateAndVPIO); devices.retain(|device| test_device_in_scope(*device, scope.clone())); devices } pub fn get_devices_info_in_scope(scope: Scope) -> Vec<TestDeviceInfo> { + debug_assert_not_running_serially(); fn print_info(info: &TestDeviceInfo) { println!("{:>4}: {}\n\tuid: {}", info.id, info.label, info.uid); } @@ -337,8 +375,9 @@ pub fn test_device_channels_in_scope( id: AudioObjectID, scope: Scope, ) -> std::result::Result<u32, OSStatus> { + debug_assert_not_running_serially(); let address = AudioObjectPropertyAddress { - mSelector: kAudioDevicePropertyStreamConfiguration, + mSelector: kAudioDevicePropertyStreams, mScope: match scope { Scope::Input => kAudioDevicePropertyScopeInput, Scope::Output => kAudioDevicePropertyScopeOutput, @@ -346,7 +385,7 @@ pub fn test_device_channels_in_scope( mElement: kAudioObjectPropertyElementMaster, }; let mut size: usize = 0; - let status = unsafe { + let status = run_serially(|| unsafe { AudioObjectGetPropertyDataSize( id, &address, @@ -354,49 +393,90 @@ pub fn test_device_channels_in_scope( ptr::null(), &mut size as *mut usize as *mut u32, ) - }; + }); if status != NO_ERR { return Err(status); } if size == 0 { return Ok(0); } - let byte_len = size / mem::size_of::<u8>(); - let mut bytes = vec![0u8; byte_len]; - let status = unsafe { + let mut stream_list = vec![0, (size / mem::size_of::<AudioObjectID>()) as u32]; + let status = run_serially(|| unsafe { AudioObjectGetPropertyData( id, &address, 0, ptr::null(), &mut size as *mut usize as *mut u32, - bytes.as_mut_ptr() as *mut c_void, + stream_list.as_mut_ptr() as *mut c_void, ) - }; + }); if status != NO_ERR { return Err(status); } - let buf_list = unsafe { &*(bytes.as_mut_ptr() as *mut AudioBufferList) }; - let buf_len = buf_list.mNumberBuffers as usize; - if buf_len == 0 { - return Ok(0); - } - let buf_ptr = buf_list.mBuffers.as_ptr() as *const AudioBuffer; - let buffers = unsafe { slice::from_raw_parts(buf_ptr, buf_len) }; - let mut channels: u32 = 0; - for buffer in buffers { - channels += buffer.mNumberChannels; - } + let channels = stream_list + .iter() + .filter(|s: &&AudioObjectID| { + if scope != Scope::Input { + return true; + } + let address = AudioObjectPropertyAddress { + mSelector: kAudioStreamPropertyTerminalType, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; + let mut ttype: u32 = 0; + let status = unsafe { + AudioObjectGetPropertyData( + **s, + &address, + 0, + ptr::null(), + &mut mem::size_of::<u32>() as *mut usize as *mut u32, + &mut ttype as *mut u32 as *mut c_void, + ) + }; + if status != NO_ERR { + return false; + } + ttype == kAudioStreamTerminalTypeMicrophone + || (INPUT_MICROPHONE..OUTPUT_UNDEFINED).contains(&ttype) + }) + .map(|s: &AudioObjectID| { + let address = AudioObjectPropertyAddress { + mSelector: kAudioStreamPropertyVirtualFormat, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; + let mut format = AudioStreamBasicDescription::default(); + let status = unsafe { + AudioObjectGetPropertyData( + *s, + &address, + 0, + ptr::null(), + &mut mem::size_of::<AudioStreamBasicDescription>() as *mut usize as *mut u32, + &mut format as *mut AudioStreamBasicDescription as *mut c_void, + ) + }; + if status != NO_ERR { + return 0; + } + format.mChannelsPerFrame + }) + .sum(); Ok(channels) } pub fn test_device_in_scope(id: AudioObjectID, scope: Scope) -> bool { + debug_assert_not_running_serially(); let channels = test_device_channels_in_scope(id, scope); channels.is_ok() && channels.unwrap() > 0 } pub fn test_get_all_onwed_devices(id: AudioDeviceID) -> Vec<AudioObjectID> { assert_ne!(id, kAudioObjectUnknown); + debug_assert_running_serially(); let address = AudioObjectPropertyAddress { mSelector: kAudioObjectPropertyOwnedObjects, @@ -409,45 +489,46 @@ pub fn test_get_all_onwed_devices(id: AudioDeviceID) -> Vec<AudioObjectID> { let qualifier_data = &class_id; let mut size: usize = 0; - unsafe { - assert_eq!( + assert_eq!( + unsafe { AudioObjectGetPropertyDataSize( id, &address, qualifier_data_size as u32, qualifier_data as *const u32 as *const c_void, - &mut size as *mut usize as *mut u32 - ), - NO_ERR - ); - } + &mut size as *mut usize as *mut u32, + ) + }, + NO_ERR + ); assert_ne!(size, 0); let elements = size / mem::size_of::<AudioObjectID>(); let mut devices: Vec<AudioObjectID> = allocate_array(elements); - unsafe { - assert_eq!( + assert_eq!( + unsafe { AudioObjectGetPropertyData( id, &address, qualifier_data_size as u32, qualifier_data as *const u32 as *const c_void, &mut size as *mut usize as *mut u32, - devices.as_mut_ptr() as *mut c_void - ), - NO_ERR - ); - } + devices.as_mut_ptr() as *mut c_void, + ) + }, + NO_ERR + ); devices } pub fn test_get_master_device(id: AudioObjectID) -> String { assert_ne!(id, kAudioObjectUnknown); + debug_assert_running_serially(); let address = AudioObjectPropertyAddress { - mSelector: kAudioAggregateDevicePropertyMasterSubDevice, + mSelector: kAudioAggregateDevicePropertyMainSubDevice, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster, }; @@ -465,6 +546,7 @@ pub fn test_get_master_device(id: AudioObjectID) -> String { } pub fn test_get_drift_compensations(id: AudioObjectID) -> std::result::Result<u32, OSStatus> { + debug_assert_running_serially(); let address = AudioObjectPropertyAddress { mSelector: kAudioSubDevicePropertyDriftCompensation, mScope: kAudioObjectPropertyScopeGlobal, @@ -491,6 +573,7 @@ pub fn test_get_drift_compensations(id: AudioObjectID) -> std::result::Result<u3 pub fn test_audiounit_scope_is_enabled(unit: AudioUnit, scope: Scope) -> bool { assert!(!unit.is_null()); + debug_assert_not_running_serially(); let mut has_io: UInt32 = 0; let (scope, element) = match scope { Scope::Input => (kAudioUnitScope_Input, AU_IN_BUS), @@ -498,14 +581,14 @@ pub fn test_audiounit_scope_is_enabled(unit: AudioUnit, scope: Scope) -> bool { }; let mut size = mem::size_of::<UInt32>(); assert_eq!( - audio_unit_get_property( + run_serially(|| audio_unit_get_property( unit, kAudioOutputUnitProperty_HasIO, scope, element, &mut has_io, &mut size - ), + )), NO_ERR ); has_io != 0 @@ -516,6 +599,7 @@ pub fn test_audiounit_get_buffer_frame_size( scope: Scope, prop_scope: PropertyScope, ) -> std::result::Result<u32, OSStatus> { + debug_assert_not_running_serially(); let element = match scope { Scope::Input => AU_IN_BUS, Scope::Output => AU_OUT_BUS, @@ -526,7 +610,7 @@ pub fn test_audiounit_get_buffer_frame_size( }; let mut buffer_frames: u32 = 0; let mut size = mem::size_of::<u32>(); - let status = unsafe { + let status = run_serially(|| unsafe { AudioUnitGetProperty( unit, kAudioDevicePropertyBufferFrameSize, @@ -535,7 +619,7 @@ pub fn test_audiounit_get_buffer_frame_size( &mut buffer_frames as *mut u32 as *mut c_void, &mut size as *mut usize as *mut u32, ) - }; + }); if status == NO_ERR { Ok(buffer_frames) } else { @@ -554,6 +638,7 @@ pub fn test_set_default_device( device: AudioObjectID, scope: Scope, ) -> std::result::Result<AudioObjectID, OSStatus> { + debug_assert_not_running_serially(); assert!(test_device_in_scope(device, scope.clone())); let default = test_get_default_device(scope.clone()).unwrap(); if default == device { @@ -570,7 +655,7 @@ pub fn test_set_default_device( mElement: kAudioObjectPropertyElementMaster, }; let size = mem::size_of::<AudioObjectID>(); - let status = unsafe { + let status = run_serially(|| unsafe { AudioObjectSetPropertyData( kAudioObjectSystemObject, &address, @@ -579,7 +664,7 @@ pub fn test_set_default_device( size as u32, &device as *const AudioObjectID as *const c_void, ) - }; + }); let new_default = test_get_default_device(scope.clone()).unwrap(); if new_default == default { Err(-1) @@ -644,6 +729,7 @@ pub fn test_create_device_change_listener<F>(scope: Scope, listener: F) -> TestP where F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus, { + debug_assert_running_serially(); let address = AudioObjectPropertyAddress { mSelector: match scope { Scope::Input => kAudioHardwarePropertyDefaultInputDevice, @@ -652,6 +738,7 @@ where mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster, }; + TestPropertyListener::new(kAudioObjectSystemObject, address, listener) } @@ -677,6 +764,7 @@ where } pub fn start(&self) -> std::result::Result<(), OSStatus> { + debug_assert_running_serially(); let status = unsafe { AudioObjectAddPropertyListener( self.device, @@ -693,6 +781,7 @@ where } pub fn stop(&self) -> std::result::Result<(), OSStatus> { + debug_assert_running_serially(); let status = unsafe { AudioObjectRemovePropertyListener( self.device, @@ -726,7 +815,7 @@ where F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus, { fn drop(&mut self) { - self.stop(); + run_serially(|| self.stop()); } } @@ -767,6 +856,7 @@ impl TestDevicePlugger { } fn destroy_aggregate_device(&mut self) -> std::result::Result<(), OSStatus> { + debug_assert_not_running_serially(); assert_ne!(self.plugin_id, kAudioObjectUnknown); assert_ne!(self.device_id, kAudioObjectUnknown); @@ -777,7 +867,7 @@ impl TestDevicePlugger { }; let mut size: usize = 0; - let status = unsafe { + let status = run_serially(|| unsafe { AudioObjectGetPropertyDataSize( self.plugin_id, &address, @@ -785,13 +875,13 @@ impl TestDevicePlugger { ptr::null(), &mut size as *mut usize as *mut u32, ) - }; + }); if status != NO_ERR { return Err(status); } assert_ne!(size, 0); - let status = unsafe { + let status = run_serially(|| unsafe { // This call can simulate removing a device. AudioObjectGetPropertyData( self.plugin_id, @@ -801,7 +891,7 @@ impl TestDevicePlugger { &mut size as *mut usize as *mut u32, &mut self.device_id as *mut AudioDeviceID as *mut c_void, ) - }; + }); if status == NO_ERR { self.device_id = kAudioObjectUnknown; Ok(()) @@ -811,6 +901,7 @@ impl TestDevicePlugger { } fn create_aggregate_device(&self) -> std::result::Result<AudioObjectID, OSStatus> { + debug_assert_not_running_serially(); use std::time::{SystemTime, UNIX_EPOCH}; const TEST_AGGREGATE_DEVICE_NAME: &str = "TestAggregateDevice"; @@ -830,7 +921,7 @@ impl TestDevicePlugger { }; let mut size: usize = 0; - let status = unsafe { + let status = run_serially(|| unsafe { AudioObjectGetPropertyDataSize( self.plugin_id, &address, @@ -838,7 +929,7 @@ impl TestDevicePlugger { ptr::null(), &mut size as *mut usize as *mut u32, ) - }; + }); if status != NO_ERR { return Err(status); } @@ -915,14 +1006,18 @@ impl TestDevicePlugger { CFRelease(sub_devices as *const c_void); // This call can simulate adding a device. - let status = AudioObjectGetPropertyData( - self.plugin_id, - &address, - mem::size_of_val(&device_dict) as u32, - &device_dict as *const CFMutableDictionaryRef as *const c_void, - &mut size as *mut usize as *mut u32, - &mut device_id as *mut AudioDeviceID as *mut c_void, - ); + let status = { + run_serially(|| { + AudioObjectGetPropertyData( + self.plugin_id, + &address, + mem::size_of_val(&device_dict) as u32, + &device_dict as *const CFMutableDictionaryRef as *const c_void, + &mut size as *mut usize as *mut u32, + &mut device_id as *mut AudioDeviceID as *mut c_void, + ) + }) + }; CFRelease(device_dict as *const c_void); status }; @@ -935,6 +1030,7 @@ impl TestDevicePlugger { } fn get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus> { + debug_assert_not_running_serially(); let address = AudioObjectPropertyAddress { mSelector: kAudioHardwarePropertyPlugInForBundleID, mScope: kAudioObjectPropertyScopeGlobal, @@ -942,7 +1038,7 @@ impl TestDevicePlugger { }; let mut size: usize = 0; - let status = unsafe { + let status = run_serially(|| unsafe { AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &address, @@ -950,7 +1046,7 @@ impl TestDevicePlugger { ptr::null(), &mut size as *mut usize as *mut u32, ) - }; + }); if status != NO_ERR { return Err(status); } @@ -967,14 +1063,16 @@ impl TestDevicePlugger { assert_eq!(size, mem::size_of_val(&translation_value)); let status = unsafe { - let status = AudioObjectGetPropertyData( - kAudioObjectSystemObject, - &address, - 0, - ptr::null(), - &mut size as *mut usize as *mut u32, - &mut translation_value as *mut AudioValueTranslation as *mut c_void, - ); + let status = run_serially(|| { + AudioObjectGetPropertyData( + kAudioObjectSystemObject, + &address, + 0, + ptr::null(), + &mut size as *mut usize as *mut u32, + &mut translation_value as *mut AudioValueTranslation as *mut c_void, + ) + }); CFRelease(in_bundle_ref as *const c_void); status }; @@ -991,10 +1089,11 @@ impl TestDevicePlugger { // them into the array, if the device is an aggregate device. See the code in // AggregateDevice::get_sub_devices and audiounit_set_aggregate_sub_device_list. fn get_sub_devices(scope: Scope) -> Option<CFArrayRef> { + debug_assert_not_running_serially(); let device = test_get_default_device(scope); device?; let device = device.unwrap(); - let uid = get_device_global_uid(device); + let uid = run_serially(|| get_device_global_uid(device)); if uid.is_err() { return None; } @@ -1033,6 +1132,7 @@ pub fn test_ops_context_operation<F>(name: &'static str, operation: F) where F: FnOnce(*mut ffi::cubeb), { + debug_assert_not_running_serially(); let name_c_string = CString::new(name).expect("Failed to create context name"); let mut context = ptr::null_mut::<ffi::cubeb>(); assert_eq!( @@ -1047,8 +1147,9 @@ where // The in-out stream initializeed with different device will create an aggregate_device and // result in firing device-collection-changed callbacks. Run in-out streams with tests // capturing device-collection-changed callbacks may cause troubles. -pub fn test_ops_stream_operation<F>( +pub fn test_ops_stream_operation_on_context<F>( name: &'static str, + context_ptr: *mut ffi::cubeb, input_device: ffi::cubeb_devid, input_stream_params: *mut ffi::cubeb_stream_params, output_device: ffi::cubeb_devid, @@ -1061,43 +1162,72 @@ pub fn test_ops_stream_operation<F>( ) where F: FnOnce(*mut ffi::cubeb_stream), { - test_ops_context_operation("context: stream operation", |context_ptr| { - // Do nothing if there is no input/output device to perform input/output tests. - if !input_stream_params.is_null() && test_get_default_device(Scope::Input).is_none() { - println!("No input device to perform input tests for \"{}\".", name); - return; - } + // Do nothing if there is no input/output device to perform input/output tests. + if !input_stream_params.is_null() && test_get_default_device(Scope::Input).is_none() { + println!("No input device to perform input tests for \"{}\".", name); + return; + } - if !output_stream_params.is_null() && test_get_default_device(Scope::Output).is_none() { - println!("No output device to perform output tests for \"{}\".", name); - return; - } + if !output_stream_params.is_null() && test_get_default_device(Scope::Output).is_none() { + println!("No output device to perform output tests for \"{}\".", name); + return; + } - let mut stream: *mut ffi::cubeb_stream = ptr::null_mut(); - let stream_name = CString::new(name).expect("Failed to create stream name"); - assert_eq!( - unsafe { - OPS.stream_init.unwrap()( - context_ptr, - &mut stream, - stream_name.as_ptr(), - input_device, - input_stream_params, - output_device, - output_stream_params, - latency_frames, - data_callback, - state_callback, - user_ptr, - ) - }, - ffi::CUBEB_OK - ); - assert!(!stream.is_null()); - operation(stream); + let mut stream: *mut ffi::cubeb_stream = ptr::null_mut(); + let stream_name = CString::new(name).expect("Failed to create stream name"); + assert_eq!( unsafe { - OPS.stream_destroy.unwrap()(stream); - } + OPS.stream_init.unwrap()( + context_ptr, + &mut stream, + stream_name.as_ptr(), + input_device, + input_stream_params, + output_device, + output_stream_params, + latency_frames, + data_callback, + state_callback, + user_ptr, + ) + }, + ffi::CUBEB_OK + ); + assert!(!stream.is_null()); + operation(stream); + unsafe { + OPS.stream_destroy.unwrap()(stream); + } +} + +pub fn test_ops_stream_operation<F>( + name: &'static str, + input_device: ffi::cubeb_devid, + input_stream_params: *mut ffi::cubeb_stream_params, + output_device: ffi::cubeb_devid, + output_stream_params: *mut ffi::cubeb_stream_params, + latency_frames: u32, + data_callback: ffi::cubeb_data_callback, + state_callback: ffi::cubeb_state_callback, + user_ptr: *mut c_void, + operation: F, +) where + F: FnOnce(*mut ffi::cubeb_stream), +{ + test_ops_context_operation("context: stream operation", |context_ptr| { + test_ops_stream_operation_on_context( + name, + context_ptr, + input_device, + input_stream_params, + output_device, + output_stream_params, + latency_frames, + data_callback, + state_callback, + user_ptr, + operation, + ); }); } @@ -1136,7 +1266,7 @@ fn test_get_raw_stream<F>( user_ptr, data_callback, state_callback, - global_latency_frames.unwrap(), + global_latency_frames, ); stream.core_stream_data = CoreStreamData::new(&stream, None, None); @@ -1154,6 +1284,7 @@ pub fn test_get_stream_with_default_data_callback_by_type<F>( ) where F: FnOnce(&mut AudioUnitStream), { + debug_assert_not_running_serially(); let mut input_params = get_dummy_stream_params(Scope::Input); let mut output_params = get_dummy_stream_params(Scope::Output); |