Source code

Revision control

Copy as Markdown

Other Tools

use super::utils::{test_get_default_device, test_ops_stream_operation, Scope};
use super::*;
use std::sync::atomic::AtomicI64;
#[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.45 * (t1 + t2));
closure.phase += 1;
}
nframes
}
fn f32_to_i16_sample(x: f32) -> i16 {
(x * f32::from(i16::max_value())) as i16
}
}