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