1 use super::utils::{test_get_default_device, test_ops_stream_operation, Scope};
2 use super::*;
3 use std::sync::atomic::{AtomicI64, Ordering};
4
5 #[test]
test_dial_tone()6 fn test_dial_tone() {
7 use std::f32::consts::PI;
8 use std::thread;
9 use std::time::Duration;
10
11 const SAMPLE_FREQUENCY: u32 = 48_000;
12
13 // Do nothing if there is no available output device.
14 if test_get_default_device(Scope::Output).is_none() {
15 println!("No output device.");
16 return;
17 }
18
19 // Make sure the parameters meet the requirements of AudioUnitContext::stream_init
20 // (in the comments).
21 let mut output_params = ffi::cubeb_stream_params::default();
22 output_params.format = ffi::CUBEB_SAMPLE_S16NE;
23 output_params.rate = SAMPLE_FREQUENCY;
24 output_params.channels = 1;
25 output_params.layout = ffi::CUBEB_LAYOUT_MONO;
26 output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
27
28 struct Closure {
29 buffer_size: AtomicI64,
30 phase: i64,
31 };
32 let mut closure = Closure {
33 buffer_size: AtomicI64::new(0),
34 phase: 0,
35 };
36 let closure_ptr = &mut closure as *mut Closure as *mut c_void;
37
38 test_ops_stream_operation(
39 "stream: North American dial tone",
40 ptr::null_mut(), // Use default input device.
41 ptr::null_mut(), // No input parameters.
42 ptr::null_mut(), // Use default output device.
43 &mut output_params,
44 4096, // TODO: Get latency by get_min_latency instead ?
45 Some(data_callback),
46 Some(state_callback),
47 closure_ptr,
48 |stream| {
49 assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
50
51 #[derive(Debug)]
52 enum State {
53 WaitingForStart,
54 PositionIncreasing,
55 Paused,
56 Resumed,
57 End,
58 };
59 let mut state = State::WaitingForStart;
60 let mut position: u64 = 0;
61 let mut prev_position: u64 = 0;
62 let mut count = 0;
63 const CHECK_COUNT: i32 = 10;
64 loop {
65 thread::sleep(Duration::from_millis(50));
66 assert_eq!(
67 unsafe { OPS.stream_get_position.unwrap()(stream, &mut position) },
68 ffi::CUBEB_OK
69 );
70 println!(
71 "State: {:?}, position: {}, previous position: {}",
72 state, position, prev_position
73 );
74 match &mut state {
75 State::WaitingForStart => {
76 // It's expected to have 0 for a few iterations here: the stream can take
77 // some time to start.
78 if position != prev_position {
79 assert!(position > prev_position);
80 prev_position = position;
81 state = State::PositionIncreasing;
82 }
83 }
84 State::PositionIncreasing => {
85 // wait a few iterations, check monotony
86 if position != prev_position {
87 assert!(position > prev_position);
88 prev_position = position;
89 count += 1;
90 if count > CHECK_COUNT {
91 state = State::Paused;
92 count = 0;
93 assert_eq!(
94 unsafe { OPS.stream_stop.unwrap()(stream) },
95 ffi::CUBEB_OK
96 );
97 // Update the position once paused.
98 assert_eq!(
99 unsafe {
100 OPS.stream_get_position.unwrap()(stream, &mut position)
101 },
102 ffi::CUBEB_OK
103 );
104 prev_position = position;
105 }
106 }
107 }
108 State::Paused => {
109 // The cubeb_stream_stop call above should synchrously stop the callbacks,
110 // hence the clock, the assert below must always holds, modulo the client
111 // side interpolation.
112 assert!(
113 position == prev_position
114 || position - prev_position
115 <= closure.buffer_size.load(Ordering::SeqCst) as u64
116 );
117 count += 1;
118 prev_position = position;
119 if count > CHECK_COUNT {
120 state = State::Resumed;
121 count = 0;
122 assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
123 }
124 }
125 State::Resumed => {
126 // wait a few iterations, this can take some time to start
127 if position != prev_position {
128 assert!(position > prev_position);
129 prev_position = position;
130 count += 1;
131 if count > CHECK_COUNT {
132 state = State::End;
133 count = 0;
134 assert_eq!(
135 unsafe { OPS.stream_stop.unwrap()(stream) },
136 ffi::CUBEB_OK
137 );
138 assert_eq!(
139 unsafe {
140 OPS.stream_get_position.unwrap()(stream, &mut position)
141 },
142 ffi::CUBEB_OK
143 );
144 prev_position = position;
145 }
146 }
147 }
148 State::End => {
149 // The cubeb_stream_stop call above should synchrously stop the callbacks,
150 // hence the clock, the assert below must always holds, modulo the client
151 // side interpolation.
152 assert!(
153 position == prev_position
154 || position - prev_position
155 <= closure.buffer_size.load(Ordering::SeqCst) as u64
156 );
157 if position == prev_position {
158 count += 1;
159 if count > CHECK_COUNT {
160 break;
161 }
162 }
163 }
164 }
165 }
166 assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
167 },
168 );
169
170 extern "C" fn state_callback(
171 stream: *mut ffi::cubeb_stream,
172 user_ptr: *mut c_void,
173 state: ffi::cubeb_state,
174 ) {
175 assert!(!stream.is_null());
176 assert!(!user_ptr.is_null());
177 assert_ne!(state, ffi::CUBEB_STATE_ERROR);
178 }
179
180 extern "C" fn data_callback(
181 stream: *mut ffi::cubeb_stream,
182 user_ptr: *mut c_void,
183 _input_buffer: *const c_void,
184 output_buffer: *mut c_void,
185 nframes: i64,
186 ) -> i64 {
187 assert!(!stream.is_null());
188 assert!(!user_ptr.is_null());
189 assert!(!output_buffer.is_null());
190
191 let buffer = unsafe {
192 let ptr = output_buffer as *mut i16;
193 let len = nframes as usize;
194 slice::from_raw_parts_mut(ptr, len)
195 };
196
197 let closure = unsafe { &mut *(user_ptr as *mut Closure) };
198
199 closure.buffer_size.store(nframes, Ordering::SeqCst);
200
201 // Generate tone on the fly.
202 for data in buffer.iter_mut() {
203 let t1 = (2.0 * PI * 350.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin();
204 let t2 = (2.0 * PI * 440.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin();
205 *data = f32_to_i16_sample(0.5 * (t1 + t2));
206 closure.phase += 1;
207 }
208
209 nframes
210 }
211
212 fn f32_to_i16_sample(x: f32) -> i16 {
213 (x * f32::from(i16::max_value())) as i16
214 }
215 }
216