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