media/libcubeb/src/cubeb_alsa.c

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

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 #define _BSD_SOURCE
michael@0 9 #define _XOPEN_SOURCE 500
michael@0 10 #include <pthread.h>
michael@0 11 #include <sys/time.h>
michael@0 12 #include <assert.h>
michael@0 13 #include <limits.h>
michael@0 14 #include <poll.h>
michael@0 15 #include <unistd.h>
michael@0 16 #include <alsa/asoundlib.h>
michael@0 17 #include "cubeb/cubeb.h"
michael@0 18 #include "cubeb-internal.h"
michael@0 19
michael@0 20 #define CUBEB_STREAM_MAX 16
michael@0 21 #define CUBEB_WATCHDOG_MS 10000
michael@0 22
michael@0 23 #define CUBEB_ALSA_PCM_NAME "default"
michael@0 24
michael@0 25 #define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin"
michael@0 26
michael@0 27 /* ALSA is not thread-safe. snd_pcm_t instances are individually protected
michael@0 28 by the owning cubeb_stream's mutex. snd_pcm_t creation and destruction
michael@0 29 is not thread-safe until ALSA 1.0.24 (see alsa-lib.git commit 91c9c8f1),
michael@0 30 so those calls must be wrapped in the following mutex. */
michael@0 31 static pthread_mutex_t cubeb_alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
michael@0 32 static int cubeb_alsa_error_handler_set = 0;
michael@0 33
michael@0 34 static struct cubeb_ops const alsa_ops;
michael@0 35
michael@0 36 struct cubeb {
michael@0 37 struct cubeb_ops const * ops;
michael@0 38
michael@0 39 pthread_t thread;
michael@0 40
michael@0 41 /* Mutex for streams array, must not be held while blocked in poll(2). */
michael@0 42 pthread_mutex_t mutex;
michael@0 43
michael@0 44 /* Sparse array of streams managed by this context. */
michael@0 45 cubeb_stream * streams[CUBEB_STREAM_MAX];
michael@0 46
michael@0 47 /* fds and nfds are only updated by alsa_run when rebuild is set. */
michael@0 48 struct pollfd * fds;
michael@0 49 nfds_t nfds;
michael@0 50 int rebuild;
michael@0 51
michael@0 52 int shutdown;
michael@0 53
michael@0 54 /* Control pipe for forcing poll to wake and rebuild fds or recalculate the timeout. */
michael@0 55 int control_fd_read;
michael@0 56 int control_fd_write;
michael@0 57
michael@0 58 /* Track number of active streams. This is limited to CUBEB_STREAM_MAX
michael@0 59 due to resource contraints. */
michael@0 60 unsigned int active_streams;
michael@0 61
michael@0 62 /* Local configuration with handle_underrun workaround set for PulseAudio
michael@0 63 ALSA plugin. Will be NULL if the PA ALSA plugin is not in use or the
michael@0 64 workaround is not required. */
michael@0 65 snd_config_t * local_config;
michael@0 66 int is_pa;
michael@0 67 };
michael@0 68
michael@0 69 enum stream_state {
michael@0 70 INACTIVE,
michael@0 71 RUNNING,
michael@0 72 DRAINING,
michael@0 73 PROCESSING,
michael@0 74 ERROR
michael@0 75 };
michael@0 76
michael@0 77 struct cubeb_stream {
michael@0 78 cubeb * context;
michael@0 79 pthread_mutex_t mutex;
michael@0 80 snd_pcm_t * pcm;
michael@0 81 cubeb_data_callback data_callback;
michael@0 82 cubeb_state_callback state_callback;
michael@0 83 void * user_ptr;
michael@0 84 snd_pcm_uframes_t write_position;
michael@0 85 snd_pcm_uframes_t last_position;
michael@0 86 snd_pcm_uframes_t buffer_size;
michael@0 87 snd_pcm_uframes_t period_size;
michael@0 88 cubeb_stream_params params;
michael@0 89
michael@0 90 /* Every member after this comment is protected by the owning context's
michael@0 91 mutex rather than the stream's mutex, or is only used on the context's
michael@0 92 run thread. */
michael@0 93 pthread_cond_t cond; /* Signaled when the stream's state is changed. */
michael@0 94
michael@0 95 enum stream_state state;
michael@0 96
michael@0 97 struct pollfd * saved_fds; /* A copy of the pollfds passed in at init time. */
michael@0 98 struct pollfd * fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */
michael@0 99 nfds_t nfds;
michael@0 100
michael@0 101 struct timeval drain_timeout;
michael@0 102
michael@0 103 /* XXX: Horrible hack -- if an active stream has been idle for
michael@0 104 CUBEB_WATCHDOG_MS it will be disabled and the error callback will be
michael@0 105 called. This works around a bug seen with older versions of ALSA and
michael@0 106 PulseAudio where streams would stop requesting new data despite still
michael@0 107 being logically active and playing. */
michael@0 108 struct timeval last_activity;
michael@0 109 };
michael@0 110
michael@0 111 static int
michael@0 112 any_revents(struct pollfd * fds, nfds_t nfds)
michael@0 113 {
michael@0 114 nfds_t i;
michael@0 115
michael@0 116 for (i = 0; i < nfds; ++i) {
michael@0 117 if (fds[i].revents) {
michael@0 118 return 1;
michael@0 119 }
michael@0 120 }
michael@0 121
michael@0 122 return 0;
michael@0 123 }
michael@0 124
michael@0 125 static int
michael@0 126 cmp_timeval(struct timeval * a, struct timeval * b)
michael@0 127 {
michael@0 128 if (a->tv_sec == b->tv_sec) {
michael@0 129 if (a->tv_usec == b->tv_usec) {
michael@0 130 return 0;
michael@0 131 }
michael@0 132 return a->tv_usec > b->tv_usec ? 1 : -1;
michael@0 133 }
michael@0 134 return a->tv_sec > b->tv_sec ? 1 : -1;
michael@0 135 }
michael@0 136
michael@0 137 static int
michael@0 138 timeval_to_relative_ms(struct timeval * tv)
michael@0 139 {
michael@0 140 struct timeval now;
michael@0 141 struct timeval dt;
michael@0 142 long long t;
michael@0 143 int r;
michael@0 144
michael@0 145 gettimeofday(&now, NULL);
michael@0 146 r = cmp_timeval(tv, &now);
michael@0 147 if (r >= 0) {
michael@0 148 timersub(tv, &now, &dt);
michael@0 149 } else {
michael@0 150 timersub(&now, tv, &dt);
michael@0 151 }
michael@0 152 t = dt.tv_sec;
michael@0 153 t *= 1000;
michael@0 154 t += (dt.tv_usec + 500) / 1000;
michael@0 155
michael@0 156 if (t > INT_MAX) {
michael@0 157 t = INT_MAX;
michael@0 158 } else if (t < INT_MIN) {
michael@0 159 t = INT_MIN;
michael@0 160 }
michael@0 161
michael@0 162 return r >= 0 ? t : -t;
michael@0 163 }
michael@0 164
michael@0 165 static int
michael@0 166 ms_until(struct timeval * tv)
michael@0 167 {
michael@0 168 return timeval_to_relative_ms(tv);
michael@0 169 }
michael@0 170
michael@0 171 static int
michael@0 172 ms_since(struct timeval * tv)
michael@0 173 {
michael@0 174 return -timeval_to_relative_ms(tv);
michael@0 175 }
michael@0 176
michael@0 177 static void
michael@0 178 rebuild(cubeb * ctx)
michael@0 179 {
michael@0 180 nfds_t nfds;
michael@0 181 int i;
michael@0 182 nfds_t j;
michael@0 183 cubeb_stream * stm;
michael@0 184
michael@0 185 assert(ctx->rebuild);
michael@0 186
michael@0 187 /* Always count context's control pipe fd. */
michael@0 188 nfds = 1;
michael@0 189 for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
michael@0 190 stm = ctx->streams[i];
michael@0 191 if (stm) {
michael@0 192 stm->fds = NULL;
michael@0 193 if (stm->state == RUNNING) {
michael@0 194 nfds += stm->nfds;
michael@0 195 }
michael@0 196 }
michael@0 197 }
michael@0 198
michael@0 199 free(ctx->fds);
michael@0 200 ctx->fds = calloc(nfds, sizeof(struct pollfd));
michael@0 201 assert(ctx->fds);
michael@0 202 ctx->nfds = nfds;
michael@0 203
michael@0 204 /* Include context's control pipe fd. */
michael@0 205 ctx->fds[0].fd = ctx->control_fd_read;
michael@0 206 ctx->fds[0].events = POLLIN | POLLERR;
michael@0 207
michael@0 208 for (i = 0, j = 1; i < CUBEB_STREAM_MAX; ++i) {
michael@0 209 stm = ctx->streams[i];
michael@0 210 if (stm && stm->state == RUNNING) {
michael@0 211 memcpy(&ctx->fds[j], stm->saved_fds, stm->nfds * sizeof(struct pollfd));
michael@0 212 stm->fds = &ctx->fds[j];
michael@0 213 j += stm->nfds;
michael@0 214 }
michael@0 215 }
michael@0 216
michael@0 217 ctx->rebuild = 0;
michael@0 218 }
michael@0 219
michael@0 220 static void
michael@0 221 poll_wake(cubeb * ctx)
michael@0 222 {
michael@0 223 write(ctx->control_fd_write, "x", 1);
michael@0 224 }
michael@0 225
michael@0 226 static void
michael@0 227 set_timeout(struct timeval * timeout, unsigned int ms)
michael@0 228 {
michael@0 229 gettimeofday(timeout, NULL);
michael@0 230 timeout->tv_sec += ms / 1000;
michael@0 231 timeout->tv_usec += (ms % 1000) * 1000;
michael@0 232 }
michael@0 233
michael@0 234 static void
michael@0 235 alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
michael@0 236 {
michael@0 237 cubeb * ctx;
michael@0 238 int r;
michael@0 239
michael@0 240 ctx = stm->context;
michael@0 241 stm->state = state;
michael@0 242 r = pthread_cond_broadcast(&stm->cond);
michael@0 243 assert(r == 0);
michael@0 244 ctx->rebuild = 1;
michael@0 245 poll_wake(ctx);
michael@0 246 }
michael@0 247
michael@0 248 static enum stream_state
michael@0 249 alsa_refill_stream(cubeb_stream * stm)
michael@0 250 {
michael@0 251 int r;
michael@0 252 unsigned short revents;
michael@0 253 snd_pcm_sframes_t avail;
michael@0 254 long got;
michael@0 255 void * p;
michael@0 256 int draining;
michael@0 257
michael@0 258 draining = 0;
michael@0 259
michael@0 260 pthread_mutex_lock(&stm->mutex);
michael@0 261
michael@0 262 r = snd_pcm_poll_descriptors_revents(stm->pcm, stm->fds, stm->nfds, &revents);
michael@0 263 if (r < 0 || revents != POLLOUT) {
michael@0 264 /* This should be a stream error; it makes no sense for poll(2) to wake
michael@0 265 for this stream and then have the stream report that it's not ready.
michael@0 266 Unfortunately, this does happen, so just bail out and try again. */
michael@0 267 pthread_mutex_unlock(&stm->mutex);
michael@0 268 return RUNNING;
michael@0 269 }
michael@0 270
michael@0 271 avail = snd_pcm_avail_update(stm->pcm);
michael@0 272 if (avail == -EPIPE) {
michael@0 273 snd_pcm_recover(stm->pcm, avail, 1);
michael@0 274 avail = snd_pcm_avail_update(stm->pcm);
michael@0 275 }
michael@0 276
michael@0 277 /* Failed to recover from an xrun, this stream must be broken. */
michael@0 278 if (avail < 0) {
michael@0 279 pthread_mutex_unlock(&stm->mutex);
michael@0 280 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
michael@0 281 return ERROR;
michael@0 282 }
michael@0 283
michael@0 284 /* This should never happen. */
michael@0 285 if ((unsigned int) avail > stm->buffer_size) {
michael@0 286 avail = stm->buffer_size;
michael@0 287 }
michael@0 288
michael@0 289 /* poll(2) claims this stream is active, so there should be some space
michael@0 290 available to write. If avail is still zero here, the stream must be in
michael@0 291 a funky state, so recover and try again. */
michael@0 292 if (avail == 0) {
michael@0 293 snd_pcm_recover(stm->pcm, -EPIPE, 1);
michael@0 294 avail = snd_pcm_avail_update(stm->pcm);
michael@0 295 if (avail <= 0) {
michael@0 296 pthread_mutex_unlock(&stm->mutex);
michael@0 297 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
michael@0 298 return ERROR;
michael@0 299 }
michael@0 300 }
michael@0 301
michael@0 302 p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail));
michael@0 303 assert(p);
michael@0 304
michael@0 305 pthread_mutex_unlock(&stm->mutex);
michael@0 306 got = stm->data_callback(stm, stm->user_ptr, p, avail);
michael@0 307 pthread_mutex_lock(&stm->mutex);
michael@0 308 if (got < 0) {
michael@0 309 pthread_mutex_unlock(&stm->mutex);
michael@0 310 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
michael@0 311 return ERROR;
michael@0 312 }
michael@0 313 if (got > 0) {
michael@0 314 snd_pcm_sframes_t wrote = snd_pcm_writei(stm->pcm, p, got);
michael@0 315 if (wrote == -EPIPE) {
michael@0 316 snd_pcm_recover(stm->pcm, wrote, 1);
michael@0 317 wrote = snd_pcm_writei(stm->pcm, p, got);
michael@0 318 }
michael@0 319 assert(wrote >= 0 && wrote == got);
michael@0 320 stm->write_position += wrote;
michael@0 321 gettimeofday(&stm->last_activity, NULL);
michael@0 322 }
michael@0 323 if (got != avail) {
michael@0 324 long buffer_fill = stm->buffer_size - (avail - got);
michael@0 325 double buffer_time = (double) buffer_fill / stm->params.rate;
michael@0 326
michael@0 327 /* Fill the remaining buffer with silence to guarantee one full period
michael@0 328 has been written. */
michael@0 329 snd_pcm_writei(stm->pcm, (char *) p + got, avail - got);
michael@0 330
michael@0 331 set_timeout(&stm->drain_timeout, buffer_time * 1000);
michael@0 332
michael@0 333 draining = 1;
michael@0 334 }
michael@0 335
michael@0 336 free(p);
michael@0 337 pthread_mutex_unlock(&stm->mutex);
michael@0 338 return draining ? DRAINING : RUNNING;
michael@0 339 }
michael@0 340
michael@0 341 static int
michael@0 342 alsa_run(cubeb * ctx)
michael@0 343 {
michael@0 344 int r;
michael@0 345 int timeout;
michael@0 346 int i;
michael@0 347 char dummy;
michael@0 348 cubeb_stream * stm;
michael@0 349 enum stream_state state;
michael@0 350
michael@0 351 pthread_mutex_lock(&ctx->mutex);
michael@0 352
michael@0 353 if (ctx->rebuild) {
michael@0 354 rebuild(ctx);
michael@0 355 }
michael@0 356
michael@0 357 /* Wake up at least once per second for the watchdog. */
michael@0 358 timeout = 1000;
michael@0 359 for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
michael@0 360 stm = ctx->streams[i];
michael@0 361 if (stm && stm->state == DRAINING) {
michael@0 362 r = ms_until(&stm->drain_timeout);
michael@0 363 if (r >= 0 && timeout > r) {
michael@0 364 timeout = r;
michael@0 365 }
michael@0 366 }
michael@0 367 }
michael@0 368
michael@0 369 pthread_mutex_unlock(&ctx->mutex);
michael@0 370 r = poll(ctx->fds, ctx->nfds, timeout);
michael@0 371 pthread_mutex_lock(&ctx->mutex);
michael@0 372
michael@0 373 if (r > 0) {
michael@0 374 if (ctx->fds[0].revents & POLLIN) {
michael@0 375 read(ctx->control_fd_read, &dummy, 1);
michael@0 376
michael@0 377 if (ctx->shutdown) {
michael@0 378 pthread_mutex_unlock(&ctx->mutex);
michael@0 379 return -1;
michael@0 380 }
michael@0 381 }
michael@0 382
michael@0 383 for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
michael@0 384 stm = ctx->streams[i];
michael@0 385 if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) {
michael@0 386 alsa_set_stream_state(stm, PROCESSING);
michael@0 387 pthread_mutex_unlock(&ctx->mutex);
michael@0 388 state = alsa_refill_stream(stm);
michael@0 389 pthread_mutex_lock(&ctx->mutex);
michael@0 390 alsa_set_stream_state(stm, state);
michael@0 391 }
michael@0 392 }
michael@0 393 } else if (r == 0) {
michael@0 394 for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
michael@0 395 stm = ctx->streams[i];
michael@0 396 if (stm) {
michael@0 397 if (stm->state == DRAINING && ms_since(&stm->drain_timeout) >= 0) {
michael@0 398 alsa_set_stream_state(stm, INACTIVE);
michael@0 399 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
michael@0 400 } else if (stm->state == RUNNING && ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) {
michael@0 401 alsa_set_stream_state(stm, ERROR);
michael@0 402 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
michael@0 403 }
michael@0 404 }
michael@0 405 }
michael@0 406 }
michael@0 407
michael@0 408 pthread_mutex_unlock(&ctx->mutex);
michael@0 409
michael@0 410 return 0;
michael@0 411 }
michael@0 412
michael@0 413 static void *
michael@0 414 alsa_run_thread(void * context)
michael@0 415 {
michael@0 416 cubeb * ctx = context;
michael@0 417 int r;
michael@0 418
michael@0 419 do {
michael@0 420 r = alsa_run(ctx);
michael@0 421 } while (r >= 0);
michael@0 422
michael@0 423 return NULL;
michael@0 424 }
michael@0 425
michael@0 426 static snd_config_t *
michael@0 427 get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
michael@0 428 {
michael@0 429 int r;
michael@0 430 snd_config_t * slave_pcm;
michael@0 431 snd_config_t * slave_def;
michael@0 432 snd_config_t * pcm;
michael@0 433 char const * string;
michael@0 434 char node_name[64];
michael@0 435
michael@0 436 slave_def = NULL;
michael@0 437
michael@0 438 r = snd_config_search(root_pcm, "slave", &slave_pcm);
michael@0 439 if (r < 0) {
michael@0 440 return NULL;
michael@0 441 }
michael@0 442
michael@0 443 r = snd_config_get_string(slave_pcm, &string);
michael@0 444 if (r >= 0) {
michael@0 445 r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def);
michael@0 446 if (r < 0) {
michael@0 447 return NULL;
michael@0 448 }
michael@0 449 }
michael@0 450
michael@0 451 do {
michael@0 452 r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
michael@0 453 if (r < 0) {
michael@0 454 break;
michael@0 455 }
michael@0 456
michael@0 457 r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string);
michael@0 458 if (r < 0) {
michael@0 459 break;
michael@0 460 }
michael@0 461
michael@0 462 r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
michael@0 463 if (r < 0 || r > (int) sizeof(node_name)) {
michael@0 464 break;
michael@0 465 }
michael@0 466 r = snd_config_search(lconf, node_name, &pcm);
michael@0 467 if (r < 0) {
michael@0 468 break;
michael@0 469 }
michael@0 470
michael@0 471 return pcm;
michael@0 472 } while (0);
michael@0 473
michael@0 474 if (slave_def) {
michael@0 475 snd_config_delete(slave_def);
michael@0 476 }
michael@0 477
michael@0 478 return NULL;
michael@0 479 }
michael@0 480
michael@0 481 /* Work around PulseAudio ALSA plugin bug where the PA server forces a
michael@0 482 higher than requested latency, but the plugin does not update its (and
michael@0 483 ALSA's) internal state to reflect that, leading to an immediate underrun
michael@0 484 situation. Inspired by WINE's make_handle_underrun_config.
michael@0 485 Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05 */
michael@0 486 static snd_config_t *
michael@0 487 init_local_config_with_workaround(char const * pcm_name)
michael@0 488 {
michael@0 489 int r;
michael@0 490 snd_config_t * lconf;
michael@0 491 snd_config_t * pcm_node;
michael@0 492 snd_config_t * node;
michael@0 493 char const * string;
michael@0 494 char node_name[64];
michael@0 495
michael@0 496 lconf = NULL;
michael@0 497
michael@0 498 if (snd_config == NULL) {
michael@0 499 return NULL;
michael@0 500 }
michael@0 501
michael@0 502 r = snd_config_copy(&lconf, snd_config);
michael@0 503 if (r < 0) {
michael@0 504 return NULL;
michael@0 505 }
michael@0 506
michael@0 507 do {
michael@0 508 r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node);
michael@0 509 if (r < 0) {
michael@0 510 break;
michael@0 511 }
michael@0 512
michael@0 513 r = snd_config_get_id(pcm_node, &string);
michael@0 514 if (r < 0) {
michael@0 515 break;
michael@0 516 }
michael@0 517
michael@0 518 r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
michael@0 519 if (r < 0 || r > (int) sizeof(node_name)) {
michael@0 520 break;
michael@0 521 }
michael@0 522 r = snd_config_search(lconf, node_name, &pcm_node);
michael@0 523 if (r < 0) {
michael@0 524 break;
michael@0 525 }
michael@0 526
michael@0 527 /* If this PCM has a slave, walk the slave configurations until we reach the bottom. */
michael@0 528 while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) {
michael@0 529 pcm_node = node;
michael@0 530 }
michael@0 531
michael@0 532 /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */
michael@0 533 r = snd_config_search(pcm_node, "type", &node);
michael@0 534 if (r < 0) {
michael@0 535 break;
michael@0 536 }
michael@0 537
michael@0 538 r = snd_config_get_string(node, &string);
michael@0 539 if (r < 0) {
michael@0 540 break;
michael@0 541 }
michael@0 542
michael@0 543 if (strcmp(string, "pulse") != 0) {
michael@0 544 break;
michael@0 545 }
michael@0 546
michael@0 547 /* Don't clobber an explicit existing handle_underrun value, set it only
michael@0 548 if it doesn't already exist. */
michael@0 549 r = snd_config_search(pcm_node, "handle_underrun", &node);
michael@0 550 if (r != -ENOENT) {
michael@0 551 break;
michael@0 552 }
michael@0 553
michael@0 554 /* Disable pcm_pulse's asynchronous underrun handling. */
michael@0 555 r = snd_config_imake_integer(&node, "handle_underrun", 0);
michael@0 556 if (r < 0) {
michael@0 557 break;
michael@0 558 }
michael@0 559
michael@0 560 r = snd_config_add(pcm_node, node);
michael@0 561 if (r < 0) {
michael@0 562 break;
michael@0 563 }
michael@0 564
michael@0 565 return lconf;
michael@0 566 } while (0);
michael@0 567
michael@0 568 snd_config_delete(lconf);
michael@0 569
michael@0 570 return NULL;
michael@0 571 }
michael@0 572
michael@0 573 static int
michael@0 574 alsa_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config)
michael@0 575 {
michael@0 576 int r;
michael@0 577
michael@0 578 pthread_mutex_lock(&cubeb_alsa_mutex);
michael@0 579 if (local_config) {
michael@0 580 r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config);
michael@0 581 } else {
michael@0 582 r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK);
michael@0 583 }
michael@0 584 pthread_mutex_unlock(&cubeb_alsa_mutex);
michael@0 585
michael@0 586 return r;
michael@0 587 }
michael@0 588
michael@0 589 static int
michael@0 590 alsa_locked_pcm_close(snd_pcm_t * pcm)
michael@0 591 {
michael@0 592 int r;
michael@0 593
michael@0 594 pthread_mutex_lock(&cubeb_alsa_mutex);
michael@0 595 r = snd_pcm_close(pcm);
michael@0 596 pthread_mutex_unlock(&cubeb_alsa_mutex);
michael@0 597
michael@0 598 return r;
michael@0 599 }
michael@0 600
michael@0 601 static int
michael@0 602 alsa_register_stream(cubeb * ctx, cubeb_stream * stm)
michael@0 603 {
michael@0 604 int i;
michael@0 605
michael@0 606 pthread_mutex_lock(&ctx->mutex);
michael@0 607 for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
michael@0 608 if (!ctx->streams[i]) {
michael@0 609 ctx->streams[i] = stm;
michael@0 610 break;
michael@0 611 }
michael@0 612 }
michael@0 613 pthread_mutex_unlock(&ctx->mutex);
michael@0 614
michael@0 615 return i == CUBEB_STREAM_MAX;
michael@0 616 }
michael@0 617
michael@0 618 static void
michael@0 619 alsa_unregister_stream(cubeb_stream * stm)
michael@0 620 {
michael@0 621 cubeb * ctx;
michael@0 622 int i;
michael@0 623
michael@0 624 ctx = stm->context;
michael@0 625
michael@0 626 pthread_mutex_lock(&ctx->mutex);
michael@0 627 for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
michael@0 628 if (ctx->streams[i] == stm) {
michael@0 629 ctx->streams[i] = NULL;
michael@0 630 break;
michael@0 631 }
michael@0 632 }
michael@0 633 pthread_mutex_unlock(&ctx->mutex);
michael@0 634 }
michael@0 635
michael@0 636 static void
michael@0 637 silent_error_handler(char const * file, int line, char const * function,
michael@0 638 int err, char const * fmt, ...)
michael@0 639 {
michael@0 640 }
michael@0 641
michael@0 642 /*static*/ int
michael@0 643 alsa_init(cubeb ** context, char const * context_name)
michael@0 644 {
michael@0 645 cubeb * ctx;
michael@0 646 int r;
michael@0 647 int i;
michael@0 648 int fd[2];
michael@0 649 pthread_attr_t attr;
michael@0 650 snd_pcm_t * dummy;
michael@0 651
michael@0 652 assert(context);
michael@0 653 *context = NULL;
michael@0 654
michael@0 655 pthread_mutex_lock(&cubeb_alsa_mutex);
michael@0 656 if (!cubeb_alsa_error_handler_set) {
michael@0 657 snd_lib_error_set_handler(silent_error_handler);
michael@0 658 cubeb_alsa_error_handler_set = 1;
michael@0 659 }
michael@0 660 pthread_mutex_unlock(&cubeb_alsa_mutex);
michael@0 661
michael@0 662 ctx = calloc(1, sizeof(*ctx));
michael@0 663 assert(ctx);
michael@0 664
michael@0 665 ctx->ops = &alsa_ops;
michael@0 666
michael@0 667 r = pthread_mutex_init(&ctx->mutex, NULL);
michael@0 668 assert(r == 0);
michael@0 669
michael@0 670 r = pipe(fd);
michael@0 671 assert(r == 0);
michael@0 672
michael@0 673 for (i = 0; i < 2; ++i) {
michael@0 674 fcntl(fd[i], F_SETFD, fcntl(fd[i], F_GETFD) | FD_CLOEXEC);
michael@0 675 fcntl(fd[i], F_SETFL, fcntl(fd[i], F_GETFL) | O_NONBLOCK);
michael@0 676 }
michael@0 677
michael@0 678 ctx->control_fd_read = fd[0];
michael@0 679 ctx->control_fd_write = fd[1];
michael@0 680
michael@0 681 /* Force an early rebuild when alsa_run is first called to ensure fds and
michael@0 682 nfds have been initialized. */
michael@0 683 ctx->rebuild = 1;
michael@0 684
michael@0 685 r = pthread_attr_init(&attr);
michael@0 686 assert(r == 0);
michael@0 687
michael@0 688 r = pthread_attr_setstacksize(&attr, 256 * 1024);
michael@0 689 assert(r == 0);
michael@0 690
michael@0 691 r = pthread_create(&ctx->thread, &attr, alsa_run_thread, ctx);
michael@0 692 assert(r == 0);
michael@0 693
michael@0 694 r = pthread_attr_destroy(&attr);
michael@0 695 assert(r == 0);
michael@0 696
michael@0 697 /* Open a dummy PCM to force the configuration space to be evaluated so that
michael@0 698 init_local_config_with_workaround can find and modify the default node. */
michael@0 699 r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL);
michael@0 700 if (r >= 0) {
michael@0 701 alsa_locked_pcm_close(dummy);
michael@0 702 }
michael@0 703 ctx->is_pa = 0;
michael@0 704 pthread_mutex_lock(&cubeb_alsa_mutex);
michael@0 705 ctx->local_config = init_local_config_with_workaround(CUBEB_ALSA_PCM_NAME);
michael@0 706 pthread_mutex_unlock(&cubeb_alsa_mutex);
michael@0 707 if (ctx->local_config) {
michael@0 708 ctx->is_pa = 1;
michael@0 709 r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
michael@0 710 /* If we got a local_config, we found a PA PCM. If opening a PCM with that
michael@0 711 config fails with EINVAL, the PA PCM is too old for this workaround. */
michael@0 712 if (r == -EINVAL) {
michael@0 713 pthread_mutex_lock(&cubeb_alsa_mutex);
michael@0 714 snd_config_delete(ctx->local_config);
michael@0 715 pthread_mutex_unlock(&cubeb_alsa_mutex);
michael@0 716 ctx->local_config = NULL;
michael@0 717 } else if (r >= 0) {
michael@0 718 alsa_locked_pcm_close(dummy);
michael@0 719 }
michael@0 720 }
michael@0 721
michael@0 722 *context = ctx;
michael@0 723
michael@0 724 return CUBEB_OK;
michael@0 725 }
michael@0 726
michael@0 727 static char const *
michael@0 728 alsa_get_backend_id(cubeb * ctx)
michael@0 729 {
michael@0 730 return "alsa";
michael@0 731 }
michael@0 732
michael@0 733 static void
michael@0 734 alsa_destroy(cubeb * ctx)
michael@0 735 {
michael@0 736 int r;
michael@0 737
michael@0 738 assert(ctx);
michael@0 739
michael@0 740 pthread_mutex_lock(&ctx->mutex);
michael@0 741 ctx->shutdown = 1;
michael@0 742 poll_wake(ctx);
michael@0 743 pthread_mutex_unlock(&ctx->mutex);
michael@0 744
michael@0 745 r = pthread_join(ctx->thread, NULL);
michael@0 746 assert(r == 0);
michael@0 747
michael@0 748 close(ctx->control_fd_read);
michael@0 749 close(ctx->control_fd_write);
michael@0 750 pthread_mutex_destroy(&ctx->mutex);
michael@0 751 free(ctx->fds);
michael@0 752
michael@0 753 if (ctx->local_config) {
michael@0 754 pthread_mutex_lock(&cubeb_alsa_mutex);
michael@0 755 snd_config_delete(ctx->local_config);
michael@0 756 pthread_mutex_unlock(&cubeb_alsa_mutex);
michael@0 757 }
michael@0 758
michael@0 759 free(ctx);
michael@0 760 }
michael@0 761
michael@0 762 static void alsa_stream_destroy(cubeb_stream * stm);
michael@0 763
michael@0 764 static int
michael@0 765 alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
michael@0 766 cubeb_stream_params stream_params, unsigned int latency,
michael@0 767 cubeb_data_callback data_callback, cubeb_state_callback state_callback,
michael@0 768 void * user_ptr)
michael@0 769 {
michael@0 770 cubeb_stream * stm;
michael@0 771 int r;
michael@0 772 snd_pcm_format_t format;
michael@0 773
michael@0 774 assert(ctx && stream);
michael@0 775
michael@0 776 *stream = NULL;
michael@0 777
michael@0 778 switch (stream_params.format) {
michael@0 779 case CUBEB_SAMPLE_S16LE:
michael@0 780 format = SND_PCM_FORMAT_S16_LE;
michael@0 781 break;
michael@0 782 case CUBEB_SAMPLE_S16BE:
michael@0 783 format = SND_PCM_FORMAT_S16_BE;
michael@0 784 break;
michael@0 785 case CUBEB_SAMPLE_FLOAT32LE:
michael@0 786 format = SND_PCM_FORMAT_FLOAT_LE;
michael@0 787 break;
michael@0 788 case CUBEB_SAMPLE_FLOAT32BE:
michael@0 789 format = SND_PCM_FORMAT_FLOAT_BE;
michael@0 790 break;
michael@0 791 default:
michael@0 792 return CUBEB_ERROR_INVALID_FORMAT;
michael@0 793 }
michael@0 794
michael@0 795 pthread_mutex_lock(&ctx->mutex);
michael@0 796 if (ctx->active_streams >= CUBEB_STREAM_MAX) {
michael@0 797 pthread_mutex_unlock(&ctx->mutex);
michael@0 798 return CUBEB_ERROR;
michael@0 799 }
michael@0 800 ctx->active_streams += 1;
michael@0 801 pthread_mutex_unlock(&ctx->mutex);
michael@0 802
michael@0 803 stm = calloc(1, sizeof(*stm));
michael@0 804 assert(stm);
michael@0 805
michael@0 806 stm->context = ctx;
michael@0 807 stm->data_callback = data_callback;
michael@0 808 stm->state_callback = state_callback;
michael@0 809 stm->user_ptr = user_ptr;
michael@0 810 stm->params = stream_params;
michael@0 811 stm->state = INACTIVE;
michael@0 812
michael@0 813 r = pthread_mutex_init(&stm->mutex, NULL);
michael@0 814 assert(r == 0);
michael@0 815
michael@0 816 r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
michael@0 817 if (r < 0) {
michael@0 818 alsa_stream_destroy(stm);
michael@0 819 return CUBEB_ERROR;
michael@0 820 }
michael@0 821
michael@0 822 r = snd_pcm_nonblock(stm->pcm, 1);
michael@0 823 assert(r == 0);
michael@0 824
michael@0 825 /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't
michael@0 826 possibly work. See https://bugzilla.mozilla.org/show_bug.cgi?id=761274.
michael@0 827 Only resort to this hack if the handle_underrun workaround failed. */
michael@0 828 if (!ctx->local_config && ctx->is_pa) {
michael@0 829 latency = latency < 500 ? 500 : latency;
michael@0 830 }
michael@0 831
michael@0 832 r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
michael@0 833 stm->params.channels, stm->params.rate, 1,
michael@0 834 latency * 1000);
michael@0 835 if (r < 0) {
michael@0 836 alsa_stream_destroy(stm);
michael@0 837 return CUBEB_ERROR_INVALID_FORMAT;
michael@0 838 }
michael@0 839
michael@0 840 r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &stm->period_size);
michael@0 841 assert(r == 0);
michael@0 842
michael@0 843 stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm);
michael@0 844 assert(stm->nfds > 0);
michael@0 845
michael@0 846 stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd));
michael@0 847 assert(stm->saved_fds);
michael@0 848 r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds);
michael@0 849 assert((nfds_t) r == stm->nfds);
michael@0 850
michael@0 851 r = pthread_cond_init(&stm->cond, NULL);
michael@0 852 assert(r == 0);
michael@0 853
michael@0 854 if (alsa_register_stream(ctx, stm) != 0) {
michael@0 855 alsa_stream_destroy(stm);
michael@0 856 return CUBEB_ERROR;
michael@0 857 }
michael@0 858
michael@0 859 *stream = stm;
michael@0 860
michael@0 861 return CUBEB_OK;
michael@0 862 }
michael@0 863
michael@0 864 static void
michael@0 865 alsa_stream_destroy(cubeb_stream * stm)
michael@0 866 {
michael@0 867 int r;
michael@0 868 cubeb * ctx;
michael@0 869
michael@0 870 assert(stm && (stm->state == INACTIVE || stm->state == ERROR));
michael@0 871
michael@0 872 ctx = stm->context;
michael@0 873
michael@0 874 pthread_mutex_lock(&stm->mutex);
michael@0 875 if (stm->pcm) {
michael@0 876 alsa_locked_pcm_close(stm->pcm);
michael@0 877 stm->pcm = NULL;
michael@0 878 }
michael@0 879 free(stm->saved_fds);
michael@0 880 pthread_mutex_unlock(&stm->mutex);
michael@0 881 pthread_mutex_destroy(&stm->mutex);
michael@0 882
michael@0 883 r = pthread_cond_destroy(&stm->cond);
michael@0 884 assert(r == 0);
michael@0 885
michael@0 886 alsa_unregister_stream(stm);
michael@0 887
michael@0 888 pthread_mutex_lock(&ctx->mutex);
michael@0 889 assert(ctx->active_streams >= 1);
michael@0 890 ctx->active_streams -= 1;
michael@0 891 pthread_mutex_unlock(&ctx->mutex);
michael@0 892
michael@0 893 free(stm);
michael@0 894 }
michael@0 895
michael@0 896 static int
michael@0 897 alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
michael@0 898 {
michael@0 899 int rv;
michael@0 900 cubeb_stream * stm;
michael@0 901 snd_pcm_hw_params_t* hw_params;
michael@0 902 cubeb_stream_params params;
michael@0 903 params.rate = 44100;
michael@0 904 params.format = CUBEB_SAMPLE_FLOAT32NE;
michael@0 905 params.channels = 2;
michael@0 906
michael@0 907 snd_pcm_hw_params_alloca(&hw_params);
michael@0 908
michael@0 909 assert(ctx);
michael@0 910
michael@0 911 rv = alsa_stream_init(ctx, &stm, "", params, 100, NULL, NULL, NULL);
michael@0 912 if (rv != CUBEB_OK) {
michael@0 913 return CUBEB_ERROR;
michael@0 914 }
michael@0 915
michael@0 916 rv = snd_pcm_hw_params_any(stm->pcm, hw_params);
michael@0 917 if (rv < 0) {
michael@0 918 return CUBEB_ERROR;
michael@0 919 }
michael@0 920
michael@0 921 rv = snd_pcm_hw_params_get_channels_max(hw_params, max_channels);
michael@0 922 if (rv < 0) {
michael@0 923 return CUBEB_ERROR;
michael@0 924 }
michael@0 925
michael@0 926 alsa_stream_destroy(stm);
michael@0 927
michael@0 928 return CUBEB_OK;
michael@0 929 }
michael@0 930
michael@0 931 static int
michael@0 932 alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
michael@0 933 int rv, dir;
michael@0 934 snd_pcm_t * pcm;
michael@0 935 snd_pcm_hw_params_t * hw_params;
michael@0 936
michael@0 937 snd_pcm_hw_params_alloca(&hw_params);
michael@0 938
michael@0 939 /* get a pcm, disabling resampling, so we get a rate the
michael@0 940 * hardware/dmix/pulse/etc. supports. */
michael@0 941 rv = snd_pcm_open(&pcm, "", SND_PCM_STREAM_PLAYBACK | SND_PCM_NO_AUTO_RESAMPLE, 0);
michael@0 942 if (rv < 0) {
michael@0 943 return CUBEB_ERROR;
michael@0 944 }
michael@0 945
michael@0 946 rv = snd_pcm_hw_params_any(pcm, hw_params);
michael@0 947 if (rv < 0) {
michael@0 948 snd_pcm_close(pcm);
michael@0 949 return CUBEB_ERROR;
michael@0 950 }
michael@0 951
michael@0 952 rv = snd_pcm_hw_params_get_rate(hw_params, rate, &dir);
michael@0 953 if (rv >= 0) {
michael@0 954 /* There is a default rate: use it. */
michael@0 955 snd_pcm_close(pcm);
michael@0 956 return CUBEB_OK;
michael@0 957 }
michael@0 958
michael@0 959 /* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */
michael@0 960 *rate = 44100;
michael@0 961
michael@0 962 rv = snd_pcm_hw_params_set_rate_near(pcm, hw_params, rate, NULL);
michael@0 963 if (rv < 0) {
michael@0 964 snd_pcm_close(pcm);
michael@0 965 return CUBEB_ERROR;
michael@0 966 }
michael@0 967
michael@0 968 snd_pcm_close(pcm);
michael@0 969
michael@0 970 return CUBEB_OK;
michael@0 971 }
michael@0 972
michael@0 973 static int
michael@0 974 alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
michael@0 975 {
michael@0 976 /* This is found to be an acceptable minimum, even on a super low-end
michael@0 977 * machine. */
michael@0 978 *latency_ms = 40;
michael@0 979
michael@0 980 return CUBEB_OK;
michael@0 981 }
michael@0 982
michael@0 983 static int
michael@0 984 alsa_stream_start(cubeb_stream * stm)
michael@0 985 {
michael@0 986 cubeb * ctx;
michael@0 987
michael@0 988 assert(stm);
michael@0 989 ctx = stm->context;
michael@0 990
michael@0 991 pthread_mutex_lock(&stm->mutex);
michael@0 992 snd_pcm_pause(stm->pcm, 0);
michael@0 993 gettimeofday(&stm->last_activity, NULL);
michael@0 994 pthread_mutex_unlock(&stm->mutex);
michael@0 995
michael@0 996 pthread_mutex_lock(&ctx->mutex);
michael@0 997 if (stm->state != INACTIVE) {
michael@0 998 pthread_mutex_unlock(&ctx->mutex);
michael@0 999 return CUBEB_ERROR;
michael@0 1000 }
michael@0 1001 alsa_set_stream_state(stm, RUNNING);
michael@0 1002 pthread_mutex_unlock(&ctx->mutex);
michael@0 1003
michael@0 1004 return CUBEB_OK;
michael@0 1005 }
michael@0 1006
michael@0 1007 static int
michael@0 1008 alsa_stream_stop(cubeb_stream * stm)
michael@0 1009 {
michael@0 1010 cubeb * ctx;
michael@0 1011 int r;
michael@0 1012
michael@0 1013 assert(stm);
michael@0 1014 ctx = stm->context;
michael@0 1015
michael@0 1016 pthread_mutex_lock(&ctx->mutex);
michael@0 1017 while (stm->state == PROCESSING) {
michael@0 1018 r = pthread_cond_wait(&stm->cond, &ctx->mutex);
michael@0 1019 assert(r == 0);
michael@0 1020 }
michael@0 1021
michael@0 1022 alsa_set_stream_state(stm, INACTIVE);
michael@0 1023 pthread_mutex_unlock(&ctx->mutex);
michael@0 1024
michael@0 1025 pthread_mutex_lock(&stm->mutex);
michael@0 1026 snd_pcm_pause(stm->pcm, 1);
michael@0 1027 pthread_mutex_unlock(&stm->mutex);
michael@0 1028
michael@0 1029 return CUBEB_OK;
michael@0 1030 }
michael@0 1031
michael@0 1032 static int
michael@0 1033 alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
michael@0 1034 {
michael@0 1035 snd_pcm_sframes_t delay;
michael@0 1036
michael@0 1037 assert(stm && position);
michael@0 1038
michael@0 1039 pthread_mutex_lock(&stm->mutex);
michael@0 1040
michael@0 1041 delay = -1;
michael@0 1042 if (snd_pcm_state(stm->pcm) != SND_PCM_STATE_RUNNING ||
michael@0 1043 snd_pcm_delay(stm->pcm, &delay) != 0) {
michael@0 1044 *position = stm->last_position;
michael@0 1045 pthread_mutex_unlock(&stm->mutex);
michael@0 1046 return CUBEB_OK;
michael@0 1047 }
michael@0 1048
michael@0 1049 assert(delay >= 0);
michael@0 1050
michael@0 1051 *position = 0;
michael@0 1052 if (stm->write_position >= (snd_pcm_uframes_t) delay) {
michael@0 1053 *position = stm->write_position - delay;
michael@0 1054 }
michael@0 1055
michael@0 1056 stm->last_position = *position;
michael@0 1057
michael@0 1058 pthread_mutex_unlock(&stm->mutex);
michael@0 1059 return CUBEB_OK;
michael@0 1060 }
michael@0 1061
michael@0 1062 int
michael@0 1063 alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
michael@0 1064 {
michael@0 1065 snd_pcm_sframes_t delay;
michael@0 1066 /* This function returns the delay in frames until a frame written using
michael@0 1067 snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */
michael@0 1068 if (snd_pcm_delay(stm->pcm, &delay)) {
michael@0 1069 return CUBEB_ERROR;
michael@0 1070 }
michael@0 1071
michael@0 1072 *latency = delay;
michael@0 1073
michael@0 1074 return CUBEB_OK;
michael@0 1075 }
michael@0 1076
michael@0 1077 static struct cubeb_ops const alsa_ops = {
michael@0 1078 .init = alsa_init,
michael@0 1079 .get_backend_id = alsa_get_backend_id,
michael@0 1080 .get_max_channel_count = alsa_get_max_channel_count,
michael@0 1081 .get_min_latency = alsa_get_min_latency,
michael@0 1082 .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
michael@0 1083 .destroy = alsa_destroy,
michael@0 1084 .stream_init = alsa_stream_init,
michael@0 1085 .stream_destroy = alsa_stream_destroy,
michael@0 1086 .stream_start = alsa_stream_start,
michael@0 1087 .stream_stop = alsa_stream_stop,
michael@0 1088 .stream_get_position = alsa_stream_get_position,
michael@0 1089 .stream_get_latency = alsa_stream_get_latency
michael@0 1090 };

mercurial