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