summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs
blob: 5f542861534a956777ff9f15dcc9afdd5ea8a86f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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
    }
}