1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2013 Collabora Ltd.
5 Author: Arun Raghavan <arun.raghavan@collabora.co.uk>
6
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2.1 of the License,
10 or (at your option) any later version.
11
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <math.h>
26
27 #include <pulsecore/log.h>
28 #include <pulsecore/macro.h>
29 #include <pulsecore/core-util.h>
30
31 #include "lo-test-util.h"
32
33 /* Keep the frequency high so RMS over ranges of a few ms remains relatively
34 * high as well */
35 #define TONE_HZ 4410
36
nop_free_cb(void * p)37 static void nop_free_cb(void *p) {
38 }
39
underflow_cb(struct pa_stream * s,void * userdata)40 static void underflow_cb(struct pa_stream *s, void *userdata) {
41 pa_log_warn("Underflow");
42 }
43
overflow_cb(struct pa_stream * s,void * userdata)44 static void overflow_cb(struct pa_stream *s, void *userdata) {
45 pa_log_warn("Overlow");
46 }
47
48 /*
49 * We run a simple volume calibration so that we know we can detect the signal
50 * being played back. We start with the playback stream at 100% volume, and
51 * capture at 0.
52 *
53 * First, we then play a sine wave and increase the capture volume till the
54 * signal is clearly received.
55 *
56 * Next, we play back silence and make sure that the level is low enough to
57 * distinguish from when playback is happening.
58 *
59 * Finally, we hand off to the real read/write callbacks to run the actual
60 * test.
61 */
62
63 enum {
64 CALIBRATION_ONE,
65 CALIBRATION_ZERO,
66 CALIBRATION_DONE,
67 };
68
69 static int cal_state = CALIBRATION_ONE;
70
calibrate_write_cb(pa_stream * s,size_t nbytes,void * userdata)71 static void calibrate_write_cb(pa_stream *s, size_t nbytes, void *userdata) {
72 pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
73 int i, nsamp = nbytes / ctx->fs;
74 float tmp[nsamp][2];
75 static int count = 0;
76
77 /* Write out a sine tone */
78 for (i = 0; i < nsamp; i++)
79 tmp[i][0] = tmp[i][1] = cal_state == CALIBRATION_ONE ? sinf(count++ * TONE_HZ * 2 * M_PI / ctx->sample_spec.rate) : 0.0f;
80
81 pa_assert_se(pa_stream_write(s, &tmp, nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE) == 0);
82
83 if (cal_state == CALIBRATION_DONE)
84 pa_stream_set_write_callback(s, ctx->write_cb, ctx);
85 }
86
calibrate_read_cb(pa_stream * s,size_t nbytes,void * userdata)87 static void calibrate_read_cb(pa_stream *s, size_t nbytes, void *userdata) {
88 pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
89 static double v = 0;
90 static int skip = 0, confirm;
91
92 pa_cvolume vol;
93 pa_operation *o;
94 int nsamp;
95 float *in;
96 size_t l;
97
98 pa_assert_se(pa_stream_peek(s, (const void **)&in, &l) == 0);
99
100 nsamp = l / ctx->fs;
101
102 /* For each state or volume step change, throw out a few samples so we know
103 * we're seeing the changed samples. */
104 if (skip++ < 100)
105 goto out;
106 else
107 skip = 0;
108
109 switch (cal_state) {
110 case CALIBRATION_ONE:
111 /* Try to detect the sine wave. RMS is 0.5, */
112 if (pa_rms(in, nsamp) < 0.40f) {
113 confirm = 0;
114 v += 0.02f;
115
116 if (v > 1.0) {
117 pa_log_error("Capture signal too weak at 100%% volume (%g). Giving up.", pa_rms(in, nsamp));
118 pa_assert_not_reached();
119 }
120
121 pa_cvolume_set(&vol, ctx->sample_spec.channels, v * PA_VOLUME_NORM);
122 o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
123 pa_assert(o != NULL);
124 pa_operation_unref(o);
125 } else {
126 /* Make sure the signal strength is steadily above our threshold */
127 if (++confirm > 5) {
128 #if 0
129 pa_log_debug(stderr, "Capture volume = %g (%g)", v, pa_rms(in, nsamp));
130 #endif
131 cal_state = CALIBRATION_ZERO;
132 }
133 }
134
135 break;
136
137 case CALIBRATION_ZERO:
138 /* Now make sure silence doesn't trigger a false positive because
139 * of noise. */
140 if (pa_rms(in, nsamp) > 0.1f) {
141 pa_log_warn("Too much noise on capture (%g). Giving up.", pa_rms(in, nsamp));
142 pa_assert_not_reached();
143 }
144
145 cal_state = CALIBRATION_DONE;
146 pa_stream_set_read_callback(s, ctx->read_cb, ctx);
147
148 break;
149
150 default:
151 break;
152 }
153
154 out:
155 pa_stream_drop(s);
156 }
157
158 /* This routine is called whenever the stream state changes */
stream_state_callback(pa_stream * s,void * userdata)159 static void stream_state_callback(pa_stream *s, void *userdata) {
160 pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
161
162 switch (pa_stream_get_state(s)) {
163 case PA_STREAM_UNCONNECTED:
164 case PA_STREAM_CREATING:
165 case PA_STREAM_TERMINATED:
166 break;
167
168 case PA_STREAM_READY: {
169 pa_cvolume vol;
170 pa_operation *o;
171
172 /* Set volumes for calibration */
173 if (s == ctx->play_stream) {
174 pa_cvolume_set(&vol, ctx->sample_spec.channels, PA_VOLUME_NORM);
175 o = pa_context_set_sink_input_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
176 } else {
177 pa_cvolume_set(&vol, ctx->sample_spec.channels, pa_sw_volume_from_linear(0.0));
178 o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
179 }
180
181 if (!o) {
182 pa_log_error("Could not set stream volume: %s", pa_strerror(pa_context_errno(ctx->context)));
183 pa_assert_not_reached();
184 } else
185 pa_operation_unref(o);
186
187 break;
188 }
189
190 case PA_STREAM_FAILED:
191 default:
192 pa_log_error("Stream error: %s", pa_strerror(pa_context_errno(ctx->context)));
193 pa_assert_not_reached();
194 }
195 }
196
197 /* This is called whenever the context status changes */
context_state_callback(pa_context * c,void * userdata)198 static void context_state_callback(pa_context *c, void *userdata) {
199 pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
200 pa_mainloop_api *api;
201
202 switch (pa_context_get_state(c)) {
203 case PA_CONTEXT_CONNECTING:
204 case PA_CONTEXT_AUTHORIZING:
205 case PA_CONTEXT_SETTING_NAME:
206 break;
207
208 case PA_CONTEXT_READY: {
209 pa_buffer_attr buffer_attr;
210
211 pa_thread_make_realtime(4);
212
213 /* Create playback stream */
214 buffer_attr.maxlength = -1;
215 buffer_attr.tlength = ctx->sample_spec.rate * ctx->fs * ctx->play_latency / 1000;
216 buffer_attr.prebuf = 0; /* Setting prebuf to 0 guarantees us the stream will run synchronously, no matter what */
217 buffer_attr.minreq = -1;
218 buffer_attr.fragsize = -1;
219
220 ctx->play_stream = pa_stream_new(c, "loopback: play", &ctx->sample_spec, NULL);
221 pa_assert(ctx->play_stream != NULL);
222 pa_stream_set_state_callback(ctx->play_stream, stream_state_callback, ctx);
223 pa_stream_set_write_callback(ctx->play_stream, calibrate_write_cb, ctx);
224 pa_stream_set_underflow_callback(ctx->play_stream, underflow_cb, userdata);
225
226 pa_stream_connect_playback(ctx->play_stream, getenv("TEST_SINK"), &buffer_attr,
227 PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
228
229 /* Create capture stream */
230 buffer_attr.maxlength = -1;
231 buffer_attr.tlength = (uint32_t) -1;
232 buffer_attr.prebuf = 0;
233 buffer_attr.minreq = (uint32_t) -1;
234 buffer_attr.fragsize = ctx->sample_spec.rate * ctx->fs * ctx->rec_latency / 1000;
235
236 ctx->rec_stream = pa_stream_new(c, "loopback: rec", &ctx->sample_spec, NULL);
237 pa_assert(ctx->rec_stream != NULL);
238 pa_stream_set_state_callback(ctx->rec_stream, stream_state_callback, ctx);
239 pa_stream_set_read_callback(ctx->rec_stream, calibrate_read_cb, ctx);
240 pa_stream_set_overflow_callback(ctx->rec_stream, overflow_cb, userdata);
241
242 pa_stream_connect_record(ctx->rec_stream, getenv("TEST_SOURCE"), &buffer_attr,
243 PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);
244
245 break;
246 }
247
248 case PA_CONTEXT_TERMINATED:
249 api = pa_mainloop_get_api(ctx->mainloop);
250 api->quit(api, 0);
251 break;
252
253 case PA_CONTEXT_FAILED:
254 default:
255 pa_log_error("Context error: %s", pa_strerror(pa_context_errno(c)));
256 pa_assert_not_reached();
257 }
258 }
259
pa_lo_test_init(pa_lo_test_context * ctx)260 int pa_lo_test_init(pa_lo_test_context *ctx) {
261 /* FIXME: need to deal with non-float samples at some point */
262 pa_assert(ctx->sample_spec.format == PA_SAMPLE_FLOAT32);
263
264 ctx->ss = pa_sample_size(&ctx->sample_spec);
265 ctx->fs = pa_frame_size(&ctx->sample_spec);
266
267 ctx->mainloop = pa_mainloop_new();
268 ctx->context = pa_context_new(pa_mainloop_get_api(ctx->mainloop), ctx->context_name);
269
270 pa_context_set_state_callback(ctx->context, context_state_callback, ctx);
271
272 /* Connect the context */
273 if (pa_context_connect(ctx->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) {
274 pa_log_error("pa_context_connect() failed.");
275 goto quit;
276 }
277
278 return 0;
279
280 quit:
281 pa_context_unref(ctx->context);
282 pa_mainloop_free(ctx->mainloop);
283
284 return -1;
285 }
286
pa_lo_test_run(pa_lo_test_context * ctx)287 int pa_lo_test_run(pa_lo_test_context *ctx) {
288 int ret;
289
290 if (pa_mainloop_run(ctx->mainloop, &ret) < 0) {
291 pa_log_error("pa_mainloop_run() failed.");
292 return -1;
293 }
294
295 return 0;
296 }
297
pa_lo_test_deinit(pa_lo_test_context * ctx)298 void pa_lo_test_deinit(pa_lo_test_context *ctx) {
299 if (ctx->play_stream) {
300 pa_stream_disconnect(ctx->play_stream);
301 pa_stream_unref(ctx->play_stream);
302 }
303
304 if (ctx->rec_stream) {
305 pa_stream_disconnect(ctx->rec_stream);
306 pa_stream_unref(ctx->rec_stream);
307 }
308
309 if (ctx->context)
310 pa_context_unref(ctx->context);
311
312 if (ctx->mainloop)
313 pa_mainloop_free(ctx->mainloop);
314 }
315
pa_rms(const float * s,int n)316 float pa_rms(const float *s, int n) {
317 float sq = 0;
318 int i;
319
320 for (i = 0; i < n; i++)
321 sq += s[i] * s[i];
322
323 return sqrtf(sq / n);
324 }
325