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_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};