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 "cubeb-internal.h"
14 #include "cubeb/cubeb.h"
15 #include "cubeb_mixer.h"
16 #include "cubeb_strings.h"
17 #include <assert.h>
18 #include <ctype.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <limits.h>
22 #include <poll.h>
23 #include <pthread.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/ioctl.h>
29 #include <sys/soundcard.h>
30 #include <sys/types.h>
31 #include <unistd.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 unsigned int bufframes;
100 unsigned int maxframes;
101
102 struct stream_info {
103 int channels;
104 int sample_rate;
105 int fmt;
106 int precision;
107 } info;
108
109 unsigned int frame_size; /* precision in bytes * channels */
110 bool floating;
111 };
112
113 struct cubeb_stream {
114 struct cubeb * context;
115 void * user_ptr;
116 pthread_t thread;
117 bool doorbell; /* (m) */
118 pthread_cond_t doorbell_cv; /* (m) */
119 pthread_cond_t stopped_cv; /* (m) */
120 pthread_mutex_t mtx; /* Members protected by this should be marked (m) */
121 bool thread_created; /* (m) */
122 bool running; /* (m) */
123 bool destroying; /* (m) */
124 cubeb_state state; /* (m) */
125 float volume /* (m) */;
126 struct oss_stream play;
127 struct oss_stream record;
128 cubeb_data_callback data_cb;
129 cubeb_state_callback state_cb;
130 uint64_t frames_written /* (m) */;
131 };
132
133 static char const *
oss_cubeb_devid_intern(cubeb * context,char const * devid)134 oss_cubeb_devid_intern(cubeb * context, char const * devid)
135 {
136 char const * is;
137 pthread_mutex_lock(&context->mutex);
138 is = cubeb_strings_intern(context->devid_strs, devid);
139 pthread_mutex_unlock(&context->mutex);
140 return is;
141 }
142
143 int
oss_init(cubeb ** context,char const * context_name)144 oss_init(cubeb ** context, char const * context_name)
145 {
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, int * fdp,
234 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,
377 strlen(SNDSTAT_USER_BEGIN_STR))) {
378 is_ud = 1;
379 skipall = 0;
380 continue;
381 }
382 if (skipall || isblank(line[0]))
383 continue;
384
385 if (oss_sndstat_line_parse(line, is_ud, &sinfo))
386 continue;
387
388 devinfop[collection_cnt].type = 0;
389 switch (sinfo.type) {
390 case CUBEB_DEVICE_TYPE_INPUT:
391 if (type & CUBEB_DEVICE_TYPE_OUTPUT)
392 continue;
393 break;
394 case CUBEB_DEVICE_TYPE_OUTPUT:
395 if (type & CUBEB_DEVICE_TYPE_INPUT)
396 continue;
397 break;
398 case 0:
399 continue;
400 }
401
402 if (oss_probe_open(sinfo.devname, type, NULL, &ai))
403 continue;
404
405 devid = oss_cubeb_devid_intern(context, sinfo.devname);
406 if (devid == NULL)
407 continue;
408
409 devinfop[collection_cnt].device_id = strdup(sinfo.devname);
410 asprintf((char **)&devinfop[collection_cnt].friendly_name, "%s: %s",
411 sinfo.devname, sinfo.desc);
412 devinfop[collection_cnt].group_id = strdup(sinfo.devname);
413 devinfop[collection_cnt].vendor_name = NULL;
414 if (devinfop[collection_cnt].device_id == NULL ||
415 devinfop[collection_cnt].friendly_name == NULL ||
416 devinfop[collection_cnt].group_id == NULL) {
417 oss_free_cubeb_device_info_strings(&devinfop[collection_cnt]);
418 continue;
419 }
420
421 devinfop[collection_cnt].type = type;
422 devinfop[collection_cnt].devid = devid;
423 devinfop[collection_cnt].state = CUBEB_DEVICE_STATE_ENABLED;
424 devinfop[collection_cnt].preferred =
425 (sinfo.preferred) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
426 devinfop[collection_cnt].format = CUBEB_DEVICE_FMT_S16NE;
427 devinfop[collection_cnt].default_format = CUBEB_DEVICE_FMT_S16NE;
428 devinfop[collection_cnt].max_channels = ai.max_channels;
429 devinfop[collection_cnt].default_rate = OSS_PREFER_RATE;
430 devinfop[collection_cnt].max_rate = ai.max_rate;
431 devinfop[collection_cnt].min_rate = ai.min_rate;
432 devinfop[collection_cnt].latency_lo = 0;
433 devinfop[collection_cnt].latency_hi = 0;
434
435 collection_cnt++;
436
437 void * newp =
438 reallocarray(devinfop, collection_cnt + 1, sizeof(cubeb_device_info));
439 if (newp == NULL)
440 goto fail;
441 devinfop = newp;
442 }
443
444 free(line);
445 fclose(sndstatfp);
446
447 collection->count = collection_cnt;
448 collection->device = devinfop;
449
450 return CUBEB_OK;
451
452 fail:
453 free(line);
454 if (sndstatfp)
455 fclose(sndstatfp);
456 free(devinfop);
457 return CUBEB_ERROR;
458 }
459
460 #else
461
462 static int
oss_enumerate_devices(cubeb * context,cubeb_device_type type,cubeb_device_collection * collection)463 oss_enumerate_devices(cubeb * context, cubeb_device_type type,
464 cubeb_device_collection * collection)
465 {
466 oss_sysinfo si;
467 int error, i;
468 cubeb_device_info * devinfop = NULL;
469 int collection_cnt = 0;
470 int mixer_fd = -1;
471
472 mixer_fd = open(OSS_DEFAULT_MIXER, O_RDWR);
473 if (mixer_fd == -1) {
474 LOG("Failed to open mixer %s. errno: %d", OSS_DEFAULT_MIXER, errno);
475 return CUBEB_ERROR;
476 }
477
478 error = ioctl(mixer_fd, SNDCTL_SYSINFO, &si);
479 if (error) {
480 LOG("Failed to run SNDCTL_SYSINFO on mixer %s. errno: %d",
481 OSS_DEFAULT_MIXER, errno);
482 goto fail;
483 }
484
485 devinfop = calloc(si.numaudios, sizeof(cubeb_device_info));
486 if (devinfop == NULL)
487 goto fail;
488
489 collection->count = 0;
490 for (i = 0; i < si.numaudios; i++) {
491 oss_audioinfo ai;
492 cubeb_device_info cdi = {0};
493 const char * devid = NULL;
494
495 ai.dev = i;
496 error = ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai);
497 if (error)
498 goto fail;
499
500 assert(ai.dev < si.numaudios);
501 if (!ai.enabled)
502 continue;
503
504 cdi.type = 0;
505 switch (ai.caps & DSP_CAP_DUPLEX) {
506 case DSP_CAP_INPUT:
507 if (type & CUBEB_DEVICE_TYPE_OUTPUT)
508 continue;
509 break;
510 case DSP_CAP_OUTPUT:
511 if (type & CUBEB_DEVICE_TYPE_INPUT)
512 continue;
513 break;
514 case 0:
515 continue;
516 }
517 cdi.type = type;
518
519 devid = oss_cubeb_devid_intern(context, ai.devnode);
520 cdi.device_id = strdup(ai.name);
521 cdi.friendly_name = strdup(ai.name);
522 cdi.group_id = strdup(ai.name);
523 if (devid == NULL || cdi.device_id == NULL || cdi.friendly_name == NULL ||
524 cdi.group_id == NULL) {
525 oss_free_cubeb_device_info_strings(&cdi);
526 continue;
527 }
528
529 cdi.devid = devid;
530 cdi.vendor_name = NULL;
531 cdi.state = CUBEB_DEVICE_STATE_ENABLED;
532 cdi.preferred = CUBEB_DEVICE_PREF_NONE;
533 cdi.format = CUBEB_DEVICE_FMT_S16NE;
534 cdi.default_format = CUBEB_DEVICE_FMT_S16NE;
535 cdi.max_channels = ai.max_channels;
536 cdi.default_rate = OSS_PREFER_RATE;
537 cdi.max_rate = ai.max_rate;
538 cdi.min_rate = ai.min_rate;
539 cdi.latency_lo = 0;
540 cdi.latency_hi = 0;
541
542 devinfop[collection_cnt++] = cdi;
543 }
544
545 collection->count = collection_cnt;
546 collection->device = devinfop;
547
548 if (mixer_fd != -1)
549 close(mixer_fd);
550 return CUBEB_OK;
551
552 fail:
553 if (mixer_fd != -1)
554 close(mixer_fd);
555 free(devinfop);
556 return CUBEB_ERROR;
557 }
558
559 #endif
560
561 static int
oss_device_collection_destroy(cubeb * context,cubeb_device_collection * collection)562 oss_device_collection_destroy(cubeb * context,
563 cubeb_device_collection * collection)
564 {
565 size_t i;
566 for (i = 0; i < collection->count; i++) {
567 oss_free_cubeb_device_info_strings(&collection->device[i]);
568 }
569 free(collection->device);
570 collection->device = NULL;
571 collection->count = 0;
572 return 0;
573 }
574
575 static unsigned int
oss_chn_from_cubeb(cubeb_channel chn)576 oss_chn_from_cubeb(cubeb_channel chn)
577 {
578 switch (chn) {
579 case CHANNEL_FRONT_LEFT:
580 return CHID_L;
581 case CHANNEL_FRONT_RIGHT:
582 return CHID_R;
583 case CHANNEL_FRONT_CENTER:
584 return CHID_C;
585 case CHANNEL_LOW_FREQUENCY:
586 return CHID_LFE;
587 case CHANNEL_BACK_LEFT:
588 return CHID_LR;
589 case CHANNEL_BACK_RIGHT:
590 return CHID_RR;
591 case CHANNEL_SIDE_LEFT:
592 return CHID_LS;
593 case CHANNEL_SIDE_RIGHT:
594 return CHID_RS;
595 default:
596 return CHID_UNDEF;
597 }
598 }
599
600 static unsigned long long
oss_cubeb_layout_to_chnorder(cubeb_channel_layout layout)601 oss_cubeb_layout_to_chnorder(cubeb_channel_layout layout)
602 {
603 unsigned int i, nchns = 0;
604 unsigned long long chnorder = 0;
605
606 for (i = 0; layout; i++, layout >>= 1) {
607 unsigned long long chid = oss_chn_from_cubeb((layout & 1) << i);
608 if (chid == CHID_UNDEF)
609 continue;
610
611 chnorder |= (chid & 0xf) << nchns * 4;
612 nchns++;
613 }
614
615 return chnorder;
616 }
617
618 static int
oss_copy_params(int fd,cubeb_stream * stream,cubeb_stream_params * params,struct stream_info * sinfo)619 oss_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
620 struct stream_info * sinfo)
621 {
622 unsigned long long chnorder;
623
624 sinfo->channels = params->channels;
625 sinfo->sample_rate = params->rate;
626 switch (params->format) {
627 case CUBEB_SAMPLE_S16LE:
628 sinfo->fmt = AFMT_S16_LE;
629 sinfo->precision = 16;
630 break;
631 case CUBEB_SAMPLE_S16BE:
632 sinfo->fmt = AFMT_S16_BE;
633 sinfo->precision = 16;
634 break;
635 case CUBEB_SAMPLE_FLOAT32NE:
636 sinfo->fmt = AFMT_S32_NE;
637 sinfo->precision = 32;
638 break;
639 default:
640 LOG("Unsupported format");
641 return CUBEB_ERROR_INVALID_FORMAT;
642 }
643 if (ioctl(fd, SNDCTL_DSP_CHANNELS, &sinfo->channels) == -1) {
644 return CUBEB_ERROR;
645 }
646 if (ioctl(fd, SNDCTL_DSP_SETFMT, &sinfo->fmt) == -1) {
647 return CUBEB_ERROR;
648 }
649 if (ioctl(fd, SNDCTL_DSP_SPEED, &sinfo->sample_rate) == -1) {
650 return CUBEB_ERROR;
651 }
652 /* Mono layout is an exception */
653 if (params->layout != CUBEB_LAYOUT_UNDEFINED &&
654 params->layout != CUBEB_LAYOUT_MONO) {
655 chnorder = oss_cubeb_layout_to_chnorder(params->layout);
656 if (ioctl(fd, SNDCTL_DSP_SET_CHNORDER, &chnorder) == -1)
657 LOG("Non-fatal error %d occured when setting channel order.", errno);
658 }
659 return CUBEB_OK;
660 }
661
662 static int
oss_stream_stop(cubeb_stream * s)663 oss_stream_stop(cubeb_stream * s)
664 {
665 pthread_mutex_lock(&s->mtx);
666 if (s->thread_created && s->running) {
667 s->running = false;
668 s->doorbell = false;
669 pthread_cond_wait(&s->stopped_cv, &s->mtx);
670 }
671 if (s->state != CUBEB_STATE_STOPPED) {
672 s->state = CUBEB_STATE_STOPPED;
673 pthread_mutex_unlock(&s->mtx);
674 s->state_cb(s, s->user_ptr, CUBEB_STATE_STOPPED);
675 } else {
676 pthread_mutex_unlock(&s->mtx);
677 }
678 return CUBEB_OK;
679 }
680
681 static void
oss_stream_destroy(cubeb_stream * s)682 oss_stream_destroy(cubeb_stream * s)
683 {
684 pthread_mutex_lock(&s->mtx);
685 if (s->thread_created) {
686 s->destroying = true;
687 s->doorbell = true;
688 pthread_cond_signal(&s->doorbell_cv);
689 }
690 pthread_mutex_unlock(&s->mtx);
691 pthread_join(s->thread, NULL);
692
693 pthread_cond_destroy(&s->doorbell_cv);
694 pthread_cond_destroy(&s->stopped_cv);
695 pthread_mutex_destroy(&s->mtx);
696 if (s->play.fd != -1) {
697 close(s->play.fd);
698 }
699 if (s->record.fd != -1) {
700 close(s->record.fd);
701 }
702 free(s->play.buf);
703 free(s->record.buf);
704 free(s);
705 }
706
707 static void
oss_float_to_linear32(void * buf,unsigned sample_count,float vol)708 oss_float_to_linear32(void * buf, unsigned sample_count, float vol)
709 {
710 float * in = buf;
711 int32_t * out = buf;
712 int32_t * tail = out + sample_count;
713
714 while (out < tail) {
715 int64_t f = *(in++) * vol * 0x80000000LL;
716 if (f < -INT32_MAX)
717 f = -INT32_MAX;
718 else if (f > INT32_MAX)
719 f = INT32_MAX;
720 *(out++) = f;
721 }
722 }
723
724 static void
oss_linear32_to_float(void * buf,unsigned sample_count)725 oss_linear32_to_float(void * buf, unsigned sample_count)
726 {
727 int32_t * in = buf;
728 float * out = buf;
729 float * tail = out + sample_count;
730
731 while (out < tail) {
732 *(out++) = (1.0 / 0x80000000LL) * *(in++);
733 }
734 }
735
736 static void
oss_linear16_set_vol(int16_t * buf,unsigned sample_count,float vol)737 oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
738 {
739 unsigned i;
740 int32_t multiplier = vol * 0x8000;
741
742 for (i = 0; i < sample_count; ++i) {
743 buf[i] = (buf[i] * multiplier) >> 15;
744 }
745 }
746
747 static int
oss_get_rec_frames(cubeb_stream * s,unsigned int nframes)748 oss_get_rec_frames(cubeb_stream * s, unsigned int nframes)
749 {
750 size_t rem = nframes * s->record.frame_size;
751 size_t read_ofs = 0;
752 while (rem > 0) {
753 ssize_t n;
754 if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, rem)) <
755 0) {
756 if (errno == EINTR)
757 continue;
758 return CUBEB_ERROR;
759 }
760 read_ofs += n;
761 rem -= n;
762 }
763 return 0;
764 }
765
766 static int
oss_put_play_frames(cubeb_stream * s,unsigned int nframes)767 oss_put_play_frames(cubeb_stream * s, unsigned int nframes)
768 {
769 size_t rem = nframes * s->play.frame_size;
770 size_t write_ofs = 0;
771 while (rem > 0) {
772 ssize_t n;
773 if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, rem)) < 0) {
774 if (errno == EINTR)
775 continue;
776 return CUBEB_ERROR;
777 }
778 pthread_mutex_lock(&s->mtx);
779 s->frames_written += n / s->play.frame_size;
780 pthread_mutex_unlock(&s->mtx);
781 write_ofs += n;
782 rem -= n;
783 }
784 return 0;
785 }
786
787 static int
oss_wait_fds_for_space(cubeb_stream * s,long * nfrp)788 oss_wait_fds_for_space(cubeb_stream * s, long * nfrp)
789 {
790 audio_buf_info bi;
791 struct pollfd pfds[2];
792 long nfr, tnfr;
793 int i;
794
795 assert(s->play.fd != -1 || s->record.fd != -1);
796 pfds[0].events = POLLOUT | POLLHUP;
797 pfds[0].revents = 0;
798 pfds[0].fd = s->play.fd;
799 pfds[1].events = POLLIN | POLLHUP;
800 pfds[1].revents = 0;
801 pfds[1].fd = s->record.fd;
802
803 retry:
804 nfr = LONG_MAX;
805
806 if (poll(pfds, 2, 1000) == -1) {
807 return CUBEB_ERROR;
808 }
809
810 for (i = 0; i < 2; i++) {
811 if (pfds[i].revents & POLLHUP) {
812 return CUBEB_ERROR;
813 }
814 }
815
816 if (s->play.fd != -1) {
817 if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
818 return CUBEB_STATE_ERROR;
819 }
820 tnfr = bi.bytes / s->play.frame_size;
821 if (tnfr <= 0) {
822 /* too little space - stop polling record, if any */
823 pfds[0].fd = s->play.fd;
824 pfds[1].fd = -1;
825 goto retry;
826 } else if (tnfr > (long)s->play.maxframes) {
827 /* too many frames available - limit */
828 tnfr = (long)s->play.maxframes;
829 }
830 if (nfr > tnfr) {
831 nfr = tnfr;
832 }
833 }
834 if (s->record.fd != -1) {
835 if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi) == -1) {
836 return CUBEB_STATE_ERROR;
837 }
838 tnfr = bi.bytes / s->record.frame_size;
839 if (tnfr <= 0) {
840 /* too little space - stop polling playback, if any */
841 pfds[0].fd = -1;
842 pfds[1].fd = s->record.fd;
843 goto retry;
844 } else if (tnfr > (long)s->record.maxframes) {
845 /* too many frames available - limit */
846 tnfr = (long)s->record.maxframes;
847 }
848 if (nfr > tnfr) {
849 nfr = tnfr;
850 }
851 }
852
853 *nfrp = nfr;
854 return 0;
855 }
856
857 /* 1 - Stopped by cubeb_stream_stop, otherwise 0 */
858 static int
oss_audio_loop(cubeb_stream * s,cubeb_state * new_state)859 oss_audio_loop(cubeb_stream * s, cubeb_state * new_state)
860 {
861 cubeb_state state = CUBEB_STATE_STOPPED;
862 int trig = 0, drain = 0;
863 const bool play_on = s->play.fd != -1, record_on = s->record.fd != -1;
864 long nfr = 0;
865
866 if (record_on) {
867 if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
868 LOG("Error %d occured when setting trigger on record fd", errno);
869 state = CUBEB_STATE_ERROR;
870 goto breakdown;
871 }
872
873 trig |= PCM_ENABLE_INPUT;
874 memset(s->record.buf, 0, s->record.bufframes * s->record.frame_size);
875
876 if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
877 LOG("Error %d occured when setting trigger on record fd", errno);
878 state = CUBEB_STATE_ERROR;
879 goto breakdown;
880 }
881 }
882
883 if (!play_on && !record_on) {
884 /*
885 * Stop here if the stream is not play & record stream,
886 * play-only stream or record-only stream
887 */
888
889 goto breakdown;
890 }
891
892 while (1) {
893 pthread_mutex_lock(&s->mtx);
894 if (!s->running || s->destroying) {
895 pthread_mutex_unlock(&s->mtx);
896 break;
897 }
898 pthread_mutex_unlock(&s->mtx);
899
900 long got = 0;
901 if (nfr > 0) {
902 if (record_on) {
903 if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
904 state = CUBEB_STATE_ERROR;
905 goto breakdown;
906 }
907 if (s->record.floating) {
908 oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
909 }
910 }
911
912 got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
913 if (got == CUBEB_ERROR) {
914 state = CUBEB_STATE_ERROR;
915 goto breakdown;
916 }
917 if (got < nfr) {
918 if (s->play.fd != -1) {
919 drain = 1;
920 } else {
921 /*
922 * This is a record-only stream and number of frames
923 * returned from data_cb() is smaller than number
924 * of frames required to read. Stop here.
925 */
926 state = CUBEB_STATE_STOPPED;
927 goto breakdown;
928 }
929 }
930
931 if (got > 0 && play_on) {
932 float vol;
933
934 pthread_mutex_lock(&s->mtx);
935 vol = s->volume;
936 pthread_mutex_unlock(&s->mtx);
937
938 if (s->play.floating) {
939 oss_float_to_linear32(s->play.buf, s->play.info.channels * got, vol);
940 } else {
941 oss_linear16_set_vol((int16_t *)s->play.buf,
942 s->play.info.channels * got, vol);
943 }
944 if (oss_put_play_frames(s, got) == CUBEB_ERROR) {
945 state = CUBEB_STATE_ERROR;
946 goto breakdown;
947 }
948 }
949 if (drain) {
950 state = CUBEB_STATE_DRAINED;
951 goto breakdown;
952 }
953 }
954
955 if (oss_wait_fds_for_space(s, &nfr) != 0) {
956 state = CUBEB_STATE_ERROR;
957 goto breakdown;
958 }
959 }
960
961 return 1;
962
963 breakdown:
964 pthread_mutex_lock(&s->mtx);
965 *new_state = s->state = state;
966 s->running = false;
967 pthread_mutex_unlock(&s->mtx);
968 return 0;
969 }
970
971 static void *
oss_io_routine(void * arg)972 oss_io_routine(void * arg)
973 {
974 cubeb_stream * s = arg;
975 cubeb_state new_state;
976 int stopped;
977
978 do {
979 pthread_mutex_lock(&s->mtx);
980 if (s->destroying) {
981 pthread_mutex_unlock(&s->mtx);
982 break;
983 }
984 pthread_mutex_unlock(&s->mtx);
985
986 stopped = oss_audio_loop(s, &new_state);
987 if (s->record.fd != -1)
988 ioctl(s->record.fd, SNDCTL_DSP_HALT_INPUT, NULL);
989 if (!stopped)
990 s->state_cb(s, s->user_ptr, new_state);
991
992 pthread_mutex_lock(&s->mtx);
993 pthread_cond_signal(&s->stopped_cv);
994 if (s->destroying) {
995 pthread_mutex_unlock(&s->mtx);
996 break;
997 }
998 while (!s->doorbell) {
999 pthread_cond_wait(&s->doorbell_cv, &s->mtx);
1000 }
1001 s->doorbell = false;
1002 pthread_mutex_unlock(&s->mtx);
1003 } while (1);
1004
1005 pthread_mutex_lock(&s->mtx);
1006 s->thread_created = false;
1007 pthread_mutex_unlock(&s->mtx);
1008 return NULL;
1009 }
1010
1011 static inline int
oss_calc_frag_shift(unsigned int frames,unsigned int frame_size)1012 oss_calc_frag_shift(unsigned int frames, unsigned int frame_size)
1013 {
1014 int n = 4;
1015 int blksize = frames * frame_size;
1016 while ((1 << n) < blksize) {
1017 n++;
1018 }
1019 return n;
1020 }
1021
1022 static inline int
oss_get_frag_params(unsigned int shift)1023 oss_get_frag_params(unsigned int shift)
1024 {
1025 return (OSS_NFRAGS << 16) | shift;
1026 }
1027
1028 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)1029 oss_stream_init(cubeb * context, cubeb_stream ** stream,
1030 char const * stream_name, 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, cubeb_data_callback data_callback,
1035 cubeb_state_callback state_callback, void * user_ptr)
1036 {
1037 int ret = CUBEB_OK;
1038 cubeb_stream * s = NULL;
1039 const char * defdsp;
1040
1041 if (!(defdsp = getenv(ENV_AUDIO_DEVICE)) || *defdsp == '\0')
1042 defdsp = OSS_DEFAULT_DEVICE;
1043
1044 (void)stream_name;
1045 if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
1046 ret = CUBEB_ERROR;
1047 goto error;
1048 }
1049 s->state = CUBEB_STATE_STOPPED;
1050 s->record.fd = s->play.fd = -1;
1051 if (input_device != NULL) {
1052 strlcpy(s->record.name, input_device, sizeof(s->record.name));
1053 } else {
1054 strlcpy(s->record.name, defdsp, sizeof(s->record.name));
1055 }
1056 if (output_device != NULL) {
1057 strlcpy(s->play.name, output_device, sizeof(s->play.name));
1058 } else {
1059 strlcpy(s->play.name, defdsp, sizeof(s->play.name));
1060 }
1061 if (input_stream_params != NULL) {
1062 unsigned int nb_channels;
1063 uint32_t minframes;
1064
1065 if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
1066 LOG("Loopback not supported");
1067 ret = CUBEB_ERROR_NOT_SUPPORTED;
1068 goto error;
1069 }
1070 nb_channels = cubeb_channel_layout_nb_channels(input_stream_params->layout);
1071 if (input_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
1072 nb_channels != input_stream_params->channels) {
1073 LOG("input_stream_params->layout does not match "
1074 "input_stream_params->channels");
1075 ret = CUBEB_ERROR_INVALID_PARAMETER;
1076 goto error;
1077 }
1078 if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
1079 LOG("Audio device \"%s\" could not be opened as read-only",
1080 s->record.name);
1081 ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
1082 goto error;
1083 }
1084 if ((ret = oss_copy_params(s->record.fd, s, input_stream_params,
1085 &s->record.info)) != CUBEB_OK) {
1086 LOG("Setting record params failed");
1087 goto error;
1088 }
1089 s->record.floating =
1090 (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
1091 s->record.frame_size =
1092 s->record.info.channels * (s->record.info.precision / 8);
1093 s->record.bufframes = latency_frames;
1094
1095 oss_get_min_latency(context, *input_stream_params, &minframes);
1096 if (s->record.bufframes < minframes) {
1097 s->record.bufframes = minframes;
1098 }
1099 }
1100 if (output_stream_params != NULL) {
1101 unsigned int nb_channels;
1102 uint32_t minframes;
1103
1104 if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
1105 LOG("Loopback not supported");
1106 ret = CUBEB_ERROR_NOT_SUPPORTED;
1107 goto error;
1108 }
1109 nb_channels =
1110 cubeb_channel_layout_nb_channels(output_stream_params->layout);
1111 if (output_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
1112 nb_channels != output_stream_params->channels) {
1113 LOG("output_stream_params->layout does not match "
1114 "output_stream_params->channels");
1115 ret = CUBEB_ERROR_INVALID_PARAMETER;
1116 goto error;
1117 }
1118 if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
1119 LOG("Audio device \"%s\" could not be opened as write-only",
1120 s->play.name);
1121 ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
1122 goto error;
1123 }
1124 if ((ret = oss_copy_params(s->play.fd, s, output_stream_params,
1125 &s->play.info)) != CUBEB_OK) {
1126 LOG("Setting play params failed");
1127 goto error;
1128 }
1129 s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
1130 s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
1131 s->play.bufframes = latency_frames;
1132
1133 oss_get_min_latency(context, *output_stream_params, &minframes);
1134 if (s->play.bufframes < minframes) {
1135 s->play.bufframes = minframes;
1136 }
1137 }
1138 if (s->play.fd != -1) {
1139 int frag = oss_get_frag_params(
1140 oss_calc_frag_shift(s->play.bufframes, s->play.frame_size));
1141 if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
1142 LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
1143 frag);
1144 audio_buf_info bi;
1145 if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
1146 LOG("Failed to get play fd's buffer info.");
1147 else {
1148 s->play.bufframes = (bi.fragsize * bi.fragstotal) / s->play.frame_size;
1149 }
1150 int lw;
1151
1152 /*
1153 * Force 32 ms service intervals at most, or when recording is
1154 * active, use the recording service intervals as a reference.
1155 */
1156 s->play.maxframes = (32 * output_stream_params->rate) / 1000;
1157 if (s->record.fd != -1 || s->play.maxframes >= s->play.bufframes) {
1158 lw = s->play.frame_size; /* Feed data when possible. */
1159 s->play.maxframes = s->play.bufframes;
1160 } else {
1161 lw = (s->play.bufframes - s->play.maxframes) * s->play.frame_size;
1162 }
1163 if (ioctl(s->play.fd, SNDCTL_DSP_LOW_WATER, &lw))
1164 LOG("Audio device \"%s\" (play) could not set trigger threshold",
1165 s->play.name);
1166 }
1167 if (s->record.fd != -1) {
1168 int frag = oss_get_frag_params(
1169 oss_calc_frag_shift(s->record.bufframes, s->record.frame_size));
1170 if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
1171 LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
1172 frag);
1173 audio_buf_info bi;
1174 if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
1175 LOG("Failed to get record fd's buffer info.");
1176 else {
1177 s->record.bufframes =
1178 (bi.fragsize * bi.fragstotal) / s->record.frame_size;
1179 }
1180
1181 s->record.maxframes = s->record.bufframes;
1182 int lw = s->record.frame_size;
1183 if (ioctl(s->record.fd, SNDCTL_DSP_LOW_WATER, &lw))
1184 LOG("Audio device \"%s\" (record) could not set trigger threshold",
1185 s->record.name);
1186 }
1187 s->context = context;
1188 s->volume = 1.0;
1189 s->state_cb = state_callback;
1190 s->data_cb = data_callback;
1191 s->user_ptr = user_ptr;
1192
1193 if (pthread_mutex_init(&s->mtx, NULL) != 0) {
1194 LOG("Failed to create mutex");
1195 goto error;
1196 }
1197 if (pthread_cond_init(&s->doorbell_cv, NULL) != 0) {
1198 LOG("Failed to create cv");
1199 goto error;
1200 }
1201 if (pthread_cond_init(&s->stopped_cv, NULL) != 0) {
1202 LOG("Failed to create cv");
1203 goto error;
1204 }
1205 s->doorbell = false;
1206
1207 if (s->play.fd != -1) {
1208 if ((s->play.buf = calloc(s->play.bufframes, s->play.frame_size)) == NULL) {
1209 ret = CUBEB_ERROR;
1210 goto error;
1211 }
1212 }
1213 if (s->record.fd != -1) {
1214 if ((s->record.buf = calloc(s->record.bufframes, s->record.frame_size)) ==
1215 NULL) {
1216 ret = CUBEB_ERROR;
1217 goto error;
1218 }
1219 }
1220
1221 *stream = s;
1222 return CUBEB_OK;
1223 error:
1224 if (s != NULL) {
1225 oss_stream_destroy(s);
1226 }
1227 return ret;
1228 }
1229
1230 static int
oss_stream_thr_create(cubeb_stream * s)1231 oss_stream_thr_create(cubeb_stream * s)
1232 {
1233 if (s->thread_created) {
1234 s->doorbell = true;
1235 pthread_cond_signal(&s->doorbell_cv);
1236 return CUBEB_OK;
1237 }
1238
1239 if (pthread_create(&s->thread, NULL, oss_io_routine, s) != 0) {
1240 LOG("Couldn't create thread");
1241 return CUBEB_ERROR;
1242 }
1243
1244 return CUBEB_OK;
1245 }
1246
1247 static int
oss_stream_start(cubeb_stream * s)1248 oss_stream_start(cubeb_stream * s)
1249 {
1250 s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
1251 pthread_mutex_lock(&s->mtx);
1252 /* Disallow starting an already started stream */
1253 assert(!s->running && s->state != CUBEB_STATE_STARTED);
1254 if (oss_stream_thr_create(s) != CUBEB_OK) {
1255 pthread_mutex_unlock(&s->mtx);
1256 s->state_cb(s, s->user_ptr, CUBEB_STATE_ERROR);
1257 return CUBEB_ERROR;
1258 }
1259 s->state = CUBEB_STATE_STARTED;
1260 s->thread_created = true;
1261 s->running = true;
1262 pthread_mutex_unlock(&s->mtx);
1263 return CUBEB_OK;
1264 }
1265
1266 static int
oss_stream_get_position(cubeb_stream * s,uint64_t * position)1267 oss_stream_get_position(cubeb_stream * s, uint64_t * position)
1268 {
1269 pthread_mutex_lock(&s->mtx);
1270 *position = s->frames_written;
1271 pthread_mutex_unlock(&s->mtx);
1272 return CUBEB_OK;
1273 }
1274
1275 static int
oss_stream_get_latency(cubeb_stream * s,uint32_t * latency)1276 oss_stream_get_latency(cubeb_stream * s, uint32_t * latency)
1277 {
1278 int delay;
1279
1280 if (ioctl(s->play.fd, SNDCTL_DSP_GETODELAY, &delay) == -1) {
1281 return CUBEB_ERROR;
1282 }
1283
1284 /* Return number of frames there */
1285 *latency = delay / s->play.frame_size;
1286 return CUBEB_OK;
1287 }
1288
1289 static int
oss_stream_set_volume(cubeb_stream * stream,float volume)1290 oss_stream_set_volume(cubeb_stream * stream, float volume)
1291 {
1292 if (volume < 0.0)
1293 volume = 0.0;
1294 else if (volume > 1.0)
1295 volume = 1.0;
1296 pthread_mutex_lock(&stream->mtx);
1297 stream->volume = volume;
1298 pthread_mutex_unlock(&stream->mtx);
1299 return CUBEB_OK;
1300 }
1301
1302 static int
oss_get_current_device(cubeb_stream * stream,cubeb_device ** const device)1303 oss_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
1304 {
1305 *device = calloc(1, sizeof(cubeb_device));
1306 if (*device == NULL) {
1307 return CUBEB_ERROR;
1308 }
1309 (*device)->input_name =
1310 stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
1311 (*device)->output_name =
1312 stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
1313 return CUBEB_OK;
1314 }
1315
1316 static int
oss_stream_device_destroy(cubeb_stream * stream,cubeb_device * device)1317 oss_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
1318 {
1319 (void)stream;
1320 free(device->input_name);
1321 free(device->output_name);
1322 free(device);
1323 return CUBEB_OK;
1324 }
1325
1326 static struct cubeb_ops const oss_ops = {
1327 .init = oss_init,
1328 .get_backend_id = oss_get_backend_id,
1329 .get_max_channel_count = oss_get_max_channel_count,
1330 .get_min_latency = oss_get_min_latency,
1331 .get_preferred_sample_rate = oss_get_preferred_sample_rate,
1332 .enumerate_devices = oss_enumerate_devices,
1333 .device_collection_destroy = oss_device_collection_destroy,
1334 .destroy = oss_destroy,
1335 .stream_init = oss_stream_init,
1336 .stream_destroy = oss_stream_destroy,
1337 .stream_start = oss_stream_start,
1338 .stream_stop = oss_stream_stop,
1339 .stream_get_position = oss_stream_get_position,
1340 .stream_get_latency = oss_stream_get_latency,
1341 .stream_get_input_latency = NULL,
1342 .stream_set_volume = oss_stream_set_volume,
1343 .stream_set_name = NULL,
1344 .stream_get_current_device = oss_get_current_device,
1345 .stream_device_destroy = oss_stream_device_destroy,
1346 .stream_register_device_changed_callback = NULL,
1347 .register_device_collection_changed = NULL};
1348