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