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
42 static float out[N_OUT][CHANNELS];
43
44 pa_lo_test_context test_ctx;
45 static const char *context_name = NULL;
46
47 static struct timeval tv_out, tv_in;
48
nop_free_cb(void * p)49 static void nop_free_cb(void *p) {
50 }
51
write_cb(pa_stream * s,size_t nbytes,void * userdata)52 static 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
read_cb(pa_stream * s,size_t nbytes,void * userdata)79 static 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
START_TEST(loopback_test)139 START_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 }
165 END_TEST
166
main(int argc,char * argv[])167 int 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