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