duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

cubeb_oss.c (34400B)


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