media/libcubeb/src/cubeb_pulse.c

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /*
michael@0 2 * Copyright © 2011 Mozilla Foundation
michael@0 3 *
michael@0 4 * This program is made available under an ISC-style license. See the
michael@0 5 * accompanying file LICENSE for details.
michael@0 6 */
michael@0 7 #undef NDEBUG
michael@0 8 #include <assert.h>
michael@0 9 #include <dlfcn.h>
michael@0 10 #include <stdlib.h>
michael@0 11 #include <pulse/pulseaudio.h>
michael@0 12 #include <string.h>
michael@0 13 #include "cubeb/cubeb.h"
michael@0 14 #include "cubeb-internal.h"
michael@0 15
michael@0 16 #ifdef DISABLE_LIBPULSE_DLOPEN
michael@0 17 #define WRAP(x) x
michael@0 18 #else
michael@0 19 #define WRAP(x) cubeb_##x
michael@0 20 #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x
michael@0 21 MAKE_TYPEDEF(pa_channel_map_init_auto);
michael@0 22 MAKE_TYPEDEF(pa_context_connect);
michael@0 23 MAKE_TYPEDEF(pa_context_disconnect);
michael@0 24 MAKE_TYPEDEF(pa_context_drain);
michael@0 25 MAKE_TYPEDEF(pa_context_get_state);
michael@0 26 MAKE_TYPEDEF(pa_context_new);
michael@0 27 MAKE_TYPEDEF(pa_context_rttime_new);
michael@0 28 MAKE_TYPEDEF(pa_context_set_state_callback);
michael@0 29 MAKE_TYPEDEF(pa_context_unref);
michael@0 30 MAKE_TYPEDEF(pa_context_get_sink_info_by_name);
michael@0 31 MAKE_TYPEDEF(pa_context_get_server_info);
michael@0 32 MAKE_TYPEDEF(pa_frame_size);
michael@0 33 MAKE_TYPEDEF(pa_operation_get_state);
michael@0 34 MAKE_TYPEDEF(pa_operation_unref);
michael@0 35 MAKE_TYPEDEF(pa_rtclock_now);
michael@0 36 MAKE_TYPEDEF(pa_stream_begin_write);
michael@0 37 MAKE_TYPEDEF(pa_stream_cancel_write);
michael@0 38 MAKE_TYPEDEF(pa_stream_connect_playback);
michael@0 39 MAKE_TYPEDEF(pa_stream_cork);
michael@0 40 MAKE_TYPEDEF(pa_stream_disconnect);
michael@0 41 MAKE_TYPEDEF(pa_stream_get_latency);
michael@0 42 MAKE_TYPEDEF(pa_stream_get_state);
michael@0 43 MAKE_TYPEDEF(pa_stream_get_time);
michael@0 44 MAKE_TYPEDEF(pa_stream_new);
michael@0 45 MAKE_TYPEDEF(pa_stream_set_state_callback);
michael@0 46 MAKE_TYPEDEF(pa_stream_set_write_callback);
michael@0 47 MAKE_TYPEDEF(pa_stream_unref);
michael@0 48 MAKE_TYPEDEF(pa_stream_update_timing_info);
michael@0 49 MAKE_TYPEDEF(pa_stream_write);
michael@0 50 MAKE_TYPEDEF(pa_threaded_mainloop_free);
michael@0 51 MAKE_TYPEDEF(pa_threaded_mainloop_get_api);
michael@0 52 MAKE_TYPEDEF(pa_threaded_mainloop_lock);
michael@0 53 MAKE_TYPEDEF(pa_threaded_mainloop_in_thread);
michael@0 54 MAKE_TYPEDEF(pa_threaded_mainloop_new);
michael@0 55 MAKE_TYPEDEF(pa_threaded_mainloop_signal);
michael@0 56 MAKE_TYPEDEF(pa_threaded_mainloop_start);
michael@0 57 MAKE_TYPEDEF(pa_threaded_mainloop_stop);
michael@0 58 MAKE_TYPEDEF(pa_threaded_mainloop_unlock);
michael@0 59 MAKE_TYPEDEF(pa_threaded_mainloop_wait);
michael@0 60 MAKE_TYPEDEF(pa_usec_to_bytes);
michael@0 61 #undef MAKE_TYPEDEF
michael@0 62 #endif
michael@0 63
michael@0 64 static struct cubeb_ops const pulse_ops;
michael@0 65
michael@0 66 struct cubeb {
michael@0 67 struct cubeb_ops const * ops;
michael@0 68 void * libpulse;
michael@0 69 pa_threaded_mainloop * mainloop;
michael@0 70 pa_context * context;
michael@0 71 pa_sink_info * default_sink_info;
michael@0 72 char * context_name;
michael@0 73 int error;
michael@0 74 };
michael@0 75
michael@0 76 struct cubeb_stream {
michael@0 77 cubeb * context;
michael@0 78 pa_stream * stream;
michael@0 79 cubeb_data_callback data_callback;
michael@0 80 cubeb_state_callback state_callback;
michael@0 81 void * user_ptr;
michael@0 82 pa_time_event * drain_timer;
michael@0 83 pa_sample_spec sample_spec;
michael@0 84 int shutdown;
michael@0 85 };
michael@0 86
michael@0 87 enum cork_state {
michael@0 88 UNCORK = 0,
michael@0 89 CORK = 1 << 0,
michael@0 90 NOTIFY = 1 << 1
michael@0 91 };
michael@0 92
michael@0 93 static void
michael@0 94 sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u)
michael@0 95 {
michael@0 96 cubeb * ctx = u;
michael@0 97 if (!eol) {
michael@0 98 ctx->default_sink_info = malloc(sizeof(pa_sink_info));
michael@0 99 memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info));
michael@0 100 }
michael@0 101 WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
michael@0 102 }
michael@0 103
michael@0 104 static void
michael@0 105 server_info_callback(pa_context * context, const pa_server_info * info, void * u)
michael@0 106 {
michael@0 107 WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u);
michael@0 108 }
michael@0 109
michael@0 110 static void
michael@0 111 context_state_callback(pa_context * c, void * u)
michael@0 112 {
michael@0 113 cubeb * ctx = u;
michael@0 114 if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
michael@0 115 ctx->error = 1;
michael@0 116 }
michael@0 117 WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
michael@0 118 }
michael@0 119
michael@0 120 static void
michael@0 121 context_notify_callback(pa_context * c, void * u)
michael@0 122 {
michael@0 123 cubeb * ctx = u;
michael@0 124 WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
michael@0 125 }
michael@0 126
michael@0 127 static void
michael@0 128 stream_success_callback(pa_stream * s, int success, void * u)
michael@0 129 {
michael@0 130 cubeb_stream * stm = u;
michael@0 131 WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
michael@0 132 }
michael@0 133
michael@0 134 static void
michael@0 135 stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
michael@0 136 {
michael@0 137 cubeb_stream * stm = u;
michael@0 138 /* there's no pa_rttime_free, so use this instead. */
michael@0 139 a->time_free(stm->drain_timer);
michael@0 140 stm->drain_timer = NULL;
michael@0 141 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
michael@0 142 }
michael@0 143
michael@0 144 static void
michael@0 145 stream_state_callback(pa_stream * s, void * u)
michael@0 146 {
michael@0 147 cubeb_stream * stm = u;
michael@0 148 if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
michael@0 149 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
michael@0 150 }
michael@0 151 WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
michael@0 152 }
michael@0 153
michael@0 154 static void
michael@0 155 stream_request_callback(pa_stream * s, size_t nbytes, void * u)
michael@0 156 {
michael@0 157 cubeb_stream * stm;
michael@0 158 void * buffer;
michael@0 159 size_t size;
michael@0 160 int r;
michael@0 161 long got;
michael@0 162 size_t towrite;
michael@0 163 size_t frame_size;
michael@0 164
michael@0 165 stm = u;
michael@0 166
michael@0 167 if (stm->shutdown)
michael@0 168 return;
michael@0 169
michael@0 170 frame_size = WRAP(pa_frame_size)(&stm->sample_spec);
michael@0 171
michael@0 172 assert(nbytes % frame_size == 0);
michael@0 173
michael@0 174 towrite = nbytes;
michael@0 175
michael@0 176 while (towrite) {
michael@0 177 size = towrite;
michael@0 178 r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
michael@0 179 assert(r == 0);
michael@0 180 assert(size > 0);
michael@0 181 assert(size % frame_size == 0);
michael@0 182
michael@0 183 got = stm->data_callback(stm, stm->user_ptr, buffer, size / frame_size);
michael@0 184 if (got < 0) {
michael@0 185 WRAP(pa_stream_cancel_write)(s);
michael@0 186 stm->shutdown = 1;
michael@0 187 return;
michael@0 188 }
michael@0 189
michael@0 190 r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE);
michael@0 191 assert(r == 0);
michael@0 192
michael@0 193 if ((size_t) got < size / frame_size) {
michael@0 194 pa_usec_t latency = 0;
michael@0 195 r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
michael@0 196 if (r == -PA_ERR_NODATA) {
michael@0 197 /* this needs a better guess. */
michael@0 198 latency = 100 * PA_USEC_PER_MSEC;
michael@0 199 }
michael@0 200 assert(r == 0 || r == -PA_ERR_NODATA);
michael@0 201 /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
michael@0 202 /* arbitrary safety margin: double the current latency. */
michael@0 203 stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
michael@0 204 stm->shutdown = 1;
michael@0 205 return;
michael@0 206 }
michael@0 207
michael@0 208 towrite -= size;
michael@0 209 }
michael@0 210
michael@0 211 assert(towrite == 0);
michael@0 212 }
michael@0 213
michael@0 214 static int
michael@0 215 wait_until_context_ready(cubeb * ctx)
michael@0 216 {
michael@0 217 for (;;) {
michael@0 218 pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
michael@0 219 if (!PA_CONTEXT_IS_GOOD(state))
michael@0 220 return -1;
michael@0 221 if (state == PA_CONTEXT_READY)
michael@0 222 break;
michael@0 223 WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
michael@0 224 }
michael@0 225 return 0;
michael@0 226 }
michael@0 227
michael@0 228 static int
michael@0 229 wait_until_stream_ready(cubeb_stream * stm)
michael@0 230 {
michael@0 231 for (;;) {
michael@0 232 pa_stream_state_t state = WRAP(pa_stream_get_state)(stm->stream);
michael@0 233 if (!PA_STREAM_IS_GOOD(state))
michael@0 234 return -1;
michael@0 235 if (state == PA_STREAM_READY)
michael@0 236 break;
michael@0 237 WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
michael@0 238 }
michael@0 239 return 0;
michael@0 240 }
michael@0 241
michael@0 242 static int
michael@0 243 operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
michael@0 244 {
michael@0 245 while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
michael@0 246 WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
michael@0 247 if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context)))
michael@0 248 return -1;
michael@0 249 if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream)))
michael@0 250 return -1;
michael@0 251 }
michael@0 252 return 0;
michael@0 253 }
michael@0 254
michael@0 255 static void
michael@0 256 stream_cork(cubeb_stream * stm, enum cork_state state)
michael@0 257 {
michael@0 258 pa_operation * o;
michael@0 259
michael@0 260 WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
michael@0 261 o = WRAP(pa_stream_cork)(stm->stream, state & CORK, stream_success_callback, stm);
michael@0 262 if (o) {
michael@0 263 operation_wait(stm->context, stm->stream, o);
michael@0 264 WRAP(pa_operation_unref)(o);
michael@0 265 }
michael@0 266 WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
michael@0 267
michael@0 268 if (state & NOTIFY) {
michael@0 269 stm->state_callback(stm, stm->user_ptr,
michael@0 270 state & CORK ? CUBEB_STATE_STOPPED : CUBEB_STATE_STARTED);
michael@0 271 }
michael@0 272 }
michael@0 273
michael@0 274 static void pulse_context_destroy(cubeb * ctx);
michael@0 275 static void pulse_destroy(cubeb * ctx);
michael@0 276
michael@0 277 static int
michael@0 278 pulse_context_init(cubeb * ctx)
michael@0 279 {
michael@0 280 if (ctx->context) {
michael@0 281 assert(ctx->error == 1);
michael@0 282 pulse_context_destroy(ctx);
michael@0 283 }
michael@0 284
michael@0 285 ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop),
michael@0 286 ctx->context_name);
michael@0 287 WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx);
michael@0 288
michael@0 289 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
michael@0 290 WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
michael@0 291
michael@0 292 if (wait_until_context_ready(ctx) != 0) {
michael@0 293 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
michael@0 294 pulse_context_destroy(ctx);
michael@0 295 ctx->context = NULL;
michael@0 296 return -1;
michael@0 297 }
michael@0 298
michael@0 299 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
michael@0 300
michael@0 301 ctx->error = 0;
michael@0 302
michael@0 303 return 0;
michael@0 304 }
michael@0 305
michael@0 306 /*static*/ int
michael@0 307 pulse_init(cubeb ** context, char const * context_name)
michael@0 308 {
michael@0 309 void * libpulse = NULL;
michael@0 310 cubeb * ctx;
michael@0 311
michael@0 312 *context = NULL;
michael@0 313
michael@0 314 #ifndef DISABLE_LIBPULSE_DLOPEN
michael@0 315 libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
michael@0 316 if (!libpulse) {
michael@0 317 return CUBEB_ERROR;
michael@0 318 }
michael@0 319
michael@0 320 #define LOAD(x) do { \
michael@0 321 cubeb_##x = dlsym(libpulse, #x); \
michael@0 322 if (!cubeb_##x) { \
michael@0 323 dlclose(libpulse); \
michael@0 324 return CUBEB_ERROR; \
michael@0 325 } \
michael@0 326 } while(0)
michael@0 327 LOAD(pa_channel_map_init_auto);
michael@0 328 LOAD(pa_context_connect);
michael@0 329 LOAD(pa_context_disconnect);
michael@0 330 LOAD(pa_context_drain);
michael@0 331 LOAD(pa_context_get_state);
michael@0 332 LOAD(pa_context_new);
michael@0 333 LOAD(pa_context_rttime_new);
michael@0 334 LOAD(pa_context_set_state_callback);
michael@0 335 LOAD(pa_context_get_sink_info_by_name);
michael@0 336 LOAD(pa_context_get_server_info);
michael@0 337 LOAD(pa_context_unref);
michael@0 338 LOAD(pa_frame_size);
michael@0 339 LOAD(pa_operation_get_state);
michael@0 340 LOAD(pa_operation_unref);
michael@0 341 LOAD(pa_rtclock_now);
michael@0 342 LOAD(pa_stream_begin_write);
michael@0 343 LOAD(pa_stream_cancel_write);
michael@0 344 LOAD(pa_stream_connect_playback);
michael@0 345 LOAD(pa_stream_cork);
michael@0 346 LOAD(pa_stream_disconnect);
michael@0 347 LOAD(pa_stream_get_latency);
michael@0 348 LOAD(pa_stream_get_state);
michael@0 349 LOAD(pa_stream_get_time);
michael@0 350 LOAD(pa_stream_new);
michael@0 351 LOAD(pa_stream_set_state_callback);
michael@0 352 LOAD(pa_stream_set_write_callback);
michael@0 353 LOAD(pa_stream_unref);
michael@0 354 LOAD(pa_stream_update_timing_info);
michael@0 355 LOAD(pa_stream_write);
michael@0 356 LOAD(pa_threaded_mainloop_free);
michael@0 357 LOAD(pa_threaded_mainloop_get_api);
michael@0 358 LOAD(pa_threaded_mainloop_lock);
michael@0 359 LOAD(pa_threaded_mainloop_in_thread);
michael@0 360 LOAD(pa_threaded_mainloop_new);
michael@0 361 LOAD(pa_threaded_mainloop_signal);
michael@0 362 LOAD(pa_threaded_mainloop_start);
michael@0 363 LOAD(pa_threaded_mainloop_stop);
michael@0 364 LOAD(pa_threaded_mainloop_unlock);
michael@0 365 LOAD(pa_threaded_mainloop_wait);
michael@0 366 LOAD(pa_usec_to_bytes);
michael@0 367 #undef LOAD
michael@0 368 #endif
michael@0 369
michael@0 370 ctx = calloc(1, sizeof(*ctx));
michael@0 371 assert(ctx);
michael@0 372
michael@0 373 ctx->ops = &pulse_ops;
michael@0 374 ctx->libpulse = libpulse;
michael@0 375
michael@0 376 ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
michael@0 377 ctx->default_sink_info = NULL;
michael@0 378
michael@0 379 WRAP(pa_threaded_mainloop_start)(ctx->mainloop);
michael@0 380
michael@0 381 ctx->context_name = context_name ? strdup(context_name) : NULL;
michael@0 382 if (pulse_context_init(ctx) != 0) {
michael@0 383 pulse_destroy(ctx);
michael@0 384 return CUBEB_ERROR;
michael@0 385 }
michael@0 386
michael@0 387 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
michael@0 388 WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
michael@0 389 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
michael@0 390
michael@0 391 *context = ctx;
michael@0 392
michael@0 393 return CUBEB_OK;
michael@0 394 }
michael@0 395
michael@0 396 static char const *
michael@0 397 pulse_get_backend_id(cubeb * ctx)
michael@0 398 {
michael@0 399 return "pulse";
michael@0 400 }
michael@0 401
michael@0 402 static int
michael@0 403 pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
michael@0 404 {
michael@0 405 assert(ctx && max_channels);
michael@0 406
michael@0 407 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
michael@0 408 while (!ctx->default_sink_info) {
michael@0 409 WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
michael@0 410 }
michael@0 411 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
michael@0 412
michael@0 413 *max_channels = ctx->default_sink_info->channel_map.channels;
michael@0 414
michael@0 415 return CUBEB_OK;
michael@0 416 }
michael@0 417
michael@0 418 static int
michael@0 419 pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
michael@0 420 {
michael@0 421 assert(ctx && rate);
michael@0 422
michael@0 423 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
michael@0 424 while (!ctx->default_sink_info) {
michael@0 425 WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
michael@0 426 }
michael@0 427 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
michael@0 428
michael@0 429 *rate = ctx->default_sink_info->sample_spec.rate;
michael@0 430
michael@0 431 return CUBEB_OK;
michael@0 432 }
michael@0 433
michael@0 434 static int
michael@0 435 pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
michael@0 436 {
michael@0 437 // According to PulseAudio developers, this is a safe minimum.
michael@0 438 *latency_ms = 40;
michael@0 439
michael@0 440 return CUBEB_OK;
michael@0 441 }
michael@0 442
michael@0 443 static void
michael@0 444 pulse_context_destroy(cubeb * ctx)
michael@0 445 {
michael@0 446 pa_operation * o;
michael@0 447
michael@0 448 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
michael@0 449 o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
michael@0 450 if (o) {
michael@0 451 operation_wait(ctx, NULL, o);
michael@0 452 WRAP(pa_operation_unref)(o);
michael@0 453 }
michael@0 454 WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
michael@0 455 WRAP(pa_context_disconnect)(ctx->context);
michael@0 456 WRAP(pa_context_unref)(ctx->context);
michael@0 457 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
michael@0 458 }
michael@0 459
michael@0 460 static void
michael@0 461 pulse_destroy(cubeb * ctx)
michael@0 462 {
michael@0 463 pa_operation * o;
michael@0 464
michael@0 465 if (ctx->context_name) {
michael@0 466 free(ctx->context_name);
michael@0 467 }
michael@0 468 if (ctx->context) {
michael@0 469 pulse_context_destroy(ctx);
michael@0 470 }
michael@0 471
michael@0 472 if (ctx->mainloop) {
michael@0 473 WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
michael@0 474 WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
michael@0 475 }
michael@0 476
michael@0 477 if (ctx->libpulse) {
michael@0 478 dlclose(ctx->libpulse);
michael@0 479 }
michael@0 480 if (ctx->default_sink_info) {
michael@0 481 free(ctx->default_sink_info);
michael@0 482 }
michael@0 483 free(ctx);
michael@0 484 }
michael@0 485
michael@0 486 static void pulse_stream_destroy(cubeb_stream * stm);
michael@0 487
michael@0 488 static int
michael@0 489 pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
michael@0 490 cubeb_stream_params stream_params, unsigned int latency,
michael@0 491 cubeb_data_callback data_callback, cubeb_state_callback state_callback,
michael@0 492 void * user_ptr)
michael@0 493 {
michael@0 494 pa_sample_spec ss;
michael@0 495 cubeb_stream * stm;
michael@0 496 pa_operation * o;
michael@0 497 pa_buffer_attr battr;
michael@0 498 int r;
michael@0 499
michael@0 500 assert(context);
michael@0 501
michael@0 502 *stream = NULL;
michael@0 503
michael@0 504 switch (stream_params.format) {
michael@0 505 case CUBEB_SAMPLE_S16LE:
michael@0 506 ss.format = PA_SAMPLE_S16LE;
michael@0 507 break;
michael@0 508 case CUBEB_SAMPLE_S16BE:
michael@0 509 ss.format = PA_SAMPLE_S16BE;
michael@0 510 break;
michael@0 511 case CUBEB_SAMPLE_FLOAT32LE:
michael@0 512 ss.format = PA_SAMPLE_FLOAT32LE;
michael@0 513 break;
michael@0 514 case CUBEB_SAMPLE_FLOAT32BE:
michael@0 515 ss.format = PA_SAMPLE_FLOAT32BE;
michael@0 516 break;
michael@0 517 default:
michael@0 518 return CUBEB_ERROR_INVALID_FORMAT;
michael@0 519 }
michael@0 520
michael@0 521 // If the connection failed for some reason, try to reconnect
michael@0 522 if (context->error == 1 && pulse_context_init(context) != 0) {
michael@0 523 return CUBEB_ERROR;
michael@0 524 }
michael@0 525
michael@0 526 ss.rate = stream_params.rate;
michael@0 527 ss.channels = stream_params.channels;
michael@0 528
michael@0 529 stm = calloc(1, sizeof(*stm));
michael@0 530 assert(stm);
michael@0 531
michael@0 532 stm->context = context;
michael@0 533
michael@0 534 stm->data_callback = data_callback;
michael@0 535 stm->state_callback = state_callback;
michael@0 536 stm->user_ptr = user_ptr;
michael@0 537
michael@0 538 stm->sample_spec = ss;
michael@0 539
michael@0 540 battr.maxlength = -1;
michael@0 541 battr.tlength = WRAP(pa_usec_to_bytes)(latency * PA_USEC_PER_MSEC, &stm->sample_spec);
michael@0 542 battr.prebuf = -1;
michael@0 543 battr.minreq = battr.tlength / 4;
michael@0 544 battr.fragsize = -1;
michael@0 545
michael@0 546 WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
michael@0 547 stm->stream = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
michael@0 548 if (!stm->stream) {
michael@0 549 pulse_stream_destroy(stm);
michael@0 550 return CUBEB_ERROR;
michael@0 551 }
michael@0 552 WRAP(pa_stream_set_state_callback)(stm->stream, stream_state_callback, stm);
michael@0 553 WRAP(pa_stream_set_write_callback)(stm->stream, stream_request_callback, stm);
michael@0 554 WRAP(pa_stream_connect_playback)(stm->stream, NULL, &battr,
michael@0 555 PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
michael@0 556 PA_STREAM_START_CORKED,
michael@0 557 NULL, NULL);
michael@0 558
michael@0 559 r = wait_until_stream_ready(stm);
michael@0 560 if (r == 0) {
michael@0 561 /* force a timing update now, otherwise timing info does not become valid
michael@0 562 until some point after initialization has completed. */
michael@0 563 o = WRAP(pa_stream_update_timing_info)(stm->stream, stream_success_callback, stm);
michael@0 564 if (o) {
michael@0 565 r = operation_wait(stm->context, stm->stream, o);
michael@0 566 WRAP(pa_operation_unref)(o);
michael@0 567 }
michael@0 568 }
michael@0 569 WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
michael@0 570
michael@0 571 if (r != 0) {
michael@0 572 pulse_stream_destroy(stm);
michael@0 573 return CUBEB_ERROR;
michael@0 574 }
michael@0 575
michael@0 576 *stream = stm;
michael@0 577
michael@0 578 return CUBEB_OK;
michael@0 579 }
michael@0 580
michael@0 581 static void
michael@0 582 pulse_stream_destroy(cubeb_stream * stm)
michael@0 583 {
michael@0 584 if (stm->stream) {
michael@0 585 stream_cork(stm, CORK);
michael@0 586
michael@0 587 WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
michael@0 588
michael@0 589 if (stm->drain_timer) {
michael@0 590 /* there's no pa_rttime_free, so use this instead. */
michael@0 591 WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer);
michael@0 592 }
michael@0 593
michael@0 594 WRAP(pa_stream_set_state_callback)(stm->stream, NULL, NULL);
michael@0 595 WRAP(pa_stream_disconnect)(stm->stream);
michael@0 596 WRAP(pa_stream_unref)(stm->stream);
michael@0 597 WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
michael@0 598 }
michael@0 599
michael@0 600 free(stm);
michael@0 601 }
michael@0 602
michael@0 603 static int
michael@0 604 pulse_stream_start(cubeb_stream * stm)
michael@0 605 {
michael@0 606 stream_cork(stm, UNCORK | NOTIFY);
michael@0 607 return CUBEB_OK;
michael@0 608 }
michael@0 609
michael@0 610 static int
michael@0 611 pulse_stream_stop(cubeb_stream * stm)
michael@0 612 {
michael@0 613 stream_cork(stm, CORK | NOTIFY);
michael@0 614 return CUBEB_OK;
michael@0 615 }
michael@0 616
michael@0 617 static int
michael@0 618 pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
michael@0 619 {
michael@0 620 int r, in_thread;
michael@0 621 pa_usec_t r_usec;
michael@0 622 uint64_t bytes;
michael@0 623
michael@0 624 in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);
michael@0 625
michael@0 626 if (!in_thread) {
michael@0 627 WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
michael@0 628 }
michael@0 629 r = WRAP(pa_stream_get_time)(stm->stream, &r_usec);
michael@0 630 if (!in_thread) {
michael@0 631 WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
michael@0 632 }
michael@0 633
michael@0 634 if (r != 0) {
michael@0 635 return CUBEB_ERROR;
michael@0 636 }
michael@0 637
michael@0 638 bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->sample_spec);
michael@0 639 *position = bytes / WRAP(pa_frame_size)(&stm->sample_spec);
michael@0 640
michael@0 641 return CUBEB_OK;
michael@0 642 }
michael@0 643
michael@0 644 int
michael@0 645 pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
michael@0 646 {
michael@0 647 pa_usec_t r_usec;
michael@0 648 int negative, r;
michael@0 649
michael@0 650 if (!stm) {
michael@0 651 return CUBEB_ERROR;
michael@0 652 }
michael@0 653
michael@0 654 r = WRAP(pa_stream_get_latency)(stm->stream, &r_usec, &negative);
michael@0 655 assert(!negative);
michael@0 656 if (r) {
michael@0 657 return CUBEB_ERROR;
michael@0 658 }
michael@0 659
michael@0 660 *latency = r_usec * stm->sample_spec.rate / PA_USEC_PER_SEC;
michael@0 661 return CUBEB_OK;
michael@0 662 }
michael@0 663
michael@0 664 static struct cubeb_ops const pulse_ops = {
michael@0 665 .init = pulse_init,
michael@0 666 .get_backend_id = pulse_get_backend_id,
michael@0 667 .get_max_channel_count = pulse_get_max_channel_count,
michael@0 668 .get_min_latency = pulse_get_min_latency,
michael@0 669 .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
michael@0 670 .destroy = pulse_destroy,
michael@0 671 .stream_init = pulse_stream_init,
michael@0 672 .stream_destroy = pulse_stream_destroy,
michael@0 673 .stream_start = pulse_stream_start,
michael@0 674 .stream_stop = pulse_stream_stop,
michael@0 675 .stream_get_position = pulse_stream_get_position,
michael@0 676 .stream_get_latency = pulse_stream_get_latency
michael@0 677 };

mercurial