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