Bug 1880244 - Update cubeb-coreaudio-rs to 7b10217149. r=cubeb-reviewers,padenot

Differential Revision: https://phabricator.services.mozilla.com/D204124
This commit is contained in:
Andreas Pehrson 2024-04-10 13:11:09 +00:00
Родитель 5d779058a8
Коммит 9b115afb13
23 изменённых файлов: 2807 добавлений и 823 удалений

Просмотреть файл

@ -65,9 +65,9 @@ git = "https://github.com/mozilla/audioipc"
rev = "409e11f8de6288e9ddfe269654523735302e59e6"
replace-with = "vendored-sources"
[source."git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=d23ab55eab684b46f46e1da177c8814f6103a009"]
[source."git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=7b1021714989bedef3d40a843a516273db38beaa"]
git = "https://github.com/mozilla/cubeb-coreaudio-rs"
rev = "d23ab55eab684b46f46e1da177c8814f6103a009"
rev = "7b1021714989bedef3d40a843a516273db38beaa"
replace-with = "vendored-sources"
[source."git+https://github.com/mozilla/cubeb-pulse-rs?rev=8ff972c8e2ec1782ff262ac4071c0415e69b1367"]

4
Cargo.lock сгенерированный
Просмотреть файл

@ -910,7 +910,7 @@ dependencies = [
[[package]]
name = "coreaudio-sys-utils"
version = "0.1.0"
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=d23ab55eab684b46f46e1da177c8814f6103a009#d23ab55eab684b46f46e1da177c8814f6103a009"
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=7b1021714989bedef3d40a843a516273db38beaa#7b1021714989bedef3d40a843a516273db38beaa"
dependencies = [
"core-foundation-sys",
"coreaudio-sys",
@ -1156,7 +1156,7 @@ dependencies = [
[[package]]
name = "cubeb-coreaudio"
version = "0.1.0"
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=d23ab55eab684b46f46e1da177c8814f6103a009#d23ab55eab684b46f46e1da177c8814f6103a009"
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=7b1021714989bedef3d40a843a516273db38beaa#7b1021714989bedef3d40a843a516273db38beaa"
dependencies = [
"atomic",
"audio-mixer",

Просмотреть файл

@ -1 +1 @@
{"files":{"Cargo.toml":"87292d055a2fc0f070f54abd549a5f79ec8ac33611ecde80ba394f256b88294c","src/aggregate_device.rs":"7d2bd5f5fd7f3d008ebb69ad81f522ca0cb73db6d7b3e50ed1a63ea26ff721f4","src/audio_device_extensions.rs":"2852f9ce65581cb5cf3f8e581f2652087eae0a569ed429be362e636db6441b6b","src/audio_object.rs":"5447179330a862659a25bceedfdc5d29a1296f63490908d1c868c6b21c5f95a1","src/audio_unit.rs":"d783878930df4923b57ad230138c0f3fd6b0b9bb80a39725092ff4c6615162d8","src/cf_mutable_dict.rs":"fc42edd270c6dfb02f123214d2d8e487bbd62b5bd923b71eec13190fd0104d2a","src/dispatch.rs":"f6267fe587217c3d3ad5fe7f3a35955221c936103bf853c477a2e44eba5f1e46","src/lib.rs":"c93ed1411dd6cc39db44f57e0d7683bbc54745f84a3c9f9533a088895ec97abe","src/string.rs":"28f88b816c768bcfcc674a60d962b93f1c94e5e0f4cc8ed2a1301138b91039e7"},"package":null}
{"files":{"Cargo.toml":"87292d055a2fc0f070f54abd549a5f79ec8ac33611ecde80ba394f256b88294c","src/aggregate_device.rs":"7d2bd5f5fd7f3d008ebb69ad81f522ca0cb73db6d7b3e50ed1a63ea26ff721f4","src/audio_device_extensions.rs":"5c869d791947d15eec8bffe0bb302fe32d0578111ffe0049213e720eb60a34e1","src/audio_object.rs":"34f7e038c1ed30d503d669d89f01864ae90e009a2fa74ef50fac343a53113ff2","src/audio_unit.rs":"d38007faed2ce4d88efb70054a1fdfadf8249d0e55b900eb3ac8eae04355bf2b","src/cf_mutable_dict.rs":"fc42edd270c6dfb02f123214d2d8e487bbd62b5bd923b71eec13190fd0104d2a","src/dispatch.rs":"24b6bcf0dcaa6618e03039cd060a274c8f9ed48264e14de465ae3aacb2daad57","src/lib.rs":"c93ed1411dd6cc39db44f57e0d7683bbc54745f84a3c9f9533a088895ec97abe","src/string.rs":"28f88b816c768bcfcc674a60d962b93f1c94e5e0f4cc8ed2a1301138b91039e7"},"package":null}

Просмотреть файл

@ -1,3 +1,4 @@
use crate::dispatch::*;
use coreaudio_sys::*;
// See https://opensource.apple.com/source/WebCore/WebCore-7604.5.6/platform/spi/cf/CoreAudioSPI.h.auto.html
@ -18,5 +19,6 @@ pub fn audio_device_duck(
in_start_time: *const AudioTimeStamp,
in_ramp_duration: f32,
) -> OSStatus {
debug_assert_running_serially();
unsafe { AudioDeviceDuck(in_device, in_ducked_level, in_start_time, in_ramp_duration) }
}

Просмотреть файл

@ -1,3 +1,4 @@
use crate::dispatch::*;
use coreaudio_sys::*;
use std::fmt;
use std::os::raw::c_void;
@ -77,6 +78,7 @@ pub fn audio_object_set_property_data<T>(
size: usize,
data: *const T,
) -> OSStatus {
debug_assert_running_serially();
unsafe {
AudioObjectSetPropertyData(
id,
@ -99,6 +101,7 @@ pub fn audio_object_add_property_listener<T>(
listener: audio_object_property_listener_proc,
data: *mut T,
) -> OSStatus {
debug_assert_running_serially();
unsafe { AudioObjectAddPropertyListener(id, address, Some(listener), data as *mut c_void) }
}
@ -108,6 +111,7 @@ pub fn audio_object_remove_property_listener<T>(
listener: audio_object_property_listener_proc,
data: *mut T,
) -> OSStatus {
debug_assert_running_serially();
unsafe { AudioObjectRemovePropertyListener(id, address, Some(listener), data as *mut c_void) }
}

Просмотреть файл

@ -1,3 +1,4 @@
use crate::dispatch::debug_assert_running_serially;
use coreaudio_sys::*;
use std::convert::TryFrom;
use std::os::raw::c_void;
@ -13,6 +14,7 @@ pub fn audio_unit_get_property_info(
) -> OSStatus {
assert!(!unit.is_null());
assert!(UInt32::try_from(*size).is_ok()); // Check if `size` can be converted to a UInt32.
debug_assert_running_serially();
unsafe {
AudioUnitGetPropertyInfo(
unit,
@ -35,6 +37,7 @@ pub fn audio_unit_get_property<T>(
) -> OSStatus {
assert!(!unit.is_null());
assert!(UInt32::try_from(*size).is_ok()); // Check if `size` can be converted to a UInt32.
debug_assert_running_serially();
unsafe {
AudioUnitGetProperty(
unit,
@ -56,6 +59,7 @@ pub fn audio_unit_set_property<T>(
size: usize,
) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe {
AudioUnitSetProperty(
unit,
@ -76,6 +80,7 @@ pub fn audio_unit_get_parameter(
value: &mut AudioUnitParameterValue,
) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe {
AudioUnitGetParameter(
unit,
@ -96,30 +101,36 @@ pub fn audio_unit_set_parameter(
buffer_offset_in_frames: UInt32,
) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe { AudioUnitSetParameter(unit, id, scope, element, value, buffer_offset_in_frames) }
}
pub fn audio_unit_initialize(unit: AudioUnit) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe { AudioUnitInitialize(unit) }
}
pub fn audio_unit_uninitialize(unit: AudioUnit) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe { AudioUnitUninitialize(unit) }
}
pub fn dispose_audio_unit(unit: AudioUnit) -> OSStatus {
debug_assert_running_serially();
unsafe { AudioComponentInstanceDispose(unit) }
}
pub fn audio_output_unit_start(unit: AudioUnit) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe { AudioOutputUnitStart(unit) }
}
pub fn audio_output_unit_stop(unit: AudioUnit) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe { AudioOutputUnitStop(unit) }
}
@ -155,6 +166,7 @@ pub fn audio_unit_add_property_listener<T>(
data: *mut T,
) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe { AudioUnitAddPropertyListener(unit, id, Some(listener), data as *mut c_void) }
}
@ -165,6 +177,7 @@ pub fn audio_unit_remove_property_listener_with_user_data<T>(
data: *mut T,
) -> OSStatus {
assert!(!unit.is_null());
debug_assert_running_serially();
unsafe {
AudioUnitRemovePropertyListenerWithUserData(unit, id, Some(listener), data as *mut c_void)
}

Просмотреть файл

@ -3,40 +3,118 @@ use coreaudio_sys::*;
use std::ffi::CString;
use std::mem;
use std::os::raw::c_void;
use std::panic;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
#[cfg(test)]
use std::thread;
#[cfg(test)]
use std::time::Duration;
use std::time::Instant;
// Queue: A wrapper around `dispatch_queue_t`.
pub const DISPATCH_QUEUE_LABEL: &str = "org.mozilla.cubeb";
pub fn get_serial_queue_singleton() -> &'static Queue {
static SERIAL_QUEUE: OnceLock<Queue> = OnceLock::new();
SERIAL_QUEUE.get_or_init(|| Queue::new(DISPATCH_QUEUE_LABEL))
}
pub fn debug_assert_running_serially() {
get_serial_queue_singleton().debug_assert_is_current();
}
pub fn debug_assert_not_running_serially() {
get_serial_queue_singleton().debug_assert_is_not_current();
}
pub fn run_serially<F, B>(work: F) -> B
where
F: FnOnce() -> B,
{
get_serial_queue_singleton().run_sync(|| work()).unwrap()
}
pub fn run_serially_forward_panics<F, B>(work: F) -> B
where
F: panic::UnwindSafe + FnOnce() -> B,
{
match run_serially(|| panic::catch_unwind(|| work())) {
Ok(res) => res,
Err(e) => panic::resume_unwind(e),
}
}
// Queue: A wrapper around `dispatch_queue_t` that is always serial.
// ------------------------------------------------------------------------------------------------
#[derive(Debug)]
pub struct Queue(dispatch_queue_t);
pub struct Queue {
queue: Mutex<dispatch_queue_t>,
owned: AtomicBool,
}
impl Queue {
pub fn new(label: &str) -> Self {
pub fn new_with_target(label: &str, target: &Queue) -> Self {
const DISPATCH_QUEUE_SERIAL: dispatch_queue_attr_t =
ptr::null_mut::<dispatch_queue_attr_s>();
let label = CString::new(label).unwrap();
let c_string = label.as_ptr();
let queue = Self(unsafe { dispatch_queue_create(c_string, DISPATCH_QUEUE_SERIAL) });
let queue = {
let target_guard = target.queue.lock().unwrap();
Self {
queue: Mutex::new(unsafe {
dispatch_queue_create_with_target(
c_string,
DISPATCH_QUEUE_SERIAL,
*target_guard,
)
}),
owned: AtomicBool::new(true),
}
};
queue.set_should_cancel(Box::new(AtomicBool::new(false)));
queue
}
pub fn new(label: &str) -> Self {
Queue::new_with_target(label, &Queue::get_global_queue())
}
pub fn get_global_queue() -> Self {
Self {
queue: Mutex::new(unsafe { dispatch_get_global_queue(QOS_CLASS_DEFAULT as isize, 0) }),
owned: AtomicBool::new(false),
}
}
#[cfg(debug_assertions)]
pub fn debug_assert_is_current(&self) {
let guard = self.queue.lock().unwrap();
unsafe {
dispatch_assert_queue(self.0);
dispatch_assert_queue(*guard);
}
}
#[cfg(not(debug_assertions))]
pub fn debug_assert_is_current(&self) {}
#[cfg(debug_assertions)]
pub fn debug_assert_is_not_current(&self) {
let guard = self.queue.lock().unwrap();
unsafe {
dispatch_assert_queue_not(*guard);
}
}
#[cfg(not(debug_assertions))]
pub fn debug_assert_is_not_current(&self) {}
pub fn run_async<F>(&self, work: F)
where
F: Send + FnOnce(),
{
let should_cancel = self.get_should_cancel();
let guard = self.queue.lock().unwrap();
let should_cancel = self.get_should_cancel(*guard);
let (closure, executor) = Self::create_closure_and_executor(|| {
if should_cancel.map_or(false, |v| v.load(Ordering::SeqCst)) {
return;
@ -44,15 +122,22 @@ impl Queue {
work();
});
unsafe {
dispatch_async_f(self.0, closure, executor);
dispatch_async_f(*guard, closure, executor);
}
}
pub fn run_sync<F>(&self, work: F)
pub fn run_after<F>(&self, when: Instant, work: F)
where
F: Send + FnOnce(),
{
let should_cancel = self.get_should_cancel();
let now = Instant::now();
if when <= now {
return self.run_async(work);
}
let nanos = (when - now).as_nanos() as i64;
let when = unsafe { dispatch_time(DISPATCH_TIME_NOW.into(), nanos) };
let guard = self.queue.lock().unwrap();
let should_cancel = self.get_should_cancel(*guard);
let (closure, executor) = Self::create_closure_and_executor(|| {
if should_cancel.map_or(false, |v| v.load(Ordering::SeqCst)) {
return;
@ -60,38 +145,85 @@ impl Queue {
work();
});
unsafe {
dispatch_sync_f(self.0, closure, executor);
dispatch_after_f(when, *guard, closure, executor);
}
}
pub fn run_final<F>(&self, work: F)
pub fn run_sync<F, B>(&self, work: F) -> Option<B>
where
F: Send + FnOnce(),
F: FnOnce() -> B,
{
let should_cancel = self.get_should_cancel();
let (closure, executor) = Self::create_closure_and_executor(|| {
work();
should_cancel
.expect("dispatch context should be allocated!")
.store(true, Ordering::SeqCst);
});
unsafe {
dispatch_sync_f(self.0, closure, executor);
let queue: Option<dispatch_queue_t>;
let mut res: Option<B> = None;
let cex: Option<(*mut c_void, dispatch_function_t)>;
{
let guard = self.queue.lock().unwrap();
queue = Some(*guard);
let should_cancel = self.get_should_cancel(*guard);
cex = Some(Self::create_closure_and_executor(|| {
if should_cancel.map_or(false, |v| v.load(Ordering::SeqCst)) {
return;
}
res = Some(work());
}));
}
let (closure, executor) = cex.unwrap();
unsafe {
dispatch_sync_f(queue.unwrap(), closure, executor);
}
res
}
fn get_should_cancel(&self) -> Option<&mut AtomicBool> {
pub fn run_final<F, B>(&self, work: F) -> Option<B>
where
F: FnOnce() -> B,
{
assert!(
self.owned.load(Ordering::SeqCst),
"Doesn't make sense to finalize global queue"
);
let queue: Option<dispatch_queue_t>;
let mut res: Option<B> = None;
let cex: Option<(*mut c_void, dispatch_function_t)>;
{
let guard = self.queue.lock().unwrap();
queue = Some(*guard);
let should_cancel = self.get_should_cancel(*guard);
debug_assert!(
should_cancel.is_some(),
"dispatch context should be allocated!"
);
cex = Some(Self::create_closure_and_executor(|| {
res = Some(work());
should_cancel
.expect("dispatch context should be allocated!")
.store(true, Ordering::SeqCst);
}));
}
let (closure, executor) = cex.unwrap();
unsafe {
let context = dispatch_get_context(
mem::transmute::<dispatch_queue_t, dispatch_object_t>(self.0),
) as *mut AtomicBool;
dispatch_sync_f(queue.unwrap(), closure, executor);
}
res
}
fn get_should_cancel(&self, queue: dispatch_queue_t) -> Option<&mut AtomicBool> {
if !self.owned.load(Ordering::SeqCst) {
return None;
}
unsafe {
let context =
dispatch_get_context(mem::transmute::<dispatch_queue_t, dispatch_object_t>(queue))
as *mut AtomicBool;
context.as_mut()
}
}
fn set_should_cancel(&self, context: Box<AtomicBool>) {
assert!(self.owned.load(Ordering::SeqCst));
unsafe {
let queue = mem::transmute::<dispatch_queue_t, dispatch_object_t>(self.0);
let guard = self.queue.lock().unwrap();
let queue = mem::transmute::<dispatch_queue_t, dispatch_object_t>(*guard);
// Leak the context from Box.
dispatch_set_context(queue, Box::into_raw(context) as *mut c_void);
@ -106,13 +238,13 @@ impl Queue {
}
fn release(&self) {
let guard = self.queue.lock().unwrap();
let queue = *guard;
unsafe {
// This will release the inner `dispatch_queue_t` asynchronously.
// TODO: It's incredibly unsafe to call `transmute` directly.
// Find another way to release the queue.
dispatch_release(mem::transmute::<dispatch_queue_t, dispatch_object_t>(
self.0,
));
dispatch_release(mem::transmute::<dispatch_queue_t, dispatch_object_t>(queue));
}
}
@ -143,23 +275,35 @@ impl Queue {
impl Drop for Queue {
fn drop(&mut self) {
self.release();
if self.owned.load(Ordering::SeqCst) {
self.release();
}
}
}
impl Clone for Queue {
fn clone(&self) -> Self {
assert!(
self.owned.load(Ordering::SeqCst),
"No need to clone a static queue"
);
let guard = self.queue.lock().unwrap();
let queue = *guard;
// TODO: It's incredibly unsafe to call `transmute` directly.
// Find another way to release the queue.
unsafe {
dispatch_retain(mem::transmute::<dispatch_queue_t, dispatch_object_t>(
self.0,
));
dispatch_retain(mem::transmute::<dispatch_queue_t, dispatch_object_t>(queue));
}
Self {
queue: Mutex::new(queue),
owned: AtomicBool::new(true),
}
Self(self.0)
}
}
unsafe impl Send for Queue {}
unsafe impl Sync for Queue {}
#[test]
fn run_tasks_in_order() {
let mut visited = Vec::<u32>::new();
@ -176,12 +320,12 @@ fn run_tasks_in_order() {
let queue = Queue::new("Run tasks in order");
queue.run_sync(move || visit(1, ptr));
queue.run_sync(move || visit(2, ptr));
queue.run_async(move || visit(3, ptr));
queue.run_async(move || visit(4, ptr));
queue.run_sync(|| visit(1, ptr));
queue.run_sync(|| visit(2, ptr));
queue.run_async(|| visit(3, ptr));
queue.run_async(|| visit(4, ptr));
// Call sync here to block the current thread and make sure all the tasks are done.
queue.run_sync(move || visit(5, ptr));
queue.run_sync(|| visit(5, ptr));
assert_eq!(visited, vec![1, 2, 3, 4, 5]);
}
@ -203,14 +347,52 @@ fn run_final_task() {
let queue = Queue::new("Task after run_final will be cancelled");
queue.run_sync(move || visit(1, ptr));
queue.run_async(move || visit(2, ptr));
queue.run_final(move || visit(3, ptr));
queue.run_async(move || visit(4, ptr));
queue.run_sync(move || visit(5, ptr));
queue.run_sync(|| visit(1, ptr));
queue.run_async(|| visit(2, ptr));
queue.run_final(|| visit(3, ptr));
queue.run_async(|| visit(4, ptr));
queue.run_sync(|| visit(5, ptr));
}
// `queue` will be dropped asynchronously and then the `finalizer` of the `queue`
// should be fired to clean up the `context` set in the `queue`.
assert_eq!(visited, vec![1, 2, 3]);
}
#[test]
fn sync_return_value() {
let q = Queue::new("Test queue");
assert_eq!(q.run_sync(|| 42), Some(42));
assert_eq!(q.run_final(|| "foo"), Some("foo"));
assert_eq!(q.run_sync(|| Ok::<(), u32>(())), None);
}
#[test]
fn run_after() {
let mut visited = Vec::<u32>::new();
{
// Rust compilter doesn't allow a pointer to be passed across threads.
// A hacky way to do that is to cast the pointer into a value, then
// the value, which is actually an address, can be copied into threads.
let ptr = &mut visited as *mut Vec<u32> as usize;
fn visit(v: u32, visited_ptr: usize) {
let visited = unsafe { &mut *(visited_ptr as *mut Vec<u32>) };
visited.push(v);
}
let queue = Queue::new("Task after run_final will be cancelled");
queue.run_async(|| visit(1, ptr));
queue.run_after(Instant::now() + Duration::from_millis(10), || visit(2, ptr));
queue.run_after(Instant::now() + Duration::from_secs(1), || visit(3, ptr));
queue.run_async(|| visit(4, ptr));
thread::sleep(Duration::from_millis(100));
queue.run_final(|| visit(5, ptr));
}
// `queue` will be dropped asynchronously and then the `finalizer` of the `queue`
// should be fired to clean up the `context` set in the `queue`.
assert_eq!(visited, vec![1, 4, 2, 5]);
}

Просмотреть файл

@ -1 +1 @@
{"files":{".circleci/config.yml":"7f3dc865105ca8f33965a7958b1fe2e627ae2d5a703f3b2a4ab6e2e796018597",".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".githooks/pre-push":"8b8b26544cd56f54c0c33812551f786bb25cb08c86dbfeb6bf3daad881c826a1",".github/workflows/test.yml":"aa1998a3b104ad131805ca3513832cef3f65300192824f8b1efc9a5a0cc108f6",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"d7e757e664c23fae52028f1dfc5917f92523c08702e3a1f95e1fd38ed714416c","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"0007782a05a5330f739ad789c19c82562c82e32386b0447000fc72c0d48405bc","build-audiounit-rust-in-cubeb.sh":"d228a05985dcd02ec1ecac66a2b64dae5a530804a25a7054ccc95905aedfb7ef","install_git_hook.sh":"d38c8e51e636f6b90b489621ac34ccd1d1b1f40dccce3d178ed1da1c5068f16d","install_rustfmt_clippy.sh":"4ae90d8dcb9757cb3ae4ae142ef80e5377c0dde61c63f4a3c32418646e80ca7b","run_device_tests.sh":"d717e598c96e4911d9494b18382d6bd3a8d5038b7d68d3166ad4336e237a97d8","run_sanitizers.sh":"84e93a0da137803018f37403511e8c92760be730426bf6cea34419d93d1a7ff8","run_tests.sh":"916a7ae4a406d2274417d6eca939a878db5adcb6144e5680d9d148bf90178f1c","src/backend/aggregate_device.rs":"43511107ba2a75a19340ac663c981362ca1b75b679b6c295d88b5035bd7e3619","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"e9bcf964347daa8952f98caa2746e34a31ea8908375204896593f56e4b6147ca","src/backend/device_property.rs":"a7622feaa41db1cd76fd35a85a022e44f4894e396a104a59008d5b8757d2ab4e","src/backend/mixer.rs":"ed299d3954e2a823060c870a8244673a7d4bca530830cb66b964d047a80ee3af","src/backend/mod.rs":"1591669c30a3d07754bfb39c9cb042cdd101f0ab89be13f6cdf74d376e441cf8","src/backend/resampler.rs":"48bf8f56ae8d60dbabca6417b768000619abee8731ac3902164b45651ac08a4d","src/backend/tests/aggregate_device.rs":"e3f94e118e1dd47941fbba4417de40bddc4254d9f06b1e938f58d8f1aa566a5c","src/backend/tests/api.rs":"cd7e7551e2e82b19da883621a494d2a6779c373f3ff2d12ee52fae8efec1e7b8","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"f68c2eaa55c3ec2a58894832fbca1e2a2e79e740b145f76a0f45452af465a934","src/backend/tests/device_property.rs":"ea0be5f8834be494cb33f854ce9d334b5763dc5287f949bcb4bd025d8a8b2d3b","src/backend/tests/interfaces.rs":"af8e3fdeb58226621699b29f1a90621b2260e3f17292dac54860cd05fe4eec71","src/backend/tests/manual.rs":"4a1634e86beb145d2703722a8be057a762953241329c82ee09acf7dc0f0d9d0c","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"59632744e70616ab7037facb0787db339b96800c8cc397d203241548c5cfb7f5","src/backend/tests/tone.rs":"779cc14fc2a362bf7f26ce66ad70c0639501176175655a99b7fefb3c59d56c7a","src/backend/tests/utils.rs":"efb8b3709aff7ed5e2923566084de3e0709f3bd9c18a04f3310d7a3b86fa4b71","src/backend/utils.rs":"6c3ffbcd602e6cc9f56deb9ecb07b2eef2e6f074ef924178e466f380aae5c595","src/capi.rs":"21b66b70545bf04ec719928004d1d9adb45b24ced51288f5b2993d79aaf78f5f","src/lib.rs":"5e586d45cd6b3722f0a6736d9252593299269817a153eef1930a5fb9bfbb56f5","todo.md":"efc1f012eb9a331a040cad4ac03aa79307f25885f71b6fb38f3ad7af8d7d515c"},"package":null}
{"files":{".circleci/config.yml":"7f3dc865105ca8f33965a7958b1fe2e627ae2d5a703f3b2a4ab6e2e796018597",".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".githooks/pre-push":"8b8b26544cd56f54c0c33812551f786bb25cb08c86dbfeb6bf3daad881c826a1",".github/workflows/test.yml":"ac8f4cf5b7631b5c738d50c0cf78113bd395940b9e76593904bbaf2d02d16a70",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"d7e757e664c23fae52028f1dfc5917f92523c08702e3a1f95e1fd38ed714416c","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"0007782a05a5330f739ad789c19c82562c82e32386b0447000fc72c0d48405bc","build-audiounit-rust-in-cubeb.sh":"d228a05985dcd02ec1ecac66a2b64dae5a530804a25a7054ccc95905aedfb7ef","install_git_hook.sh":"d38c8e51e636f6b90b489621ac34ccd1d1b1f40dccce3d178ed1da1c5068f16d","install_rustfmt_clippy.sh":"4ae90d8dcb9757cb3ae4ae142ef80e5377c0dde61c63f4a3c32418646e80ca7b","run_device_tests.sh":"90c2542fa3ff8a35fed894fae3a1aa0157117b7f0e28df14b8e6f7b1f1f43797","run_sanitizers.sh":"84e93a0da137803018f37403511e8c92760be730426bf6cea34419d93d1a7ff8","run_tests.sh":"bae82f66dd47a060b6fdcc238520084aec1079d5b1b1d66d103baa1ffaa8773d","src/backend/aggregate_device.rs":"db7d644358090b1d65ff2d53ad854369790ae4ad7dfa12b79888c0002c1b4950","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"e9bcf964347daa8952f98caa2746e34a31ea8908375204896593f56e4b6147ca","src/backend/device_property.rs":"a7622feaa41db1cd76fd35a85a022e44f4894e396a104a59008d5b8757d2ab4e","src/backend/mixer.rs":"ed299d3954e2a823060c870a8244673a7d4bca530830cb66b964d047a80ee3af","src/backend/mod.rs":"44652840c745dc8be30125f1111e29e365b39ff06853c8628818083148e3c931","src/backend/resampler.rs":"48bf8f56ae8d60dbabca6417b768000619abee8731ac3902164b45651ac08a4d","src/backend/tests/aggregate_device.rs":"770cf90f32b5ab2203476031c1fbc8379b713baa97bec36f7fd0d77fef1efd60","src/backend/tests/api.rs":"d72d7c0de8d12e880966948be4686bcf8c789f0ef19cb435c242fd72f2d252f9","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"babf50326fb38db24fe80f24f546e1b6ad04319ae8835bb372d893fc9b3038a2","src/backend/tests/device_property.rs":"73c25f579a995e8a59c9b7d391813afb75f739b5e2f825480cba04499a1d46e8","src/backend/tests/interfaces.rs":"3bb50c8bb8bb2a6e2b0195122e54bad8ae6ab1695f740d0ee0895a3a4517d451","src/backend/tests/manual.rs":"e550cc8bb7619bb80b68e49bf7f475c029e0f1b34323d1d30edcbe322cf4efc7","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"a7ebd579339c40ca64c0757cc9da6baec641e670f226e1b2ec5049894700bd7a","src/backend/tests/tone.rs":"b028c67777b6453a26190b6a49785dfe28556adcbe179cb10862ce0d47ee8509","src/backend/tests/utils.rs":"80d7e4ebc06b23c63a4d2867e0c80e0bfe05449fa55edd21e785ed2c089bf7d5","src/backend/utils.rs":"6c3ffbcd602e6cc9f56deb9ecb07b2eef2e6f074ef924178e466f380aae5c595","src/capi.rs":"21b66b70545bf04ec719928004d1d9adb45b24ced51288f5b2993d79aaf78f5f","src/lib.rs":"5e586d45cd6b3722f0a6736d9252593299269817a153eef1930a5fb9bfbb56f5","todo.md":"efc1f012eb9a331a040cad4ac03aa79307f25885f71b6fb38f3ad7af8d7d515c"},"package":null}

Просмотреть файл

@ -4,36 +4,52 @@ on: [push, pull_request]
jobs:
build:
runs-on: macOS-latest
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
os: [macos-12, macos-13, macos-14]
rust: [stable]
experimental: [false]
include:
- rust: nightly
- os: macos-14
rust: nightly
experimental: true
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Rust
run: rustup toolchain install ${{ matrix.rust }} --profile minimal --component rustfmt clippy
- name: Setup
- name: Setup Rust
run: |
rustup default ${{ matrix.rust }}
toolchain=$(rustup default)
echo "Use Rust toolchain: $toolchain"
rustc --version
cargo --version
- name: Setup Audio
if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' }}
run: |
brew install switchaudio-osx
brew install blackhole-2ch
SwitchAudioSource -s "BlackHole 2ch" -t input
SwitchAudioSource -s "BlackHole 2ch" -t output
- name: Grant microphone access
if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' }}
env:
tcc_extra_columns: ${{ matrix.os == 'macos-14' && ',NULL,NULL,''UNUSED'',1687786159' || '' }}
run: sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159${{ env.tcc_extra_columns }});"
- name: Build
run: cargo build --verbose
- name: Regular Test
run: sh run_tests.sh

Просмотреть файл

@ -15,8 +15,6 @@ cargo test test_plug_and_unplug_device -- --ignored --nocapture
cargo test test_register_device_changed_callback_to_check_default_device_changed_input -- --ignored --nocapture
cargo test test_register_device_changed_callback_to_check_default_device_changed_output -- --ignored --nocapture
cargo test test_register_device_changed_callback_to_check_default_device_changed_duplex -- --ignored --nocapture
cargo test test_register_device_changed_callback_to_check_input_alive_changed_input -- --ignored --nocapture
cargo test test_register_device_changed_callback_to_check_input_alive_changed_duplex -- --ignored --nocapture
cargo test test_destroy_input_stream_after_unplugging_a_nondefault_input_device -- --ignored --nocapture
cargo test test_suspend_input_stream_by_unplugging_a_nondefault_input_device -- --ignored --nocapture

Просмотреть файл

@ -39,8 +39,9 @@ cargo clippy -- -D warnings
# Regular Tests
cargo test --verbose
cargo test test_configure_output -- --ignored
cargo test test_aggregate -- --ignored --test-threads=1
# Timing sensitive tests must run serially so they cannot be impacted by other tasks on the queue
cargo test test_ops_timing_sensitive -- --ignored --test-threads=1
# Parallel Tests
cargo test test_parallel -- --ignored --nocapture --test-threads=1

Просмотреть файл

@ -69,6 +69,7 @@ impl AggregateDevice {
input_id: AudioObjectID,
output_id: AudioObjectID,
) -> std::result::Result<Self, Error> {
debug_assert_running_serially();
let plugin_id = Self::get_system_plugin_id()?;
let device_id = Self::create_blank_device_sync(plugin_id)?;
@ -399,12 +400,12 @@ impl AggregateDevice {
let sub_devices = CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks);
// The order of the items in the array is significant and is used to determine the order of the streams
// of the AudioAggregateDevice.
for device in output_sub_devices {
for device in input_sub_devices {
let uid = get_device_global_uid(device)?;
CFArrayAppendValue(sub_devices, uid.get_raw() as *const c_void);
}
for device in input_sub_devices {
for device in output_sub_devices {
let uid = get_device_global_uid(device)?;
CFArrayAppendValue(sub_devices, uid.get_raw() as *const c_void);
}
@ -466,6 +467,28 @@ impl AggregateDevice {
}
}
pub fn get_master_device_uid(device_id: AudioDeviceID) -> std::result::Result<String, Error> {
let address = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyMainSubDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut master: CFStringRef = ptr::null_mut();
let mut size = mem::size_of::<CFStringRef>();
let status = audio_object_get_property_data(device_id, &address, &mut size, &mut master);
if status != NO_ERR {
return Err(Error::from(status));
}
if master.is_null() {
return Ok(String::default());
}
let master = StringRef::new(master as _);
Ok(master.into_string())
}
pub fn set_master_device(
device_id: AudioDeviceID,
primary_id: AudioDeviceID,
@ -480,12 +503,12 @@ impl AggregateDevice {
);
let address = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyMasterSubDevice,
mSelector: kAudioAggregateDevicePropertyMainSubDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
// Master become the 1st sub device of the primary device
// The master device will be the 1st sub device of the primary device.
let output_sub_devices = Self::get_sub_devices(primary_id)?;
assert!(!output_sub_devices.is_empty());
let master_sub_device_uid = get_device_global_uid(output_sub_devices[0]).unwrap();
@ -548,16 +571,23 @@ impl AggregateDevice {
return Err(Error::from(status));
}
let master_sub_device_uid = Self::get_master_device_uid(device_id)?;
let address = AudioObjectPropertyAddress {
mSelector: kAudioSubDevicePropertyDriftCompensation,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
// Start from the second device since the first is the master clock
for device in &sub_devices[1..] {
for &device in &sub_devices {
let uid = get_device_global_uid(device)
.map(|sr| sr.into_string())
.unwrap_or_default();
if uid == master_sub_device_uid {
continue;
}
let status = audio_object_set_property_data(
*device,
device,
&address,
mem::size_of::<u32>(),
&DRIFT_COMPENSATION,
@ -671,6 +701,7 @@ impl Default for AggregateDevice {
impl Drop for AggregateDevice {
fn drop(&mut self) {
debug_assert_running_serially();
if self.plugin_id != kAudioObjectUnknown && self.device_id != kAudioObjectUnknown {
if let Err(r) = Self::destroy_device(self.plugin_id, self.device_id) {
cubeb_log!(

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -3,6 +3,8 @@ use super::utils::{
test_get_drift_compensations, test_get_master_device, DeviceFilter, Scope,
};
use super::*;
use std::iter::zip;
use std::panic;
// AggregateDevice::set_sub_devices
// ------------------------------------
@ -19,21 +21,27 @@ fn test_aggregate_set_sub_devices_for_an_unknown_aggregate_device() {
let default_input = default_input.unwrap();
let default_output = default_output.unwrap();
assert!(
AggregateDevice::set_sub_devices(kAudioObjectUnknown, default_input, default_output)
.is_err()
run_serially_forward_panics(|| AggregateDevice::set_sub_devices(
kAudioObjectUnknown,
default_input,
default_output
))
.is_err()
);
}
#[test]
#[should_panic]
fn test_aggregate_set_sub_devices_for_unknown_devices() {
// If aggregate device id is kAudioObjectUnknown, we are unable to set device list.
assert!(AggregateDevice::set_sub_devices(
kAudioObjectUnknown,
kAudioObjectUnknown,
kAudioObjectUnknown
)
.is_err());
run_serially_forward_panics(|| {
// If aggregate device id is kAudioObjectUnknown, we are unable to set device list.
assert!(AggregateDevice::set_sub_devices(
kAudioObjectUnknown,
kAudioObjectUnknown,
kAudioObjectUnknown
)
.is_err());
});
}
// AggregateDevice::get_sub_devices
@ -48,7 +56,13 @@ fn test_aggregate_get_sub_devices() {
// containing `device` itself if it's not an aggregate device. This test assumes devices
// is not an empty aggregate device (Test will panic when calling get_sub_devices with
// an empty aggregate device).
let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
println!(
"get_sub_devices({}={})",
device,
run_serially_forward_panics(|| get_device_uid(device))
);
let sub_devices =
run_serially_forward_panics(|| AggregateDevice::get_sub_devices(device).unwrap());
// TODO: If the device is a blank aggregate device, then the assertion fails!
assert!(!sub_devices.is_empty());
}
@ -57,8 +71,10 @@ fn test_aggregate_get_sub_devices() {
#[test]
#[should_panic]
fn test_aggregate_get_sub_devices_for_a_unknown_device() {
let devices = AggregateDevice::get_sub_devices(kAudioObjectUnknown).unwrap();
assert!(devices.is_empty());
run_serially_forward_panics(|| {
let devices = AggregateDevice::get_sub_devices(kAudioObjectUnknown).unwrap();
assert!(devices.is_empty());
});
}
// AggregateDevice::set_master_device
@ -66,7 +82,11 @@ fn test_aggregate_get_sub_devices_for_a_unknown_device() {
#[test]
#[should_panic]
fn test_aggregate_set_master_device_for_an_unknown_aggregate_device() {
assert!(AggregateDevice::set_master_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err());
run_serially_forward_panics(|| {
assert!(
AggregateDevice::set_master_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err()
);
});
}
// AggregateDevice::activate_clock_drift_compensation
@ -74,7 +94,9 @@ fn test_aggregate_set_master_device_for_an_unknown_aggregate_device() {
#[test]
#[should_panic]
fn test_aggregate_activate_clock_drift_compensation_for_an_unknown_aggregate_device() {
assert!(AggregateDevice::activate_clock_drift_compensation(kAudioObjectUnknown).is_err());
run_serially_forward_panics(|| {
assert!(AggregateDevice::activate_clock_drift_compensation(kAudioObjectUnknown).is_err());
});
}
// AggregateDevice::destroy_device
@ -82,60 +104,55 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_unknown_aggregate_dev
#[test]
#[should_panic]
fn test_aggregate_destroy_device_for_unknown_plugin_and_aggregate_devices() {
assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err())
run_serially_forward_panics(|| {
assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err())
});
}
#[test]
#[should_panic]
fn test_aggregate_destroy_aggregate_device_for_a_unknown_aggregate_device() {
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
assert!(AggregateDevice::destroy_device(plugin, kAudioObjectUnknown).is_err());
run_serially_forward_panics(|| {
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
assert!(AggregateDevice::destroy_device(plugin, kAudioObjectUnknown).is_err());
});
}
// Default Ignored Tests
// ================================================================================================
// The following tests that calls `AggregateDevice::create_blank_device` are marked `ignore` by
// default since the device-collection-changed callbacks will be fired upon
// `AggregateDevice::create_blank_device` is called (it will plug a new device in system!).
// Some tests rely on the device-collection-changed callbacks in a certain way. The callbacks
// fired from a unexpected `AggregateDevice::create_blank_device` will break those tests.
// AggregateDevice::create_blank_device_sync
// ------------------------------------
#[test]
#[ignore]
fn test_aggregate_create_blank_device() {
// TODO: Test this when there is no available devices.
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
let devices = test_get_all_devices(DeviceFilter::IncludeAll);
let device = devices.into_iter().find(|dev| dev == &device).unwrap();
let uid = get_device_global_uid(device).unwrap().into_string();
let uid = run_serially(|| get_device_global_uid(device).unwrap().into_string());
assert!(uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME));
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
// AggregateDevice::get_sub_devices
// ------------------------------------
#[test]
#[ignore]
#[should_panic]
fn test_aggregate_get_sub_devices_for_blank_aggregate_devices() {
// TODO: Test this when there is no available devices.
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
// There is no sub device in a blank aggregate device!
// AggregateDevice::get_sub_devices guarantees returning a non-empty devices vector, so
// the following call will panic!
let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
assert!(sub_devices.is_empty());
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
run_serially_forward_panics(|| {
// TODO: Test this when there is no available devices.
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
// There is no sub device in a blank aggregate device!
// AggregateDevice::get_sub_devices guarantees returning a non-empty devices vector, so
// the following call will panic!
let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
assert!(sub_devices.is_empty());
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
});
}
// AggregateDevice::set_sub_devices_sync
// ------------------------------------
#[test]
#[ignore]
fn test_aggregate_set_sub_devices() {
let input_device = test_get_default_device(Scope::Input);
let output_device = test_get_default_device(Scope::Output);
@ -147,13 +164,20 @@ fn test_aggregate_set_sub_devices() {
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok());
let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
assert!(run_serially(|| AggregateDevice::set_sub_devices_sync(
device,
input_device,
output_device
))
.is_ok());
let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
let input_sub_devices = AggregateDevice::get_sub_devices(input_device).unwrap();
let output_sub_devices = AggregateDevice::get_sub_devices(output_device).unwrap();
let sub_devices = run_serially(|| AggregateDevice::get_sub_devices(device)).unwrap();
let input_sub_devices =
run_serially(|| AggregateDevice::get_sub_devices(input_device)).unwrap();
let output_sub_devices =
run_serially(|| AggregateDevice::get_sub_devices(output_device)).unwrap();
// TODO: There may be overlapping devices between input_sub_devices and output_sub_devices,
// but now AggregateDevice::set_sub_devices will add them directly.
@ -168,10 +192,10 @@ fn test_aggregate_set_sub_devices() {
assert!(sub_devices.contains(dev));
}
let onwed_devices = test_get_all_onwed_devices(device);
let onwed_device_uids = get_device_uids(&onwed_devices);
let input_sub_device_uids = get_device_uids(&input_sub_devices);
let output_sub_device_uids = get_device_uids(&output_sub_devices);
let onwed_devices = run_serially(|| test_get_all_onwed_devices(device));
let onwed_device_uids = run_serially(|| get_device_uids(&onwed_devices));
let input_sub_device_uids = run_serially(|| get_device_uids(&input_sub_devices));
let output_sub_device_uids = run_serially(|| get_device_uids(&output_sub_devices));
for uid in &input_sub_device_uids {
assert!(onwed_device_uids.contains(uid));
}
@ -179,11 +203,10 @@ fn test_aggregate_set_sub_devices() {
assert!(onwed_device_uids.contains(uid));
}
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
#[test]
#[ignore]
#[should_panic]
fn test_aggregate_set_sub_devices_for_unknown_input_devices() {
let output_device = test_get_default_device(Scope::Output);
@ -192,16 +215,19 @@ fn test_aggregate_set_sub_devices_for_unknown_input_devices() {
}
let output_device = output_device.unwrap();
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
run_serially_forward_panics(|| {
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::set_sub_devices(device, kAudioObjectUnknown, output_device).is_err());
assert!(
AggregateDevice::set_sub_devices(device, kAudioObjectUnknown, output_device).is_err()
);
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
});
}
#[test]
#[ignore]
#[should_panic]
fn test_aggregate_set_sub_devices_for_unknown_output_devices() {
let input_device = test_get_default_device(Scope::Input);
@ -210,12 +236,16 @@ fn test_aggregate_set_sub_devices_for_unknown_output_devices() {
}
let input_device = input_device.unwrap();
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
run_serially_forward_panics(|| {
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::set_sub_devices(device, input_device, kAudioObjectUnknown).is_err());
assert!(
AggregateDevice::set_sub_devices(device, input_device, kAudioObjectUnknown).is_err()
);
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
});
}
fn get_device_uids(devices: &Vec<AudioObjectID>) -> Vec<String> {
@ -228,7 +258,6 @@ fn get_device_uids(devices: &Vec<AudioObjectID>) -> Vec<String> {
// AggregateDevice::set_master_device
// ------------------------------------
#[test]
#[ignore]
fn test_aggregate_set_master_device() {
let input_device = test_get_default_device(Scope::Input);
let output_device = test_get_default_device(Scope::Output);
@ -240,22 +269,28 @@ fn test_aggregate_set_master_device() {
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok());
assert!(AggregateDevice::set_master_device(device, output_device).is_ok());
let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
assert!(run_serially(|| AggregateDevice::set_sub_devices_sync(
device,
input_device,
output_device
))
.is_ok());
assert!(run_serially(|| AggregateDevice::set_master_device(device, output_device)).is_ok());
// Check if master is set to the first sub device of the default output device.
let first_output_sub_device_uid =
get_device_uid(AggregateDevice::get_sub_devices(device).unwrap()[0]);
let master_device_uid = test_get_master_device(device);
let output_sub_devices =
run_serially(|| AggregateDevice::get_sub_devices(output_device)).unwrap();
let first_output_sub_device_uid = run_serially(|| get_device_uid(output_sub_devices[0]));
// Check that the first sub device of the output device is set as master device.
let master_device_uid = run_serially(|| test_get_master_device(device));
assert_eq!(first_output_sub_device_uid, master_device_uid);
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
#[test]
#[ignore]
fn test_aggregate_set_master_device_for_a_blank_aggregate_device() {
let output_device = test_get_default_device(Scope::Output);
if output_device.is_none() {
@ -263,9 +298,11 @@ fn test_aggregate_set_master_device_for_a_blank_aggregate_device() {
return;
}
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::set_master_device(device, output_device.unwrap()).is_ok());
let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
assert!(
run_serially(|| AggregateDevice::set_master_device(device, output_device.unwrap())).is_ok()
);
// TODO: it's really weird the aggregate device actually own nothing
// but its master device can be set successfully!
@ -275,17 +312,16 @@ fn test_aggregate_set_master_device_for_a_blank_aggregate_device() {
// The CFStringRef of the master device returned from `test_get_master_device` is actually
// non-null.
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
fn get_device_uid(id: AudioObjectID) -> String {
get_device_global_uid(id).unwrap().into_string()
get_device_global_uid(id).map_or(String::new(), |uid| uid.into_string())
}
// AggregateDevice::activate_clock_drift_compensation
// ------------------------------------
#[test]
#[ignore]
fn test_aggregate_activate_clock_drift_compensation() {
let input_device = test_get_default_device(Scope::Input);
let output_device = test_get_default_device(Scope::Output);
@ -297,27 +333,40 @@ fn test_aggregate_activate_clock_drift_compensation() {
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok());
assert!(AggregateDevice::set_master_device(device, output_device).is_ok());
assert!(AggregateDevice::activate_clock_drift_compensation(device).is_ok());
let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
assert!(run_serially(|| AggregateDevice::set_sub_devices_sync(
device,
input_device,
output_device
))
.is_ok());
assert!(run_serially(|| AggregateDevice::set_master_device(device, output_device)).is_ok());
assert!(run_serially(|| AggregateDevice::activate_clock_drift_compensation(device)).is_ok());
// Check the compensations.
let devices = test_get_all_onwed_devices(device);
let compensations = get_drift_compensations(&devices);
let devices = run_serially(|| test_get_all_onwed_devices(device));
let compensations = run_serially(|| get_drift_compensations(&devices));
let master_device_uid = run_serially(|| test_get_master_device(device));
assert!(!compensations.is_empty());
assert_eq!(devices.len(), compensations.len());
for (i, compensation) in compensations.iter().enumerate() {
assert_eq!(*compensation, if i == 0 { 0 } else { DRIFT_COMPENSATION });
for (device, compensation) in zip(devices, compensations) {
let uid = get_device_uid(device);
assert_eq!(
compensation,
if uid == master_device_uid {
0
} else {
DRIFT_COMPENSATION
}
);
}
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
#[test]
#[ignore]
fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_without_master_device()
{
let input_device = test_get_default_device(Scope::Input);
@ -330,25 +379,32 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_with
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok());
let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
assert!(run_serially(|| AggregateDevice::set_sub_devices_sync(
device,
input_device,
output_device
))
.is_ok());
// 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);
// The master device is by default the first sub device in the list.
// This happens to be the first sub device of the input device, see implementation of
// AggregateDevice::set_sub_devices.
let first_input_sub_device_uid =
run_serially(|| get_device_uid(AggregateDevice::get_sub_devices(input_device).unwrap()[0]));
let first_sub_device_uid =
run_serially(|| get_device_uid(AggregateDevice::get_sub_devices(device).unwrap()[0]));
assert_eq!(first_input_sub_device_uid, first_sub_device_uid);
let master_device_uid = run_serially(|| test_get_master_device(device));
assert_eq!(first_sub_device_uid, master_device_uid);
// Compensate the drift directly without setting master device.
assert!(AggregateDevice::activate_clock_drift_compensation(device).is_ok());
assert!(run_serially(|| AggregateDevice::activate_clock_drift_compensation(device)).is_ok());
// Check the compensations.
let devices = test_get_all_onwed_devices(device);
let compensations = get_drift_compensations(&devices);
let devices = run_serially(|| test_get_all_onwed_devices(device));
let compensations = run_serially(|| get_drift_compensations(&devices));
assert!(!compensations.is_empty());
assert_eq!(devices.len(), compensations.len());
@ -356,25 +412,26 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_with
assert_eq!(*compensation, if i == 0 { 0 } else { DRIFT_COMPENSATION });
}
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
#[test]
#[should_panic]
#[ignore]
fn test_aggregate_activate_clock_drift_compensation_for_a_blank_aggregate_device() {
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
run_serially_forward_panics(|| {
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
assert!(sub_devices.is_empty());
let onwed_devices = test_get_all_onwed_devices(device);
assert!(onwed_devices.is_empty());
let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
assert!(sub_devices.is_empty());
let onwed_devices = test_get_all_onwed_devices(device);
assert!(onwed_devices.is_empty());
// Get a panic since no sub devices to be set compensation.
assert!(AggregateDevice::activate_clock_drift_compensation(device).is_err());
// Get a panic since no sub devices to be set compensation.
assert!(AggregateDevice::activate_clock_drift_compensation(device).is_err());
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
});
}
fn get_drift_compensations(devices: &Vec<AudioObjectID>) -> Vec<u32> {
@ -391,10 +448,56 @@ fn get_drift_compensations(devices: &Vec<AudioObjectID>) -> Vec<u32> {
// AggregateDevice::destroy_device
// ------------------------------------
#[test]
#[ignore]
#[should_panic]
fn test_aggregate_destroy_aggregate_device_for_a_unknown_plugin_device() {
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, device).is_err());
run_serially_forward_panics(|| {
let plugin = AggregateDevice::get_system_plugin_id().unwrap();
let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, device).is_err());
});
}
// AggregateDevice::new
// ------------------------------------
#[test]
fn test_aggregate_new() {
let input_device = test_get_default_device(Scope::Input);
let output_device = test_get_default_device(Scope::Output);
if input_device.is_none() || output_device.is_none() || input_device == output_device {
println!("No input or output device to create an aggregate device.");
return;
}
run_serially_forward_panics(|| {
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
let aggr = AggregateDevice::new(input_device, output_device).unwrap();
// Check main device
let output_sub_devices = AggregateDevice::get_sub_devices(output_device).unwrap();
let first_output_sub_device_uid = get_device_uid(output_sub_devices[0]);
let master_device_uid = test_get_master_device(aggr.get_device_id());
assert_eq!(first_output_sub_device_uid, master_device_uid);
// Check drift compensation
let devices = test_get_all_onwed_devices(aggr.get_device_id());
let compensations = get_drift_compensations(&devices);
assert!(!compensations.is_empty());
assert_eq!(devices.len(), compensations.len());
let device_uids = devices.iter().map(|&id| get_device_uid(id));
for (uid, compensation) in zip(device_uids, compensations) {
assert_eq!(
compensation,
if uid == master_device_uid {
0
} else {
DRIFT_COMPENSATION
},
"Unexpected drift value for device with uid {}",
uid
);
}
});
}

Просмотреть файл

@ -56,7 +56,7 @@ fn test_increase_and_decrease_context_streams() {
assert_eq!(context.active_streams(), STREAMS);
check_streams(&context, STREAMS);
check_latency(&context, latencies[0]);
check_latency(&context, Some(latencies[0]));
for i in 0..latencies.len() - 1 {
assert_eq!(latencies[i], latencies[i + 1]);
}
@ -149,7 +149,8 @@ fn test_minimum_resampling_input_frames_equal_input_output_rate() {
#[test]
fn test_create_device_info_from_unknown_input_device() {
if let Some(default_device_id) = test_get_default_device(Scope::Input) {
let default_device = create_device_info(kAudioObjectUnknown, DeviceType::INPUT).unwrap();
let default_device =
run_serially(|| create_device_info(kAudioObjectUnknown, DeviceType::INPUT).unwrap());
assert_eq!(default_device.id, default_device_id);
assert_eq!(
default_device.flags,
@ -163,7 +164,8 @@ fn test_create_device_info_from_unknown_input_device() {
#[test]
fn test_create_device_info_from_unknown_output_device() {
if let Some(default_device_id) = test_get_default_device(Scope::Output) {
let default_device = create_device_info(kAudioObjectUnknown, DeviceType::OUTPUT).unwrap();
let default_device =
run_serially(|| create_device_info(kAudioObjectUnknown, DeviceType::OUTPUT)).unwrap();
assert_eq!(default_device.id, default_device_id);
assert_eq!(
default_device.flags,
@ -177,13 +179,17 @@ fn test_create_device_info_from_unknown_output_device() {
#[test]
#[should_panic]
fn test_set_device_info_to_system_input_device() {
let _device = create_device_info(kAudioObjectSystemObject, DeviceType::INPUT);
let _device = run_serially_forward_panics(|| {
create_device_info(kAudioObjectSystemObject, DeviceType::INPUT)
});
}
#[test]
#[should_panic]
fn test_set_device_info_to_system_output_device() {
let _device = create_device_info(kAudioObjectSystemObject, DeviceType::OUTPUT);
let _device = run_serially_forward_panics(|| {
create_device_info(kAudioObjectSystemObject, DeviceType::OUTPUT)
});
}
// FIXME: Is it ok to set input device to a nonexistent device ?
@ -192,7 +198,8 @@ fn test_set_device_info_to_system_output_device() {
#[should_panic]
fn test_set_device_info_to_nonexistent_input_device() {
let nonexistent_id = std::u32::MAX;
let _device = create_device_info(nonexistent_id, DeviceType::INPUT);
let _device =
run_serially_forward_panics(|| create_device_info(nonexistent_id, DeviceType::INPUT));
}
// FIXME: Is it ok to set output device to a nonexistent device ?
@ -201,7 +208,8 @@ fn test_set_device_info_to_nonexistent_input_device() {
#[should_panic]
fn test_set_device_info_to_nonexistent_output_device() {
let nonexistent_id = std::u32::MAX;
let _device = create_device_info(nonexistent_id, DeviceType::OUTPUT);
let _device =
run_serially_forward_panics(|| create_device_info(nonexistent_id, DeviceType::OUTPUT));
}
// add_listener (for default output device)
@ -227,10 +235,10 @@ fn test_add_listener_unknown_device() {
),
callback,
);
let mut res: OSStatus = 0;
stream
let res = stream
.queue
.run_sync(|| res = stream.add_device_listener(&listener));
.run_sync(|| stream.add_device_listener(&listener))
.unwrap();
assert_eq!(res, kAudioHardwareBadObjectError as OSStatus);
});
}
@ -258,14 +266,15 @@ fn test_add_listener_then_remove_system_device() {
),
callback,
);
let mut res: OSStatus = 0;
stream
let res = stream
.queue
.run_sync(|| res = stream.add_device_listener(&listener));
.run_sync(|| stream.add_device_listener(&listener))
.unwrap();
assert_eq!(res, NO_ERR);
stream
let res = stream
.queue
.run_sync(|| res = stream.remove_device_listener(&listener));
.run_sync(|| stream.remove_device_listener(&listener))
.unwrap();
assert_eq!(res, NO_ERR);
});
}
@ -291,10 +300,10 @@ fn test_remove_listener_without_adding_any_listener_before_system_device() {
),
callback,
);
let mut res: OSStatus = 0;
stream
let res = stream
.queue
.run_sync(|| res = stream.remove_device_listener(&listener));
.run_sync(|| stream.remove_device_listener(&listener))
.unwrap();
assert_eq!(res, NO_ERR);
});
}
@ -320,10 +329,10 @@ fn test_remove_listener_unknown_device() {
),
callback,
);
let mut res: OSStatus = 0;
stream
let res = stream
.queue
.run_sync(|| res = stream.remove_device_listener(&listener));
.run_sync(|| stream.remove_device_listener(&listener))
.unwrap();
assert_eq!(res, kAudioHardwareBadObjectError as OSStatus);
});
}
@ -334,14 +343,14 @@ fn test_remove_listener_unknown_device() {
fn test_get_default_device_id() {
if test_get_default_device(Scope::Input).is_some() {
assert_ne!(
get_default_device_id(DeviceType::INPUT).unwrap(),
run_serially(|| get_default_device_id(DeviceType::INPUT)).unwrap(),
kAudioObjectUnknown,
);
}
if test_get_default_device(Scope::Output).is_some() {
assert_ne!(
get_default_device_id(DeviceType::OUTPUT).unwrap(),
run_serially(|| get_default_device_id(DeviceType::OUTPUT)).unwrap(),
kAudioObjectUnknown,
);
}
@ -350,13 +359,16 @@ fn test_get_default_device_id() {
#[test]
#[should_panic]
fn test_get_default_device_id_with_unknown_type() {
assert!(get_default_device_id(DeviceType::UNKNOWN).is_err());
assert!(run_serially_forward_panics(|| get_default_device_id(DeviceType::UNKNOWN)).is_err());
}
#[test]
#[should_panic]
fn test_get_default_device_id_with_inout_type() {
assert!(get_default_device_id(DeviceType::INPUT | DeviceType::OUTPUT).is_err());
assert!(run_serially_forward_panics(|| get_default_device_id(
DeviceType::INPUT | DeviceType::OUTPUT
))
.is_err());
}
// convert_channel_layout
@ -724,9 +736,11 @@ fn test_convert_channel_layout() {
#[test]
fn test_get_preferred_channel_layout_output() {
match test_get_default_audiounit(Scope::Output) {
Some(unit) => assert!(!audiounit_get_preferred_channel_layout(unit.get_inner())
.unwrap()
.is_empty()),
Some(unit) => assert!(!run_serially(|| audiounit_get_preferred_channel_layout(
unit.get_inner()
))
.unwrap()
.is_empty()),
None => println!("No output audiounit for test."),
}
}
@ -736,9 +750,15 @@ fn test_get_preferred_channel_layout_output() {
#[test]
fn test_get_current_channel_layout_output() {
match test_get_default_audiounit(Scope::Output) {
Some(unit) => assert!(!audiounit_get_current_channel_layout(unit.get_inner())
.unwrap()
.is_empty()),
Some(unit) => {
assert!(
!run_serially_forward_panics(|| audiounit_get_current_channel_layout(
unit.get_inner()
))
.unwrap()
.is_empty()
)
}
None => println!("No output audiounit for test."),
}
}
@ -814,10 +834,30 @@ fn test_enable_audiounit_scope() {
// for the unit whose subtype is kAudioUnitSubType_HALOutput
// even when there is no available input or output devices.
if let Some(unit) = test_create_audiounit(ComponentSubType::HALOutput) {
assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true).is_ok());
assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false).is_ok());
assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true).is_ok());
assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false).is_ok());
assert!(run_serially_forward_panics(|| enable_audiounit_scope(
unit.get_inner(),
DeviceType::OUTPUT,
true
))
.is_ok());
assert!(run_serially_forward_panics(|| enable_audiounit_scope(
unit.get_inner(),
DeviceType::OUTPUT,
false
))
.is_ok());
assert!(run_serially_forward_panics(|| enable_audiounit_scope(
unit.get_inner(),
DeviceType::INPUT,
true
))
.is_ok());
assert!(run_serially_forward_panics(|| enable_audiounit_scope(
unit.get_inner(),
DeviceType::INPUT,
false
))
.is_ok());
} else {
println!("No audiounit to perform test.");
}
@ -827,19 +867,23 @@ fn test_enable_audiounit_scope() {
fn test_enable_audiounit_scope_for_default_output_unit() {
if let Some(unit) = test_create_audiounit(ComponentSubType::DefaultOutput) {
assert_eq!(
enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true).unwrap_err(),
run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true))
.unwrap_err(),
kAudioUnitErr_InvalidProperty
);
assert_eq!(
enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false).unwrap_err(),
run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false))
.unwrap_err(),
kAudioUnitErr_InvalidProperty
);
assert_eq!(
enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true).unwrap_err(),
run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true))
.unwrap_err(),
kAudioUnitErr_InvalidProperty
);
assert_eq!(
enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false).unwrap_err(),
run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false))
.unwrap_err(),
kAudioUnitErr_InvalidProperty
);
}
@ -849,7 +893,10 @@ fn test_enable_audiounit_scope_for_default_output_unit() {
#[should_panic]
fn test_enable_audiounit_scope_with_null_unit() {
let unit: AudioUnit = ptr::null_mut();
assert!(enable_audiounit_scope(unit, DeviceType::INPUT, false).is_err());
assert!(
run_serially_forward_panics(|| enable_audiounit_scope(unit, DeviceType::INPUT, false))
.is_err()
);
}
// create_audiounit
@ -868,29 +915,29 @@ fn test_for_create_audiounit() {
// Check the output scope is enabled.
if device.flags.contains(device_flags::DEV_OUTPUT) && default_output.is_some() {
device.id = default_output.unwrap();
let unit = create_audiounit(&device).unwrap();
let unit = run_serially(|| create_audiounit(&device).unwrap());
assert!(!unit.is_null());
assert!(test_audiounit_scope_is_enabled(unit, Scope::Output));
// Destroy the AudioUnit.
unsafe {
run_serially(|| unsafe {
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
}
});
}
// Check the input scope is enabled.
if device.flags.contains(device_flags::DEV_INPUT) && default_input.is_some() {
let device_id = default_input.unwrap();
device.id = device_id;
let unit = create_audiounit(&device).unwrap();
let unit = run_serially(|| create_audiounit(&device).unwrap());
assert!(!unit.is_null());
assert!(test_audiounit_scope_is_enabled(unit, Scope::Input));
// Destroy the AudioUnit.
unsafe {
run_serially(|| unsafe {
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
}
});
}
}
}
@ -899,7 +946,7 @@ fn test_for_create_audiounit() {
#[should_panic]
fn test_create_audiounit_with_unknown_scope() {
let device = device_info::default();
let _unit = create_audiounit(&device);
let _unit = run_serially_forward_panics(|| create_audiounit(&device));
}
// set_buffer_size_sync
@ -927,9 +974,12 @@ fn test_set_buffer_size_sync() {
.unwrap();
assert_ne!(buffer_frames, 0);
buffer_frames *= 2;
assert!(
set_buffer_size_sync(unit.get_inner(), scope.clone().into(), buffer_frames).is_ok()
);
assert!(run_serially(|| set_buffer_size_sync(
unit.get_inner(),
scope.clone().into(),
buffer_frames
))
.is_ok());
let new_buffer_frames =
test_audiounit_get_buffer_frame_size(unit.get_inner(), scope.clone(), prop_scope)
.unwrap();
@ -951,7 +1001,9 @@ fn test_set_buffer_size_sync_for_output_with_null_output_unit() {
fn test_set_buffer_size_sync_by_scope_with_null_unit(scope: Scope) {
let unit: AudioUnit = ptr::null_mut();
assert!(set_buffer_size_sync(unit, scope.into(), 2048).is_err());
assert!(
run_serially_forward_panics(|| set_buffer_size_sync(unit, scope.into(), 2048)).is_err()
);
}
// get_volume, set_volume
@ -960,8 +1012,11 @@ fn test_set_buffer_size_sync_by_scope_with_null_unit(scope: Scope) {
fn test_stream_get_volume() {
if let Some(unit) = test_get_default_audiounit(Scope::Output) {
let expected_volume: f32 = 0.5;
set_volume(unit.get_inner(), expected_volume);
assert_eq!(expected_volume, get_volume(unit.get_inner()).unwrap());
run_serially(|| set_volume(unit.get_inner(), expected_volume));
assert_eq!(
expected_volume,
run_serially(|| get_volume(unit.get_inner()).unwrap())
);
} else {
println!("No output audiounit.");
}
@ -988,7 +1043,9 @@ fn test_get_channel_count() {
fn test_channel_count(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
let channels = get_channel_count(device, DeviceType::from(scope.clone())).unwrap();
let channels =
run_serially(|| get_channel_count(device, DeviceType::from(scope.clone())))
.unwrap();
assert!(channels > 0);
assert_eq!(
channels,
@ -1008,7 +1065,7 @@ fn test_get_channel_count_of_input_for_a_output_only_deivce() {
if test_device_in_scope(device, Scope::Input) {
continue;
}
let count = get_channel_count(device, DeviceType::INPUT).unwrap();
let count = run_serially(|| get_channel_count(device, DeviceType::INPUT)).unwrap();
assert_eq!(count, 0);
}
}
@ -1021,7 +1078,7 @@ fn test_get_channel_count_of_output_for_a_input_only_deivce() {
if test_device_in_scope(device, Scope::Output) {
continue;
}
let count = get_channel_count(device, DeviceType::OUTPUT).unwrap();
let count = run_serially(|| get_channel_count(device, DeviceType::OUTPUT)).unwrap();
assert_eq!(count, 0);
}
}
@ -1029,7 +1086,11 @@ fn test_get_channel_count_of_output_for_a_input_only_deivce() {
#[test]
#[should_panic]
fn test_get_channel_count_of_unknown_device() {
assert!(get_channel_count(kAudioObjectUnknown, DeviceType::OUTPUT).is_err());
assert!(run_serially_forward_panics(|| get_channel_count(
kAudioObjectUnknown,
DeviceType::OUTPUT
))
.is_err());
}
#[test]
@ -1039,14 +1100,16 @@ fn test_get_channel_count_of_inout_type() {
fn test_channel_count(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
assert_eq!(
get_channel_count(device, DeviceType::INPUT | DeviceType::OUTPUT),
get_channel_count(device, DeviceType::INPUT).map(|c| c + get_channel_count(
device,
DeviceType::OUTPUT
)
.unwrap_or(0))
);
run_serially_forward_panics(|| {
assert_eq!(
get_channel_count(device, DeviceType::INPUT | DeviceType::OUTPUT),
get_channel_count(device, DeviceType::INPUT).map(|c| c + get_channel_count(
device,
DeviceType::OUTPUT
)
.unwrap_or(0))
);
});
} else {
println!("No device for {:?}.", scope);
}
@ -1095,7 +1158,9 @@ fn test_get_range_of_sample_rates() {
];
let mut ranges = Vec::new();
for scope in scopes.iter() {
ranges.push(get_range_of_sample_rates(id, *scope).unwrap());
ranges.push(
run_serially_forward_panics(|| get_range_of_sample_rates(id, *scope)).unwrap(),
);
}
ranges
}
@ -1117,7 +1182,7 @@ fn test_get_device_presentation_latency() {
fn test_get_device_presentation_latencies_in_scope(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
// TODO: The latencies very from devices to devices. Check nothing here.
let latency = get_fixed_latency(device, scope.clone().into());
let latency = run_serially(|| get_fixed_latency(device, scope.clone().into()));
println!(
"present latency on the device {} in scope {:?}: {}",
device, scope, latency
@ -1133,7 +1198,7 @@ fn test_get_device_presentation_latency() {
#[test]
fn test_get_device_group_id() {
if let Some(device) = test_get_default_device(Scope::Input) {
match get_device_group_id(device, DeviceType::INPUT) {
match run_serially(|| get_device_group_id(device, DeviceType::INPUT)) {
Ok(id) => println!("input group id: {:?}", id),
Err(e) => println!("No input group id. Error: {}", e),
}
@ -1142,7 +1207,7 @@ fn test_get_device_group_id() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
match get_device_group_id(device, DeviceType::OUTPUT) {
match run_serially(|| get_device_group_id(device, DeviceType::OUTPUT)) {
Ok(id) => println!("output group id: {:?}", id),
Err(e) => println!("No output group id. Error: {}", e),
}
@ -1165,8 +1230,8 @@ fn test_get_same_group_id_for_builtin_device_pairs() {
let mut input_group_ids = HashMap::<u32, String>::new();
let input_devices = test_get_devices_in_scope(Scope::Input);
for device in input_devices.iter() {
match get_device_source(*device, DeviceType::INPUT) {
Ok(source) => match get_device_group_id(*device, DeviceType::INPUT) {
match run_serially(|| get_device_source(*device, DeviceType::INPUT)) {
Ok(source) => match run_serially(|| get_device_group_id(*device, DeviceType::INPUT)) {
Ok(id) => assert!(input_group_ids
.insert(source, id.into_string().unwrap())
.is_none()),
@ -1181,8 +1246,8 @@ fn test_get_same_group_id_for_builtin_device_pairs() {
let mut output_group_ids = HashMap::<u32, String>::new();
let output_devices = test_get_devices_in_scope(Scope::Output);
for device in output_devices.iter() {
match get_device_source(*device, DeviceType::OUTPUT) {
Ok(source) => match get_device_group_id(*device, DeviceType::OUTPUT) {
match run_serially(|| get_device_source(*device, DeviceType::OUTPUT)) {
Ok(source) => match run_serially(|| get_device_group_id(*device, DeviceType::OUTPUT)) {
Ok(id) => assert!(output_group_ids
.insert(source, id.into_string().unwrap())
.is_none()),
@ -1210,7 +1275,11 @@ fn test_get_same_group_id_for_builtin_device_pairs() {
#[test]
#[should_panic]
fn test_get_device_group_id_by_unknown_device() {
assert!(get_device_group_id(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_group_id(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_label
@ -1218,14 +1287,14 @@ fn test_get_device_group_id_by_unknown_device() {
#[test]
fn test_get_device_label() {
if let Some(device) = test_get_default_device(Scope::Input) {
let name = get_device_label(device, DeviceType::INPUT).unwrap();
let name = run_serially(|| get_device_label(device, DeviceType::INPUT)).unwrap();
println!("input device label: {}", name.into_string());
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
let name = get_device_label(device, DeviceType::OUTPUT).unwrap();
let name = run_serially(|| get_device_label(device, DeviceType::OUTPUT)).unwrap();
println!("output device label: {}", name.into_string());
} else {
println!("No output device.");
@ -1235,7 +1304,11 @@ fn test_get_device_label() {
#[test]
#[should_panic]
fn test_get_device_label_by_unknown_device() {
assert!(get_device_label(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_label(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_global_uid
@ -1244,14 +1317,14 @@ fn test_get_device_label_by_unknown_device() {
fn test_get_device_global_uid() {
// Input device.
if let Some(input) = test_get_default_device(Scope::Input) {
let uid = get_device_global_uid(input).unwrap();
let uid = run_serially(|| get_device_global_uid(input)).unwrap();
let uid = uid.into_string();
assert!(!uid.is_empty());
}
// Output device.
if let Some(output) = test_get_default_device(Scope::Output) {
let uid = get_device_global_uid(output).unwrap();
let uid = run_serially(|| get_device_global_uid(output)).unwrap();
let uid = uid.into_string();
assert!(!uid.is_empty());
}
@ -1285,7 +1358,7 @@ fn test_create_cubeb_device_info() {
if is_input {
let mut input_device_info = input_result.unwrap();
check_device_info_by_device(&input_device_info, device, Scope::Input);
destroy_cubeb_device_info(&mut input_device_info);
run_serially(|| destroy_cubeb_device_info(&mut input_device_info));
} else {
assert_eq!(input_result.unwrap_err(), Error::error());
}
@ -1294,7 +1367,7 @@ fn test_create_cubeb_device_info() {
if is_output {
let mut output_device_info = output_result.unwrap();
check_device_info_by_device(&output_device_info, device, Scope::Output);
destroy_cubeb_device_info(&mut output_device_info);
run_serially(|| destroy_cubeb_device_info(&mut output_device_info));
} else {
assert_eq!(output_result.unwrap_err(), Error::error());
}
@ -1309,7 +1382,7 @@ fn test_create_cubeb_device_info() {
let dev_types = [DeviceType::INPUT, DeviceType::OUTPUT];
let mut results = VecDeque::new();
for dev_type in dev_types.iter() {
results.push_back(create_cubeb_device_info(id, *dev_type));
results.push_back(run_serially(|| create_cubeb_device_info(id, *dev_type)));
}
results
}
@ -1369,7 +1442,9 @@ fn test_create_device_info_with_unknown_type() {
fn test_create_device_info_with_unknown_type_by_scope(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
assert!(create_cubeb_device_info(device, DeviceType::UNKNOWN).is_err());
assert!(
run_serially(|| create_cubeb_device_info(device, DeviceType::UNKNOWN)).is_err()
);
}
}
}
@ -1401,9 +1476,11 @@ fn test_create_device_from_hwdev_with_inout_type() {
fn test_create_device_from_hwdev_with_inout_type_by_scope(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
// Get a kAudioHardwareUnknownPropertyError in get_channel_count actually.
assert!(
create_cubeb_device_info(device, DeviceType::INPUT | DeviceType::OUTPUT).is_err()
);
assert!(run_serially(|| create_cubeb_device_info(
device,
DeviceType::INPUT | DeviceType::OUTPUT
))
.is_err());
} else {
println!("No device for {:?}.", scope);
}
@ -1416,9 +1493,10 @@ fn test_create_device_from_hwdev_with_inout_type() {
fn test_get_devices_of_type() {
use std::collections::HashSet;
let all_devices = audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT);
let input_devices = audiounit_get_devices_of_type(DeviceType::INPUT);
let output_devices = audiounit_get_devices_of_type(DeviceType::OUTPUT);
let all_devices =
run_serially(|| audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT));
let input_devices = run_serially(|| audiounit_get_devices_of_type(DeviceType::INPUT));
let output_devices = run_serially(|| audiounit_get_devices_of_type(DeviceType::OUTPUT));
let mut expected_all = test_get_all_devices(DeviceFilter::ExcludeCubebAggregateAndVPIO);
expected_all.sort();
@ -1443,8 +1521,10 @@ fn test_get_devices_of_type() {
#[test]
#[should_panic]
fn test_get_devices_of_type_unknown() {
let no_devs = audiounit_get_devices_of_type(DeviceType::UNKNOWN);
assert!(no_devs.is_empty());
run_serially_forward_panics(|| {
let no_devs = audiounit_get_devices_of_type(DeviceType::UNKNOWN);
assert!(no_devs.is_empty());
});
}
// add_devices_changed_listener
@ -1468,9 +1548,10 @@ fn test_add_devices_changed_listener() {
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
// Register a callback within a specific scope.
assert!(context
.add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut())
.is_ok());
assert!(run_serially(|| {
context.add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut())
})
.is_ok());
if devtype.contains(DeviceType::INPUT) {
let cb = get_devices_changed_callback(context, Scope::Input);
@ -1491,9 +1572,10 @@ fn test_add_devices_changed_listener() {
}
// Unregister the callbacks within all scopes.
assert!(context
.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
.is_ok());
assert!(run_serially(|| {
context.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
})
.is_ok());
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
@ -1547,9 +1629,12 @@ fn test_remove_devices_changed_listener() {
// Register callbacks within all scopes.
for (scope, listener) in map.iter() {
assert!(context
.add_devices_changed_listener(*scope, Some(*listener), ptr::null_mut())
.is_ok());
assert!(run_serially(|| context.add_devices_changed_listener(
*scope,
Some(*listener),
ptr::null_mut()
))
.is_ok());
}
let input_callback = get_devices_changed_callback(context, Scope::Input);
@ -1566,7 +1651,7 @@ fn test_remove_devices_changed_listener() {
);
// Unregister the callbacks within one specific scopes.
assert!(context.remove_devices_changed_listener(*devtype).is_ok());
assert!(run_serially(|| context.remove_devices_changed_listener(*devtype)).is_ok());
if devtype.contains(DeviceType::INPUT) {
let cb = get_devices_changed_callback(context, Scope::Input);
@ -1587,9 +1672,10 @@ fn test_remove_devices_changed_listener() {
}
// Unregister the callbacks within all scopes.
assert!(context
.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
.is_ok());
assert!(run_serially(
|| context.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
)
.is_ok());
}
});
}
@ -1602,7 +1688,7 @@ fn test_remove_devices_changed_listener_without_adding_listeners() {
DeviceType::OUTPUT,
DeviceType::INPUT | DeviceType::OUTPUT,
] {
assert!(context.remove_devices_changed_listener(*devtype).is_ok());
assert!(run_serially(|| context.remove_devices_changed_listener(*devtype)).is_ok());
}
});
}
@ -1625,9 +1711,12 @@ fn test_remove_devices_changed_listener_within_all_scopes() {
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
assert!(context
.add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut())
.is_ok());
assert!(run_serially(|| context.add_devices_changed_listener(
*devtype,
Some(*callback),
ptr::null_mut()
))
.is_ok());
if devtype.contains(DeviceType::INPUT) {
let cb = get_devices_changed_callback(context, Scope::Input);
@ -1641,9 +1730,10 @@ fn test_remove_devices_changed_listener_within_all_scopes() {
assert_eq!(cb.unwrap(), *callback);
}
assert!(context
.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
.is_ok());
assert!(run_serially(
|| context.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
)
.is_ok());
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
@ -1661,3 +1751,135 @@ fn get_devices_changed_callback(
Scope::Output => devices_guard.output.changed_callback,
}
}
// SharedVoiceProcessingUnitManager
// ------------------------------------
#[test]
fn test_shared_voice_processing_unit() {
let queue = Queue::new_with_target(
"test_shared_voice_processing_unit",
get_serial_queue_singleton(),
);
let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone());
let r1 = queue.run_sync(|| shared.take()).unwrap();
assert!(r1.is_err());
let r2 = queue.run_sync(|| shared.take_or_create()).unwrap();
assert!(r2.is_ok());
{
let _handle = r2.unwrap();
let r3 = queue.run_sync(|| shared.take()).unwrap();
assert!(r3.is_err());
}
let r4 = queue.run_sync(|| shared.take()).unwrap();
assert!(r4.is_ok());
}
#[test]
#[should_panic]
fn test_shared_voice_processing_unit_bad_release_order() {
let queue = Queue::new_with_target(
"test_shared_voice_processing_unit_bad_release_order",
get_serial_queue_singleton(),
);
let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone());
let r1 = queue.run_sync(|| shared.take()).unwrap();
assert!(r1.is_ok());
drop(shared);
run_serially_forward_panics(|| drop(r1));
}
#[test]
fn test_shared_voice_processing_multiple_units() {
let queue = Queue::new_with_target(
"test_shared_voice_processing_multiple_units",
get_serial_queue_singleton(),
);
let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone());
let r1 = queue.run_sync(|| shared.take_or_create()).unwrap();
assert!(r1.is_ok());
let r2 = queue.run_sync(|| shared.take_or_create()).unwrap();
assert!(r2.is_ok());
{
let _handle1 = r1.unwrap();
let _handle2 = r2.unwrap();
let r3 = queue.run_sync(|| shared.take()).unwrap();
assert!(r3.is_err());
}
let r1 = queue.run_sync(|| shared.take()).unwrap();
assert!(r1.is_ok());
let r2 = queue.run_sync(|| shared.take()).unwrap();
assert!(r2.is_ok());
let r3 = queue.run_sync(|| shared.take()).unwrap();
assert!(r3.is_err());
}
#[test]
fn test_shared_voice_processing_release_on_idle() {
let queue = Queue::new_with_target(
"test_shared_voice_processing_release_on_idle",
get_serial_queue_singleton(),
);
let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout(
queue.clone(),
Duration::from_millis(0),
);
let r = queue.run_sync(|| shared.take_or_create()).unwrap();
assert!(r.is_ok());
{
let _handle = r.unwrap();
}
queue.run_sync(|| {});
let r = queue.run_sync(|| shared.take()).unwrap();
assert!(r.is_err());
}
#[test]
fn test_shared_voice_processing_no_release_on_outstanding() {
let queue = Queue::new_with_target(
"test_shared_voice_processing_no_release_on_outstanding",
get_serial_queue_singleton(),
);
let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout(
queue.clone(),
Duration::from_millis(0),
);
let r1 = queue.run_sync(|| shared.take_or_create()).unwrap();
assert!(r1.is_ok());
let r2 = queue.run_sync(|| shared.take_or_create()).unwrap();
assert!(r2.is_ok());
{
let _handle1 = r1.unwrap();
}
queue.run_sync(|| {});
let r1 = queue.run_sync(|| shared.take()).unwrap();
assert!(r1.is_ok());
}
#[test]
fn test_shared_voice_processing_release_on_idle_cancel_on_take() {
let queue = Queue::new_with_target(
"test_shared_voice_processing_release_on_idle_cancel_on_take",
get_serial_queue_singleton(),
);
let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout(
queue.clone(),
Duration::from_millis(0),
);
let r1 = queue.run_sync(|| shared.take_or_create()).unwrap();
assert!(r1.is_ok());
let r2 = queue.run_sync(|| shared.take_or_create()).unwrap();
assert!(r2.is_ok());
let r1 = queue
.run_sync(|| {
{
let _handle1 = r1.unwrap();
let _handle2 = r2.unwrap();
}
shared.take()
})
.unwrap();
assert!(r1.is_ok());
queue.run_sync(|| {});
let r2 = queue.run_sync(|| shared.take()).unwrap();
assert!(r2.is_ok());
}

Просмотреть файл

@ -21,7 +21,7 @@ use super::utils::{
test_set_default_device, Scope, StreamType, TestDevicePlugger, TestDeviceSwitcher,
};
use super::*;
use std::sync::{LockResult, MutexGuard, WaitTimeoutResult};
use std::sync::{LockResult, WaitTimeoutResult};
// Switch default devices used by the active streams, to test stream reinitialization
// ================================================================================================
@ -49,19 +49,21 @@ fn test_switch_device_in_scope(scope: Scope) {
let notifier = Arc::new(Notifier::new(0));
let also_notifier = notifier.clone();
let listener = test_create_device_change_listener(scope.clone(), move |_addresses| {
let mut cnt = notifier.lock().unwrap();
*cnt += 1;
notifier.notify(cnt);
NO_ERR
let listener = run_serially(|| {
test_create_device_change_listener(scope.clone(), move |_addresses| {
let mut cnt = notifier.lock().unwrap();
*cnt += 1;
notifier.notify(cnt);
NO_ERR
})
});
listener.start();
run_serially(|| listener.start());
let changed_watcher = Watcher::new(&also_notifier);
test_get_started_stream_in_scope(scope.clone(), move |_stream| loop {
let mut guard = changed_watcher.lock().unwrap();
let start_cnt = guard.clone();
let start_cnt = changed_watcher.lock().unwrap().clone();
device_switcher.next();
let mut guard = changed_watcher.lock().unwrap();
guard = changed_watcher
.wait_while(guard, |cnt| *cnt == start_cnt)
.unwrap();
@ -709,11 +711,7 @@ fn test_unplug_a_device_on_an_active_stream(
state_callback,
device_changed_callback,
|stream| {
stream.start();
let changed_watcher = Watcher::new(&notifier);
let mut data_guard = notifier.lock().unwrap();
assert_eq!(data_guard.states.last().unwrap(), &ffi::CUBEB_STATE_STARTED);
assert_eq!(stream.start(), Ok(()));
println!(
"Stream runs on the device {} for {:?}",
@ -722,13 +720,20 @@ fn test_unplug_a_device_on_an_active_stream(
);
let dev = plugger.get_device_id();
let start_changed_count = data_guard.changed_count.clone();
let start_changed_count = {
let guard = notifier.lock().unwrap();
assert_eq!(guard.states.last().unwrap(), &ffi::CUBEB_STATE_STARTED);
guard.changed_count.clone()
};
assert!(plugger.unplug().is_ok());
let changed_watcher = Watcher::new(&notifier);
if set_device_to_default {
// The stream will be reinitialized if it follows the default input or output device.
println!("Waiting for default device to change and reinit");
let mut data_guard = notifier.lock().unwrap();
data_guard = changed_watcher
.wait_while(data_guard, |data| {
data.changed_count == start_changed_count
@ -740,6 +745,7 @@ fn test_unplug_a_device_on_an_active_stream(
// stream can be dropped immediately before device-changed callback
// so we only check the states if we wait for it explicitly.
println!("Waiting for non-default device to enter error state");
let mut data_guard = notifier.lock().unwrap();
let (new_guard, timeout_res) = changed_watcher
.wait_timeout_while(data_guard, Duration::from_millis(wait_up_to_ms), |data| {
data.states.last().unwrap_or(&ffi::CUBEB_STATE_STARTED)

Просмотреть файл

@ -7,14 +7,14 @@ use super::*;
fn test_get_device_uid() {
// Input device.
if let Some(input) = test_get_default_device(Scope::Input) {
let uid = get_device_uid(input, DeviceType::INPUT).unwrap();
let uid = run_serially(|| get_device_uid(input, DeviceType::INPUT)).unwrap();
let uid = uid.into_string();
assert!(!uid.is_empty());
}
// Output device.
if let Some(output) = test_get_default_device(Scope::Output) {
let uid = get_device_uid(output, DeviceType::OUTPUT).unwrap();
let uid = run_serially(|| get_device_uid(output, DeviceType::OUTPUT)).unwrap();
let uid = uid.into_string();
assert!(!uid.is_empty());
}
@ -24,7 +24,10 @@ fn test_get_device_uid() {
#[should_panic]
fn test_get_device_uid_by_unknwon_device() {
// Unknown device.
assert!(get_device_uid(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(
run_serially_forward_panics(|| get_device_uid(kAudioObjectUnknown, DeviceType::INPUT))
.is_err()
);
}
// get_device_model_uid
@ -33,7 +36,7 @@ fn test_get_device_uid_by_unknwon_device() {
#[test]
fn test_get_device_model_uid() {
if let Some(device) = test_get_default_device(Scope::Input) {
match get_device_model_uid(device, DeviceType::INPUT) {
match run_serially(|| get_device_model_uid(device, DeviceType::INPUT)) {
Ok(uid) => println!("input model uid: {}", uid.into_string()),
Err(e) => println!("No input model uid. Error: {}", e),
}
@ -42,7 +45,7 @@ fn test_get_device_model_uid() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
match get_device_model_uid(device, DeviceType::OUTPUT) {
match run_serially(|| get_device_model_uid(device, DeviceType::OUTPUT)) {
Ok(uid) => println!("output model uid: {}", uid.into_string()),
Err(e) => println!("No output model uid. Error: {}", e),
}
@ -54,7 +57,11 @@ fn test_get_device_model_uid() {
#[test]
#[should_panic]
fn test_get_device_model_uid_by_unknown_device() {
assert!(get_device_model_uid(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_model_uid(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_transport_type
@ -62,7 +69,7 @@ fn test_get_device_model_uid_by_unknown_device() {
#[test]
fn test_get_device_transport_type() {
if let Some(device) = test_get_default_device(Scope::Input) {
match get_device_transport_type(device, DeviceType::INPUT) {
match run_serially(|| get_device_transport_type(device, DeviceType::INPUT)) {
Ok(trans_type) => println!(
"input transport type: {:X}, {:?}",
trans_type,
@ -75,7 +82,7 @@ fn test_get_device_transport_type() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
match get_device_transport_type(device, DeviceType::OUTPUT) {
match run_serially(|| get_device_transport_type(device, DeviceType::OUTPUT)) {
Ok(trans_type) => println!(
"output transport type: {:X}, {:?}",
trans_type,
@ -91,7 +98,11 @@ fn test_get_device_transport_type() {
#[test]
#[should_panic]
fn test_get_device_transport_type_by_unknown_device() {
assert!(get_device_transport_type(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_transport_type(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_source
@ -100,7 +111,7 @@ fn test_get_device_transport_type_by_unknown_device() {
#[test]
fn test_get_device_source() {
if let Some(device) = test_get_default_device(Scope::Input) {
match get_device_source(device, DeviceType::INPUT) {
match run_serially(|| get_device_source(device, DeviceType::INPUT)) {
Ok(source) => println!(
"input source: {:X}, {:?}",
source,
@ -113,7 +124,7 @@ fn test_get_device_source() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
match get_device_source(device, DeviceType::OUTPUT) {
match run_serially(|| get_device_source(device, DeviceType::OUTPUT)) {
Ok(source) => println!(
"output source: {:X}, {:?}",
source,
@ -129,7 +140,11 @@ fn test_get_device_source() {
#[test]
#[should_panic]
fn test_get_device_source_by_unknown_device() {
assert!(get_device_source(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_source(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_source_name
@ -137,7 +152,7 @@ fn test_get_device_source_by_unknown_device() {
#[test]
fn test_get_device_source_name() {
if let Some(device) = test_get_default_device(Scope::Input) {
match get_device_source_name(device, DeviceType::INPUT) {
match run_serially(|| get_device_source_name(device, DeviceType::INPUT)) {
Ok(name) => println!("input: {}", name.into_string()),
Err(e) => println!("No input data source name. Error: {}", e),
}
@ -146,7 +161,7 @@ fn test_get_device_source_name() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
match get_device_source_name(device, DeviceType::OUTPUT) {
match run_serially(|| get_device_source_name(device, DeviceType::OUTPUT)) {
Ok(name) => println!("output: {}", name.into_string()),
Err(e) => println!("No output data source name. Error: {}", e),
}
@ -158,7 +173,11 @@ fn test_get_device_source_name() {
#[test]
#[should_panic]
fn test_get_device_source_name_by_unknown_device() {
assert!(get_device_source_name(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_source_name(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_name
@ -166,14 +185,14 @@ fn test_get_device_source_name_by_unknown_device() {
#[test]
fn test_get_device_name() {
if let Some(device) = test_get_default_device(Scope::Input) {
let name = get_device_name(device, DeviceType::INPUT).unwrap();
let name = run_serially(|| get_device_name(device, DeviceType::INPUT)).unwrap();
println!("input device name: {}", name.into_string());
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
let name = get_device_name(device, DeviceType::OUTPUT).unwrap();
let name = run_serially(|| get_device_name(device, DeviceType::OUTPUT).unwrap());
println!("output device name: {}", name.into_string());
} else {
println!("No output device.");
@ -183,7 +202,11 @@ fn test_get_device_name() {
#[test]
#[should_panic]
fn test_get_device_name_by_unknown_device() {
assert!(get_device_name(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_name(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_manufacturer
@ -193,7 +216,7 @@ fn test_get_device_manufacturer() {
if let Some(device) = test_get_default_device(Scope::Input) {
// Some devices like AirPods cannot get the vendor info so we print the error directly.
// TODO: Replace `map` and `unwrap_or_else` by `map_or_else`
let name = get_device_manufacturer(device, DeviceType::INPUT)
let name = run_serially(|| get_device_manufacturer(device, DeviceType::INPUT))
.map(|name| name.into_string())
.unwrap_or_else(|e| format!("Error: {}", e));
println!("input device vendor: {}", name);
@ -204,9 +227,10 @@ fn test_get_device_manufacturer() {
if let Some(device) = test_get_default_device(Scope::Output) {
// Some devices like AirPods cannot get the vendor info so we print the error directly.
// TODO: Replace `map` and `unwrap_or_else` by `map_or_else`
let name = get_device_manufacturer(device, DeviceType::OUTPUT)
.map(|name| name.into_string())
.unwrap_or_else(|e| format!("Error: {}", e));
let name =
run_serially_forward_panics(|| get_device_manufacturer(device, DeviceType::OUTPUT))
.map(|name| name.into_string())
.unwrap_or_else(|e| format!("Error: {}", e));
println!("output device vendor: {}", name);
} else {
println!("No output device.");
@ -216,7 +240,11 @@ fn test_get_device_manufacturer() {
#[test]
#[should_panic]
fn test_get_device_manufacturer_by_unknown_device() {
assert!(get_device_manufacturer(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_manufacturer(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_buffer_frame_size_range
@ -224,7 +252,8 @@ fn test_get_device_manufacturer_by_unknown_device() {
#[test]
fn test_get_device_buffer_frame_size_range() {
if let Some(device) = test_get_default_device(Scope::Input) {
let range = get_device_buffer_frame_size_range(device, DeviceType::INPUT).unwrap();
let range =
run_serially(|| get_device_buffer_frame_size_range(device, DeviceType::INPUT)).unwrap();
println!(
"range of input buffer frame size: {}-{}",
range.mMinimum, range.mMaximum
@ -234,7 +263,8 @@ fn test_get_device_buffer_frame_size_range() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
let range = get_device_buffer_frame_size_range(device, DeviceType::OUTPUT).unwrap();
let range = run_serially(|| get_device_buffer_frame_size_range(device, DeviceType::OUTPUT))
.unwrap();
println!(
"range of output buffer frame size: {}-{}",
range.mMinimum, range.mMaximum
@ -247,7 +277,13 @@ fn test_get_device_buffer_frame_size_range() {
#[test]
#[should_panic]
fn test_get_device_buffer_frame_size_range_by_unknown_device() {
assert!(get_device_buffer_frame_size_range(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(
run_serially_forward_panics(|| get_device_buffer_frame_size_range(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err()
);
}
// get_device_latency
@ -255,14 +291,14 @@ fn test_get_device_buffer_frame_size_range_by_unknown_device() {
#[test]
fn test_get_device_latency() {
if let Some(device) = test_get_default_device(Scope::Input) {
let latency = get_device_latency(device, DeviceType::INPUT).unwrap();
let latency = run_serially(|| get_device_latency(device, DeviceType::INPUT)).unwrap();
println!("latency of input device: {}", latency);
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
let latency = get_device_latency(device, DeviceType::OUTPUT).unwrap();
let latency = run_serially(|| get_device_latency(device, DeviceType::OUTPUT)).unwrap();
println!("latency of output device: {}", latency);
} else {
println!("No output device.");
@ -272,7 +308,11 @@ fn test_get_device_latency() {
#[test]
#[should_panic]
fn test_get_device_latency_by_unknown_device() {
assert!(get_device_latency(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_latency(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_streams
@ -280,7 +320,7 @@ fn test_get_device_latency_by_unknown_device() {
#[test]
fn test_get_device_streams() {
if let Some(device) = test_get_default_device(Scope::Input) {
let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap();
println!("streams on the input device: {:?}", streams);
assert!(!streams.is_empty());
} else {
@ -288,7 +328,7 @@ fn test_get_device_streams() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap();
println!("streams on the output device: {:?}", streams);
assert!(!streams.is_empty());
} else {
@ -299,7 +339,11 @@ fn test_get_device_streams() {
#[test]
#[should_panic]
fn test_get_device_streams_by_unknown_device() {
assert!(get_device_streams(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_streams(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_device_sample_rate
@ -307,14 +351,14 @@ fn test_get_device_streams_by_unknown_device() {
#[test]
fn test_get_device_sample_rate() {
if let Some(device) = test_get_default_device(Scope::Input) {
let rate = get_device_sample_rate(device, DeviceType::INPUT).unwrap();
let rate = run_serially(|| get_device_sample_rate(device, DeviceType::INPUT)).unwrap();
println!("input sample rate: {}", rate);
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
let rate = get_device_sample_rate(device, DeviceType::OUTPUT).unwrap();
let rate = run_serially(|| get_device_sample_rate(device, DeviceType::OUTPUT).unwrap());
println!("output sample rate: {}", rate);
} else {
println!("No output device.");
@ -324,7 +368,11 @@ fn test_get_device_sample_rate() {
#[test]
#[should_panic]
fn test_get_device_sample_rate_by_unknown_device() {
assert!(get_device_sample_rate(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(run_serially_forward_panics(|| get_device_sample_rate(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err());
}
// get_ranges_of_device_sample_rate
@ -332,14 +380,16 @@ fn test_get_device_sample_rate_by_unknown_device() {
#[test]
fn test_get_ranges_of_device_sample_rate() {
if let Some(device) = test_get_default_device(Scope::Input) {
let ranges = get_ranges_of_device_sample_rate(device, DeviceType::INPUT).unwrap();
let ranges =
run_serially(|| get_ranges_of_device_sample_rate(device, DeviceType::INPUT)).unwrap();
println!("ranges of input sample rate: {:?}", ranges);
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
let ranges = get_ranges_of_device_sample_rate(device, DeviceType::OUTPUT).unwrap();
let ranges =
run_serially(|| get_ranges_of_device_sample_rate(device, DeviceType::OUTPUT)).unwrap();
println!("ranges of output sample rate: {:?}", ranges);
} else {
println!("No output device.");
@ -349,7 +399,13 @@ fn test_get_ranges_of_device_sample_rate() {
#[test]
#[should_panic]
fn test_get_ranges_of_device_sample_rate_by_unknown_device() {
assert!(get_ranges_of_device_sample_rate(kAudioObjectUnknown, DeviceType::INPUT).is_err());
assert!(
run_serially_forward_panics(|| get_ranges_of_device_sample_rate(
kAudioObjectUnknown,
DeviceType::INPUT
))
.is_err()
);
}
// get_stream_latency
@ -357,9 +413,9 @@ fn test_get_ranges_of_device_sample_rate_by_unknown_device() {
#[test]
fn test_get_stream_latency() {
if let Some(device) = test_get_default_device(Scope::Input) {
let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap();
for stream in streams {
let latency = get_stream_latency(stream).unwrap();
let latency = run_serially(|| get_stream_latency(stream)).unwrap();
println!("latency of the input stream {} is {}", stream, latency);
}
} else {
@ -367,9 +423,9 @@ fn test_get_stream_latency() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap();
for stream in streams {
let latency = get_stream_latency(stream).unwrap();
let latency = run_serially(|| get_stream_latency(stream)).unwrap();
println!("latency of the output stream {} is {}", stream, latency);
}
} else {
@ -388,10 +444,10 @@ fn test_get_stream_latency_by_unknown_device() {
#[test]
fn test_get_stream_virtual_format() {
if let Some(device) = test_get_default_device(Scope::Input) {
let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap();
let formats = streams
.iter()
.map(|s| get_stream_virtual_format(*s))
.map(|s| run_serially(|| get_stream_virtual_format(*s)))
.collect::<Vec<std::result::Result<AudioStreamBasicDescription, OSStatus>>>();
println!("input stream formats: {:?}", formats);
assert!(!formats.is_empty());
@ -400,10 +456,10 @@ fn test_get_stream_virtual_format() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap();
let formats = streams
.iter()
.map(|s| get_stream_virtual_format(*s))
.map(|s| run_serially(|| get_stream_virtual_format(*s)))
.collect::<Vec<std::result::Result<AudioStreamBasicDescription, OSStatus>>>();
println!("output stream formats: {:?}", formats);
assert!(!formats.is_empty());
@ -415,7 +471,9 @@ fn test_get_stream_virtual_format() {
#[test]
#[should_panic]
fn test_get_stream_virtual_format_by_unknown_stream() {
assert!(get_stream_virtual_format(kAudioObjectUnknown).is_err());
assert!(
run_serially_forward_panics(|| get_stream_virtual_format(kAudioObjectUnknown)).is_err()
);
}
// get_stream_terminal_type
@ -442,25 +500,21 @@ fn test_get_stream_terminal_type() {
}
}
if let Some(device) = test_get_default_device(Scope::Input) {
let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
for stream in streams {
assert_eq!(
terminal_type_to_device_type(get_stream_terminal_type(stream).unwrap()),
Some(DeviceType::INPUT)
);
}
let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap();
assert!(streams.iter().any(|&s| {
terminal_type_to_device_type(run_serially(|| get_stream_terminal_type(s)).unwrap())
== Some(DeviceType::INPUT)
}));
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
for stream in streams {
assert_eq!(
terminal_type_to_device_type(get_stream_terminal_type(stream).unwrap()),
Some(DeviceType::OUTPUT)
);
}
let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap();
assert!(streams.iter().any(|&s| {
terminal_type_to_device_type(run_serially(|| get_stream_terminal_type(s)).unwrap())
== Some(DeviceType::OUTPUT)
}));
} else {
println!("No output device.");
}

Просмотреть файл

@ -2,10 +2,12 @@ extern crate itertools;
use self::itertools::iproduct;
use super::utils::{
get_devices_info_in_scope, noop_data_callback, test_device_channels_in_scope,
test_get_default_device, test_ops_context_operation, test_ops_stream_operation, Scope,
draining_data_callback, get_devices_info_in_scope, noop_data_callback,
test_device_channels_in_scope, test_get_default_device, test_ops_context_operation,
test_ops_stream_operation, test_ops_stream_operation_on_context, Scope,
};
use super::*;
use std::thread;
// Context Operations
// ------------------------------------------------------------------------------------------------
@ -368,9 +370,6 @@ fn test_ops_context_register_device_collection_changed() {
#[test]
fn test_ops_context_register_device_collection_changed_with_a_duplex_stream() {
use std::thread;
use std::time::Duration;
extern "C" fn callback(_: *mut ffi::cubeb, got_called_ptr: *mut c_void) {
let got_called = unsafe { &mut *(got_called_ptr as *mut bool) };
*got_called = true;
@ -667,14 +666,20 @@ fn test_ops_context_stream_init_channel_rate_combinations() {
ffi::CUBEB_OK
);
assert!(!stream.is_null());
unsafe { OPS.stream_destroy.unwrap()(stream) };
}
});
}
// Stream Operations
// ------------------------------------------------------------------------------------------------
fn test_default_output_stream_operation<F>(name: &'static str, operation: F)
where
fn test_default_output_stream_operation_on_context_with_callback<F>(
name: &'static str,
context_ptr: *mut ffi::cubeb,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
@ -686,23 +691,52 @@ where
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
test_ops_stream_operation(
test_ops_stream_operation_on_context(
name,
context_ptr,
ptr::null_mut(), // Use default input device.
ptr::null_mut(), // No input parameters.
ptr::null_mut(), // Use default output device.
&mut output_params,
4096, // TODO: Get latency by get_min_latency instead ?
Some(noop_data_callback),
data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
fn test_default_duplex_stream_operation<F>(name: &'static str, operation: F)
fn test_default_output_stream_operation_with_callback<F>(
name: &'static str,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_ops_context_operation("context: default output stream operation", |context_ptr| {
test_default_output_stream_operation_on_context_with_callback(
name,
context_ptr,
data_callback,
operation,
);
});
}
fn test_default_output_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_default_output_stream_operation_with_callback(name, Some(noop_data_callback), operation);
}
fn test_default_duplex_stream_operation_on_context_with_callback<F>(
name: &'static str,
context_ptr: *mut ffi::cubeb,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
// (in the comments).
@ -720,23 +754,52 @@ where
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
test_ops_stream_operation(
test_ops_stream_operation_on_context(
name,
context_ptr,
ptr::null_mut(), // Use default input device.
&mut input_params,
ptr::null_mut(), // Use default output device.
&mut output_params,
4096, // TODO: Get latency by get_min_latency instead ?
Some(noop_data_callback),
data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
fn test_stereo_input_duplex_stream_operation<F>(name: &'static str, operation: F)
fn test_default_duplex_stream_operation_with_callback<F>(
name: &'static str,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_ops_context_operation("context: default duplex stream operation", |context_ptr| {
test_default_duplex_stream_operation_on_context_with_callback(
name,
context_ptr,
data_callback,
operation,
);
});
}
fn test_default_duplex_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_default_duplex_stream_operation_with_callback(name, Some(noop_data_callback), operation);
}
fn test_stereo_input_duplex_stream_operation_on_context_with_callback<F>(
name: &'static str,
context_ptr: *mut ffi::cubeb,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
let mut input_devices = get_devices_info_in_scope(Scope::Input);
input_devices.retain(|d| test_device_channels_in_scope(d.id, Scope::Input).unwrap_or(0) >= 2);
@ -759,23 +822,137 @@ where
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
test_ops_stream_operation(
test_ops_stream_operation_on_context(
name,
context_ptr,
input_devices[0].id as ffi::cubeb_devid,
&mut input_params,
ptr::null_mut(), // Use default output device.
&mut output_params,
4096, // TODO: Get latency by get_min_latency instead ?
Some(noop_data_callback),
data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
fn test_default_duplex_voice_stream_operation<F>(name: &'static str, operation: F)
fn test_stereo_input_duplex_stream_operation_with_callback<F>(
name: &'static str,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_ops_context_operation(
"context: stereo input duplex stream operation",
|context_ptr| {
test_stereo_input_duplex_stream_operation_on_context_with_callback(
name,
context_ptr,
data_callback,
operation,
);
},
);
}
fn test_stereo_input_duplex_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_stereo_input_duplex_stream_operation_with_callback(
name,
Some(noop_data_callback),
operation,
);
}
fn test_default_input_voice_stream_operation_on_context_with_callback<F>(
name: &'static str,
context_ptr: *mut ffi::cubeb,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
// (in the comments).
let mut input_params = ffi::cubeb_stream_params::default();
input_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
input_params.rate = 44100;
input_params.channels = 1;
input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
input_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
test_ops_stream_operation_on_context(
name,
context_ptr,
ptr::null_mut(), // Use default input device.
&mut input_params,
ptr::null_mut(), // Use default output device.
ptr::null_mut(), // No output parameters.
4096, // TODO: Get latency by get_min_latency instead ?
data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
fn test_default_input_voice_stream_operation_on_context<F>(
name: &'static str,
context_ptr: *mut ffi::cubeb,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_default_input_voice_stream_operation_on_context_with_callback(
name,
context_ptr,
Some(noop_data_callback),
operation,
);
}
fn test_default_input_voice_stream_operation_with_callback<F>(
name: &'static str,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_ops_context_operation(
"context: default input voice stream operation",
|context_ptr| {
test_default_input_voice_stream_operation_on_context_with_callback(
name,
context_ptr,
data_callback,
operation,
);
},
);
}
fn test_default_input_voice_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_default_input_voice_stream_operation_with_callback(
name,
Some(noop_data_callback),
operation,
);
}
fn test_default_duplex_voice_stream_operation_on_context_with_callback<F>(
name: &'static str,
context_ptr: *mut ffi::cubeb,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
// (in the comments).
@ -793,20 +970,64 @@ where
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
test_ops_stream_operation(
test_ops_stream_operation_on_context(
name,
context_ptr,
ptr::null_mut(), // Use default input device.
&mut input_params,
ptr::null_mut(), // Use default output device.
&mut output_params,
4096, // TODO: Get latency by get_min_latency instead ?
Some(noop_data_callback),
data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
fn test_default_duplex_voice_stream_operation_on_context<F>(
name: &'static str,
context_ptr: *mut ffi::cubeb,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_default_duplex_voice_stream_operation_on_context_with_callback(
name,
context_ptr,
Some(noop_data_callback),
operation,
);
}
fn test_default_duplex_voice_stream_operation_with_callback<F>(
name: &'static str,
data_callback: ffi::cubeb_data_callback,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_ops_context_operation("context: duplex voice stream operation", |context_ptr| {
test_default_duplex_voice_stream_operation_on_context_with_callback(
name,
context_ptr,
data_callback,
operation,
);
});
}
fn test_default_duplex_voice_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
{
test_default_duplex_voice_stream_operation_with_callback(
name,
Some(noop_data_callback),
operation,
);
}
fn test_stereo_input_duplex_voice_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
@ -865,6 +1086,18 @@ fn test_ops_stream_stop() {
});
}
#[test]
fn test_ops_stream_drain() {
test_default_output_stream_operation_with_callback(
"stream: drain",
Some(draining_data_callback),
|stream| {
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
thread::sleep(Duration::from_millis(10));
},
);
}
#[test]
fn test_ops_stream_position() {
test_default_output_stream_operation("stream: position", |stream| {
@ -987,17 +1220,57 @@ fn test_ops_stereo_input_duplex_stream_stop() {
}
#[test]
fn test_ops_duplex_voice_stream_init_and_destroy() {
test_default_duplex_voice_stream_operation(
"duplex voice stream: init and destroy",
|_stream| {},
fn test_ops_stereo_input_duplex_stream_drain() {
test_stereo_input_duplex_stream_operation_with_callback(
"stereo-input duplex stream: drain",
Some(draining_data_callback),
|stream| {
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
thread::sleep(Duration::from_millis(10));
},
);
}
#[test]
fn test_ops_input_voice_stream_init_and_destroy() {
test_default_input_voice_stream_operation("input voice stream: init and destroy", |stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
#[test]
fn test_ops_input_voice_stream_start() {
test_default_input_voice_stream_operation("input voice stream: start", |stream| {
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
#[test]
fn test_ops_input_voice_stream_stop() {
test_default_input_voice_stream_operation("input voice stream: stop", |stream| {
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
#[test]
fn test_ops_duplex_voice_stream_init_and_destroy() {
test_default_duplex_voice_stream_operation("duplex voice stream: init and destroy", |stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
#[test]
fn test_ops_duplex_voice_stream_start() {
test_default_duplex_voice_stream_operation("duplex voice stream: start", |stream| {
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
@ -1005,6 +1278,291 @@ fn test_ops_duplex_voice_stream_start() {
fn test_ops_duplex_voice_stream_stop() {
test_default_duplex_voice_stream_operation("duplex voice stream: stop", |stream| {
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
#[test]
fn test_ops_duplex_voice_stream_drain() {
test_default_duplex_voice_stream_operation_with_callback(
"duplex voice stream: drain",
Some(draining_data_callback),
|stream| {
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
thread::sleep(Duration::from_millis(10));
},
);
}
#[test]
#[ignore]
fn test_ops_timing_sensitive_multiple_voice_stream_init_and_destroy() {
let start = Instant::now();
let mut t1 = start;
let mut t2 = start;
let mut t3 = start;
let mut t4 = start;
let mut t5 = start;
let mut t6 = start;
let mut t7 = start;
let mut t8 = start;
let mut t9 = start;
let mut t10 = start;
test_ops_context_operation("multiple duplex voice streams", |context_ptr| {
// First stream uses vpio, creates the shared vpio unit.
test_default_duplex_voice_stream_operation_on_context(
"multiple voice streams: stream 1, duplex",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
// Two concurrent vpio streams are supported.
test_default_input_voice_stream_operation_on_context(
"multiple voice streams: stream 2, input-only",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
// Three concurrent vpio streams are supported.
test_default_duplex_voice_stream_operation_on_context(
"multiple voice streams: stream 3, duplex",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
},
);
},
);
},
);
t1 = Instant::now();
// Fourth stream uses vpio, allows reuse of one already created.
test_default_duplex_voice_stream_operation_on_context(
"multiple voice streams: stream 4, duplex",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
t2 = Instant::now();
// Fifth stream uses vpio, allows reuse of one already created.
test_default_duplex_voice_stream_operation_on_context(
"multiple voice streams: stream 5, duplex",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
t3 = Instant::now();
// Sixth stream uses vpio, allows reuse of one already created.
test_default_input_voice_stream_operation_on_context(
"multiple voice streams: stream 6, input-only",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
t4 = Instant::now();
// Seventh stream uses vpio, but is created anew.
test_default_input_voice_stream_operation_on_context(
"multiple voice streams: stream 7, input-only",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
t5 = Instant::now();
},
);
t6 = Instant::now();
},
);
t7 = Instant::now();
},
);
t8 = Instant::now();
},
);
t9 = Instant::now();
});
t10 = Instant::now();
let reuse_vpio_1 = t2 - t1;
let reuse_vpio_2 = t3 - t2;
let reuse_vpio_3 = t4 - t3;
let create_standalone_vpio = t5 - t4;
assert!(
create_standalone_vpio > reuse_vpio_1 * 2,
"Failed create_standalone_vpio={}s > reuse_vpio_1={}s * 2",
create_standalone_vpio.as_secs_f32(),
reuse_vpio_1.as_secs_f32()
);
assert!(
create_standalone_vpio > reuse_vpio_2 * 2,
"Failed create_standalone_vpio={}s > reuse_vpio_2={}s * 2",
create_standalone_vpio.as_secs_f32(),
reuse_vpio_2.as_secs_f32()
);
assert!(
create_standalone_vpio > reuse_vpio_3 * 2,
"Failed create_standalone_vpio={}s > reuse_vpio_3={}s * 2",
create_standalone_vpio.as_secs_f32(),
reuse_vpio_3.as_secs_f32()
);
let recycle_vpio_1 = t6 - t5;
let recycle_vpio_2 = t7 - t6;
let recycle_vpio_3 = t8 - t7;
let recycle_vpio_4 = t9 - t8;
let dispose_vpios = t10 - t9;
assert!(
dispose_vpios > recycle_vpio_1 * 2,
"Failed dispose_vpios={}s > recycle_vpio_1 ={}s * 2",
dispose_vpios.as_secs_f32(),
recycle_vpio_1.as_secs_f32()
);
assert!(
dispose_vpios > recycle_vpio_2 * 2,
"Failed dispose_vpios={}s > recycle_vpio_2 ={}s * 2",
dispose_vpios.as_secs_f32(),
recycle_vpio_2.as_secs_f32()
);
assert!(
dispose_vpios > recycle_vpio_3 * 2,
"Failed dispose_vpios={}s > recycle_vpio_3 ={}s * 2",
dispose_vpios.as_secs_f32(),
recycle_vpio_3.as_secs_f32()
);
assert!(
dispose_vpios > recycle_vpio_4 * 2,
"Failed dispose_vpios={}s > recycle_vpio_4 ={}s * 2",
dispose_vpios.as_secs_f32(),
recycle_vpio_4.as_secs_f32()
);
}
#[test]
#[ignore]
fn test_ops_timing_sensitive_multiple_duplex_voice_stream_start() {
test_ops_context_operation("multiple duplex voice streams", |context_ptr| {
let start = Instant::now();
// First stream uses vpio, creates the shared vpio unit.
test_default_duplex_voice_stream_operation_on_context(
"multiple duplex voice streams: stream 1",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
},
);
let d1 = start.elapsed();
// Second stream uses vpio, allows reuse of the one already created.
test_default_duplex_voice_stream_operation_on_context(
"multiple duplex voice streams: stream 2",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
},
);
let d2 = start.elapsed() - d1;
// d1 being significantly longer than d2 is proof we reuse vpio.
assert!(
d1 > d2 * 2,
"Failed d1={}s > d2={}s * s",
d1.as_secs_f32(),
d2.as_secs_f32()
);
});
}
#[test]
#[ignore]
fn test_ops_timing_sensitive_multiple_duplex_voice_stream_params() {
test_ops_context_operation("multiple duplex voice streams with params", |context_ptr| {
let start = Instant::now();
// First stream uses vpio, creates the shared vpio unit.
test_default_duplex_voice_stream_operation_on_context(
"multiple duplex voice streams: stream 1",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
assert_eq!(
unsafe {
OPS.stream_set_input_processing_params.unwrap()(
stream,
ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
| ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
)
},
ffi::CUBEB_OK
);
assert_eq!(
unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
ffi::CUBEB_OK
);
},
);
let d1 = start.elapsed();
// Second stream uses vpio, allows reuse of the one already created.
test_default_duplex_voice_stream_operation_on_context(
"multiple duplex voice streams: stream 2",
context_ptr,
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
let queue = stm.queue.clone();
// Test that input processing params does not carry over when reusing vpio.
let mut bypass: u32 = 0;
let r = queue
.run_sync(|| {
audio_unit_get_property(
stm.core_stream_data.input_unit,
kAUVoiceIOProperty_BypassVoiceProcessing,
kAudioUnitScope_Global,
AU_IN_BUS,
&mut bypass,
&mut mem::size_of::<u32>(),
)
})
.unwrap();
assert_eq!(r, NO_ERR);
assert_eq!(bypass, 1);
// Test that input mute state does not carry over when reusing vpio.
let mut mute: u32 = 0;
let r = queue
.run_sync(|| {
audio_unit_get_property(
stm.core_stream_data.input_unit,
kAUVoiceIOProperty_MuteOutput,
kAudioUnitScope_Global,
AU_IN_BUS,
&mut mute,
&mut mem::size_of::<u32>(),
)
})
.unwrap();
assert_eq!(r, NO_ERR);
assert_eq!(mute, 0);
},
);
let d2 = start.elapsed() - d1;
// d1 being significantly longer than d2 is proof we reuse vpio.
assert!(
d1 > d2 * 2,
"Failed d1={}s > d2={}s * 2",
d1.as_secs_f32(),
d2.as_secs_f32()
);
});
}
@ -1015,6 +1573,8 @@ fn test_ops_duplex_voice_stream_set_input_mute() {
unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
ffi::CUBEB_OK
);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
@ -1028,6 +1588,8 @@ fn test_ops_duplex_voice_stream_set_input_mute_before_start() {
ffi::CUBEB_OK
);
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
},
);
}
@ -1045,6 +1607,7 @@ fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() {
// Hacky cast, but testing this here was simplest for now.
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
stm.reinit_async();
let queue = stm.queue.clone();
let mut mute_after_reinit = false;
@ -1074,6 +1637,8 @@ fn test_ops_duplex_voice_stream_set_input_mute_after_start() {
unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
ffi::CUBEB_OK
);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
@ -1088,6 +1653,8 @@ fn test_ops_duplex_voice_stream_set_input_processing_params() {
unsafe { OPS.stream_set_input_processing_params.unwrap()(stream, params) },
ffi::CUBEB_OK
);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
@ -1105,6 +1672,8 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_before_start() {
ffi::CUBEB_OK
);
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
},
);
}
@ -1126,6 +1695,7 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_re
// Hacky cast, but testing this here was simplest for now.
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
stm.reinit_async();
let queue = stm.queue.clone();
let mut params_after_reinit: ffi::cubeb_input_processing_params =
@ -1173,6 +1743,8 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_after_start() {
test_default_duplex_voice_stream_operation(
"duplex voice stream: processing after start",
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
let params: ffi::cubeb_input_processing_params =
ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
@ -1190,7 +1762,10 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_after_start() {
fn test_ops_stereo_input_duplex_voice_stream_init_and_destroy() {
test_stereo_input_duplex_voice_stream_operation(
"stereo-input duplex voice stream: init and destroy",
|_stream| {},
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
},
);
}
@ -1199,6 +1774,8 @@ fn test_ops_stereo_input_duplex_voice_stream_start() {
test_stereo_input_duplex_voice_stream_operation(
"stereo-input duplex voice stream: start",
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
},
);
@ -1209,6 +1786,8 @@ fn test_ops_stereo_input_duplex_voice_stream_stop() {
test_stereo_input_duplex_voice_stream_operation(
"stereo-input duplex voice stream: stop",
|stream| {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
assert!(stm.core_stream_data.using_voice_processing_unit());
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
},
);

Просмотреть файл

@ -4,7 +4,6 @@ use super::utils::{
};
use super::*;
use std::io;
use std::sync::atomic::AtomicBool;
#[ignore]
#[test]
@ -153,25 +152,84 @@ fn test_device_collection_change() {
let _ = std::io::stdin().read_line(&mut input);
}
struct StreamData {
stream_ptr: *mut ffi::cubeb_stream,
enable_loopback: AtomicBool,
}
impl StreamData {
fn new() -> Self {
Self {
stream_ptr: ptr::null_mut(),
enable_loopback: AtomicBool::new(false),
}
}
}
struct StreamsData {
streams: Vec<StreamData>,
current_idx: Option<usize>,
}
impl StreamsData {
fn new() -> Self {
Self {
streams: Vec::new(),
current_idx: None,
}
}
fn len(&self) -> usize {
self.streams.len()
}
fn current_mut(&mut self) -> &mut StreamData {
&mut self.streams[self.current_idx.unwrap()]
}
fn current(&self) -> &StreamData {
&self.streams[self.current_idx.unwrap()]
}
fn select(&mut self, idx: usize) {
assert!(idx < self.len());
self.current_idx = Some(idx);
}
fn push(&mut self, stream: StreamData) {
self.streams.push(stream)
}
}
#[ignore]
#[test]
fn test_stream_tester() {
test_ops_context_operation("context: stream tester", |context_ptr| {
let mut stream_ptr: *mut ffi::cubeb_stream = ptr::null_mut();
let enable_loopback = AtomicBool::new(false);
let mut input_prefs = StreamPrefs::NONE;
let mut output_prefs = StreamPrefs::NONE;
let mut streams = StreamsData::new();
loop {
println!(
"commands:\n\
"Current stream: {} (of {}). Commands:\n\
\t'q': quit\n\
\t'b': change current stream\n\
\t'i': set input stream prefs to be used for creating streams\n\
\t'o': set output stream prefs to be used for creating streams\n\
\t'c': create a stream\n\
\t'd': destroy a stream\n\
\t's': start the created stream\n\
\t't': stop the created stream\n\
Commands on the current stream:\n\
\t'd': destroy\n\
\t's': start\n\
\t't': stop\n\
\t'r': register a device changed callback\n\
\t'l': set loopback (DUPLEX-only)\n\
\t'v': set volume\n\
\t'm': set input mute\n\
\t'p': set input processing"
\t'p': set input processing",
streams
.current_idx
.map(|i| format!("{}", i + 1 as usize))
.unwrap_or(String::from("N/A")),
streams.len(),
);
let mut command = String::new();
@ -181,66 +239,130 @@ fn test_stream_tester() {
match command.as_str() {
"q" => {
println!("Quit.");
destroy_stream(&mut stream_ptr);
for mut stream in streams.streams {
if !stream.stream_ptr.is_null() {
destroy_stream(&mut stream);
}
}
break;
}
"c" => create_stream(&mut stream_ptr, context_ptr, &enable_loopback),
"d" => destroy_stream(&mut stream_ptr),
"s" => start_stream(stream_ptr),
"t" => stop_stream(stream_ptr),
"r" => register_device_change_callback(stream_ptr),
"l" => set_loopback(stream_ptr, &enable_loopback),
"v" => set_volume(stream_ptr),
"m" => set_input_mute(stream_ptr),
"p" => set_input_processing(stream_ptr),
x => println!("Unknown command: {}", x),
"i" => set_prefs(&mut input_prefs),
"o" => set_prefs(&mut output_prefs),
"c" => create_stream(context_ptr, &mut streams, input_prefs, output_prefs),
_ if streams.current_idx.is_none() => {
println!("There are no streams! Create a stream first.")
}
cmd => match cmd {
"b" => select_stream(&mut streams),
"d" => destroy_stream(streams.current_mut()),
"s" => start_stream(streams.current()),
"t" => stop_stream(streams.current()),
"r" => register_device_change_callback(streams.current()),
"l" => set_loopback(streams.current()),
"v" => set_volume(streams.current()),
"m" => set_input_mute(streams.current()),
"p" => set_input_processing(streams.current()),
x => println!("Unknown command: {}", x),
},
}
}
});
fn start_stream(stream_ptr: *mut ffi::cubeb_stream) {
if stream_ptr.is_null() {
fn set_prefs(prefs: &mut StreamPrefs) {
let mut done = false;
while !done {
println!(
"Current prefs: {:?}\nSelect action:\n\
\t1) Set None\n\
\t2) Toggle Loopback\n\
\t3) Toggle Disable Device Switching\n\
\t4) Toggle Voice\n\
\t5) Set All\n\
\t0) Done",
prefs
);
let mut input = String::new();
let _ = io::stdin().read_line(&mut input);
assert_eq!(input.pop().unwrap(), '\n');
match input.as_str() {
"1" => *prefs = StreamPrefs::NONE,
"2" => prefs.toggle(StreamPrefs::LOOPBACK),
"3" => prefs.toggle(StreamPrefs::DISABLE_DEVICE_SWITCHING),
"4" => prefs.toggle(StreamPrefs::VOICE),
"5" => *prefs = StreamPrefs::all(),
"0" => done = true,
_ => println!("Invalid action. Select again.\n"),
}
}
}
fn select_stream(streams: &mut StreamsData) {
let num_streams = streams.len();
let current_idx = streams.current_idx.unwrap();
println!(
"Current stream is {}. Select stream 1 to {} on which to apply commands:",
current_idx + 1 as usize,
num_streams
);
let mut selection: Option<usize> = None;
while selection.is_none() {
let mut input = String::new();
let _ = io::stdin().read_line(&mut input);
assert_eq!(input.pop().unwrap(), '\n');
selection = match input.parse::<usize>() {
Ok(i) if (1..=num_streams).contains((&i).into()) => Some(i),
_ => {
println!("Invalid stream. Select again.\n");
None
}
}
}
streams.select(selection.unwrap() - 1)
}
fn start_stream(stream: &StreamData) {
if stream.stream_ptr.is_null() {
println!("No stream can start.");
return;
}
assert_eq!(
unsafe { OPS.stream_start.unwrap()(stream_ptr) },
unsafe { OPS.stream_start.unwrap()(stream.stream_ptr) },
ffi::CUBEB_OK
);
println!("Stream {:p} started.", stream_ptr);
println!("Stream {:p} started.", stream.stream_ptr);
}
fn stop_stream(stream_ptr: *mut ffi::cubeb_stream) {
if stream_ptr.is_null() {
fn stop_stream(stream: &StreamData) {
if stream.stream_ptr.is_null() {
println!("No stream can stop.");
return;
}
assert_eq!(
unsafe { OPS.stream_stop.unwrap()(stream_ptr) },
unsafe { OPS.stream_stop.unwrap()(stream.stream_ptr) },
ffi::CUBEB_OK
);
println!("Stream {:p} stopped.", stream_ptr);
println!("Stream {:p} stopped.", stream.stream_ptr);
}
fn set_volume(stream_ptr: *mut ffi::cubeb_stream) {
if stream_ptr.is_null() {
fn set_volume(stream: &StreamData) {
if stream.stream_ptr.is_null() {
println!("No stream can set volume.");
return;
}
const VOL: f32 = 0.5;
assert_eq!(
unsafe { OPS.stream_set_volume.unwrap()(stream_ptr, VOL) },
unsafe { OPS.stream_set_volume.unwrap()(stream.stream_ptr, VOL) },
ffi::CUBEB_OK
);
println!("Set stream {:p} volume to {}", stream_ptr, VOL);
println!("Set stream {:p} volume to {}", stream.stream_ptr, VOL);
}
fn set_loopback(stream_ptr: *mut ffi::cubeb_stream, enable_loopback: &AtomicBool) {
if stream_ptr.is_null() {
fn set_loopback(stream: &StreamData) {
if stream.stream_ptr.is_null() {
println!("No stream can set loopback.");
return;
}
let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) };
let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) };
if !stm.core_stream_data.has_input() || !stm.core_stream_data.has_output() {
println!("Duplex stream needed to set loopback");
return;
@ -261,20 +383,20 @@ fn test_stream_tester() {
}
}
let loopback = loopback.unwrap();
enable_loopback.store(loopback, Ordering::SeqCst);
stream.enable_loopback.store(loopback, Ordering::SeqCst);
println!(
"Loopback {} for stream {:p}",
if loopback { "enabled" } else { "disabled" },
stream_ptr
stream.stream_ptr
);
}
fn set_input_mute(stream_ptr: *mut ffi::cubeb_stream) {
if stream_ptr.is_null() {
fn set_input_mute(stream: &StreamData) {
if stream.stream_ptr.is_null() {
println!("No stream can set input mute.");
return;
}
let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) };
let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) };
if !stm.core_stream_data.has_input() {
println!("Input stream needed to set loopback");
return;
@ -295,7 +417,7 @@ fn test_stream_tester() {
}
}
let mute = mute.unwrap();
let res = unsafe { OPS.stream_set_input_mute.unwrap()(stream_ptr, mute.into()) };
let res = unsafe { OPS.stream_set_input_mute.unwrap()(stream.stream_ptr, mute.into()) };
println!(
"{} set stream {:p} input {}",
if res == ffi::CUBEB_OK {
@ -303,17 +425,17 @@ fn test_stream_tester() {
} else {
"Failed to"
},
stream_ptr,
stream.stream_ptr,
if mute { "mute" } else { "unmute" }
);
}
fn set_input_processing(stream_ptr: *mut ffi::cubeb_stream) {
if stream_ptr.is_null() {
fn set_input_processing(stream: &StreamData) {
if stream.stream_ptr.is_null() {
println!("No stream can set input processing.");
return;
}
let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) };
let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) };
if !stm.core_stream_data.using_voice_processing_unit() {
println!("Duplex stream with voice processing needed to set input processing params");
return;
@ -338,6 +460,23 @@ fn test_stream_tester() {
params.set(InputProcessingParams::ECHO_CANCELLATION, true);
params.set(InputProcessingParams::NOISE_SUPPRESSION, true);
}
let mut agc = u32::from(false);
let mut size: usize = mem::size_of::<u32>();
assert_eq!(
audio_unit_get_property(
stm.core_stream_data.input_unit,
kAUVoiceIOProperty_VoiceProcessingEnableAGC,
kAudioUnitScope_Global,
AU_IN_BUS,
&mut agc,
&mut size,
),
NO_ERR
);
assert_eq!(size, mem::size_of::<u32>());
if agc == 1 {
params.set(InputProcessingParams::AUTOMATIC_GAIN_CONTROL, true);
}
}
let mut done = false;
while !done {
@ -367,8 +506,9 @@ fn test_stream_tester() {
_ => println!("Invalid action. Select again.\n"),
}
}
let res =
unsafe { OPS.stream_set_input_processing_params.unwrap()(stream_ptr, params.bits()) };
let res = unsafe {
OPS.stream_set_input_processing_params.unwrap()(stream.stream_ptr, params.bits())
};
println!(
"{} set stream {:p} input processing params to {:?}",
if res == ffi::CUBEB_OK {
@ -376,48 +516,62 @@ fn test_stream_tester() {
} else {
"Failed to"
},
stream_ptr,
stream.stream_ptr,
params,
);
}
fn register_device_change_callback(stream_ptr: *mut ffi::cubeb_stream) {
fn register_device_change_callback(stream: &StreamData) {
extern "C" fn callback(user_ptr: *mut c_void) {
println!("user pointer @ {:p}", user_ptr);
assert!(user_ptr.is_null());
}
if stream_ptr.is_null() {
if stream.stream_ptr.is_null() {
println!("No stream for registering the callback.");
return;
}
assert_eq!(
unsafe {
OPS.stream_register_device_changed_callback.unwrap()(stream_ptr, Some(callback))
OPS.stream_register_device_changed_callback.unwrap()(
stream.stream_ptr,
Some(callback),
)
},
ffi::CUBEB_OK
);
println!("Stream {:p} now has a device change callback.", stream_ptr);
println!(
"Stream {:p} now has a device change callback.",
stream.stream_ptr
);
}
fn destroy_stream(stream_ptr: &mut *mut ffi::cubeb_stream) {
if stream_ptr.is_null() {
fn destroy_stream(stream: &mut StreamData) {
if stream.stream_ptr.is_null() {
println!("No need to destroy stream.");
return;
}
unsafe {
OPS.stream_destroy.unwrap()(*stream_ptr);
OPS.stream_destroy.unwrap()((*stream).stream_ptr);
}
println!("Stream {:p} destroyed.", *stream_ptr);
*stream_ptr = ptr::null_mut();
println!("Stream {:p} destroyed.", stream.stream_ptr);
stream.stream_ptr = ptr::null_mut();
}
fn create_stream(
stream_ptr: &mut *mut ffi::cubeb_stream,
context_ptr: *mut ffi::cubeb,
enable_loopback: &AtomicBool,
streams: &mut StreamsData,
input_prefs: StreamPrefs,
output_prefs: StreamPrefs,
) {
if !stream_ptr.is_null() {
if streams.len() == 0 || !streams.current().stream_ptr.is_null() {
println!("Allocating stream {}.", streams.len() + 1);
streams.push(StreamData::new());
streams.select(streams.len() - 1);
}
let stream = streams.current_mut();
if !stream.stream_ptr.is_null() {
println!("Stream has been created.");
return;
}
@ -486,8 +640,8 @@ fn test_stream_tester() {
}
};
let mut input_params = get_dummy_stream_params(Scope::Input);
let mut output_params = get_dummy_stream_params(Scope::Output);
let mut input_params = get_dummy_stream_params(Scope::Input, input_prefs);
let mut output_params = get_dummy_stream_params(Scope::Output, output_prefs);
let (input_device, input_stream_params) = if stream_type.contains(StreamType::INPUT) {
(
@ -519,7 +673,7 @@ fn test_stream_tester() {
unsafe {
OPS.stream_init.unwrap()(
context_ptr,
stream_ptr,
&mut stream.stream_ptr,
stream_name.as_ptr(),
input_device as ffi::cubeb_devid,
input_stream_params,
@ -528,13 +682,13 @@ fn test_stream_tester() {
4096, // latency
Some(data_callback),
Some(state_callback),
enable_loopback as *const AtomicBool as *mut c_void, // user pointer
&stream.enable_loopback as *const AtomicBool as *mut c_void, // user pointer
)
},
ffi::CUBEB_OK
);
assert!(!stream_ptr.is_null());
println!("Stream {:p} created.", *stream_ptr);
assert!(!stream.stream_ptr.is_null());
println!("Stream {:p} created.", stream.stream_ptr);
extern "C" fn state_callback(
stream: *mut ffi::cubeb_stream,
@ -592,14 +746,14 @@ fn test_stream_tester() {
nframes
}
fn get_dummy_stream_params(scope: Scope) -> ffi::cubeb_stream_params {
fn get_dummy_stream_params(scope: Scope, prefs: StreamPrefs) -> ffi::cubeb_stream_params {
// The stream format for input and output must be same.
const STREAM_FORMAT: u32 = ffi::CUBEB_SAMPLE_FLOAT32NE;
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
// (in the comments).
let mut stream_params = ffi::cubeb_stream_params::default();
stream_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
stream_params.prefs = prefs.bits();
let (format, rate, channels, layout) = match scope {
Scope::Input => (STREAM_FORMAT, 48000, 1, ffi::CUBEB_LAYOUT_MONO),
Scope::Output => (STREAM_FORMAT, 44100, 2, ffi::CUBEB_LAYOUT_STEREO),

Просмотреть файл

@ -537,14 +537,16 @@ fn test_set_buffer_frame_size_in_parallel_in_scope(scope: Scope) {
units.push(test_get_default_audiounit(scope.clone()).unwrap());
let unit_value = units.last().unwrap().get_inner() as usize;
join_handles.push(thread::spawn(move || {
let status = audio_unit_set_property(
unit_value as AudioUnit,
kAudioDevicePropertyBufferFrameSize,
unit_scope,
unit_element,
&latency_frames,
mem::size_of::<u32>(),
);
let status = run_serially(|| {
audio_unit_set_property(
unit_value as AudioUnit,
kAudioDevicePropertyBufferFrameSize,
unit_scope,
unit_element,
&latency_frames,
mem::size_of::<u32>(),
)
});
(latency_frames, status)
}));
}

Просмотреть файл

@ -1,6 +1,6 @@
use super::utils::{test_get_default_device, test_ops_stream_operation, Scope};
use super::*;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::atomic::AtomicI64;
#[test]
fn test_dial_tone() {
@ -202,7 +202,7 @@ fn test_dial_tone() {
for data in buffer.iter_mut() {
let t1 = (2.0 * PI * 350.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin();
let t2 = (2.0 * PI * 440.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin();
*data = f32_to_i16_sample(0.5 * (t1 + t2));
*data = f32_to_i16_sample(0.45 * (t1 + t2));
closure.phase += 1;
}

Просмотреть файл

@ -25,6 +25,29 @@ pub extern "C" fn noop_data_callback(
nframes
}
pub extern "C" fn draining_data_callback(
stream: *mut ffi::cubeb_stream,
_user_ptr: *mut c_void,
_input_buffer: *const c_void,
output_buffer: *mut c_void,
nframes: i64,
) -> i64 {
assert!(!stream.is_null());
// Feed silence data to output buffer
if !output_buffer.is_null() {
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
let channels = stm.core_stream_data.output_stream_params.channels();
let samples = nframes as usize * channels as usize;
let sample_size = cubeb_sample_size(stm.core_stream_data.output_stream_params.format());
unsafe {
ptr::write_bytes(output_buffer, 0, samples * sample_size);
}
}
nframes - 1
}
#[derive(Clone, Debug, PartialEq)]
pub enum Scope {
Input,
@ -47,31 +70,35 @@ pub enum PropertyScope {
}
pub fn test_get_default_device(scope: Scope) -> Option<AudioObjectID> {
let address = AudioObjectPropertyAddress {
mSelector: match scope {
Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
},
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
debug_assert_not_running_serially();
run_serially_forward_panics(|| {
let address = AudioObjectPropertyAddress {
mSelector: match scope {
Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
},
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut devid: AudioObjectID = kAudioObjectUnknown;
let mut size = mem::size_of::<AudioObjectID>();
let status = unsafe {
AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut UInt32,
&mut devid as *mut AudioObjectID as *mut c_void,
)
};
if status != NO_ERR || devid == kAudioObjectUnknown {
return None;
}
Some(devid)
let mut devid: AudioObjectID = kAudioObjectUnknown;
let mut size = mem::size_of::<AudioObjectID>();
let status = unsafe {
AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut UInt32,
&mut devid as *mut AudioObjectID as *mut c_void,
)
};
if status != NO_ERR || devid == kAudioObjectUnknown {
return None;
}
Some(devid)
})
}
// TODO: Create a GetProperty trait and add a default implementation for it, then implement it
@ -99,16 +126,17 @@ impl TestAudioUnit {
impl Drop for TestAudioUnit {
fn drop(&mut self) {
unsafe {
run_serially_forward_panics(|| unsafe {
AudioUnitUninitialize(self.0);
AudioComponentInstanceDispose(self.0);
}
});
}
}
// TODO: 1. Return Result with custom errors.
// 2. Allow to create a in-out unit.
pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> {
debug_assert_not_running_serially();
let device = test_get_default_device(scope.clone());
let unit = test_create_audiounit(ComponentSubType::HALOutput);
if device.is_none() || unit.is_none() {
@ -133,7 +161,7 @@ pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> {
}
}
let status = unsafe {
let status = run_serially(|| unsafe {
AudioUnitSetProperty(
unit.get_inner(),
kAudioOutputUnitProperty_CurrentDevice,
@ -142,7 +170,7 @@ pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> {
&device as *const AudioObjectID as *const c_void,
mem::size_of::<AudioObjectID>() as u32,
)
};
});
if status == NO_ERR {
Some(unit)
} else {
@ -159,6 +187,7 @@ pub enum ComponentSubType {
// Surprisingly the AudioUnit can be created even when there is no any device on the platform,
// no matter its subtype is HALOutput or DefaultOutput.
pub fn test_create_audiounit(unit_type: ComponentSubType) -> Option<TestAudioUnit> {
debug_assert_not_running_serially();
let desc = AudioComponentDescription {
componentType: kAudioUnitType_Output,
componentSubType: match unit_type {
@ -169,12 +198,12 @@ pub fn test_create_audiounit(unit_type: ComponentSubType) -> Option<TestAudioUni
componentFlags: 0,
componentFlagsMask: 0,
};
let comp = unsafe { AudioComponentFindNext(ptr::null_mut(), &desc) };
let comp = run_serially(|| unsafe { AudioComponentFindNext(ptr::null_mut(), &desc) });
if comp.is_null() {
return None;
}
let mut unit: AudioUnit = ptr::null_mut();
let status = unsafe { AudioComponentInstanceNew(comp, &mut unit) };
let status = run_serially(|| unsafe { AudioComponentInstanceNew(comp, &mut unit) });
// TODO: Is unit possible to be null when no error returns ?
if status != NO_ERR || unit.is_null() {
None
@ -188,13 +217,14 @@ fn test_enable_audiounit_in_scope(
scope: Scope,
enable: bool,
) -> std::result::Result<(), OSStatus> {
debug_assert_not_running_serially();
assert!(!unit.is_null());
let (scope, element) = match scope {
Scope::Input => (kAudioUnitScope_Input, AU_IN_BUS),
Scope::Output => (kAudioUnitScope_Output, AU_OUT_BUS),
};
let on_off: u32 = if enable { 1 } else { 0 };
let status = unsafe {
let status = run_serially(|| unsafe {
AudioUnitSetProperty(
unit,
kAudioOutputUnitProperty_EnableIO,
@ -203,7 +233,7 @@ fn test_enable_audiounit_in_scope(
&on_off as *const u32 as *const c_void,
mem::size_of::<u32>() as u32,
)
};
});
if status == NO_ERR {
Ok(())
} else {
@ -216,72 +246,80 @@ pub enum DeviceFilter {
IncludeAll,
}
pub fn test_get_all_devices(filter: DeviceFilter) -> Vec<AudioObjectID> {
let mut devices = Vec::new();
let address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDevices,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
let status = unsafe {
AudioObjectGetPropertyDataSize(
kAudioObjectSystemObject,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut u32,
)
};
// size will be 0 if there is no device at all.
if status != NO_ERR || size == 0 {
return devices;
}
assert_eq!(size % mem::size_of::<AudioObjectID>(), 0);
let elements = size / mem::size_of::<AudioObjectID>();
devices.resize(elements, kAudioObjectUnknown);
let status = unsafe {
AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut u32,
devices.as_mut_ptr() as *mut c_void,
)
};
if status != NO_ERR {
devices.clear();
return devices;
}
for device in devices.iter() {
assert_ne!(*device, kAudioObjectUnknown);
}
match filter {
DeviceFilter::ExcludeCubebAggregateAndVPIO => {
devices.retain(|&device| {
if let Ok(uid) = get_device_global_uid(device) {
let uid = uid.into_string();
!uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME)
&& !uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME)
} else {
true
}
});
debug_assert_not_running_serially();
// To avoid races, the devices getter and the device name filtering have
// to run in the same serial task. If not, a device may exist when the
// getter runs but not when getting its uid.
run_serially_forward_panics(|| {
let mut devices = Vec::new();
let address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDevices,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
let status = unsafe {
AudioObjectGetPropertyDataSize(
kAudioObjectSystemObject,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut u32,
)
};
// size will be 0 if there is no device at all.
if status != NO_ERR || size == 0 {
return devices;
}
assert_eq!(size % mem::size_of::<AudioObjectID>(), 0);
let elements = size / mem::size_of::<AudioObjectID>();
devices.resize(elements, kAudioObjectUnknown);
let status = unsafe {
AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut u32,
devices.as_mut_ptr() as *mut c_void,
)
};
if status != NO_ERR {
devices.clear();
return devices;
}
for device in devices.iter() {
assert_ne!(*device, kAudioObjectUnknown);
}
_ => {}
}
devices
match filter {
DeviceFilter::ExcludeCubebAggregateAndVPIO => {
devices.retain(|&device| {
if let Ok(uid) = get_device_global_uid(device) {
let uid = uid.into_string();
!uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME)
&& !uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME)
} else {
true
}
});
}
_ => {}
}
devices
})
}
pub fn test_get_devices_in_scope(scope: Scope) -> Vec<AudioObjectID> {
debug_assert_not_running_serially();
let mut devices = test_get_all_devices(DeviceFilter::ExcludeCubebAggregateAndVPIO);
devices.retain(|device| test_device_in_scope(*device, scope.clone()));
devices
}
pub fn get_devices_info_in_scope(scope: Scope) -> Vec<TestDeviceInfo> {
debug_assert_not_running_serially();
fn print_info(info: &TestDeviceInfo) {
println!("{:>4}: {}\n\tuid: {}", info.id, info.label, info.uid);
}
@ -337,8 +375,9 @@ pub fn test_device_channels_in_scope(
id: AudioObjectID,
scope: Scope,
) -> std::result::Result<u32, OSStatus> {
debug_assert_not_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyStreamConfiguration,
mSelector: kAudioDevicePropertyStreams,
mScope: match scope {
Scope::Input => kAudioDevicePropertyScopeInput,
Scope::Output => kAudioDevicePropertyScopeOutput,
@ -346,7 +385,7 @@ pub fn test_device_channels_in_scope(
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
let status = unsafe {
let status = run_serially(|| unsafe {
AudioObjectGetPropertyDataSize(
id,
&address,
@ -354,49 +393,90 @@ pub fn test_device_channels_in_scope(
ptr::null(),
&mut size as *mut usize as *mut u32,
)
};
});
if status != NO_ERR {
return Err(status);
}
if size == 0 {
return Ok(0);
}
let byte_len = size / mem::size_of::<u8>();
let mut bytes = vec![0u8; byte_len];
let status = unsafe {
let mut stream_list = vec![0, (size / mem::size_of::<AudioObjectID>()) as u32];
let status = run_serially(|| unsafe {
AudioObjectGetPropertyData(
id,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut u32,
bytes.as_mut_ptr() as *mut c_void,
stream_list.as_mut_ptr() as *mut c_void,
)
};
});
if status != NO_ERR {
return Err(status);
}
let buf_list = unsafe { &*(bytes.as_mut_ptr() as *mut AudioBufferList) };
let buf_len = buf_list.mNumberBuffers as usize;
if buf_len == 0 {
return Ok(0);
}
let buf_ptr = buf_list.mBuffers.as_ptr() as *const AudioBuffer;
let buffers = unsafe { slice::from_raw_parts(buf_ptr, buf_len) };
let mut channels: u32 = 0;
for buffer in buffers {
channels += buffer.mNumberChannels;
}
let channels = stream_list
.iter()
.filter(|s: &&AudioObjectID| {
if scope != Scope::Input {
return true;
}
let address = AudioObjectPropertyAddress {
mSelector: kAudioStreamPropertyTerminalType,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut ttype: u32 = 0;
let status = unsafe {
AudioObjectGetPropertyData(
**s,
&address,
0,
ptr::null(),
&mut mem::size_of::<u32>() as *mut usize as *mut u32,
&mut ttype as *mut u32 as *mut c_void,
)
};
if status != NO_ERR {
return false;
}
ttype == kAudioStreamTerminalTypeMicrophone
|| (INPUT_MICROPHONE..OUTPUT_UNDEFINED).contains(&ttype)
})
.map(|s: &AudioObjectID| {
let address = AudioObjectPropertyAddress {
mSelector: kAudioStreamPropertyVirtualFormat,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut format = AudioStreamBasicDescription::default();
let status = unsafe {
AudioObjectGetPropertyData(
*s,
&address,
0,
ptr::null(),
&mut mem::size_of::<AudioStreamBasicDescription>() as *mut usize as *mut u32,
&mut format as *mut AudioStreamBasicDescription as *mut c_void,
)
};
if status != NO_ERR {
return 0;
}
format.mChannelsPerFrame
})
.sum();
Ok(channels)
}
pub fn test_device_in_scope(id: AudioObjectID, scope: Scope) -> bool {
debug_assert_not_running_serially();
let channels = test_device_channels_in_scope(id, scope);
channels.is_ok() && channels.unwrap() > 0
}
pub fn test_get_all_onwed_devices(id: AudioDeviceID) -> Vec<AudioObjectID> {
assert_ne!(id, kAudioObjectUnknown);
debug_assert_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: kAudioObjectPropertyOwnedObjects,
@ -409,45 +489,46 @@ pub fn test_get_all_onwed_devices(id: AudioDeviceID) -> Vec<AudioObjectID> {
let qualifier_data = &class_id;
let mut size: usize = 0;
unsafe {
assert_eq!(
assert_eq!(
unsafe {
AudioObjectGetPropertyDataSize(
id,
&address,
qualifier_data_size as u32,
qualifier_data as *const u32 as *const c_void,
&mut size as *mut usize as *mut u32
),
NO_ERR
);
}
&mut size as *mut usize as *mut u32,
)
},
NO_ERR
);
assert_ne!(size, 0);
let elements = size / mem::size_of::<AudioObjectID>();
let mut devices: Vec<AudioObjectID> = allocate_array(elements);
unsafe {
assert_eq!(
assert_eq!(
unsafe {
AudioObjectGetPropertyData(
id,
&address,
qualifier_data_size as u32,
qualifier_data as *const u32 as *const c_void,
&mut size as *mut usize as *mut u32,
devices.as_mut_ptr() as *mut c_void
),
NO_ERR
);
}
devices.as_mut_ptr() as *mut c_void,
)
},
NO_ERR
);
devices
}
pub fn test_get_master_device(id: AudioObjectID) -> String {
assert_ne!(id, kAudioObjectUnknown);
debug_assert_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: kAudioAggregateDevicePropertyMasterSubDevice,
mSelector: kAudioAggregateDevicePropertyMainSubDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
@ -465,6 +546,7 @@ pub fn test_get_master_device(id: AudioObjectID) -> String {
}
pub fn test_get_drift_compensations(id: AudioObjectID) -> std::result::Result<u32, OSStatus> {
debug_assert_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: kAudioSubDevicePropertyDriftCompensation,
mScope: kAudioObjectPropertyScopeGlobal,
@ -491,6 +573,7 @@ pub fn test_get_drift_compensations(id: AudioObjectID) -> std::result::Result<u3
pub fn test_audiounit_scope_is_enabled(unit: AudioUnit, scope: Scope) -> bool {
assert!(!unit.is_null());
debug_assert_not_running_serially();
let mut has_io: UInt32 = 0;
let (scope, element) = match scope {
Scope::Input => (kAudioUnitScope_Input, AU_IN_BUS),
@ -498,14 +581,14 @@ pub fn test_audiounit_scope_is_enabled(unit: AudioUnit, scope: Scope) -> bool {
};
let mut size = mem::size_of::<UInt32>();
assert_eq!(
audio_unit_get_property(
run_serially(|| audio_unit_get_property(
unit,
kAudioOutputUnitProperty_HasIO,
scope,
element,
&mut has_io,
&mut size
),
)),
NO_ERR
);
has_io != 0
@ -516,6 +599,7 @@ pub fn test_audiounit_get_buffer_frame_size(
scope: Scope,
prop_scope: PropertyScope,
) -> std::result::Result<u32, OSStatus> {
debug_assert_not_running_serially();
let element = match scope {
Scope::Input => AU_IN_BUS,
Scope::Output => AU_OUT_BUS,
@ -526,7 +610,7 @@ pub fn test_audiounit_get_buffer_frame_size(
};
let mut buffer_frames: u32 = 0;
let mut size = mem::size_of::<u32>();
let status = unsafe {
let status = run_serially(|| unsafe {
AudioUnitGetProperty(
unit,
kAudioDevicePropertyBufferFrameSize,
@ -535,7 +619,7 @@ pub fn test_audiounit_get_buffer_frame_size(
&mut buffer_frames as *mut u32 as *mut c_void,
&mut size as *mut usize as *mut u32,
)
};
});
if status == NO_ERR {
Ok(buffer_frames)
} else {
@ -554,6 +638,7 @@ pub fn test_set_default_device(
device: AudioObjectID,
scope: Scope,
) -> std::result::Result<AudioObjectID, OSStatus> {
debug_assert_not_running_serially();
assert!(test_device_in_scope(device, scope.clone()));
let default = test_get_default_device(scope.clone()).unwrap();
if default == device {
@ -570,7 +655,7 @@ pub fn test_set_default_device(
mElement: kAudioObjectPropertyElementMaster,
};
let size = mem::size_of::<AudioObjectID>();
let status = unsafe {
let status = run_serially(|| unsafe {
AudioObjectSetPropertyData(
kAudioObjectSystemObject,
&address,
@ -579,7 +664,7 @@ pub fn test_set_default_device(
size as u32,
&device as *const AudioObjectID as *const c_void,
)
};
});
let new_default = test_get_default_device(scope.clone()).unwrap();
if new_default == default {
Err(-1)
@ -644,6 +729,7 @@ pub fn test_create_device_change_listener<F>(scope: Scope, listener: F) -> TestP
where
F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
{
debug_assert_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: match scope {
Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
@ -652,6 +738,7 @@ where
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
TestPropertyListener::new(kAudioObjectSystemObject, address, listener)
}
@ -677,6 +764,7 @@ where
}
pub fn start(&self) -> std::result::Result<(), OSStatus> {
debug_assert_running_serially();
let status = unsafe {
AudioObjectAddPropertyListener(
self.device,
@ -693,6 +781,7 @@ where
}
pub fn stop(&self) -> std::result::Result<(), OSStatus> {
debug_assert_running_serially();
let status = unsafe {
AudioObjectRemovePropertyListener(
self.device,
@ -726,7 +815,7 @@ where
F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
{
fn drop(&mut self) {
self.stop();
run_serially(|| self.stop());
}
}
@ -767,6 +856,7 @@ impl TestDevicePlugger {
}
fn destroy_aggregate_device(&mut self) -> std::result::Result<(), OSStatus> {
debug_assert_not_running_serially();
assert_ne!(self.plugin_id, kAudioObjectUnknown);
assert_ne!(self.device_id, kAudioObjectUnknown);
@ -777,7 +867,7 @@ impl TestDevicePlugger {
};
let mut size: usize = 0;
let status = unsafe {
let status = run_serially(|| unsafe {
AudioObjectGetPropertyDataSize(
self.plugin_id,
&address,
@ -785,13 +875,13 @@ impl TestDevicePlugger {
ptr::null(),
&mut size as *mut usize as *mut u32,
)
};
});
if status != NO_ERR {
return Err(status);
}
assert_ne!(size, 0);
let status = unsafe {
let status = run_serially(|| unsafe {
// This call can simulate removing a device.
AudioObjectGetPropertyData(
self.plugin_id,
@ -801,7 +891,7 @@ impl TestDevicePlugger {
&mut size as *mut usize as *mut u32,
&mut self.device_id as *mut AudioDeviceID as *mut c_void,
)
};
});
if status == NO_ERR {
self.device_id = kAudioObjectUnknown;
Ok(())
@ -811,6 +901,7 @@ impl TestDevicePlugger {
}
fn create_aggregate_device(&self) -> std::result::Result<AudioObjectID, OSStatus> {
debug_assert_not_running_serially();
use std::time::{SystemTime, UNIX_EPOCH};
const TEST_AGGREGATE_DEVICE_NAME: &str = "TestAggregateDevice";
@ -830,7 +921,7 @@ impl TestDevicePlugger {
};
let mut size: usize = 0;
let status = unsafe {
let status = run_serially(|| unsafe {
AudioObjectGetPropertyDataSize(
self.plugin_id,
&address,
@ -838,7 +929,7 @@ impl TestDevicePlugger {
ptr::null(),
&mut size as *mut usize as *mut u32,
)
};
});
if status != NO_ERR {
return Err(status);
}
@ -915,14 +1006,18 @@ impl TestDevicePlugger {
CFRelease(sub_devices as *const c_void);
// This call can simulate adding a device.
let status = AudioObjectGetPropertyData(
self.plugin_id,
&address,
mem::size_of_val(&device_dict) as u32,
&device_dict as *const CFMutableDictionaryRef as *const c_void,
&mut size as *mut usize as *mut u32,
&mut device_id as *mut AudioDeviceID as *mut c_void,
);
let status = {
run_serially(|| {
AudioObjectGetPropertyData(
self.plugin_id,
&address,
mem::size_of_val(&device_dict) as u32,
&device_dict as *const CFMutableDictionaryRef as *const c_void,
&mut size as *mut usize as *mut u32,
&mut device_id as *mut AudioDeviceID as *mut c_void,
)
})
};
CFRelease(device_dict as *const c_void);
status
};
@ -935,6 +1030,7 @@ impl TestDevicePlugger {
}
fn get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus> {
debug_assert_not_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyPlugInForBundleID,
mScope: kAudioObjectPropertyScopeGlobal,
@ -942,7 +1038,7 @@ impl TestDevicePlugger {
};
let mut size: usize = 0;
let status = unsafe {
let status = run_serially(|| unsafe {
AudioObjectGetPropertyDataSize(
kAudioObjectSystemObject,
&address,
@ -950,7 +1046,7 @@ impl TestDevicePlugger {
ptr::null(),
&mut size as *mut usize as *mut u32,
)
};
});
if status != NO_ERR {
return Err(status);
}
@ -967,14 +1063,16 @@ impl TestDevicePlugger {
assert_eq!(size, mem::size_of_val(&translation_value));
let status = unsafe {
let status = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut u32,
&mut translation_value as *mut AudioValueTranslation as *mut c_void,
);
let status = run_serially(|| {
AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut u32,
&mut translation_value as *mut AudioValueTranslation as *mut c_void,
)
});
CFRelease(in_bundle_ref as *const c_void);
status
};
@ -991,10 +1089,11 @@ impl TestDevicePlugger {
// them into the array, if the device is an aggregate device. See the code in
// AggregateDevice::get_sub_devices and audiounit_set_aggregate_sub_device_list.
fn get_sub_devices(scope: Scope) -> Option<CFArrayRef> {
debug_assert_not_running_serially();
let device = test_get_default_device(scope);
device?;
let device = device.unwrap();
let uid = get_device_global_uid(device);
let uid = run_serially(|| get_device_global_uid(device));
if uid.is_err() {
return None;
}
@ -1033,6 +1132,7 @@ pub fn test_ops_context_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb),
{
debug_assert_not_running_serially();
let name_c_string = CString::new(name).expect("Failed to create context name");
let mut context = ptr::null_mut::<ffi::cubeb>();
assert_eq!(
@ -1047,6 +1147,59 @@ where
// The in-out stream initializeed with different device will create an aggregate_device and
// result in firing device-collection-changed callbacks. Run in-out streams with tests
// capturing device-collection-changed callbacks may cause troubles.
pub fn test_ops_stream_operation_on_context<F>(
name: &'static str,
context_ptr: *mut ffi::cubeb,
input_device: ffi::cubeb_devid,
input_stream_params: *mut ffi::cubeb_stream_params,
output_device: ffi::cubeb_devid,
output_stream_params: *mut ffi::cubeb_stream_params,
latency_frames: u32,
data_callback: ffi::cubeb_data_callback,
state_callback: ffi::cubeb_state_callback,
user_ptr: *mut c_void,
operation: F,
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
// Do nothing if there is no input/output device to perform input/output tests.
if !input_stream_params.is_null() && test_get_default_device(Scope::Input).is_none() {
println!("No input device to perform input tests for \"{}\".", name);
return;
}
if !output_stream_params.is_null() && test_get_default_device(Scope::Output).is_none() {
println!("No output device to perform output tests for \"{}\".", name);
return;
}
let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
let stream_name = CString::new(name).expect("Failed to create stream name");
assert_eq!(
unsafe {
OPS.stream_init.unwrap()(
context_ptr,
&mut stream,
stream_name.as_ptr(),
input_device,
input_stream_params,
output_device,
output_stream_params,
latency_frames,
data_callback,
state_callback,
user_ptr,
)
},
ffi::CUBEB_OK
);
assert!(!stream.is_null());
operation(stream);
unsafe {
OPS.stream_destroy.unwrap()(stream);
}
}
pub fn test_ops_stream_operation<F>(
name: &'static str,
input_device: ffi::cubeb_devid,
@ -1062,42 +1215,19 @@ pub fn test_ops_stream_operation<F>(
F: FnOnce(*mut ffi::cubeb_stream),
{
test_ops_context_operation("context: stream operation", |context_ptr| {
// Do nothing if there is no input/output device to perform input/output tests.
if !input_stream_params.is_null() && test_get_default_device(Scope::Input).is_none() {
println!("No input device to perform input tests for \"{}\".", name);
return;
}
if !output_stream_params.is_null() && test_get_default_device(Scope::Output).is_none() {
println!("No output device to perform output tests for \"{}\".", name);
return;
}
let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
let stream_name = CString::new(name).expect("Failed to create stream name");
assert_eq!(
unsafe {
OPS.stream_init.unwrap()(
context_ptr,
&mut stream,
stream_name.as_ptr(),
input_device,
input_stream_params,
output_device,
output_stream_params,
latency_frames,
data_callback,
state_callback,
user_ptr,
)
},
ffi::CUBEB_OK
test_ops_stream_operation_on_context(
name,
context_ptr,
input_device,
input_stream_params,
output_device,
output_stream_params,
latency_frames,
data_callback,
state_callback,
user_ptr,
operation,
);
assert!(!stream.is_null());
operation(stream);
unsafe {
OPS.stream_destroy.unwrap()(stream);
}
});
}
@ -1136,7 +1266,7 @@ fn test_get_raw_stream<F>(
user_ptr,
data_callback,
state_callback,
global_latency_frames.unwrap(),
global_latency_frames,
);
stream.core_stream_data = CoreStreamData::new(&stream, None, None);
@ -1154,6 +1284,7 @@ pub fn test_get_stream_with_default_data_callback_by_type<F>(
) where
F: FnOnce(&mut AudioUnitStream),
{
debug_assert_not_running_serially();
let mut input_params = get_dummy_stream_params(Scope::Input);
let mut output_params = get_dummy_stream_params(Scope::Output);

Просмотреть файл

@ -22,7 +22,7 @@ static_prefs = { path = "../../../../modules/libpref/init/static_prefs" }
profiler_helper = { path = "../../../../tools/profiler/rust-helper", optional = true }
mozurl = { path = "../../../../netwerk/base/mozurl" }
webrender_bindings = { path = "../../../../gfx/webrender_bindings" }
cubeb-coreaudio = { git = "https://github.com/mozilla/cubeb-coreaudio-rs", rev = "d23ab55eab684b46f46e1da177c8814f6103a009", optional = true }
cubeb-coreaudio = { git = "https://github.com/mozilla/cubeb-coreaudio-rs", rev = "7b1021714989bedef3d40a843a516273db38beaa", optional = true }
cubeb-pulse = { git = "https://github.com/mozilla/cubeb-pulse-rs", rev="8ff972c8e2ec1782ff262ac4071c0415e69b1367", optional = true, features=["pulse-dlopen"] }
cubeb-sys = { version = "0.12.0", optional = true, features=["gecko-in-tree"] }
audioipc2-client = { git = "https://github.com/mozilla/audioipc", rev = "409e11f8de6288e9ddfe269654523735302e59e6", optional = true }