diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /third_party/rust/cubeb-coreaudio | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
16 files changed, 2661 insertions, 775 deletions
diff --git a/third_party/rust/cubeb-coreaudio/.cargo-checksum.json b/third_party/rust/cubeb-coreaudio/.cargo-checksum.json index 5c8366f60a..fa6f229b4c 100644 --- a/third_party/rust/cubeb-coreaudio/.cargo-checksum.json +++ b/third_party/rust/cubeb-coreaudio/.cargo-checksum.json @@ -1 +1 @@ -{"files":{".circleci/config.yml":"7f3dc865105ca8f33965a7958b1fe2e627ae2d5a703f3b2a4ab6e2e796018597",".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".githooks/pre-push":"8b8b26544cd56f54c0c33812551f786bb25cb08c86dbfeb6bf3daad881c826a1",".github/workflows/test.yml":"aa1998a3b104ad131805ca3513832cef3f65300192824f8b1efc9a5a0cc108f6",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"d7e757e664c23fae52028f1dfc5917f92523c08702e3a1f95e1fd38ed714416c","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"0007782a05a5330f739ad789c19c82562c82e32386b0447000fc72c0d48405bc","build-audiounit-rust-in-cubeb.sh":"d228a05985dcd02ec1ecac66a2b64dae5a530804a25a7054ccc95905aedfb7ef","install_git_hook.sh":"d38c8e51e636f6b90b489621ac34ccd1d1b1f40dccce3d178ed1da1c5068f16d","install_rustfmt_clippy.sh":"4ae90d8dcb9757cb3ae4ae142ef80e5377c0dde61c63f4a3c32418646e80ca7b","run_device_tests.sh":"d717e598c96e4911d9494b18382d6bd3a8d5038b7d68d3166ad4336e237a97d8","run_sanitizers.sh":"84e93a0da137803018f37403511e8c92760be730426bf6cea34419d93d1a7ff8","run_tests.sh":"916a7ae4a406d2274417d6eca939a878db5adcb6144e5680d9d148bf90178f1c","src/backend/aggregate_device.rs":"43511107ba2a75a19340ac663c981362ca1b75b679b6c295d88b5035bd7e3619","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"e9bcf964347daa8952f98caa2746e34a31ea8908375204896593f56e4b6147ca","src/backend/device_property.rs":"a7622feaa41db1cd76fd35a85a022e44f4894e396a104a59008d5b8757d2ab4e","src/backend/mixer.rs":"ed299d3954e2a823060c870a8244673a7d4bca530830cb66b964d047a80ee3af","src/backend/mod.rs":"1591669c30a3d07754bfb39c9cb042cdd101f0ab89be13f6cdf74d376e441cf8","src/backend/resampler.rs":"48bf8f56ae8d60dbabca6417b768000619abee8731ac3902164b45651ac08a4d","src/backend/tests/aggregate_device.rs":"e3f94e118e1dd47941fbba4417de40bddc4254d9f06b1e938f58d8f1aa566a5c","src/backend/tests/api.rs":"cd7e7551e2e82b19da883621a494d2a6779c373f3ff2d12ee52fae8efec1e7b8","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"f68c2eaa55c3ec2a58894832fbca1e2a2e79e740b145f76a0f45452af465a934","src/backend/tests/device_property.rs":"ea0be5f8834be494cb33f854ce9d334b5763dc5287f949bcb4bd025d8a8b2d3b","src/backend/tests/interfaces.rs":"af8e3fdeb58226621699b29f1a90621b2260e3f17292dac54860cd05fe4eec71","src/backend/tests/manual.rs":"4a1634e86beb145d2703722a8be057a762953241329c82ee09acf7dc0f0d9d0c","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"59632744e70616ab7037facb0787db339b96800c8cc397d203241548c5cfb7f5","src/backend/tests/tone.rs":"779cc14fc2a362bf7f26ce66ad70c0639501176175655a99b7fefb3c59d56c7a","src/backend/tests/utils.rs":"efb8b3709aff7ed5e2923566084de3e0709f3bd9c18a04f3310d7a3b86fa4b71","src/backend/utils.rs":"6c3ffbcd602e6cc9f56deb9ecb07b2eef2e6f074ef924178e466f380aae5c595","src/capi.rs":"21b66b70545bf04ec719928004d1d9adb45b24ced51288f5b2993d79aaf78f5f","src/lib.rs":"5e586d45cd6b3722f0a6736d9252593299269817a153eef1930a5fb9bfbb56f5","todo.md":"efc1f012eb9a331a040cad4ac03aa79307f25885f71b6fb38f3ad7af8d7d515c"},"package":null}
\ No newline at end of file +{"files":{".circleci/config.yml":"7f3dc865105ca8f33965a7958b1fe2e627ae2d5a703f3b2a4ab6e2e796018597",".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".githooks/pre-push":"8b8b26544cd56f54c0c33812551f786bb25cb08c86dbfeb6bf3daad881c826a1",".github/workflows/test.yml":"ac8f4cf5b7631b5c738d50c0cf78113bd395940b9e76593904bbaf2d02d16a70",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"0fb7c56c04e05dacffa5176f885cb8019ee6ab7f885479be501aba0eaac2148f","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"0007782a05a5330f739ad789c19c82562c82e32386b0447000fc72c0d48405bc","build-audiounit-rust-in-cubeb.sh":"d228a05985dcd02ec1ecac66a2b64dae5a530804a25a7054ccc95905aedfb7ef","install_git_hook.sh":"d38c8e51e636f6b90b489621ac34ccd1d1b1f40dccce3d178ed1da1c5068f16d","install_rustfmt_clippy.sh":"4ae90d8dcb9757cb3ae4ae142ef80e5377c0dde61c63f4a3c32418646e80ca7b","run_device_tests.sh":"90c2542fa3ff8a35fed894fae3a1aa0157117b7f0e28df14b8e6f7b1f1f43797","run_sanitizers.sh":"84e93a0da137803018f37403511e8c92760be730426bf6cea34419d93d1a7ff8","run_tests.sh":"bae82f66dd47a060b6fdcc238520084aec1079d5b1b1d66d103baa1ffaa8773d","src/backend/aggregate_device.rs":"db7d644358090b1d65ff2d53ad854369790ae4ad7dfa12b79888c0002c1b4950","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"e9bcf964347daa8952f98caa2746e34a31ea8908375204896593f56e4b6147ca","src/backend/device_property.rs":"a7622feaa41db1cd76fd35a85a022e44f4894e396a104a59008d5b8757d2ab4e","src/backend/mixer.rs":"ed299d3954e2a823060c870a8244673a7d4bca530830cb66b964d047a80ee3af","src/backend/mod.rs":"e52b79a17dbf7faa072ec87cc3e4201b907772104c3be777498275733b9c334e","src/backend/resampler.rs":"48bf8f56ae8d60dbabca6417b768000619abee8731ac3902164b45651ac08a4d","src/backend/tests/aggregate_device.rs":"770cf90f32b5ab2203476031c1fbc8379b713baa97bec36f7fd0d77fef1efd60","src/backend/tests/api.rs":"d72d7c0de8d12e880966948be4686bcf8c789f0ef19cb435c242fd72f2d252f9","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"babf50326fb38db24fe80f24f546e1b6ad04319ae8835bb372d893fc9b3038a2","src/backend/tests/device_property.rs":"73c25f579a995e8a59c9b7d391813afb75f739b5e2f825480cba04499a1d46e8","src/backend/tests/interfaces.rs":"654333cd6d6023e72ba392d98872d33bc55f8f052205a9f701aec72069449e24","src/backend/tests/manual.rs":"e550cc8bb7619bb80b68e49bf7f475c029e0f1b34323d1d30edcbe322cf4efc7","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"a7ebd579339c40ca64c0757cc9da6baec641e670f226e1b2ec5049894700bd7a","src/backend/tests/tone.rs":"b028c67777b6453a26190b6a49785dfe28556adcbe179cb10862ce0d47ee8509","src/backend/tests/utils.rs":"80d7e4ebc06b23c63a4d2867e0c80e0bfe05449fa55edd21e785ed2c089bf7d5","src/backend/utils.rs":"6c3ffbcd602e6cc9f56deb9ecb07b2eef2e6f074ef924178e466f380aae5c595","src/capi.rs":"21b66b70545bf04ec719928004d1d9adb45b24ced51288f5b2993d79aaf78f5f","src/lib.rs":"5e586d45cd6b3722f0a6736d9252593299269817a153eef1930a5fb9bfbb56f5","todo.md":"efc1f012eb9a331a040cad4ac03aa79307f25885f71b6fb38f3ad7af8d7d515c"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/cubeb-coreaudio/.github/workflows/test.yml b/third_party/rust/cubeb-coreaudio/.github/workflows/test.yml index 06fc86fa33..2bbb9eab5d 100644 --- a/third_party/rust/cubeb-coreaudio/.github/workflows/test.yml +++ b/third_party/rust/cubeb-coreaudio/.github/workflows/test.yml @@ -4,36 +4,52 @@ on: [push, pull_request] jobs: build: - runs-on: macOS-latest + runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: + os: [macos-12, macos-13, macos-14] rust: [stable] experimental: [false] include: - - rust: nightly + - os: macos-14 + rust: nightly experimental: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive - name: Install Rust run: rustup toolchain install ${{ matrix.rust }} --profile minimal --component rustfmt clippy - - - name: Setup + + - name: Setup Rust run: | rustup default ${{ matrix.rust }} toolchain=$(rustup default) echo "Use Rust toolchain: $toolchain" rustc --version cargo --version - + + - name: Setup Audio + if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' }} + run: | + brew install switchaudio-osx + brew install blackhole-2ch + SwitchAudioSource -s "BlackHole 2ch" -t input + SwitchAudioSource -s "BlackHole 2ch" -t output + + - name: Grant microphone access + if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' }} + env: + tcc_extra_columns: ${{ matrix.os == 'macos-14' && ',NULL,NULL,''UNUSED'',1687786159' || '' }} + run: sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159${{ env.tcc_extra_columns }});" + - name: Build run: cargo build --verbose - + - name: Regular Test run: sh run_tests.sh diff --git a/third_party/rust/cubeb-coreaudio/Cargo.toml b/third_party/rust/cubeb-coreaudio/Cargo.toml index f9067d3c08..8a73548f57 100644 --- a/third_party/rust/cubeb-coreaudio/Cargo.toml +++ b/third_party/rust/cubeb-coreaudio/Cargo.toml @@ -36,6 +36,7 @@ libc = "0.2" mach = "0.3" ringbuf = "0.2.6" triple_buffer = "5.0.5" +whatsys = "0.3" [dependencies.coreaudio-sys-utils] path = "coreaudio-sys-utils" diff --git a/third_party/rust/cubeb-coreaudio/run_device_tests.sh b/third_party/rust/cubeb-coreaudio/run_device_tests.sh index ae6df49713..69bb3385f4 100755 --- a/third_party/rust/cubeb-coreaudio/run_device_tests.sh +++ b/third_party/rust/cubeb-coreaudio/run_device_tests.sh @@ -15,8 +15,6 @@ cargo test test_plug_and_unplug_device -- --ignored --nocapture cargo test test_register_device_changed_callback_to_check_default_device_changed_input -- --ignored --nocapture cargo test test_register_device_changed_callback_to_check_default_device_changed_output -- --ignored --nocapture cargo test test_register_device_changed_callback_to_check_default_device_changed_duplex -- --ignored --nocapture -cargo test test_register_device_changed_callback_to_check_input_alive_changed_input -- --ignored --nocapture -cargo test test_register_device_changed_callback_to_check_input_alive_changed_duplex -- --ignored --nocapture cargo test test_destroy_input_stream_after_unplugging_a_nondefault_input_device -- --ignored --nocapture cargo test test_suspend_input_stream_by_unplugging_a_nondefault_input_device -- --ignored --nocapture diff --git a/third_party/rust/cubeb-coreaudio/run_tests.sh b/third_party/rust/cubeb-coreaudio/run_tests.sh index e119da1f03..2b048f4790 100755 --- a/third_party/rust/cubeb-coreaudio/run_tests.sh +++ b/third_party/rust/cubeb-coreaudio/run_tests.sh @@ -39,8 +39,9 @@ cargo clippy -- -D warnings # Regular Tests cargo test --verbose -cargo test test_configure_output -- --ignored -cargo test test_aggregate -- --ignored --test-threads=1 + +# Timing sensitive tests must run serially so they cannot be impacted by other tasks on the queue +cargo test test_ops_timing_sensitive -- --ignored --test-threads=1 # Parallel Tests cargo test test_parallel -- --ignored --nocapture --test-threads=1 diff --git a/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs b/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs index 2738631b87..782e76de2f 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs @@ -69,6 +69,7 @@ impl AggregateDevice { input_id: AudioObjectID, output_id: AudioObjectID, ) -> std::result::Result<Self, Error> { + debug_assert_running_serially(); let plugin_id = Self::get_system_plugin_id()?; let device_id = Self::create_blank_device_sync(plugin_id)?; @@ -399,12 +400,12 @@ impl AggregateDevice { let sub_devices = CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks); // The order of the items in the array is significant and is used to determine the order of the streams // of the AudioAggregateDevice. - for device in output_sub_devices { + for device in input_sub_devices { let uid = get_device_global_uid(device)?; CFArrayAppendValue(sub_devices, uid.get_raw() as *const c_void); } - for device in input_sub_devices { + for device in output_sub_devices { let uid = get_device_global_uid(device)?; CFArrayAppendValue(sub_devices, uid.get_raw() as *const c_void); } @@ -466,6 +467,28 @@ impl AggregateDevice { } } + pub fn get_master_device_uid(device_id: AudioDeviceID) -> std::result::Result<String, Error> { + let address = AudioObjectPropertyAddress { + mSelector: kAudioAggregateDevicePropertyMainSubDevice, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; + + let mut master: CFStringRef = ptr::null_mut(); + let mut size = mem::size_of::<CFStringRef>(); + let status = audio_object_get_property_data(device_id, &address, &mut size, &mut master); + if status != NO_ERR { + return Err(Error::from(status)); + } + + if master.is_null() { + return Ok(String::default()); + } + + let master = StringRef::new(master as _); + Ok(master.into_string()) + } + pub fn set_master_device( device_id: AudioDeviceID, primary_id: AudioDeviceID, @@ -480,12 +503,12 @@ impl AggregateDevice { ); let address = AudioObjectPropertyAddress { - mSelector: kAudioAggregateDevicePropertyMasterSubDevice, + mSelector: kAudioAggregateDevicePropertyMainSubDevice, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster, }; - // Master become the 1st sub device of the primary device + // The master device will be the 1st sub device of the primary device. let output_sub_devices = Self::get_sub_devices(primary_id)?; assert!(!output_sub_devices.is_empty()); let master_sub_device_uid = get_device_global_uid(output_sub_devices[0]).unwrap(); @@ -548,16 +571,23 @@ impl AggregateDevice { return Err(Error::from(status)); } + let master_sub_device_uid = Self::get_master_device_uid(device_id)?; + let address = AudioObjectPropertyAddress { mSelector: kAudioSubDevicePropertyDriftCompensation, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster, }; - // Start from the second device since the first is the master clock - for device in &sub_devices[1..] { + for &device in &sub_devices { + let uid = get_device_global_uid(device) + .map(|sr| sr.into_string()) + .unwrap_or_default(); + if uid == master_sub_device_uid { + continue; + } let status = audio_object_set_property_data( - *device, + device, &address, mem::size_of::<u32>(), &DRIFT_COMPENSATION, @@ -671,6 +701,7 @@ impl Default for AggregateDevice { impl Drop for AggregateDevice { fn drop(&mut self) { + debug_assert_running_serially(); if self.plugin_id != kAudioObjectUnknown && self.device_id != kAudioObjectUnknown { if let Err(r) = Self::destroy_device(self.plugin_id, self.device_id) { cubeb_log!( diff --git a/third_party/rust/cubeb-coreaudio/src/backend/mod.rs b/third_party/rust/cubeb-coreaudio/src/backend/mod.rs index 61ae44fea1..e6be028a2e 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/mod.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/mod.rs @@ -46,15 +46,15 @@ use std::mem; use std::os::raw::{c_uint, c_void}; use std::ptr; use std::slice; +use std::str::FromStr; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; +use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak}; +use std::time::{Duration, Instant}; const NO_ERR: OSStatus = 0; const AU_OUT_BUS: AudioUnitElement = 0; const AU_IN_BUS: AudioUnitElement = 1; -const DISPATCH_QUEUE_LABEL: &str = "org.mozilla.cubeb"; const PRIVATE_AGGREGATE_DEVICE_NAME: &str = "CubebAggregateDevice"; const VOICEPROCESSING_AGGREGATE_DEVICE_NAME: &str = "VPAUAggregateAudioDevice"; @@ -65,6 +65,34 @@ const APPLE_STUDIO_DISPLAY_USB_ID: &str = "05AC:1114"; const SAFE_MIN_LATENCY_FRAMES: u32 = 128; const SAFE_MAX_LATENCY_FRAMES: u32 = 512; +const VPIO_IDLE_TIMEOUT: Duration = Duration::from_secs(10); + +const MACOS_KERNEL_MAJOR_VERSION_MONTEREY: u32 = 21; + +#[derive(Debug, PartialEq)] +enum ParseMacOSKernelVersionError { + SysCtl, + Malformed, + Parsing, +} + +fn macos_kernel_major_version() -> std::result::Result<u32, ParseMacOSKernelVersionError> { + let ver = whatsys::kernel_version(); + if ver.is_none() { + return Err(ParseMacOSKernelVersionError::SysCtl); + } + let ver = ver.unwrap(); + let major = ver.split('.').next(); + if major.is_none() { + return Err(ParseMacOSKernelVersionError::Malformed); + } + let parsed_major = u32::from_str(major.unwrap()); + if parsed_major.is_err() { + return Err(ParseMacOSKernelVersionError::Parsing); + } + Ok(parsed_major.unwrap()) +} + bitflags! { #[allow(non_camel_case_types)] #[derive(Clone, Debug, PartialEq, Copy)] @@ -191,6 +219,7 @@ fn set_notification_runloop() { fn create_device_info(devid: AudioDeviceID, devtype: DeviceType) -> Option<device_info> { assert_ne!(devid, kAudioObjectSystemObject); + debug_assert_running_serially(); let mut flags = match devtype { DeviceType::INPUT => device_flags::DEV_INPUT, @@ -457,7 +486,6 @@ extern "C" fn audiounit_input_callback( assert!(!user_ptr.is_null()); let stm = unsafe { &mut *(user_ptr as *mut AudioUnitStream) }; - let using_voice_processing_unit = stm.core_stream_data.using_voice_processing_unit(); if unsafe { *flags | kAudioTimeStampHostTimeValid } != 0 { let now = unsafe { mach_absolute_time() }; @@ -597,17 +625,20 @@ extern "C" fn audiounit_input_callback( ErrorHandle::Return(NO_ERR) }; - // If the input (input-only stream) or the output is drained (duplex stream), - // cancel this callback. Note that for voice processing cases (a single unit), - // the output callback handles stopping the unit and notifying of state. - if !using_voice_processing_unit && stm.draining.load(Ordering::SeqCst) { - let r = stop_audiounit(stm.core_stream_data.input_unit); - assert!(r.is_ok()); - // Only fire state-changed callback for input-only stream. - // The state-changed callback for the duplex stream is fired in the output callback. - if stm.core_stream_data.output_unit.is_null() { - stm.notify_state_changed(State::Drained); - } + // If the input (input-only stream) is drained, cancel this callback. Whenever an output + // is involved, the output callback handles stopping all units and notifying of state. + if stm.core_stream_data.output_unit.is_null() && stm.draining.load(Ordering::SeqCst) { + stm.stopped.store(true, Ordering::SeqCst); + cubeb_alog!("({:p}) Input-only drained.", stm as *const AudioUnitStream); + stm.notify_state_changed(State::Drained); + let queue = stm.queue.clone(); + // Use a new thread, through the queue, to avoid deadlock when calling + // AudioOutputUnitStop method from inside render callback + let stm_ptr = user_ptr as usize; + queue.run_async(move || { + let stm = unsafe { &mut *(stm_ptr as *mut AudioUnitStream) }; + stm.core_stream_data.stop_audiounits(); + }); } match handle { @@ -681,11 +712,17 @@ extern "C" fn audiounit_output_callback( } if stm.draining.load(Ordering::SeqCst) { - // Cancel the output callback only. For duplex stream, - // the input callback will be cancelled in its own callback. - let r = stop_audiounit(stm.core_stream_data.output_unit); - assert!(r.is_ok()); + // Cancel all callbacks. For input-only streams, the input callback handles + // cancelling itself. + stm.stopped.store(true, Ordering::SeqCst); + cubeb_alog!("({:p}) output drained.", stm as *const AudioUnitStream); stm.notify_state_changed(State::Drained); + let queue = stm.queue.clone(); + // Use a new thread, through the queue, to avoid deadlock when calling + // AudioOutputUnitStop method from inside render callback + queue.run_async(move || { + stm.core_stream_data.stop_audiounits(); + }); audiounit_make_silent(&buffers[0]); return NO_ERR; } @@ -1157,38 +1194,56 @@ fn create_audiounit(device: &device_info) -> Result<AudioUnit> { Ok(unit) } -fn create_voiceprocessing_audiounit( +fn get_voiceprocessing_audiounit( + shared_voice_processing_unit: &mut SharedVoiceProcessingUnitManager, in_device: &device_info, out_device: &device_info, -) -> Result<AudioUnit> { +) -> Result<OwningHandle<VoiceProcessingUnit>> { + debug_assert_running_serially(); assert!(in_device.flags.contains(device_flags::DEV_INPUT)); assert!(!in_device.flags.contains(device_flags::DEV_OUTPUT)); assert!(!out_device.flags.contains(device_flags::DEV_INPUT)); - assert!(out_device.flags.contains(device_flags::DEV_OUTPUT)); - - let unit = create_typed_audiounit(kAudioUnitSubType_VoiceProcessingIO)?; - if let Err(e) = set_device_to_audiounit(unit, in_device.id, AU_IN_BUS) { + let unit_handle = shared_voice_processing_unit.take_or_create(); + if let Err(e) = unit_handle { cubeb_log!( - "Failed to set in device {} to the created audiounit. Error: {}", - in_device.id, + "Failed to create shared voiceprocessing audiounit. Error: {}", e ); - dispose_audio_unit(unit); return Err(Error::error()); } + let mut unit_handle = unit_handle.unwrap(); - if let Err(e) = set_device_to_audiounit(unit, out_device.id, AU_OUT_BUS) { + if let Err(e) = set_device_to_audiounit(unit_handle.as_mut().unit, in_device.id, AU_IN_BUS) { cubeb_log!( - "Failed to set out device {} to the created audiounit. Error: {}", - out_device.id, + "Failed to set in device {} to the created audiounit. Error: {}", + in_device.id, e ); - dispose_audio_unit(unit); return Err(Error::error()); } - Ok(unit) + let has_output = out_device.id != kAudioObjectUnknown; + if let Err(e) = + enable_audiounit_scope(unit_handle.as_mut().unit, DeviceType::OUTPUT, has_output) + { + cubeb_log!("Failed to enable audiounit input scope. Error: {}", e); + return Err(Error::error()); + } + if has_output { + if let Err(e) = + set_device_to_audiounit(unit_handle.as_mut().unit, out_device.id, AU_OUT_BUS) + { + cubeb_log!( + "Failed to set out device {} to the created audiounit. Error: {}", + out_device.id, + e + ); + return Err(Error::error()); + } + } + + Ok(unit_handle) } fn enable_audiounit_scope( @@ -1276,6 +1331,31 @@ fn create_blank_audiounit() -> Result<AudioUnit> { return create_typed_audiounit(kAudioUnitSubType_RemoteIO); } +fn create_voiceprocessing_audiounit() -> Result<VoiceProcessingUnit> { + let res = create_typed_audiounit(kAudioUnitSubType_VoiceProcessingIO); + if res.is_err() { + return Err(Error::error()); + } + + match get_default_device(DeviceType::OUTPUT) { + None => { + cubeb_log!("Could not get default output device in order to undo vpio ducking"); + } + Some(id) => { + let r = audio_device_duck(id, 1.0, ptr::null_mut(), 0.5); + if r != NO_ERR { + cubeb_log!( + "Failed to undo ducking of voiceprocessing on output device {}. Proceeding... Error: {}", + id, + r + ); + } + } + }; + + res.map(|unit| VoiceProcessingUnit { unit }) +} + fn get_buffer_size(unit: AudioUnit, devtype: DeviceType) -> std::result::Result<u32, OSStatus> { assert!(!unit.is_null()); let (scope, element) = match devtype { @@ -1558,7 +1638,7 @@ fn get_range_of_sample_rates( if rates.is_empty() { return Err(String::from("No data")); } - let (mut min, mut max) = (std::f64::MAX, std::f64::MIN); + let (mut min, mut max) = (f64::MAX, f64::MIN); for rate in rates { if rate.mMaximum > max { max = rate.mMaximum; @@ -2056,7 +2136,7 @@ struct LatencyController { } impl LatencyController { - fn add_stream(&mut self, latency: u32) -> Option<u32> { + fn add_stream(&mut self, latency: u32) -> u32 { self.streams += 1; // For the 1st stream set anything within safe min-max if self.streams == 1 { @@ -2065,16 +2145,322 @@ impl LatencyController { // synthetize the clock from the callbacks, and we want the clock to update often. self.latency = Some(latency.clamp(SAFE_MIN_LATENCY_FRAMES, SAFE_MAX_LATENCY_FRAMES)); } - self.latency + self.latency.unwrap_or(latency) } - fn subtract_stream(&mut self) -> Option<u32> { + fn subtract_stream(&mut self) { self.streams -= 1; if self.streams == 0 { assert!(self.latency.is_some()); self.latency = None; } - self.latency + } +} + +// SharedStorage<T> below looks generic but has evolved to be pretty tailored +// the observed behavior of VoiceProcessingIO audio units on macOS 14. +// Some key points are: +// - Creating the first VoiceProcessingIO unit in a process takes a long time, often > 3s. +// - Creating a second VoiceProcessingIO unit in a process is significantly faster, < 1s. +// - Disposing of a VoiceProcessingIO unit when all other VoiceProcessingIO units are +// uninitialized will take significantly longer than disposing the remaining +// VoiceProcessingIO units, and will have other side effects: starting another +// VoiceProcessingIO unit after this is on par with creating the first one in the +// process, bluetooth devices will move away from the handsfree profile, etc. +// The takeaway is that there is something internal to the VoiceProcessingIO audio unit +// that is costly to create and dispose of and its creation is triggered by creation of +// the first VoiceProcessingIO unit, and its disposal is triggered by the disposal of +// the first VoiceProcessingIO unit when no other VoiceProcessingIO units are initialized. +// +// The intended behavior of SharedStorage<T> and SharedVoiceProcessingUnitManager is therefore: +// - Retain ideally just one VoiceProcessingIO unit after stream destruction, so device +// switching is fast. The benefit of retaining more than one is unclear. +// - Dispose of either all VoiceProcessingIO units, or none at all, such that the retained +// VoiceProcessingIO unit really helps speed up creating and starting the next. In practice +// this means we retain all VoiceProcessingIO units until they can all be disposed of. + +#[derive(Debug)] +struct SharedStorageInternal<T> { + // Storage for shared elements. + elements: Vec<T>, + // Number of elements in use, i.e. all elements created/taken and not recycled. + outstanding_element_count: usize, + // Used for invalidation of in-flight tasks to clear elements. + // Incremented when something takes a shared element. + generation: usize, +} + +#[derive(Debug)] +struct SharedStorage<T> { + queue: Queue, + idle_timeout: Duration, + storage: Mutex<SharedStorageInternal<T>>, +} + +impl<T: Send> SharedStorage<T> { + fn with_idle_timeout(queue: Queue, idle_timeout: Duration) -> Self { + Self { + queue, + idle_timeout, + storage: Mutex::new(SharedStorageInternal::<T> { + elements: Vec::default(), + outstanding_element_count: 0, + generation: 0, + }), + } + } + + fn take_locked(guard: &mut MutexGuard<'_, SharedStorageInternal<T>>) -> Result<T> { + if let Some(e) = guard.elements.pop() { + cubeb_log!("Taking shared element #{}.", guard.elements.len()); + guard.outstanding_element_count += 1; + guard.generation += 1; + return Ok(e); + } + + Err(Error::not_supported()) + } + + fn create_with_locked<F>( + guard: &mut MutexGuard<'_, SharedStorageInternal<T>>, + f: F, + ) -> Result<T> + where + F: FnOnce() -> Result<T>, + { + let start = Instant::now(); + match f() { + Ok(obj) => { + cubeb_log!( + "Just created shared element #{}. Took {}s.", + guard.outstanding_element_count, + (Instant::now() - start).as_secs_f32() + ); + guard.outstanding_element_count += 1; + guard.generation += 1; + Ok(obj) + } + Err(_) => { + cubeb_log!("Creating shared element failed"); + Err(Error::error()) + } + } + } + + #[cfg(test)] + fn take(&self) -> Result<T> { + let mut guard = self.storage.lock().unwrap(); + SharedStorage::take_locked(&mut guard) + } + + fn take_or_create_with<F>(&self, f: F) -> Result<T> + where + F: FnOnce() -> Result<T>, + { + let mut guard = self.storage.lock().unwrap(); + SharedStorage::take_locked(&mut guard) + .or_else(|_| SharedStorage::create_with_locked(&mut guard, f)) + } + + fn recycle(&self, obj: T) { + let mut guard = self.storage.lock().unwrap(); + guard.outstanding_element_count -= 1; + cubeb_log!( + "Recycling shared element #{}. Nr of live elements now {}.", + guard.elements.len(), + guard.outstanding_element_count + ); + guard.elements.push(obj); + } + + fn clear_locked(guard: &mut MutexGuard<'_, SharedStorageInternal<T>>) { + let count = guard.elements.len(); + let start = Instant::now(); + guard.elements.clear(); + cubeb_log!( + "Cleared {} shared element{}. Took {}s.", + count, + if count == 1 { "" } else { "s" }, + (Instant::now() - start).as_secs_f32() + ); + } + + fn clear(&self) { + debug_assert_running_serially(); + let mut guard = self.storage.lock().unwrap(); + SharedStorage::clear_locked(&mut guard); + } + + fn clear_if_all_idle_async(storage: &Arc<SharedStorage<T>>) { + let (queue, outstanding_element_count, generation) = { + let guard = storage.storage.lock().unwrap(); + ( + storage.queue.clone(), + guard.outstanding_element_count, + guard.generation, + ) + }; + if outstanding_element_count > 0 { + cubeb_log!( + "Not clearing shared voiceprocessing unit storage because {} elements are in use. Generation={}.", + outstanding_element_count, + generation + ); + return; + } + cubeb_log!( + "Clearing shared voiceprocessing unit storage in {}s if still at generation {}.", + storage.idle_timeout.as_secs_f32(), + generation + ); + let storage = storage.clone(); + queue.run_after(Instant::now() + storage.idle_timeout, move || { + let mut guard = storage.storage.lock().unwrap(); + if generation != guard.generation { + cubeb_log!( + "Not clearing shared voiceprocessing unit storage for generation {} as we're now at {}.", + generation, + guard.generation + ); + return; + } + SharedStorage::clear_locked(&mut guard); + }); + } +} + +#[derive(Debug)] +struct OwningHandle<T> +where + T: Send, +{ + storage: Weak<SharedStorage<T>>, + obj: Option<T>, +} + +impl<T: Send> OwningHandle<T> { + fn new(storage: Weak<SharedStorage<T>>, obj: T) -> Self { + Self { + storage, + obj: Some(obj), + } + } +} + +impl<T: Send> AsRef<T> for OwningHandle<T> { + fn as_ref(&self) -> &T { + self.obj.as_ref().unwrap() + } +} + +impl<T: Send> AsMut<T> for OwningHandle<T> { + fn as_mut(&mut self) -> &mut T { + self.obj.as_mut().unwrap() + } +} + +impl<T: Send> Drop for OwningHandle<T> { + fn drop(&mut self) { + let storage = self.storage.upgrade(); + assert!( + storage.is_some(), + "Storage must outlive the handle, but didn't" + ); + let storage = storage.unwrap(); + if self.obj.is_none() { + return; + } + let obj = self.obj.take().unwrap(); + storage.recycle(obj); + SharedStorage::clear_if_all_idle_async(&storage); + } +} + +#[derive(Debug)] +struct VoiceProcessingUnit { + unit: AudioUnit, +} + +impl Drop for VoiceProcessingUnit { + fn drop(&mut self) { + assert!(!self.unit.is_null()); + dispose_audio_unit(self.unit); + } +} + +unsafe impl Send for VoiceProcessingUnit {} + +#[derive(Debug)] +struct SharedVoiceProcessingUnitManager { + sync_storage: Mutex<Option<Arc<SharedStorage<VoiceProcessingUnit>>>>, + queue: Queue, + idle_timeout: Duration, +} + +impl SharedVoiceProcessingUnitManager { + fn with_idle_timeout(queue: Queue, idle_timeout: Duration) -> Self { + Self { + sync_storage: Mutex::new(None), + queue, + idle_timeout, + } + } + + fn new(queue: Queue) -> Self { + SharedVoiceProcessingUnitManager::with_idle_timeout(queue, VPIO_IDLE_TIMEOUT) + } + + fn ensure_storage_locked( + &self, + guard: &mut MutexGuard<Option<Arc<SharedStorage<VoiceProcessingUnit>>>>, + ) { + if guard.is_some() { + return; + } + cubeb_log!("Creating shared voiceprocessing storage."); + let storage = SharedStorage::<VoiceProcessingUnit>::with_idle_timeout( + self.queue.clone(), + self.idle_timeout, + ); + let old_storage = guard.replace(Arc::from(storage)); + assert!(old_storage.is_none()); + } + + // Take an already existing, shared, vpio unit, if one is available. + #[cfg(test)] + fn take(&mut self) -> Result<OwningHandle<VoiceProcessingUnit>> { + debug_assert_running_serially(); + let mut guard = self.sync_storage.lock().unwrap(); + self.ensure_storage_locked(&mut guard); + let storage = guard.as_mut().unwrap(); + let res = storage.take(); + res.map(|u| OwningHandle::new(Arc::downgrade(storage), u)) + } + + // Take an already existing, shared, vpio unit, or create one if none are available. + fn take_or_create(&mut self) -> Result<OwningHandle<VoiceProcessingUnit>> { + debug_assert_running_serially(); + let mut guard = self.sync_storage.lock().unwrap(); + self.ensure_storage_locked(&mut guard); + let storage = guard.as_mut().unwrap(); + let res = storage.take_or_create_with(create_voiceprocessing_audiounit); + res.map(|u| OwningHandle::new(Arc::downgrade(storage), u)) + } +} + +unsafe impl Send for SharedVoiceProcessingUnitManager {} +unsafe impl Sync for SharedVoiceProcessingUnitManager {} + +impl Drop for SharedVoiceProcessingUnitManager { + fn drop(&mut self) { + debug_assert_not_running_serially(); + self.queue.run_final(|| { + let mut guard = self.sync_storage.lock().unwrap(); + if guard.is_none() { + return; + } + guard.as_mut().unwrap().clear(); + }); } } @@ -2091,15 +2477,26 @@ pub struct AudioUnitContext { serial_queue: Queue, latency_controller: Mutex<LatencyController>, devices: Mutex<SharedDevices>, + // Storage for a context-global vpio unit. Duplex streams that need one will take this + // and return it when done. + shared_voice_processing_unit: SharedVoiceProcessingUnitManager, } impl AudioUnitContext { fn new() -> Self { + let queue_label = format!("{}.context", DISPATCH_QUEUE_LABEL); + let serial_queue = + Queue::new_with_target(queue_label.as_str(), get_serial_queue_singleton()); + let shared_vp_queue = Queue::new_with_target( + format!("{}.context.shared_vpio", DISPATCH_QUEUE_LABEL).as_str(), + &serial_queue, + ); Self { _ops: &OPS as *const _, - serial_queue: Queue::new(DISPATCH_QUEUE_LABEL), + serial_queue, latency_controller: Mutex::new(LatencyController::default()), devices: Mutex::new(SharedDevices::default()), + shared_voice_processing_unit: SharedVoiceProcessingUnitManager::new(shared_vp_queue), } } @@ -2108,14 +2505,14 @@ impl AudioUnitContext { controller.streams } - fn update_latency_by_adding_stream(&self, latency_frames: u32) -> Option<u32> { + fn update_latency_by_adding_stream(&self, latency_frames: u32) -> u32 { let mut controller = self.latency_controller.lock().unwrap(); controller.add_stream(latency_frames) } - fn update_latency_by_removing_stream(&self) -> Option<u32> { + fn update_latency_by_removing_stream(&self) { let mut controller = self.latency_controller.lock().unwrap(); - controller.subtract_stream() + controller.subtract_stream(); } fn add_devices_changed_listener( @@ -2228,8 +2625,16 @@ impl AudioUnitContext { impl ContextOps for AudioUnitContext { fn init(_context_name: Option<&CStr>) -> Result<Context> { - set_notification_runloop(); - let ctx = Box::new(AudioUnitContext::new()); + run_serially(set_notification_runloop); + let mut ctx = Box::new(AudioUnitContext::new()); + let queue_label = format!("{}.context.{:p}", DISPATCH_QUEUE_LABEL, ctx.as_ref()); + ctx.serial_queue = + Queue::new_with_target(queue_label.as_str(), get_serial_queue_singleton()); + let shared_vp_queue = Queue::new_with_target( + format!("{}.shared_vpio", queue_label).as_str(), + &ctx.serial_queue, + ); + ctx.shared_voice_processing_unit = SharedVoiceProcessingUnitManager::new(shared_vp_queue); Ok(unsafe { Context::from_ptr(Box::into_raw(ctx) as *mut _) }) } @@ -2379,28 +2784,18 @@ impl ContextOps for AudioUnitContext { return Err(Error::invalid_parameter()); } - // Latency cannot change if another stream is operating in parallel. In this case - // latency is set to the other stream value. - let global_latency_frames = self - .update_latency_by_adding_stream(latency_frames) - .unwrap(); - if global_latency_frames != latency_frames { - cubeb_log!( - "Use global latency {} instead of the requested latency {}.", - global_latency_frames, - latency_frames - ); - } - let in_stm_settings = if let Some(params) = input_stream_params { - let in_device = - match create_device_info(input_device as AudioDeviceID, DeviceType::INPUT) { - None => { - cubeb_log!("Fail to create device info for input"); - return Err(Error::error()); - } - Some(d) => d, - }; + let in_device = match self + .serial_queue + .run_sync(|| create_device_info(input_device as AudioDeviceID, DeviceType::INPUT)) + .unwrap() + { + None => { + cubeb_log!("Fail to create device info for input"); + return Err(Error::error()); + } + Some(d) => d, + }; let stm_params = StreamParams::from(unsafe { *params.as_ptr() }); Some((stm_params, in_device)) } else { @@ -2408,20 +2803,34 @@ impl ContextOps for AudioUnitContext { }; let out_stm_settings = if let Some(params) = output_stream_params { - let out_device = - match create_device_info(output_device as AudioDeviceID, DeviceType::OUTPUT) { - None => { - cubeb_log!("Fail to create device info for output"); - return Err(Error::error()); - } - Some(d) => d, - }; + let out_device = match self + .serial_queue + .run_sync(|| create_device_info(output_device as AudioDeviceID, DeviceType::OUTPUT)) + .unwrap() + { + None => { + cubeb_log!("Fail to create device info for output"); + return Err(Error::error()); + } + Some(d) => d, + }; let stm_params = StreamParams::from(unsafe { *params.as_ptr() }); Some((stm_params, out_device)) } else { None }; + // Latency cannot change if another stream is operating in parallel. In this case + // latency is set to the other stream value. + let global_latency_frames = self.update_latency_by_adding_stream(latency_frames); + if global_latency_frames != latency_frames { + cubeb_log!( + "Use global latency {} instead of the requested latency {}.", + global_latency_frames, + latency_frames + ); + } + let mut boxed_stream = Box::new(AudioUnitStream::new( self, user_ptr, @@ -2431,16 +2840,25 @@ impl ContextOps for AudioUnitContext { )); // Rename the task queue to be an unique label. - let queue_label = format!("{}.{:p}", DISPATCH_QUEUE_LABEL, boxed_stream.as_ref()); - boxed_stream.queue = Queue::new(queue_label.as_str()); + let queue_label = format!( + "{}.stream.{:p}", + DISPATCH_QUEUE_LABEL, + boxed_stream.as_ref() + ); + boxed_stream.queue = Queue::new_with_target(queue_label.as_str(), &boxed_stream.queue); boxed_stream.core_stream_data = CoreStreamData::new(boxed_stream.as_ref(), in_stm_settings, out_stm_settings); - let mut result = Ok(()); - boxed_stream.queue.clone().run_sync(|| { - result = boxed_stream.core_stream_data.setup(); - }); + let result = boxed_stream + .queue + .clone() + .run_sync(|| { + boxed_stream + .core_stream_data + .setup(&mut boxed_stream.context.shared_voice_processing_unit) + }) + .unwrap(); if let Err(r) = result { cubeb_log!( "({:p}) Could not setup the audiounit stream.", @@ -2465,25 +2883,43 @@ impl ContextOps for AudioUnitContext { if devtype == DeviceType::UNKNOWN { return Err(Error::invalid_parameter()); } - if collection_changed_callback.is_some() { - self.add_devices_changed_listener(devtype, collection_changed_callback, user_ptr) - } else { - self.remove_devices_changed_listener(devtype) - } + self.serial_queue + .clone() + .run_sync(|| { + if collection_changed_callback.is_some() { + self.add_devices_changed_listener( + devtype, + collection_changed_callback, + user_ptr, + ) + } else { + self.remove_devices_changed_listener(devtype) + } + }) + .unwrap() } } impl Drop for AudioUnitContext { fn drop(&mut self) { - let devices = self.devices.lock().unwrap(); - assert!( + assert!({ + let devices = self.devices.lock().unwrap(); devices.input.changed_callback.is_none() && devices.output.changed_callback.is_none() - ); + }); + + self.shared_voice_processing_unit = + SharedVoiceProcessingUnitManager::new(self.serial_queue.clone()); + + // Make sure all the pending (device-collection-changed-callback) tasks + // in queue are done, and cancel all the tasks appended after `drop` is executed. + let queue = self.serial_queue.clone(); + queue.run_final(|| {}); { let controller = self.latency_controller.lock().unwrap(); - // Disabling this assert for bug 1083664 -- we seem to leak a stream + // Disabling this assert in release for bug 1083664 -- we seem to leak a stream // assert(controller.streams == 0); + debug_assert!(controller.streams == 0); if controller.streams > 0 { cubeb_log!( "({:p}) API misuse, {} streams active when context destroyed!", @@ -2492,10 +2928,6 @@ impl Drop for AudioUnitContext { ); } } - // Make sure all the pending (device-collection-changed-callback) tasks - // in queue are done, and cancel all the tasks appended after `drop` is executed. - let queue = self.serial_queue.clone(); - queue.run_final(|| {}); } } @@ -2562,6 +2994,8 @@ struct CoreStreamData<'ctx> { // I/O AudioUnits. input_unit: AudioUnit, output_unit: AudioUnit, + // Handle to shared voiceprocessing AudioUnit, if in use. + voiceprocessing_unit_handle: Option<OwningHandle<VoiceProcessingUnit>>, // Info of the I/O devices. input_device: device_info, output_device: device_info, @@ -2603,6 +3037,7 @@ impl<'ctx> Default for CoreStreamData<'ctx> { output_dev_desc: AudioStreamBasicDescription::default(), input_unit: ptr::null_mut(), output_unit: ptr::null_mut(), + voiceprocessing_unit_handle: None, input_device: device_info::default(), output_device: device_info::default(), input_processing_params: InputProcessingParams::NONE, @@ -2649,6 +3084,7 @@ impl<'ctx> CoreStreamData<'ctx> { output_dev_desc: AudioStreamBasicDescription::default(), input_unit: ptr::null_mut(), output_unit: ptr::null_mut(), + voiceprocessing_unit_handle: None, input_device: in_dev, output_device: out_dev, input_processing_params: InputProcessingParams::NONE, @@ -2716,7 +3152,7 @@ impl<'ctx> CoreStreamData<'ctx> { } fn using_voice_processing_unit(&self) -> bool { - !self.input_unit.is_null() && self.input_unit == self.output_unit + self.voiceprocessing_unit_handle.is_some() } fn same_clock_domain(&self) -> bool { @@ -2744,6 +3180,22 @@ impl<'ctx> CoreStreamData<'ctx> { input_domain == output_domain } + #[allow(non_upper_case_globals)] + fn should_force_vpio_for_input_device(&self, in_device: &device_info) -> bool { + assert!(in_device.id != kAudioObjectUnknown); + self.debug_assert_is_on_stream_queue(); + match get_device_transport_type(in_device.id, DeviceType::INPUT) { + Ok(kAudioDeviceTransportTypeBuiltIn) => { + cubeb_log!( + "Forcing VPIO because input device is built in, and its volume \ + is known to be very low without VPIO whenever VPIO is hooked up to it elsewhere." + ); + true + } + _ => false, + } + } + fn should_block_vpio_for_device_pair( &self, in_device: &device_info, @@ -2751,7 +3203,10 @@ impl<'ctx> CoreStreamData<'ctx> { ) -> bool { self.debug_assert_is_on_stream_queue(); cubeb_log!("Evaluating device pair against VPIO block list"); - let log_device = |id, devtype| -> std::result::Result<(), OSStatus> { + let log_device_and_get_model_uid = |id, devtype| -> String { + let device_model_uid = get_device_model_uid(id, devtype) + .map(|s| s.into_string()) + .unwrap_or_default(); cubeb_log!("{} uid=\"{}\", model_uid=\"{}\", transport_type={:?}, source={:?}, source_name=\"{}\", name=\"{}\", manufacturer=\"{}\"", if devtype == DeviceType::INPUT { "Input" @@ -2760,43 +3215,59 @@ impl<'ctx> CoreStreamData<'ctx> { "Output" }, get_device_uid(id, devtype).map(|s| s.into_string()).unwrap_or_default(), - get_device_model_uid(id, devtype).map(|s| s.into_string()).unwrap_or_default(), + device_model_uid, convert_uint32_into_string(get_device_transport_type(id, devtype).unwrap_or(0)), convert_uint32_into_string(get_device_source(id, devtype).unwrap_or(0)), get_device_source_name(id, devtype).map(|s| s.into_string()).unwrap_or_default(), get_device_name(id, devtype).map(|s| s.into_string()).unwrap_or_default(), get_device_manufacturer(id, devtype).map(|s| s.into_string()).unwrap_or_default()); - Ok(()) + device_model_uid }; - log_device(in_device.id, DeviceType::INPUT); - log_device(out_device.id, DeviceType::OUTPUT); - match ( - get_device_model_uid(in_device.id, DeviceType::INPUT).map(|s| s.to_string()), - get_device_model_uid(out_device.id, DeviceType::OUTPUT).map(|s| s.to_string()), - ) { - (Ok(in_model_uid), Ok(out_model_uid)) - if in_model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID) - && out_model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID) => - { - cubeb_log!("Both input and output device is an Apple Studio Display. BLOCKED"); - true - } - _ => { - cubeb_log!("Device pair is not blocked"); - false - } + + #[allow(non_upper_case_globals)] + let in_id = match in_device.id { + kAudioObjectUnknown => None, + id => Some(id), + }; + #[allow(non_upper_case_globals)] + let out_id = match out_device.id { + kAudioObjectUnknown => None, + id => Some(id), + }; + + let (in_model_uid, out_model_uid) = ( + in_id + .map(|id| log_device_and_get_model_uid(id, DeviceType::INPUT)) + .unwrap_or_default(), + out_id + .map(|id| log_device_and_get_model_uid(id, DeviceType::OUTPUT)) + .unwrap_or_default(), + ); + + if in_model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID) + && out_model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID) + { + cubeb_log!("Both input and output device is an Apple Studio Display. BLOCKED"); + return true; } + + cubeb_log!("Device pair is not blocked"); + false } - fn create_audiounits(&mut self) -> Result<(device_info, device_info)> { + fn create_audiounits( + &mut self, + shared_voice_processing_unit: &mut SharedVoiceProcessingUnitManager, + ) -> Result<(device_info, device_info)> { self.debug_assert_is_on_stream_queue(); let should_use_voice_processing_unit = self.has_input() - && self.has_output() - && self + && (self .input_stream_params .prefs() .contains(StreamPrefs::VOICE) - && !self.should_block_vpio_for_device_pair(&self.input_device, &self.output_device); + || self.should_force_vpio_for_input_device(&self.input_device)) + && !self.should_block_vpio_for_device_pair(&self.input_device, &self.output_device) + && macos_kernel_major_version() != Ok(MACOS_KERNEL_MAJOR_VERSION_MONTEREY); let should_use_aggregate_device = { // It's impossible to create an aggregate device from an aggregate device, and it's @@ -2843,16 +3314,20 @@ impl<'ctx> CoreStreamData<'ctx> { // - As last resort, create regular AudioUnits. This is also the normal non-duplex path. if should_use_voice_processing_unit { - if let Ok(au) = - create_voiceprocessing_audiounit(&self.input_device, &self.output_device) - { - cubeb_log!("({:p}) Using VoiceProcessingIO AudioUnit", self.stm_ptr); - self.input_unit = au; - self.output_unit = au; + if let Ok(mut au_handle) = get_voiceprocessing_audiounit( + shared_voice_processing_unit, + &self.input_device, + &self.output_device, + ) { + self.input_unit = au_handle.as_mut().unit; + if self.has_output() { + self.output_unit = au_handle.as_mut().unit; + } + self.voiceprocessing_unit_handle = Some(au_handle); return Ok((self.input_device.clone(), self.output_device.clone())); } cubeb_log!( - "({:p}) Failed to create VoiceProcessingIO AudioUnit. Trying a regular one.", + "({:p}) Failed to get VoiceProcessingIO AudioUnit. Trying a regular one.", self.stm_ptr ); } @@ -2954,7 +3429,10 @@ impl<'ctx> CoreStreamData<'ctx> { } #[allow(clippy::cognitive_complexity)] // TODO: Refactoring. - fn setup(&mut self) -> Result<()> { + fn setup( + &mut self, + shared_voice_processing_unit: &mut SharedVoiceProcessingUnitManager, + ) -> Result<()> { self.debug_assert_is_on_stream_queue(); if self .input_stream_params @@ -2970,7 +3448,7 @@ impl<'ctx> CoreStreamData<'ctx> { } let same_clock_domain = self.same_clock_domain(); - let (in_dev_info, out_dev_info) = self.create_audiounits()?; + let (in_dev_info, out_dev_info) = self.create_audiounits(shared_voice_processing_unit)?; let using_voice_processing_unit = self.using_voice_processing_unit(); assert!(!self.stm_ptr.is_null()); @@ -3155,6 +3633,25 @@ impl<'ctx> CoreStreamData<'ctx> { ); } + if self.has_input() && !self.has_output() && using_voice_processing_unit { + // We must configure the output side of VPIO to match the input side, even if we don't use it. + let r = audio_unit_set_property( + self.input_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + AU_OUT_BUS, + &self.input_dev_desc, + mem::size_of::<AudioStreamBasicDescription>(), + ); + if r != NO_ERR { + cubeb_log!( + "AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv={}", + r + ); + return Err(Error::error()); + } + } + if self.has_output() { assert!(!self.output_unit.is_null()); @@ -3463,15 +3960,31 @@ impl<'ctx> CoreStreamData<'ctx> { // NOTE: On MacOS 14 the ducking happens on creation of the VPIO AudioUnit. // On MacOS 10.15 it happens on both creation and initialization, which // is why we defer the unducking until now. - let r = audio_device_duck(self.output_device.id, 1.0, ptr::null_mut(), 0.5); - if r != NO_ERR { - cubeb_log!( - "({:p}) Failed to undo ducking of voiceprocessing on output device {}. Proceeding... Error: {}", - self.stm_ptr, - self.output_device.id, - r - ); - } + #[allow(non_upper_case_globals)] + let mut device = match self.output_device.id { + kAudioObjectUnknown => None, + id => Some(id), + }; + device = device.or_else(|| get_default_device(DeviceType::OUTPUT)); + match device { + None => { + cubeb_log!( + "({:p}) No output device to undo vpio ducking on", + self.stm_ptr + ); + } + Some(id) => { + let r = audio_device_duck(id, 1.0, ptr::null_mut(), 0.5); + if r != NO_ERR { + cubeb_log!( + "({:p}) Failed to undo ducking of voiceprocessing on output device {}. Proceeding... Error: {}", + self.stm_ptr, + id, + r + ); + } + } + }; // Always try to remember the applied input mute state. If it cannot be applied // to the new device pair, we notify the client of an error and it will have to @@ -3558,10 +4071,16 @@ impl<'ctx> CoreStreamData<'ctx> { } if !self.input_unit.is_null() { - dispose_audio_unit(self.input_unit); + if !self.using_voice_processing_unit() { + // The VPIO unit is shared and must not be disposed. + dispose_audio_unit(self.input_unit); + } self.input_unit = ptr::null_mut(); } + // Return the VPIO unit if present. + self.voiceprocessing_unit_handle = None; + self.resampler.destroy(); self.mixer = None; self.aggregate_device = None; @@ -3873,8 +4392,6 @@ struct OutputCallbackTimingData { // #[repr(C)] is used to prevent any padding from being added in the beginning of the AudioUnitStream. #[repr(C)] #[derive(Debug)] -// Allow exposing this private struct in public interfaces when running tests. -#[cfg_attr(test, allow(private_in_public))] struct AudioUnitStream<'ctx> { context: &'ctx mut AudioUnitContext, user_ptr: *mut c_void, @@ -3927,10 +4444,11 @@ impl<'ctx> AudioUnitStream<'ctx> { }); let (output_callback_timing_data_write, output_callback_timing_data_read) = output_callback_timing_data.split(); + let queue = context.serial_queue.clone(); AudioUnitStream { context, user_ptr, - queue: Queue::new(DISPATCH_QUEUE_LABEL), + queue, data_callback, state_callback, device_changed_callback: Mutex::new(None), @@ -4046,10 +4564,12 @@ impl<'ctx> AudioUnitStream<'ctx> { } } - self.core_stream_data.setup().map_err(|e| { - cubeb_log!("({:p}) Setup failed.", self.core_stream_data.stm_ptr); - e - })?; + self.core_stream_data + .setup(&mut self.context.shared_voice_processing_unit) + .map_err(|e| { + cubeb_log!("({:p}) Setup failed.", self.core_stream_data.stm_ptr); + e + })?; if let Ok(volume) = vol_rv { set_volume(self.core_stream_data.output_unit, volume); @@ -4171,7 +4691,7 @@ impl<'ctx> AudioUnitStream<'ctx> { impl<'ctx> Drop for AudioUnitStream<'ctx> { fn drop(&mut self) { // Execute destroy in serial queue to avoid collision with reinit when un/plug devices - self.queue.clone().run_final(move || { + self.queue.clone().run_final(|| { self.destroy(); self.core_stream_data = CoreStreamData::default(); }); @@ -4184,12 +4704,10 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> { self.draining.store(false, Ordering::SeqCst); // Execute start in serial queue to avoid racing with destroy or reinit. - let mut result = Err(Error::error()); - let started = &mut result; - let stream = &self; - self.queue.run_sync(move || { - *started = stream.core_stream_data.start_audiounits(); - }); + let result = self + .queue + .run_sync(|| self.core_stream_data.start_audiounits()) + .unwrap(); result?; @@ -4205,10 +4723,8 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> { self.stopped.store(true, Ordering::SeqCst); // Execute stop in serial queue to avoid racing with destroy or reinit. - let stream = &self; - self.queue.run_sync(move || { - stream.core_stream_data.stop_audiounits(); - }); + self.queue + .run_sync(|| self.core_stream_data.stop_audiounits()); self.notify_state_changed(State::Stopped); @@ -4285,12 +4801,10 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> { } fn set_volume(&mut self, volume: f32) -> Result<()> { // Execute set_volume in serial queue to avoid racing with destroy or reinit. - let mut result = Err(Error::error()); - let set = &mut result; - let stream = &self; - self.queue.run_sync(move || { - *set = set_volume(stream.core_stream_data.output_unit, volume); - }); + let result = self + .queue + .run_sync(|| set_volume(self.core_stream_data.output_unit, volume)) + .unwrap(); result?; 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); |