Source code
Revision control
Copy as Markdown
Other Tools
extern crate itertools;
use self::itertools::iproduct;
use super::utils::{
    draining_data_callback, get_devices_info_in_scope, noop_data_callback, state_tracking_cb,
    test_device_channels_in_scope, test_get_default_device, test_object_id_to_devid,
    test_ops_context_operation, test_ops_stream_operation, test_ops_stream_operation_on_context,
    Scope, StateCallbackData,
};
use super::*;
use std::mem::ManuallyDrop;
use std::thread;
// 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!(
            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 device_infos = ManuallyDrop::new(Box::new([DeviceInfo::default().into()]));
        let mut coll = ffi::cubeb_device_collection {
            device: device_infos.as_mut_ptr(),
            count: device_infos.len(),
        };
        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() {
    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");
        let input_device =
            test_object_id_to_devid(context_ptr, input_device.unwrap(), DeviceType::INPUT);
        assert_eq!(
            unsafe {
                OPS.stream_init.unwrap()(
                    context_ptr,
                    &mut stream,
                    stream_name.as_ptr(),
                    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_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());
            unsafe { OPS.stream_destroy.unwrap()(stream) };
        }
    });
}
// Stream Operations
// ------------------------------------------------------------------------------------------------
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
    // (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_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 ?
        data_callback,
        None,            // No state callback.
        ptr::null_mut(), // No user data pointer.
        operation,
    );
}
fn test_default_output_stream_operation_with_callback<F>(
    name: &'static str,
    data_callback: ffi::cubeb_data_callback,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_ops_context_operation("context: default output stream operation", |context_ptr| {
        test_default_output_stream_operation_on_context_with_callback(
            name,
            context_ptr,
            data_callback,
            operation,
        );
    });
}
fn test_default_output_stream_operation<F>(name: &'static str, operation: F)
where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_default_output_stream_operation_with_callback(name, Some(noop_data_callback), operation);
}
fn test_default_duplex_stream_operation_on_context_with_callback<F>(
    name: &'static str,
    context_ptr: *mut ffi::cubeb,
    data_callback: ffi::cubeb_data_callback,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    // Make sure the parameters meet the requirements of AudioUnitContext::stream_init
    // (in the comments).
    let mut input_params = ffi::cubeb_stream_params::default();
    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_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 ?
        data_callback,
        None,            // No state callback.
        ptr::null_mut(), // No user data pointer.
        operation,
    );
}
fn test_default_duplex_stream_operation_with_callback<F>(
    name: &'static str,
    data_callback: ffi::cubeb_data_callback,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_ops_context_operation("context: default duplex stream operation", |context_ptr| {
        test_default_duplex_stream_operation_on_context_with_callback(
            name,
            context_ptr,
            data_callback,
            operation,
        );
    });
}
fn test_default_duplex_stream_operation<F>(name: &'static str, operation: F)
where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_default_duplex_stream_operation_with_callback(name, Some(noop_data_callback), operation);
}
fn test_stereo_input_duplex_stream_operation_on_context_with_callback<F>(
    name: &'static str,
    context_ptr: *mut ffi::cubeb,
    data_callback: ffi::cubeb_data_callback,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    let mut input_devices = get_devices_info_in_scope(Scope::Input);
    input_devices.retain(|d| test_device_channels_in_scope(d.id, Scope::Input).unwrap_or(0) >= 2);
    if input_devices.is_empty() {
        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;
    let input_device = test_object_id_to_devid(context_ptr, input_devices[0].id, DeviceType::INPUT);
    test_ops_stream_operation_on_context(
        name,
        context_ptr,
        input_device,
        &mut input_params,
        ptr::null_mut(), // Use default output device.
        &mut output_params,
        4096, // TODO: Get latency by get_min_latency instead ?
        data_callback,
        None,            // No state callback.
        ptr::null_mut(), // No user data pointer.
        operation,
    );
}
fn test_stereo_input_duplex_stream_operation_with_callback<F>(
    name: &'static str,
    data_callback: ffi::cubeb_data_callback,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_ops_context_operation(
        "context: stereo input duplex stream operation",
        |context_ptr| {
            test_stereo_input_duplex_stream_operation_on_context_with_callback(
                name,
                context_ptr,
                data_callback,
                operation,
            );
        },
    );
}
fn test_stereo_input_duplex_stream_operation<F>(name: &'static str, operation: F)
where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_stereo_input_duplex_stream_operation_with_callback(
        name,
        Some(noop_data_callback),
        operation,
    );
}
fn test_default_input_voice_stream_operation_on_context_with_callback<F>(
    name: &'static str,
    context_ptr: *mut ffi::cubeb,
    data_callback: ffi::cubeb_data_callback,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    // Make sure the parameters meet the requirements of AudioUnitContext::stream_init
    // (in the comments).
    let mut input_params = ffi::cubeb_stream_params::default();
    input_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
    input_params.rate = 44100;
    input_params.channels = 1;
    input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
    input_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
    test_ops_stream_operation_on_context(
        name,
        context_ptr,
        ptr::null_mut(), // Use default input device.
        &mut input_params,
        ptr::null_mut(), // Use default output device.
        ptr::null_mut(), // No output parameters.
        4096,            // TODO: Get latency by get_min_latency instead ?
        data_callback,
        None,            // No state callback.
        ptr::null_mut(), // No user data pointer.
        operation,
    );
}
fn test_default_input_voice_stream_operation_on_context<F>(
    name: &'static str,
    context_ptr: *mut ffi::cubeb,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_default_input_voice_stream_operation_on_context_with_callback(
        name,
        context_ptr,
        Some(noop_data_callback),
        operation,
    );
}
fn test_default_input_voice_stream_operation_with_callback<F>(
    name: &'static str,
    data_callback: ffi::cubeb_data_callback,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_ops_context_operation(
        "context: default input voice stream operation",
        |context_ptr| {
            test_default_input_voice_stream_operation_on_context_with_callback(
                name,
                context_ptr,
                data_callback,
                operation,
            );
        },
    );
}
fn test_default_input_voice_stream_operation<F>(name: &'static str, operation: F)
where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    test_default_input_voice_stream_operation_with_callback(
        name,
        Some(noop_data_callback),
        operation,
    );
}
fn test_default_duplex_voice_stream_operation_on_context_with_callback<F>(
    name: &'static str,
    context_ptr: *mut ffi::cubeb,
    data_callback: ffi::cubeb_data_callback,
    operation: F,
) where
    F: FnOnce(*mut ffi::cubeb_stream),
{
    // Make sure the parameters meet the requirements of AudioUnitContext::stream_init
    // (in the comments).
    let mut input_params = ffi::cubeb_stream_params::default();
    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_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 ?
        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),
{
    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_drain() {
    test_default_output_stream_operation_with_callback(
        "stream: drain",
        Some(draining_data_callback),
        |stream| {
            assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
            thread::sleep(Duration::from_millis(10));
        },
    );
}
#[test]
fn test_ops_stream_position() {
    test_default_output_stream_operation("stream: position", |stream| {
        let mut position = u64::max_value();
        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_ERROR_INVALID_PARAMETER
        );
    });
}
pub extern "C" fn reiniting_and_erroring_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());
    let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
    // Feed silence data to output buffer
    if !output_buffer.is_null() {
        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);
        }
    }
    // Trigger an async reinit before the backend handles the error below.
    // This scenario could happen in the backend's internal input callback.
    stm.reinit_async();
    ffi::CUBEB_ERROR.into()
}
#[test]
fn test_ops_stream_racy_reinit() {
    // 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;
    let mut data = StateCallbackData::default();
    test_ops_stream_operation(
        "stream: racy reinit",
        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(reiniting_and_erroring_data_callback),
        Some(state_tracking_cb),
        &mut data as *mut StateCallbackData as *mut c_void,
        |stream| {
            assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
            while data.error_cnt() == 0 && data.stopped_cnt() == 0 {
                thread::sleep(Duration::from_millis(1));
            }
            assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
        },
    );
    assert_eq!(data.started_cnt(), 1);
    assert_eq!(data.stopped_cnt(), 0);
    assert_eq!(data.drained_cnt(), 0);
    assert_eq!(data.error_cnt(), 1);
}
#[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_stereo_input_duplex_stream_drain() {
    test_stereo_input_duplex_stream_operation_with_callback(
        "stereo-input duplex stream: drain",
        Some(draining_data_callback),
        |stream| {
            assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
            thread::sleep(Duration::from_millis(10));
        },
    );
}
#[test]
fn test_ops_input_voice_stream_init_and_destroy() {
    test_default_input_voice_stream_operation("input voice stream: init and destroy", |stream| {
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert_eq!(
            stm.core_stream_data.using_voice_processing_unit(),
            macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
        );
    });
}
#[test]
fn test_ops_input_voice_stream_start() {
    test_default_input_voice_stream_operation("input voice stream: start", |stream| {
        assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert_eq!(
            stm.core_stream_data.using_voice_processing_unit(),
            macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
        );
    });
}
#[test]
fn test_ops_input_voice_stream_stop() {
    test_default_input_voice_stream_operation("input voice stream: stop", |stream| {
        assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert_eq!(
            stm.core_stream_data.using_voice_processing_unit(),
            macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
        );
    });
}
#[test]
fn test_ops_duplex_voice_stream_init_and_destroy() {
    test_default_duplex_voice_stream_operation("duplex voice stream: init and destroy", |stream| {
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert_eq!(
            stm.core_stream_data.using_voice_processing_unit(),
            macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
        );
    });
}
#[test]
fn test_ops_duplex_voice_stream_start() {
    test_default_duplex_voice_stream_operation("duplex voice stream: start", |stream| {
        assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert_eq!(
            stm.core_stream_data.using_voice_processing_unit(),
            macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
        );
    });
}
#[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);
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert_eq!(
            stm.core_stream_data.using_voice_processing_unit(),
            macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
        );
    });
}
#[test]
fn test_ops_duplex_voice_stream_drain() {
    test_default_duplex_voice_stream_operation_with_callback(
        "duplex voice stream: drain",
        Some(draining_data_callback),
        |stream| {
            assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
            let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
            assert_eq!(
                stm.core_stream_data.using_voice_processing_unit(),
                macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
            );
            thread::sleep(Duration::from_millis(10));
        },
    );
}
#[test]
#[ignore]
fn test_ops_timing_sensitive_multiple_voice_stream_init_and_destroy() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    let start = Instant::now();
    let mut t1 = start;
    let mut t2 = start;
    let mut t3 = start;
    let mut t4 = start;
    let mut t5 = start;
    let mut t6 = start;
    let mut t7 = start;
    let mut t8 = start;
    let mut t9 = start;
    let mut t10 = start;
    test_ops_context_operation("multiple duplex voice streams", |context_ptr| {
        // First stream uses vpio, creates the shared vpio unit.
        test_default_duplex_voice_stream_operation_on_context(
            "multiple voice streams: stream 1, duplex",
            context_ptr,
            |stream| {
                let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                assert!(stm.core_stream_data.using_voice_processing_unit());
                // Two concurrent vpio streams are supported.
                test_default_input_voice_stream_operation_on_context(
                    "multiple voice streams: stream 2, input-only",
                    context_ptr,
                    |stream| {
                        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                        assert!(stm.core_stream_data.using_voice_processing_unit());
                        // Three concurrent vpio streams are supported.
                        test_default_duplex_voice_stream_operation_on_context(
                            "multiple voice streams: stream 3, duplex",
                            context_ptr,
                            |stream| {
                                let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                                assert!(stm.core_stream_data.using_voice_processing_unit());
                            },
                        );
                    },
                );
            },
        );
        t1 = Instant::now();
        // Fourth stream uses vpio, allows reuse of one already created.
        test_default_duplex_voice_stream_operation_on_context(
            "multiple voice streams: stream 4, duplex",
            context_ptr,
            |stream| {
                let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                assert!(stm.core_stream_data.using_voice_processing_unit());
                t2 = Instant::now();
                // Fifth stream uses vpio, allows reuse of one already created.
                test_default_duplex_voice_stream_operation_on_context(
                    "multiple voice streams: stream 5, duplex",
                    context_ptr,
                    |stream| {
                        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                        assert!(stm.core_stream_data.using_voice_processing_unit());
                        t3 = Instant::now();
                        // Sixth stream uses vpio, allows reuse of one already created.
                        test_default_input_voice_stream_operation_on_context(
                            "multiple voice streams: stream 6, input-only",
                            context_ptr,
                            |stream| {
                                let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                                assert!(stm.core_stream_data.using_voice_processing_unit());
                                t4 = Instant::now();
                                // Seventh stream uses vpio, but is created anew.
                                test_default_input_voice_stream_operation_on_context(
                                    "multiple voice streams: stream 7, input-only",
                                    context_ptr,
                                    |stream| {
                                        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                                        assert!(stm.core_stream_data.using_voice_processing_unit());
                                        t5 = Instant::now();
                                    },
                                );
                                t6 = Instant::now();
                            },
                        );
                        t7 = Instant::now();
                    },
                );
                t8 = Instant::now();
            },
        );
        t9 = Instant::now();
    });
    t10 = Instant::now();
    let reuse_vpio_1 = t2 - t1;
    let reuse_vpio_2 = t3 - t2;
    let reuse_vpio_3 = t4 - t3;
    let create_standalone_vpio = t5 - t4;
    assert!(
        create_standalone_vpio > reuse_vpio_1 * 2,
        "Failed create_standalone_vpio={}s > reuse_vpio_1={}s * 2",
        create_standalone_vpio.as_secs_f32(),
        reuse_vpio_1.as_secs_f32()
    );
    assert!(
        create_standalone_vpio > reuse_vpio_2 * 2,
        "Failed create_standalone_vpio={}s > reuse_vpio_2={}s * 2",
        create_standalone_vpio.as_secs_f32(),
        reuse_vpio_2.as_secs_f32()
    );
    assert!(
        create_standalone_vpio > reuse_vpio_3 * 2,
        "Failed create_standalone_vpio={}s > reuse_vpio_3={}s * 2",
        create_standalone_vpio.as_secs_f32(),
        reuse_vpio_3.as_secs_f32()
    );
    let recycle_vpio_1 = t6 - t5;
    let recycle_vpio_2 = t7 - t6;
    let recycle_vpio_3 = t8 - t7;
    let recycle_vpio_4 = t9 - t8;
    let dispose_vpios = t10 - t9;
    assert!(
        dispose_vpios > recycle_vpio_1 * 2,
        "Failed dispose_vpios={}s > recycle_vpio_1 ={}s * 2",
        dispose_vpios.as_secs_f32(),
        recycle_vpio_1.as_secs_f32()
    );
    assert!(
        dispose_vpios > recycle_vpio_2 * 2,
        "Failed dispose_vpios={}s > recycle_vpio_2 ={}s * 2",
        dispose_vpios.as_secs_f32(),
        recycle_vpio_2.as_secs_f32()
    );
    assert!(
        dispose_vpios > recycle_vpio_3 * 2,
        "Failed dispose_vpios={}s > recycle_vpio_3 ={}s * 2",
        dispose_vpios.as_secs_f32(),
        recycle_vpio_3.as_secs_f32()
    );
    assert!(
        dispose_vpios > recycle_vpio_4 * 2,
        "Failed dispose_vpios={}s > recycle_vpio_4 ={}s * 2",
        dispose_vpios.as_secs_f32(),
        recycle_vpio_4.as_secs_f32()
    );
}
#[test]
#[ignore]
fn test_ops_timing_sensitive_multiple_duplex_voice_stream_start() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_ops_context_operation("multiple duplex voice streams", |context_ptr| {
        let start = Instant::now();
        // First stream uses vpio, creates the shared vpio unit.
        test_default_duplex_voice_stream_operation_on_context(
            "multiple duplex voice streams: stream 1",
            context_ptr,
            |stream| {
                let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                assert!(stm.core_stream_data.using_voice_processing_unit());
                assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
            },
        );
        let d1 = start.elapsed();
        // Second stream uses vpio, allows reuse of the one already created.
        test_default_duplex_voice_stream_operation_on_context(
            "multiple duplex voice streams: stream 2",
            context_ptr,
            |stream| {
                let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                assert!(stm.core_stream_data.using_voice_processing_unit());
                assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
            },
        );
        let d2 = start.elapsed() - d1;
        // d1 being significantly longer than d2 is proof we reuse vpio.
        assert!(
            d1 > d2 * 2,
            "Failed d1={}s > d2={}s * s",
            d1.as_secs_f32(),
            d2.as_secs_f32()
        );
    });
}
#[test]
#[ignore]
fn test_ops_timing_sensitive_multiple_duplex_voice_stream_params() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_ops_context_operation("multiple duplex voice streams with params", |context_ptr| {
        let start = Instant::now();
        // First stream uses vpio, creates the shared vpio unit.
        test_default_duplex_voice_stream_operation_on_context(
            "multiple duplex voice streams: stream 1",
            context_ptr,
            |stream| {
                let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                assert!(stm.core_stream_data.using_voice_processing_unit());
                assert_eq!(
                    unsafe {
                        OPS.stream_set_input_processing_params.unwrap()(
                            stream,
                            ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
                                | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
                        )
                    },
                    ffi::CUBEB_OK
                );
                assert_eq!(
                    unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
                    ffi::CUBEB_OK
                );
            },
        );
        let d1 = start.elapsed();
        // Second stream uses vpio, allows reuse of the one already created.
        test_default_duplex_voice_stream_operation_on_context(
            "multiple duplex voice streams: stream 2",
            context_ptr,
            |stream| {
                let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
                assert!(stm.core_stream_data.using_voice_processing_unit());
                assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
                let queue = stm.queue.clone();
                // Test that input processing params does not carry over when reusing vpio.
                let mut bypass: u32 = 0;
                let r = queue
                    .run_sync(|| {
                        audio_unit_get_property(
                            stm.core_stream_data.input_unit,
                            kAUVoiceIOProperty_BypassVoiceProcessing,
                            kAudioUnitScope_Global,
                            AU_IN_BUS,
                            &mut bypass,
                            &mut mem::size_of::<u32>(),
                        )
                    })
                    .unwrap();
                assert_eq!(r, NO_ERR);
                assert_eq!(bypass, 1);
                // Test that input mute state does not carry over when reusing vpio.
                let mut mute: u32 = 0;
                let r = queue
                    .run_sync(|| {
                        audio_unit_get_property(
                            stm.core_stream_data.input_unit,
                            kAUVoiceIOProperty_MuteOutput,
                            kAudioUnitScope_Global,
                            AU_IN_BUS,
                            &mut mute,
                            &mut mem::size_of::<u32>(),
                        )
                    })
                    .unwrap();
                assert_eq!(r, NO_ERR);
                assert_eq!(mute, 0);
            },
        );
        let d2 = start.elapsed() - d1;
        // d1 being significantly longer than d2 is proof we reuse vpio.
        assert!(
            d1 > d2 * 2,
            "Failed d1={}s > d2={}s * 2",
            d1.as_secs_f32(),
            d2.as_secs_f32()
        );
    });
}
#[test]
fn test_ops_duplex_voice_stream_set_input_mute() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_default_duplex_voice_stream_operation("duplex voice stream: mute", |stream| {
        assert_eq!(
            unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
            ffi::CUBEB_OK
        );
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert!(stm.core_stream_data.using_voice_processing_unit());
    });
}
#[test]
fn test_ops_duplex_voice_stream_set_input_mute_before_start() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_default_duplex_voice_stream_operation(
        "duplex voice stream: mute before start",
        |stream| {
            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);
            let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
            assert!(stm.core_stream_data.using_voice_processing_unit());
        },
    );
}
#[test]
fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_default_duplex_voice_stream_operation(
        "duplex voice stream: mute before start with reinit",
        |stream| {
            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) };
            assert!(stm.core_stream_data.using_voice_processing_unit());
            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!(mute_after_reinit);
        },
    );
}
#[test]
fn test_ops_duplex_voice_stream_set_input_mute_after_start() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_default_duplex_voice_stream_operation("duplex voice stream: mute after start", |stream| {
        assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
        assert_eq!(
            unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
            ffi::CUBEB_OK
        );
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert!(stm.core_stream_data.using_voice_processing_unit());
    });
}
#[test]
fn test_ops_duplex_voice_stream_set_input_processing_params() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_default_duplex_voice_stream_operation("duplex voice stream: processing", |stream| {
        let params: ffi::cubeb_input_processing_params =
            ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
                | 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
        );
        let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
        assert!(stm.core_stream_data.using_voice_processing_unit());
    });
}
#[test]
fn test_ops_duplex_voice_stream_set_input_processing_params_before_start() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_default_duplex_voice_stream_operation(
        "duplex voice stream: processing before start",
        |stream| {
            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);
            let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
            assert!(stm.core_stream_data.using_voice_processing_unit());
        },
    );
}
#[test]
fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_reinit() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_default_duplex_voice_stream_operation(
        "duplex voice stream: processing before start with reinit",
        |stream| {
            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) };
            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 =
                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 |= 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() {
    if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
        // We disable VPIO on Monterey.
        return;
    }
    test_default_duplex_voice_stream_operation(
        "duplex voice stream: processing after start",
        |stream| {
            let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
            assert!(stm.core_stream_data.using_voice_processing_unit());
            assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
            let params: ffi::cubeb_input_processing_params =
                ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
                    | 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| {
            let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
            assert_eq!(
                stm.core_stream_data.using_voice_processing_unit(),
                macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
            );
        },
    );
}
#[test]
fn test_ops_stereo_input_duplex_voice_stream_start() {
    test_stereo_input_duplex_voice_stream_operation(
        "stereo-input duplex voice stream: start",
        |stream| {
            let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
            assert_eq!(
                stm.core_stream_data.using_voice_processing_unit(),
                macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
            );
            assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
        },
    );
}
#[test]
fn test_ops_stereo_input_duplex_voice_stream_stop() {
    test_stereo_input_duplex_voice_stream_operation(
        "stereo-input duplex voice stream: stop",
        |stream| {
            let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
            assert_eq!(
                stm.core_stream_data.using_voice_processing_unit(),
                macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
            );
            assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
        },
    );
}