• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
46 static pa_threaded_mainloop *mainloop = NULL;
47 static pa_context *context = NULL;
48 static pa_mainloop_api *mainloop_api = NULL;
49 static uint32_t module_idx = PA_INVALID_INDEX;
50 static int sink_num = 0;
51 static char sink_name[256] = { 0, };
52 static const char *bname = NULL;
53 
54 /* This is called whenever the context status changes */
context_state_callback(pa_context * c,void * userdata)55 static 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 
module_index_cb(pa_context * c,uint32_t idx,void * userdata)86 static 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 
success_cb(pa_context * c,int success,void * userdata)94 static 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 
passthrough_teardown()100 static 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 
passthrough_setup()119 static 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 
nop_free_cb(void * p)158 static void nop_free_cb(void *p) {}
159 
underflow_cb(struct pa_stream * s,void * userdata)160 static 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 */
stream_state_callback(pa_stream * s,void * userdata)166 static 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 
connect_stream()204 static 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 
disconnect_stream(pa_stream * s)236 static 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 
START_TEST(passthrough_playback_test)252 START_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 }
267 END_TEST
268 
sink_info_cb(pa_context * c,const pa_sink_info * i,int eol,void * userdata)269 static 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 
get_sink_volume(pa_cvolume * v)280 static 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 
START_TEST(passthrough_volume_test)291 START_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 }
322 END_TEST
323 
main(int argc,char * argv[])324 int 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