зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1601859 - Vendor cubeb-coreaudio. r=padenot
This technically breaks mach vendor rust because of the missing licenses, but this will be fixed subsequently. Differential Revision: https://phabricator.services.mozilla.com/D56160 --HG-- rename : media/libcubeb/cubeb-coreaudio-rs/coreaudio-sys-utils/Cargo.toml => third_party/rust/coreaudio-sys-utils/Cargo.toml rename : media/libcubeb/cubeb-coreaudio-rs/coreaudio-sys-utils/src/aggregate_device.rs => third_party/rust/coreaudio-sys-utils/src/aggregate_device.rs rename : media/libcubeb/cubeb-coreaudio-rs/coreaudio-sys-utils/src/audio_object.rs => third_party/rust/coreaudio-sys-utils/src/audio_object.rs rename : media/libcubeb/cubeb-coreaudio-rs/coreaudio-sys-utils/src/audio_unit.rs => third_party/rust/coreaudio-sys-utils/src/audio_unit.rs rename : media/libcubeb/cubeb-coreaudio-rs/coreaudio-sys-utils/src/cf_mutable_dict.rs => third_party/rust/coreaudio-sys-utils/src/cf_mutable_dict.rs rename : media/libcubeb/cubeb-coreaudio-rs/coreaudio-sys-utils/src/dispatch.rs => third_party/rust/coreaudio-sys-utils/src/dispatch.rs rename : media/libcubeb/cubeb-coreaudio-rs/coreaudio-sys-utils/src/lib.rs => third_party/rust/coreaudio-sys-utils/src/lib.rs rename : media/libcubeb/cubeb-coreaudio-rs/coreaudio-sys-utils/src/string.rs => third_party/rust/coreaudio-sys-utils/src/string.rs rename : media/libcubeb/cubeb-coreaudio-rs/Cargo.toml => third_party/rust/cubeb-coreaudio/Cargo.toml rename : media/libcubeb/cubeb-coreaudio-rs/LICENSE => third_party/rust/cubeb-coreaudio/LICENSE rename : media/libcubeb/cubeb-coreaudio-rs/src/backend/aggregate_device.rs => third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/backend/auto_array.rs => third_party/rust/cubeb-coreaudio/src/backend/auto_array.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/backend/auto_release.rs => third_party/rust/cubeb-coreaudio/src/backend/auto_release.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/backend/device_property.rs => third_party/rust/cubeb-coreaudio/src/backend/device_property.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/backend/mixer.rs => third_party/rust/cubeb-coreaudio/src/backend/mixer.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/backend/mod.rs => third_party/rust/cubeb-coreaudio/src/backend/mod.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/backend/resampler.rs => third_party/rust/cubeb-coreaudio/src/backend/resampler.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/backend/utils.rs => third_party/rust/cubeb-coreaudio/src/backend/utils.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/capi.rs => third_party/rust/cubeb-coreaudio/src/capi.rs rename : media/libcubeb/cubeb-coreaudio-rs/src/lib.rs => third_party/rust/cubeb-coreaudio/src/lib.rs extra : moz-landing-system : lando
This commit is contained in:
Родитель
37ebcfc664
Коммит
1c4ef0191c
|
@ -57,6 +57,11 @@ branch = "stable"
|
|||
git = "https://github.com/NikVolf/tokio-named-pipes"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."https://github.com/ChunMinChang/cubeb-coreaudio-rs"]
|
||||
git = "https://github.com/ChunMinChang/cubeb-coreaudio-rs"
|
||||
replace-with = "vendored-sources"
|
||||
rev = "0920240e4166d2b562840c8062e149d63f7c3a02"
|
||||
|
||||
[source.crates-io]
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
|
|
|
@ -608,6 +608,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "coreaudio-sys-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=0920240e4166d2b562840c8062e149d63f7c3a02#0920240e4166d2b562840c8062e149d63f7c3a02"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"coreaudio-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -833,10 +834,11 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cubeb-coreaudio"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=0920240e4166d2b562840c8062e149d63f7c3a02#0920240e4166d2b562840c8062e149d63f7c3a02"
|
||||
dependencies = [
|
||||
"atomic 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"coreaudio-sys-utils 0.1.0",
|
||||
"coreaudio-sys-utils 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=0920240e4166d2b562840c8062e149d63f7c3a02)",
|
||||
"cubeb-backend 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1453,7 +1455,7 @@ dependencies = [
|
|||
"bookmark_sync 0.1.0",
|
||||
"cert_storage 0.0.1",
|
||||
"cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb-coreaudio 0.1.0",
|
||||
"cubeb-coreaudio 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=0920240e4166d2b562840c8062e149d63f7c3a02)",
|
||||
"cubeb-pulse 0.3.0",
|
||||
"cubeb-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_glue 0.1.0",
|
||||
|
@ -4755,6 +4757,7 @@ dependencies = [
|
|||
"checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9"
|
||||
"checksum core-text 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f3f46450d6f2397261af420b4ccce23807add2e45fa206410a03d66fb7f050ae"
|
||||
"checksum coreaudio-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e8f5954c1c7ccb55340443e8b29fca24013545a5e7d72c1ca7db4fc02b982ce"
|
||||
"checksum coreaudio-sys-utils 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=0920240e4166d2b562840c8062e149d63f7c3a02)" = "<none>"
|
||||
"checksum cose 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72fa26cb151d3ae4b70f63d67d0fed57ce04220feafafbae7f503bef7aae590d"
|
||||
"checksum cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "49726015ab0ca765144fcca61e4a7a543a16b795a777fa53f554da2fffff9a94"
|
||||
"checksum cranelift-bforest 0.51.0 (git+https://github.com/bytecodealliance/cranelift?rev=4727b70b67abfa4f3ae1c276454a0da7a76e1d49)" = "<none>"
|
||||
|
@ -4778,6 +4781,7 @@ dependencies = [
|
|||
"checksum cubeb 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbcdfde9ea319160af6eff068ffaa96aad3532e1b5c0ebc134614cfacacae24"
|
||||
"checksum cubeb-backend 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5a1e7add4e7642a8aebb24172922318482bed52389a12cb339f728bbd4c4ed9c"
|
||||
"checksum cubeb-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfd9b2ea1cb6afed9419b0d18fc4093df552ccb2300eb57793629f8cd370b4c8"
|
||||
"checksum cubeb-coreaudio 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=0920240e4166d2b562840c8062e149d63f7c3a02)" = "<none>"
|
||||
"checksum cubeb-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "309c5839c5fa03c08363bd308566cbe4654b25a9984342d7546a33d55b80a3d6"
|
||||
"checksum d3d12 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc7ed48e89905e5e146bcc1951cc3facb9e44aea9adf5dc01078cda1bd24b662"
|
||||
"checksum darling 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe629a532efad5526454efb0700f86d5ad7ff001acb37e431c8bf017a432a8e"
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
The source from this directory was copied from the cubeb-coreaudio-rs
|
||||
git repository using the update.sh script.
|
||||
|
||||
The cubeb-coreaudio-rs git repository is: https://github.com/ChunMinChang/cubeb-coreaudio-rs
|
||||
|
||||
The git commit ID used was 0920240e4166d2b562840c8062e149d63f7c3a02 (2019-11-13 09:18:08 -0800)
|
|
@ -1,11 +0,0 @@
|
|||
diff --git a/media/libcubeb/cubeb-coreaudio-rs/src/backend/mod.rs b/media/libcubeb/cubeb-coreaudio-rs/src/backend/mod.rs
|
||||
index f2da36fee65b..d9c402ee14b8 100644
|
||||
--- a/media/libcubeb/cubeb-coreaudio-rs/src/backend/mod.rs
|
||||
+++ b/media/libcubeb/cubeb-coreaudio-rs/src/backend/mod.rs
|
||||
@@ -4118,6 +4118,3 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> {
|
||||
// An unsafe workaround to pass AudioUnitStream across threads.
|
||||
unsafe impl<'ctx> Send for AudioUnitStream<'ctx> {}
|
||||
unsafe impl<'ctx> Sync for AudioUnitStream<'ctx> {}
|
||||
-
|
||||
-#[cfg(test)]
|
||||
-mod tests;
|
|
@ -1,42 +0,0 @@
|
|||
use super::coreaudio_sys_utils::sys::*;
|
||||
|
||||
pub const DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS: AudioObjectPropertyAddress =
|
||||
AudioObjectPropertyAddress {
|
||||
mSelector: kAudioHardwarePropertyDefaultInputDevice,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
pub const DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS: AudioObjectPropertyAddress =
|
||||
AudioObjectPropertyAddress {
|
||||
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
pub const DEVICE_IS_ALIVE_PROPERTY_ADDRESS: AudioObjectPropertyAddress =
|
||||
AudioObjectPropertyAddress {
|
||||
mSelector: kAudioDevicePropertyDeviceIsAlive,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
pub const DEVICES_PROPERTY_ADDRESS: AudioObjectPropertyAddress = AudioObjectPropertyAddress {
|
||||
mSelector: kAudioHardwarePropertyDevices,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
pub const INPUT_DATA_SOURCE_PROPERTY_ADDRESS: AudioObjectPropertyAddress =
|
||||
AudioObjectPropertyAddress {
|
||||
mSelector: kAudioDevicePropertyDataSource,
|
||||
mScope: kAudioDevicePropertyScopeInput,
|
||||
mElement: kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
pub const OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS: AudioObjectPropertyAddress =
|
||||
AudioObjectPropertyAddress {
|
||||
mSelector: kAudioDevicePropertyDataSource,
|
||||
mScope: kAudioDevicePropertyScopeOutput,
|
||||
mElement: kAudioObjectPropertyElementMaster,
|
||||
};
|
|
@ -1,40 +0,0 @@
|
|||
# Usage: sh update.sh <upstream_src_directory>
|
||||
set -e
|
||||
|
||||
cp -p $1/LICENSE .
|
||||
cp -p $1/Cargo.toml .
|
||||
cp -r $1/coreaudio-sys-utils .
|
||||
test -d src || mkdir -p src
|
||||
# Copy all the files under src folder, except tests.
|
||||
rsync -av --progress $1/src/ src/ --exclude backend/tests
|
||||
|
||||
if [ -d $1/.git ]; then
|
||||
rev=$(cd $1 && git rev-parse --verify HEAD)
|
||||
date=$(cd $1 && git show -s --format=%ci HEAD)
|
||||
dirty=$(cd $1 && git diff-index --name-only HEAD)
|
||||
set +e
|
||||
pre_rev=$(grep -o '[[:xdigit:]]\{40\}' README_MOZILLA)
|
||||
commits=$(cd $1 && git log --pretty=format:'%h - %s' $pre_rev..$rev)
|
||||
set -e
|
||||
fi
|
||||
|
||||
if [ -n "$rev" ]; then
|
||||
version=$rev
|
||||
if [ -n "$dirty" ]; then
|
||||
version=$version-dirty
|
||||
echo "WARNING: updating from a dirty git repository."
|
||||
fi
|
||||
echo "$version ($date)"
|
||||
sed -i.bak -e "/The git commit ID used was/ s/[0-9a-f]\{40\}\(-dirty\)\{0,1\} .\{1,100\}/$version ($date)/" README_MOZILLA
|
||||
rm README_MOZILLA.bak
|
||||
[ -n "$commits" ] && echo -e "Pick commits:\n$commits"
|
||||
else
|
||||
echo "Remember to update README_MOZILLA with the version details."
|
||||
fi
|
||||
|
||||
# Apply patches for gecko
|
||||
for patch in *.patch; do
|
||||
[ -f "$patch" ] || continue
|
||||
echo "Apply $patch"
|
||||
patch -p4 < $patch
|
||||
done
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.toml":"077906135ef930990c17a944953291db52f5e2e178cad992872228f6dc35d263","src/aggregate_device.rs":"7d2bd5f5fd7f3d008ebb69ad81f522ca0cb73db6d7b3e50ed1a63ea26ff721f4","src/audio_object.rs":"df10160d9fd83a2c23a49e69b78d39db3a9d6389607df6acfc05821293b6af5f","src/audio_unit.rs":"bc743a1b8033ab5459520c75d7f5230d24cda5ea1198a5b4e1594256af308f47","src/cf_mutable_dict.rs":"fc42edd270c6dfb02f123214d2d8e487bbd62b5bd923b71eec13190fd0104d2a","src/dispatch.rs":"c3d43571f610cb8524ef49b5928da8363651507bb2ccec443be58c8688e111cb","src/lib.rs":"bcc559d69ef6ed0cbea5b2a36fec89d8c011eb9da70e2f26c00f881ad97a2546","src/string.rs":"ddce19b0f0e6aceb64fa96d2f15f6b191051255f58b340737028fb464087d4e1"},"package":null}
|
|
@ -0,0 +1 @@
|
|||
{"files":{".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".travis.yml":"bea421508af5f4d00b941866dae0ae7d94a51b9276688a7f626686e8ed8fbbf3","Cargo.toml":"208c7d2c2240a1e38070313a981f61c72f81a017faf93d5ca350e0fae3a35df4","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"72d8a890d6bda3cdba393432e5ae2018a385980785ebb2b96e9c3f82a48a1b59","run_tests.sh":"871864068903c37b04857f3509361f93fbbcf2b81d74eae5715ac2b451458813","src/backend/aggregate_device.rs":"7cd732f3e1e71876753515b26ee69a315414e58869216e99ff95c6828408c4db","src/backend/auto_array.rs":"5f35545baba2b005e13a2225bd1cbdd94ffc2097554d61479929bfc5442a6dd6","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/device_property.rs":"1b066b48ed09026a9286b1b8f40e2720854c3410b0f02c795a580792fda34ea9","src/backend/mixer.rs":"74dcac459493e2f919b61ed3bebe500027e422eb06b1ecd30b73a47079c61f7c","src/backend/mod.rs":"7b57ec50f24cffbacd323162b6b9357c276c151e2844c8e32fbe779b18ef3179","src/backend/resampler.rs":"fd1281d28a4db1659d2f75e43b8457651745e1b6eb5a53a77f04d752135f6dc7","src/backend/tests/aggregate_device.rs":"107f5c637844cd5ae43d2b42cec4ef3369bb702751586078c0a9d50f039161cd","src/backend/tests/api.rs":"d76c1574179085e0c1342614cd674e5aa211dd456f3725aeb294def6b32750fd","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"e098dafaeedd7fbbf58378c2e74e7a245dba1cf8832695c1b08932a669131e44","src/backend/tests/device_property.rs":"b1a9ae79aa5b9a3f180040d0ef0954b134680d586882d2062c5e017b555bff57","src/backend/tests/interfaces.rs":"01fc2d54ddb50f014a44f9c137f078645738bcc81e48140a3e7ae68e18a61b6b","src/backend/tests/manual.rs":"066a7d981dc02d7a3fd83486b51e27ea1c9223496167ccb23a6478fde073e882","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"f9e1883660d6146b6e5075806561f5f689810e25c5e7764dfd28c9b939821a49","src/backend/tests/tone.rs":"16150438317ce501986734167b5fb97bfec567228acbcd8f3b4c4484c22f29e0","src/backend/tests/utils.rs":"eb552657e68e67b8a60d04ad1bbb46cd1401bcafa27d383dccf4db141c8089c5","src/backend/utils.rs":"ee77bc266d672d3d9e23eb3290c1f897687394c6e459338804a17433380a6fd2","src/capi.rs":"61f8f0c4373adaefba1eb6e7084687e83a10136db96438bc35884327668e411f","src/lib.rs":"1ff4b738ed194061fca4ff745f847dea4de4e7a4fa1f898e7b4ad5e70c62386d","todo.md":"a66296c220cad24d08ee780308007a702f7e421edf0bb60464c3ce8feeda1882"},"package":null}
|
|
@ -0,0 +1,12 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
|
@ -0,0 +1,13 @@
|
|||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
os:
|
||||
- osx
|
||||
before_script:
|
||||
- rustc --version
|
||||
- cargo --version
|
||||
script:
|
||||
- cargo build --verbose
|
||||
- sh run_tests.sh
|
|
@ -0,0 +1,105 @@
|
|||
# cubeb-coreaudio-rs
|
||||
|
||||
[![Build Status](https://travis-ci.org/ChunMinChang/cubeb-coreaudio-rs.svg?branch=trailblazer)](https://travis-ci.org/ChunMinChang/cubeb-coreaudio-rs)
|
||||
|
||||
*Rust* implementation of [Cubeb][cubeb] on [the MacOS platform][cubeb-au].
|
||||
|
||||
## Current Goals
|
||||
- Keep refactoring the implementation until it looks rusty! (it's translated from C at first.)
|
||||
- Check the [todo list][todo] first
|
||||
|
||||
## Status
|
||||
|
||||
The code is currently tested in the _Firefox Nightly_ under a _perf_.
|
||||
|
||||
- Try it:
|
||||
- Open `about:config`
|
||||
- Add a perf `media.cubeb.backend` with string `audiounit-rust`
|
||||
- Restart Firefox Nightly
|
||||
- Open `about:support`
|
||||
- Check if the `Audio Backend` in `Media` section is `audiounit-rust` or not
|
||||
- Retart Firefox Nightly again if it's not.
|
||||
|
||||
## Test
|
||||
Please run `sh run_tests.sh`.
|
||||
|
||||
Some tests cannot be run in parallel.
|
||||
They may operate the same device at the same time,
|
||||
or indirectly fire some system events that are listened by some tests.
|
||||
|
||||
The tests that may affect others are marked `#[ignore]`.
|
||||
They will be run by `cargo test ... -- --ignored ...`
|
||||
after finishing normal tests.
|
||||
Most of the tests are executed in `run_tests.sh`.
|
||||
Only those tests commented with *FIXIT* are left.
|
||||
|
||||
### Device Switching
|
||||
The system default device will be changed during our tests.
|
||||
All the available devices will take turns being the system default device.
|
||||
However, after finishing the tests, the default device will be set to the original one.
|
||||
The sounds in the tests should be able to continue whatever the system default device is.
|
||||
|
||||
### Device Plugging/Unplugging
|
||||
We implement APIs simulating plugging or unplugging a device
|
||||
by adding or removing an aggregate device programmatically.
|
||||
It's used to verify our callbacks for minitoring the system devices work.
|
||||
|
||||
### Manual Test
|
||||
- Output devices switching
|
||||
- `$ cargo test test_switch_output_device -- --ignored --nocapture`
|
||||
- Enter `s` to switch output devices
|
||||
- Enter `q` to finish test
|
||||
- Device change events listener
|
||||
- `$ cargo test test_add_then_remove_listeners -- --ignored --nocapture`
|
||||
- Plug/Unplug devices or switch input/output devices to see events log.
|
||||
- Device collection change
|
||||
- `cargo test test_device_collection_change -- --ignored --nocapture`
|
||||
- Plug/Unplug devices to see events log.
|
||||
|
||||
## TODO
|
||||
See [todo list][todo]
|
||||
|
||||
## Issues
|
||||
- Atomic:
|
||||
- We need atomic type around `f32` but there is no this type in the stardard Rust
|
||||
- Using [atomic-rs](https://github.com/Amanieu/atomic-rs) to do this.
|
||||
- No guarantee on `audiounit_set_channel_layout`
|
||||
- This call doesn't work all the times
|
||||
- Returned `NO_ERR` doesn't guarantee the layout is set to the one we want
|
||||
- The layouts on some devices won't be changed even no errors are returned,
|
||||
e.g., we can set _stereo_ layout to a _4-channels aggregate device_ with _QUAD_ layout
|
||||
(created by Audio MIDI Setup) without any error. However, the layout
|
||||
of this 4-channels aggregate device is still QUAD after setting it without error
|
||||
- Another weird thing is that we will get a `kAudioUnitErr_InvalidPropertyValue`
|
||||
if we set the layout to _QUAD_. It's the same layout as its original one but it cannot be set!
|
||||
- `kAudioDevicePropertyBufferFrameSize` cannot be set when another stream using the same device with smaller buffer size is active. See [here][chg-buf-sz] for details.
|
||||
|
||||
### Test issues
|
||||
- Fail to run tests that depend on `AggregateDevice::create_blank_device` with the tests that work with the device event listeners
|
||||
- The `AggregateDevice::create_blank_device` will add an aggregate device to the system and fire the device-change events indirectly.
|
||||
- `TestDeviceSwitcher` cannot work when there is an alive full-duplex stream
|
||||
- An aggregate device will be created for a duplex stream when its input and output devices are different.
|
||||
- `TestDeviceSwitcher` will cached the available devices, upon it's created, as the candidates for default device
|
||||
- Hence the created aggregate device may be cached in `TestDeviceSwitcher`
|
||||
- If the aggregate device is destroyed (when the destroying the duplex stream created it) but the `TestDeviceSwitcher` is still working,
|
||||
it will set a destroyed device as the default device
|
||||
- See details in [device_change.rs](src/backend/tests/device_change.rs)
|
||||
|
||||
## Branches
|
||||
- [trailblazer][trailblazer]: Main branch
|
||||
- [plain-translation-from-c][from-c]: The code is rewritten from C code on a line-by-line basis
|
||||
- [ocs-disposal][ocs-disposal]: The first version that replace our custom mutex by Rust Mutex
|
||||
|
||||
[cubeb]: https://github.com/kinetiknz/cubeb "Cross platform audio library"
|
||||
[cubeb-au]: https://github.com/kinetiknz/cubeb/blob/master/src/cubeb_audiounit.cpp "Cubeb AudioUnit"
|
||||
|
||||
[chg-buf-sz]: https://cs.chromium.org/chromium/src/media/audio/mac/audio_manager_mac.cc?l=982-989&rcl=0207eefb445f9855c2ed46280cb835b6f08bdb30 "issue on changing buffer size"
|
||||
|
||||
[todo]: todo.md
|
||||
|
||||
[bmo1572273]: https://bugzilla.mozilla.org/show_bug.cgi?id=1572273
|
||||
[bmo1572273-c13]: https://bugzilla.mozilla.org/show_bug.cgi?id=1572273#c13
|
||||
|
||||
[from-c]: https://github.com/ChunMinChang/cubeb-coreaudio-rs/tree/plain-translation-from-c
|
||||
[ocs-disposal]: https://github.com/ChunMinChang/cubeb-coreaudio-rs/tree/ocs-disposal
|
||||
[trailblazer]: https://github.com/ChunMinChang/cubeb-coreaudio-rs/tree/trailblazer
|
|
@ -0,0 +1,44 @@
|
|||
# Regular Tests
|
||||
cargo test --verbose
|
||||
cargo test test_configure_output -- --ignored
|
||||
cargo test test_aggregate -- --ignored --test-threads=1
|
||||
|
||||
# Parallel Tests
|
||||
cargo test test_parallel -- --ignored --nocapture --test-threads=1
|
||||
|
||||
# Device-changed Tests
|
||||
cargo test test_switch_device -- --ignored --nocapture
|
||||
cargo test test_plug_and_unplug_device -- --ignored --nocapture
|
||||
# cargo test test_register_device_changed_callback -- --ignored --nocapture --test-threads=1
|
||||
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_destroy_input_stream_after_unplugging_a_default_input_device -- --ignored --nocapture
|
||||
# FIXIT: The following test will hang since we don't monitor the alive status of the output device
|
||||
# cargo test test_destroy_output_stream_after_unplugging_a_nondefault_output_device -- --ignored --nocapture
|
||||
cargo test test_destroy_output_stream_after_unplugging_a_default_output_device -- --ignored --nocapture
|
||||
cargo test test_destroy_duplex_stream_after_unplugging_a_nondefault_input_device -- --ignored --nocapture
|
||||
cargo test test_destroy_duplex_stream_after_unplugging_a_default_input_device -- --ignored --nocapture
|
||||
# FIXIT: The following test will hang since we don't monitor the alive status of the output device
|
||||
# cargo test test_destroy_duplex_stream_after_unplugging_a_nondefault_output_device -- --ignored --nocapture
|
||||
cargo test test_destroy_duplex_stream_after_unplugging_a_default_output_device -- --ignored --nocapture
|
||||
|
||||
cargo test test_reinit_input_stream_by_unplugging_a_nondefault_input_device -- --ignored --nocapture
|
||||
cargo test test_reinit_input_stream_by_unplugging_a_default_input_device -- --ignored --nocapture
|
||||
# FIXIT: The following test will hang since we don't monitor the alive status of the output device
|
||||
# cargo test test_reinit_output_stream_by_unplugging_a_nondefault_output_device -- --ignored --nocapture
|
||||
cargo test test_reinit_output_stream_by_unplugging_a_default_output_device -- --ignored --nocapture
|
||||
cargo test test_reinit_duplex_stream_by_unplugging_a_nondefault_input_device -- --ignored --nocapture
|
||||
cargo test test_reinit_duplex_stream_by_unplugging_a_default_input_device -- --ignored --nocapture
|
||||
# FIXIT: The following test will hang since we don't monitor the alive status of the output device
|
||||
# cargo test test_reinit_duplex_stream_by_unplugging_a_nondefault_output_device -- --ignored --nocapture
|
||||
cargo test test_reinit_duplex_stream_by_unplugging_a_default_output_device -- --ignored --nocapture
|
||||
|
||||
# Manual Tests
|
||||
# cargo test test_switch_output_device -- --ignored --nocapture
|
||||
# cargo test test_add_then_remove_listeners -- --ignored --nocapture
|
||||
# cargo test test_device_collection_change -- --ignored --nocapture
|
|
@ -3528,3 +3528,6 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> {
|
|||
|
||||
unsafe impl<'ctx> Send for AudioUnitStream<'ctx> {}
|
||||
unsafe impl<'ctx> Sync for AudioUnitStream<'ctx> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
|
@ -0,0 +1,401 @@
|
|||
use super::utils::{
|
||||
test_get_all_devices, test_get_all_onwed_devices, test_get_default_device,
|
||||
test_get_drift_compensations, test_get_master_device, Scope,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
// AggregateDevice::set_sub_devices
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_aggregate_set_sub_devices_for_an_unknown_aggregate_device() {
|
||||
// If aggregate device id is kAudioObjectUnknown, we are unable to set device list.
|
||||
let default_input = test_get_default_device(Scope::Input);
|
||||
let default_output = test_get_default_device(Scope::Output);
|
||||
if default_input.is_none() || default_output.is_none() {
|
||||
panic!("No input or output 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()
|
||||
);
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
||||
// 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]
|
||||
fn test_aggregate_get_sub_devices() {
|
||||
let devices = test_get_all_devices();
|
||||
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).
|
||||
let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
|
||||
// TODO: If the device is a blank aggregate device, then the assertion fails!
|
||||
assert!(!sub_devices.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
||||
// AggregateDevice::set_master_device
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_aggregate_set_master_device_for_an_unknown_aggregate_device() {
|
||||
assert!(AggregateDevice::set_master_device(kAudioObjectUnknown).is_err());
|
||||
}
|
||||
|
||||
// AggregateDevice::activate_clock_drift_compensation
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_aggregate_activate_clock_drift_compensation_for_an_unknown_aggregate_device() {
|
||||
assert!(AggregateDevice::activate_clock_drift_compensation(kAudioObjectUnknown).is_err());
|
||||
}
|
||||
|
||||
// AggregateDevice::destroy_device
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_aggregate_destroy_device_for_unknown_plugin_and_aggregate_devices() {
|
||||
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());
|
||||
}
|
||||
|
||||
// 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 devices = test_get_all_devices();
|
||||
let device = devices.into_iter().find(|dev| dev == &device).unwrap();
|
||||
let uid = get_device_global_uid(device).unwrap().into_string();
|
||||
assert!(uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME));
|
||||
assert!(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());
|
||||
}
|
||||
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// TODO: There may be overlapping devices between input_sub_devices and output_sub_devices,
|
||||
// but now AggregateDevice::set_sub_devices will add them directly.
|
||||
assert_eq!(
|
||||
sub_devices.len(),
|
||||
input_sub_devices.len() + output_sub_devices.len()
|
||||
);
|
||||
for dev in &input_sub_devices {
|
||||
assert!(sub_devices.contains(dev));
|
||||
}
|
||||
for dev in &output_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);
|
||||
for uid in &input_sub_device_uids {
|
||||
assert!(onwed_device_uids.contains(uid));
|
||||
}
|
||||
for uid in &output_sub_device_uids {
|
||||
assert!(onwed_device_uids.contains(uid));
|
||||
}
|
||||
|
||||
assert!(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);
|
||||
if output_device.is_none() {
|
||||
panic!("Need a output device for the test!");
|
||||
}
|
||||
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(device, kAudioObjectUnknown, output_device).is_err());
|
||||
|
||||
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);
|
||||
if input_device.is_none() {
|
||||
panic!("Need a input device for the test!");
|
||||
}
|
||||
let input_device = input_device.unwrap();
|
||||
|
||||
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::destroy_device(plugin, device).is_ok());
|
||||
}
|
||||
|
||||
fn get_device_uids(devices: &Vec<AudioObjectID>) -> Vec<String> {
|
||||
devices
|
||||
.iter()
|
||||
.map(|device| get_device_global_uid(*device).unwrap().into_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
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).is_ok());
|
||||
|
||||
// Check if master is set to the first sub device of the default output device.
|
||||
// TODO: What if the output device in the aggregate device is not 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);
|
||||
assert_eq!(first_output_sub_device_uid, master_device_uid);
|
||||
|
||||
assert!(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() {
|
||||
println!("No output device to test.");
|
||||
return;
|
||||
}
|
||||
|
||||
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
|
||||
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
|
||||
assert!(AggregateDevice::set_master_device(device).is_ok());
|
||||
|
||||
// TODO: it's really weird the aggregate device actually own nothing
|
||||
// but its master device can be set successfully!
|
||||
// The sub devices of this blank aggregate device (by `AggregateDevice::get_sub_devices`)
|
||||
// and the own devices (by `test_get_all_onwed_devices`) is empty since the size returned
|
||||
// from `audio_object_get_property_data_size` is 0.
|
||||
// The CFStringRef of the master device returned from `test_get_master_device` is actually
|
||||
// non-null.
|
||||
|
||||
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
|
||||
}
|
||||
|
||||
fn get_device_uid(id: AudioObjectID) -> String {
|
||||
get_device_global_uid(id).unwrap().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);
|
||||
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;
|
||||
}
|
||||
|
||||
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).is_ok());
|
||||
assert!(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);
|
||||
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 });
|
||||
}
|
||||
|
||||
assert!(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);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Compensate the drift directly without setting master device.
|
||||
assert!(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);
|
||||
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 });
|
||||
}
|
||||
|
||||
assert!(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();
|
||||
|
||||
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());
|
||||
|
||||
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
|
||||
}
|
||||
|
||||
fn get_drift_compensations(devices: &Vec<AudioObjectID>) -> Vec<u32> {
|
||||
assert!(!devices.is_empty());
|
||||
let mut compensations = Vec::new();
|
||||
for device in devices {
|
||||
let compensation = test_get_drift_compensations(*device).unwrap();
|
||||
compensations.push(compensation);
|
||||
}
|
||||
|
||||
compensations
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,36 @@
|
|||
// Copyright © 2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
use super::utils::test_get_default_raw_stream;
|
||||
use super::*;
|
||||
|
||||
// Interface
|
||||
// ============================================================================
|
||||
// Remove these after test_ops_stream_register_device_changed_callback works.
|
||||
#[test]
|
||||
fn test_stream_register_device_changed_callback() {
|
||||
extern "C" fn callback(_: *mut c_void) {}
|
||||
|
||||
test_get_default_raw_stream(|stream| {
|
||||
assert!(stream
|
||||
.register_device_changed_callback(Some(callback))
|
||||
.is_ok());
|
||||
assert!(stream.register_device_changed_callback(None).is_ok());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stream_register_device_changed_callback_twice() {
|
||||
extern "C" fn callback1(_: *mut c_void) {}
|
||||
extern "C" fn callback2(_: *mut c_void) {}
|
||||
|
||||
test_get_default_raw_stream(|stream| {
|
||||
assert!(stream
|
||||
.register_device_changed_callback(Some(callback1))
|
||||
.is_ok());
|
||||
assert!(stream
|
||||
.register_device_changed_callback(Some(callback2))
|
||||
.is_err());
|
||||
});
|
||||
}
|
|
@ -0,0 +1,736 @@
|
|||
// NOTICE:
|
||||
// Avoid running TestDeviceSwitcher with TestDevicePlugger or active full-duplex streams
|
||||
// sequentially!
|
||||
//
|
||||
// The TestDeviceSwitcher cannot work with any test that will create an aggregate device that is
|
||||
// soon being destroyed. The TestDeviceSwitcher will cache the available devices, upon it's
|
||||
// created, as the candidates for the default device. Therefore, those created aggregate devices
|
||||
// may be cached in TestDeviceSwitcher. However, those aggregate devices may be destroyed when
|
||||
// TestDeviceSwitcher is using them or they are in the cached list of TestDeviceSwitcher.
|
||||
//
|
||||
// Running those tests by setting `test-threads=1` doesn't really help (e.g.,
|
||||
// `cargo test test_register_device_changed_callback -- --ignored --nocapture --test-threads=1`).
|
||||
// The aggregate device won't be destroyed immediately when `kAudioPlugInDestroyAggregateDevice`
|
||||
// is set. As a result, the following tests requiring changing the devices will be run separately
|
||||
// in the run_tests.sh script and marked by `ignore` by default.
|
||||
|
||||
use super::utils::{
|
||||
test_create_device_change_listener, test_device_in_scope, test_get_default_device,
|
||||
test_get_devices_in_scope, test_ops_stream_operation, test_set_default_device, Scope,
|
||||
TestDevicePlugger, TestDeviceSwitcher,
|
||||
};
|
||||
use super::*;
|
||||
use std::fmt::Debug;
|
||||
use std::thread;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_switch_device() {
|
||||
test_switch_device_in_scope(Scope::Input);
|
||||
test_switch_device_in_scope(Scope::Output);
|
||||
}
|
||||
|
||||
fn test_switch_device_in_scope(scope: Scope) {
|
||||
// Do nothing if there is no 2 available devices at least.
|
||||
let devices = test_get_devices_in_scope(scope.clone());
|
||||
if devices.len() < 2 {
|
||||
println!("Need 2 devices for {:?} at least.", scope);
|
||||
return;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Switch default device for {:?} while the stream is working.",
|
||||
scope
|
||||
);
|
||||
|
||||
let device_switcher = TestDeviceSwitcher::new(scope.clone());
|
||||
|
||||
let count = Arc::new(Mutex::new(0));
|
||||
let also_count = Arc::clone(&count);
|
||||
let listener = test_create_device_change_listener(scope.clone(), move |_addresses| {
|
||||
let mut cnt = also_count.lock().unwrap();
|
||||
*cnt += 1;
|
||||
NO_ERR
|
||||
});
|
||||
listener.start();
|
||||
|
||||
let mut changed_watcher = Watcher::new(&count);
|
||||
test_get_started_stream_in_scope(scope.clone(), move |_stream| loop {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
changed_watcher.prepare();
|
||||
assert!(device_switcher.next().unwrap());
|
||||
changed_watcher.wait_for_change();
|
||||
if changed_watcher.current_result() >= devices.len() {
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn test_get_started_stream_in_scope<F>(scope: Scope, operation: F)
|
||||
where
|
||||
F: FnOnce(*mut ffi::cubeb_stream),
|
||||
{
|
||||
use std::f32::consts::PI;
|
||||
const SAMPLE_FREQUENCY: u32 = 48_000;
|
||||
|
||||
// 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.format = ffi::CUBEB_SAMPLE_S16NE;
|
||||
stream_params.rate = SAMPLE_FREQUENCY;
|
||||
stream_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
|
||||
stream_params.channels = 1;
|
||||
stream_params.layout = ffi::CUBEB_LAYOUT_MONO;
|
||||
|
||||
let (input_params, output_params) = match scope {
|
||||
Scope::Input => (
|
||||
&mut stream_params as *mut ffi::cubeb_stream_params,
|
||||
ptr::null_mut(),
|
||||
),
|
||||
Scope::Output => (
|
||||
ptr::null_mut(),
|
||||
&mut stream_params as *mut ffi::cubeb_stream_params,
|
||||
),
|
||||
};
|
||||
|
||||
extern "C" fn state_callback(
|
||||
stream: *mut ffi::cubeb_stream,
|
||||
user_ptr: *mut c_void,
|
||||
state: ffi::cubeb_state,
|
||||
) {
|
||||
assert!(!stream.is_null());
|
||||
assert!(!user_ptr.is_null());
|
||||
assert_ne!(state, ffi::CUBEB_STATE_ERROR);
|
||||
}
|
||||
|
||||
extern "C" fn input_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());
|
||||
assert!(!user_ptr.is_null());
|
||||
assert!(!input_buffer.is_null());
|
||||
assert!(output_buffer.is_null());
|
||||
nframes
|
||||
}
|
||||
|
||||
let mut position: i64 = 0; // TODO: Use Atomic instead.
|
||||
|
||||
fn f32_to_i16_sample(x: f32) -> i16 {
|
||||
(x * f32::from(i16::max_value())) as i16
|
||||
}
|
||||
|
||||
extern "C" fn output_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());
|
||||
assert!(!user_ptr.is_null());
|
||||
assert!(input_buffer.is_null());
|
||||
assert!(!output_buffer.is_null());
|
||||
|
||||
let buffer = unsafe {
|
||||
let ptr = output_buffer as *mut i16;
|
||||
let len = nframes as usize;
|
||||
slice::from_raw_parts_mut(ptr, len)
|
||||
};
|
||||
|
||||
let position = unsafe { &mut *(user_ptr as *mut i64) };
|
||||
|
||||
// Generate tone on the fly.
|
||||
for data in buffer.iter_mut() {
|
||||
let t1 = (2.0 * PI * 350.0 * (*position) as f32 / SAMPLE_FREQUENCY as f32).sin();
|
||||
let t2 = (2.0 * PI * 440.0 * (*position) as f32 / SAMPLE_FREQUENCY as f32).sin();
|
||||
*data = f32_to_i16_sample(0.5 * (t1 + t2));
|
||||
*position += 1;
|
||||
}
|
||||
|
||||
nframes
|
||||
}
|
||||
|
||||
test_ops_stream_operation(
|
||||
"stream",
|
||||
ptr::null_mut(), // Use default input device.
|
||||
input_params,
|
||||
ptr::null_mut(), // Use default output device.
|
||||
output_params,
|
||||
4096, // TODO: Get latency by get_min_latency instead ?
|
||||
match scope {
|
||||
Scope::Input => Some(input_data_callback),
|
||||
Scope::Output => Some(output_data_callback),
|
||||
},
|
||||
Some(state_callback),
|
||||
&mut position as *mut i64 as *mut c_void,
|
||||
|stream| {
|
||||
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
|
||||
operation(stream);
|
||||
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_plug_and_unplug_device() {
|
||||
test_plug_and_unplug_device_in_scope(Scope::Input);
|
||||
test_plug_and_unplug_device_in_scope(Scope::Output);
|
||||
}
|
||||
|
||||
fn test_plug_and_unplug_device_in_scope(scope: Scope) {
|
||||
let default_device = test_get_default_device(scope.clone());
|
||||
if default_device.is_none() {
|
||||
println!("No device for {:?} to test", scope);
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Run test for {:?}", scope);
|
||||
println!("NOTICE: The test will hang if the default input or output is an aggregate device.\nWe will fix this later.");
|
||||
|
||||
let default_device = default_device.unwrap();
|
||||
let is_input = test_device_in_scope(default_device, Scope::Input);
|
||||
let is_output = test_device_in_scope(default_device, Scope::Output);
|
||||
|
||||
let mut context = AudioUnitContext::new();
|
||||
|
||||
// Register the devices-changed callbacks.
|
||||
let input_count = Arc::new(Mutex::new(0u32));
|
||||
let also_input_count = Arc::clone(&input_count);
|
||||
let input_mtx_ptr = also_input_count.as_ref() as *const Mutex<u32>;
|
||||
|
||||
assert!(context
|
||||
.register_device_collection_changed(
|
||||
DeviceType::INPUT,
|
||||
Some(input_changed_callback),
|
||||
input_mtx_ptr as *mut c_void,
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let output_count = Arc::new(Mutex::new(0u32));
|
||||
let also_output_count = Arc::clone(&output_count);
|
||||
let output_mtx_ptr = also_output_count.as_ref() as *const Mutex<u32>;
|
||||
|
||||
assert!(context
|
||||
.register_device_collection_changed(
|
||||
DeviceType::OUTPUT,
|
||||
Some(output_changed_callback),
|
||||
output_mtx_ptr as *mut c_void,
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let mut input_watcher = Watcher::new(&input_count);
|
||||
let mut output_watcher = Watcher::new(&output_count);
|
||||
|
||||
let mut device_plugger = TestDevicePlugger::new(scope).unwrap();
|
||||
|
||||
// Simulate adding devices and monitor the devices-changed callbacks.
|
||||
input_watcher.prepare();
|
||||
output_watcher.prepare();
|
||||
|
||||
assert!(device_plugger.plug().is_ok());
|
||||
|
||||
if is_input {
|
||||
input_watcher.wait_for_change();
|
||||
}
|
||||
if is_output {
|
||||
output_watcher.wait_for_change();
|
||||
}
|
||||
|
||||
// Check changed count.
|
||||
check_result(is_input, (1, 0), &input_watcher);
|
||||
check_result(is_output, (1, 0), &output_watcher);
|
||||
|
||||
// Simulate removing devices and monitor the devices-changed callbacks.
|
||||
input_watcher.prepare();
|
||||
output_watcher.prepare();
|
||||
|
||||
assert!(device_plugger.unplug().is_ok());
|
||||
|
||||
if is_input {
|
||||
input_watcher.wait_for_change();
|
||||
}
|
||||
if is_output {
|
||||
output_watcher.wait_for_change();
|
||||
}
|
||||
|
||||
check_result(is_input, (2, 0), &input_watcher);
|
||||
check_result(is_output, (2, 0), &output_watcher);
|
||||
|
||||
extern "C" fn input_changed_callback(context: *mut ffi::cubeb, data: *mut c_void) {
|
||||
println!(
|
||||
"Input device collection @ {:p} is changed. Data @ {:p}",
|
||||
context, data
|
||||
);
|
||||
let count = unsafe { &*(data as *const Mutex<u32>) };
|
||||
{
|
||||
let mut guard = count.lock().unwrap();
|
||||
*guard += 1;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn output_changed_callback(context: *mut ffi::cubeb, data: *mut c_void) {
|
||||
println!(
|
||||
"output device collection @ {:p} is changed. Data @ {:p}",
|
||||
context, data
|
||||
);
|
||||
let count = unsafe { &*(data as *const Mutex<u32>) };
|
||||
{
|
||||
let mut guard = count.lock().unwrap();
|
||||
*guard += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_result<T: Clone + Debug + PartialEq>(
|
||||
in_scope: bool,
|
||||
expected: (T, T),
|
||||
watcher: &Watcher<T>,
|
||||
) {
|
||||
assert_eq!(
|
||||
watcher.current_result(),
|
||||
if in_scope { expected.0 } else { expected.1 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_register_device_changed_callback_to_check_default_device_changed_input() {
|
||||
test_register_device_changed_callback_to_check_default_device_changed(StreamType::INPUT);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_register_device_changed_callback_to_check_default_device_changed_output() {
|
||||
test_register_device_changed_callback_to_check_default_device_changed(StreamType::OUTPUT);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_register_device_changed_callback_to_check_default_device_changed_duplex() {
|
||||
test_register_device_changed_callback_to_check_default_device_changed(StreamType::DUPLEX);
|
||||
}
|
||||
|
||||
fn test_register_device_changed_callback_to_check_default_device_changed(stm_type: StreamType) {
|
||||
println!("NOTICE: The test will hang if the default input or output is an aggregate device.\nWe will fix this later.");
|
||||
|
||||
let input_devices = test_get_devices_in_scope(Scope::Input).len();
|
||||
let output_devices = test_get_devices_in_scope(Scope::Output).len();
|
||||
|
||||
let input_available = input_devices >= 2;
|
||||
let output_available = output_devices >= 2;
|
||||
|
||||
let run_available = match stm_type {
|
||||
StreamType::INPUT => input_available,
|
||||
StreamType::OUTPUT => output_available,
|
||||
StreamType::DUPLEX => input_available | output_available,
|
||||
_ => {
|
||||
println!("Only test input, output, or duplex stream!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !run_available {
|
||||
println!("No enough devices to run the test!");
|
||||
}
|
||||
|
||||
let changed_count = Arc::new(Mutex::new(0u32));
|
||||
let also_changed_count = Arc::clone(&changed_count);
|
||||
let mtx_ptr = also_changed_count.as_ref() as *const Mutex<u32>;
|
||||
|
||||
let input_count = if stm_type.contains(StreamType::INPUT) {
|
||||
input_devices
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let output_count = if stm_type.contains(StreamType::OUTPUT) {
|
||||
output_devices
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let input_device_switcher = TestDeviceSwitcher::new(Scope::Input);
|
||||
let output_device_switcher = TestDeviceSwitcher::new(Scope::Output);
|
||||
|
||||
test_get_stream_with_device_changed_callback(
|
||||
"stream: test callback for default device changed",
|
||||
stm_type,
|
||||
None, // Use default input device.
|
||||
None, // Use default output device.
|
||||
mtx_ptr as *mut c_void,
|
||||
callback,
|
||||
|stream| {
|
||||
// If the duplex stream uses different input and output device,
|
||||
// an aggregate device will be created and it will work for this duplex stream.
|
||||
// This aggregate device will be added into the device list, but it won't
|
||||
// be assigned to the default device, since the device list for setting
|
||||
// default device is cached upon {input, output}_device_switcher is initialized.
|
||||
|
||||
let mut changed_watcher = Watcher::new(&changed_count);
|
||||
|
||||
for _ in 0..input_count {
|
||||
// While the stream is re-initializing for the default device switch,
|
||||
// switching for the default device again will be ignored.
|
||||
while stream.switching_device.load(atomic::Ordering::SeqCst) {}
|
||||
changed_watcher.prepare();
|
||||
assert!(input_device_switcher.next().unwrap());
|
||||
changed_watcher.wait_for_change();
|
||||
}
|
||||
|
||||
for _ in 0..output_count {
|
||||
// While the stream is re-initializing for the default device switch,
|
||||
// switching for the default device again will be ignored.
|
||||
while stream.switching_device.load(atomic::Ordering::SeqCst) {}
|
||||
changed_watcher.prepare();
|
||||
assert!(output_device_switcher.next().unwrap());
|
||||
changed_watcher.wait_for_change();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
extern "C" fn callback(data: *mut c_void) {
|
||||
println!("Device change callback. data @ {:p}", data);
|
||||
let count = unsafe { &*(data as *const Mutex<u32>) };
|
||||
{
|
||||
let mut guard = count.lock().unwrap();
|
||||
*guard += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_destroy_input_stream_after_unplugging_a_nondefault_input_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::INPUT, Scope::Input, false, 0);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_destroy_input_stream_after_unplugging_a_default_input_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::INPUT, Scope::Input, true, 0);
|
||||
}
|
||||
|
||||
// FIXIT: The following test will hang since we don't monitor the alive status of the output device
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_destroy_output_stream_after_unplugging_a_nondefault_output_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::OUTPUT, Scope::Output, false, 0);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_destroy_output_stream_after_unplugging_a_default_output_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::OUTPUT, Scope::Output, true, 0);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_destroy_duplex_stream_after_unplugging_a_nondefault_input_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Input, false, 0);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_destroy_duplex_stream_after_unplugging_a_default_input_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Input, true, 0);
|
||||
}
|
||||
|
||||
// FIXIT: The following test will hang since we don't monitor the alive status of the output device
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_destroy_duplex_stream_after_unplugging_a_nondefault_output_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Output, false, 0);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_destroy_duplex_stream_after_unplugging_a_default_output_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Output, true, 0);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_reinit_input_stream_by_unplugging_a_nondefault_input_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::INPUT, Scope::Input, false, 500);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_reinit_input_stream_by_unplugging_a_default_input_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::INPUT, Scope::Input, true, 500);
|
||||
}
|
||||
|
||||
// FIXIT: The following test will hang since we don't monitor the alive status of the output device
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_reinit_output_stream_by_unplugging_a_nondefault_output_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::OUTPUT, Scope::Output, false, 500);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_reinit_output_stream_by_unplugging_a_default_output_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::OUTPUT, Scope::Output, true, 500);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_reinit_duplex_stream_by_unplugging_a_nondefault_input_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Input, false, 500);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_reinit_duplex_stream_by_unplugging_a_default_input_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Input, true, 500);
|
||||
}
|
||||
|
||||
// FIXIT: The following test will hang since we don't monitor the alive status of the output device
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_reinit_duplex_stream_by_unplugging_a_nondefault_output_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Output, false, 500);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_reinit_duplex_stream_by_unplugging_a_default_output_device() {
|
||||
test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Output, true, 500);
|
||||
}
|
||||
|
||||
fn test_unplug_a_device_on_an_active_stream(
|
||||
stream_type: StreamType,
|
||||
device_scope: Scope,
|
||||
set_device_to_default: bool,
|
||||
wait_for_reinit_millis: u64,
|
||||
) {
|
||||
let has_input = test_get_default_device(Scope::Input).is_some();
|
||||
let has_output = test_get_default_device(Scope::Output).is_some();
|
||||
|
||||
if stream_type.contains(StreamType::INPUT) && !has_input {
|
||||
println!("No input device for input or duplex stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
if stream_type.contains(StreamType::OUTPUT) && !has_output {
|
||||
println!("No output device for ouput or duplex stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut plugger = TestDevicePlugger::new(device_scope.clone()).unwrap();
|
||||
assert!(plugger.plug().is_ok());
|
||||
assert_ne!(plugger.get_device_id(), kAudioObjectUnknown);
|
||||
if set_device_to_default {
|
||||
assert!(test_set_default_device(plugger.get_device_id(), device_scope.clone()).unwrap());
|
||||
}
|
||||
|
||||
let (input_device, output_device) = match device_scope {
|
||||
Scope::Input => (Some(plugger.get_device_id()), None),
|
||||
Scope::Output => (None, Some(plugger.get_device_id())),
|
||||
};
|
||||
|
||||
let changed_count = Arc::new(Mutex::new(0u32));
|
||||
let also_changed_count = Arc::clone(&changed_count);
|
||||
let mtx_ptr = also_changed_count.as_ref() as *const Mutex<u32>;
|
||||
|
||||
test_get_stream_with_device_changed_callback(
|
||||
"stream: test stream reinit/destroy after unplugging a device",
|
||||
stream_type,
|
||||
input_device,
|
||||
output_device,
|
||||
mtx_ptr as *mut c_void,
|
||||
callback,
|
||||
|stream| {
|
||||
let mut changed_watcher = Watcher::new(&changed_count);
|
||||
changed_watcher.prepare();
|
||||
stream.start();
|
||||
// Wait for stream data callback.
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
assert!(plugger.unplug().is_ok());
|
||||
changed_watcher.wait_for_change();
|
||||
// Wait for stream re-initialization or destroy stream directly.
|
||||
if wait_for_reinit_millis > 0 {
|
||||
thread::sleep(Duration::from_millis(wait_for_reinit_millis));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
extern "C" fn callback(data: *mut c_void) {
|
||||
println!("Device change callback. data @ {:p}", data);
|
||||
let count = unsafe { &*(data as *const Mutex<u32>) };
|
||||
{
|
||||
let mut guard = count.lock().unwrap();
|
||||
*guard += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Watcher<T: Clone + PartialEq> {
|
||||
watching: Arc<Mutex<T>>,
|
||||
current: Option<T>,
|
||||
}
|
||||
|
||||
impl<T: Clone + PartialEq> Watcher<T> {
|
||||
fn new(value: &Arc<Mutex<T>>) -> Self {
|
||||
Self {
|
||||
watching: Arc::clone(value),
|
||||
current: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(&mut self) {
|
||||
self.current = Some(self.current_result());
|
||||
}
|
||||
|
||||
fn wait_for_change(&self) {
|
||||
loop {
|
||||
if self.current_result() != self.current.clone().unwrap() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_result(&self) -> T {
|
||||
let guard = self.watching.lock().unwrap();
|
||||
guard.clone()
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct StreamType: u8 {
|
||||
const INPUT = 0b01;
|
||||
const OUTPUT = 0b10;
|
||||
const DUPLEX = Self::INPUT.bits | Self::OUTPUT.bits;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_stream_with_device_changed_callback<F>(
|
||||
name: &'static str,
|
||||
stm_type: StreamType,
|
||||
input_device: Option<AudioObjectID>,
|
||||
output_device: Option<AudioObjectID>,
|
||||
data: *mut c_void,
|
||||
callback: extern "C" fn(*mut c_void),
|
||||
operation: F,
|
||||
) where
|
||||
F: FnOnce(&mut AudioUnitStream),
|
||||
{
|
||||
let mut input_params = get_dummy_stream_params(Scope::Input);
|
||||
let mut output_params = get_dummy_stream_params(Scope::Output);
|
||||
|
||||
let in_params = if stm_type.contains(StreamType::INPUT) {
|
||||
&mut input_params as *mut ffi::cubeb_stream_params
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
let out_params = if stm_type.contains(StreamType::OUTPUT) {
|
||||
&mut output_params as *mut ffi::cubeb_stream_params
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
let in_device = if let Some(id) = input_device {
|
||||
id as ffi::cubeb_devid
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
let out_device = if let Some(id) = output_device {
|
||||
id as ffi::cubeb_devid
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
|
||||
test_ops_default_callbacks_stream_operation(
|
||||
name,
|
||||
in_device,
|
||||
in_params,
|
||||
out_device,
|
||||
out_params,
|
||||
data,
|
||||
|stream| {
|
||||
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
|
||||
assert!(stm.register_device_changed_callback(Some(callback)).is_ok());
|
||||
operation(stm);
|
||||
assert!(stm.register_device_changed_callback(None).is_ok());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn test_ops_default_callbacks_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,
|
||||
data: *mut c_void,
|
||||
operation: F,
|
||||
) where
|
||||
F: FnOnce(*mut ffi::cubeb_stream),
|
||||
{
|
||||
test_ops_stream_operation(
|
||||
name,
|
||||
input_device,
|
||||
input_stream_params,
|
||||
output_device,
|
||||
output_stream_params,
|
||||
4096, // TODO: Get latency by get_min_latency instead ?
|
||||
Some(data_callback),
|
||||
Some(state_callback),
|
||||
data,
|
||||
operation,
|
||||
);
|
||||
|
||||
extern "C" fn state_callback(
|
||||
stream: *mut ffi::cubeb_stream,
|
||||
_user_ptr: *mut c_void,
|
||||
state: ffi::cubeb_state,
|
||||
) {
|
||||
assert!(!stream.is_null());
|
||||
assert_ne!(state, ffi::CUBEB_STATE_ERROR);
|
||||
}
|
||||
|
||||
extern "C" fn 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
|
||||
}
|
||||
}
|
||||
|
||||
// The stream format for input and output must be same.
|
||||
const STREAM_FORMAT: u32 = ffi::CUBEB_SAMPLE_FLOAT32NE;
|
||||
|
||||
fn get_dummy_stream_params(scope: Scope) -> ffi::cubeb_stream_params {
|
||||
// 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_NONE;
|
||||
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),
|
||||
};
|
||||
stream_params.format = format;
|
||||
stream_params.rate = rate;
|
||||
stream_params.channels = channels;
|
||||
stream_params.layout = layout;
|
||||
stream_params
|
||||
}
|
|
@ -0,0 +1,422 @@
|
|||
use super::utils::{test_get_default_device, Scope};
|
||||
use super::*;
|
||||
|
||||
// get_device_global_uid
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
fn test_get_device_global_uid() {
|
||||
// Input device.
|
||||
if let Some(input) = test_get_default_device(Scope::Input) {
|
||||
let uid = 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 = uid.into_string();
|
||||
assert!(!uid.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_global_uid_by_unknwon_device() {
|
||||
// Unknown device.
|
||||
assert!(get_device_global_uid(kAudioObjectUnknown).is_err());
|
||||
}
|
||||
|
||||
// get_device_uid
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
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 = 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 = uid.into_string();
|
||||
assert!(!uid.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_uid_by_unknwon_device() {
|
||||
// Unknown device.
|
||||
assert!(get_device_uid(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_source
|
||||
// ------------------------------------
|
||||
// Some USB headsets (e.g., Plantronic .Audio 628) fails to get data source.
|
||||
#[test]
|
||||
fn test_get_device_source() {
|
||||
if let Some(device) = test_get_default_device(Scope::Input) {
|
||||
if let Ok(source) = get_device_source(device, DeviceType::INPUT) {
|
||||
println!(
|
||||
"input: {:X}, {:?}",
|
||||
source,
|
||||
convert_uint32_into_string(source)
|
||||
);
|
||||
} else {
|
||||
println!("No input data source.");
|
||||
}
|
||||
} else {
|
||||
println!("No input device.");
|
||||
}
|
||||
|
||||
if let Some(device) = test_get_default_device(Scope::Output) {
|
||||
if let Ok(source) = get_device_source(device, DeviceType::OUTPUT) {
|
||||
println!(
|
||||
"output: {:X}, {:?}",
|
||||
source,
|
||||
convert_uint32_into_string(source)
|
||||
);
|
||||
} else {
|
||||
println!("No output data source.");
|
||||
}
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_source_by_unknown_device() {
|
||||
assert!(get_device_source(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_source_name
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
fn test_get_device_source_name() {
|
||||
if let Some(device) = test_get_default_device(Scope::Input) {
|
||||
if let Ok(name) = get_device_source_name(device, DeviceType::INPUT) {
|
||||
println!("input: {}", name.into_string());
|
||||
} else {
|
||||
println!("No input data source name.");
|
||||
}
|
||||
} else {
|
||||
println!("No input device.");
|
||||
}
|
||||
|
||||
if let Some(device) = test_get_default_device(Scope::Output) {
|
||||
if let Ok(name) = get_device_source_name(device, DeviceType::OUTPUT) {
|
||||
println!("output: {}", name.into_string());
|
||||
} else {
|
||||
println!("No output data source name.");
|
||||
}
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_source_name_by_unknown_device() {
|
||||
assert!(get_device_source_name(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_name
|
||||
// ------------------------------------
|
||||
#[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();
|
||||
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();
|
||||
println!("output device name: {}", name.into_string());
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_name_by_unknown_device() {
|
||||
assert!(get_device_name(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_label
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
fn test_get_device_label() {
|
||||
if let Some(device) = test_get_default_device(Scope::Input) {
|
||||
let name = 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();
|
||||
println!("output device label: {}", name.into_string());
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_label_by_unknown_device() {
|
||||
assert!(get_device_label(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_manufacturer
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
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)
|
||||
.map(|name| name.into_string())
|
||||
.unwrap_or_else(|e| format!("Error: {}", e));
|
||||
println!("input device vendor: {}", name);
|
||||
} else {
|
||||
println!("No input device.");
|
||||
}
|
||||
|
||||
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));
|
||||
println!("output device vendor: {}", name);
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_manufacturer_by_unknown_device() {
|
||||
assert!(get_device_manufacturer(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_buffer_frame_size_range
|
||||
// ------------------------------------
|
||||
#[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();
|
||||
println!(
|
||||
"range of input buffer frame size: {}-{}",
|
||||
range.mMinimum, range.mMaximum
|
||||
);
|
||||
} else {
|
||||
println!("No input device.");
|
||||
}
|
||||
|
||||
if let Some(device) = test_get_default_device(Scope::Output) {
|
||||
let range = get_device_buffer_frame_size_range(device, DeviceType::OUTPUT).unwrap();
|
||||
println!(
|
||||
"range of output buffer frame size: {}-{}",
|
||||
range.mMinimum, range.mMaximum
|
||||
);
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
||||
// get_device_latency
|
||||
// ------------------------------------
|
||||
#[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();
|
||||
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();
|
||||
println!("latency of output device: {}", latency);
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_latency_by_unknown_device() {
|
||||
assert!(get_device_latency(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_streams
|
||||
// ------------------------------------
|
||||
#[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();
|
||||
println!("streams on the input device: {:?}", streams);
|
||||
} else {
|
||||
println!("No input device.");
|
||||
}
|
||||
|
||||
if let Some(device) = test_get_default_device(Scope::Output) {
|
||||
let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
|
||||
println!("streams on the output device: {:?}", streams);
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_streams_by_unknown_device() {
|
||||
assert!(get_device_streams(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_sample_rate
|
||||
// ------------------------------------
|
||||
#[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();
|
||||
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();
|
||||
println!("output sample rate: {}", rate);
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_sample_rate_by_unknown_device() {
|
||||
assert!(get_device_sample_rate(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_ranges_of_device_sample_rate
|
||||
// ------------------------------------
|
||||
#[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();
|
||||
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();
|
||||
println!("ranges of output sample rate: {:?}", ranges);
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
||||
// get_device_stream_format
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
fn test_get_device_stream_format() {
|
||||
if let Some(device) = test_get_default_device(Scope::Input) {
|
||||
let format = get_device_stream_format(device, DeviceType::INPUT).unwrap();
|
||||
println!("input stream format: {:?}", format);
|
||||
} else {
|
||||
println!("No input device.");
|
||||
}
|
||||
|
||||
if let Some(device) = test_get_default_device(Scope::Output) {
|
||||
let format = get_device_stream_format(device, DeviceType::OUTPUT).unwrap();
|
||||
println!("output stream format: {:?}", format);
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_stream_format_by_unknown_device() {
|
||||
assert!(get_device_stream_format(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_device_stream_configuration
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
fn test_get_device_stream_configuration() {
|
||||
if let Some(device) = test_get_default_device(Scope::Input) {
|
||||
let buffers = get_device_stream_configuration(device, DeviceType::INPUT).unwrap();
|
||||
println!("input stream config: {:?}", buffers);
|
||||
dbg!(buffers);
|
||||
} else {
|
||||
println!("No input device.");
|
||||
}
|
||||
|
||||
if let Some(device) = test_get_default_device(Scope::Output) {
|
||||
let buffers = get_device_stream_configuration(device, DeviceType::OUTPUT).unwrap();
|
||||
println!("output stream config: {:?}", buffers);
|
||||
dbg!(buffers);
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_device_stream_configuration_by_unknown_device() {
|
||||
assert!(get_device_stream_configuration(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
||||
|
||||
// get_stream_latency
|
||||
// ------------------------------------
|
||||
#[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();
|
||||
for stream in streams {
|
||||
let latency = get_stream_latency(stream, DeviceType::INPUT).unwrap();
|
||||
println!("latency of the input stream {} is {}", stream, latency);
|
||||
}
|
||||
} 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 {
|
||||
let latency = get_stream_latency(stream, DeviceType::OUTPUT).unwrap();
|
||||
println!("latency of the output stream {} is {}", stream, latency);
|
||||
}
|
||||
} else {
|
||||
println!("No output device.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_get_stream_latency_by_unknown_device() {
|
||||
assert!(get_stream_latency(kAudioObjectUnknown, DeviceType::INPUT).is_err());
|
||||
}
|
|
@ -0,0 +1,547 @@
|
|||
use super::utils::{
|
||||
test_get_default_device, test_ops_context_operation, test_ops_stream_operation, Scope,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
// Context Operations
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
#[test]
|
||||
fn test_ops_context_init_and_destroy() {
|
||||
test_ops_context_operation("context: init and destroy", |_context_ptr| {});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_backend_id() {
|
||||
test_ops_context_operation("context: backend id", |context_ptr| {
|
||||
let backend = unsafe {
|
||||
let ptr = OPS.get_backend_id.unwrap()(context_ptr);
|
||||
CStr::from_ptr(ptr).to_string_lossy().into_owned()
|
||||
};
|
||||
assert_eq!(backend, "audiounit-rust");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_max_channel_count() {
|
||||
test_ops_context_operation("context: max channel count", |context_ptr| {
|
||||
let output_exists = test_get_default_device(Scope::Output).is_some();
|
||||
let mut max_channel_count = 0;
|
||||
let r = unsafe { OPS.get_max_channel_count.unwrap()(context_ptr, &mut max_channel_count) };
|
||||
if output_exists {
|
||||
assert_eq!(r, ffi::CUBEB_OK);
|
||||
assert_ne!(max_channel_count, 0);
|
||||
} else {
|
||||
assert_eq!(r, ffi::CUBEB_ERROR);
|
||||
assert_eq!(max_channel_count, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_min_latency() {
|
||||
test_ops_context_operation("context: min latency", |context_ptr| {
|
||||
let output_exists = test_get_default_device(Scope::Output).is_some();
|
||||
let params = ffi::cubeb_stream_params::default();
|
||||
let mut latency = u32::max_value();
|
||||
let r = unsafe { OPS.get_min_latency.unwrap()(context_ptr, params, &mut latency) };
|
||||
if output_exists {
|
||||
assert_eq!(r, ffi::CUBEB_OK);
|
||||
assert!(latency >= SAFE_MIN_LATENCY_FRAMES);
|
||||
assert!(SAFE_MAX_LATENCY_FRAMES >= latency);
|
||||
} else {
|
||||
assert_eq!(r, ffi::CUBEB_ERROR);
|
||||
assert_eq!(latency, u32::max_value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_preferred_sample_rate() {
|
||||
test_ops_context_operation("context: preferred sample rate", |context_ptr| {
|
||||
let output_exists = test_get_default_device(Scope::Output).is_some();
|
||||
let mut rate = u32::max_value();
|
||||
let r = unsafe { OPS.get_preferred_sample_rate.unwrap()(context_ptr, &mut rate) };
|
||||
if output_exists {
|
||||
assert_eq!(r, ffi::CUBEB_OK);
|
||||
assert_ne!(rate, u32::max_value());
|
||||
assert_ne!(rate, 0);
|
||||
} else {
|
||||
assert_eq!(r, ffi::CUBEB_ERROR);
|
||||
assert_eq!(rate, u32::max_value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_enumerate_devices_unknown() {
|
||||
test_ops_context_operation("context: enumerate devices (unknown)", |context_ptr| {
|
||||
let mut coll = ffi::cubeb_device_collection {
|
||||
device: ptr::null_mut(),
|
||||
count: 0,
|
||||
};
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.enumerate_devices.unwrap()(
|
||||
context_ptr,
|
||||
ffi::CUBEB_DEVICE_TYPE_UNKNOWN,
|
||||
&mut coll,
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert_eq!(coll.count, 0);
|
||||
assert_eq!(coll.device, ptr::null_mut());
|
||||
assert_eq!(
|
||||
unsafe { OPS.device_collection_destroy.unwrap()(context_ptr, &mut coll) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert_eq!(coll.count, 0);
|
||||
assert_eq!(coll.device, ptr::null_mut());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_enumerate_devices_input() {
|
||||
test_ops_context_operation("context: enumerate devices (input)", |context_ptr| {
|
||||
let having_input = test_get_default_device(Scope::Input).is_some();
|
||||
let mut coll = ffi::cubeb_device_collection {
|
||||
device: ptr::null_mut(),
|
||||
count: 0,
|
||||
};
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.enumerate_devices.unwrap()(context_ptr, ffi::CUBEB_DEVICE_TYPE_INPUT, &mut coll)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
if having_input {
|
||||
assert_ne!(coll.count, 0);
|
||||
assert_ne!(coll.device, ptr::null_mut());
|
||||
} else {
|
||||
assert_eq!(coll.count, 0);
|
||||
assert_eq!(coll.device, ptr::null_mut());
|
||||
}
|
||||
assert_eq!(
|
||||
unsafe { OPS.device_collection_destroy.unwrap()(context_ptr, &mut coll) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert_eq!(coll.count, 0);
|
||||
assert_eq!(coll.device, ptr::null_mut());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_enumerate_devices_output() {
|
||||
test_ops_context_operation("context: enumerate devices (output)", |context_ptr| {
|
||||
let output_exists = test_get_default_device(Scope::Output).is_some();
|
||||
let mut coll = ffi::cubeb_device_collection {
|
||||
device: ptr::null_mut(),
|
||||
count: 0,
|
||||
};
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.enumerate_devices.unwrap()(
|
||||
context_ptr,
|
||||
ffi::CUBEB_DEVICE_TYPE_OUTPUT,
|
||||
&mut coll,
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
if output_exists {
|
||||
assert_ne!(coll.count, 0);
|
||||
assert_ne!(coll.device, ptr::null_mut());
|
||||
} else {
|
||||
assert_eq!(coll.count, 0);
|
||||
assert_eq!(coll.device, ptr::null_mut());
|
||||
}
|
||||
assert_eq!(
|
||||
unsafe { OPS.device_collection_destroy.unwrap()(context_ptr, &mut coll) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert_eq!(coll.count, 0);
|
||||
assert_eq!(coll.device, ptr::null_mut());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_device_collection_destroy() {
|
||||
// Destroy a dummy device collection, without calling enumerate_devices to allocate memory for the device collection
|
||||
test_ops_context_operation("context: device collection destroy", |context_ptr| {
|
||||
let mut coll = ffi::cubeb_device_collection {
|
||||
device: ptr::null_mut(),
|
||||
count: 0,
|
||||
};
|
||||
assert_eq!(
|
||||
unsafe { OPS.device_collection_destroy.unwrap()(context_ptr, &mut coll) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert_eq!(coll.device, ptr::null_mut());
|
||||
assert_eq!(coll.count, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_register_device_collection_changed_unknown() {
|
||||
test_ops_context_operation(
|
||||
"context: register device collection changed (unknown)",
|
||||
|context_ptr| {
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
ffi::CUBEB_DEVICE_TYPE_UNKNOWN,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_ERROR_INVALID_PARAMETER
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_register_device_collection_changed_twice_input() {
|
||||
test_ops_context_register_device_collection_changed_twice(ffi::CUBEB_DEVICE_TYPE_INPUT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_register_device_collection_changed_twice_output() {
|
||||
test_ops_context_register_device_collection_changed_twice(ffi::CUBEB_DEVICE_TYPE_OUTPUT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_register_device_collection_changed_twice_inout() {
|
||||
test_ops_context_register_device_collection_changed_twice(
|
||||
ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT,
|
||||
);
|
||||
}
|
||||
|
||||
fn test_ops_context_register_device_collection_changed_twice(devtype: u32) {
|
||||
extern "C" fn callback(_: *mut ffi::cubeb, _: *mut c_void) {}
|
||||
let label_input: &'static str = "context: register device collection changed twice (input)";
|
||||
let label_output: &'static str = "context: register device collection changed twice (output)";
|
||||
let label_inout: &'static str = "context: register device collection changed twice (inout)";
|
||||
let label = if devtype == ffi::CUBEB_DEVICE_TYPE_INPUT {
|
||||
label_input
|
||||
} else if devtype == ffi::CUBEB_DEVICE_TYPE_OUTPUT {
|
||||
label_output
|
||||
} else if devtype == ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT {
|
||||
label_inout
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
test_ops_context_operation(label, |context_ptr| {
|
||||
// Register a callback within the defined scope.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
devtype,
|
||||
Some(callback),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
devtype,
|
||||
Some(callback),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_ERROR_INVALID_PARAMETER
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_context_register_device_collection_changed() {
|
||||
extern "C" fn callback(_: *mut ffi::cubeb, _: *mut c_void) {}
|
||||
test_ops_context_operation(
|
||||
"context: register device collection changed",
|
||||
|context_ptr| {
|
||||
let devtypes: [ffi::cubeb_device_type; 3] = [
|
||||
ffi::CUBEB_DEVICE_TYPE_INPUT,
|
||||
ffi::CUBEB_DEVICE_TYPE_OUTPUT,
|
||||
ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT,
|
||||
];
|
||||
|
||||
for devtype in &devtypes {
|
||||
// Register a callback in the defined scoped.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
*devtype,
|
||||
Some(callback),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
|
||||
// Unregister all callbacks regardless of the scope.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
|
||||
// Register callback in the defined scoped again.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
*devtype,
|
||||
Some(callback),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
|
||||
// Unregister callback within the defined scope.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
*devtype,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_ops_context_register_device_collection_changed_manual() {
|
||||
test_ops_context_operation(
|
||||
"(manual) context: register device collection changed",
|
||||
|context_ptr| {
|
||||
println!("context @ {:p}", context_ptr);
|
||||
|
||||
struct Data {
|
||||
context: *mut ffi::cubeb,
|
||||
touched: u32, // TODO: Use AtomicU32 instead
|
||||
}
|
||||
|
||||
extern "C" fn input_callback(context: *mut ffi::cubeb, user: *mut c_void) {
|
||||
println!("input > context @ {:p}", context);
|
||||
let data = unsafe { &mut (*(user as *mut Data)) };
|
||||
assert_eq!(context, data.context);
|
||||
data.touched += 1;
|
||||
}
|
||||
|
||||
extern "C" fn output_callback(context: *mut ffi::cubeb, user: *mut c_void) {
|
||||
println!("output > context @ {:p}", context);
|
||||
let data = unsafe { &mut (*(user as *mut Data)) };
|
||||
assert_eq!(context, data.context);
|
||||
data.touched += 1;
|
||||
}
|
||||
|
||||
let mut data = Data {
|
||||
context: context_ptr,
|
||||
touched: 0,
|
||||
};
|
||||
|
||||
// Register a callback for input scope.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
ffi::CUBEB_DEVICE_TYPE_INPUT,
|
||||
Some(input_callback),
|
||||
&mut data as *mut Data as *mut c_void,
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
|
||||
// Register a callback for output scope.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.register_device_collection_changed.unwrap()(
|
||||
context_ptr,
|
||||
ffi::CUBEB_DEVICE_TYPE_OUTPUT,
|
||||
Some(output_callback),
|
||||
&mut data as *mut Data as *mut c_void,
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
|
||||
while data.touched < 2 {}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Stream Operations
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
fn test_default_output_stream_operation<F>(name: &'static str, 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 output_params = ffi::cubeb_stream_params::default();
|
||||
output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
|
||||
output_params.rate = 44100;
|
||||
output_params.channels = 2;
|
||||
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
|
||||
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
test_ops_stream_operation(
|
||||
name,
|
||||
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 ?
|
||||
None, // No data callback.
|
||||
None, // No state callback.
|
||||
ptr::null_mut(), // No user data pointer.
|
||||
operation,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_init_and_destroy() {
|
||||
test_default_output_stream_operation("stream: init and destroy", |_stream| {});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_start() {
|
||||
test_default_output_stream_operation("stream: start", |stream| {
|
||||
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_stop() {
|
||||
test_default_output_stream_operation("stream: stop", |stream| {
|
||||
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_reset_default_device() {
|
||||
test_default_output_stream_operation("stream: reset default device", |stream| {
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_reset_default_device.unwrap()(stream) },
|
||||
ffi::CUBEB_ERROR_NOT_SUPPORTED
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_position() {
|
||||
test_default_output_stream_operation("stream: position", |stream| {
|
||||
let mut position = u64::max_value();
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_get_position.unwrap()(stream, &mut position) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert_eq!(position, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_latency() {
|
||||
test_default_output_stream_operation("stream: latency", |stream| {
|
||||
let mut latency = u32::max_value();
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_get_latency.unwrap()(stream, &mut latency) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert_ne!(latency, u32::max_value());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_set_volume() {
|
||||
test_default_output_stream_operation("stream: set volume", |stream| {
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_set_volume.unwrap()(stream, 0.5) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_current_device() {
|
||||
test_default_output_stream_operation("stream: get current device and destroy it", |stream| {
|
||||
if test_get_default_device(Scope::Input).is_none()
|
||||
|| test_get_default_device(Scope::Output).is_none()
|
||||
{
|
||||
println!("stream_get_current_device only works when the machine has both input and output devices");
|
||||
return;
|
||||
}
|
||||
let mut device: *mut ffi::cubeb_device = ptr::null_mut();
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_get_current_device.unwrap()(stream, &mut device) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert!(!device.is_null());
|
||||
// Uncomment the below to print out the results.
|
||||
// let deviceref = unsafe { DeviceRef::from_ptr(device) };
|
||||
// println!(
|
||||
// "output: {}",
|
||||
// deviceref.output_name().unwrap_or("(no device name)")
|
||||
// );
|
||||
// println!(
|
||||
// "input: {}",
|
||||
// deviceref.input_name().unwrap_or("(no device name)")
|
||||
// );
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_device_destroy.unwrap()(stream, device) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_stream_device_destroy() {
|
||||
test_default_output_stream_operation("stream: destroy null device", |stream| {
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_device_destroy.unwrap()(stream, ptr::null_mut()) },
|
||||
ffi::CUBEB_OK // It returns OK anyway.
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Enable this after cubeb-rs version is updated to one that implements
|
||||
// stream_register_device_changed_callback operation.
|
||||
// #[test]
|
||||
// fn test_ops_stream_register_device_changed_callback() {
|
||||
// extern "C" fn callback(_: *mut c_void) {}
|
||||
|
||||
// test_default_output_stream_operation("stream: register device changed callback", |stream| {
|
||||
// assert_eq!(
|
||||
// unsafe {
|
||||
// OPS.stream_register_device_changed_callback.unwrap()(
|
||||
// stream,
|
||||
// Some(callback)
|
||||
// )
|
||||
// },
|
||||
// ffi::CUBEB_OK
|
||||
// );
|
||||
// });
|
||||
// }
|
|
@ -0,0 +1,253 @@
|
|||
use super::utils::{
|
||||
test_get_default_device, test_get_default_raw_stream, test_get_devices_in_scope,
|
||||
test_ops_stream_operation, Scope, TestDeviceSwitcher,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_switch_output_device() {
|
||||
use std::f32::consts::PI;
|
||||
use std::io;
|
||||
|
||||
const SAMPLE_FREQUENCY: u32 = 48_000;
|
||||
|
||||
// Do nothing if there is no 2 available output devices at least.
|
||||
let devices = test_get_devices_in_scope(Scope::Output);
|
||||
if devices.len() < 2 {
|
||||
println!("Need 2 output devices at least.");
|
||||
return;
|
||||
}
|
||||
|
||||
let output_device_switcher = TestDeviceSwitcher::new(Scope::Output);
|
||||
|
||||
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
|
||||
// (in the comments).
|
||||
let mut output_params = ffi::cubeb_stream_params::default();
|
||||
output_params.format = ffi::CUBEB_SAMPLE_S16NE;
|
||||
output_params.rate = SAMPLE_FREQUENCY;
|
||||
output_params.channels = 1;
|
||||
output_params.layout = ffi::CUBEB_LAYOUT_MONO;
|
||||
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
// Used to calculate the tone's wave.
|
||||
let mut position: i64 = 0; // TODO: Use Atomic instead.
|
||||
|
||||
test_ops_stream_operation(
|
||||
"stream: North American dial tone",
|
||||
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(data_callback),
|
||||
Some(state_callback),
|
||||
&mut position as *mut i64 as *mut c_void,
|
||||
|stream| {
|
||||
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
|
||||
println!("Start playing! Enter 's' to switch device. Enter 'q' to quit.");
|
||||
loop {
|
||||
let mut input = String::new();
|
||||
let _ = io::stdin().read_line(&mut input);
|
||||
assert_eq!(input.pop().unwrap(), '\n');
|
||||
match input.as_str() {
|
||||
"s" => {
|
||||
assert!(output_device_switcher.next().unwrap());
|
||||
}
|
||||
"q" => {
|
||||
println!("Quit.");
|
||||
break;
|
||||
}
|
||||
x => {
|
||||
println!("Unknown command: {}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
|
||||
},
|
||||
);
|
||||
|
||||
extern "C" fn state_callback(
|
||||
stream: *mut ffi::cubeb_stream,
|
||||
user_ptr: *mut c_void,
|
||||
state: ffi::cubeb_state,
|
||||
) {
|
||||
assert!(!stream.is_null());
|
||||
assert!(!user_ptr.is_null());
|
||||
assert_ne!(state, ffi::CUBEB_STATE_ERROR);
|
||||
}
|
||||
|
||||
extern "C" fn 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());
|
||||
assert!(!user_ptr.is_null());
|
||||
assert!(!output_buffer.is_null());
|
||||
|
||||
let buffer = unsafe {
|
||||
let ptr = output_buffer as *mut i16;
|
||||
let len = nframes as usize;
|
||||
slice::from_raw_parts_mut(ptr, len)
|
||||
};
|
||||
|
||||
let position = unsafe { &mut *(user_ptr as *mut i64) };
|
||||
|
||||
// Generate tone on the fly.
|
||||
for data in buffer.iter_mut() {
|
||||
let t1 = (2.0 * PI * 350.0 * (*position) as f32 / SAMPLE_FREQUENCY as f32).sin();
|
||||
let t2 = (2.0 * PI * 440.0 * (*position) as f32 / SAMPLE_FREQUENCY as f32).sin();
|
||||
*data = f32_to_i16_sample(0.5 * (t1 + t2));
|
||||
*position += 1;
|
||||
}
|
||||
|
||||
nframes
|
||||
}
|
||||
|
||||
fn f32_to_i16_sample(x: f32) -> i16 {
|
||||
(x * f32::from(i16::max_value())) as i16
|
||||
}
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_add_then_remove_listeners() {
|
||||
extern "C" fn callback(
|
||||
id: AudioObjectID,
|
||||
number_of_addresses: u32,
|
||||
addresses: *const AudioObjectPropertyAddress,
|
||||
data: *mut c_void,
|
||||
) -> OSStatus {
|
||||
println!("device: {}, data @ {:p}", id, data);
|
||||
let addrs = unsafe { std::slice::from_raw_parts(addresses, number_of_addresses as usize) };
|
||||
for (i, addr) in addrs.iter().enumerate() {
|
||||
let property_selector = PropertySelector::new(addr.mSelector);
|
||||
println!(
|
||||
"address {}\n\tselector {}({})\n\tscope {}\n\telement {}",
|
||||
i, addr.mSelector, property_selector, addr.mScope, addr.mElement
|
||||
);
|
||||
}
|
||||
|
||||
NO_ERR
|
||||
}
|
||||
|
||||
test_get_default_raw_stream(|stream| {
|
||||
let mut listeners = Vec::new();
|
||||
|
||||
let default_output_listener = device_property_listener::new(
|
||||
kAudioObjectSystemObject,
|
||||
get_property_address(
|
||||
Property::HardwareDefaultOutputDevice,
|
||||
DeviceType::INPUT | DeviceType::OUTPUT,
|
||||
),
|
||||
callback,
|
||||
);
|
||||
listeners.push(default_output_listener);
|
||||
|
||||
let default_input_listener = device_property_listener::new(
|
||||
kAudioObjectSystemObject,
|
||||
get_property_address(
|
||||
Property::HardwareDefaultInputDevice,
|
||||
DeviceType::INPUT | DeviceType::OUTPUT,
|
||||
),
|
||||
callback,
|
||||
);
|
||||
listeners.push(default_input_listener);
|
||||
|
||||
if let Some(device) = test_get_default_device(Scope::Output) {
|
||||
let output_source_listener = device_property_listener::new(
|
||||
device,
|
||||
get_property_address(Property::DeviceSource, DeviceType::OUTPUT),
|
||||
callback,
|
||||
);
|
||||
listeners.push(output_source_listener);
|
||||
}
|
||||
|
||||
if let Some(device) = test_get_default_device(Scope::Input) {
|
||||
let input_source_listener = device_property_listener::new(
|
||||
device,
|
||||
get_property_address(Property::DeviceSource, DeviceType::INPUT),
|
||||
callback,
|
||||
);
|
||||
listeners.push(input_source_listener);
|
||||
|
||||
let input_alive_listener = device_property_listener::new(
|
||||
device,
|
||||
get_property_address(
|
||||
Property::DeviceIsAlive,
|
||||
DeviceType::INPUT | DeviceType::OUTPUT,
|
||||
),
|
||||
callback,
|
||||
);
|
||||
listeners.push(input_alive_listener);
|
||||
}
|
||||
|
||||
if listeners.is_empty() {
|
||||
println!("No listeners to test.");
|
||||
return;
|
||||
}
|
||||
|
||||
add_listeners(stream, &listeners);
|
||||
|
||||
println!("Unplug/Plug device or switch input/output device to see the event log.\nEnter anything to finish.");
|
||||
let mut input = String::new();
|
||||
let _ = std::io::stdin().read_line(&mut input);
|
||||
|
||||
remove_listeners(stream, &listeners);
|
||||
});
|
||||
|
||||
fn add_listeners(stream: &AudioUnitStream, listeners: &Vec<device_property_listener>) {
|
||||
for listener in listeners {
|
||||
assert_eq!(stream.add_device_listener(listener), NO_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_listeners(stream: &AudioUnitStream, listeners: &Vec<device_property_listener>) {
|
||||
for listener in listeners {
|
||||
assert_eq!(stream.remove_device_listener(listener), NO_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_device_collection_change() {
|
||||
const DUMMY_PTR: *mut c_void = 0xDEAD_BEEF as *mut c_void;
|
||||
let mut context = AudioUnitContext::new();
|
||||
println!("Context allocated @ {:p}", &context);
|
||||
|
||||
extern "C" fn input_changed_callback(context: *mut ffi::cubeb, data: *mut c_void) {
|
||||
println!(
|
||||
"Input device collection @ {:p} is changed. Data @ {:p}",
|
||||
context, data
|
||||
);
|
||||
assert_eq!(data, DUMMY_PTR);
|
||||
}
|
||||
|
||||
extern "C" fn output_changed_callback(context: *mut ffi::cubeb, data: *mut c_void) {
|
||||
println!(
|
||||
"output device collection @ {:p} is changed. Data @ {:p}",
|
||||
context, data
|
||||
);
|
||||
assert_eq!(data, DUMMY_PTR);
|
||||
}
|
||||
|
||||
context.register_device_collection_changed(
|
||||
DeviceType::INPUT,
|
||||
Some(input_changed_callback),
|
||||
DUMMY_PTR,
|
||||
);
|
||||
|
||||
context.register_device_collection_changed(
|
||||
DeviceType::OUTPUT,
|
||||
Some(output_changed_callback),
|
||||
DUMMY_PTR,
|
||||
);
|
||||
|
||||
println!("Unplug/Plug device to see the event log.\nEnter anything to finish.");
|
||||
let mut input = String::new();
|
||||
let _ = std::io::stdin().read_line(&mut input);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
use super::*;
|
||||
|
||||
mod aggregate_device;
|
||||
mod api;
|
||||
mod backlog;
|
||||
mod device_change;
|
||||
mod device_property;
|
||||
mod interfaces;
|
||||
mod manual;
|
||||
mod parallel;
|
||||
mod tone;
|
||||
mod utils;
|
|
@ -0,0 +1,572 @@
|
|||
use super::utils::{
|
||||
test_audiounit_get_buffer_frame_size, test_get_default_audiounit, test_get_default_device,
|
||||
test_ops_context_operation, PropertyScope, Scope,
|
||||
};
|
||||
use super::*;
|
||||
use std::thread;
|
||||
|
||||
// Ignore the test by default to avoid overwritting the buffer frame size of the device that is
|
||||
// currently used by other streams in other tests.
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_parallel_ops_init_streams_in_parallel_input() {
|
||||
const THREADS: u32 = 50;
|
||||
create_streams_by_ops_in_parallel_with_different_latency(
|
||||
THREADS,
|
||||
StreamType::Input,
|
||||
|streams| {
|
||||
// All the latency frames should be the same value as the first stream's one, since the
|
||||
// latency frames cannot be changed if another stream is operating in parallel.
|
||||
let mut latency_frames = vec![];
|
||||
let mut in_buffer_frame_sizes = vec![];
|
||||
|
||||
for stream in streams {
|
||||
latency_frames.push(stream.latency_frames);
|
||||
|
||||
assert!(!stream.core_stream_data.input_unit.is_null());
|
||||
let in_buffer_frame_size = test_audiounit_get_buffer_frame_size(
|
||||
stream.core_stream_data.input_unit,
|
||||
Scope::Input,
|
||||
PropertyScope::Output,
|
||||
)
|
||||
.unwrap();
|
||||
in_buffer_frame_sizes.push(in_buffer_frame_size);
|
||||
|
||||
assert!(stream.core_stream_data.output_unit.is_null());
|
||||
}
|
||||
|
||||
// Make sure all the latency frames are same as the first stream's one.
|
||||
for i in 0..latency_frames.len() - 1 {
|
||||
assert_eq!(latency_frames[i], latency_frames[i + 1]);
|
||||
}
|
||||
|
||||
// Make sure all the buffer frame sizes on output scope of the input audiounit are same
|
||||
// as the defined latency of the first initial stream.
|
||||
for i in 0..in_buffer_frame_sizes.len() - 1 {
|
||||
assert_eq!(in_buffer_frame_sizes[i], in_buffer_frame_sizes[i + 1]);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Ignore the test by default to avoid overwritting the buffer frame size of the device that is
|
||||
// currently used by other streams in other tests.
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_parallel_ops_init_streams_in_parallel_output() {
|
||||
const THREADS: u32 = 50;
|
||||
create_streams_by_ops_in_parallel_with_different_latency(
|
||||
THREADS,
|
||||
StreamType::Output,
|
||||
|streams| {
|
||||
// All the latency frames should be the same value as the first stream's one, since the
|
||||
// latency frames cannot be changed if another stream is operating in parallel.
|
||||
let mut latency_frames = vec![];
|
||||
let mut out_buffer_frame_sizes = vec![];
|
||||
|
||||
for stream in streams {
|
||||
latency_frames.push(stream.latency_frames);
|
||||
|
||||
assert!(stream.core_stream_data.input_unit.is_null());
|
||||
|
||||
assert!(!stream.core_stream_data.output_unit.is_null());
|
||||
let out_buffer_frame_size = test_audiounit_get_buffer_frame_size(
|
||||
stream.core_stream_data.output_unit,
|
||||
Scope::Output,
|
||||
PropertyScope::Input,
|
||||
)
|
||||
.unwrap();
|
||||
out_buffer_frame_sizes.push(out_buffer_frame_size);
|
||||
}
|
||||
|
||||
// Make sure all the latency frames are same as the first stream's one.
|
||||
for i in 0..latency_frames.len() - 1 {
|
||||
assert_eq!(latency_frames[i], latency_frames[i + 1]);
|
||||
}
|
||||
|
||||
// Make sure all the buffer frame sizes on input scope of the output audiounit are same
|
||||
// as the defined latency of the first initial stream.
|
||||
for i in 0..out_buffer_frame_sizes.len() - 1 {
|
||||
assert_eq!(out_buffer_frame_sizes[i], out_buffer_frame_sizes[i + 1]);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Ignore the test by default to avoid overwritting the buffer frame size of the device that is
|
||||
// currently used by other streams in other tests.
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_parallel_ops_init_streams_in_parallel_duplex() {
|
||||
const THREADS: u32 = 50;
|
||||
create_streams_by_ops_in_parallel_with_different_latency(
|
||||
THREADS,
|
||||
StreamType::Duplex,
|
||||
|streams| {
|
||||
// All the latency frames should be the same value as the first stream's one, since the
|
||||
// latency frames cannot be changed if another stream is operating in parallel.
|
||||
let mut latency_frames = vec![];
|
||||
let mut in_buffer_frame_sizes = vec![];
|
||||
let mut out_buffer_frame_sizes = vec![];
|
||||
|
||||
for stream in streams {
|
||||
latency_frames.push(stream.latency_frames);
|
||||
|
||||
assert!(!stream.core_stream_data.input_unit.is_null());
|
||||
let in_buffer_frame_size = test_audiounit_get_buffer_frame_size(
|
||||
stream.core_stream_data.input_unit,
|
||||
Scope::Input,
|
||||
PropertyScope::Output,
|
||||
)
|
||||
.unwrap();
|
||||
in_buffer_frame_sizes.push(in_buffer_frame_size);
|
||||
|
||||
assert!(!stream.core_stream_data.output_unit.is_null());
|
||||
let out_buffer_frame_size = test_audiounit_get_buffer_frame_size(
|
||||
stream.core_stream_data.output_unit,
|
||||
Scope::Output,
|
||||
PropertyScope::Input,
|
||||
)
|
||||
.unwrap();
|
||||
out_buffer_frame_sizes.push(out_buffer_frame_size);
|
||||
}
|
||||
|
||||
// Make sure all the latency frames are same as the first stream's one.
|
||||
for i in 0..latency_frames.len() - 1 {
|
||||
assert_eq!(latency_frames[i], latency_frames[i + 1]);
|
||||
}
|
||||
|
||||
// Make sure all the buffer frame sizes on output scope of the input audiounit are same
|
||||
// as the defined latency of the first initial stream.
|
||||
for i in 0..in_buffer_frame_sizes.len() - 1 {
|
||||
assert_eq!(in_buffer_frame_sizes[i], in_buffer_frame_sizes[i + 1]);
|
||||
}
|
||||
|
||||
// Make sure all the buffer frame sizes on input scope of the output audiounit are same
|
||||
// as the defined latency of the first initial stream.
|
||||
for i in 0..out_buffer_frame_sizes.len() - 1 {
|
||||
assert_eq!(out_buffer_frame_sizes[i], out_buffer_frame_sizes[i + 1]);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn create_streams_by_ops_in_parallel_with_different_latency<F>(
|
||||
amount: u32,
|
||||
stm_type: StreamType,
|
||||
callback: F,
|
||||
) where
|
||||
F: FnOnce(Vec<&AudioUnitStream>),
|
||||
{
|
||||
let default_input = test_get_default_device(Scope::Input);
|
||||
let default_output = test_get_default_device(Scope::Output);
|
||||
|
||||
let has_input = stm_type == StreamType::Input || stm_type == StreamType::Duplex;
|
||||
let has_output = stm_type == StreamType::Output || stm_type == StreamType::Duplex;
|
||||
|
||||
if has_input && default_input.is_none() {
|
||||
println!("No input device to perform the test.");
|
||||
return;
|
||||
}
|
||||
|
||||
if has_output && default_output.is_none() {
|
||||
println!("No output device to perform the test.");
|
||||
return;
|
||||
}
|
||||
|
||||
test_ops_context_operation("context: init and destroy", |context_ptr| {
|
||||
let context_ptr_value = context_ptr as usize;
|
||||
|
||||
let mut join_handles = vec![];
|
||||
for i in 0..amount {
|
||||
// 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 = 48_000;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
|
||||
input_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
let mut output_params = ffi::cubeb_stream_params::default();
|
||||
output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
|
||||
output_params.rate = 44100;
|
||||
output_params.channels = 2;
|
||||
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
|
||||
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
// Latency cannot be changed if another stream is operating in parallel. All the latecy
|
||||
// should be set to the same latency value of the first stream that is operating in the
|
||||
// context.
|
||||
let latency_frames = SAFE_MIN_LATENCY_FRAMES + i;
|
||||
assert!(latency_frames < SAFE_MAX_LATENCY_FRAMES);
|
||||
|
||||
// Create many streams within the same context. The order of the stream creation
|
||||
// is random (The order of execution of the spawned threads is random.).assert!
|
||||
// It's super dangerous to pass `context_ptr_value` across threads and convert it back
|
||||
// to a pointer. However, it's the cheapest way to make sure the inside mutex works.
|
||||
let thread_name = format!("stream {} @ context {:?}", i, context_ptr);
|
||||
join_handles.push(
|
||||
thread::Builder::new()
|
||||
.name(thread_name)
|
||||
.spawn(move || {
|
||||
let context_ptr = context_ptr_value as *mut ffi::cubeb;
|
||||
let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
|
||||
let stream_name = CString::new(format!("stream {}", i)).unwrap();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
OPS.stream_init.unwrap()(
|
||||
context_ptr,
|
||||
&mut stream,
|
||||
stream_name.as_ptr(),
|
||||
ptr::null_mut(), // Use default input device.
|
||||
if has_input {
|
||||
&mut input_params
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
ptr::null_mut(), // Use default output device.
|
||||
if has_output {
|
||||
&mut output_params
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
latency_frames,
|
||||
None, // No data callback.
|
||||
None, // No state callback.
|
||||
ptr::null_mut(), // No user data pointer.
|
||||
)
|
||||
},
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert!(!stream.is_null());
|
||||
stream as usize
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut streams = vec![];
|
||||
// Wait for finishing the tasks on the different threads.
|
||||
for handle in join_handles {
|
||||
let stream_ptr_value = handle.join().unwrap();
|
||||
let stream = unsafe { Box::from_raw(stream_ptr_value as *mut AudioUnitStream) };
|
||||
streams.push(stream);
|
||||
}
|
||||
|
||||
let stream_refs: Vec<&AudioUnitStream> = streams.iter().map(|stm| stm.as_ref()).collect();
|
||||
callback(stream_refs);
|
||||
});
|
||||
}
|
||||
|
||||
// Ignore the test by default to avoid overwritting the buffer frame size of the device that is
|
||||
// currently used by other streams in other tests.
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_parallel_init_streams_in_parallel_input() {
|
||||
const THREADS: u32 = 10;
|
||||
create_streams_in_parallel_with_different_latency(THREADS, StreamType::Input, |streams| {
|
||||
// All the latency frames should be the same value as the first stream's one, since the
|
||||
// latency frames cannot be changed if another stream is operating in parallel.
|
||||
let mut latency_frames = vec![];
|
||||
let mut in_buffer_frame_sizes = vec![];
|
||||
|
||||
for stream in streams {
|
||||
latency_frames.push(stream.latency_frames);
|
||||
|
||||
assert!(!stream.core_stream_data.input_unit.is_null());
|
||||
let in_buffer_frame_size = test_audiounit_get_buffer_frame_size(
|
||||
stream.core_stream_data.input_unit,
|
||||
Scope::Input,
|
||||
PropertyScope::Output,
|
||||
)
|
||||
.unwrap();
|
||||
in_buffer_frame_sizes.push(in_buffer_frame_size);
|
||||
|
||||
assert!(stream.core_stream_data.output_unit.is_null());
|
||||
}
|
||||
|
||||
// Make sure all the latency frames are same as the first stream's one.
|
||||
for i in 0..latency_frames.len() - 1 {
|
||||
assert_eq!(latency_frames[i], latency_frames[i + 1]);
|
||||
}
|
||||
|
||||
// Make sure all the buffer frame sizes on output scope of the input audiounit are same
|
||||
// as the defined latency of the first initial stream.
|
||||
for i in 0..in_buffer_frame_sizes.len() - 1 {
|
||||
assert_eq!(in_buffer_frame_sizes[i], in_buffer_frame_sizes[i + 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Ignore the test by default to avoid overwritting the buffer frame size of the device that is
|
||||
// currently used by other streams in other tests.
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_parallel_init_streams_in_parallel_output() {
|
||||
const THREADS: u32 = 10;
|
||||
create_streams_in_parallel_with_different_latency(THREADS, StreamType::Output, |streams| {
|
||||
// All the latency frames should be the same value as the first stream's one, since the
|
||||
// latency frames cannot be changed if another stream is operating in parallel.
|
||||
let mut latency_frames = vec![];
|
||||
let mut out_buffer_frame_sizes = vec![];
|
||||
|
||||
for stream in streams {
|
||||
latency_frames.push(stream.latency_frames);
|
||||
|
||||
assert!(stream.core_stream_data.input_unit.is_null());
|
||||
|
||||
assert!(!stream.core_stream_data.output_unit.is_null());
|
||||
let out_buffer_frame_size = test_audiounit_get_buffer_frame_size(
|
||||
stream.core_stream_data.output_unit,
|
||||
Scope::Output,
|
||||
PropertyScope::Input,
|
||||
)
|
||||
.unwrap();
|
||||
out_buffer_frame_sizes.push(out_buffer_frame_size);
|
||||
}
|
||||
|
||||
// Make sure all the latency frames are same as the first stream's one.
|
||||
for i in 0..latency_frames.len() - 1 {
|
||||
assert_eq!(latency_frames[i], latency_frames[i + 1]);
|
||||
}
|
||||
|
||||
// Make sure all the buffer frame sizes on input scope of the output audiounit are same
|
||||
// as the defined latency of the first initial stream.
|
||||
for i in 0..out_buffer_frame_sizes.len() - 1 {
|
||||
assert_eq!(out_buffer_frame_sizes[i], out_buffer_frame_sizes[i + 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Ignore the test by default to avoid overwritting the buffer frame size of the device that is
|
||||
// currently used by other streams in other tests.
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_parallel_init_streams_in_parallel_duplex() {
|
||||
const THREADS: u32 = 10;
|
||||
create_streams_in_parallel_with_different_latency(THREADS, StreamType::Duplex, |streams| {
|
||||
// All the latency frames should be the same value as the first stream's one, since the
|
||||
// latency frames cannot be changed if another stream is operating in parallel.
|
||||
let mut latency_frames = vec![];
|
||||
let mut in_buffer_frame_sizes = vec![];
|
||||
let mut out_buffer_frame_sizes = vec![];
|
||||
|
||||
for stream in streams {
|
||||
latency_frames.push(stream.latency_frames);
|
||||
|
||||
assert!(!stream.core_stream_data.input_unit.is_null());
|
||||
let in_buffer_frame_size = test_audiounit_get_buffer_frame_size(
|
||||
stream.core_stream_data.input_unit,
|
||||
Scope::Input,
|
||||
PropertyScope::Output,
|
||||
)
|
||||
.unwrap();
|
||||
in_buffer_frame_sizes.push(in_buffer_frame_size);
|
||||
|
||||
assert!(!stream.core_stream_data.output_unit.is_null());
|
||||
let out_buffer_frame_size = test_audiounit_get_buffer_frame_size(
|
||||
stream.core_stream_data.output_unit,
|
||||
Scope::Output,
|
||||
PropertyScope::Input,
|
||||
)
|
||||
.unwrap();
|
||||
out_buffer_frame_sizes.push(out_buffer_frame_size);
|
||||
}
|
||||
|
||||
// Make sure all the latency frames are same as the first stream's one.
|
||||
for i in 0..latency_frames.len() - 1 {
|
||||
assert_eq!(latency_frames[i], latency_frames[i + 1]);
|
||||
}
|
||||
|
||||
// Make sure all the buffer frame sizes on output scope of the input audiounit are same
|
||||
// as the defined latency of the first initial stream.
|
||||
for i in 0..in_buffer_frame_sizes.len() - 1 {
|
||||
assert_eq!(in_buffer_frame_sizes[i], in_buffer_frame_sizes[i + 1]);
|
||||
}
|
||||
|
||||
// Make sure all the buffer frame sizes on input scope of the output audiounit are same
|
||||
// as the defined latency of the first initial stream.
|
||||
for i in 0..out_buffer_frame_sizes.len() - 1 {
|
||||
assert_eq!(out_buffer_frame_sizes[i], out_buffer_frame_sizes[i + 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_streams_in_parallel_with_different_latency<F>(
|
||||
amount: u32,
|
||||
stm_type: StreamType,
|
||||
callback: F,
|
||||
) where
|
||||
F: FnOnce(Vec<&AudioUnitStream>),
|
||||
{
|
||||
let default_input = test_get_default_device(Scope::Input);
|
||||
let default_output = test_get_default_device(Scope::Output);
|
||||
|
||||
let has_input = stm_type == StreamType::Input || stm_type == StreamType::Duplex;
|
||||
let has_output = stm_type == StreamType::Output || stm_type == StreamType::Duplex;
|
||||
|
||||
if has_input && default_input.is_none() {
|
||||
println!("No input device to perform the test.");
|
||||
return;
|
||||
}
|
||||
|
||||
if has_output && default_output.is_none() {
|
||||
println!("No output device to perform the test.");
|
||||
return;
|
||||
}
|
||||
|
||||
let context = AudioUnitContext::new();
|
||||
|
||||
let context_ptr_value = &context as *const AudioUnitContext as usize;
|
||||
|
||||
let mut join_handles = vec![];
|
||||
for i in 0..amount {
|
||||
// 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 = 48_000;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
|
||||
input_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
let mut output_params = ffi::cubeb_stream_params::default();
|
||||
output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
|
||||
output_params.rate = 44100;
|
||||
output_params.channels = 2;
|
||||
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
|
||||
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
// Latency cannot be changed if another stream is operating in parallel. All the latecy
|
||||
// should be set to the same latency value of the first stream that is operating in the
|
||||
// context.
|
||||
let latency_frames = SAFE_MIN_LATENCY_FRAMES + i;
|
||||
assert!(latency_frames < SAFE_MAX_LATENCY_FRAMES);
|
||||
|
||||
// Create many streams within the same context. The order of the stream creation
|
||||
// is random. (The order of execution of the spawned threads is random.)
|
||||
// It's super dangerous to pass `context_ptr_value` across threads and convert it back
|
||||
// to a reference. However, it's the cheapest way to make sure the inside mutex works.
|
||||
let thread_name = format!("stream {} @ context {:?}", i, context_ptr_value);
|
||||
join_handles.push(
|
||||
thread::Builder::new()
|
||||
.name(thread_name)
|
||||
.spawn(move || {
|
||||
let context = unsafe { &mut *(context_ptr_value as *mut AudioUnitContext) };
|
||||
let input_params = unsafe { StreamParamsRef::from_ptr(&mut input_params) };
|
||||
let output_params = unsafe { StreamParamsRef::from_ptr(&mut output_params) };
|
||||
let stream = context
|
||||
.stream_init(
|
||||
None,
|
||||
ptr::null_mut(), // Use default input device.
|
||||
if has_input { Some(input_params) } else { None },
|
||||
ptr::null_mut(), // Use default output device.
|
||||
if has_output {
|
||||
Some(output_params)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
latency_frames,
|
||||
None, // No data callback.
|
||||
None, // No state callback.
|
||||
ptr::null_mut(), // No user data pointer.
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!stream.as_ptr().is_null());
|
||||
let stream_ptr_value = stream.as_ptr() as usize;
|
||||
// Prevent the stream from being destroyed by leaking this stream.
|
||||
mem::forget(stream);
|
||||
stream_ptr_value
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut streams = vec![];
|
||||
// Wait for finishing the tasks on the different threads.
|
||||
for handle in join_handles {
|
||||
let stream_ptr_value = handle.join().unwrap();
|
||||
// Retake the leaked stream.
|
||||
let stream = unsafe { Box::from_raw(stream_ptr_value as *mut AudioUnitStream) };
|
||||
streams.push(stream);
|
||||
}
|
||||
|
||||
let stream_refs: Vec<&AudioUnitStream> = streams.iter().map(|stm| stm.as_ref()).collect();
|
||||
callback(stream_refs);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum StreamType {
|
||||
Input,
|
||||
Output,
|
||||
Duplex,
|
||||
}
|
||||
|
||||
// This is used to interfere other active streams.
|
||||
// From this testing, it's ok to set the buffer frame size of a device that is currently used by
|
||||
// other tests. It works on OSX 10.13, not sure if it works on other versions.
|
||||
// However, other tests may check the buffer frame size they set at the same time,
|
||||
// so we ignore this by default incase those checks fail.
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_set_buffer_frame_size_in_parallel() {
|
||||
test_set_buffer_frame_size_in_parallel_in_scope(Scope::Input);
|
||||
test_set_buffer_frame_size_in_parallel_in_scope(Scope::Output);
|
||||
}
|
||||
|
||||
fn test_set_buffer_frame_size_in_parallel_in_scope(scope: Scope) {
|
||||
const THREADS: u32 = 100;
|
||||
|
||||
let unit = test_get_default_audiounit(scope.clone());
|
||||
if unit.is_none() {
|
||||
println!("No unit for {:?}", scope);
|
||||
return;
|
||||
}
|
||||
|
||||
let (unit_scope, unit_element, prop_scope) = match scope {
|
||||
Scope::Input => (kAudioUnitScope_Output, AU_IN_BUS, PropertyScope::Output),
|
||||
Scope::Output => (kAudioUnitScope_Input, AU_OUT_BUS, PropertyScope::Input),
|
||||
};
|
||||
|
||||
let mut units = vec![];
|
||||
let mut join_handles = vec![];
|
||||
for i in 0..THREADS {
|
||||
let latency_frames = SAFE_MIN_LATENCY_FRAMES + i;
|
||||
assert!(latency_frames < SAFE_MAX_LATENCY_FRAMES);
|
||||
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>(),
|
||||
);
|
||||
(latency_frames, status)
|
||||
}));
|
||||
}
|
||||
|
||||
let mut latencies = vec![];
|
||||
let mut statuses = vec![];
|
||||
for handle in join_handles {
|
||||
let (latency, status) = handle.join().unwrap();
|
||||
latencies.push(latency);
|
||||
statuses.push(status);
|
||||
}
|
||||
|
||||
let mut buffer_frames_list = vec![];
|
||||
for unit in units.iter() {
|
||||
buffer_frames_list.push(unit.get_buffer_frame_size(scope.clone(), prop_scope.clone()));
|
||||
}
|
||||
|
||||
for status in statuses {
|
||||
assert_eq!(status, NO_ERR);
|
||||
}
|
||||
|
||||
for i in 0..buffer_frames_list.len() - 1 {
|
||||
assert_eq!(buffer_frames_list[i], buffer_frames_list[i + 1]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
use super::utils::{test_get_default_device, test_ops_stream_operation, Scope};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dial_tone() {
|
||||
use std::f32::consts::PI;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
const SAMPLE_FREQUENCY: u32 = 48_000;
|
||||
|
||||
// Do nothing if there is no available output device.
|
||||
if test_get_default_device(Scope::Output).is_none() {
|
||||
println!("No output device.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
|
||||
// (in the comments).
|
||||
let mut output_params = ffi::cubeb_stream_params::default();
|
||||
output_params.format = ffi::CUBEB_SAMPLE_S16NE;
|
||||
output_params.rate = SAMPLE_FREQUENCY;
|
||||
output_params.channels = 1;
|
||||
output_params.layout = ffi::CUBEB_LAYOUT_MONO;
|
||||
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
// Used to calculate the tone's wave.
|
||||
let mut position: i64 = 0; // TODO: Use Atomic instead.
|
||||
|
||||
test_ops_stream_operation(
|
||||
"stream: North American dial tone",
|
||||
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(data_callback),
|
||||
Some(state_callback),
|
||||
&mut position as *mut i64 as *mut c_void,
|
||||
|stream| {
|
||||
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
|
||||
},
|
||||
);
|
||||
|
||||
extern "C" fn state_callback(
|
||||
stream: *mut ffi::cubeb_stream,
|
||||
user_ptr: *mut c_void,
|
||||
state: ffi::cubeb_state,
|
||||
) {
|
||||
assert!(!stream.is_null());
|
||||
assert!(!user_ptr.is_null());
|
||||
assert_ne!(state, ffi::CUBEB_STATE_ERROR);
|
||||
}
|
||||
|
||||
extern "C" fn 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());
|
||||
assert!(!user_ptr.is_null());
|
||||
assert!(!output_buffer.is_null());
|
||||
|
||||
let buffer = unsafe {
|
||||
let ptr = output_buffer as *mut i16;
|
||||
let len = nframes as usize;
|
||||
slice::from_raw_parts_mut(ptr, len)
|
||||
};
|
||||
|
||||
let position = unsafe { &mut *(user_ptr as *mut i64) };
|
||||
|
||||
// Generate tone on the fly.
|
||||
for data in buffer.iter_mut() {
|
||||
let t1 = (2.0 * PI * 350.0 * (*position) as f32 / SAMPLE_FREQUENCY as f32).sin();
|
||||
let t2 = (2.0 * PI * 440.0 * (*position) as f32 / SAMPLE_FREQUENCY as f32).sin();
|
||||
*data = f32_to_i16_sample(0.5 * (t1 + t2));
|
||||
*position += 1;
|
||||
}
|
||||
|
||||
nframes
|
||||
}
|
||||
|
||||
fn f32_to_i16_sample(x: f32) -> i16 {
|
||||
(x * f32::from(i16::max_value())) as i16
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,106 @@
|
|||
# TO DO
|
||||
|
||||
## General
|
||||
- Some of bugs are found when adding tests. Search *FIXIT* to find them.
|
||||
- Remove `#[allow(non_camel_case_types)]`, `#![allow(unused_assignments)]`, `#![allow(unused_must_use)]`
|
||||
- Use `ErrorChain`
|
||||
- Centralize the error log in one place
|
||||
- Support `enumerate_devices` with in-out type?
|
||||
- Monitor `kAudioDevicePropertyDeviceIsAlive` for output device.
|
||||
- Create a wrapper for `CFArrayCreateMutable` like what we do for `CFMutableDictionaryRef`
|
||||
- Create a wrapper for property listener’s callback
|
||||
- Use `Option<AggregateDevice>` rather than `AggregateDevice` for `aggregate_device` in `CoreStreamData`
|
||||
|
||||
### Type of stream
|
||||
- Use `Option<device_info>` rather than `device_info` for `{input, output}_device` in `CoreStreamData`
|
||||
- Use `Option<StreamParams>` rather than `StreamParams` for `{input, output}_stream_params` in `CoreStreamData`
|
||||
- Same as `{input, output}_desc`, `{input, output}_hw_rate`, ...etc
|
||||
- It would much clearer if we have a `struct X` to wrap all the above stuff and use `input_x` and `output_x` in `CoreStreamData`
|
||||
|
||||
### Generics
|
||||
- Create a _generics_ for `input_linear_buffer`
|
||||
- Consider replacing `AutoArrayWrapper` by [`ringbuf`](https://github.com/agerasev/ringbuf)
|
||||
|
||||
## Separate the stream implementation from the interface
|
||||
The goal is to separate the audio stream into two parts(modules).
|
||||
One is _inner_, the other is _outer_.
|
||||
- The _outer_ stream implements the cubeb interface, based on the _inner_ stream.
|
||||
- The _inner_ stream implements the stream operations based on the _CoreAudio_ APIs.
|
||||
Now the _outer_ stream is named `AudioUnitStream`, the _inner_ stream is named `CoreStreamData`.
|
||||
|
||||
The problem now is that we don't have a clear boundry of the data ownership
|
||||
between the _outer_ stream and _inner_ stream. They access the data owned by the other.
|
||||
- `audiounit_property_listener_callback` is tied to _outer_ stream
|
||||
but the event listeners are in _inner_ stream
|
||||
- `audiounit_input_callback`, `audiounit_output_callback` are registered by the _inner_ stream
|
||||
but the main logic are tied to _outer_ stream
|
||||
|
||||
### Callback separation
|
||||
- Create static callbacks in _inner_ stream
|
||||
- Render _inner_ stream's callbacks to _outer_ stream's callbacks
|
||||
|
||||
### Reinitialization
|
||||
If the _outer_ stream and the _inner_ stream are separate properly,
|
||||
when we need to reinitialize the stream, we can just drop the _inner_ stream
|
||||
and create a new one. It's easier than the current implementation.
|
||||
|
||||
## Aggregate device
|
||||
### Usage policy
|
||||
- [BMO 1563475][bmo1563475]: Only use _aggregate device_ when the mic is a input-only and the speaker is output-only device.
|
||||
- Test if we should do drift compensation.
|
||||
- Add a test for `should_use_aggregate_device`
|
||||
- Create a dummy stream and check
|
||||
- Check again after reinit
|
||||
- Input only: expect false
|
||||
- Output only: expect false
|
||||
- Duplex
|
||||
- Default input and output are different and they are mic-only and speaker-only: expect true
|
||||
- Otherwise: expect false
|
||||
|
||||
[bmo1563475]: https://bugzilla.mozilla.org/show_bug.cgi?id=1563475#c4
|
||||
### Get sub devices
|
||||
- A better pattern for `AggregateDevice::get_sub_devices`
|
||||
### Set sub devices
|
||||
- We will add overlapping devices between `input_sub_devices` and `output_sub_devices`.
|
||||
- if they are same device
|
||||
- if either one of them or both of them are aggregate devices
|
||||
### Setting master device
|
||||
- We always set the master device to the first subdevice of the default output device
|
||||
but the output device (forming the aggregate device) may not be the default output device
|
||||
- Check if the first subdevice of the default output device is in the list of
|
||||
sub devices list of the aggregate device
|
||||
- Check the `name: CFStringRef` of the master device is not `NULL`
|
||||
|
||||
## Interface to system types and APIs
|
||||
- Check if we need `AudioDeviceID` and `AudioObjectID` at the same time
|
||||
- Create wrapper for `AudioObjectGetPropertyData(Size)` with _qualifier_ info
|
||||
- Create wrapper for `CF` related types
|
||||
- Create wrapper struct for `AudioObjectId`
|
||||
- Add `get_data`, `get_data_size`, `set_data`
|
||||
- Create wrapper struct for `AudioUnit`
|
||||
- Implement `get_data`, `set_data`
|
||||
- Create wrapper for `audio_unit_{add, remove}_property_listener`, `audio_object_{add, remove}_property_listener` and their callbacks
|
||||
- Add/Remove listener with generic `*mut T` data, fire their callback with generic `*mut T` data
|
||||
|
||||
## Interface to other module
|
||||
- Create a binding layer for the `resampler`
|
||||
|
||||
## [Cubeb Interface][cubeb-rs]
|
||||
- Implement `From` trait for `enum cubeb_device_type` so we can use `devtype.into()` to get `ffi::CUBEB_DEVICE_TYPE_*`.
|
||||
- Implement `to_owned` in [`StreamParamsRef`][cubeb-rs-stmparamsref]
|
||||
- Check the passed parameters like what [cubeb.c][cubeb] does!
|
||||
- Check the input `StreamParams` parameters properly, or we will set a invalid format into `AudioUnit`.
|
||||
- For example, for a duplex stream, the format of the input stream and output stream should be same.
|
||||
Using different stream formats will cause memory corruption
|
||||
since our resampler assumes the types (_short_ or _float_) of input stream (buffer) and output stream (buffer) are same
|
||||
(The resampler will use the format of the input stream if it exists, otherwise it uses the format of the output stream).
|
||||
- In fact, we should check **all** the parameters properly so we can make sure we don't mess up the streams/devices settings!
|
||||
|
||||
[cubeb-rs]: https://github.com/djg/cubeb-rs "cubeb-rs"
|
||||
[cubeb-rs-stmparamsref]: https://github.com/djg/cubeb-rs/blob/78ed9459b8ac2ca50ea37bb72f8a06847eb8d379/cubeb-core/src/stream.rs#L61 "StreamParamsRef"
|
||||
|
||||
## Test
|
||||
- Rewrite some tests under _cubeb/test/*_ in _Rust_ as part of the integration tests
|
||||
- Add tests for capturing/recording, output, duplex streams
|
||||
- Update the manual tests
|
||||
- Those tests are created in the middle of the development. Thay might be not outdated now.
|
|
@ -19,7 +19,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", optional = true }
|
||||
cubeb-coreaudio = { path = "../../../../media/libcubeb/cubeb-coreaudio-rs", optional = true }
|
||||
cubeb-coreaudio = { git = "https://github.com/ChunMinChang/cubeb-coreaudio-rs", rev = "0920240e4166d2b562840c8062e149d63f7c3a02", optional = true }
|
||||
cubeb-pulse = { path = "../../../../media/libcubeb/cubeb-pulse-rs", optional = true, features=["pulse-dlopen"] }
|
||||
cubeb-sys = { version = "0.6", optional = true, features=["gecko-in-tree"] }
|
||||
encoding_glue = { path = "../../../../intl/encoding_glue" }
|
||||
|
|
Загрузка…
Ссылка в новой задаче