This moves over setup code for the loopback latency test into a private library so that we can easily write more tests using the same framework. --- src/Makefile.am | 9 +- src/tests/lo-latency-test.c | 330 +++++--------------------------------------- src/tests/lo-test-util.c | 328 +++++++++++++++++++++++++++++++++++++++++++ src/tests/lo-test-util.h | 57 ++++++++ 4 files changed, 424 insertions(+), 300 deletions(-) create mode 100644 src/tests/lo-test-util.c create mode 100644 src/tests/lo-test-util.h diff --git a/src/Makefile.am b/src/Makefile.am index 163976e..44c191d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -223,6 +223,7 @@ pax11publish_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) ################################### # Test programs # ################################### +noinst_LTLIBRARIES = TESTS_default = \ mainloop-test \ @@ -575,8 +576,13 @@ echo_cancel_test_CXXFLAGS = $(module_echo_cancel_la_CXXFLAGS) -DECHO_CANCEL_TEST endif echo_cancel_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) +liblo_test_util_la_SOURCES = tests/lo-test-util.h tests/lo-test-util.c +liblo_test_util_la_LIBADD = libpulsecore- at PA_MAJORMINOR@.la +liblo_test_util_la_LDFLAGS = -avoid-version +noinst_LTLIBRARIES += liblo-test-util.la + lo_latency_test_SOURCES = tests/lo-latency-test.c -lo_latency_test_LDADD = $(AM_LDADD) libpulse.la +lo_latency_test_LDADD = $(AM_LDADD) libpulse.la liblo-test-util.la lo_latency_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS) lo_latency_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS) @@ -855,7 +861,6 @@ libpulsedsp_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version -disable-static ################################### lib_LTLIBRARIES += libpulsecore- at PA_MAJORMINOR@.la -noinst_LTLIBRARIES = # Pure core stuff libpulsecore_ at PA_MAJORMINOR@_la_SOURCES = \ diff --git a/src/tests/lo-latency-test.c b/src/tests/lo-latency-test.c index 8f3b04d..124693d 100644 --- a/src/tests/lo-latency-test.c +++ b/src/tests/lo-latency-test.c @@ -32,62 +32,33 @@ #include <unistd.h> #include <stdio.h> #include <stdlib.h> -#include <math.h> #include <check.h> -#include <pulse/pulseaudio.h> -#include <pulse/mainloop.h> - -/* for pa_make_realtime */ -#include <pulsecore/core-util.h> +#include "lo-test-util.h" #define SAMPLE_HZ 44100 #define CHANNELS 2 #define N_OUT (SAMPLE_HZ * 1) -#define TONE_HZ (SAMPLE_HZ / 100) -#define PLAYBACK_LATENCY 25 /* ms */ -#define CAPTURE_LATENCY 5 /* ms */ - -static pa_context *context = NULL; -static pa_stream *pstream, *rstream; -static pa_mainloop_api *mainloop_api = NULL; -static const char *context_name = NULL; - static float out[N_OUT][CHANNELS]; -static int ppos = 0; -static int n_underflow = 0; -static int n_overflow = 0; +pa_lo_test_context test_ctx; +static const char *context_name = NULL; static struct timeval tv_out, tv_in; -static const pa_sample_spec sample_spec = { - .format = PA_SAMPLE_FLOAT32, - .rate = SAMPLE_HZ, - .channels = CHANNELS, -}; -static int ss, fs; - -static void nop_free_cb(void *p) {} - -static void underflow_cb(struct pa_stream *s, void *userdata) { - fprintf(stderr, "Underflow\n"); - n_underflow++; -} - -static void overflow_cb(struct pa_stream *s, void *userdata) { - fprintf(stderr, "Overlow\n"); - n_overflow++; +static void nop_free_cb(void *p) { } static void write_cb(pa_stream *s, size_t nbytes, void *userdata) { - int r, nsamp = nbytes / fs; + pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; + static int ppos = 0; + int r, nsamp = nbytes / ctx->fs; if (ppos + nsamp > N_OUT) { - r = pa_stream_write(s, &out[ppos][0], (N_OUT - ppos) * fs, nop_free_cb, 0, PA_SEEK_RELATIVE); - nbytes -= (N_OUT - ppos) * fs; + r = pa_stream_write(s, &out[ppos][0], (N_OUT - ppos) * ctx->fs, nop_free_cb, 0, PA_SEEK_RELATIVE); + nbytes -= (N_OUT - ppos) * ctx->fs; ppos = 0; } @@ -97,22 +68,13 @@ static void write_cb(pa_stream *s, size_t nbytes, void *userdata) { r = pa_stream_write(s, &out[ppos][0], nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE); fail_unless(r == 0); - ppos = (ppos + nbytes / fs) % N_OUT; -} - -static inline float rms(const float *s, int n) { - float sq = 0; - int i; - - for (i = 0; i < n; i++) - sq += s[i] * s[i]; - - return sqrtf(sq / n); + ppos = (ppos + nbytes / ctx->fs) % N_OUT; } #define WINDOW (2 * CHANNELS) static void read_cb(pa_stream *s, size_t nbytes, void *userdata) { + pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; static float last = 0.0f; const float *in; float cur; @@ -143,16 +105,16 @@ static void read_cb(pa_stream *s, size_t nbytes, void *userdata) { #if 0 { int j; - fprintf(stderr, "%g (", rms(in, WINDOW)); + fprintf(stderr, "%g (", pa_rms(in, WINDOW)); for (j = 0; j < WINDOW; j++) fprintf(stderr, "%g ", in[j]); fprintf(stderr, ")\n"); } #endif - if (i + (ss * WINDOW) < l) - cur = rms(in, WINDOW); + if (i + (ctx->ss * WINDOW) < l) + cur = pa_rms(in, WINDOW); else - cur = rms(in, (l - i)/ss); + cur = pa_rms(in, (l - i) / ctx->ss); /* We leave the definition of 0 generous since the window might * straddle the 0->1 transition, raising the average power. We keep the @@ -165,223 +127,26 @@ static void read_cb(pa_stream *s, size_t nbytes, void *userdata) { last = cur; in += WINDOW; - i += ss * WINDOW; - } while (i + (ss * WINDOW) <= l); - - pa_stream_drop(s); -} - -/* - * We run a simple volume calibration so that we know we can detect the signal - * being played back. We start with the playback stream at 100% volume, and - * capture at 0. - * - * First, we then play a sine wave and increase the capture volume till the - * signal is clearly received. - * - * Next, we play back silence and make sure that the level is low enough to - * distinguish from when playback is happening. - * - * Finally, we hand off to the real read/write callbacks to run the actual - * test. - */ - -enum { - CALIBRATION_ONE, - CALIBRATION_ZERO, - CALIBRATION_DONE, -}; - -static int cal_state = CALIBRATION_ONE; - -static void calibrate_write_cb(pa_stream *s, size_t nbytes, void *userdata) { - int i, r, nsamp = nbytes / fs; - float tmp[nsamp][2]; - static int count = 0; - - /* Write out a sine tone */ - for (i = 0; i < nsamp; i++) - tmp[i][0] = tmp[i][1] = cal_state == CALIBRATION_ONE ? sinf(count++ * TONE_HZ * 2 * M_PI / SAMPLE_HZ) : 0.0f; - - r = pa_stream_write(s, &tmp, nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE); - fail_unless(r == 0); - - if (cal_state == CALIBRATION_DONE) - pa_stream_set_write_callback(s, write_cb, NULL); -} - -static void calibrate_read_cb(pa_stream *s, size_t nbytes, void *userdata) { - static double v = 0; - static int skip = 0, confirm; - - pa_cvolume vol; - pa_operation *o; - int r, nsamp; - float *in; - size_t l; - - r = pa_stream_peek(s, (const void **)&in, &l); - fail_unless(r == 0); - - nsamp = l / fs; - - /* For each state or volume step change, throw out a few samples so we know - * we're seeing the changed samples. */ - if (skip++ < 100) - goto out; - else - skip = 0; - - switch (cal_state) { - case CALIBRATION_ONE: - /* Try to detect the sine wave. RMS is 0.5, */ - if (rms(in, nsamp) < 0.40f) { - confirm = 0; - v += 0.02f; - - if (v > 1.0) { - fprintf(stderr, "Capture signal too weak at 100%% volume (%g). Giving up.\n", rms(in, nsamp)); - fail(); - } - - pa_cvolume_set(&vol, CHANNELS, v * PA_VOLUME_NORM); - o = pa_context_set_source_output_volume(context, pa_stream_get_index(s), &vol, NULL, NULL); - fail_if(o == NULL); - pa_operation_unref(o); - } else { - /* Make sure the signal strength is steadily above our threshold */ - if (++confirm > 5) { -#if 0 - fprintf(stderr, "Capture volume = %g (%g)\n", v, rms(in, nsamp)); -#endif - cal_state = CALIBRATION_ZERO; - } - } - - break; - - case CALIBRATION_ZERO: - /* Now make sure silence doesn't trigger a false positive because - * of noise. */ - if (rms(in, nsamp) > 0.1f) { - fprintf(stderr, "Too much noise on capture (%g). Giving up.\n", rms(in, nsamp)); - fail(); - } - - cal_state = CALIBRATION_DONE; - pa_stream_set_read_callback(s, read_cb, NULL); - - break; - - default: - break; - } + i += ctx->ss * WINDOW; + } while (i + (ctx->ss * WINDOW) <= l); -out: pa_stream_drop(s); } -/* This routine is called whenever the stream state changes */ -static void stream_state_callback(pa_stream *s, void *userdata) { - switch (pa_stream_get_state(s)) { - case PA_STREAM_UNCONNECTED: - case PA_STREAM_CREATING: - case PA_STREAM_TERMINATED: - break; - - case PA_STREAM_READY: { - pa_cvolume vol; - pa_operation *o; - - /* Set volumes for calibration */ - if (!userdata) { - pa_cvolume_set(&vol, CHANNELS, PA_VOLUME_NORM); - o = pa_context_set_sink_input_volume(context, pa_stream_get_index(s), &vol, NULL, NULL); - } else { - pa_cvolume_set(&vol, CHANNELS, pa_sw_volume_from_linear(0.0)); - o = pa_context_set_source_output_volume(context, pa_stream_get_index(s), &vol, NULL, NULL); - } - - if (!o) { - fprintf(stderr, "Could not set stream volume: %s\n", pa_strerror(pa_context_errno(context))); - fail(); - } else - pa_operation_unref(o); - - break; - } - - case PA_STREAM_FAILED: - default: - fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); - fail(); - } -} +START_TEST (loopback_test) { + int i, pulse_hz = SAMPLE_HZ / 1000; -/* This is called whenever the context status changes */ -static void context_state_callback(pa_context *c, void *userdata) { - fail_unless(c != NULL); - - switch (pa_context_get_state(c)) { - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - - case PA_CONTEXT_READY: { - pa_buffer_attr buffer_attr; - - pa_make_realtime(4); - - /* Create playback stream */ - buffer_attr.maxlength = -1; - buffer_attr.tlength = SAMPLE_HZ * fs * PLAYBACK_LATENCY / 1000; - buffer_attr.prebuf = 0; /* Setting prebuf to 0 guarantees us the stream will run synchronously, no matter what */ - buffer_attr.minreq = -1; - buffer_attr.fragsize = -1; - - pstream = pa_stream_new(c, "loopback: play", &sample_spec, NULL); - fail_unless(pstream != NULL); - pa_stream_set_state_callback(pstream, stream_state_callback, (void *) 0); - pa_stream_set_write_callback(pstream, calibrate_write_cb, NULL); - pa_stream_set_underflow_callback(pstream, underflow_cb, userdata); - - pa_stream_connect_playback(pstream, getenv("TEST_SINK"), &buffer_attr, - PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); - - /* Create capture stream */ - buffer_attr.maxlength = -1; - buffer_attr.tlength = (uint32_t) -1; - buffer_attr.prebuf = 0; - buffer_attr.minreq = (uint32_t) -1; - buffer_attr.fragsize = SAMPLE_HZ * fs * CAPTURE_LATENCY / 1000; - - rstream = pa_stream_new(c, "loopback: rec", &sample_spec, NULL); - fail_unless(rstream != NULL); - pa_stream_set_state_callback(rstream, stream_state_callback, (void *) 1); - pa_stream_set_read_callback(rstream, calibrate_read_cb, NULL); - pa_stream_set_overflow_callback(rstream, overflow_cb, userdata); - - pa_stream_connect_record(rstream, getenv("TEST_SOURCE"), &buffer_attr, - PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); - - break; - } + test_ctx.context_name = context_name; - case PA_CONTEXT_TERMINATED: - mainloop_api->quit(mainloop_api, 0); - break; + test_ctx.sample_spec.format = PA_SAMPLE_FLOAT32, + test_ctx.sample_spec.rate = SAMPLE_HZ, + test_ctx.sample_spec.channels = CHANNELS, - case PA_CONTEXT_FAILED: - default: - fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); - fail(); - } -} + test_ctx.play_latency = 25; + test_ctx.rec_latency = 5; -START_TEST (loopback_test) { - pa_mainloop* m = NULL; - int i, ret = 0, pulse_hz = SAMPLE_HZ / 1000; + test_ctx.read_cb = read_cb; + test_ctx.write_cb = write_cb; /* Generate a square pulse */ for (i = 0; i < N_OUT; i++) @@ -390,40 +155,9 @@ START_TEST (loopback_test) { else out[i][0] = out[i][1] = 0.0f; - ss = pa_sample_size(&sample_spec); - fs = pa_frame_size(&sample_spec); - - pstream = NULL; - - /* Set up a new main loop */ - m = pa_mainloop_new(); - fail_unless(m != NULL); - - mainloop_api = pa_mainloop_get_api(m); - - context = pa_context_new(mainloop_api, context_name); - fail_unless(context != NULL); - - pa_context_set_state_callback(context, context_state_callback, NULL); - - /* Connect the context */ - if (pa_context_connect(context, NULL, 0, NULL) < 0) { - fprintf(stderr, "pa_context_connect() failed.\n"); - goto quit; - } - - if (pa_mainloop_run(m, &ret) < 0) - fprintf(stderr, "pa_mainloop_run() failed.\n"); - -quit: - pa_context_unref(context); - - if (pstream) - pa_stream_unref(pstream); - - pa_mainloop_free(m); - - fail_unless(ret == 0); + fail_unless(pa_lo_test_init(&test_ctx) == 0); + fail_unless(pa_lo_test_run(&test_ctx) == 0); + pa_lo_test_deinit(&test_ctx); } END_TEST @@ -435,8 +169,8 @@ int main(int argc, char *argv[]) { context_name = argv[0]; - s = suite_create("Loopback"); - tc = tcase_create("loopback"); + s = suite_create("Loopback latency"); + tc = tcase_create("loopback latency"); tcase_add_test(tc, loopback_test); tcase_set_timeout(tc, 5 * 60); suite_add_tcase(s, tc); diff --git a/src/tests/lo-test-util.c b/src/tests/lo-test-util.c new file mode 100644 index 0000000..01eb295 --- /dev/null +++ b/src/tests/lo-test-util.c @@ -0,0 +1,328 @@ +/*** + This file is part of PulseAudio. + + Copyright 2013 Collabora Ltd. + Author: Arun Raghavan <arun.raghavan at collabora.co.uk> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "lo-test-util.h" + +/* Keep the frequency high so RMS over ranges of a few ms remains relatively + * high as well */ +#define TONE_HZ 4410 + +static void nop_free_cb(void *p) { +} + +static void underflow_cb(struct pa_stream *s, void *userdata) { + pa_log_warn("Underflow\n"); +} + +static void overflow_cb(struct pa_stream *s, void *userdata) { + pa_log_warn("Overlow\n"); +} + +/* + * We run a simple volume calibration so that we know we can detect the signal + * being played back. We start with the playback stream at 100% volume, and + * capture at 0. + * + * First, we then play a sine wave and increase the capture volume till the + * signal is clearly received. + * + * Next, we play back silence and make sure that the level is low enough to + * distinguish from when playback is happening. + * + * Finally, we hand off to the real read/write callbacks to run the actual + * test. + */ + +enum { + CALIBRATION_ONE, + CALIBRATION_ZERO, + CALIBRATION_DONE, +}; + +static int cal_state = CALIBRATION_ONE; + +static void calibrate_write_cb(pa_stream *s, size_t nbytes, void *userdata) { + pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; + int i, r, nsamp = nbytes / ctx->fs; + float tmp[nsamp][2]; + static int count = 0; + + /* Write out a sine tone */ + for (i = 0; i < nsamp; i++) + tmp[i][0] = tmp[i][1] = cal_state == CALIBRATION_ONE ? sinf(count++ * TONE_HZ * 2 * M_PI / ctx->sample_spec.rate) : 0.0f; + + r = pa_stream_write(s, &tmp, nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE); + pa_assert(r == 0); + + if (cal_state == CALIBRATION_DONE) + pa_stream_set_write_callback(s, ctx->write_cb, ctx); +} + +static void calibrate_read_cb(pa_stream *s, size_t nbytes, void *userdata) { + pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; + static double v = 0; + static int skip = 0, confirm; + + pa_cvolume vol; + pa_operation *o; + int r, nsamp; + float *in; + size_t l; + + r = pa_stream_peek(s, (const void **)&in, &l); + pa_assert(r == 0); + + nsamp = l / ctx->fs; + + /* For each state or volume step change, throw out a few samples so we know + * we're seeing the changed samples. */ + if (skip++ < 100) + goto out; + else + skip = 0; + + switch (cal_state) { + case CALIBRATION_ONE: + /* Try to detect the sine wave. RMS is 0.5, */ + if (pa_rms(in, nsamp) < 0.40f) { + confirm = 0; + v += 0.02f; + + if (v > 1.0) { + pa_log_error("Capture signal too weak at 100%% volume (%g). Giving up.\n", pa_rms(in, nsamp)); + pa_assert_not_reached(); + } + + pa_cvolume_set(&vol, ctx->sample_spec.channels, v * PA_VOLUME_NORM); + o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL); + pa_assert(o != NULL); + pa_operation_unref(o); + } else { + /* Make sure the signal strength is steadily above our threshold */ + if (++confirm > 5) { +#if 0 + pa_log_debug(stderr, "Capture volume = %g (%g)\n", v, pa_rms(in, nsamp)); +#endif + cal_state = CALIBRATION_ZERO; + } + } + + break; + + case CALIBRATION_ZERO: + /* Now make sure silence doesn't trigger a false positive because + * of noise. */ + if (pa_rms(in, nsamp) > 0.1f) { + fprintf(stderr, "Too much noise on capture (%g). Giving up.\n", pa_rms(in, nsamp)); + pa_assert_not_reached(); + } + + cal_state = CALIBRATION_DONE; + pa_stream_set_read_callback(s, ctx->read_cb, ctx); + + break; + + default: + break; + } + +out: + pa_stream_drop(s); +} + +/* This routine is called whenever the stream state changes */ +static void stream_state_callback(pa_stream *s, void *userdata) { + pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; + + switch (pa_stream_get_state(s)) { + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_READY: { + pa_cvolume vol; + pa_operation *o; + + /* Set volumes for calibration */ + if (s == ctx->play_stream) { + pa_cvolume_set(&vol, ctx->sample_spec.channels, PA_VOLUME_NORM); + o = pa_context_set_sink_input_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL); + } else { + pa_cvolume_set(&vol, ctx->sample_spec.channels, pa_sw_volume_from_linear(0.0)); + o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL); + } + + if (!o) { + pa_log_error("Could not set stream volume: %s\n", pa_strerror(pa_context_errno(ctx->context))); + pa_assert_not_reached(); + } else + pa_operation_unref(o); + + break; + } + + case PA_STREAM_FAILED: + default: + pa_log_error("Stream error: %s\n", pa_strerror(pa_context_errno(ctx->context))); + pa_assert_not_reached(); + } +} + +/* This is called whenever the context status changes */ +static void context_state_callback(pa_context *c, void *userdata) { + pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; + pa_mainloop_api *api; + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: { + pa_buffer_attr buffer_attr; + + pa_make_realtime(4); + + /* Create playback stream */ + buffer_attr.maxlength = -1; + buffer_attr.tlength = ctx->sample_spec.rate * ctx->fs * ctx->play_latency / 1000; + buffer_attr.prebuf = 0; /* Setting prebuf to 0 guarantees us the stream will run synchronously, no matter what */ + buffer_attr.minreq = -1; + buffer_attr.fragsize = -1; + + ctx->play_stream = pa_stream_new(c, "loopback: play", &ctx->sample_spec, NULL); + pa_assert(ctx->play_stream != NULL); + pa_stream_set_state_callback(ctx->play_stream, stream_state_callback, ctx); + pa_stream_set_write_callback(ctx->play_stream, calibrate_write_cb, ctx); + pa_stream_set_underflow_callback(ctx->play_stream, underflow_cb, userdata); + + pa_stream_connect_playback(ctx->play_stream, getenv("TEST_SINK"), &buffer_attr, + PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + + /* Create capture stream */ + buffer_attr.maxlength = -1; + buffer_attr.tlength = (uint32_t) -1; + buffer_attr.prebuf = 0; + buffer_attr.minreq = (uint32_t) -1; + buffer_attr.fragsize = ctx->sample_spec.rate * ctx->fs * ctx->rec_latency / 1000; + + ctx->rec_stream = pa_stream_new(c, "loopback: rec", &ctx->sample_spec, NULL); + pa_assert(ctx->rec_stream != NULL); + pa_stream_set_state_callback(ctx->rec_stream, stream_state_callback, ctx); + pa_stream_set_read_callback(ctx->rec_stream, calibrate_read_cb, ctx); + pa_stream_set_overflow_callback(ctx->rec_stream, overflow_cb, userdata); + + pa_stream_connect_record(ctx->rec_stream, getenv("TEST_SOURCE"), &buffer_attr, + PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); + + break; + } + + case PA_CONTEXT_TERMINATED: + api = pa_mainloop_get_api(ctx->mainloop); + api->quit(api, 0); + break; + + case PA_CONTEXT_FAILED: + default: + pa_log_error("Context error: %s\n", pa_strerror(pa_context_errno(c))); + pa_assert_not_reached(); + } +} + +int pa_lo_test_init(pa_lo_test_context *ctx) { + /* FIXME: need to deal with non-float samples at some point */ + pa_assert(ctx->sample_spec.format == PA_SAMPLE_FLOAT32); + + ctx->ss = pa_sample_size(&ctx->sample_spec); + ctx->fs = pa_frame_size(&ctx->sample_spec); + + ctx->mainloop = pa_mainloop_new(); + ctx->context = pa_context_new(pa_mainloop_get_api(ctx->mainloop), ctx->context_name); + + pa_context_set_state_callback(ctx->context, context_state_callback, ctx); + + /* Connect the context */ + if (pa_context_connect(ctx->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_log_error("pa_context_connect() failed.\n"); + goto quit; + } + + return 0; + +quit: + pa_context_unref(ctx->context); + pa_mainloop_free(ctx->mainloop); + + return -1; +} + +int pa_lo_test_run(pa_lo_test_context *ctx) { + int ret; + + if (pa_mainloop_run(ctx->mainloop, &ret) < 0) { + pa_log_error("pa_mainloop_run() failed.\n"); + return -1; + } + + return 0; +} + +void pa_lo_test_deinit(pa_lo_test_context *ctx) { + if (ctx->play_stream) { + pa_stream_disconnect(ctx->play_stream); + pa_stream_unref(ctx->play_stream); + } + + if (ctx->rec_stream) { + pa_stream_disconnect(ctx->rec_stream); + pa_stream_unref(ctx->rec_stream); + } + + if (ctx->context) + pa_context_unref(ctx->context); + + if (ctx->mainloop) + pa_mainloop_free(ctx->mainloop); +} + +float pa_rms(const float *s, int n) { + float sq = 0; + int i; + + for (i = 0; i < n; i++) + sq += s[i] * s[i]; + + return sqrtf(sq / n); +} diff --git a/src/tests/lo-test-util.h b/src/tests/lo-test-util.h new file mode 100644 index 0000000..d0de609 --- /dev/null +++ b/src/tests/lo-test-util.h @@ -0,0 +1,57 @@ +/*** + This file is part of PulseAudio. + + Copyright 2013 Collabora Ltd. + Author: Arun Raghavan <arun.raghavan at collabora.co.uk> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/pulseaudio.h> + +typedef struct pa_lo_test_context { + /* Tests need to set these */ + const char *context_name; + + pa_sample_spec sample_spec; + int play_latency; /* ms */ + int rec_latency; /* ms */ + + pa_stream_request_cb_t write_cb, read_cb; + + /* These are set by lo_test_init() */ + pa_mainloop *mainloop; + pa_context *context; + + pa_stream *play_stream, *rec_stream; + + int ss, fs; /* sample size, frame size for convenience */ +} pa_lo_test_context; + +/* Initialise the test parameters, connect */ +int pa_lo_test_init(pa_lo_test_context *ctx); +/* Start running the test */ +int pa_lo_test_run(pa_lo_test_context *ctx); +/* Clean up */ +void pa_lo_test_deinit(pa_lo_test_context *ctx); + +/* Return RMS for the given signal. Assumes the data is a single channel for + * simplicity */ +float pa_rms(const float *s, int n); -- 1.8.2.1