• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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