tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

cubeb_sndio.c (18820B)


      1 /*
      2 * Copyright (c) 2011 Alexandre Ratchov <alex@caoua.org>
      3 *
      4 * This program is made available under an ISC-style license.  See the
      5 * accompanying file LICENSE for details.
      6 */
      7 #include "cubeb-internal.h"
      8 #include "cubeb/cubeb.h"
      9 #include "cubeb_tracing.h"
     10 #include <assert.h>
     11 #include <dlfcn.h>
     12 #include <inttypes.h>
     13 #include <math.h>
     14 #include <poll.h>
     15 #include <pthread.h>
     16 #include <sndio.h>
     17 #include <stdbool.h>
     18 #include <stdio.h>
     19 #include <stdlib.h>
     20 
     21 #if defined(CUBEB_SNDIO_DEBUG)
     22 #define DPR(...) fprintf(stderr, __VA_ARGS__);
     23 #else
     24 #define DPR(...)                                                               \
     25  do {                                                                         \
     26  } while (0)
     27 #endif
     28 
     29 #ifdef DISABLE_LIBSNDIO_DLOPEN
     30 #define WRAP(x) x
     31 #else
     32 #define WRAP(x) (*cubeb_##x)
     33 #define LIBSNDIO_API_VISIT(X)                                                  \
     34  X(sio_close)                                                                 \
     35  X(sio_eof)                                                                   \
     36  X(sio_getpar)                                                                \
     37  X(sio_initpar)                                                               \
     38  X(sio_nfds)                                                                  \
     39  X(sio_onmove)                                                                \
     40  X(sio_open)                                                                  \
     41  X(sio_pollfd)                                                                \
     42  X(sio_read)                                                                  \
     43  X(sio_revents)                                                               \
     44  X(sio_setpar)                                                                \
     45  X(sio_start)                                                                 \
     46  X(sio_stop)                                                                  \
     47  X(sio_write)
     48 
     49 #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
     50 LIBSNDIO_API_VISIT(MAKE_TYPEDEF);
     51 #undef MAKE_TYPEDEF
     52 #endif
     53 
     54 static struct cubeb_ops const sndio_ops;
     55 
     56 struct cubeb {
     57  struct cubeb_ops const * ops;
     58  void * libsndio;
     59 };
     60 
     61 struct cubeb_stream {
     62  /* Note: Must match cubeb_stream layout in cubeb.c. */
     63  cubeb * context;
     64  void * arg; /* user arg to {data,state}_cb */
     65  /**/
     66  pthread_t th;                  /* to run real-time audio i/o */
     67  pthread_mutex_t mtx;           /* protects hdl and pos */
     68  struct sio_hdl * hdl;          /* link us to sndio */
     69  int mode;                      /* bitmap of SIO_{PLAY,REC} */
     70  int active;                    /* cubec_start() called */
     71  int conv;                      /* need float->s24 conversion */
     72  unsigned char * rbuf;          /* rec data consumed from here */
     73  unsigned char * pbuf;          /* play data is prepared here */
     74  unsigned int nfr;              /* number of frames in ibuf and obuf */
     75  unsigned int rbpf;             /* rec bytes per frame */
     76  unsigned int pbpf;             /* play bytes per frame */
     77  unsigned int rchan;            /* number of rec channels */
     78  unsigned int pchan;            /* number of play channels */
     79  unsigned int nblks;            /* number of blocks in the buffer */
     80  uint64_t hwpos;                /* frame number Joe hears right now */
     81  uint64_t swpos;                /* number of frames produced/consumed */
     82  cubeb_data_callback data_cb;   /* cb to preapare data */
     83  cubeb_state_callback state_cb; /* cb to notify about state changes */
     84  float volume;                  /* current volume */
     85 };
     86 
     87 static void
     88 s16_setvol(void * ptr, long nsamp, float volume)
     89 {
     90  int16_t * dst = ptr;
     91  int32_t mult = volume * 32768;
     92  int32_t s;
     93 
     94  while (nsamp-- > 0) {
     95    s = *dst;
     96    s = (s * mult) >> 15;
     97    *(dst++) = s;
     98  }
     99 }
    100 
    101 static void
    102 float_to_s24(void * ptr, long nsamp, float volume)
    103 {
    104  int32_t * dst = ptr;
    105  float * src = ptr;
    106  float mult = volume * 8388608;
    107  int s;
    108 
    109  while (nsamp-- > 0) {
    110    s = lrintf(*(src++) * mult);
    111    if (s < -8388608)
    112      s = -8388608;
    113    else if (s > 8388607)
    114      s = 8388607;
    115    *(dst++) = s;
    116  }
    117 }
    118 
    119 static void
    120 s24_to_float(void * ptr, long nsamp)
    121 {
    122  int32_t * src = ptr;
    123  float * dst = ptr;
    124 
    125  src += nsamp;
    126  dst += nsamp;
    127  while (nsamp-- > 0)
    128    *(--dst) = (1. / 8388608) * *(--src);
    129 }
    130 
    131 static const char *
    132 sndio_get_device()
    133 {
    134 #ifdef __linux__
    135  /*
    136   * On other platforms default to sndio devices,
    137   * so cubebs other backends can be used instead.
    138   */
    139  const char * dev = getenv("AUDIODEVICE");
    140  if (dev == NULL || *dev == '\0')
    141    return "snd/0";
    142  return dev;
    143 #else
    144  return SIO_DEVANY;
    145 #endif
    146 }
    147 
    148 static void
    149 sndio_onmove(void * arg, int delta)
    150 {
    151  cubeb_stream * s = (cubeb_stream *)arg;
    152 
    153  s->hwpos += delta;
    154 }
    155 
    156 static void *
    157 sndio_mainloop(void * arg)
    158 {
    159  struct pollfd * pfds;
    160  cubeb_stream * s = arg;
    161  int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED;
    162  size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
    163  long nfr;
    164 
    165  CUBEB_REGISTER_THREAD("cubeb rendering thread");
    166 
    167  nfds = WRAP(sio_nfds)(s->hdl);
    168  pfds = calloc(nfds, sizeof(struct pollfd));
    169  if (pfds == NULL) {
    170    CUBEB_UNREGISTER_THREAD();
    171    return NULL;
    172  }
    173 
    174  DPR("sndio_mainloop()\n");
    175  s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
    176  pthread_mutex_lock(&s->mtx);
    177  if (!WRAP(sio_start)(s->hdl)) {
    178    pthread_mutex_unlock(&s->mtx);
    179    free(pfds);
    180    CUBEB_UNREGISTER_THREAD();
    181    return NULL;
    182  }
    183  DPR("sndio_mainloop(), started\n");
    184 
    185  if (s->mode & SIO_PLAY) {
    186    pstart = pend = s->nfr * s->pbpf;
    187    prime = s->nblks;
    188    if (s->mode & SIO_REC) {
    189      memset(s->rbuf, 0, s->nfr * s->rbpf);
    190      rstart = rend = s->nfr * s->rbpf;
    191    }
    192  } else {
    193    prime = 0;
    194    rstart = 0;
    195    rend = s->nfr * s->rbpf;
    196  }
    197 
    198  for (;;) {
    199    if (!s->active) {
    200      DPR("sndio_mainloop() stopped\n");
    201      state = CUBEB_STATE_STOPPED;
    202      break;
    203    }
    204 
    205    /* do we have a complete block? */
    206    if ((!(s->mode & SIO_PLAY) || pstart == pend) &&
    207        (!(s->mode & SIO_REC) || rstart == rend)) {
    208 
    209      if (eof) {
    210        DPR("sndio_mainloop() drained\n");
    211        state = CUBEB_STATE_DRAINED;
    212        break;
    213      }
    214 
    215      if ((s->mode & SIO_REC) && s->conv)
    216        s24_to_float(s->rbuf, s->nfr * s->rchan);
    217 
    218      /* invoke call-back, it returns less that s->nfr if done */
    219      pthread_mutex_unlock(&s->mtx);
    220      nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr);
    221      pthread_mutex_lock(&s->mtx);
    222      if (nfr < 0) {
    223        DPR("sndio_mainloop() cb err\n");
    224        state = CUBEB_STATE_ERROR;
    225        break;
    226      }
    227      s->swpos += nfr;
    228 
    229      /* was this last call-back invocation (aka end-of-stream) ? */
    230      if (nfr < s->nfr) {
    231 
    232        if (!(s->mode & SIO_PLAY) || nfr == 0) {
    233          state = CUBEB_STATE_DRAINED;
    234          break;
    235        }
    236 
    237        /* need to write (aka drain) the partial play block we got */
    238        pend = nfr * s->pbpf;
    239        eof = 1;
    240      }
    241 
    242      if (prime > 0)
    243        prime--;
    244 
    245      if (s->mode & SIO_PLAY) {
    246        if (s->conv)
    247          float_to_s24(s->pbuf, nfr * s->pchan, s->volume);
    248        else
    249          s16_setvol(s->pbuf, nfr * s->pchan, s->volume);
    250      }
    251 
    252      if (s->mode & SIO_REC)
    253        rstart = 0;
    254      if (s->mode & SIO_PLAY)
    255        pstart = 0;
    256    }
    257 
    258    events = 0;
    259    if ((s->mode & SIO_REC) && rstart < rend && prime == 0)
    260      events |= POLLIN;
    261    if ((s->mode & SIO_PLAY) && pstart < pend)
    262      events |= POLLOUT;
    263    nfds = WRAP(sio_pollfd)(s->hdl, pfds, events);
    264 
    265    if (nfds > 0) {
    266      pthread_mutex_unlock(&s->mtx);
    267      n = poll(pfds, nfds, -1);
    268      pthread_mutex_lock(&s->mtx);
    269      if (n < 0)
    270        continue;
    271    }
    272 
    273    revents = WRAP(sio_revents)(s->hdl, pfds);
    274 
    275    if (revents & POLLHUP) {
    276      state = CUBEB_STATE_ERROR;
    277      break;
    278    }
    279 
    280    if (revents & POLLOUT) {
    281      n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart);
    282      if (n == 0 && WRAP(sio_eof)(s->hdl)) {
    283        DPR("sndio_mainloop() werr\n");
    284        state = CUBEB_STATE_ERROR;
    285        break;
    286      }
    287      pstart += n;
    288    }
    289 
    290    if (revents & POLLIN) {
    291      n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart);
    292      if (n == 0 && WRAP(sio_eof)(s->hdl)) {
    293        DPR("sndio_mainloop() rerr\n");
    294        state = CUBEB_STATE_ERROR;
    295        break;
    296      }
    297      rstart += n;
    298    }
    299 
    300    /* skip rec block, if not recording (yet) */
    301    if (prime > 0 && (s->mode & SIO_REC))
    302      rstart = rend;
    303  }
    304  WRAP(sio_stop)(s->hdl);
    305  s->hwpos = s->swpos;
    306  pthread_mutex_unlock(&s->mtx);
    307  s->state_cb(s, s->arg, state);
    308  free(pfds);
    309  CUBEB_UNREGISTER_THREAD();
    310  return NULL;
    311 }
    312 
    313 /*static*/ int
    314 sndio_init(cubeb ** context, char const * context_name)
    315 {
    316  void * libsndio = NULL;
    317  struct sio_hdl * hdl;
    318 
    319  assert(context);
    320 
    321 #ifndef DISABLE_LIBSNDIO_DLOPEN
    322  libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY);
    323  if (!libsndio) {
    324    libsndio = dlopen("libsndio.so", RTLD_LAZY);
    325    if (!libsndio) {
    326      DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name);
    327      return CUBEB_ERROR;
    328    }
    329  }
    330 
    331 #define LOAD(x)                                                                \
    332  {                                                                            \
    333    cubeb_##x = dlsym(libsndio, #x);                                           \
    334    if (!cubeb_##x) {                                                          \
    335      DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x);              \
    336      dlclose(libsndio);                                                       \
    337      return CUBEB_ERROR;                                                      \
    338    }                                                                          \
    339  }
    340 
    341  LIBSNDIO_API_VISIT(LOAD);
    342 #undef LOAD
    343 #endif
    344 
    345  /* test if sndio works */
    346  hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1);
    347  if (hdl == NULL) {
    348    return CUBEB_ERROR;
    349  }
    350  WRAP(sio_close)(hdl);
    351 
    352  DPR("sndio_init(%s)\n", context_name);
    353  *context = malloc(sizeof(**context));
    354  if (*context == NULL)
    355    return CUBEB_ERROR;
    356  (*context)->libsndio = libsndio;
    357  (*context)->ops = &sndio_ops;
    358  (void)context_name;
    359  return CUBEB_OK;
    360 }
    361 
    362 static char const *
    363 sndio_get_backend_id(cubeb * context)
    364 {
    365  return "sndio";
    366 }
    367 
    368 static void
    369 sndio_destroy(cubeb * context)
    370 {
    371  DPR("sndio_destroy()\n");
    372 #ifndef DISABLE_LIBSNDIO_DLOPEN
    373  if (context->libsndio)
    374    dlclose(context->libsndio);
    375 #endif
    376  free(context);
    377 }
    378 
    379 static int
    380 sndio_stream_init(cubeb * context, cubeb_stream ** stream,
    381                  char const * stream_name, cubeb_devid input_device,
    382                  cubeb_stream_params * input_stream_params,
    383                  cubeb_devid output_device,
    384                  cubeb_stream_params * output_stream_params,
    385                  unsigned int latency_frames,
    386                  cubeb_data_callback data_callback,
    387                  cubeb_state_callback state_callback, void * user_ptr)
    388 {
    389  cubeb_stream * s;
    390  struct sio_par wpar, rpar;
    391  cubeb_sample_format format;
    392  int rate;
    393  size_t bps;
    394 
    395  DPR("sndio_stream_init(%s)\n", stream_name);
    396 
    397  s = malloc(sizeof(cubeb_stream));
    398  if (s == NULL)
    399    return CUBEB_ERROR;
    400  memset(s, 0, sizeof(cubeb_stream));
    401  s->mode = 0;
    402  if (input_stream_params) {
    403    if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
    404      DPR("sndio_stream_init(), loopback not supported\n");
    405      goto err;
    406    }
    407    s->mode |= SIO_REC;
    408    format = input_stream_params->format;
    409    rate = input_stream_params->rate;
    410  }
    411  if (output_stream_params) {
    412    if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
    413      DPR("sndio_stream_init(), loopback not supported\n");
    414      goto err;
    415    }
    416    s->mode |= SIO_PLAY;
    417    format = output_stream_params->format;
    418    rate = output_stream_params->rate;
    419  }
    420  if (s->mode == 0) {
    421    DPR("sndio_stream_init(), neither playing nor recording\n");
    422    goto err;
    423  }
    424  s->context = context;
    425  s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1);
    426  if (s->hdl == NULL) {
    427    DPR("sndio_stream_init(), sio_open() failed\n");
    428    goto err;
    429  }
    430  WRAP(sio_initpar)(&wpar);
    431  wpar.sig = 1;
    432  switch (format) {
    433  case CUBEB_SAMPLE_S16LE:
    434    wpar.le = 1;
    435    wpar.bits = 16;
    436    break;
    437  case CUBEB_SAMPLE_S16BE:
    438    wpar.le = 0;
    439    wpar.bits = 16;
    440    break;
    441  case CUBEB_SAMPLE_FLOAT32NE:
    442    wpar.le = SIO_LE_NATIVE;
    443    wpar.bits = 24;
    444    wpar.msb = 0;
    445    break;
    446  default:
    447    DPR("sndio_stream_init() unsupported format\n");
    448    goto err;
    449  }
    450  wpar.bps = SIO_BPS(wpar.bits);
    451  wpar.rate = rate;
    452  if (s->mode & SIO_REC)
    453    wpar.rchan = input_stream_params->channels;
    454  if (s->mode & SIO_PLAY)
    455    wpar.pchan = output_stream_params->channels;
    456  wpar.appbufsz = latency_frames;
    457  if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) {
    458    DPR("sndio_stream_init(), sio_setpar() failed\n");
    459    goto err;
    460  }
    461  if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig ||
    462      rpar.bps != wpar.bps ||
    463      (wpar.bits < 8 * wpar.bps && rpar.msb != wpar.msb) ||
    464      rpar.rate != wpar.rate ||
    465      ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
    466      ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {
    467    DPR("sndio_stream_init() unsupported params\n");
    468    goto err;
    469  }
    470  WRAP(sio_onmove)(s->hdl, sndio_onmove, s);
    471  s->active = 0;
    472  s->nfr = rpar.round;
    473  s->rbpf = rpar.bps * rpar.rchan;
    474  s->pbpf = rpar.bps * rpar.pchan;
    475  s->rchan = rpar.rchan;
    476  s->pchan = rpar.pchan;
    477  s->nblks = rpar.bufsz / rpar.round;
    478  s->data_cb = data_callback;
    479  s->state_cb = state_callback;
    480  s->arg = user_ptr;
    481  s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
    482  s->hwpos = s->swpos = 0;
    483  if (format == CUBEB_SAMPLE_FLOAT32LE) {
    484    s->conv = 1;
    485    bps = sizeof(float);
    486  } else {
    487    s->conv = 0;
    488    bps = rpar.bps;
    489  }
    490  if (s->mode & SIO_PLAY) {
    491    s->pbuf = malloc(bps * rpar.pchan * rpar.round);
    492    if (s->pbuf == NULL)
    493      goto err;
    494  }
    495  if (s->mode & SIO_REC) {
    496    s->rbuf = malloc(bps * rpar.rchan * rpar.round);
    497    if (s->rbuf == NULL)
    498      goto err;
    499  }
    500  s->volume = 1.;
    501  *stream = s;
    502  DPR("sndio_stream_init() end, ok\n");
    503  (void)context;
    504  (void)stream_name;
    505  return CUBEB_OK;
    506 err:
    507  if (s->hdl)
    508    WRAP(sio_close)(s->hdl);
    509  if (s->pbuf)
    510    free(s->pbuf);
    511  if (s->rbuf)
    512    free(s->pbuf);
    513  free(s);
    514  return CUBEB_ERROR;
    515 }
    516 
    517 static int
    518 sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
    519 {
    520  assert(ctx && max_channels);
    521 
    522  *max_channels = 8;
    523 
    524  return CUBEB_OK;
    525 }
    526 
    527 static int
    528 sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
    529 {
    530  /*
    531   * We've no device-independent prefered rate; any rate will work if
    532   * sndiod is running. If it isn't, 48kHz is what is most likely to
    533   * work as most (but not all) devices support it.
    534   */
    535  *rate = 48000;
    536  return CUBEB_OK;
    537 }
    538 
    539 static int
    540 sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
    541                      uint32_t * latency_frames)
    542 {
    543  /*
    544   * We've no device-independent minimum latency.
    545   */
    546  *latency_frames = 2048;
    547 
    548  return CUBEB_OK;
    549 }
    550 
    551 static void
    552 sndio_stream_destroy(cubeb_stream * s)
    553 {
    554  DPR("sndio_stream_destroy()\n");
    555  WRAP(sio_close)(s->hdl);
    556  if (s->mode & SIO_PLAY)
    557    free(s->pbuf);
    558  if (s->mode & SIO_REC)
    559    free(s->rbuf);
    560  free(s);
    561 }
    562 
    563 static int
    564 sndio_stream_start(cubeb_stream * s)
    565 {
    566  int err;
    567 
    568  DPR("sndio_stream_start()\n");
    569  s->active = 1;
    570  err = pthread_create(&s->th, NULL, sndio_mainloop, s);
    571  if (err) {
    572    s->active = 0;
    573    return CUBEB_ERROR;
    574  }
    575  return CUBEB_OK;
    576 }
    577 
    578 static int
    579 sndio_stream_stop(cubeb_stream * s)
    580 {
    581  void * dummy;
    582 
    583  DPR("sndio_stream_stop()\n");
    584  if (s->active) {
    585    s->active = 0;
    586    pthread_join(s->th, &dummy);
    587  }
    588  return CUBEB_OK;
    589 }
    590 
    591 static int
    592 sndio_stream_get_position(cubeb_stream * s, uint64_t * p)
    593 {
    594  pthread_mutex_lock(&s->mtx);
    595  DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos);
    596  *p = s->hwpos;
    597  pthread_mutex_unlock(&s->mtx);
    598  return CUBEB_OK;
    599 }
    600 
    601 static int
    602 sndio_stream_set_volume(cubeb_stream * s, float volume)
    603 {
    604  DPR("sndio_stream_set_volume(%f)\n", volume);
    605  pthread_mutex_lock(&s->mtx);
    606  if (volume < 0.)
    607    volume = 0.;
    608  else if (volume > 1.0)
    609    volume = 1.;
    610  s->volume = volume;
    611  pthread_mutex_unlock(&s->mtx);
    612  return CUBEB_OK;
    613 }
    614 
    615 int
    616 sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
    617 {
    618  // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open
    619  // in the "Measuring the latency and buffers usage" paragraph.
    620  *latency = stm->swpos - stm->hwpos;
    621  return CUBEB_OK;
    622 }
    623 
    624 static int
    625 sndio_enumerate_devices(cubeb * context, cubeb_device_type type,
    626                        cubeb_device_collection * collection)
    627 {
    628  static char dev[] = SIO_DEVANY;
    629  cubeb_device_info * device;
    630 
    631  device = malloc(sizeof(cubeb_device_info));
    632  if (device == NULL)
    633    return CUBEB_ERROR;
    634 
    635  device->devid = dev;         /* passed to stream_init() */
    636  device->device_id = dev;     /* printable in UI */
    637  device->friendly_name = dev; /* same, but friendly */
    638  device->group_id = dev;      /* actual device if full-duplex */
    639  device->vendor_name = NULL;  /* may be NULL */
    640  device->type = type;         /* Input/Output */
    641  device->state = CUBEB_DEVICE_STATE_ENABLED;
    642  device->preferred = CUBEB_DEVICE_PREF_ALL;
    643  device->format = CUBEB_DEVICE_FMT_S16NE;
    644  device->default_format = CUBEB_DEVICE_FMT_S16NE;
    645  device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8;
    646  device->default_rate = 48000;
    647  device->min_rate = 4000;
    648  device->max_rate = 192000;
    649  device->latency_lo = 480;
    650  device->latency_hi = 9600;
    651  collection->device = device;
    652  collection->count = 1;
    653  return CUBEB_OK;
    654 }
    655 
    656 static int
    657 sndio_device_collection_destroy(cubeb * context,
    658                                cubeb_device_collection * collection)
    659 {
    660  free(collection->device);
    661  return CUBEB_OK;
    662 }
    663 
    664 static struct cubeb_ops const sndio_ops = {
    665    .init = sndio_init,
    666    .get_backend_id = sndio_get_backend_id,
    667    .get_max_channel_count = sndio_get_max_channel_count,
    668    .get_min_latency = sndio_get_min_latency,
    669    .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
    670    .get_supported_input_processing_params = NULL,
    671    .enumerate_devices = sndio_enumerate_devices,
    672    .device_collection_destroy = sndio_device_collection_destroy,
    673    .destroy = sndio_destroy,
    674    .stream_init = sndio_stream_init,
    675    .stream_destroy = sndio_stream_destroy,
    676    .stream_start = sndio_stream_start,
    677    .stream_stop = sndio_stream_stop,
    678    .stream_get_position = sndio_stream_get_position,
    679    .stream_get_latency = sndio_stream_get_latency,
    680    .stream_set_volume = sndio_stream_set_volume,
    681    .stream_set_name = NULL,
    682    .stream_get_current_device = NULL,
    683    .stream_set_input_mute = NULL,
    684    .stream_set_input_processing_params = NULL,
    685    .stream_device_destroy = NULL,
    686    .stream_register_device_changed_callback = NULL,
    687    .register_device_collection_changed = NULL};