diff --git a/.cargo/config.toml.in b/.cargo/config.toml.in index 65ce349d5302..7af3bdbc47bd 100644 --- a/.cargo/config.toml.in +++ b/.cargo/config.toml.in @@ -70,9 +70,9 @@ git = "https://github.com/mozilla/audioipc" rev = "3495905752a4263827f5d43737f9ca3ed0243ce0" replace-with = "vendored-sources" -[source."git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=4cc24c7dc74a4801455ba0bbe20ea67a49f28514"] +[source."git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=1796ace5bdd08ec8baa56bbf7170a08d760c984b"] git = "https://github.com/mozilla/cubeb-coreaudio-rs" -rev = "4cc24c7dc74a4801455ba0bbe20ea67a49f28514" +rev = "1796ace5bdd08ec8baa56bbf7170a08d760c984b" replace-with = "vendored-sources" [source."git+https://github.com/mozilla/cubeb-pulse-rs?rev=8678dcab1c287de79c4c184ccc2e065bc62b70e2"] diff --git a/Cargo.lock b/Cargo.lock index 6adc553757da..f2757c00fb51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -921,7 +921,7 @@ dependencies = [ [[package]] name = "coreaudio-sys-utils" version = "0.1.0" -source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=4cc24c7dc74a4801455ba0bbe20ea67a49f28514#4cc24c7dc74a4801455ba0bbe20ea67a49f28514" +source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=1796ace5bdd08ec8baa56bbf7170a08d760c984b#1796ace5bdd08ec8baa56bbf7170a08d760c984b" dependencies = [ "core-foundation-sys", "coreaudio-sys", @@ -1169,7 +1169,7 @@ dependencies = [ [[package]] name = "cubeb-coreaudio" version = "0.1.0" -source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=4cc24c7dc74a4801455ba0bbe20ea67a49f28514#4cc24c7dc74a4801455ba0bbe20ea67a49f28514" +source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=1796ace5bdd08ec8baa56bbf7170a08d760c984b#1796ace5bdd08ec8baa56bbf7170a08d760c984b" dependencies = [ "atomic", "audio-mixer", diff --git a/third_party/rust/cubeb-coreaudio/.cargo-checksum.json b/third_party/rust/cubeb-coreaudio/.cargo-checksum.json index 321c03d4dcfa..3e8be83de343 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":"cf6ebe6d41b022897360866b526d19ba8843aa82ae99a1d28393985576b6a782",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"2698cf87581d8d551ed3ac5875564720ed23d7b788e8d145d4281c8026203cd2","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":"6e94c36c09081a728b1ab748b460fe8f538cf5f50bc62fd47171a393fe2d609a","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"e9bcf964347daa8952f98caa2746e34a31ea8908375204896593f56e4b6147ca","src/backend/device_property.rs":"0714b90c3187b0b1709f5e4b7757e1b434659276e00db48a3f3270fbfd429640","src/backend/mixer.rs":"c4d09291598cbffb2217b551770ec590f34b6dd6b461dd99b019d5bb70f0eef3","src/backend/mod.rs":"48380d5a273fc3ff8b597e5974fc00361c66b512b1d95b6c15a6cea332a11a57","src/backend/resampler.rs":"48bf8f56ae8d60dbabca6417b768000619abee8731ac3902164b45651ac08a4d","src/backend/tests/aggregate_device.rs":"48e291b355a7c0c643fc58e9d238ed00234b4f1ac0f4c26737cc74862d4f2ac8","src/backend/tests/api.rs":"ef3babcd3410394b8d5bcdaf0ea526486b14d8e42f33211997aafe179430bf4a","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":"cd58614435574444d8a1f039dc201cf371cccacd58efbae8ed8fbff919550d0a","src/backend/tests/manual.rs":"f72625c05110534775c4608ccc45472ea108286657ffc1f029844a13d0b883bf","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"a7ebd579339c40ca64c0757cc9da6baec641e670f226e1b2ec5049894700bd7a","src/backend/tests/tone.rs":"b028c67777b6453a26190b6a49785dfe28556adcbe179cb10862ce0d47ee8509","src/backend/tests/utils.rs":"21c8e7f6f18da0f8d33733ad0fc981041b43586db6a637c3f7aec7e7b3936aed","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":"cf6ebe6d41b022897360866b526d19ba8843aa82ae99a1d28393985576b6a782",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"2698cf87581d8d551ed3ac5875564720ed23d7b788e8d145d4281c8026203cd2","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":"1403232694fabeae004179be8399d1fe2a1b100d60cd90db37d8860eddbaf2ae","run_sanitizers.sh":"84e93a0da137803018f37403511e8c92760be730426bf6cea34419d93d1a7ff8","run_tests.sh":"bae82f66dd47a060b6fdcc238520084aec1079d5b1b1d66d103baa1ffaa8773d","src/backend/aggregate_device.rs":"a910b9d596b1971cb4fee34f5030809ade584f41eb5cbad73a09abe7352ebd15","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"e9bcf964347daa8952f98caa2746e34a31ea8908375204896593f56e4b6147ca","src/backend/device_property.rs":"4719226a5fb3b5697d4624ccf1b15f0d522ddbc3f64a98fba47d9c55c5aeee36","src/backend/mixer.rs":"c4d09291598cbffb2217b551770ec590f34b6dd6b461dd99b019d5bb70f0eef3","src/backend/mod.rs":"c13d293223402bdb53451e6587a3de8a22be36d6e944baba88bda3977d2cebd9","src/backend/resampler.rs":"48bf8f56ae8d60dbabca6417b768000619abee8731ac3902164b45651ac08a4d","src/backend/tests/aggregate_device.rs":"afbdf1da1fcaddcad2986bd3146bf93ca75c24b3362f5f23a09517a926290ca2","src/backend/tests/api.rs":"3b0936810b3afa84cb80428c471e1097701fd790460d00c0a5715fd8026d0a4d","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"babf50326fb38db24fe80f24f546e1b6ad04319ae8835bb372d893fc9b3038a2","src/backend/tests/device_property.rs":"4ef3ab625809fe95e944c19cc5dc1cc79f473520a4314d123b1f80c6b7e11411","src/backend/tests/interfaces.rs":"cd58614435574444d8a1f039dc201cf371cccacd58efbae8ed8fbff919550d0a","src/backend/tests/manual.rs":"f72625c05110534775c4608ccc45472ea108286657ffc1f029844a13d0b883bf","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"a7ebd579339c40ca64c0757cc9da6baec641e670f226e1b2ec5049894700bd7a","src/backend/tests/tone.rs":"b028c67777b6453a26190b6a49785dfe28556adcbe179cb10862ce0d47ee8509","src/backend/tests/utils.rs":"3e435569798b883db8342137098832b88837a387008852005363f74e5e6ff18e","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/run_device_tests.sh b/third_party/rust/cubeb-coreaudio/run_device_tests.sh index 69bb3385f475..73b1cd24916e 100755 --- a/third_party/rust/cubeb-coreaudio/run_device_tests.sh +++ b/third_party/rust/cubeb-coreaudio/run_device_tests.sh @@ -8,10 +8,15 @@ if [[ -z "${RUST_BACKTRACE}" ]]; then fi echo "RUST_BACKTRACE is set to ${RUST_BACKTRACE}\n" +cargo test test_aggregate -- --ignored --nocapture + cargo test test_switch_device -- --ignored --nocapture cargo test test_plug_and_unplug_device -- --ignored --nocapture +cargo test test_get_channel_count_of_input_devices_with_vpio -- --ignored --nocapture +cargo test test_get_channel_count_of_input_devices_with_aggregate_device_and_vpio -- --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 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 d0f71a73a181..2174421ba5f9 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs @@ -185,9 +185,7 @@ impl AggregateDevice { let (lock, cvar) = &*condvar_pair; let guard = lock.lock().unwrap(); let (_guard, timeout_res) = cvar - .wait_timeout_while(guard, waiting_time, |()| { - !audiounit_get_devices().contains(&device) - }) + .wait_timeout_while(guard, waiting_time, |()| !get_devices().contains(&device)) .unwrap(); if timeout_res.timed_out() { cubeb_log!( @@ -396,8 +394,8 @@ impl AggregateDevice { assert_ne!(input_id, output_id); debug_assert_running_serially(); - let output_sub_devices = Self::get_sub_devices(output_id)?; - let input_sub_devices = Self::get_sub_devices(input_id)?; + let output_sub_devices = Self::get_sub_devices_or_self(output_id)?; + let input_sub_devices = Self::get_sub_devices_or_self(input_id)?; unsafe { let sub_devices = CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks); @@ -445,16 +443,10 @@ impl AggregateDevice { let mut size: usize = 0; let status = audio_object_get_property_data_size(device_id, &address, &mut size); - if status == kAudioHardwareUnknownPropertyError as OSStatus { - // Return a vector containing the device itself if the device has no sub devices. - sub_devices.push(device_id); - return Ok(sub_devices); - } else if status != NO_ERR { + if status != NO_ERR { return Err(Error::from(status)); } - assert_ne!(size, 0); - let count = size / mem::size_of::(); sub_devices = allocate_array(count); let status = audio_object_get_property_data( @@ -471,6 +463,17 @@ impl AggregateDevice { } } + pub fn get_sub_devices_or_self( + device_id: AudioDeviceID, + ) -> std::result::Result, Error> { + AggregateDevice::get_sub_devices(device_id).or_else(|e| match e { + Error::OS(status) if status == kAudioHardwareUnknownPropertyError as OSStatus => { + Ok(vec![device_id]) + } + _ => Err(e), + }) + } + pub fn get_master_device_uid(device_id: AudioDeviceID) -> std::result::Result { debug_assert_running_serially(); let address = AudioObjectPropertyAddress { @@ -515,7 +518,7 @@ impl AggregateDevice { }; // The master device will be the 1st sub device of the primary device. - let output_sub_devices = Self::get_sub_devices(primary_id)?; + let output_sub_devices = Self::get_sub_devices_or_self(primary_id)?; assert!(!output_sub_devices.is_empty()); let master_sub_device_uid = get_device_global_uid(output_sub_devices[0]).unwrap(); let master_sub_device = master_sub_device_uid.get_raw(); diff --git a/third_party/rust/cubeb-coreaudio/src/backend/device_property.rs b/third_party/rust/cubeb-coreaudio/src/backend/device_property.rs index 6a85f22359aa..18dadefcbba7 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/device_property.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/device_property.rs @@ -18,6 +18,32 @@ pub fn get_device_uid( } } +pub fn get_devices() -> Vec { + debug_assert_running_serially(); + let mut size: usize = 0; + let address = get_property_address( + Property::HardwareDevices, + DeviceType::INPUT | DeviceType::OUTPUT, + ); + let mut ret = + audio_object_get_property_data_size(kAudioObjectSystemObject, &address, &mut size); + if ret != NO_ERR { + return Vec::new(); + } + // Total number of input and output devices. + let mut devices: Vec = allocate_array_by_size(size); + ret = audio_object_get_property_data( + kAudioObjectSystemObject, + &address, + &mut size, + devices.as_mut_ptr(), + ); + if ret != NO_ERR { + return Vec::new(); + } + devices +} + pub fn get_device_model_uid( id: AudioDeviceID, devtype: DeviceType, @@ -169,10 +195,15 @@ pub fn get_device_latency( } } +#[derive(Debug)] +pub struct DeviceStream { + pub device: AudioDeviceID, + pub stream: AudioStreamID, +} pub fn get_device_streams( id: AudioDeviceID, devtype: DeviceType, -) -> std::result::Result, OSStatus> { +) -> std::result::Result, OSStatus> { assert_ne!(id, kAudioObjectUnknown); debug_assert_running_serially(); @@ -184,13 +215,77 @@ pub fn get_device_streams( return Err(err); } - let mut streams: Vec = allocate_array_by_size(size); + let mut streams = vec![AudioObjectID::default(); size / mem::size_of::()]; let err = audio_object_get_property_data(id, &address, &mut size, streams.as_mut_ptr()); - if err == NO_ERR { - Ok(streams) - } else { - Err(err) + if err != NO_ERR { + return Err(err); } + + let mut device_streams = streams + .into_iter() + .map(|stream| DeviceStream { device: id, stream }) + .collect::>(); + if devtype.contains(DeviceType::INPUT) { + // With VPIO, output devices will/may get a Tap that appears as an input stream on the + // output device id. It is unclear what kind of Tap this is as it cannot be enumerated + // as a Tap through the public APIs. There is no property on the stream itself that + // can consistently identify it as originating from another device's output either. + // TerminalType gets close but is often kAudioStreamTerminalTypeUnknown, and there are + // cases reported where real input streams have that TerminalType, too. + // See Firefox bug 1890186. + // We rely on AudioObjectID order instead. AudioDeviceID and AudioStreamID (and more) + // are all AudioObjectIDs underneath, and they're all distinct. The Tap streams + // mentioned above are created when VPIO is created, and their AudioObjectIDs are higher + // than the VPIO device's AudioObjectID, but lower than the next *real* device's + // AudioObjectID. + // Simplified, a device's native streams have AudioObjectIDs higher than their device's + // AudioObjectID but lower than the next device's AudioObjectID. + // We use this to filter streams, and hope that it holds across macOS versions. + // Note that for aggregate devices this does not hold, as their stream IDs seem to be + // repurposed by VPIO. We sum up the result of the above algorithm for each of their sub + // devices instead, as that seems to hold. + let mut devices = get_devices(); + let sub_devices = AggregateDevice::get_sub_devices(id); + if let Ok(sub_device_ids) = sub_devices { + cubeb_log!( + "Getting input device streams for aggregate device {}. Summing over sub devices {:?}.", + id, + sub_device_ids + ); + return Ok(sub_device_ids + .into_iter() + .filter_map(|sub_id| get_device_streams(sub_id, devtype).ok()) + .flatten() + .collect()); + } + debug_assert!(devices.contains(&id)); + devices.sort(); + let next_id = devices.into_iter().skip_while(|&i| i != id).nth(1); + cubeb_log!( + "Filtering input streams {:?} for device {}. Next device is {:?}.", + device_streams + .iter() + .map(|ds| ds.stream) + .collect::>(), + id, + next_id + ); + if let Some(next_id) = next_id { + device_streams.retain(|ds| ds.stream > id && ds.stream < next_id); + } else { + device_streams.retain(|ds| ds.stream > id); + } + cubeb_log!( + "Input stream filtering for device {} retained {:?}.", + id, + device_streams + .iter() + .map(|ds| ds.stream) + .collect::>() + ); + } + + Ok(device_streams) } pub fn get_device_sample_rate( @@ -253,24 +348,6 @@ pub fn get_stream_latency(id: AudioStreamID) -> std::result::Result std::result::Result { - assert_ne!(id, kAudioObjectUnknown); - debug_assert_running_serially(); - - let address = get_property_address( - Property::StreamTerminalType, - DeviceType::INPUT | DeviceType::OUTPUT, - ); - let mut size = mem::size_of::(); - let mut terminal_type: u32 = 0; - let err = audio_object_get_property_data(id, &address, &mut size, &mut terminal_type); - if err == NO_ERR { - Ok(terminal_type) - } else { - Err(err) - } -} - pub fn get_stream_virtual_format( id: AudioStreamID, ) -> std::result::Result { diff --git a/third_party/rust/cubeb-coreaudio/src/backend/mod.rs b/third_party/rust/cubeb-coreaudio/src/backend/mod.rs index d6dc55e229f4..b66398f593b2 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/mod.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/mod.rs @@ -1620,77 +1620,17 @@ fn get_channel_count( assert_ne!(devid, kAudioObjectUnknown); debug_assert_running_serially(); - let mut streams = get_device_streams(devid, devtype)?; - let model_uid = - get_device_model_uid(devid, devtype).map_or_else(|_| String::new(), |s| s.into_string()); - - if devtype == DeviceType::INPUT { - // With VPIO, output devices will/may get a Tap that appears as input channels on the - // output device id. One could check for whether the output device has a tap enabled, - // but it is impossible to distinguish an output-only device from an input+output - // device. There have also been corner cases observed, where the device does NOT have - // a Tap enabled, but it still has the extra input channels from the Tap. - // We can check the terminal type of the input stream instead, the VPIO type is - // INPUT_UNDEFINED or an output type, we explicitly ignore those and keep all other cases. - streams.retain(|stream| { - let terminal_type = get_stream_terminal_type(*stream); - if terminal_type.is_err() { - return true; - } - - #[allow(non_upper_case_globals)] - match terminal_type.unwrap() { - kAudioStreamTerminalTypeMicrophone - | kAudioStreamTerminalTypeHeadsetMicrophone - | kAudioStreamTerminalTypeReceiverMicrophone => true, - kAudioStreamTerminalTypeUnknown => { - cubeb_log!("Unknown TerminalType for input stream. Ignoring its channels."); - false - } - t if [ - kAudioStreamTerminalTypeSpeaker, - kAudioStreamTerminalTypeHeadphones, - kAudioStreamTerminalTypeLFESpeaker, - kAudioStreamTerminalTypeReceiverSpeaker, - ] - .contains(&t) => - { - cubeb_log!( - "Output TerminalType {:#06X} for input stream. Ignoring its channels.", - t - ); - false - } - INPUT_UNDEFINED => { - cubeb_log!( - "INPUT_UNDEFINED TerminalType for input stream. Ignoring its channels." - ); - false - } - // The input tap stream on the Studio Display Speakers has a terminal type that - // is not clearly output-specific. We special-case it here. - EXTERNAL_DIGITAL_AUDIO_INTERFACE - if model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID) => - { - false - } - // Note INPUT_UNDEFINED is 0x200 and INPUT_MICROPHONE is 0x201 - t if (INPUT_MICROPHONE..OUTPUT_UNDEFINED).contains(&t) => true, - t if (OUTPUT_UNDEFINED..BIDIRECTIONAL_UNDEFINED).contains(&t) => false, - t if (BIDIRECTIONAL_UNDEFINED..TELEPHONY_UNDEFINED).contains(&t) => true, - t if (TELEPHONY_UNDEFINED..EXTERNAL_UNDEFINED).contains(&t) => true, - t => { - cubeb_log!("Unknown TerminalType {:#06X} for input stream.", t); - true - } - } - }); - } - - let mut count = 0; - for stream in streams { - if let Ok(format) = get_stream_virtual_format(stream) { - count += format.mChannelsPerFrame; + let devstreams = get_device_streams(devid, devtype)?; + let mut count: u32 = 0; + for ds in devstreams { + if devtype == DeviceType::INPUT + && CoreStreamData::should_force_vpio_for_input_device(ds.device) + { + count += 1; + } else { + count += get_stream_virtual_format(ds.stream) + .map(|f| f.mChannelsPerFrame) + .unwrap_or(0); } } Ok(count) @@ -1736,8 +1676,8 @@ fn get_fixed_latency(devid: AudioObjectID, devtype: DeviceType) -> u32 { } }; - let stream_latency = get_device_streams(devid, devtype).and_then(|streams| { - if streams.is_empty() { + let stream_latency = get_device_streams(devid, devtype).and_then(|devstreams| { + if devstreams.is_empty() { cubeb_log!( "No stream on device {} in {:?} scope!", devid, @@ -1745,7 +1685,7 @@ fn get_fixed_latency(devid: AudioObjectID, devtype: DeviceType) -> u32 { ); Ok(0) // default stream latency } else { - get_stream_latency(streams[0]) + get_stream_latency(devstreams[0].stream) } }).map_err(|e| { cubeb_log!( @@ -2031,37 +1971,11 @@ fn destroy_cubeb_device_info(device: &mut ffi::cubeb_device_info) { } } -fn audiounit_get_devices() -> Vec { - debug_assert_running_serially(); - let mut size: usize = 0; - let address = get_property_address( - Property::HardwareDevices, - DeviceType::INPUT | DeviceType::OUTPUT, - ); - let mut ret = - audio_object_get_property_data_size(kAudioObjectSystemObject, &address, &mut size); - if ret != NO_ERR { - return Vec::new(); - } - // Total number of input and output devices. - let mut devices: Vec = allocate_array_by_size(size); - ret = audio_object_get_property_data( - kAudioObjectSystemObject, - &address, - &mut size, - devices.as_mut_ptr(), - ); - if ret != NO_ERR { - return Vec::new(); - } - devices -} - fn audiounit_get_devices_of_type(devtype: DeviceType) -> Vec { assert!(devtype.intersects(DeviceType::INPUT | DeviceType::OUTPUT)); debug_assert_running_serially(); - let mut devices = audiounit_get_devices(); + let mut devices = get_devices(); // Remove the aggregate device from the list of devices (if any). devices.retain(|&device| { @@ -3304,14 +3218,15 @@ impl<'ctx> CoreStreamData<'ctx> { } #[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) { + fn should_force_vpio_for_input_device(id: AudioDeviceID) -> bool { + assert!(id != kAudioObjectUnknown); + debug_assert_running_serially(); + match get_device_transport_type(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." + "Input device {} is on the VPIO force list because it is built in, \ + and its volume is known to be very low without VPIO whenever VPIO \ + is hooked up to it elsewhere." ); true } @@ -3388,7 +3303,7 @@ impl<'ctx> CoreStreamData<'ctx> { .input_stream_params .prefs() .contains(StreamPrefs::VOICE) - || self.should_force_vpio_for_input_device(&self.input_device)) + || CoreStreamData::should_force_vpio_for_input_device(self.input_device.id)) && !self.should_block_vpio_for_device_pair(&self.input_device, &self.output_device) && macos_kernel_major_version() != Ok(MACOS_KERNEL_MAJOR_VERSION_MONTEREY); 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 c846cadb4b6a..9e978fceacb0 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 @@ -1,8 +1,11 @@ +extern crate itertools; + use super::utils::{ test_get_all_devices, test_get_all_onwed_devices, test_get_default_device, test_get_drift_compensations, test_get_master_device, DeviceFilter, Scope, }; use super::*; +use std::collections::HashSet; use std::iter::zip; use std::panic; @@ -46,25 +49,73 @@ fn test_aggregate_set_sub_devices_for_unknown_devices() { // AggregateDevice::get_sub_devices // ------------------------------------ -// You can check this by creating an aggregate device in `Audio MIDI Setup` -// application and print out the sub devices of them! #[test] +#[ignore] fn test_aggregate_get_sub_devices() { - let devices = test_get_all_devices(DeviceFilter::ExcludeCubebAggregateAndVPIO); - for device in devices { - // `AggregateDevice::get_sub_devices(device)` will return a single-element vector - // 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). - println!( - "get_sub_devices({}={})", - device, - run_serially_forward_panics(|| get_device_uid(device)) + fn diff(lhs: Vec, rhs: Vec) -> Vec { + let left: HashSet = lhs.into_iter().collect(); + let right: HashSet = rhs.into_iter().collect(); + left.symmetric_difference(&right) + .map(|&i| i.clone()) + .collect() + } + + // Run in a large block so other test cases cannot add or remove devices while this runs. + 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 and output device to create an aggregate device."); + return; + } + let devices_base = test_get_all_devices(DeviceFilter::ExcludeVPIO); + run_serially_forward_panics(|| { + assert!( + devices_base + .clone() + .into_iter() + .map(AggregateDevice::get_sub_devices) + .any(|r| r.is_err()), + "There should be some device that is not an aggregate." + ) + }); + + { + // Test get_sub_devices on an empty aggregate device. + let plugin_id = AggregateDevice::get_system_plugin_id().unwrap(); + let aggr = run_serially_forward_panics(|| AggregateDevice::create_blank_device(plugin_id)) + .unwrap(); + let new = diff( + devices_base.clone(), + test_get_all_devices(DeviceFilter::ExcludeVPIO), ); - 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()); + assert_eq!(new.len(), 1); + let new_subs = run_serially_forward_panics(|| AggregateDevice::get_sub_devices(new[0])); + assert!(new_subs.is_ok()); + assert_eq!(new_subs.unwrap().len(), 0); + assert!( + run_serially_forward_panics(|| AggregateDevice::destroy_device(plugin_id, aggr)) + .is_ok() + ); + } + + { + // Test get_sub_devices on an aggregate device with two sub devices. + let aggr = run_serially_forward_panics(|| { + let input_device = input_device.unwrap(); + let output_device = output_device.unwrap(); + + AggregateDevice::new(input_device, output_device) + }) + .unwrap(); + let new = diff( + devices_base.clone(), + test_get_all_devices(DeviceFilter::ExcludeVPIO), + ); + assert_eq!(new.len(), 1); + let new_subs = run_serially_forward_panics(|| AggregateDevice::get_sub_devices(new[0])); + assert!(new_subs.is_ok()); + assert_eq!(new_subs.unwrap().len(), 2); + run_serially_forward_panics(|| drop(aggr)); } } @@ -72,8 +123,7 @@ fn test_aggregate_get_sub_devices() { #[should_panic] fn test_aggregate_get_sub_devices_for_a_unknown_device() { run_serially_forward_panics(|| { - let devices = AggregateDevice::get_sub_devices(kAudioObjectUnknown).unwrap(); - assert!(devices.is_empty()); + AggregateDevice::get_sub_devices(kAudioObjectUnknown); }); } @@ -135,15 +185,11 @@ fn test_aggregate_create_blank_device() { // AggregateDevice::get_sub_devices // ------------------------------------ #[test] -#[should_panic] fn test_aggregate_get_sub_devices_for_blank_aggregate_devices() { 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()); @@ -173,11 +219,11 @@ fn test_aggregate_set_sub_devices() { )) .is_ok()); - let sub_devices = run_serially(|| AggregateDevice::get_sub_devices(device)).unwrap(); + let sub_devices = run_serially(|| AggregateDevice::get_sub_devices_or_self(device)).unwrap(); let input_sub_devices = - run_serially(|| AggregateDevice::get_sub_devices(input_device)).unwrap(); + run_serially(|| AggregateDevice::get_sub_devices_or_self(input_device)).unwrap(); let output_sub_devices = - run_serially(|| AggregateDevice::get_sub_devices(output_device)).unwrap(); + run_serially(|| AggregateDevice::get_sub_devices_or_self(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. @@ -280,7 +326,7 @@ fn test_aggregate_set_master_device() { 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(); + run_serially(|| AggregateDevice::get_sub_devices_or_self(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. @@ -391,10 +437,12 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_with // 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])); + let first_input_sub_device_uid = run_serially(|| { + get_device_uid(AggregateDevice::get_sub_devices_or_self(input_device).unwrap()[0]) + }); + let first_sub_device_uid = run_serially(|| { + get_device_uid(AggregateDevice::get_sub_devices_or_self(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); @@ -422,7 +470,7 @@ 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(); - let sub_devices = AggregateDevice::get_sub_devices(device).unwrap(); + let sub_devices = AggregateDevice::get_sub_devices_or_self(device).unwrap(); assert!(sub_devices.is_empty()); let onwed_devices = test_get_all_onwed_devices(device); assert!(onwed_devices.is_empty()); @@ -475,7 +523,7 @@ fn test_aggregate_new() { let aggr = AggregateDevice::new(input_device, output_device).unwrap(); // Check main device - let output_sub_devices = AggregateDevice::get_sub_devices(output_device).unwrap(); + let output_sub_devices = AggregateDevice::get_sub_devices_or_self(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); 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 3ef3930dbd14..bc6f395b0b21 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs @@ -1134,6 +1134,98 @@ fn test_get_channel_count_of_unknwon_type() { } } +fn is_vpio(id: AudioObjectID) -> bool { + debug_assert_running_serially(); + get_device_global_uid(id) + .map(|uid| uid.into_string()) + .map(|uid| uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME)) + .unwrap_or(false) +} + +fn get_nonvpio_input_channel_counts() -> Vec { + debug_assert_running_serially(); + get_devices() + .into_iter() + .filter(|&id| !is_vpio(id)) + .filter_map(|id| get_channel_count(id, DeviceType::INPUT).ok()) + .collect() +} + +#[test] +#[ignore] +fn test_get_channel_count_of_input_devices_with_vpio() { + let non_vpio_channel_counts = + run_serially_forward_panics(|| get_nonvpio_input_channel_counts()); + + let queue = Queue::new_with_target( + "test_get_channel_count_of_input_devices_with_vpio", + get_serial_queue_singleton(), + ); + let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone()); + let _vpio = queue.run_sync(|| shared.take_or_create()).unwrap().unwrap(); + + let vpio_channel_counts = run_serially_forward_panics(|| get_nonvpio_input_channel_counts()); + assert_eq!(non_vpio_channel_counts, vpio_channel_counts); +} + +#[test] +#[ignore] +fn test_get_channel_count_of_input_devices_with_aggregate_device_and_vpio() { + let input_device = test_get_default_device(Scope::Input); + let output_device = test_get_default_device(Scope::Output); + if input_device.is_none() || output_device.is_none() || input_device == output_device { + println!("No input or output device to create an aggregate device."); + return; + } + + #[derive(Default)] + struct State { + aggr: Option, + vpio_mgr: Option, + vpio: Option>, + } + impl Drop for State { + fn drop(&mut self) { + let mut aggr = self.aggr.take(); + let mut vpio = self.vpio.take(); + run_serially_forward_panics(move || { + aggr.take(); + vpio.take(); + }); + } + } + let state = Arc::new(Mutex::new(State::default())); + + // Set up an AggregateDevice with input and output. + let initial_channel_counts = run_serially_forward_panics(|| get_nonvpio_input_channel_counts()); + let s1 = state.clone(); + let aggr_channel_counts = run_serially_forward_panics(|| { + let mut state = s1.lock().unwrap(); + state.aggr = + Some(AggregateDevice::new(input_device.unwrap(), output_device.unwrap()).unwrap()); + + get_nonvpio_input_channel_counts() + }); + assert_eq!(initial_channel_counts.len() + 1, aggr_channel_counts.len()); + + let queue = Queue::new_with_target( + "test_get_channel_count_of_input_devices_with_aggregate_device_and_vpio", + get_serial_queue_singleton(), + ); + { + let mut s = state.lock().unwrap(); + s.vpio_mgr = Some(SharedVoiceProcessingUnitManager::new(queue.clone())); + } + let s2 = state.clone(); + let aggr_vpio_channel_counts = run_serially_forward_panics(|| { + let mut state = s2.lock().unwrap(); + state.vpio = state.vpio_mgr.as_mut().map(|m| m.take_or_create().unwrap()); + + get_nonvpio_input_channel_counts() + }); + assert_eq!(aggr_channel_counts, aggr_vpio_channel_counts); +} + // get_range_of_sample_rates // ------------------------------------ #[test] 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 a974aee64bb7..15eeb2a2c47e 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 @@ -413,20 +413,20 @@ 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 = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap(); - for stream in streams { - let latency = run_serially(|| get_stream_latency(stream)).unwrap(); - println!("latency of the input stream {} is {}", stream, latency); + let devstreams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap(); + for ds in devstreams { + let latency = run_serially(|| get_stream_latency(ds.stream)).unwrap(); + println!("latency of the input stream {} is {}", ds.stream, latency); } } else { println!("No input device."); } if let Some(device) = test_get_default_device(Scope::Output) { - let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap(); - for stream in streams { - let latency = run_serially(|| get_stream_latency(stream)).unwrap(); - println!("latency of the output stream {} is {}", stream, latency); + let devstreams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap(); + for ds in devstreams { + let latency = run_serially(|| get_stream_latency(ds.stream)).unwrap(); + println!("latency of the output stream {} is {}", ds.stream, latency); } } else { println!("No output device."); @@ -444,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 = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap(); - let formats = streams + let devstreams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap(); + let formats = devstreams .iter() - .map(|s| run_serially(|| get_stream_virtual_format(*s))) + .map(|ds| run_serially(|| get_stream_virtual_format(ds.stream))) .collect::>>(); println!("input stream formats: {:?}", formats); assert!(!formats.is_empty()); @@ -456,10 +456,10 @@ fn test_get_stream_virtual_format() { } if let Some(device) = test_get_default_device(Scope::Output) { - let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap(); - let formats = streams + let devstreams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap(); + let formats = devstreams .iter() - .map(|s| run_serially(|| get_stream_virtual_format(*s))) + .map(|ds| run_serially(|| get_stream_virtual_format(ds.stream))) .collect::>>(); println!("output stream formats: {:?}", formats); assert!(!formats.is_empty()); @@ -476,52 +476,22 @@ fn test_get_stream_virtual_format_by_unknown_stream() { ); } -// get_stream_terminal_type +// get_devices // ------------------------------------ #[test] -fn test_get_stream_terminal_type() { - fn terminal_type_to_device_type(terminal_type: u32) -> Option { - #[allow(non_upper_case_globals)] - match terminal_type { - kAudioStreamTerminalTypeMicrophone - | kAudioStreamTerminalTypeHeadsetMicrophone - | kAudioStreamTerminalTypeReceiverMicrophone => Some(DeviceType::INPUT), - kAudioStreamTerminalTypeSpeaker - | kAudioStreamTerminalTypeHeadphones - | kAudioStreamTerminalTypeLFESpeaker - | kAudioStreamTerminalTypeReceiverSpeaker => Some(DeviceType::OUTPUT), - t if t > INPUT_UNDEFINED && t < OUTPUT_UNDEFINED => Some(DeviceType::INPUT), - t if t > OUTPUT_UNDEFINED && t < BIDIRECTIONAL_UNDEFINED => Some(DeviceType::OUTPUT), - t => { - println!("UNKNOWN TerminalType {:#06x}", t); - None - } - } - } +fn test_get_devices() { if let Some(device) = test_get_default_device(Scope::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) - })); + let devices = run_serially(|| get_devices()); + assert!(devices.contains(&device)); } else { println!("No input device."); } if let Some(device) = test_get_default_device(Scope::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) - })); + let devices = run_serially(|| get_devices()); + assert!(devices.contains(&device)); } else { println!("No output device."); } } - -#[test] -#[should_panic] -fn test_get_stream_terminal_type_by_unknown_stream() { - assert!(get_stream_terminal_type(kAudioObjectUnknown).is_err()); -} 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 e14c5851958d..de060ca300aa 100644 --- a/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs @@ -293,6 +293,7 @@ fn test_enable_audiounit_in_scope( pub enum DeviceFilter { ExcludeCubebAggregateAndVPIO, + ExcludeVPIO, IncludeAll, } pub fn test_get_all_devices(filter: DeviceFilter) -> Vec { @@ -354,6 +355,16 @@ pub fn test_get_all_devices(filter: DeviceFilter) -> Vec { } }); } + DeviceFilter::ExcludeVPIO => { + devices.retain(|&device| { + if let Ok(uid) = get_device_global_uid(device) { + let uid = uid.into_string(); + !uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME) + } else { + true + } + }); + } _ => {} } diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index cd27735b7929..e1004bf4fb96 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -22,7 +22,7 @@ static_prefs = { path = "../../../../modules/libpref/init/static_prefs" } profiler_helper = { path = "../../../../tools/profiler/rust-helper", optional = true } mozurl = { path = "../../../../netwerk/base/mozurl" } webrender_bindings = { path = "../../../../gfx/webrender_bindings" } -cubeb-coreaudio = { git = "https://github.com/mozilla/cubeb-coreaudio-rs", rev = "4cc24c7dc74a4801455ba0bbe20ea67a49f28514", optional = true } +cubeb-coreaudio = { git = "https://github.com/mozilla/cubeb-coreaudio-rs", rev = "1796ace5bdd08ec8baa56bbf7170a08d760c984b", optional = true } cubeb-pulse = { git = "https://github.com/mozilla/cubeb-pulse-rs", rev="8678dcab1c287de79c4c184ccc2e065bc62b70e2", optional = true, features=["pulse-dlopen"] } cubeb-sys = { version = "0.13", optional = true, features=["gecko-in-tree"] } audioipc2-client = { git = "https://github.com/mozilla/audioipc", rev = "3495905752a4263827f5d43737f9ca3ed0243ce0", optional = true }