Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/dep/cubeb/src/cubeb_alsa.c
4246 views
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, &params, 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};
1494
1495