cubeb_alsa.c (40461B)
1 /* 2 * Copyright © 2011 Mozilla Foundation 3 * 4 * This program is made available under an ISC-style license. See the 5 * accompanying file LICENSE for details. 6 */ 7 #undef NDEBUG 8 #define _DEFAULT_SOURCE 9 #define _BSD_SOURCE 10 #if defined(__NetBSD__) 11 #define _NETBSD_SOURCE /* timersub() */ 12 #endif 13 #define _XOPEN_SOURCE 500 14 #include "cubeb-internal.h" 15 #include "cubeb/cubeb.h" 16 #include "cubeb_tracing.h" 17 #include <alsa/asoundlib.h> 18 #include <assert.h> 19 #include <dlfcn.h> 20 #include <limits.h> 21 #include <poll.h> 22 #include <pthread.h> 23 #include <sys/time.h> 24 #include <unistd.h> 25 26 #ifdef DISABLE_LIBASOUND_DLOPEN 27 #define WRAP(x) x 28 #else 29 #define WRAP(x) (*cubeb_##x) 30 #define LIBASOUND_API_VISIT(X) \ 31 X(snd_config) \ 32 X(snd_config_add) \ 33 X(snd_config_copy) \ 34 X(snd_config_delete) \ 35 X(snd_config_get_id) \ 36 X(snd_config_get_string) \ 37 X(snd_config_imake_integer) \ 38 X(snd_config_search) \ 39 X(snd_config_search_definition) \ 40 X(snd_lib_error_set_handler) \ 41 X(snd_pcm_avail_update) \ 42 X(snd_pcm_close) \ 43 X(snd_pcm_delay) \ 44 X(snd_pcm_drain) \ 45 X(snd_pcm_frames_to_bytes) \ 46 X(snd_pcm_get_params) \ 47 X(snd_pcm_hw_params_any) \ 48 X(snd_pcm_hw_params_get_channels_max) \ 49 X(snd_pcm_hw_params_get_rate) \ 50 X(snd_pcm_hw_params_set_rate_near) \ 51 X(snd_pcm_hw_params_sizeof) \ 52 X(snd_pcm_nonblock) \ 53 X(snd_pcm_open) \ 54 X(snd_pcm_open_lconf) \ 55 X(snd_pcm_pause) \ 56 X(snd_pcm_poll_descriptors) \ 57 X(snd_pcm_poll_descriptors_count) \ 58 X(snd_pcm_poll_descriptors_revents) \ 59 X(snd_pcm_readi) \ 60 X(snd_pcm_recover) \ 61 X(snd_pcm_set_params) \ 62 X(snd_pcm_start) \ 63 X(snd_pcm_state) \ 64 X(snd_pcm_writei) 65 66 #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; 67 LIBASOUND_API_VISIT(MAKE_TYPEDEF); 68 #undef MAKE_TYPEDEF 69 /* snd_pcm_hw_params_alloca is actually a macro */ 70 #define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof 71 #endif 72 73 #define CUBEB_STREAM_MAX 16 74 #define CUBEB_WATCHDOG_MS 10000 75 76 #define CUBEB_ALSA_PCM_NAME "default" 77 78 #define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin" 79 80 /* ALSA is not thread-safe. snd_pcm_t instances are individually protected 81 by the owning cubeb_stream's mutex. snd_pcm_t creation and destruction 82 is not thread-safe until ALSA 1.0.24 (see alsa-lib.git commit 91c9c8f1), 83 so those calls must be wrapped in the following mutex. */ 84 static pthread_mutex_t cubeb_alsa_mutex = PTHREAD_MUTEX_INITIALIZER; 85 static int cubeb_alsa_error_handler_set = 0; 86 87 static struct cubeb_ops const alsa_ops; 88 89 struct cubeb { 90 struct cubeb_ops const * ops; 91 void * libasound; 92 93 pthread_t thread; 94 95 /* Mutex for streams array, must not be held while blocked in poll(2). */ 96 pthread_mutex_t mutex; 97 98 /* Sparse array of streams managed by this context. */ 99 cubeb_stream * streams[CUBEB_STREAM_MAX]; 100 101 /* fds and nfds are only updated by alsa_run when rebuild is set. */ 102 struct pollfd * fds; 103 nfds_t nfds; 104 int rebuild; 105 106 int shutdown; 107 108 /* Control pipe for forcing poll to wake and rebuild fds or recalculate the 109 * timeout. */ 110 int control_fd_read; 111 int control_fd_write; 112 113 /* Track number of active streams. This is limited to CUBEB_STREAM_MAX 114 due to resource contraints. */ 115 unsigned int active_streams; 116 117 /* Local configuration with handle_underrun workaround set for PulseAudio 118 ALSA plugin. Will be NULL if the PA ALSA plugin is not in use or the 119 workaround is not required. */ 120 snd_config_t * local_config; 121 int is_pa; 122 }; 123 124 enum stream_state { INACTIVE, RUNNING, DRAINING, PROCESSING, ERROR }; 125 126 struct cubeb_stream { 127 /* Note: Must match cubeb_stream layout in cubeb.c. */ 128 cubeb * context; 129 void * user_ptr; 130 /**/ 131 pthread_mutex_t mutex; 132 snd_pcm_t * pcm; 133 cubeb_data_callback data_callback; 134 cubeb_state_callback state_callback; 135 snd_pcm_uframes_t stream_position; 136 snd_pcm_uframes_t last_position; 137 snd_pcm_uframes_t buffer_size; 138 cubeb_stream_params params; 139 140 /* Every member after this comment is protected by the owning context's 141 mutex rather than the stream's mutex, or is only used on the context's 142 run thread. */ 143 pthread_cond_t cond; /* Signaled when the stream's state is changed. */ 144 145 enum stream_state state; 146 147 struct pollfd * saved_fds; /* A copy of the pollfds passed in at init time. */ 148 struct pollfd * 149 fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */ 150 nfds_t nfds; 151 152 struct timeval drain_timeout; 153 154 /* XXX: Horrible hack -- if an active stream has been idle for 155 CUBEB_WATCHDOG_MS it will be disabled and the error callback will be 156 called. This works around a bug seen with older versions of ALSA and 157 PulseAudio where streams would stop requesting new data despite still 158 being logically active and playing. */ 159 struct timeval last_activity; 160 float volume; 161 162 char * buffer; 163 snd_pcm_uframes_t bufframes; 164 snd_pcm_stream_t stream_type; 165 166 struct cubeb_stream * other_stream; 167 }; 168 169 static int 170 any_revents(struct pollfd * fds, nfds_t nfds) 171 { 172 nfds_t i; 173 174 for (i = 0; i < nfds; ++i) { 175 if (fds[i].revents) { 176 return 1; 177 } 178 } 179 180 return 0; 181 } 182 183 static int 184 cmp_timeval(struct timeval * a, struct timeval * b) 185 { 186 if (a->tv_sec == b->tv_sec) { 187 if (a->tv_usec == b->tv_usec) { 188 return 0; 189 } 190 return a->tv_usec > b->tv_usec ? 1 : -1; 191 } 192 return a->tv_sec > b->tv_sec ? 1 : -1; 193 } 194 195 static int 196 timeval_to_relative_ms(struct timeval * tv) 197 { 198 struct timeval now; 199 struct timeval dt; 200 long long t; 201 int r; 202 203 gettimeofday(&now, NULL); 204 r = cmp_timeval(tv, &now); 205 if (r >= 0) { 206 timersub(tv, &now, &dt); 207 } else { 208 timersub(&now, tv, &dt); 209 } 210 t = dt.tv_sec; 211 t *= 1000; 212 t += (dt.tv_usec + 500) / 1000; 213 214 if (t > INT_MAX) { 215 t = INT_MAX; 216 } else if (t < INT_MIN) { 217 t = INT_MIN; 218 } 219 220 return r >= 0 ? t : -t; 221 } 222 223 static int 224 ms_until(struct timeval * tv) 225 { 226 return timeval_to_relative_ms(tv); 227 } 228 229 static int 230 ms_since(struct timeval * tv) 231 { 232 return -timeval_to_relative_ms(tv); 233 } 234 235 static void 236 rebuild(cubeb * ctx) 237 { 238 nfds_t nfds; 239 int i; 240 nfds_t j; 241 cubeb_stream * stm; 242 243 assert(ctx->rebuild); 244 245 /* Always count context's control pipe fd. */ 246 nfds = 1; 247 for (i = 0; i < CUBEB_STREAM_MAX; ++i) { 248 stm = ctx->streams[i]; 249 if (stm) { 250 stm->fds = NULL; 251 if (stm->state == RUNNING) { 252 nfds += stm->nfds; 253 } 254 } 255 } 256 257 free(ctx->fds); 258 ctx->fds = calloc(nfds, sizeof(struct pollfd)); 259 assert(ctx->fds); 260 ctx->nfds = nfds; 261 262 /* Include context's control pipe fd. */ 263 ctx->fds[0].fd = ctx->control_fd_read; 264 ctx->fds[0].events = POLLIN | POLLERR; 265 266 for (i = 0, j = 1; i < CUBEB_STREAM_MAX; ++i) { 267 stm = ctx->streams[i]; 268 if (stm && stm->state == RUNNING) { 269 memcpy(&ctx->fds[j], stm->saved_fds, stm->nfds * sizeof(struct pollfd)); 270 stm->fds = &ctx->fds[j]; 271 j += stm->nfds; 272 } 273 } 274 275 ctx->rebuild = 0; 276 } 277 278 static void 279 poll_wake(cubeb * ctx) 280 { 281 if (write(ctx->control_fd_write, "x", 1) < 0) { 282 /* ignore write error */ 283 } 284 } 285 286 static void 287 set_timeout(struct timeval * timeout, unsigned int ms) 288 { 289 gettimeofday(timeout, NULL); 290 timeout->tv_sec += ms / 1000; 291 timeout->tv_usec += (ms % 1000) * 1000; 292 } 293 294 static void 295 stream_buffer_decrement(cubeb_stream * stm, long count) 296 { 297 char * bufremains = 298 stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count); 299 memmove(stm->buffer, bufremains, 300 WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count)); 301 stm->bufframes -= count; 302 } 303 304 static void 305 alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) 306 { 307 cubeb * ctx; 308 int r; 309 310 ctx = stm->context; 311 stm->state = state; 312 r = pthread_cond_broadcast(&stm->cond); 313 assert(r == 0); 314 ctx->rebuild = 1; 315 poll_wake(ctx); 316 } 317 318 static enum stream_state 319 alsa_process_stream(cubeb_stream * stm) 320 { 321 unsigned short revents; 322 snd_pcm_sframes_t avail; 323 int draining; 324 325 draining = 0; 326 327 pthread_mutex_lock(&stm->mutex); 328 329 /* Call _poll_descriptors_revents() even if we don't use it 330 to let underlying plugins clear null events. Otherwise poll() 331 may wake up again and again, producing unnecessary CPU usage. */ 332 WRAP(snd_pcm_poll_descriptors_revents) 333 (stm->pcm, stm->fds, stm->nfds, &revents); 334 335 avail = WRAP(snd_pcm_avail_update)(stm->pcm); 336 337 /* Got null event? Bail and wait for another wakeup. */ 338 if (avail == 0) { 339 pthread_mutex_unlock(&stm->mutex); 340 return RUNNING; 341 } 342 343 /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time. 344 */ 345 if ((unsigned int)avail > stm->buffer_size) { 346 avail = stm->buffer_size; 347 } 348 349 /* Capture: Read available frames */ 350 if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) { 351 snd_pcm_sframes_t got; 352 353 if (avail + stm->bufframes > stm->buffer_size) { 354 /* Buffer overflow. Skip and overwrite with new data. */ 355 stm->bufframes = 0; 356 // TODO: should it be marked as DRAINING? 357 } 358 359 got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer + stm->bufframes, avail); 360 361 if (got < 0) { 362 avail = got; // the error handler below will recover us 363 } else { 364 stm->bufframes += got; 365 stm->stream_position += got; 366 367 gettimeofday(&stm->last_activity, NULL); 368 } 369 } 370 371 /* Capture: Pass read frames to callback function */ 372 if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 && 373 (!stm->other_stream || 374 stm->other_stream->bufframes < stm->other_stream->buffer_size)) { 375 snd_pcm_sframes_t wrote = stm->bufframes; 376 struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm; 377 void * other_buffer = stm->other_stream ? stm->other_stream->buffer + 378 stm->other_stream->bufframes 379 : NULL; 380 381 /* Correct write size to the other stream available space */ 382 if (stm->other_stream && 383 wrote > (snd_pcm_sframes_t)(stm->other_stream->buffer_size - 384 stm->other_stream->bufframes)) { 385 wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes; 386 } 387 388 pthread_mutex_unlock(&stm->mutex); 389 wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer, 390 other_buffer, wrote); 391 pthread_mutex_lock(&stm->mutex); 392 393 if (wrote < 0) { 394 avail = wrote; // the error handler below will recover us 395 } else { 396 stream_buffer_decrement(stm, wrote); 397 398 if (stm->other_stream) { 399 stm->other_stream->bufframes += wrote; 400 } 401 } 402 } 403 404 /* Playback: Don't have enough data? Let's ask for more. */ 405 if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && 406 avail > (snd_pcm_sframes_t)stm->bufframes && 407 (!stm->other_stream || stm->other_stream->bufframes > 0)) { 408 long got = avail - stm->bufframes; 409 void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL; 410 char * buftail = 411 stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); 412 413 /* Correct read size to the other stream available frames */ 414 if (stm->other_stream && 415 got > (snd_pcm_sframes_t)stm->other_stream->bufframes) { 416 got = stm->other_stream->bufframes; 417 } 418 419 pthread_mutex_unlock(&stm->mutex); 420 got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got); 421 pthread_mutex_lock(&stm->mutex); 422 423 if (got < 0) { 424 avail = got; // the error handler below will recover us 425 } else { 426 stm->bufframes += got; 427 428 if (stm->other_stream) { 429 stream_buffer_decrement(stm->other_stream, got); 430 } 431 } 432 } 433 434 /* Playback: Still don't have enough data? Add some silence. */ 435 if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && 436 avail > (snd_pcm_sframes_t)stm->bufframes) { 437 long drain_frames = avail - stm->bufframes; 438 double drain_time = (double)drain_frames / stm->params.rate; 439 440 char * buftail = 441 stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); 442 memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames)); 443 stm->bufframes = avail; 444 445 /* Mark as draining, unless we're waiting for capture */ 446 if (!stm->other_stream || stm->other_stream->bufframes > 0) { 447 set_timeout(&stm->drain_timeout, drain_time * 1000); 448 449 draining = 1; 450 } 451 } 452 453 /* Playback: Have enough data and no errors. Let's write it out. */ 454 if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) { 455 snd_pcm_sframes_t wrote; 456 457 if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) { 458 float * b = (float *)stm->buffer; 459 for (uint32_t i = 0; i < avail * stm->params.channels; i++) { 460 b[i] *= stm->volume; 461 } 462 } else { 463 short * b = (short *)stm->buffer; 464 for (uint32_t i = 0; i < avail * stm->params.channels; i++) { 465 b[i] *= stm->volume; 466 } 467 } 468 469 wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail); 470 if (wrote < 0) { 471 avail = wrote; // the error handler below will recover us 472 } else { 473 stream_buffer_decrement(stm, wrote); 474 475 stm->stream_position += wrote; 476 gettimeofday(&stm->last_activity, NULL); 477 } 478 } 479 480 /* Got some error? Let's try to recover the stream. */ 481 if (avail < 0) { 482 avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0); 483 484 /* Capture pcm must be started after initial setup/recover */ 485 if (avail >= 0 && stm->stream_type == SND_PCM_STREAM_CAPTURE && 486 WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { 487 avail = WRAP(snd_pcm_start)(stm->pcm); 488 } 489 } 490 491 /* Failed to recover, this stream must be broken. */ 492 if (avail < 0) { 493 pthread_mutex_unlock(&stm->mutex); 494 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); 495 return ERROR; 496 } 497 498 pthread_mutex_unlock(&stm->mutex); 499 return draining ? DRAINING : RUNNING; 500 } 501 502 static int 503 alsa_run(cubeb * ctx) 504 { 505 int r; 506 int timeout; 507 int i; 508 char dummy; 509 cubeb_stream * stm; 510 enum stream_state state; 511 512 pthread_mutex_lock(&ctx->mutex); 513 514 if (ctx->rebuild) { 515 rebuild(ctx); 516 } 517 518 /* Wake up at least once per second for the watchdog. */ 519 timeout = 1000; 520 for (i = 0; i < CUBEB_STREAM_MAX; ++i) { 521 stm = ctx->streams[i]; 522 if (stm && stm->state == DRAINING) { 523 r = ms_until(&stm->drain_timeout); 524 if (r >= 0 && timeout > r) { 525 timeout = r; 526 } 527 } 528 } 529 530 pthread_mutex_unlock(&ctx->mutex); 531 r = poll(ctx->fds, ctx->nfds, timeout); 532 pthread_mutex_lock(&ctx->mutex); 533 534 if (r > 0) { 535 if (ctx->fds[0].revents & POLLIN) { 536 if (read(ctx->control_fd_read, &dummy, 1) < 0) { 537 /* ignore read error */ 538 } 539 540 if (ctx->shutdown) { 541 pthread_mutex_unlock(&ctx->mutex); 542 return -1; 543 } 544 } 545 546 for (i = 0; i < CUBEB_STREAM_MAX; ++i) { 547 stm = ctx->streams[i]; 548 /* We can't use snd_pcm_poll_descriptors_revents here because of 549 https://github.com/kinetiknz/cubeb/issues/135. */ 550 if (stm && stm->state == RUNNING && stm->fds && 551 any_revents(stm->fds, stm->nfds)) { 552 alsa_set_stream_state(stm, PROCESSING); 553 pthread_mutex_unlock(&ctx->mutex); 554 state = alsa_process_stream(stm); 555 pthread_mutex_lock(&ctx->mutex); 556 alsa_set_stream_state(stm, state); 557 } 558 } 559 } else if (r == 0) { 560 for (i = 0; i < CUBEB_STREAM_MAX; ++i) { 561 stm = ctx->streams[i]; 562 if (stm) { 563 if (stm->state == DRAINING && ms_since(&stm->drain_timeout) >= 0) { 564 alsa_set_stream_state(stm, INACTIVE); 565 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); 566 } else if (stm->state == RUNNING && 567 ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) { 568 alsa_set_stream_state(stm, ERROR); 569 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); 570 } 571 } 572 } 573 } 574 575 pthread_mutex_unlock(&ctx->mutex); 576 577 return 0; 578 } 579 580 static void * 581 alsa_run_thread(void * context) 582 { 583 cubeb * ctx = context; 584 int r; 585 586 CUBEB_REGISTER_THREAD("cubeb rendering thread"); 587 588 do { 589 r = alsa_run(ctx); 590 } while (r >= 0); 591 592 CUBEB_UNREGISTER_THREAD(); 593 594 return NULL; 595 } 596 597 static snd_config_t * 598 get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) 599 { 600 int r; 601 snd_config_t * slave_pcm; 602 snd_config_t * slave_def; 603 snd_config_t * pcm; 604 char const * string; 605 char node_name[64]; 606 607 slave_def = NULL; 608 609 r = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm); 610 if (r < 0) { 611 return NULL; 612 } 613 614 r = WRAP(snd_config_get_string)(slave_pcm, &string); 615 if (r >= 0) { 616 r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string, 617 &slave_def); 618 if (r < 0) { 619 return NULL; 620 } 621 } 622 623 do { 624 r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm); 625 if (r < 0) { 626 break; 627 } 628 629 r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string); 630 if (r < 0) { 631 break; 632 } 633 634 r = snprintf(node_name, sizeof(node_name), "pcm.%s", string); 635 if (r < 0 || r > (int)sizeof(node_name)) { 636 break; 637 } 638 r = WRAP(snd_config_search)(lconf, node_name, &pcm); 639 if (r < 0) { 640 break; 641 } 642 643 return pcm; 644 } while (0); 645 646 if (slave_def) { 647 WRAP(snd_config_delete)(slave_def); 648 } 649 650 return NULL; 651 } 652 653 /* Work around PulseAudio ALSA plugin bug where the PA server forces a 654 higher than requested latency, but the plugin does not update its (and 655 ALSA's) internal state to reflect that, leading to an immediate underrun 656 situation. Inspired by WINE's make_handle_underrun_config. 657 Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05 658 */ 659 static snd_config_t * 660 init_local_config_with_workaround(char const * pcm_name) 661 { 662 int r; 663 snd_config_t * lconf; 664 snd_config_t * pcm_node; 665 snd_config_t * node; 666 char const * string; 667 char node_name[64]; 668 669 lconf = NULL; 670 671 if (WRAP(snd_config) == NULL) { 672 return NULL; 673 } 674 675 r = WRAP(snd_config_copy)(&lconf, WRAP(snd_config)); 676 if (r < 0) { 677 return NULL; 678 } 679 680 do { 681 r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node); 682 if (r < 0) { 683 break; 684 } 685 686 r = WRAP(snd_config_get_id)(pcm_node, &string); 687 if (r < 0) { 688 break; 689 } 690 691 r = snprintf(node_name, sizeof(node_name), "pcm.%s", string); 692 if (r < 0 || r > (int)sizeof(node_name)) { 693 break; 694 } 695 r = WRAP(snd_config_search)(lconf, node_name, &pcm_node); 696 if (r < 0) { 697 break; 698 } 699 700 /* If this PCM has a slave, walk the slave configurations until we reach the 701 * bottom. */ 702 while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) { 703 pcm_node = node; 704 } 705 706 /* Fetch the PCM node's type, and bail out if it's not the PulseAudio 707 * plugin. */ 708 r = WRAP(snd_config_search)(pcm_node, "type", &node); 709 if (r < 0) { 710 break; 711 } 712 713 r = WRAP(snd_config_get_string)(node, &string); 714 if (r < 0) { 715 break; 716 } 717 718 if (strcmp(string, "pulse") != 0) { 719 break; 720 } 721 722 /* Don't clobber an explicit existing handle_underrun value, set it only 723 if it doesn't already exist. */ 724 r = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node); 725 if (r != -ENOENT) { 726 break; 727 } 728 729 /* Disable pcm_pulse's asynchronous underrun handling. */ 730 r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0); 731 if (r < 0) { 732 break; 733 } 734 735 r = WRAP(snd_config_add)(pcm_node, node); 736 if (r < 0) { 737 break; 738 } 739 740 return lconf; 741 } while (0); 742 743 WRAP(snd_config_delete)(lconf); 744 745 return NULL; 746 } 747 748 static int 749 alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, 750 snd_pcm_stream_t stream, snd_config_t * local_config) 751 { 752 int r; 753 754 pthread_mutex_lock(&cubeb_alsa_mutex); 755 if (local_config) { 756 r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK, 757 local_config); 758 } else { 759 r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK); 760 } 761 pthread_mutex_unlock(&cubeb_alsa_mutex); 762 763 return r; 764 } 765 766 static int 767 alsa_locked_pcm_close(snd_pcm_t * pcm) 768 { 769 int r; 770 771 pthread_mutex_lock(&cubeb_alsa_mutex); 772 r = WRAP(snd_pcm_close)(pcm); 773 pthread_mutex_unlock(&cubeb_alsa_mutex); 774 775 return r; 776 } 777 778 static int 779 alsa_register_stream(cubeb * ctx, cubeb_stream * stm) 780 { 781 int i; 782 783 pthread_mutex_lock(&ctx->mutex); 784 for (i = 0; i < CUBEB_STREAM_MAX; ++i) { 785 if (!ctx->streams[i]) { 786 ctx->streams[i] = stm; 787 break; 788 } 789 } 790 pthread_mutex_unlock(&ctx->mutex); 791 792 return i == CUBEB_STREAM_MAX; 793 } 794 795 static void 796 alsa_unregister_stream(cubeb_stream * stm) 797 { 798 cubeb * ctx; 799 int i; 800 801 ctx = stm->context; 802 803 pthread_mutex_lock(&ctx->mutex); 804 for (i = 0; i < CUBEB_STREAM_MAX; ++i) { 805 if (ctx->streams[i] == stm) { 806 ctx->streams[i] = NULL; 807 break; 808 } 809 } 810 pthread_mutex_unlock(&ctx->mutex); 811 } 812 813 static void 814 silent_error_handler(char const * file, int line, char const * function, 815 int err, char const * fmt, ...) 816 { 817 (void)file; 818 (void)line; 819 (void)function; 820 (void)err; 821 (void)fmt; 822 } 823 824 /*static*/ int 825 alsa_init(cubeb ** context, char const * context_name) 826 { 827 (void)context_name; 828 void * libasound = NULL; 829 cubeb * ctx; 830 int r; 831 int i; 832 int fd[2]; 833 pthread_attr_t attr; 834 snd_pcm_t * dummy; 835 836 assert(context); 837 *context = NULL; 838 839 #ifndef DISABLE_LIBASOUND_DLOPEN 840 libasound = dlopen("libasound.so.2", RTLD_LAZY); 841 if (!libasound) { 842 libasound = dlopen("libasound.so", RTLD_LAZY); 843 if (!libasound) { 844 return CUBEB_ERROR; 845 } 846 } 847 848 #define LOAD(x) \ 849 { \ 850 cubeb_##x = dlsym(libasound, #x); \ 851 if (!cubeb_##x) { \ 852 dlclose(libasound); \ 853 return CUBEB_ERROR; \ 854 } \ 855 } 856 857 LIBASOUND_API_VISIT(LOAD); 858 #undef LOAD 859 #endif 860 861 pthread_mutex_lock(&cubeb_alsa_mutex); 862 if (!cubeb_alsa_error_handler_set) { 863 WRAP(snd_lib_error_set_handler)(silent_error_handler); 864 cubeb_alsa_error_handler_set = 1; 865 } 866 pthread_mutex_unlock(&cubeb_alsa_mutex); 867 868 ctx = calloc(1, sizeof(*ctx)); 869 assert(ctx); 870 871 ctx->ops = &alsa_ops; 872 ctx->libasound = libasound; 873 874 r = pthread_mutex_init(&ctx->mutex, NULL); 875 assert(r == 0); 876 877 r = pipe(fd); 878 assert(r == 0); 879 880 for (i = 0; i < 2; ++i) { 881 fcntl(fd[i], F_SETFD, fcntl(fd[i], F_GETFD) | FD_CLOEXEC); 882 fcntl(fd[i], F_SETFL, fcntl(fd[i], F_GETFL) | O_NONBLOCK); 883 } 884 885 ctx->control_fd_read = fd[0]; 886 ctx->control_fd_write = fd[1]; 887 888 /* Force an early rebuild when alsa_run is first called to ensure fds and 889 nfds have been initialized. */ 890 ctx->rebuild = 1; 891 892 r = pthread_attr_init(&attr); 893 assert(r == 0); 894 895 r = pthread_attr_setstacksize(&attr, 256 * 1024); 896 assert(r == 0); 897 898 r = pthread_create(&ctx->thread, &attr, alsa_run_thread, ctx); 899 assert(r == 0); 900 901 r = pthread_attr_destroy(&attr); 902 assert(r == 0); 903 904 /* Open a dummy PCM to force the configuration space to be evaluated so that 905 init_local_config_with_workaround can find and modify the default node. */ 906 r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, 907 NULL); 908 if (r >= 0) { 909 alsa_locked_pcm_close(dummy); 910 } 911 ctx->is_pa = 0; 912 pthread_mutex_lock(&cubeb_alsa_mutex); 913 ctx->local_config = init_local_config_with_workaround(CUBEB_ALSA_PCM_NAME); 914 pthread_mutex_unlock(&cubeb_alsa_mutex); 915 if (ctx->local_config) { 916 ctx->is_pa = 1; 917 r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, 918 SND_PCM_STREAM_PLAYBACK, ctx->local_config); 919 /* If we got a local_config, we found a PA PCM. If opening a PCM with that 920 config fails with EINVAL, the PA PCM is too old for this workaround. */ 921 if (r == -EINVAL) { 922 pthread_mutex_lock(&cubeb_alsa_mutex); 923 WRAP(snd_config_delete)(ctx->local_config); 924 pthread_mutex_unlock(&cubeb_alsa_mutex); 925 ctx->local_config = NULL; 926 } else if (r >= 0) { 927 alsa_locked_pcm_close(dummy); 928 } 929 } 930 931 *context = ctx; 932 933 return CUBEB_OK; 934 } 935 936 static char const * 937 alsa_get_backend_id(cubeb * ctx) 938 { 939 (void)ctx; 940 return "alsa"; 941 } 942 943 static void 944 alsa_destroy(cubeb * ctx) 945 { 946 int r; 947 948 assert(ctx); 949 950 pthread_mutex_lock(&ctx->mutex); 951 ctx->shutdown = 1; 952 poll_wake(ctx); 953 pthread_mutex_unlock(&ctx->mutex); 954 955 r = pthread_join(ctx->thread, NULL); 956 assert(r == 0); 957 958 close(ctx->control_fd_read); 959 close(ctx->control_fd_write); 960 pthread_mutex_destroy(&ctx->mutex); 961 free(ctx->fds); 962 963 if (ctx->local_config) { 964 pthread_mutex_lock(&cubeb_alsa_mutex); 965 WRAP(snd_config_delete)(ctx->local_config); 966 pthread_mutex_unlock(&cubeb_alsa_mutex); 967 } 968 #ifndef DISABLE_LIBASOUND_DLOPEN 969 if (ctx->libasound) { 970 dlclose(ctx->libasound); 971 } 972 #endif 973 free(ctx); 974 } 975 976 static void 977 alsa_stream_destroy(cubeb_stream * stm); 978 979 static int 980 alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, 981 char const * stream_name, snd_pcm_stream_t stream_type, 982 cubeb_devid deviceid, 983 cubeb_stream_params * stream_params, 984 unsigned int latency_frames, 985 cubeb_data_callback data_callback, 986 cubeb_state_callback state_callback, void * user_ptr) 987 { 988 (void)stream_name; 989 cubeb_stream * stm; 990 int r; 991 snd_pcm_format_t format; 992 snd_pcm_uframes_t period_size; 993 int latency_us = 0; 994 char const * pcm_name = 995 deviceid ? (char const *)deviceid : CUBEB_ALSA_PCM_NAME; 996 997 assert(ctx && stream); 998 999 *stream = NULL; 1000 1001 if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { 1002 return CUBEB_ERROR_NOT_SUPPORTED; 1003 } 1004 1005 switch (stream_params->format) { 1006 case CUBEB_SAMPLE_S16LE: 1007 format = SND_PCM_FORMAT_S16_LE; 1008 break; 1009 case CUBEB_SAMPLE_S16BE: 1010 format = SND_PCM_FORMAT_S16_BE; 1011 break; 1012 case CUBEB_SAMPLE_FLOAT32LE: 1013 format = SND_PCM_FORMAT_FLOAT_LE; 1014 break; 1015 case CUBEB_SAMPLE_FLOAT32BE: 1016 format = SND_PCM_FORMAT_FLOAT_BE; 1017 break; 1018 default: 1019 return CUBEB_ERROR_INVALID_FORMAT; 1020 } 1021 1022 pthread_mutex_lock(&ctx->mutex); 1023 if (ctx->active_streams >= CUBEB_STREAM_MAX) { 1024 pthread_mutex_unlock(&ctx->mutex); 1025 return CUBEB_ERROR; 1026 } 1027 ctx->active_streams += 1; 1028 pthread_mutex_unlock(&ctx->mutex); 1029 1030 stm = calloc(1, sizeof(*stm)); 1031 assert(stm); 1032 1033 stm->context = ctx; 1034 stm->data_callback = data_callback; 1035 stm->state_callback = state_callback; 1036 stm->user_ptr = user_ptr; 1037 stm->params = *stream_params; 1038 stm->state = INACTIVE; 1039 stm->volume = 1.0; 1040 stm->buffer = NULL; 1041 stm->bufframes = 0; 1042 stm->stream_type = stream_type; 1043 stm->other_stream = NULL; 1044 1045 r = pthread_mutex_init(&stm->mutex, NULL); 1046 assert(r == 0); 1047 1048 r = pthread_cond_init(&stm->cond, NULL); 1049 assert(r == 0); 1050 1051 r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type, 1052 ctx->local_config); 1053 if (r < 0) { 1054 alsa_stream_destroy(stm); 1055 return CUBEB_ERROR; 1056 } 1057 1058 r = WRAP(snd_pcm_nonblock)(stm->pcm, 1); 1059 assert(r == 0); 1060 1061 latency_us = latency_frames * 1e6 / stm->params.rate; 1062 1063 /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't 1064 possibly work. See https://bugzilla.mozilla.org/show_bug.cgi?id=761274. 1065 Only resort to this hack if the handle_underrun workaround failed. */ 1066 if (!ctx->local_config && ctx->is_pa) { 1067 const int min_latency = 5e5; 1068 latency_us = latency_us < min_latency ? min_latency : latency_us; 1069 } 1070 1071 r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, 1072 stm->params.channels, stm->params.rate, 1, 1073 latency_us); 1074 if (r < 0) { 1075 alsa_stream_destroy(stm); 1076 return CUBEB_ERROR_INVALID_FORMAT; 1077 } 1078 1079 r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size); 1080 assert(r == 0); 1081 1082 /* Double internal buffer size to have enough space when waiting for the other 1083 * side of duplex connection */ 1084 stm->buffer_size *= 2; 1085 stm->buffer = 1086 calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size)); 1087 assert(stm->buffer); 1088 1089 stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm); 1090 assert(stm->nfds > 0); 1091 1092 stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd)); 1093 assert(stm->saved_fds); 1094 r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds); 1095 assert((nfds_t)r == stm->nfds); 1096 1097 if (alsa_register_stream(ctx, stm) != 0) { 1098 alsa_stream_destroy(stm); 1099 return CUBEB_ERROR; 1100 } 1101 1102 *stream = stm; 1103 1104 return CUBEB_OK; 1105 } 1106 1107 static int 1108 alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, 1109 cubeb_devid input_device, 1110 cubeb_stream_params * input_stream_params, 1111 cubeb_devid output_device, 1112 cubeb_stream_params * output_stream_params, 1113 unsigned int latency_frames, cubeb_data_callback data_callback, 1114 cubeb_state_callback state_callback, void * user_ptr) 1115 { 1116 int result = CUBEB_OK; 1117 cubeb_stream *instm = NULL, *outstm = NULL; 1118 1119 if (result == CUBEB_OK && input_stream_params) { 1120 result = alsa_stream_init_single(ctx, &instm, stream_name, 1121 SND_PCM_STREAM_CAPTURE, input_device, 1122 input_stream_params, latency_frames, 1123 data_callback, state_callback, user_ptr); 1124 } 1125 1126 if (result == CUBEB_OK && output_stream_params) { 1127 result = alsa_stream_init_single(ctx, &outstm, stream_name, 1128 SND_PCM_STREAM_PLAYBACK, output_device, 1129 output_stream_params, latency_frames, 1130 data_callback, state_callback, user_ptr); 1131 } 1132 1133 if (result == CUBEB_OK && input_stream_params && output_stream_params) { 1134 instm->other_stream = outstm; 1135 outstm->other_stream = instm; 1136 } 1137 1138 if (result != CUBEB_OK && instm) { 1139 alsa_stream_destroy(instm); 1140 } 1141 1142 *stream = outstm ? outstm : instm; 1143 1144 return result; 1145 } 1146 1147 static void 1148 alsa_stream_destroy(cubeb_stream * stm) 1149 { 1150 int r; 1151 cubeb * ctx; 1152 1153 assert(stm && (stm->state == INACTIVE || stm->state == ERROR || 1154 stm->state == DRAINING)); 1155 1156 ctx = stm->context; 1157 1158 if (stm->other_stream) { 1159 stm->other_stream->other_stream = NULL; // to stop infinite recursion 1160 alsa_stream_destroy(stm->other_stream); 1161 } 1162 1163 pthread_mutex_lock(&stm->mutex); 1164 if (stm->pcm) { 1165 if (stm->state == DRAINING) { 1166 WRAP(snd_pcm_drain)(stm->pcm); 1167 } 1168 alsa_locked_pcm_close(stm->pcm); 1169 stm->pcm = NULL; 1170 } 1171 free(stm->saved_fds); 1172 pthread_mutex_unlock(&stm->mutex); 1173 pthread_mutex_destroy(&stm->mutex); 1174 1175 r = pthread_cond_destroy(&stm->cond); 1176 assert(r == 0); 1177 1178 alsa_unregister_stream(stm); 1179 1180 pthread_mutex_lock(&ctx->mutex); 1181 assert(ctx->active_streams >= 1); 1182 ctx->active_streams -= 1; 1183 pthread_mutex_unlock(&ctx->mutex); 1184 1185 free(stm->buffer); 1186 1187 free(stm); 1188 } 1189 1190 static int 1191 alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) 1192 { 1193 int r; 1194 cubeb_stream * stm; 1195 snd_pcm_hw_params_t * hw_params; 1196 cubeb_stream_params params; 1197 params.rate = 44100; 1198 params.format = CUBEB_SAMPLE_FLOAT32NE; 1199 params.channels = 2; 1200 1201 snd_pcm_hw_params_alloca(&hw_params); 1202 1203 assert(ctx); 1204 1205 r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, ¶ms, 100, NULL, 1206 NULL, NULL); 1207 if (r != CUBEB_OK) { 1208 return CUBEB_ERROR; 1209 } 1210 1211 assert(stm); 1212 1213 r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params); 1214 if (r < 0) { 1215 return CUBEB_ERROR; 1216 } 1217 1218 r = WRAP(snd_pcm_hw_params_get_channels_max)(hw_params, max_channels); 1219 if (r < 0) { 1220 return CUBEB_ERROR; 1221 } 1222 1223 alsa_stream_destroy(stm); 1224 1225 return CUBEB_OK; 1226 } 1227 1228 static int 1229 alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) 1230 { 1231 (void)ctx; 1232 int r, dir; 1233 snd_pcm_t * pcm; 1234 snd_pcm_hw_params_t * hw_params; 1235 1236 snd_pcm_hw_params_alloca(&hw_params); 1237 1238 /* get a pcm, disabling resampling, so we get a rate the 1239 * hardware/dmix/pulse/etc. supports. */ 1240 r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, 1241 SND_PCM_NO_AUTO_RESAMPLE); 1242 if (r < 0) { 1243 return CUBEB_ERROR; 1244 } 1245 1246 r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params); 1247 if (r < 0) { 1248 WRAP(snd_pcm_close)(pcm); 1249 return CUBEB_ERROR; 1250 } 1251 1252 r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir); 1253 if (r >= 0) { 1254 /* There is a default rate: use it. */ 1255 WRAP(snd_pcm_close)(pcm); 1256 return CUBEB_OK; 1257 } 1258 1259 /* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */ 1260 *rate = 44100; 1261 1262 r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL); 1263 if (r < 0) { 1264 WRAP(snd_pcm_close)(pcm); 1265 return CUBEB_ERROR; 1266 } 1267 1268 WRAP(snd_pcm_close)(pcm); 1269 1270 return CUBEB_OK; 1271 } 1272 1273 static int 1274 alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params, 1275 uint32_t * latency_frames) 1276 { 1277 (void)ctx; 1278 /* 40ms is found to be an acceptable minimum, even on a super low-end 1279 * machine. */ 1280 *latency_frames = 40 * params.rate / 1000; 1281 1282 return CUBEB_OK; 1283 } 1284 1285 static int 1286 alsa_stream_start(cubeb_stream * stm) 1287 { 1288 cubeb * ctx; 1289 1290 assert(stm); 1291 ctx = stm->context; 1292 1293 if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { 1294 int r = alsa_stream_start(stm->other_stream); 1295 if (r != CUBEB_OK) 1296 return r; 1297 } 1298 1299 pthread_mutex_lock(&stm->mutex); 1300 /* Capture pcm must be started after initial setup/recover */ 1301 if (stm->stream_type == SND_PCM_STREAM_CAPTURE && 1302 WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { 1303 WRAP(snd_pcm_start)(stm->pcm); 1304 } 1305 WRAP(snd_pcm_pause)(stm->pcm, 0); 1306 gettimeofday(&stm->last_activity, NULL); 1307 pthread_mutex_unlock(&stm->mutex); 1308 1309 pthread_mutex_lock(&ctx->mutex); 1310 if (stm->state != INACTIVE) { 1311 pthread_mutex_unlock(&ctx->mutex); 1312 return CUBEB_ERROR; 1313 } 1314 alsa_set_stream_state(stm, RUNNING); 1315 pthread_mutex_unlock(&ctx->mutex); 1316 1317 return CUBEB_OK; 1318 } 1319 1320 static int 1321 alsa_stream_stop(cubeb_stream * stm) 1322 { 1323 cubeb * ctx; 1324 int r; 1325 1326 assert(stm); 1327 ctx = stm->context; 1328 1329 if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { 1330 int r = alsa_stream_stop(stm->other_stream); 1331 if (r != CUBEB_OK) 1332 return r; 1333 } 1334 1335 pthread_mutex_lock(&ctx->mutex); 1336 while (stm->state == PROCESSING) { 1337 r = pthread_cond_wait(&stm->cond, &ctx->mutex); 1338 assert(r == 0); 1339 } 1340 1341 alsa_set_stream_state(stm, INACTIVE); 1342 pthread_mutex_unlock(&ctx->mutex); 1343 1344 pthread_mutex_lock(&stm->mutex); 1345 WRAP(snd_pcm_pause)(stm->pcm, 1); 1346 pthread_mutex_unlock(&stm->mutex); 1347 1348 return CUBEB_OK; 1349 } 1350 1351 static int 1352 alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) 1353 { 1354 snd_pcm_sframes_t delay; 1355 1356 assert(stm && position); 1357 1358 pthread_mutex_lock(&stm->mutex); 1359 1360 delay = -1; 1361 if (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING || 1362 WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) { 1363 *position = stm->last_position; 1364 pthread_mutex_unlock(&stm->mutex); 1365 return CUBEB_OK; 1366 } 1367 1368 assert(delay >= 0); 1369 1370 *position = 0; 1371 if (stm->stream_position >= (snd_pcm_uframes_t)delay) { 1372 *position = stm->stream_position - delay; 1373 } 1374 1375 stm->last_position = *position; 1376 1377 pthread_mutex_unlock(&stm->mutex); 1378 return CUBEB_OK; 1379 } 1380 1381 static int 1382 alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency) 1383 { 1384 snd_pcm_sframes_t delay; 1385 /* This function returns the delay in frames until a frame written using 1386 snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. 1387 */ 1388 if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) { 1389 return CUBEB_ERROR; 1390 } 1391 1392 *latency = delay; 1393 1394 return CUBEB_OK; 1395 } 1396 1397 static int 1398 alsa_stream_set_volume(cubeb_stream * stm, float volume) 1399 { 1400 /* setting the volume using an API call does not seem very stable/supported */ 1401 pthread_mutex_lock(&stm->mutex); 1402 stm->volume = volume; 1403 pthread_mutex_unlock(&stm->mutex); 1404 1405 return CUBEB_OK; 1406 } 1407 1408 static int 1409 alsa_enumerate_devices(cubeb * context, cubeb_device_type type, 1410 cubeb_device_collection * collection) 1411 { 1412 cubeb_device_info * device = NULL; 1413 1414 if (!context) 1415 return CUBEB_ERROR; 1416 1417 uint32_t rate, max_channels; 1418 int r; 1419 1420 r = alsa_get_preferred_sample_rate(context, &rate); 1421 if (r != CUBEB_OK) { 1422 return CUBEB_ERROR; 1423 } 1424 1425 r = alsa_get_max_channel_count(context, &max_channels); 1426 if (r != CUBEB_OK) { 1427 return CUBEB_ERROR; 1428 } 1429 1430 char const * a_name = "default"; 1431 device = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info)); 1432 assert(device); 1433 if (!device) 1434 return CUBEB_ERROR; 1435 1436 device->device_id = a_name; 1437 device->devid = (cubeb_devid)device->device_id; 1438 device->friendly_name = a_name; 1439 device->group_id = a_name; 1440 device->vendor_name = a_name; 1441 device->type = type; 1442 device->state = CUBEB_DEVICE_STATE_ENABLED; 1443 device->preferred = CUBEB_DEVICE_PREF_ALL; 1444 device->format = CUBEB_DEVICE_FMT_S16NE; 1445 device->default_format = CUBEB_DEVICE_FMT_S16NE; 1446 device->max_channels = max_channels; 1447 device->min_rate = rate; 1448 device->max_rate = rate; 1449 device->default_rate = rate; 1450 device->latency_lo = 0; 1451 device->latency_hi = 0; 1452 1453 collection->device = device; 1454 collection->count = 1; 1455 1456 return CUBEB_OK; 1457 } 1458 1459 static int 1460 alsa_device_collection_destroy(cubeb * context, 1461 cubeb_device_collection * collection) 1462 { 1463 assert(collection->count == 1); 1464 (void)context; 1465 free(collection->device); 1466 return CUBEB_OK; 1467 } 1468 1469 static struct cubeb_ops const alsa_ops = { 1470 .init = alsa_init, 1471 .get_backend_id = alsa_get_backend_id, 1472 .get_max_channel_count = alsa_get_max_channel_count, 1473 .get_min_latency = alsa_get_min_latency, 1474 .get_preferred_sample_rate = alsa_get_preferred_sample_rate, 1475 .get_supported_input_processing_params = NULL, 1476 .enumerate_devices = alsa_enumerate_devices, 1477 .device_collection_destroy = alsa_device_collection_destroy, 1478 .destroy = alsa_destroy, 1479 .stream_init = alsa_stream_init, 1480 .stream_destroy = alsa_stream_destroy, 1481 .stream_start = alsa_stream_start, 1482 .stream_stop = alsa_stream_stop, 1483 .stream_get_position = alsa_stream_get_position, 1484 .stream_get_latency = alsa_stream_get_latency, 1485 .stream_get_input_latency = NULL, 1486 .stream_set_volume = alsa_stream_set_volume, 1487 .stream_set_name = NULL, 1488 .stream_get_current_device = NULL, 1489 .stream_set_input_mute = NULL, 1490 .stream_set_input_processing_params = NULL, 1491 .stream_device_destroy = NULL, 1492 .stream_register_device_changed_callback = NULL, 1493 .register_device_collection_changed = NULL};