• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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