Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/dep/cubeb/src/cubeb_oss.c
4246 views
1
/*
2
* Copyright © 2019-2020 Nia Alarie <[email protected]>
3
* Copyright © 2020 Ka Ho Ng <[email protected]>
4
* Copyright © 2020 The FreeBSD Foundation
5
*
6
* Portions of this software were developed by Ka Ho Ng
7
* under sponsorship from the FreeBSD Foundation.
8
*
9
* This program is made available under an ISC-style license. See the
10
* accompanying file LICENSE for details.
11
*/
12
13
#include "cubeb-internal.h"
14
#include "cubeb/cubeb.h"
15
#include "cubeb_mixer.h"
16
#include "cubeb_strings.h"
17
#include "cubeb_tracing.h"
18
#include <assert.h>
19
#include <ctype.h>
20
#include <errno.h>
21
#include <fcntl.h>
22
#include <limits.h>
23
#include <poll.h>
24
#include <pthread.h>
25
#include <stdbool.h>
26
#include <stdio.h>
27
#include <stdlib.h>
28
#include <string.h>
29
#include <sys/ioctl.h>
30
#include <sys/soundcard.h>
31
#include <sys/types.h>
32
#include <unistd.h>
33
34
/* Supported well by most hardware. */
35
#ifndef OSS_PREFER_RATE
36
#define OSS_PREFER_RATE (48000)
37
#endif
38
39
/* Standard acceptable minimum. */
40
#ifndef OSS_LATENCY_MS
41
#define OSS_LATENCY_MS (8)
42
#endif
43
44
#ifndef OSS_NFRAGS
45
#define OSS_NFRAGS (4)
46
#endif
47
48
#ifndef OSS_DEFAULT_DEVICE
49
#define OSS_DEFAULT_DEVICE "/dev/dsp"
50
#endif
51
52
#ifndef OSS_DEFAULT_MIXER
53
#define OSS_DEFAULT_MIXER "/dev/mixer"
54
#endif
55
56
#define ENV_AUDIO_DEVICE "AUDIO_DEVICE"
57
58
#ifndef OSS_MAX_CHANNELS
59
#if defined(__FreeBSD__) || defined(__DragonFly__)
60
/*
61
* The current maximum number of channels supported
62
* on FreeBSD is 8.
63
*
64
* Reference: FreeBSD 12.1-RELEASE
65
*/
66
#define OSS_MAX_CHANNELS (8)
67
#elif defined(__sun__)
68
/*
69
* The current maximum number of channels supported
70
* on Illumos is 16.
71
*
72
* Reference: PSARC 2008/318
73
*/
74
#define OSS_MAX_CHANNELS (16)
75
#else
76
#define OSS_MAX_CHANNELS (2)
77
#endif
78
#endif
79
80
#if defined(__FreeBSD__) || defined(__DragonFly__)
81
#define SNDSTAT_BEGIN_STR "Installed devices:"
82
#define SNDSTAT_USER_BEGIN_STR "Installed devices from userspace:"
83
#define SNDSTAT_FV_BEGIN_STR "File Versions:"
84
#endif
85
86
static struct cubeb_ops const oss_ops;
87
88
struct cubeb {
89
struct cubeb_ops const * ops;
90
91
/* Our intern string store */
92
pthread_mutex_t mutex; /* protects devid_strs */
93
cubeb_strings * devid_strs;
94
};
95
96
struct oss_stream {
97
oss_devnode_t name;
98
int fd;
99
void * buf;
100
unsigned int bufframes;
101
unsigned int maxframes;
102
103
struct stream_info {
104
int channels;
105
int sample_rate;
106
int fmt;
107
int precision;
108
} info;
109
110
unsigned int frame_size; /* precision in bytes * channels */
111
bool floating;
112
};
113
114
struct cubeb_stream {
115
struct cubeb * context;
116
void * user_ptr;
117
pthread_t thread;
118
bool doorbell; /* (m) */
119
pthread_cond_t doorbell_cv; /* (m) */
120
pthread_cond_t stopped_cv; /* (m) */
121
pthread_mutex_t mtx; /* Members protected by this should be marked (m) */
122
bool thread_created; /* (m) */
123
bool running; /* (m) */
124
bool destroying; /* (m) */
125
cubeb_state state; /* (m) */
126
float volume /* (m) */;
127
struct oss_stream play;
128
struct oss_stream record;
129
cubeb_data_callback data_cb;
130
cubeb_state_callback state_cb;
131
uint64_t frames_written /* (m) */;
132
};
133
134
static char const *
135
oss_cubeb_devid_intern(cubeb * context, char const * devid)
136
{
137
char const * is;
138
pthread_mutex_lock(&context->mutex);
139
is = cubeb_strings_intern(context->devid_strs, devid);
140
pthread_mutex_unlock(&context->mutex);
141
return is;
142
}
143
144
int
145
oss_init(cubeb ** context, char const * context_name)
146
{
147
cubeb * c;
148
149
(void)context_name;
150
if ((c = calloc(1, sizeof(cubeb))) == NULL) {
151
return CUBEB_ERROR;
152
}
153
154
if (cubeb_strings_init(&c->devid_strs) == CUBEB_ERROR) {
155
goto fail;
156
}
157
158
if (pthread_mutex_init(&c->mutex, NULL) != 0) {
159
goto fail;
160
}
161
162
c->ops = &oss_ops;
163
*context = c;
164
return CUBEB_OK;
165
166
fail:
167
cubeb_strings_destroy(c->devid_strs);
168
free(c);
169
return CUBEB_ERROR;
170
}
171
172
static void
173
oss_destroy(cubeb * context)
174
{
175
pthread_mutex_destroy(&context->mutex);
176
cubeb_strings_destroy(context->devid_strs);
177
free(context);
178
}
179
180
static char const *
181
oss_get_backend_id(cubeb * context)
182
{
183
return "oss";
184
}
185
186
static int
187
oss_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
188
{
189
(void)context;
190
191
*rate = OSS_PREFER_RATE;
192
return CUBEB_OK;
193
}
194
195
static int
196
oss_get_max_channel_count(cubeb * context, uint32_t * max_channels)
197
{
198
(void)context;
199
200
*max_channels = OSS_MAX_CHANNELS;
201
return CUBEB_OK;
202
}
203
204
static int
205
oss_get_min_latency(cubeb * context, cubeb_stream_params params,
206
uint32_t * latency_frames)
207
{
208
(void)context;
209
210
*latency_frames = (OSS_LATENCY_MS * params.rate) / 1000;
211
return CUBEB_OK;
212
}
213
214
static void
215
oss_free_cubeb_device_info_strings(cubeb_device_info * cdi)
216
{
217
free((char *)cdi->device_id);
218
free((char *)cdi->friendly_name);
219
free((char *)cdi->group_id);
220
cdi->device_id = NULL;
221
cdi->friendly_name = NULL;
222
cdi->group_id = NULL;
223
}
224
225
#if defined(__FreeBSD__) || defined(__DragonFly__)
226
/*
227
* Check if the specified DSP is okay for the purpose specified
228
* in type. Here type can only specify one operation each time
229
* this helper is called.
230
*
231
* Return 0 if OK, otherwise 1.
232
*/
233
static int
234
oss_probe_open(const char * dsppath, cubeb_device_type type, int * fdp,
235
oss_audioinfo * resai)
236
{
237
oss_audioinfo ai;
238
int error;
239
int oflags = (type == CUBEB_DEVICE_TYPE_INPUT) ? O_RDONLY : O_WRONLY;
240
int dspfd = open(dsppath, oflags);
241
if (dspfd == -1)
242
return 1;
243
244
ai.dev = -1;
245
error = ioctl(dspfd, SNDCTL_AUDIOINFO, &ai);
246
if (error < 0) {
247
close(dspfd);
248
return 1;
249
}
250
251
if (resai)
252
*resai = ai;
253
if (fdp)
254
*fdp = dspfd;
255
else
256
close(dspfd);
257
return 0;
258
}
259
260
struct sndstat_info {
261
oss_devnode_t devname;
262
const char * desc;
263
cubeb_device_type type;
264
int preferred;
265
};
266
267
static int
268
oss_sndstat_line_parse(char * line, int is_ud, struct sndstat_info * sinfo)
269
{
270
char *matchptr = line, *n = NULL;
271
struct sndstat_info res;
272
273
memset(&res, 0, sizeof(res));
274
275
n = strchr(matchptr, ':');
276
if (n == NULL)
277
goto fail;
278
if (is_ud == 0) {
279
unsigned int devunit;
280
281
if (sscanf(matchptr, "pcm%u: ", &devunit) < 1)
282
goto fail;
283
284
if (snprintf(res.devname, sizeof(res.devname), "/dev/dsp%u", devunit) < 1)
285
goto fail;
286
} else {
287
if (n - matchptr >= (ssize_t)(sizeof(res.devname) - strlen("/dev/")))
288
goto fail;
289
290
strlcpy(res.devname, "/dev/", sizeof(res.devname));
291
strncat(res.devname, matchptr, n - matchptr);
292
}
293
matchptr = n + 1;
294
295
n = strchr(matchptr, '<');
296
if (n == NULL)
297
goto fail;
298
matchptr = n + 1;
299
n = strrchr(matchptr, '>');
300
if (n == NULL)
301
goto fail;
302
*n = 0;
303
res.desc = matchptr;
304
matchptr = n + 1;
305
306
n = strchr(matchptr, '(');
307
if (n == NULL)
308
goto fail;
309
matchptr = n + 1;
310
n = strrchr(matchptr, ')');
311
if (n == NULL)
312
goto fail;
313
*n = 0;
314
if (!isdigit(matchptr[0])) {
315
if (strstr(matchptr, "play") != NULL)
316
res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
317
if (strstr(matchptr, "rec") != NULL)
318
res.type |= CUBEB_DEVICE_TYPE_INPUT;
319
} else {
320
int p, r;
321
if (sscanf(matchptr, "%dp:%*dv/%dr:%*dv", &p, &r) != 2)
322
goto fail;
323
if (p > 0)
324
res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
325
if (r > 0)
326
res.type |= CUBEB_DEVICE_TYPE_INPUT;
327
}
328
matchptr = n + 1;
329
if (strstr(matchptr, "default") != NULL)
330
res.preferred = 1;
331
332
*sinfo = res;
333
return 0;
334
335
fail:
336
return 1;
337
}
338
339
/*
340
* XXX: On FreeBSD we have to rely on SNDCTL_CARDINFO to get all
341
* the usable audio devices currently, as SNDCTL_AUDIOINFO will
342
* never return directly usable audio device nodes.
343
*/
344
static int
345
oss_enumerate_devices(cubeb * context, cubeb_device_type type,
346
cubeb_device_collection * collection)
347
{
348
cubeb_device_info * devinfop = NULL;
349
char * line = NULL;
350
size_t linecap = 0;
351
FILE * sndstatfp = NULL;
352
int collection_cnt = 0;
353
int is_ud = 0;
354
int skipall = 0;
355
356
devinfop = calloc(1, sizeof(cubeb_device_info));
357
if (devinfop == NULL)
358
goto fail;
359
360
sndstatfp = fopen("/dev/sndstat", "r");
361
if (sndstatfp == NULL)
362
goto fail;
363
while (getline(&line, &linecap, sndstatfp) > 0) {
364
const char * devid = NULL;
365
struct sndstat_info sinfo;
366
oss_audioinfo ai;
367
368
if (!strncmp(line, SNDSTAT_FV_BEGIN_STR, strlen(SNDSTAT_FV_BEGIN_STR))) {
369
skipall = 1;
370
continue;
371
}
372
if (!strncmp(line, SNDSTAT_BEGIN_STR, strlen(SNDSTAT_BEGIN_STR))) {
373
is_ud = 0;
374
skipall = 0;
375
continue;
376
}
377
if (!strncmp(line, SNDSTAT_USER_BEGIN_STR,
378
strlen(SNDSTAT_USER_BEGIN_STR))) {
379
is_ud = 1;
380
skipall = 0;
381
continue;
382
}
383
if (skipall || isblank(line[0]))
384
continue;
385
386
if (oss_sndstat_line_parse(line, is_ud, &sinfo))
387
continue;
388
389
devinfop[collection_cnt].type = 0;
390
switch (sinfo.type) {
391
case CUBEB_DEVICE_TYPE_INPUT:
392
if (type & CUBEB_DEVICE_TYPE_OUTPUT)
393
continue;
394
break;
395
case CUBEB_DEVICE_TYPE_OUTPUT:
396
if (type & CUBEB_DEVICE_TYPE_INPUT)
397
continue;
398
break;
399
case 0:
400
continue;
401
}
402
403
if (oss_probe_open(sinfo.devname, type, NULL, &ai))
404
continue;
405
406
devid = oss_cubeb_devid_intern(context, sinfo.devname);
407
if (devid == NULL)
408
continue;
409
410
devinfop[collection_cnt].device_id = strdup(sinfo.devname);
411
asprintf((char **)&devinfop[collection_cnt].friendly_name, "%s: %s",
412
sinfo.devname, sinfo.desc);
413
devinfop[collection_cnt].group_id = strdup(sinfo.devname);
414
devinfop[collection_cnt].vendor_name = NULL;
415
if (devinfop[collection_cnt].device_id == NULL ||
416
devinfop[collection_cnt].friendly_name == NULL ||
417
devinfop[collection_cnt].group_id == NULL) {
418
oss_free_cubeb_device_info_strings(&devinfop[collection_cnt]);
419
continue;
420
}
421
422
devinfop[collection_cnt].type = type;
423
devinfop[collection_cnt].devid = devid;
424
devinfop[collection_cnt].state = CUBEB_DEVICE_STATE_ENABLED;
425
devinfop[collection_cnt].preferred =
426
(sinfo.preferred) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
427
devinfop[collection_cnt].format = CUBEB_DEVICE_FMT_S16NE;
428
devinfop[collection_cnt].default_format = CUBEB_DEVICE_FMT_S16NE;
429
devinfop[collection_cnt].max_channels = ai.max_channels;
430
devinfop[collection_cnt].default_rate = OSS_PREFER_RATE;
431
devinfop[collection_cnt].max_rate = ai.max_rate;
432
devinfop[collection_cnt].min_rate = ai.min_rate;
433
devinfop[collection_cnt].latency_lo = 0;
434
devinfop[collection_cnt].latency_hi = 0;
435
436
collection_cnt++;
437
438
void * newp =
439
reallocarray(devinfop, collection_cnt + 1, sizeof(cubeb_device_info));
440
if (newp == NULL)
441
goto fail;
442
devinfop = newp;
443
}
444
445
free(line);
446
fclose(sndstatfp);
447
448
collection->count = collection_cnt;
449
collection->device = devinfop;
450
451
return CUBEB_OK;
452
453
fail:
454
free(line);
455
if (sndstatfp)
456
fclose(sndstatfp);
457
free(devinfop);
458
return CUBEB_ERROR;
459
}
460
461
#else
462
463
static int
464
oss_enumerate_devices(cubeb * context, cubeb_device_type type,
465
cubeb_device_collection * collection)
466
{
467
oss_sysinfo si;
468
int error, i;
469
cubeb_device_info * devinfop = NULL;
470
int collection_cnt = 0;
471
int mixer_fd = -1;
472
473
mixer_fd = open(OSS_DEFAULT_MIXER, O_RDWR);
474
if (mixer_fd == -1) {
475
LOG("Failed to open mixer %s. errno: %d", OSS_DEFAULT_MIXER, errno);
476
return CUBEB_ERROR;
477
}
478
479
error = ioctl(mixer_fd, SNDCTL_SYSINFO, &si);
480
if (error) {
481
LOG("Failed to run SNDCTL_SYSINFO on mixer %s. errno: %d",
482
OSS_DEFAULT_MIXER, errno);
483
goto fail;
484
}
485
486
devinfop = calloc(si.numaudios, sizeof(cubeb_device_info));
487
if (devinfop == NULL)
488
goto fail;
489
490
collection->count = 0;
491
for (i = 0; i < si.numaudios; i++) {
492
oss_audioinfo ai;
493
cubeb_device_info cdi = {0};
494
const char * devid = NULL;
495
496
ai.dev = i;
497
error = ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai);
498
if (error)
499
goto fail;
500
501
assert(ai.dev < si.numaudios);
502
if (!ai.enabled)
503
continue;
504
505
cdi.type = 0;
506
switch (ai.caps & DSP_CAP_DUPLEX) {
507
case DSP_CAP_INPUT:
508
if (type & CUBEB_DEVICE_TYPE_OUTPUT)
509
continue;
510
break;
511
case DSP_CAP_OUTPUT:
512
if (type & CUBEB_DEVICE_TYPE_INPUT)
513
continue;
514
break;
515
case 0:
516
continue;
517
}
518
cdi.type = type;
519
520
devid = oss_cubeb_devid_intern(context, ai.devnode);
521
cdi.device_id = strdup(ai.name);
522
cdi.friendly_name = strdup(ai.name);
523
cdi.group_id = strdup(ai.name);
524
if (devid == NULL || cdi.device_id == NULL || cdi.friendly_name == NULL ||
525
cdi.group_id == NULL) {
526
oss_free_cubeb_device_info_strings(&cdi);
527
continue;
528
}
529
530
cdi.devid = devid;
531
cdi.vendor_name = NULL;
532
cdi.state = CUBEB_DEVICE_STATE_ENABLED;
533
cdi.preferred = CUBEB_DEVICE_PREF_NONE;
534
cdi.format = CUBEB_DEVICE_FMT_S16NE;
535
cdi.default_format = CUBEB_DEVICE_FMT_S16NE;
536
cdi.max_channels = ai.max_channels;
537
cdi.default_rate = OSS_PREFER_RATE;
538
cdi.max_rate = ai.max_rate;
539
cdi.min_rate = ai.min_rate;
540
cdi.latency_lo = 0;
541
cdi.latency_hi = 0;
542
543
devinfop[collection_cnt++] = cdi;
544
}
545
546
collection->count = collection_cnt;
547
collection->device = devinfop;
548
549
if (mixer_fd != -1)
550
close(mixer_fd);
551
return CUBEB_OK;
552
553
fail:
554
if (mixer_fd != -1)
555
close(mixer_fd);
556
free(devinfop);
557
return CUBEB_ERROR;
558
}
559
560
#endif
561
562
static int
563
oss_device_collection_destroy(cubeb * context,
564
cubeb_device_collection * collection)
565
{
566
size_t i;
567
for (i = 0; i < collection->count; i++) {
568
oss_free_cubeb_device_info_strings(&collection->device[i]);
569
}
570
free(collection->device);
571
collection->device = NULL;
572
collection->count = 0;
573
return 0;
574
}
575
576
static unsigned int
577
oss_chn_from_cubeb(cubeb_channel chn)
578
{
579
switch (chn) {
580
case CHANNEL_FRONT_LEFT:
581
return CHID_L;
582
case CHANNEL_FRONT_RIGHT:
583
return CHID_R;
584
case CHANNEL_FRONT_CENTER:
585
return CHID_C;
586
case CHANNEL_LOW_FREQUENCY:
587
return CHID_LFE;
588
case CHANNEL_BACK_LEFT:
589
return CHID_LR;
590
case CHANNEL_BACK_RIGHT:
591
return CHID_RR;
592
case CHANNEL_SIDE_LEFT:
593
return CHID_LS;
594
case CHANNEL_SIDE_RIGHT:
595
return CHID_RS;
596
default:
597
return CHID_UNDEF;
598
}
599
}
600
601
static unsigned long long
602
oss_cubeb_layout_to_chnorder(cubeb_channel_layout layout)
603
{
604
unsigned int i, nchns = 0;
605
unsigned long long chnorder = 0;
606
607
for (i = 0; layout; i++, layout >>= 1) {
608
unsigned long long chid = oss_chn_from_cubeb((layout & 1) << i);
609
if (chid == CHID_UNDEF)
610
continue;
611
612
chnorder |= (chid & 0xf) << nchns * 4;
613
nchns++;
614
}
615
616
return chnorder;
617
}
618
619
static int
620
oss_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
621
struct stream_info * sinfo)
622
{
623
unsigned long long chnorder;
624
625
sinfo->channels = params->channels;
626
sinfo->sample_rate = params->rate;
627
switch (params->format) {
628
case CUBEB_SAMPLE_S16LE:
629
sinfo->fmt = AFMT_S16_LE;
630
sinfo->precision = 16;
631
break;
632
case CUBEB_SAMPLE_S16BE:
633
sinfo->fmt = AFMT_S16_BE;
634
sinfo->precision = 16;
635
break;
636
case CUBEB_SAMPLE_FLOAT32NE:
637
sinfo->fmt = AFMT_S32_NE;
638
sinfo->precision = 32;
639
break;
640
default:
641
LOG("Unsupported format");
642
return CUBEB_ERROR_INVALID_FORMAT;
643
}
644
if (ioctl(fd, SNDCTL_DSP_CHANNELS, &sinfo->channels) == -1) {
645
return CUBEB_ERROR;
646
}
647
if (ioctl(fd, SNDCTL_DSP_SETFMT, &sinfo->fmt) == -1) {
648
return CUBEB_ERROR;
649
}
650
if (ioctl(fd, SNDCTL_DSP_SPEED, &sinfo->sample_rate) == -1) {
651
return CUBEB_ERROR;
652
}
653
/* Mono layout is an exception */
654
if (params->layout != CUBEB_LAYOUT_UNDEFINED &&
655
params->layout != CUBEB_LAYOUT_MONO) {
656
chnorder = oss_cubeb_layout_to_chnorder(params->layout);
657
if (ioctl(fd, SNDCTL_DSP_SET_CHNORDER, &chnorder) == -1)
658
LOG("Non-fatal error %d occured when setting channel order.", errno);
659
}
660
return CUBEB_OK;
661
}
662
663
static int
664
oss_stream_stop(cubeb_stream * s)
665
{
666
pthread_mutex_lock(&s->mtx);
667
if (s->thread_created && s->running) {
668
s->running = false;
669
s->doorbell = false;
670
pthread_cond_wait(&s->stopped_cv, &s->mtx);
671
}
672
if (s->state != CUBEB_STATE_STOPPED) {
673
s->state = CUBEB_STATE_STOPPED;
674
pthread_mutex_unlock(&s->mtx);
675
s->state_cb(s, s->user_ptr, CUBEB_STATE_STOPPED);
676
} else {
677
pthread_mutex_unlock(&s->mtx);
678
}
679
return CUBEB_OK;
680
}
681
682
static void
683
oss_stream_destroy(cubeb_stream * s)
684
{
685
pthread_mutex_lock(&s->mtx);
686
if (s->thread_created) {
687
s->destroying = true;
688
s->doorbell = true;
689
pthread_cond_signal(&s->doorbell_cv);
690
}
691
pthread_mutex_unlock(&s->mtx);
692
pthread_join(s->thread, NULL);
693
694
pthread_cond_destroy(&s->doorbell_cv);
695
pthread_cond_destroy(&s->stopped_cv);
696
pthread_mutex_destroy(&s->mtx);
697
if (s->play.fd != -1) {
698
close(s->play.fd);
699
}
700
if (s->record.fd != -1) {
701
close(s->record.fd);
702
}
703
free(s->play.buf);
704
free(s->record.buf);
705
free(s);
706
}
707
708
static void
709
oss_float_to_linear32(void * buf, unsigned sample_count, float vol)
710
{
711
float * in = buf;
712
int32_t * out = buf;
713
int32_t * tail = out + sample_count;
714
715
while (out < tail) {
716
int64_t f = *(in++) * vol * 0x80000000LL;
717
if (f < -INT32_MAX)
718
f = -INT32_MAX;
719
else if (f > INT32_MAX)
720
f = INT32_MAX;
721
*(out++) = f;
722
}
723
}
724
725
static void
726
oss_linear32_to_float(void * buf, unsigned sample_count)
727
{
728
int32_t * in = buf;
729
float * out = buf;
730
float * tail = out + sample_count;
731
732
while (out < tail) {
733
*(out++) = (1.0 / 0x80000000LL) * *(in++);
734
}
735
}
736
737
static void
738
oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
739
{
740
unsigned i;
741
int32_t multiplier = vol * 0x8000;
742
743
for (i = 0; i < sample_count; ++i) {
744
buf[i] = (buf[i] * multiplier) >> 15;
745
}
746
}
747
748
static int
749
oss_get_rec_frames(cubeb_stream * s, unsigned int nframes)
750
{
751
size_t rem = nframes * s->record.frame_size;
752
size_t read_ofs = 0;
753
while (rem > 0) {
754
ssize_t n;
755
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, rem)) <
756
0) {
757
if (errno == EINTR)
758
continue;
759
return CUBEB_ERROR;
760
}
761
read_ofs += n;
762
rem -= n;
763
}
764
return 0;
765
}
766
767
static int
768
oss_put_play_frames(cubeb_stream * s, unsigned int nframes)
769
{
770
size_t rem = nframes * s->play.frame_size;
771
size_t write_ofs = 0;
772
while (rem > 0) {
773
ssize_t n;
774
if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, rem)) < 0) {
775
if (errno == EINTR)
776
continue;
777
return CUBEB_ERROR;
778
}
779
pthread_mutex_lock(&s->mtx);
780
s->frames_written += n / s->play.frame_size;
781
pthread_mutex_unlock(&s->mtx);
782
write_ofs += n;
783
rem -= n;
784
}
785
return 0;
786
}
787
788
static int
789
oss_wait_fds_for_space(cubeb_stream * s, long * nfrp)
790
{
791
audio_buf_info bi;
792
struct pollfd pfds[2];
793
long nfr, tnfr;
794
int i;
795
796
assert(s->play.fd != -1 || s->record.fd != -1);
797
pfds[0].events = POLLOUT | POLLHUP;
798
pfds[0].revents = 0;
799
pfds[0].fd = s->play.fd;
800
pfds[1].events = POLLIN | POLLHUP;
801
pfds[1].revents = 0;
802
pfds[1].fd = s->record.fd;
803
804
retry:
805
nfr = LONG_MAX;
806
807
if (poll(pfds, 2, 1000) == -1) {
808
return CUBEB_ERROR;
809
}
810
811
for (i = 0; i < 2; i++) {
812
if (pfds[i].revents & POLLHUP) {
813
return CUBEB_ERROR;
814
}
815
}
816
817
if (s->play.fd != -1) {
818
if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
819
return CUBEB_STATE_ERROR;
820
}
821
tnfr = bi.bytes / s->play.frame_size;
822
if (tnfr <= 0) {
823
/* too little space - stop polling record, if any */
824
pfds[0].fd = s->play.fd;
825
pfds[1].fd = -1;
826
goto retry;
827
} else if (tnfr > (long)s->play.maxframes) {
828
/* too many frames available - limit */
829
tnfr = (long)s->play.maxframes;
830
}
831
if (nfr > tnfr) {
832
nfr = tnfr;
833
}
834
}
835
if (s->record.fd != -1) {
836
if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi) == -1) {
837
return CUBEB_STATE_ERROR;
838
}
839
tnfr = bi.bytes / s->record.frame_size;
840
if (tnfr <= 0) {
841
/* too little space - stop polling playback, if any */
842
pfds[0].fd = -1;
843
pfds[1].fd = s->record.fd;
844
goto retry;
845
} else if (tnfr > (long)s->record.maxframes) {
846
/* too many frames available - limit */
847
tnfr = (long)s->record.maxframes;
848
}
849
if (nfr > tnfr) {
850
nfr = tnfr;
851
}
852
}
853
854
*nfrp = nfr;
855
return 0;
856
}
857
858
/* 1 - Stopped by cubeb_stream_stop, otherwise 0 */
859
static int
860
oss_audio_loop(cubeb_stream * s, cubeb_state * new_state)
861
{
862
cubeb_state state = CUBEB_STATE_STOPPED;
863
int trig = 0, drain = 0;
864
const bool play_on = s->play.fd != -1, record_on = s->record.fd != -1;
865
long nfr = 0;
866
867
if (record_on) {
868
if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
869
LOG("Error %d occured when setting trigger on record fd", errno);
870
state = CUBEB_STATE_ERROR;
871
goto breakdown;
872
}
873
874
trig |= PCM_ENABLE_INPUT;
875
memset(s->record.buf, 0, s->record.bufframes * s->record.frame_size);
876
877
if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
878
LOG("Error %d occured when setting trigger on record fd", errno);
879
state = CUBEB_STATE_ERROR;
880
goto breakdown;
881
}
882
}
883
884
if (!play_on && !record_on) {
885
/*
886
* Stop here if the stream is not play & record stream,
887
* play-only stream or record-only stream
888
*/
889
890
goto breakdown;
891
}
892
893
while (1) {
894
pthread_mutex_lock(&s->mtx);
895
if (!s->running || s->destroying) {
896
pthread_mutex_unlock(&s->mtx);
897
break;
898
}
899
pthread_mutex_unlock(&s->mtx);
900
901
long got = 0;
902
if (nfr > 0) {
903
if (record_on) {
904
if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
905
state = CUBEB_STATE_ERROR;
906
goto breakdown;
907
}
908
if (s->record.floating) {
909
oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
910
}
911
}
912
913
got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
914
if (got == CUBEB_ERROR) {
915
state = CUBEB_STATE_ERROR;
916
goto breakdown;
917
}
918
if (got < nfr) {
919
if (s->play.fd != -1) {
920
drain = 1;
921
} else {
922
/*
923
* This is a record-only stream and number of frames
924
* returned from data_cb() is smaller than number
925
* of frames required to read. Stop here.
926
*/
927
state = CUBEB_STATE_STOPPED;
928
goto breakdown;
929
}
930
}
931
932
if (got > 0 && play_on) {
933
float vol;
934
935
pthread_mutex_lock(&s->mtx);
936
vol = s->volume;
937
pthread_mutex_unlock(&s->mtx);
938
939
if (s->play.floating) {
940
oss_float_to_linear32(s->play.buf, s->play.info.channels * got, vol);
941
} else {
942
oss_linear16_set_vol((int16_t *)s->play.buf,
943
s->play.info.channels * got, vol);
944
}
945
if (oss_put_play_frames(s, got) == CUBEB_ERROR) {
946
state = CUBEB_STATE_ERROR;
947
goto breakdown;
948
}
949
}
950
if (drain) {
951
state = CUBEB_STATE_DRAINED;
952
goto breakdown;
953
}
954
}
955
956
if (oss_wait_fds_for_space(s, &nfr) != 0) {
957
state = CUBEB_STATE_ERROR;
958
goto breakdown;
959
}
960
}
961
962
return 1;
963
964
breakdown:
965
pthread_mutex_lock(&s->mtx);
966
*new_state = s->state = state;
967
s->running = false;
968
pthread_mutex_unlock(&s->mtx);
969
return 0;
970
}
971
972
static void *
973
oss_io_routine(void * arg)
974
{
975
cubeb_stream * s = arg;
976
cubeb_state new_state;
977
int stopped;
978
979
CUBEB_REGISTER_THREAD("cubeb rendering thread");
980
981
do {
982
pthread_mutex_lock(&s->mtx);
983
if (s->destroying) {
984
pthread_mutex_unlock(&s->mtx);
985
break;
986
}
987
pthread_mutex_unlock(&s->mtx);
988
989
stopped = oss_audio_loop(s, &new_state);
990
if (s->record.fd != -1)
991
ioctl(s->record.fd, SNDCTL_DSP_HALT_INPUT, NULL);
992
if (!stopped)
993
s->state_cb(s, s->user_ptr, new_state);
994
995
pthread_mutex_lock(&s->mtx);
996
pthread_cond_signal(&s->stopped_cv);
997
if (s->destroying) {
998
pthread_mutex_unlock(&s->mtx);
999
break;
1000
}
1001
while (!s->doorbell) {
1002
pthread_cond_wait(&s->doorbell_cv, &s->mtx);
1003
}
1004
s->doorbell = false;
1005
pthread_mutex_unlock(&s->mtx);
1006
} while (1);
1007
1008
pthread_mutex_lock(&s->mtx);
1009
s->thread_created = false;
1010
pthread_mutex_unlock(&s->mtx);
1011
1012
CUBEB_UNREGISTER_THREAD();
1013
1014
return NULL;
1015
}
1016
1017
static inline int
1018
oss_calc_frag_shift(unsigned int frames, unsigned int frame_size)
1019
{
1020
int n = 4;
1021
int blksize = frames * frame_size;
1022
while ((1 << n) < blksize) {
1023
n++;
1024
}
1025
return n;
1026
}
1027
1028
static inline int
1029
oss_get_frag_params(unsigned int shift)
1030
{
1031
return (OSS_NFRAGS << 16) | shift;
1032
}
1033
1034
static int
1035
oss_stream_init(cubeb * context, cubeb_stream ** stream,
1036
char const * stream_name, cubeb_devid input_device,
1037
cubeb_stream_params * input_stream_params,
1038
cubeb_devid output_device,
1039
cubeb_stream_params * output_stream_params,
1040
unsigned int latency_frames, cubeb_data_callback data_callback,
1041
cubeb_state_callback state_callback, void * user_ptr)
1042
{
1043
int ret = CUBEB_OK;
1044
cubeb_stream * s = NULL;
1045
const char * defdsp;
1046
1047
if (!(defdsp = getenv(ENV_AUDIO_DEVICE)) || *defdsp == '\0')
1048
defdsp = OSS_DEFAULT_DEVICE;
1049
1050
(void)stream_name;
1051
if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
1052
ret = CUBEB_ERROR;
1053
goto error;
1054
}
1055
s->state = CUBEB_STATE_STOPPED;
1056
s->record.fd = s->play.fd = -1;
1057
if (input_device != NULL) {
1058
strlcpy(s->record.name, input_device, sizeof(s->record.name));
1059
} else {
1060
strlcpy(s->record.name, defdsp, sizeof(s->record.name));
1061
}
1062
if (output_device != NULL) {
1063
strlcpy(s->play.name, output_device, sizeof(s->play.name));
1064
} else {
1065
strlcpy(s->play.name, defdsp, sizeof(s->play.name));
1066
}
1067
if (input_stream_params != NULL) {
1068
unsigned int nb_channels;
1069
uint32_t minframes;
1070
1071
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
1072
LOG("Loopback not supported");
1073
ret = CUBEB_ERROR_NOT_SUPPORTED;
1074
goto error;
1075
}
1076
nb_channels = cubeb_channel_layout_nb_channels(input_stream_params->layout);
1077
if (input_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
1078
nb_channels != input_stream_params->channels) {
1079
LOG("input_stream_params->layout does not match "
1080
"input_stream_params->channels");
1081
ret = CUBEB_ERROR_INVALID_PARAMETER;
1082
goto error;
1083
}
1084
if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
1085
LOG("Audio device \"%s\" could not be opened as read-only",
1086
s->record.name);
1087
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
1088
goto error;
1089
}
1090
if ((ret = oss_copy_params(s->record.fd, s, input_stream_params,
1091
&s->record.info)) != CUBEB_OK) {
1092
LOG("Setting record params failed");
1093
goto error;
1094
}
1095
s->record.floating =
1096
(input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
1097
s->record.frame_size =
1098
s->record.info.channels * (s->record.info.precision / 8);
1099
s->record.bufframes = latency_frames;
1100
1101
oss_get_min_latency(context, *input_stream_params, &minframes);
1102
if (s->record.bufframes < minframes) {
1103
s->record.bufframes = minframes;
1104
}
1105
}
1106
if (output_stream_params != NULL) {
1107
unsigned int nb_channels;
1108
uint32_t minframes;
1109
1110
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
1111
LOG("Loopback not supported");
1112
ret = CUBEB_ERROR_NOT_SUPPORTED;
1113
goto error;
1114
}
1115
nb_channels =
1116
cubeb_channel_layout_nb_channels(output_stream_params->layout);
1117
if (output_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
1118
nb_channels != output_stream_params->channels) {
1119
LOG("output_stream_params->layout does not match "
1120
"output_stream_params->channels");
1121
ret = CUBEB_ERROR_INVALID_PARAMETER;
1122
goto error;
1123
}
1124
if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
1125
LOG("Audio device \"%s\" could not be opened as write-only",
1126
s->play.name);
1127
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
1128
goto error;
1129
}
1130
if ((ret = oss_copy_params(s->play.fd, s, output_stream_params,
1131
&s->play.info)) != CUBEB_OK) {
1132
LOG("Setting play params failed");
1133
goto error;
1134
}
1135
s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
1136
s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
1137
s->play.bufframes = latency_frames;
1138
1139
oss_get_min_latency(context, *output_stream_params, &minframes);
1140
if (s->play.bufframes < minframes) {
1141
s->play.bufframes = minframes;
1142
}
1143
}
1144
if (s->play.fd != -1) {
1145
int frag = oss_get_frag_params(
1146
oss_calc_frag_shift(s->play.bufframes, s->play.frame_size));
1147
if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
1148
LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
1149
frag);
1150
audio_buf_info bi;
1151
if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
1152
LOG("Failed to get play fd's buffer info.");
1153
else {
1154
s->play.bufframes = (bi.fragsize * bi.fragstotal) / s->play.frame_size;
1155
}
1156
int lw;
1157
1158
/*
1159
* Force 32 ms service intervals at most, or when recording is
1160
* active, use the recording service intervals as a reference.
1161
*/
1162
s->play.maxframes = (32 * output_stream_params->rate) / 1000;
1163
if (s->record.fd != -1 || s->play.maxframes >= s->play.bufframes) {
1164
lw = s->play.frame_size; /* Feed data when possible. */
1165
s->play.maxframes = s->play.bufframes;
1166
} else {
1167
lw = (s->play.bufframes - s->play.maxframes) * s->play.frame_size;
1168
}
1169
if (ioctl(s->play.fd, SNDCTL_DSP_LOW_WATER, &lw))
1170
LOG("Audio device \"%s\" (play) could not set trigger threshold",
1171
s->play.name);
1172
}
1173
if (s->record.fd != -1) {
1174
int frag = oss_get_frag_params(
1175
oss_calc_frag_shift(s->record.bufframes, s->record.frame_size));
1176
if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
1177
LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
1178
frag);
1179
audio_buf_info bi;
1180
if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
1181
LOG("Failed to get record fd's buffer info.");
1182
else {
1183
s->record.bufframes =
1184
(bi.fragsize * bi.fragstotal) / s->record.frame_size;
1185
}
1186
1187
s->record.maxframes = s->record.bufframes;
1188
int lw = s->record.frame_size;
1189
if (ioctl(s->record.fd, SNDCTL_DSP_LOW_WATER, &lw))
1190
LOG("Audio device \"%s\" (record) could not set trigger threshold",
1191
s->record.name);
1192
}
1193
s->context = context;
1194
s->volume = 1.0;
1195
s->state_cb = state_callback;
1196
s->data_cb = data_callback;
1197
s->user_ptr = user_ptr;
1198
1199
if (pthread_mutex_init(&s->mtx, NULL) != 0) {
1200
LOG("Failed to create mutex");
1201
goto error;
1202
}
1203
if (pthread_cond_init(&s->doorbell_cv, NULL) != 0) {
1204
LOG("Failed to create cv");
1205
goto error;
1206
}
1207
if (pthread_cond_init(&s->stopped_cv, NULL) != 0) {
1208
LOG("Failed to create cv");
1209
goto error;
1210
}
1211
s->doorbell = false;
1212
1213
if (s->play.fd != -1) {
1214
if ((s->play.buf = calloc(s->play.bufframes, s->play.frame_size)) == NULL) {
1215
ret = CUBEB_ERROR;
1216
goto error;
1217
}
1218
}
1219
if (s->record.fd != -1) {
1220
if ((s->record.buf = calloc(s->record.bufframes, s->record.frame_size)) ==
1221
NULL) {
1222
ret = CUBEB_ERROR;
1223
goto error;
1224
}
1225
}
1226
1227
*stream = s;
1228
return CUBEB_OK;
1229
error:
1230
if (s != NULL) {
1231
oss_stream_destroy(s);
1232
}
1233
return ret;
1234
}
1235
1236
static int
1237
oss_stream_thr_create(cubeb_stream * s)
1238
{
1239
if (s->thread_created) {
1240
s->doorbell = true;
1241
pthread_cond_signal(&s->doorbell_cv);
1242
return CUBEB_OK;
1243
}
1244
1245
if (pthread_create(&s->thread, NULL, oss_io_routine, s) != 0) {
1246
LOG("Couldn't create thread");
1247
return CUBEB_ERROR;
1248
}
1249
1250
return CUBEB_OK;
1251
}
1252
1253
static int
1254
oss_stream_start(cubeb_stream * s)
1255
{
1256
s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
1257
pthread_mutex_lock(&s->mtx);
1258
/* Disallow starting an already started stream */
1259
assert(!s->running && s->state != CUBEB_STATE_STARTED);
1260
if (oss_stream_thr_create(s) != CUBEB_OK) {
1261
pthread_mutex_unlock(&s->mtx);
1262
s->state_cb(s, s->user_ptr, CUBEB_STATE_ERROR);
1263
return CUBEB_ERROR;
1264
}
1265
s->state = CUBEB_STATE_STARTED;
1266
s->thread_created = true;
1267
s->running = true;
1268
pthread_mutex_unlock(&s->mtx);
1269
return CUBEB_OK;
1270
}
1271
1272
static int
1273
oss_stream_get_position(cubeb_stream * s, uint64_t * position)
1274
{
1275
pthread_mutex_lock(&s->mtx);
1276
*position = s->frames_written;
1277
pthread_mutex_unlock(&s->mtx);
1278
return CUBEB_OK;
1279
}
1280
1281
static int
1282
oss_stream_get_latency(cubeb_stream * s, uint32_t * latency)
1283
{
1284
int delay;
1285
1286
if (ioctl(s->play.fd, SNDCTL_DSP_GETODELAY, &delay) == -1) {
1287
return CUBEB_ERROR;
1288
}
1289
1290
/* Return number of frames there */
1291
*latency = delay / s->play.frame_size;
1292
return CUBEB_OK;
1293
}
1294
1295
static int
1296
oss_stream_set_volume(cubeb_stream * stream, float volume)
1297
{
1298
if (volume < 0.0)
1299
volume = 0.0;
1300
else if (volume > 1.0)
1301
volume = 1.0;
1302
pthread_mutex_lock(&stream->mtx);
1303
stream->volume = volume;
1304
pthread_mutex_unlock(&stream->mtx);
1305
return CUBEB_OK;
1306
}
1307
1308
static int
1309
oss_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
1310
{
1311
*device = calloc(1, sizeof(cubeb_device));
1312
if (*device == NULL) {
1313
return CUBEB_ERROR;
1314
}
1315
(*device)->input_name =
1316
stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
1317
(*device)->output_name =
1318
stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
1319
return CUBEB_OK;
1320
}
1321
1322
static int
1323
oss_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
1324
{
1325
(void)stream;
1326
free(device->input_name);
1327
free(device->output_name);
1328
free(device);
1329
return CUBEB_OK;
1330
}
1331
1332
static struct cubeb_ops const oss_ops = {
1333
.init = oss_init,
1334
.get_backend_id = oss_get_backend_id,
1335
.get_max_channel_count = oss_get_max_channel_count,
1336
.get_min_latency = oss_get_min_latency,
1337
.get_preferred_sample_rate = oss_get_preferred_sample_rate,
1338
.get_supported_input_processing_params = NULL,
1339
.enumerate_devices = oss_enumerate_devices,
1340
.device_collection_destroy = oss_device_collection_destroy,
1341
.destroy = oss_destroy,
1342
.stream_init = oss_stream_init,
1343
.stream_destroy = oss_stream_destroy,
1344
.stream_start = oss_stream_start,
1345
.stream_stop = oss_stream_stop,
1346
.stream_get_position = oss_stream_get_position,
1347
.stream_get_latency = oss_stream_get_latency,
1348
.stream_get_input_latency = NULL,
1349
.stream_set_volume = oss_stream_set_volume,
1350
.stream_set_name = NULL,
1351
.stream_get_current_device = oss_get_current_device,
1352
.stream_set_input_mute = NULL,
1353
.stream_set_input_processing_params = NULL,
1354
.stream_device_destroy = oss_stream_device_destroy,
1355
.stream_register_device_changed_callback = NULL,
1356
.register_device_collection_changed = NULL};
1357
1358