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