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