summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs')
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs1215
1 files changed, 1215 insertions, 0 deletions
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs
new file mode 100644
index 0000000000..340fec002d
--- /dev/null
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs
@@ -0,0 +1,1215 @@
+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,
+};
+use super::*;
+
+// Context Operations
+// ------------------------------------------------------------------------------------------------
+#[test]
+fn test_ops_context_init_and_destroy() {
+ test_ops_context_operation("context: init and destroy", |_context_ptr| {});
+}
+
+#[test]
+fn test_ops_context_backend_id() {
+ test_ops_context_operation("context: backend id", |context_ptr| {
+ let backend = unsafe {
+ let ptr = OPS.get_backend_id.unwrap()(context_ptr);
+ CStr::from_ptr(ptr).to_string_lossy().into_owned()
+ };
+ assert_eq!(backend, "audiounit-rust");
+ });
+}
+
+#[test]
+fn test_ops_context_max_channel_count() {
+ test_ops_context_operation("context: max channel count", |context_ptr| {
+ let output_exists = test_get_default_device(Scope::Output).is_some();
+ let mut max_channel_count = 0;
+ let r = unsafe { OPS.get_max_channel_count.unwrap()(context_ptr, &mut max_channel_count) };
+ if output_exists {
+ assert_eq!(r, ffi::CUBEB_OK);
+ assert_ne!(max_channel_count, 0);
+ } else {
+ assert_eq!(r, ffi::CUBEB_ERROR);
+ assert_eq!(max_channel_count, 0);
+ }
+ });
+}
+
+#[test]
+fn test_ops_context_min_latency() {
+ test_ops_context_operation("context: min latency", |context_ptr| {
+ let output_exists = test_get_default_device(Scope::Output).is_some();
+ let params = ffi::cubeb_stream_params::default();
+ let mut latency = u32::max_value();
+ let r = unsafe { OPS.get_min_latency.unwrap()(context_ptr, params, &mut latency) };
+ if output_exists {
+ assert_eq!(r, ffi::CUBEB_OK);
+ assert!(latency >= SAFE_MIN_LATENCY_FRAMES);
+ assert!(SAFE_MAX_LATENCY_FRAMES >= latency);
+ } else {
+ assert_eq!(r, ffi::CUBEB_ERROR);
+ assert_eq!(latency, u32::max_value());
+ }
+ });
+}
+
+#[test]
+fn test_ops_context_preferred_sample_rate() {
+ test_ops_context_operation("context: preferred sample rate", |context_ptr| {
+ let output_exists = test_get_default_device(Scope::Output).is_some();
+ let mut rate = u32::max_value();
+ let r = unsafe { OPS.get_preferred_sample_rate.unwrap()(context_ptr, &mut rate) };
+ if output_exists {
+ assert_eq!(r, ffi::CUBEB_OK);
+ assert_ne!(rate, u32::max_value());
+ assert_ne!(rate, 0);
+ } else {
+ assert_eq!(r, ffi::CUBEB_ERROR);
+ assert_eq!(rate, u32::max_value());
+ }
+ });
+}
+
+#[test]
+fn test_ops_context_supported_input_processing_params() {
+ test_ops_context_operation(
+ "context: supported input processing params",
+ |context_ptr| {
+ let mut params = ffi::CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ let r = unsafe {
+ OPS.get_supported_input_processing_params.unwrap()(context_ptr, &mut params)
+ };
+ assert_eq!(r, ffi::CUBEB_OK);
+ assert_eq!(
+ params,
+ ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL
+ );
+ },
+ );
+}
+
+#[test]
+fn test_ops_context_enumerate_devices_unknown() {
+ test_ops_context_operation("context: enumerate devices (unknown)", |context_ptr| {
+ let mut coll = ffi::cubeb_device_collection {
+ device: ptr::null_mut(),
+ count: 0,
+ };
+ assert_eq!(
+ unsafe {
+ OPS.enumerate_devices.unwrap()(
+ context_ptr,
+ ffi::CUBEB_DEVICE_TYPE_UNKNOWN,
+ &mut coll,
+ )
+ },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(coll.count, 0);
+ assert_eq!(coll.device, ptr::null_mut());
+ assert_eq!(
+ unsafe { OPS.device_collection_destroy.unwrap()(context_ptr, &mut coll) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(coll.count, 0);
+ assert_eq!(coll.device, ptr::null_mut());
+ });
+}
+
+#[test]
+fn test_ops_context_enumerate_devices_input() {
+ test_ops_context_operation("context: enumerate devices (input)", |context_ptr| {
+ let having_input = test_get_default_device(Scope::Input).is_some();
+ let mut coll = ffi::cubeb_device_collection {
+ device: ptr::null_mut(),
+ count: 0,
+ };
+ assert_eq!(
+ unsafe {
+ OPS.enumerate_devices.unwrap()(context_ptr, ffi::CUBEB_DEVICE_TYPE_INPUT, &mut coll)
+ },
+ ffi::CUBEB_OK
+ );
+ if having_input {
+ assert_ne!(coll.count, 0);
+ assert_ne!(coll.device, ptr::null_mut());
+ } else {
+ assert_eq!(coll.count, 0);
+ assert_eq!(coll.device, ptr::null_mut());
+ }
+ assert_eq!(
+ unsafe { OPS.device_collection_destroy.unwrap()(context_ptr, &mut coll) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(coll.count, 0);
+ assert_eq!(coll.device, ptr::null_mut());
+ });
+}
+
+#[test]
+fn test_ops_context_enumerate_devices_output() {
+ test_ops_context_operation("context: enumerate devices (output)", |context_ptr| {
+ let output_exists = test_get_default_device(Scope::Output).is_some();
+ let mut coll = ffi::cubeb_device_collection {
+ device: ptr::null_mut(),
+ count: 0,
+ };
+ assert_eq!(
+ unsafe {
+ OPS.enumerate_devices.unwrap()(
+ context_ptr,
+ ffi::CUBEB_DEVICE_TYPE_OUTPUT,
+ &mut coll,
+ )
+ },
+ ffi::CUBEB_OK
+ );
+ if output_exists {
+ assert_ne!(coll.count, 0);
+ assert_ne!(coll.device, ptr::null_mut());
+ } else {
+ assert_eq!(coll.count, 0);
+ assert_eq!(coll.device, ptr::null_mut());
+ }
+ assert_eq!(
+ unsafe { OPS.device_collection_destroy.unwrap()(context_ptr, &mut coll) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(coll.count, 0);
+ assert_eq!(coll.device, ptr::null_mut());
+ });
+}
+
+#[test]
+fn test_ops_context_device_collection_destroy() {
+ // Destroy a dummy device collection, without calling enumerate_devices to allocate memory for the device collection
+ test_ops_context_operation("context: device collection destroy", |context_ptr| {
+ let mut coll = ffi::cubeb_device_collection {
+ device: ptr::null_mut(),
+ count: 0,
+ };
+ assert_eq!(
+ unsafe { OPS.device_collection_destroy.unwrap()(context_ptr, &mut coll) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(coll.device, ptr::null_mut());
+ assert_eq!(coll.count, 0);
+ });
+}
+
+#[test]
+fn test_ops_context_register_device_collection_changed_unknown() {
+ test_ops_context_operation(
+ "context: register device collection changed (unknown)",
+ |context_ptr| {
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ ffi::CUBEB_DEVICE_TYPE_UNKNOWN,
+ None,
+ ptr::null_mut(),
+ )
+ },
+ ffi::CUBEB_ERROR_INVALID_PARAMETER
+ );
+ },
+ );
+}
+
+#[test]
+fn test_ops_context_register_device_collection_changed_twice_input() {
+ test_ops_context_register_device_collection_changed_twice(ffi::CUBEB_DEVICE_TYPE_INPUT);
+}
+
+#[test]
+fn test_ops_context_register_device_collection_changed_twice_output() {
+ test_ops_context_register_device_collection_changed_twice(ffi::CUBEB_DEVICE_TYPE_OUTPUT);
+}
+
+#[test]
+fn test_ops_context_register_device_collection_changed_twice_inout() {
+ test_ops_context_register_device_collection_changed_twice(
+ ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT,
+ );
+}
+
+fn test_ops_context_register_device_collection_changed_twice(devtype: u32) {
+ extern "C" fn callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+ let label_input: &'static str = "context: register device collection changed twice (input)";
+ let label_output: &'static str = "context: register device collection changed twice (output)";
+ let label_inout: &'static str = "context: register device collection changed twice (inout)";
+ let label = if devtype == ffi::CUBEB_DEVICE_TYPE_INPUT {
+ label_input
+ } else if devtype == ffi::CUBEB_DEVICE_TYPE_OUTPUT {
+ label_output
+ } else if devtype == ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT {
+ label_inout
+ } else {
+ return;
+ };
+
+ test_ops_context_operation(label, |context_ptr| {
+ // Register a callback within the defined scope.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ devtype,
+ Some(callback),
+ ptr::null_mut(),
+ )
+ },
+ ffi::CUBEB_OK
+ );
+
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ devtype,
+ Some(callback),
+ ptr::null_mut(),
+ )
+ },
+ ffi::CUBEB_ERROR_INVALID_PARAMETER
+ );
+ // Unregister
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ devtype,
+ None,
+ ptr::null_mut(),
+ )
+ },
+ ffi::CUBEB_OK
+ );
+ });
+}
+
+#[test]
+fn test_ops_context_register_device_collection_changed() {
+ extern "C" fn callback(_: *mut ffi::cubeb, _: *mut c_void) {}
+ test_ops_context_operation(
+ "context: register device collection changed",
+ |context_ptr| {
+ let devtypes: [ffi::cubeb_device_type; 3] = [
+ ffi::CUBEB_DEVICE_TYPE_INPUT,
+ ffi::CUBEB_DEVICE_TYPE_OUTPUT,
+ ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT,
+ ];
+
+ for devtype in &devtypes {
+ // Register a callback in the defined scoped.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ *devtype,
+ Some(callback),
+ ptr::null_mut(),
+ )
+ },
+ ffi::CUBEB_OK
+ );
+
+ // Unregister all callbacks regardless of the scope.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT,
+ None,
+ ptr::null_mut(),
+ )
+ },
+ ffi::CUBEB_OK
+ );
+
+ // Register callback in the defined scoped again.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ *devtype,
+ Some(callback),
+ ptr::null_mut(),
+ )
+ },
+ ffi::CUBEB_OK
+ );
+
+ // Unregister callback within the defined scope.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ *devtype,
+ None,
+ ptr::null_mut(),
+ )
+ },
+ ffi::CUBEB_OK
+ );
+ }
+ },
+ );
+}
+
+#[test]
+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;
+ }
+
+ test_ops_context_operation(
+ "context: register device collection changed and create a duplex stream",
+ |context_ptr| {
+ let got_called = Box::new(false);
+ let got_called_ptr = Box::into_raw(got_called);
+
+ // Register a callback monitoring both input and output device collection.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT,
+ Some(callback),
+ got_called_ptr as *mut c_void,
+ )
+ },
+ ffi::CUBEB_OK
+ );
+
+ // The aggregate device is very likely to be created in the system
+ // when creating a duplex stream. We need to make sure it won't trigger
+ // the callback.
+ test_default_duplex_stream_operation("duplex stream", |_stream| {
+ // Do nothing but wait for device-collection change.
+ thread::sleep(Duration::from_millis(200));
+ });
+
+ // Unregister the callback.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ ffi::CUBEB_DEVICE_TYPE_INPUT | ffi::CUBEB_DEVICE_TYPE_OUTPUT,
+ None,
+ got_called_ptr as *mut c_void,
+ )
+ },
+ ffi::CUBEB_OK
+ );
+
+ let got_called = unsafe { Box::from_raw(got_called_ptr) };
+ assert!(!got_called.as_ref());
+ },
+ );
+}
+
+#[test]
+#[ignore]
+fn test_ops_context_register_device_collection_changed_manual() {
+ test_ops_context_operation(
+ "(manual) context: register device collection changed",
+ |context_ptr| {
+ println!("context @ {:p}", context_ptr);
+
+ struct Data {
+ context: *mut ffi::cubeb,
+ touched: u32, // TODO: Use AtomicU32 instead
+ }
+
+ extern "C" fn input_callback(context: *mut ffi::cubeb, user: *mut c_void) {
+ println!("input > context @ {:p}", context);
+ let data = unsafe { &mut (*(user as *mut Data)) };
+ assert_eq!(context, data.context);
+ data.touched += 1;
+ }
+
+ extern "C" fn output_callback(context: *mut ffi::cubeb, user: *mut c_void) {
+ println!("output > context @ {:p}", context);
+ let data = unsafe { &mut (*(user as *mut Data)) };
+ assert_eq!(context, data.context);
+ data.touched += 1;
+ }
+
+ let mut data = Data {
+ context: context_ptr,
+ touched: 0,
+ };
+
+ // Register a callback for input scope.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ ffi::CUBEB_DEVICE_TYPE_INPUT,
+ Some(input_callback),
+ &mut data as *mut Data as *mut c_void,
+ )
+ },
+ ffi::CUBEB_OK
+ );
+
+ // Register a callback for output scope.
+ assert_eq!(
+ unsafe {
+ OPS.register_device_collection_changed.unwrap()(
+ context_ptr,
+ ffi::CUBEB_DEVICE_TYPE_OUTPUT,
+ Some(output_callback),
+ &mut data as *mut Data as *mut c_void,
+ )
+ },
+ ffi::CUBEB_OK
+ );
+
+ while data.touched < 2 {}
+ },
+ );
+}
+
+#[test]
+fn test_ops_context_stream_init_no_stream_params() {
+ let name = "context: stream_init with no stream params";
+ test_ops_context_operation(name, |context_ptr| {
+ 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(),
+ ptr::null_mut(), // Use default input device.
+ ptr::null_mut(), // No input parameters.
+ ptr::null_mut(), // Use default output device.
+ ptr::null_mut(), // No output parameters.
+ 4096, // TODO: Get latency by get_min_latency instead ?
+ Some(noop_data_callback),
+ None, // No state callback.
+ ptr::null_mut(), // No user data pointer.
+ )
+ },
+ ffi::CUBEB_ERROR_INVALID_PARAMETER
+ );
+ assert!(stream.is_null());
+ });
+}
+
+#[test]
+fn test_ops_context_stream_init_no_input_stream_params() {
+ let name = "context: stream_init with no input stream params";
+ let input_device = test_get_default_device(Scope::Input);
+ if input_device.is_none() {
+ println!("No input device to perform input tests for \"{}\".", name);
+ return;
+ }
+ test_ops_context_operation(name, |context_ptr| {
+ 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.unwrap() as ffi::cubeb_devid,
+ ptr::null_mut(), // No input parameters.
+ ptr::null_mut(), // Use default output device.
+ ptr::null_mut(), // No output parameters.
+ 4096, // TODO: Get latency by get_min_latency instead ?
+ Some(noop_data_callback),
+ None, // No state callback.
+ ptr::null_mut(), // No user data pointer.
+ )
+ },
+ ffi::CUBEB_ERROR_INVALID_PARAMETER
+ );
+ assert!(stream.is_null());
+ });
+}
+
+#[test]
+fn test_ops_context_stream_init_no_output_stream_params() {
+ let name = "context: stream_init with no output stream params";
+ let output_device = test_get_default_device(Scope::Output);
+ if output_device.is_none() {
+ println!("No output device to perform output tests for \"{}\".", name);
+ return;
+ }
+ test_ops_context_operation(name, |context_ptr| {
+ 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(),
+ ptr::null_mut(), // Use default input device.
+ ptr::null_mut(), // No input parameters.
+ output_device.unwrap() as ffi::cubeb_devid,
+ ptr::null_mut(), // No output parameters.
+ 4096, // TODO: Get latency by get_min_latency instead ?
+ Some(noop_data_callback),
+ None, // No state callback.
+ ptr::null_mut(), // No user data pointer.
+ )
+ },
+ ffi::CUBEB_ERROR_INVALID_PARAMETER
+ );
+ assert!(stream.is_null());
+ });
+}
+
+#[test]
+fn test_ops_context_stream_init_no_data_callback() {
+ let name = "context: stream_init with no data callback";
+ test_ops_context_operation(name, |context_ptr| {
+ let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
+ let stream_name = CString::new(name).expect("Failed to create stream name");
+
+ let mut output_params = ffi::cubeb_stream_params::default();
+ output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = 44100;
+ output_params.channels = 2;
+ output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
+
+ assert_eq!(
+ unsafe {
+ OPS.stream_init.unwrap()(
+ context_ptr,
+ &mut stream,
+ stream_name.as_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 ?
+ None, // No data callback.
+ None, // No state callback.
+ ptr::null_mut(), // No user data pointer.
+ )
+ },
+ ffi::CUBEB_ERROR_INVALID_PARAMETER
+ );
+ assert!(stream.is_null());
+ });
+}
+
+#[test]
+fn test_ops_context_stream_init_channel_rate_combinations() {
+ let name = "context: stream_init with various channels and rates";
+ test_ops_context_operation(name, |context_ptr| {
+ let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
+ let stream_name = CString::new(name).expect("Failed to create stream name");
+
+ const MAX_NUM_CHANNELS: u32 = 32;
+ let channel_values: Vec<u32> = vec![1, 2, 3, 4, 6];
+ let freq_values: Vec<u32> = vec![16000, 24000, 44100, 48000];
+ let is_float_values: Vec<bool> = vec![false, true];
+
+ for (channels, freq, is_float) in iproduct!(channel_values, freq_values, is_float_values) {
+ assert!(channels < MAX_NUM_CHANNELS);
+ println!("--------------------------");
+ println!(
+ "Testing {} channel(s), {} Hz, {}\n",
+ channels,
+ freq,
+ if is_float { "float" } else { "short" }
+ );
+
+ let mut output_params = ffi::cubeb_stream_params::default();
+ output_params.format = if is_float {
+ ffi::CUBEB_SAMPLE_FLOAT32NE
+ } else {
+ ffi::CUBEB_SAMPLE_S16NE
+ };
+ output_params.rate = freq;
+ output_params.channels = channels;
+ output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
+
+ assert_eq!(
+ unsafe {
+ OPS.stream_init.unwrap()(
+ context_ptr,
+ &mut stream,
+ stream_name.as_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), // No data callback.
+ None, // No state callback.
+ ptr::null_mut(), // No user data pointer.
+ )
+ },
+ ffi::CUBEB_OK
+ );
+ assert!(!stream.is_null());
+ }
+ });
+}
+
+// Stream Operations
+// ------------------------------------------------------------------------------------------------
+fn test_default_output_stream_operation<F>(name: &'static str, operation: F)
+where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ // Make sure the parameters meet the requirements of AudioUnitContext::stream_init
+ // (in the comments).
+ let mut output_params = ffi::cubeb_stream_params::default();
+ output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = 44100;
+ output_params.channels = 2;
+ output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
+
+ test_ops_stream_operation(
+ name,
+ ptr::null_mut(), // Use default input device.
+ ptr::null_mut(), // No input parameters.
+ ptr::null_mut(), // Use default output device.
+ &mut output_params,
+ 4096, // TODO: Get latency by get_min_latency instead ?
+ Some(noop_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)
+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 = 48000;
+ input_params.channels = 1;
+ input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
+
+ let mut output_params = ffi::cubeb_stream_params::default();
+ output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = 44100;
+ output_params.channels = 2;
+ output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
+
+ test_ops_stream_operation(
+ name,
+ 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),
+ 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)
+where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ let mut input_devices = get_devices_info_in_scope(Scope::Input);
+ input_devices.retain(|d| test_device_channels_in_scope(d.id, Scope::Input).unwrap_or(0) >= 2);
+ if input_devices.is_empty() {
+ println!("No stereo input device present. Skipping stereo-input test.");
+ return;
+ }
+
+ let mut input_params = ffi::cubeb_stream_params::default();
+ input_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = 48000;
+ input_params.channels = 2;
+ input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
+
+ let mut output_params = ffi::cubeb_stream_params::default();
+ output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = 48000;
+ output_params.channels = 2;
+ output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
+
+ test_ops_stream_operation(
+ name,
+ 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),
+ 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)
+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;
+
+ let mut output_params = ffi::cubeb_stream_params::default();
+ output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = 48000;
+ output_params.channels = 2;
+ output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
+
+ test_ops_stream_operation(
+ name,
+ 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),
+ None, // No state callback.
+ ptr::null_mut(), // No user data pointer.
+ operation,
+ );
+}
+
+fn test_stereo_input_duplex_voice_stream_operation<F>(name: &'static str, operation: F)
+where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ let mut input_devices = get_devices_info_in_scope(Scope::Input);
+ input_devices.retain(|d| test_device_channels_in_scope(d.id, Scope::Input).unwrap_or(0) >= 2);
+ if input_devices.is_empty() {
+ println!("No stereo input device present. Skipping stereo-input test.");
+ return;
+ }
+
+ let mut input_params = ffi::cubeb_stream_params::default();
+ input_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = 44100;
+ input_params.channels = 2;
+ input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
+
+ let mut output_params = ffi::cubeb_stream_params::default();
+ output_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = 44100;
+ output_params.channels = 2;
+ output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
+
+ test_ops_stream_operation(
+ name,
+ 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),
+ None, // No state callback.
+ ptr::null_mut(), // No user data pointer.
+ operation,
+ );
+}
+
+#[test]
+fn test_ops_stream_init_and_destroy() {
+ test_default_output_stream_operation("stream: init and destroy", |_stream| {});
+}
+
+#[test]
+fn test_ops_stream_start() {
+ test_default_output_stream_operation("stream: start", |stream| {
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ });
+}
+
+#[test]
+fn test_ops_stream_stop() {
+ test_default_output_stream_operation("stream: stop", |stream| {
+ assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
+ });
+}
+
+#[test]
+fn test_ops_stream_position() {
+ test_default_output_stream_operation("stream: position", |stream| {
+ let mut position = u64::max_value();
+ assert_eq!(
+ unsafe { OPS.stream_get_position.unwrap()(stream, &mut position) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(position, 0);
+ });
+}
+
+#[test]
+fn test_ops_stream_latency() {
+ test_default_output_stream_operation("stream: latency", |stream| {
+ let mut latency = u32::max_value();
+ assert_eq!(
+ unsafe { OPS.stream_get_latency.unwrap()(stream, &mut latency) },
+ ffi::CUBEB_OK
+ );
+ assert_ne!(latency, u32::max_value());
+ });
+}
+
+#[test]
+fn test_ops_stream_set_volume() {
+ test_default_output_stream_operation("stream: set volume", |stream| {
+ assert_eq!(
+ unsafe { OPS.stream_set_volume.unwrap()(stream, 0.5) },
+ ffi::CUBEB_OK
+ );
+ });
+}
+
+#[test]
+fn test_ops_stream_current_device() {
+ test_default_output_stream_operation("stream: get current device and destroy it", |stream| {
+ if test_get_default_device(Scope::Input).is_none()
+ || test_get_default_device(Scope::Output).is_none()
+ {
+ println!("stream_get_current_device only works when the machine has both input and output devices");
+ return;
+ }
+
+ let mut device: *mut ffi::cubeb_device = ptr::null_mut();
+ if unsafe { OPS.stream_get_current_device.unwrap()(stream, &mut device) } != ffi::CUBEB_OK {
+ // It can happen when we fail to get the device source.
+ println!("stream_get_current_device fails. Skip this test.");
+ return;
+ }
+
+ assert!(!device.is_null());
+ // Uncomment the below to print out the results.
+ // let deviceref = unsafe { DeviceRef::from_ptr(device) };
+ // println!(
+ // "output: {}",
+ // deviceref.output_name().unwrap_or("(no device name)")
+ // );
+ // println!(
+ // "input: {}",
+ // deviceref.input_name().unwrap_or("(no device name)")
+ // );
+ assert_eq!(
+ unsafe { OPS.stream_device_destroy.unwrap()(stream, device) },
+ ffi::CUBEB_OK
+ );
+ });
+}
+
+#[test]
+fn test_ops_stream_device_destroy() {
+ test_default_output_stream_operation("stream: destroy null device", |stream| {
+ assert_eq!(
+ unsafe { OPS.stream_device_destroy.unwrap()(stream, ptr::null_mut()) },
+ ffi::CUBEB_OK // It returns OK anyway.
+ );
+ });
+}
+
+#[test]
+fn test_ops_stream_register_device_changed_callback() {
+ extern "C" fn callback(_: *mut c_void) {}
+
+ test_default_output_stream_operation("stream: register device changed callback", |stream| {
+ assert_eq!(
+ unsafe { OPS.stream_register_device_changed_callback.unwrap()(stream, Some(callback)) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(
+ unsafe { OPS.stream_register_device_changed_callback.unwrap()(stream, Some(callback)) },
+ ffi::CUBEB_ERROR_INVALID_PARAMETER
+ );
+ assert_eq!(
+ unsafe { OPS.stream_register_device_changed_callback.unwrap()(stream, None) },
+ ffi::CUBEB_OK
+ );
+ });
+}
+
+#[test]
+fn test_ops_stereo_input_duplex_stream_init_and_destroy() {
+ test_stereo_input_duplex_stream_operation(
+ "stereo-input duplex stream: init and destroy",
+ |_stream| {},
+ );
+}
+
+#[test]
+fn test_ops_stereo_input_duplex_stream_start() {
+ test_stereo_input_duplex_stream_operation("stereo-input duplex stream: start", |stream| {
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ });
+}
+
+#[test]
+fn test_ops_stereo_input_duplex_stream_stop() {
+ test_stereo_input_duplex_stream_operation("stereo-input duplex stream: stop", |stream| {
+ assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
+ });
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_init_and_destroy() {
+ test_default_duplex_voice_stream_operation(
+ "duplex voice stream: init and destroy",
+ |_stream| {},
+ );
+}
+
+#[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);
+ });
+}
+
+#[test]
+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);
+ });
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_set_input_mute() {
+ test_default_duplex_voice_stream_operation("duplex voice stream: mute", |stream| {
+ assert_eq!(
+ unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
+ ffi::CUBEB_OK
+ );
+ });
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_set_input_mute_before_start() {
+ test_default_duplex_voice_stream_operation(
+ "duplex voice stream: mute before start",
+ |stream| {
+ assert_eq!(
+ unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ },
+ );
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() {
+ test_default_duplex_voice_stream_operation(
+ "duplex voice stream: mute before start with reinit",
+ |stream| {
+ assert_eq!(
+ unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+
+ // Hacky cast, but testing this here was simplest for now.
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ stm.reinit_async();
+ let queue = stm.queue.clone();
+ let mut mute_after_reinit = false;
+ queue.run_sync(|| {
+ let mut mute: u32 = 0;
+ let r = audio_unit_get_property(
+ stm.core_stream_data.input_unit,
+ kAUVoiceIOProperty_MuteOutput,
+ kAudioUnitScope_Global,
+ AU_IN_BUS,
+ &mut mute,
+ &mut mem::size_of::<u32>(),
+ );
+ assert_eq!(r, NO_ERR);
+ mute_after_reinit = mute == 1;
+ });
+ assert_eq!(mute_after_reinit, true);
+ },
+ );
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_set_input_mute_after_start() {
+ test_default_duplex_voice_stream_operation("duplex voice stream: mute after start", |stream| {
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ assert_eq!(
+ unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
+ ffi::CUBEB_OK
+ );
+ });
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_set_input_processing_params() {
+ test_default_duplex_voice_stream_operation("duplex voice stream: processing", |stream| {
+ let params: ffi::cubeb_input_processing_params =
+ ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL;
+ assert_eq!(
+ unsafe { OPS.stream_set_input_processing_params.unwrap()(stream, params) },
+ ffi::CUBEB_OK
+ );
+ });
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_set_input_processing_params_before_start() {
+ test_default_duplex_voice_stream_operation(
+ "duplex voice stream: processing before start",
+ |stream| {
+ let params: ffi::cubeb_input_processing_params =
+ ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL;
+ assert_eq!(
+ unsafe { OPS.stream_set_input_processing_params.unwrap()(stream, params) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ },
+ );
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_reinit() {
+ test_default_duplex_voice_stream_operation(
+ "duplex voice stream: processing before start with reinit",
+ |stream| {
+ let params: ffi::cubeb_input_processing_params =
+ ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL;
+ assert_eq!(
+ unsafe { OPS.stream_set_input_processing_params.unwrap()(stream, params) },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+
+ // Hacky cast, but testing this here was simplest for now.
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ stm.reinit_async();
+ let queue = stm.queue.clone();
+ let mut params_after_reinit: ffi::cubeb_input_processing_params =
+ ffi::CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ queue.run_sync(|| {
+ let mut params: ffi::cubeb_input_processing_params =
+ ffi::CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ let mut agc: u32 = 0;
+ let r = audio_unit_get_property(
+ stm.core_stream_data.input_unit,
+ kAUVoiceIOProperty_VoiceProcessingEnableAGC,
+ kAudioUnitScope_Global,
+ AU_IN_BUS,
+ &mut agc,
+ &mut mem::size_of::<u32>(),
+ );
+ assert_eq!(r, NO_ERR);
+ if agc == 1 {
+ params = params | ffi::CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL;
+ }
+ let mut bypass: u32 = 0;
+ let r = audio_unit_get_property(
+ stm.core_stream_data.input_unit,
+ kAUVoiceIOProperty_BypassVoiceProcessing,
+ kAudioUnitScope_Global,
+ AU_IN_BUS,
+ &mut bypass,
+ &mut mem::size_of::<u32>(),
+ );
+ assert_eq!(r, NO_ERR);
+ if bypass == 0 {
+ params = params
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION;
+ }
+ params_after_reinit = params;
+ });
+ assert_eq!(params, params_after_reinit);
+ },
+ );
+}
+
+#[test]
+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| {
+ 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
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL;
+ assert_eq!(
+ unsafe { OPS.stream_set_input_processing_params.unwrap()(stream, params) },
+ ffi::CUBEB_OK
+ );
+ },
+ );
+}
+
+#[test]
+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| {},
+ );
+}
+
+#[test]
+fn test_ops_stereo_input_duplex_voice_stream_start() {
+ test_stereo_input_duplex_voice_stream_operation(
+ "stereo-input duplex voice stream: start",
+ |stream| {
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ },
+ );
+}
+
+#[test]
+fn test_ops_stereo_input_duplex_voice_stream_stop() {
+ test_stereo_input_duplex_voice_stream_operation(
+ "stereo-input duplex voice stream: stop",
+ |stream| {
+ assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
+ },
+ );
+}