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 <sys/types.h> 26#include <sys/stat.h> 27#include <fcntl.h> 28 29#include <errno.h> 30#include <unistd.h> 31#include <stdio.h> 32#include <stdlib.h> 33 34#include <check.h> 35 36#include "lo-test-util.h" 37 38#define SAMPLE_HZ 44100 39#define CHANNELS 2 40#define N_OUT (SAMPLE_HZ * 1) 41 42static float out[N_OUT][CHANNELS]; 43 44pa_lo_test_context test_ctx; 45static const char *context_name = NULL; 46 47static struct timeval tv_out, tv_in; 48 49static void nop_free_cb(void *p) { 50} 51 52static void write_cb(pa_stream *s, size_t nbytes, void *userdata) { 53 pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; 54 static int ppos = 0; 55 int r, nsamp; 56 57 /* Get the real requested bytes since the last write might have been 58 * incomplete if it caused a wrap around */ 59 nbytes = pa_stream_writable_size(s); 60 nsamp = nbytes / ctx->fs; 61 62 if (ppos + nsamp > N_OUT) { 63 /* Wrap-around, write to end and exit. Next iteration will fill up the 64 * rest */ 65 nbytes = (N_OUT - ppos) * ctx->fs; 66 } 67 68 if (ppos == 0) 69 pa_gettimeofday(&tv_out); 70 71 r = pa_stream_write(s, &out[ppos][0], nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE); 72 fail_unless(r == 0); 73 74 ppos = (ppos + nbytes / ctx->fs) % N_OUT; 75} 76 77#define WINDOW (2 * CHANNELS) 78 79static void read_cb(pa_stream *s, size_t nbytes, void *userdata) { 80 pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; 81 static float last = 0.0f; 82 const float *in; 83 float cur; 84 int r; 85 unsigned int i = 0; 86 size_t l; 87 88 r = pa_stream_peek(s, (const void **)&in, &l); 89 fail_unless(r == 0); 90 91 if (l == 0) 92 return; 93 94#if 0 95 { 96 static int fd = -1; 97 98 if (fd == -1) { 99 fd = open("loopback.raw", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR); 100 fail_if(fd < 0); 101 } 102 103 r = write(fd, in, l); 104 } 105#endif 106 107 do { 108#if 0 109 { 110 int j; 111 fprintf(stderr, "%g (", pa_rms(in, WINDOW)); 112 for (j = 0; j < WINDOW; j++) 113 fprintf(stderr, "%g ", in[j]); 114 fprintf(stderr, ")\n"); 115 } 116#endif 117 if (i + (ctx->ss * WINDOW) < l) 118 cur = pa_rms(in, WINDOW); 119 else 120 cur = pa_rms(in, (l - i) / ctx->ss); 121 122 /* We leave the definition of 0 generous since the window might 123 * straddle the 0->1 transition, raising the average power. We keep the 124 * definition of 1 tight in this case and detect the transition in the 125 * next round. */ 126 if (cur - last > 0.4f) { 127 pa_gettimeofday(&tv_in); 128 fprintf(stderr, "Latency %llu\n", (unsigned long long) pa_timeval_diff(&tv_in, &tv_out)); 129 } 130 131 last = cur; 132 in += WINDOW; 133 i += ctx->ss * WINDOW; 134 } while (i + (ctx->ss * WINDOW) <= l); 135 136 pa_stream_drop(s); 137} 138 139START_TEST (loopback_test) { 140 int i, pulse_hz = SAMPLE_HZ / 1000; 141 142 test_ctx.context_name = context_name; 143 144 test_ctx.sample_spec.format = PA_SAMPLE_FLOAT32, 145 test_ctx.sample_spec.rate = SAMPLE_HZ, 146 test_ctx.sample_spec.channels = CHANNELS, 147 148 test_ctx.play_latency = 25; 149 test_ctx.rec_latency = 5; 150 151 test_ctx.read_cb = read_cb; 152 test_ctx.write_cb = write_cb; 153 154 /* Generate a square pulse */ 155 for (i = 0; i < N_OUT; i++) 156 if (i < pulse_hz) 157 out[i][0] = out[i][1] = 1.0f; 158 else 159 out[i][0] = out[i][1] = 0.0f; 160 161 fail_unless(pa_lo_test_init(&test_ctx) == 0); 162 fail_unless(pa_lo_test_run(&test_ctx) == 0); 163 pa_lo_test_deinit(&test_ctx); 164} 165END_TEST 166 167int main(int argc, char *argv[]) { 168 int failed = 0; 169 Suite *s; 170 TCase *tc; 171 SRunner *sr; 172 173 context_name = argv[0]; 174 175 s = suite_create("Loopback latency"); 176 tc = tcase_create("loopback latency"); 177 tcase_add_test(tc, loopback_test); 178 tcase_set_timeout(tc, 5 * 60); 179 suite_add_tcase(s, tc); 180 181 sr = srunner_create(s); 182 srunner_set_fork_status(sr, CK_NOFORK); 183 srunner_run_all(sr, CK_NORMAL); 184 failed = srunner_ntests_failed(sr); 185 srunner_free(sr); 186 187 return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 188} 189