Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/dep/cubeb/src/cubeb_sndio.c
4246 views
1
/*
2
* Copyright (c) 2011 Alexandre Ratchov <[email protected]>
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};
688
689