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
37static void nop_free_cb(void *p) {
38}
39
40static void underflow_cb(struct pa_stream *s, void *userdata) {
41    pa_log_warn("Underflow");
42}
43
44static 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
63enum {
64    CALIBRATION_ONE,
65    CALIBRATION_ZERO,
66    CALIBRATION_DONE,
67};
68
69static int cal_state = CALIBRATION_ONE;
70
71static 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
87static 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
154out:
155    pa_stream_drop(s);
156}
157
158/* This routine is called whenever the stream state changes */
159static 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 */
198static 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
260int 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
280quit:
281    pa_context_unref(ctx->context);
282    pa_mainloop_free(ctx->mainloop);
283
284    return -1;
285}
286
287int 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
298void 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
316float 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