1/***
2  This file is part of PulseAudio.
3
4  PulseAudio is free software; you can redistribute it and/or modify
5  it under the terms of the GNU Lesser General Public License as published
6  by the Free Software Foundation; either version 2.1 of the License,
7  or (at your option) any later version.
8
9  PulseAudio is distributed in the hope that it will be useful, but
10  WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  General Public License for more details.
13
14  You should have received a copy of the GNU Lesser General Public License
15  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
16***/
17
18#ifdef HAVE_CONFIG_H
19#include <config.h>
20#endif
21
22#include <stdio.h>
23#include <stdbool.h>
24
25#include <check.h>
26
27#include <pulse/pulseaudio.h>
28
29#include <pulsecore/core-util.h>
30
31#define SINK_NAME "passthrough-test"
32
33#define RATE 48000
34#define CHANNELS 6
35
36#define WAIT_FOR_OPERATION(o)                                           \
37    do {                                                                \
38        while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {     \
39            pa_threaded_mainloop_wait(mainloop);                        \
40        }                                                               \
41                                                                        \
42        fail_unless(pa_operation_get_state(o) == PA_OPERATION_DONE);    \
43        pa_operation_unref(o);                                          \
44    } while (false)
45
46static pa_threaded_mainloop *mainloop = NULL;
47static pa_context *context = NULL;
48static pa_mainloop_api *mainloop_api = NULL;
49static uint32_t module_idx = PA_INVALID_INDEX;
50static int sink_num = 0;
51static char sink_name[256] = { 0, };
52static const char *bname = NULL;
53
54/* This is called whenever the context status changes */
55static void context_state_callback(pa_context *c, void *userdata) {
56    fail_unless(c != NULL);
57
58    switch (pa_context_get_state(c)) {
59        case PA_CONTEXT_CONNECTING:
60        case PA_CONTEXT_AUTHORIZING:
61        case PA_CONTEXT_SETTING_NAME:
62            break;
63
64        case PA_CONTEXT_READY:
65            fprintf(stderr, "Connection established.\n");
66            pa_threaded_mainloop_signal(mainloop, false);
67            break;
68
69        case PA_CONTEXT_TERMINATED:
70            mainloop_api->quit(mainloop_api, 0);
71            pa_threaded_mainloop_signal(mainloop, false);
72            break;
73
74        case PA_CONTEXT_FAILED:
75            mainloop_api->quit(mainloop_api, 0);
76            pa_threaded_mainloop_signal(mainloop, false);
77            fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
78            fail();
79            break;
80
81        default:
82            fail();
83    }
84}
85
86static void module_index_cb(pa_context *c, uint32_t idx, void *userdata) {
87    fail_unless(idx != PA_INVALID_INDEX);
88
89    module_idx = idx;
90
91    pa_threaded_mainloop_signal(mainloop, false);
92}
93
94static void success_cb(pa_context *c, int success, void *userdata) {
95    fail_unless(success != 0);
96
97    pa_threaded_mainloop_signal(mainloop, false);
98}
99
100static void passthrough_teardown() {
101    pa_operation *o;
102
103    pa_threaded_mainloop_lock(mainloop);
104
105    if (module_idx != PA_INVALID_INDEX) {
106        o = pa_context_unload_module(context, module_idx, success_cb, NULL);
107        WAIT_FOR_OPERATION(o);
108    }
109
110    pa_context_disconnect(context);
111    pa_context_unref(context);
112
113    pa_threaded_mainloop_unlock(mainloop);
114
115    pa_threaded_mainloop_stop(mainloop);
116    pa_threaded_mainloop_free(mainloop);
117}
118
119static void passthrough_setup() {
120    char modargs[128];
121    pa_operation *o;
122    int r;
123
124    /* Set up a new main loop */
125    mainloop = pa_threaded_mainloop_new();
126    fail_unless(mainloop != NULL);
127
128    mainloop_api = pa_threaded_mainloop_get_api(mainloop);
129
130    pa_threaded_mainloop_lock(mainloop);
131
132    pa_threaded_mainloop_start(mainloop);
133
134    context = pa_context_new(mainloop_api, bname);
135    fail_unless(context != NULL);
136
137    pa_context_set_state_callback(context, context_state_callback, NULL);
138
139    /* Connect the context */
140    r = pa_context_connect(context, NULL, 0, NULL);
141    fail_unless(r == 0);
142
143    pa_threaded_mainloop_wait(mainloop);
144
145    fail_unless(pa_context_get_state(context) == PA_CONTEXT_READY);
146
147    pa_snprintf(sink_name, sizeof(sink_name), "%s-%d", SINK_NAME, sink_num);
148    pa_snprintf(modargs, sizeof(modargs), "sink_name='%s' formats='ac3-iec61937, format.rate=\"[32000, 44100, 48000]\" format.channels=\"6\"; pcm'", sink_name);
149
150    o = pa_context_load_module(context, "module-null-sink", modargs, module_index_cb, NULL);
151    WAIT_FOR_OPERATION(o);
152
153    pa_threaded_mainloop_unlock(mainloop);
154
155    return;
156}
157
158static void nop_free_cb(void *p) {}
159
160static void underflow_cb(struct pa_stream *s, void *userdata) {
161    fprintf(stderr, "Stream finished\n");
162    pa_threaded_mainloop_signal(mainloop, false);
163}
164
165/* This routine is called whenever the stream state changes */
166static void stream_state_callback(pa_stream *s, void *userdata) {
167    /* We fill in fake AC3 data in terms of the corresponding PCM sample spec (S16LE, 2ch, at the given rate) */
168    int16_t data[RATE * 2] = { 0, }; /* one second space */
169
170    fail_unless(s != NULL);
171
172    switch (pa_stream_get_state(s)) {
173        case PA_STREAM_UNCONNECTED:
174        case PA_STREAM_CREATING:
175            break;
176
177        case PA_STREAM_TERMINATED:
178            pa_threaded_mainloop_signal(mainloop, false);
179            break;
180
181        case PA_STREAM_READY: {
182            int r;
183
184            r = pa_stream_write(s, data, sizeof(data), nop_free_cb, 0, PA_SEEK_ABSOLUTE);
185            fail_unless(r == 0);
186
187            /* Be notified when this stream is drained */
188            pa_stream_set_underflow_callback(s, underflow_cb, userdata);
189
190            pa_threaded_mainloop_signal(mainloop, false);
191            break;
192        }
193
194        case PA_STREAM_FAILED:
195            fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
196            pa_threaded_mainloop_signal(mainloop, false);
197            break;
198
199        default:
200            fail();
201    }
202}
203
204static pa_stream* connect_stream() {
205    int r;
206    pa_stream *s;
207    pa_format_info *formats[1];
208
209    pa_threaded_mainloop_lock(mainloop);
210
211    formats[0] = pa_format_info_new();
212    formats[0]->encoding = PA_ENCODING_AC3_IEC61937;
213    /* We set rate and channels to test that negotiation actually works. This
214     * must correspond to the rate and channels we configure module-null-sink
215     * for above. */
216    pa_format_info_set_rate(formats[0], RATE);
217    pa_format_info_set_channels(formats[0], CHANNELS);
218
219    s = pa_stream_new_extended(context, "passthrough test", formats, 1, NULL);
220    fail_unless(s != NULL);
221
222    pa_stream_set_state_callback(s, stream_state_callback, NULL);
223    r = pa_stream_connect_playback(s, sink_name, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
224
225    fail_unless(r == 0);
226
227    pa_threaded_mainloop_wait(mainloop);
228
229    fail_unless(pa_stream_get_state(s) == PA_STREAM_READY);
230
231    pa_threaded_mainloop_unlock(mainloop);
232
233    return s;
234}
235
236static void disconnect_stream(pa_stream *s) {
237    int r;
238
239    pa_threaded_mainloop_lock(mainloop);
240
241    r = pa_stream_disconnect(s);
242    fail_unless(r == 0);
243
244    pa_threaded_mainloop_wait(mainloop);
245    fail_unless(pa_stream_get_state(s) == PA_STREAM_TERMINATED);
246
247    pa_stream_unref(s);
248
249    pa_threaded_mainloop_unlock(mainloop);
250}
251
252START_TEST (passthrough_playback_test) {
253    /* Create a passthrough stream, and make sure format negotiation actually
254     * works */
255    pa_stream *stream;
256
257    stream = connect_stream();
258
259    /* Wait for underflow_cb() */
260    pa_threaded_mainloop_lock(mainloop);
261    pa_threaded_mainloop_wait(mainloop);
262    fail_unless(pa_stream_get_state(stream) == PA_STREAM_READY);
263    pa_threaded_mainloop_unlock(mainloop);
264
265    disconnect_stream(stream);
266}
267END_TEST
268
269static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
270    pa_cvolume *v = (pa_cvolume *) userdata;
271
272    if (eol)
273        return;
274
275    *v = i->volume;
276
277    pa_threaded_mainloop_signal(mainloop, false);
278}
279
280static void get_sink_volume(pa_cvolume *v) {
281    pa_operation *o;
282
283    pa_threaded_mainloop_lock(mainloop);
284
285    o = pa_context_get_sink_info_by_name(context, sink_name, sink_info_cb, v);
286    WAIT_FOR_OPERATION(o);
287
288    pa_threaded_mainloop_unlock(mainloop);
289}
290
291START_TEST (passthrough_volume_test) {
292    /* Set a non-100% volume of the sink before playback, create a passthrough
293     * stream, make sure volume gets set to 100%, and then restored when the
294     * stream goes away */
295    pa_stream *stream;
296    pa_operation *o;
297    pa_cvolume volume, tmp;
298
299    pa_threaded_mainloop_lock(mainloop);
300
301    pa_cvolume_set(&volume, 2, PA_VOLUME_NORM / 2);
302    o = pa_context_set_sink_volume_by_name(context, sink_name, &volume, success_cb, NULL);
303    WAIT_FOR_OPERATION(o);
304
305    pa_threaded_mainloop_unlock(mainloop);
306
307    stream = connect_stream();
308
309    pa_threaded_mainloop_lock(mainloop);
310    pa_threaded_mainloop_wait(mainloop);
311    fail_unless(PA_STREAM_IS_GOOD(pa_stream_get_state(stream)));
312    pa_threaded_mainloop_unlock(mainloop);
313
314    get_sink_volume(&tmp);
315    fail_unless(pa_cvolume_is_norm(&tmp));
316
317    disconnect_stream(stream);
318
319    get_sink_volume(&tmp);
320    fail_unless(pa_cvolume_equal(&volume, &tmp));
321}
322END_TEST
323
324int main(int argc, char *argv[]) {
325    int failed = 0;
326    Suite *s;
327    TCase *tc;
328    SRunner *sr;
329
330    bname = argv[0];
331
332    s = suite_create("Passthrough");
333    tc = tcase_create("passthrough");
334    tcase_add_checked_fixture(tc, passthrough_setup, passthrough_teardown);
335    tcase_add_test(tc, passthrough_playback_test);
336    sink_num++;
337    tcase_add_test(tc, passthrough_volume_test);
338    tcase_set_timeout(tc, 5);
339    suite_add_tcase(s, tc);
340
341    sr = srunner_create(s);
342    srunner_run_all(sr, CK_NORMAL);
343    failed = srunner_ntests_failed(sr);
344    srunner_free(sr);
345
346    return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
347}
348