diff options
Diffstat (limited to 'third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs')
-rw-r--r-- | third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs new file mode 100644 index 0000000000..42cb9ee997 --- /dev/null +++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs @@ -0,0 +1,215 @@ +use super::utils::{test_get_default_device, test_ops_stream_operation, Scope}; +use super::*; +use std::sync::atomic::{AtomicI64, Ordering}; + +#[test] +fn test_dial_tone() { + use std::f32::consts::PI; + use std::thread; + use std::time::Duration; + + const SAMPLE_FREQUENCY: u32 = 48_000; + + // Do nothing if there is no available output device. + if test_get_default_device(Scope::Output).is_none() { + println!("No output device."); + return; + } + + // Make sure the parameters meet the requirements of AudioUnitContext::stream_init + // (in the comments). + let mut output_params = ffi::cubeb_stream_params::default(); + output_params.format = ffi::CUBEB_SAMPLE_S16NE; + output_params.rate = SAMPLE_FREQUENCY; + output_params.channels = 1; + output_params.layout = ffi::CUBEB_LAYOUT_MONO; + output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE; + + struct Closure { + buffer_size: AtomicI64, + phase: i64, + } + let mut closure = Closure { + buffer_size: AtomicI64::new(0), + phase: 0, + }; + let closure_ptr = &mut closure as *mut Closure as *mut c_void; + + test_ops_stream_operation( + "stream: North American dial tone", + ptr::null_mut(), // Use default input device. + ptr::null_mut(), // No input parameters. + ptr::null_mut(), // Use default output device. + &mut output_params, + 4096, // TODO: Get latency by get_min_latency instead ? + Some(data_callback), + Some(state_callback), + closure_ptr, + |stream| { + assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + + #[derive(Debug)] + enum State { + WaitingForStart, + PositionIncreasing, + Paused, + Resumed, + End, + } + let mut state = State::WaitingForStart; + let mut position: u64 = 0; + let mut prev_position: u64 = 0; + let mut count = 0; + const CHECK_COUNT: i32 = 10; + loop { + thread::sleep(Duration::from_millis(50)); + assert_eq!( + unsafe { OPS.stream_get_position.unwrap()(stream, &mut position) }, + ffi::CUBEB_OK + ); + println!( + "State: {:?}, position: {}, previous position: {}", + state, position, prev_position + ); + match &mut state { + State::WaitingForStart => { + // It's expected to have 0 for a few iterations here: the stream can take + // some time to start. + if position != prev_position { + assert!(position > prev_position); + prev_position = position; + state = State::PositionIncreasing; + } + } + State::PositionIncreasing => { + // wait a few iterations, check monotony + if position != prev_position { + assert!(position > prev_position); + prev_position = position; + count += 1; + if count > CHECK_COUNT { + state = State::Paused; + count = 0; + assert_eq!( + unsafe { OPS.stream_stop.unwrap()(stream) }, + ffi::CUBEB_OK + ); + // Update the position once paused. + assert_eq!( + unsafe { + OPS.stream_get_position.unwrap()(stream, &mut position) + }, + ffi::CUBEB_OK + ); + prev_position = position; + } + } + } + State::Paused => { + // The cubeb_stream_stop call above should synchrously stop the callbacks, + // hence the clock, the assert below must always holds, modulo the client + // side interpolation. + assert!( + position == prev_position + || position - prev_position + <= closure.buffer_size.load(Ordering::SeqCst) as u64 + ); + count += 1; + prev_position = position; + if count > CHECK_COUNT { + state = State::Resumed; + count = 0; + assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK); + } + } + State::Resumed => { + // wait a few iterations, this can take some time to start + if position != prev_position { + assert!(position > prev_position); + prev_position = position; + count += 1; + if count > CHECK_COUNT { + state = State::End; + count = 0; + assert_eq!( + unsafe { OPS.stream_stop.unwrap()(stream) }, + ffi::CUBEB_OK + ); + assert_eq!( + unsafe { + OPS.stream_get_position.unwrap()(stream, &mut position) + }, + ffi::CUBEB_OK + ); + prev_position = position; + } + } + } + State::End => { + // The cubeb_stream_stop call above should synchrously stop the callbacks, + // hence the clock, the assert below must always holds, modulo the client + // side interpolation. + assert!( + position == prev_position + || position - prev_position + <= closure.buffer_size.load(Ordering::SeqCst) as u64 + ); + if position == prev_position { + count += 1; + if count > CHECK_COUNT { + break; + } + } + } + } + } + assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK); + }, + ); + + extern "C" fn state_callback( + stream: *mut ffi::cubeb_stream, + user_ptr: *mut c_void, + state: ffi::cubeb_state, + ) { + assert!(!stream.is_null()); + assert!(!user_ptr.is_null()); + assert_ne!(state, ffi::CUBEB_STATE_ERROR); + } + + extern "C" fn data_callback( + stream: *mut ffi::cubeb_stream, + user_ptr: *mut c_void, + _input_buffer: *const c_void, + output_buffer: *mut c_void, + nframes: i64, + ) -> i64 { + assert!(!stream.is_null()); + assert!(!user_ptr.is_null()); + assert!(!output_buffer.is_null()); + + let buffer = unsafe { + let ptr = output_buffer as *mut i16; + let len = nframes as usize; + slice::from_raw_parts_mut(ptr, len) + }; + + let closure = unsafe { &mut *(user_ptr as *mut Closure) }; + + closure.buffer_size.store(nframes, Ordering::SeqCst); + + // Generate tone on the fly. + for data in buffer.iter_mut() { + let t1 = (2.0 * PI * 350.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin(); + let t2 = (2.0 * PI * 440.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin(); + *data = f32_to_i16_sample(0.5 * (t1 + t2)); + closure.phase += 1; + } + + nframes + } + + fn f32_to_i16_sample(x: f32) -> i16 { + (x * f32::from(i16::max_value())) as i16 + } +} |