cubeb_sndio.c (18820B)
1 /* 2 * Copyright (c) 2011 Alexandre Ratchov <alex@caoua.org> 3 * 4 * This program is made available under an ISC-style license. See the 5 * accompanying file LICENSE for details. 6 */ 7 #include "cubeb-internal.h" 8 #include "cubeb/cubeb.h" 9 #include "cubeb_tracing.h" 10 #include <assert.h> 11 #include <dlfcn.h> 12 #include <inttypes.h> 13 #include <math.h> 14 #include <poll.h> 15 #include <pthread.h> 16 #include <sndio.h> 17 #include <stdbool.h> 18 #include <stdio.h> 19 #include <stdlib.h> 20 21 #if defined(CUBEB_SNDIO_DEBUG) 22 #define DPR(...) fprintf(stderr, __VA_ARGS__); 23 #else 24 #define DPR(...) \ 25 do { \ 26 } while (0) 27 #endif 28 29 #ifdef DISABLE_LIBSNDIO_DLOPEN 30 #define WRAP(x) x 31 #else 32 #define WRAP(x) (*cubeb_##x) 33 #define LIBSNDIO_API_VISIT(X) \ 34 X(sio_close) \ 35 X(sio_eof) \ 36 X(sio_getpar) \ 37 X(sio_initpar) \ 38 X(sio_nfds) \ 39 X(sio_onmove) \ 40 X(sio_open) \ 41 X(sio_pollfd) \ 42 X(sio_read) \ 43 X(sio_revents) \ 44 X(sio_setpar) \ 45 X(sio_start) \ 46 X(sio_stop) \ 47 X(sio_write) 48 49 #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; 50 LIBSNDIO_API_VISIT(MAKE_TYPEDEF); 51 #undef MAKE_TYPEDEF 52 #endif 53 54 static struct cubeb_ops const sndio_ops; 55 56 struct cubeb { 57 struct cubeb_ops const * ops; 58 void * libsndio; 59 }; 60 61 struct cubeb_stream { 62 /* Note: Must match cubeb_stream layout in cubeb.c. */ 63 cubeb * context; 64 void * arg; /* user arg to {data,state}_cb */ 65 /**/ 66 pthread_t th; /* to run real-time audio i/o */ 67 pthread_mutex_t mtx; /* protects hdl and pos */ 68 struct sio_hdl * hdl; /* link us to sndio */ 69 int mode; /* bitmap of SIO_{PLAY,REC} */ 70 int active; /* cubec_start() called */ 71 int conv; /* need float->s24 conversion */ 72 unsigned char * rbuf; /* rec data consumed from here */ 73 unsigned char * pbuf; /* play data is prepared here */ 74 unsigned int nfr; /* number of frames in ibuf and obuf */ 75 unsigned int rbpf; /* rec bytes per frame */ 76 unsigned int pbpf; /* play bytes per frame */ 77 unsigned int rchan; /* number of rec channels */ 78 unsigned int pchan; /* number of play channels */ 79 unsigned int nblks; /* number of blocks in the buffer */ 80 uint64_t hwpos; /* frame number Joe hears right now */ 81 uint64_t swpos; /* number of frames produced/consumed */ 82 cubeb_data_callback data_cb; /* cb to preapare data */ 83 cubeb_state_callback state_cb; /* cb to notify about state changes */ 84 float volume; /* current volume */ 85 }; 86 87 static void 88 s16_setvol(void * ptr, long nsamp, float volume) 89 { 90 int16_t * dst = ptr; 91 int32_t mult = volume * 32768; 92 int32_t s; 93 94 while (nsamp-- > 0) { 95 s = *dst; 96 s = (s * mult) >> 15; 97 *(dst++) = s; 98 } 99 } 100 101 static void 102 float_to_s24(void * ptr, long nsamp, float volume) 103 { 104 int32_t * dst = ptr; 105 float * src = ptr; 106 float mult = volume * 8388608; 107 int s; 108 109 while (nsamp-- > 0) { 110 s = lrintf(*(src++) * mult); 111 if (s < -8388608) 112 s = -8388608; 113 else if (s > 8388607) 114 s = 8388607; 115 *(dst++) = s; 116 } 117 } 118 119 static void 120 s24_to_float(void * ptr, long nsamp) 121 { 122 int32_t * src = ptr; 123 float * dst = ptr; 124 125 src += nsamp; 126 dst += nsamp; 127 while (nsamp-- > 0) 128 *(--dst) = (1. / 8388608) * *(--src); 129 } 130 131 static const char * 132 sndio_get_device() 133 { 134 #ifdef __linux__ 135 /* 136 * On other platforms default to sndio devices, 137 * so cubebs other backends can be used instead. 138 */ 139 const char * dev = getenv("AUDIODEVICE"); 140 if (dev == NULL || *dev == '\0') 141 return "snd/0"; 142 return dev; 143 #else 144 return SIO_DEVANY; 145 #endif 146 } 147 148 static void 149 sndio_onmove(void * arg, int delta) 150 { 151 cubeb_stream * s = (cubeb_stream *)arg; 152 153 s->hwpos += delta; 154 } 155 156 static void * 157 sndio_mainloop(void * arg) 158 { 159 struct pollfd * pfds; 160 cubeb_stream * s = arg; 161 int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED; 162 size_t pstart = 0, pend = 0, rstart = 0, rend = 0; 163 long nfr; 164 165 CUBEB_REGISTER_THREAD("cubeb rendering thread"); 166 167 nfds = WRAP(sio_nfds)(s->hdl); 168 pfds = calloc(nfds, sizeof(struct pollfd)); 169 if (pfds == NULL) { 170 CUBEB_UNREGISTER_THREAD(); 171 return NULL; 172 } 173 174 DPR("sndio_mainloop()\n"); 175 s->state_cb(s, s->arg, CUBEB_STATE_STARTED); 176 pthread_mutex_lock(&s->mtx); 177 if (!WRAP(sio_start)(s->hdl)) { 178 pthread_mutex_unlock(&s->mtx); 179 free(pfds); 180 CUBEB_UNREGISTER_THREAD(); 181 return NULL; 182 } 183 DPR("sndio_mainloop(), started\n"); 184 185 if (s->mode & SIO_PLAY) { 186 pstart = pend = s->nfr * s->pbpf; 187 prime = s->nblks; 188 if (s->mode & SIO_REC) { 189 memset(s->rbuf, 0, s->nfr * s->rbpf); 190 rstart = rend = s->nfr * s->rbpf; 191 } 192 } else { 193 prime = 0; 194 rstart = 0; 195 rend = s->nfr * s->rbpf; 196 } 197 198 for (;;) { 199 if (!s->active) { 200 DPR("sndio_mainloop() stopped\n"); 201 state = CUBEB_STATE_STOPPED; 202 break; 203 } 204 205 /* do we have a complete block? */ 206 if ((!(s->mode & SIO_PLAY) || pstart == pend) && 207 (!(s->mode & SIO_REC) || rstart == rend)) { 208 209 if (eof) { 210 DPR("sndio_mainloop() drained\n"); 211 state = CUBEB_STATE_DRAINED; 212 break; 213 } 214 215 if ((s->mode & SIO_REC) && s->conv) 216 s24_to_float(s->rbuf, s->nfr * s->rchan); 217 218 /* invoke call-back, it returns less that s->nfr if done */ 219 pthread_mutex_unlock(&s->mtx); 220 nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr); 221 pthread_mutex_lock(&s->mtx); 222 if (nfr < 0) { 223 DPR("sndio_mainloop() cb err\n"); 224 state = CUBEB_STATE_ERROR; 225 break; 226 } 227 s->swpos += nfr; 228 229 /* was this last call-back invocation (aka end-of-stream) ? */ 230 if (nfr < s->nfr) { 231 232 if (!(s->mode & SIO_PLAY) || nfr == 0) { 233 state = CUBEB_STATE_DRAINED; 234 break; 235 } 236 237 /* need to write (aka drain) the partial play block we got */ 238 pend = nfr * s->pbpf; 239 eof = 1; 240 } 241 242 if (prime > 0) 243 prime--; 244 245 if (s->mode & SIO_PLAY) { 246 if (s->conv) 247 float_to_s24(s->pbuf, nfr * s->pchan, s->volume); 248 else 249 s16_setvol(s->pbuf, nfr * s->pchan, s->volume); 250 } 251 252 if (s->mode & SIO_REC) 253 rstart = 0; 254 if (s->mode & SIO_PLAY) 255 pstart = 0; 256 } 257 258 events = 0; 259 if ((s->mode & SIO_REC) && rstart < rend && prime == 0) 260 events |= POLLIN; 261 if ((s->mode & SIO_PLAY) && pstart < pend) 262 events |= POLLOUT; 263 nfds = WRAP(sio_pollfd)(s->hdl, pfds, events); 264 265 if (nfds > 0) { 266 pthread_mutex_unlock(&s->mtx); 267 n = poll(pfds, nfds, -1); 268 pthread_mutex_lock(&s->mtx); 269 if (n < 0) 270 continue; 271 } 272 273 revents = WRAP(sio_revents)(s->hdl, pfds); 274 275 if (revents & POLLHUP) { 276 state = CUBEB_STATE_ERROR; 277 break; 278 } 279 280 if (revents & POLLOUT) { 281 n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart); 282 if (n == 0 && WRAP(sio_eof)(s->hdl)) { 283 DPR("sndio_mainloop() werr\n"); 284 state = CUBEB_STATE_ERROR; 285 break; 286 } 287 pstart += n; 288 } 289 290 if (revents & POLLIN) { 291 n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart); 292 if (n == 0 && WRAP(sio_eof)(s->hdl)) { 293 DPR("sndio_mainloop() rerr\n"); 294 state = CUBEB_STATE_ERROR; 295 break; 296 } 297 rstart += n; 298 } 299 300 /* skip rec block, if not recording (yet) */ 301 if (prime > 0 && (s->mode & SIO_REC)) 302 rstart = rend; 303 } 304 WRAP(sio_stop)(s->hdl); 305 s->hwpos = s->swpos; 306 pthread_mutex_unlock(&s->mtx); 307 s->state_cb(s, s->arg, state); 308 free(pfds); 309 CUBEB_UNREGISTER_THREAD(); 310 return NULL; 311 } 312 313 /*static*/ int 314 sndio_init(cubeb ** context, char const * context_name) 315 { 316 void * libsndio = NULL; 317 struct sio_hdl * hdl; 318 319 assert(context); 320 321 #ifndef DISABLE_LIBSNDIO_DLOPEN 322 libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY); 323 if (!libsndio) { 324 libsndio = dlopen("libsndio.so", RTLD_LAZY); 325 if (!libsndio) { 326 DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name); 327 return CUBEB_ERROR; 328 } 329 } 330 331 #define LOAD(x) \ 332 { \ 333 cubeb_##x = dlsym(libsndio, #x); \ 334 if (!cubeb_##x) { \ 335 DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \ 336 dlclose(libsndio); \ 337 return CUBEB_ERROR; \ 338 } \ 339 } 340 341 LIBSNDIO_API_VISIT(LOAD); 342 #undef LOAD 343 #endif 344 345 /* test if sndio works */ 346 hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1); 347 if (hdl == NULL) { 348 return CUBEB_ERROR; 349 } 350 WRAP(sio_close)(hdl); 351 352 DPR("sndio_init(%s)\n", context_name); 353 *context = malloc(sizeof(**context)); 354 if (*context == NULL) 355 return CUBEB_ERROR; 356 (*context)->libsndio = libsndio; 357 (*context)->ops = &sndio_ops; 358 (void)context_name; 359 return CUBEB_OK; 360 } 361 362 static char const * 363 sndio_get_backend_id(cubeb * context) 364 { 365 return "sndio"; 366 } 367 368 static void 369 sndio_destroy(cubeb * context) 370 { 371 DPR("sndio_destroy()\n"); 372 #ifndef DISABLE_LIBSNDIO_DLOPEN 373 if (context->libsndio) 374 dlclose(context->libsndio); 375 #endif 376 free(context); 377 } 378 379 static int 380 sndio_stream_init(cubeb * context, cubeb_stream ** stream, 381 char const * stream_name, cubeb_devid input_device, 382 cubeb_stream_params * input_stream_params, 383 cubeb_devid output_device, 384 cubeb_stream_params * output_stream_params, 385 unsigned int latency_frames, 386 cubeb_data_callback data_callback, 387 cubeb_state_callback state_callback, void * user_ptr) 388 { 389 cubeb_stream * s; 390 struct sio_par wpar, rpar; 391 cubeb_sample_format format; 392 int rate; 393 size_t bps; 394 395 DPR("sndio_stream_init(%s)\n", stream_name); 396 397 s = malloc(sizeof(cubeb_stream)); 398 if (s == NULL) 399 return CUBEB_ERROR; 400 memset(s, 0, sizeof(cubeb_stream)); 401 s->mode = 0; 402 if (input_stream_params) { 403 if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { 404 DPR("sndio_stream_init(), loopback not supported\n"); 405 goto err; 406 } 407 s->mode |= SIO_REC; 408 format = input_stream_params->format; 409 rate = input_stream_params->rate; 410 } 411 if (output_stream_params) { 412 if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { 413 DPR("sndio_stream_init(), loopback not supported\n"); 414 goto err; 415 } 416 s->mode |= SIO_PLAY; 417 format = output_stream_params->format; 418 rate = output_stream_params->rate; 419 } 420 if (s->mode == 0) { 421 DPR("sndio_stream_init(), neither playing nor recording\n"); 422 goto err; 423 } 424 s->context = context; 425 s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1); 426 if (s->hdl == NULL) { 427 DPR("sndio_stream_init(), sio_open() failed\n"); 428 goto err; 429 } 430 WRAP(sio_initpar)(&wpar); 431 wpar.sig = 1; 432 switch (format) { 433 case CUBEB_SAMPLE_S16LE: 434 wpar.le = 1; 435 wpar.bits = 16; 436 break; 437 case CUBEB_SAMPLE_S16BE: 438 wpar.le = 0; 439 wpar.bits = 16; 440 break; 441 case CUBEB_SAMPLE_FLOAT32NE: 442 wpar.le = SIO_LE_NATIVE; 443 wpar.bits = 24; 444 wpar.msb = 0; 445 break; 446 default: 447 DPR("sndio_stream_init() unsupported format\n"); 448 goto err; 449 } 450 wpar.bps = SIO_BPS(wpar.bits); 451 wpar.rate = rate; 452 if (s->mode & SIO_REC) 453 wpar.rchan = input_stream_params->channels; 454 if (s->mode & SIO_PLAY) 455 wpar.pchan = output_stream_params->channels; 456 wpar.appbufsz = latency_frames; 457 if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) { 458 DPR("sndio_stream_init(), sio_setpar() failed\n"); 459 goto err; 460 } 461 if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig || 462 rpar.bps != wpar.bps || 463 (wpar.bits < 8 * wpar.bps && rpar.msb != wpar.msb) || 464 rpar.rate != wpar.rate || 465 ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) || 466 ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) { 467 DPR("sndio_stream_init() unsupported params\n"); 468 goto err; 469 } 470 WRAP(sio_onmove)(s->hdl, sndio_onmove, s); 471 s->active = 0; 472 s->nfr = rpar.round; 473 s->rbpf = rpar.bps * rpar.rchan; 474 s->pbpf = rpar.bps * rpar.pchan; 475 s->rchan = rpar.rchan; 476 s->pchan = rpar.pchan; 477 s->nblks = rpar.bufsz / rpar.round; 478 s->data_cb = data_callback; 479 s->state_cb = state_callback; 480 s->arg = user_ptr; 481 s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; 482 s->hwpos = s->swpos = 0; 483 if (format == CUBEB_SAMPLE_FLOAT32LE) { 484 s->conv = 1; 485 bps = sizeof(float); 486 } else { 487 s->conv = 0; 488 bps = rpar.bps; 489 } 490 if (s->mode & SIO_PLAY) { 491 s->pbuf = malloc(bps * rpar.pchan * rpar.round); 492 if (s->pbuf == NULL) 493 goto err; 494 } 495 if (s->mode & SIO_REC) { 496 s->rbuf = malloc(bps * rpar.rchan * rpar.round); 497 if (s->rbuf == NULL) 498 goto err; 499 } 500 s->volume = 1.; 501 *stream = s; 502 DPR("sndio_stream_init() end, ok\n"); 503 (void)context; 504 (void)stream_name; 505 return CUBEB_OK; 506 err: 507 if (s->hdl) 508 WRAP(sio_close)(s->hdl); 509 if (s->pbuf) 510 free(s->pbuf); 511 if (s->rbuf) 512 free(s->pbuf); 513 free(s); 514 return CUBEB_ERROR; 515 } 516 517 static int 518 sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) 519 { 520 assert(ctx && max_channels); 521 522 *max_channels = 8; 523 524 return CUBEB_OK; 525 } 526 527 static int 528 sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) 529 { 530 /* 531 * We've no device-independent prefered rate; any rate will work if 532 * sndiod is running. If it isn't, 48kHz is what is most likely to 533 * work as most (but not all) devices support it. 534 */ 535 *rate = 48000; 536 return CUBEB_OK; 537 } 538 539 static int 540 sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, 541 uint32_t * latency_frames) 542 { 543 /* 544 * We've no device-independent minimum latency. 545 */ 546 *latency_frames = 2048; 547 548 return CUBEB_OK; 549 } 550 551 static void 552 sndio_stream_destroy(cubeb_stream * s) 553 { 554 DPR("sndio_stream_destroy()\n"); 555 WRAP(sio_close)(s->hdl); 556 if (s->mode & SIO_PLAY) 557 free(s->pbuf); 558 if (s->mode & SIO_REC) 559 free(s->rbuf); 560 free(s); 561 } 562 563 static int 564 sndio_stream_start(cubeb_stream * s) 565 { 566 int err; 567 568 DPR("sndio_stream_start()\n"); 569 s->active = 1; 570 err = pthread_create(&s->th, NULL, sndio_mainloop, s); 571 if (err) { 572 s->active = 0; 573 return CUBEB_ERROR; 574 } 575 return CUBEB_OK; 576 } 577 578 static int 579 sndio_stream_stop(cubeb_stream * s) 580 { 581 void * dummy; 582 583 DPR("sndio_stream_stop()\n"); 584 if (s->active) { 585 s->active = 0; 586 pthread_join(s->th, &dummy); 587 } 588 return CUBEB_OK; 589 } 590 591 static int 592 sndio_stream_get_position(cubeb_stream * s, uint64_t * p) 593 { 594 pthread_mutex_lock(&s->mtx); 595 DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos); 596 *p = s->hwpos; 597 pthread_mutex_unlock(&s->mtx); 598 return CUBEB_OK; 599 } 600 601 static int 602 sndio_stream_set_volume(cubeb_stream * s, float volume) 603 { 604 DPR("sndio_stream_set_volume(%f)\n", volume); 605 pthread_mutex_lock(&s->mtx); 606 if (volume < 0.) 607 volume = 0.; 608 else if (volume > 1.0) 609 volume = 1.; 610 s->volume = volume; 611 pthread_mutex_unlock(&s->mtx); 612 return CUBEB_OK; 613 } 614 615 int 616 sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) 617 { 618 // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open 619 // in the "Measuring the latency and buffers usage" paragraph. 620 *latency = stm->swpos - stm->hwpos; 621 return CUBEB_OK; 622 } 623 624 static int 625 sndio_enumerate_devices(cubeb * context, cubeb_device_type type, 626 cubeb_device_collection * collection) 627 { 628 static char dev[] = SIO_DEVANY; 629 cubeb_device_info * device; 630 631 device = malloc(sizeof(cubeb_device_info)); 632 if (device == NULL) 633 return CUBEB_ERROR; 634 635 device->devid = dev; /* passed to stream_init() */ 636 device->device_id = dev; /* printable in UI */ 637 device->friendly_name = dev; /* same, but friendly */ 638 device->group_id = dev; /* actual device if full-duplex */ 639 device->vendor_name = NULL; /* may be NULL */ 640 device->type = type; /* Input/Output */ 641 device->state = CUBEB_DEVICE_STATE_ENABLED; 642 device->preferred = CUBEB_DEVICE_PREF_ALL; 643 device->format = CUBEB_DEVICE_FMT_S16NE; 644 device->default_format = CUBEB_DEVICE_FMT_S16NE; 645 device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8; 646 device->default_rate = 48000; 647 device->min_rate = 4000; 648 device->max_rate = 192000; 649 device->latency_lo = 480; 650 device->latency_hi = 9600; 651 collection->device = device; 652 collection->count = 1; 653 return CUBEB_OK; 654 } 655 656 static int 657 sndio_device_collection_destroy(cubeb * context, 658 cubeb_device_collection * collection) 659 { 660 free(collection->device); 661 return CUBEB_OK; 662 } 663 664 static struct cubeb_ops const sndio_ops = { 665 .init = sndio_init, 666 .get_backend_id = sndio_get_backend_id, 667 .get_max_channel_count = sndio_get_max_channel_count, 668 .get_min_latency = sndio_get_min_latency, 669 .get_preferred_sample_rate = sndio_get_preferred_sample_rate, 670 .get_supported_input_processing_params = NULL, 671 .enumerate_devices = sndio_enumerate_devices, 672 .device_collection_destroy = sndio_device_collection_destroy, 673 .destroy = sndio_destroy, 674 .stream_init = sndio_stream_init, 675 .stream_destroy = sndio_stream_destroy, 676 .stream_start = sndio_stream_start, 677 .stream_stop = sndio_stream_stop, 678 .stream_get_position = sndio_stream_get_position, 679 .stream_get_latency = sndio_stream_get_latency, 680 .stream_set_volume = sndio_stream_set_volume, 681 .stream_set_name = NULL, 682 .stream_get_current_device = NULL, 683 .stream_set_input_mute = NULL, 684 .stream_set_input_processing_params = NULL, 685 .stream_device_destroy = NULL, 686 .stream_register_device_changed_callback = NULL, 687 .register_device_collection_changed = NULL};