diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_sndio.c')
-rw-r--r-- | media/libcubeb/src/cubeb_sndio.c | 556 |
1 files changed, 135 insertions, 421 deletions
diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index d7fb79202e..c7ac184465 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -4,109 +4,56 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ -#include "cubeb-internal.h" -#include "cubeb/cubeb.h" -#include <assert.h> -#include <dlfcn.h> -#include <inttypes.h> #include <math.h> #include <poll.h> #include <pthread.h> #include <sndio.h> #include <stdbool.h> -#include <stdio.h> #include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" #if defined(CUBEB_SNDIO_DEBUG) #define DPR(...) fprintf(stderr, __VA_ARGS__); #else -#define DPR(...) \ - do { \ - } while (0) -#endif - -#ifdef DISABLE_LIBSNDIO_DLOPEN -#define WRAP(x) x -#else -#define WRAP(x) cubeb_##x -#define LIBSNDIO_API_VISIT(X) \ - X(sio_close) \ - X(sio_eof) \ - X(sio_getpar) \ - X(sio_initpar) \ - X(sio_nfds) \ - X(sio_onmove) \ - X(sio_open) \ - X(sio_pollfd) \ - X(sio_read) \ - X(sio_revents) \ - X(sio_setpar) \ - X(sio_start) \ - X(sio_stop) \ - X(sio_write) - -#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; -LIBSNDIO_API_VISIT(MAKE_TYPEDEF); -#undef MAKE_TYPEDEF +#define DPR(...) do {} while(0) #endif static struct cubeb_ops const sndio_ops; struct cubeb { struct cubeb_ops const * ops; - void * libsndio; }; struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * arg; /* user arg to {data,state}_cb */ - /**/ - pthread_t th; /* to run real-time audio i/o */ - pthread_mutex_t mtx; /* protects hdl and pos */ - struct sio_hdl * hdl; /* link us to sndio */ - int mode; /* bitmap of SIO_{PLAY,REC} */ - int active; /* cubec_start() called */ - int conv; /* need float->s16 conversion */ - unsigned char * rbuf; /* rec data consumed from here */ - unsigned char * pbuf; /* play data is prepared here */ - unsigned int nfr; /* number of frames in ibuf and obuf */ - unsigned int rbpf; /* rec bytes per frame */ - unsigned int pbpf; /* play bytes per frame */ - unsigned int rchan; /* number of rec channels */ - unsigned int pchan; /* number of play channels */ - unsigned int nblks; /* number of blocks in the buffer */ - uint64_t hwpos; /* frame number Joe hears right now */ - uint64_t swpos; /* number of frames produced/consumed */ - cubeb_data_callback data_cb; /* cb to preapare data */ - cubeb_state_callback state_cb; /* cb to notify about state changes */ - float volume; /* current volume */ + pthread_t th; /* to run real-time audio i/o */ + pthread_mutex_t mtx; /* protects hdl and pos */ + struct sio_hdl *hdl; /* link us to sndio */ + int active; /* cubec_start() called */ + int conv; /* need float->s16 conversion */ + unsigned char *buf; /* data is prepared here */ + unsigned int nfr; /* number of frames in buf */ + unsigned int bpf; /* bytes per frame */ + unsigned int pchan; /* number of play channels */ + uint64_t rdpos; /* frame number Joe hears right now */ + uint64_t wrpos; /* number of written frames */ + cubeb_data_callback data_cb; /* cb to preapare data */ + cubeb_state_callback state_cb; /* cb to notify about state changes */ + void *arg; /* user arg to {data,state}_cb */ }; static void -s16_setvol(void * ptr, long nsamp, float volume) +float_to_s16(void *ptr, long nsamp) { - int16_t * dst = ptr; - int32_t mult = volume * 32768; - int32_t s; - - while (nsamp-- > 0) { - s = *dst; - s = (s * mult) >> 15; - *(dst++) = s; - } -} - -static void -float_to_s16(void * ptr, long nsamp, float volume) -{ - int16_t * dst = ptr; - float * src = ptr; - float mult = volume * 32768; + int16_t *dst = ptr; + float *src = ptr; int s; while (nsamp-- > 0) { - s = lrintf(*(src++) * mult); + s = lrintf(*(src++) * 32768); if (s < -32768) s = -32768; else if (s > 32767) @@ -116,146 +63,61 @@ float_to_s16(void * ptr, long nsamp, float volume) } static void -s16_to_float(void * ptr, long nsamp) +sndio_onmove(void *arg, int delta) { - int16_t * src = ptr; - float * dst = ptr; + cubeb_stream *s = (cubeb_stream *)arg; - src += nsamp; - dst += nsamp; - while (nsamp-- > 0) - *(--dst) = (1. / 32768) * *(--src); -} - -static const char * -sndio_get_device() -{ -#ifdef __linux__ - /* - * On other platforms default to sndio devices, - * so cubebs other backends can be used instead. - */ - const char * dev = getenv("AUDIODEVICE"); - if (dev == NULL || *dev == '\0') - return "snd/0"; - return dev; -#else - return SIO_DEVANY; -#endif -} - -static void -sndio_onmove(void * arg, int delta) -{ - cubeb_stream * s = (cubeb_stream *)arg; - - s->hwpos += delta; + s->rdpos += delta * s->bpf; } static void * -sndio_mainloop(void * arg) +sndio_mainloop(void *arg) { - struct pollfd * pfds; - cubeb_stream * s = arg; - int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED; - size_t pstart = 0, pend = 0, rstart = 0, rend = 0; +#define MAXFDS 8 + struct pollfd pfds[MAXFDS]; + cubeb_stream *s = arg; + int n, nfds, revents, state = CUBEB_STATE_STARTED; + size_t start = 0, end = 0; long nfr; - nfds = WRAP(sio_nfds)(s->hdl); - pfds = calloc(nfds, sizeof(struct pollfd)); - if (pfds == NULL) - return NULL; - DPR("sndio_mainloop()\n"); s->state_cb(s, s->arg, CUBEB_STATE_STARTED); pthread_mutex_lock(&s->mtx); - if (!WRAP(sio_start)(s->hdl)) { + if (!sio_start(s->hdl)) { pthread_mutex_unlock(&s->mtx); - free(pfds); return NULL; } DPR("sndio_mainloop(), started\n"); - if (s->mode & SIO_PLAY) { - pstart = pend = s->nfr * s->pbpf; - prime = s->nblks; - if (s->mode & SIO_REC) { - memset(s->rbuf, 0, s->nfr * s->rbpf); - rstart = rend = s->nfr * s->rbpf; - } - } else { - prime = 0; - rstart = 0; - rend = s->nfr * s->rbpf; - } - + start = end = s->nfr; for (;;) { if (!s->active) { DPR("sndio_mainloop() stopped\n"); state = CUBEB_STATE_STOPPED; break; } - - /* do we have a complete block? */ - if ((!(s->mode & SIO_PLAY) || pstart == pend) && - (!(s->mode & SIO_REC) || rstart == rend)) { - - if (eof) { + if (start == end) { + if (end < s->nfr) { DPR("sndio_mainloop() drained\n"); state = CUBEB_STATE_DRAINED; break; } - - if ((s->mode & SIO_REC) && s->conv) - s16_to_float(s->rbuf, s->nfr * s->rchan); - - /* invoke call-back, it returns less that s->nfr if done */ pthread_mutex_unlock(&s->mtx); - nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr); + nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr); pthread_mutex_lock(&s->mtx); if (nfr < 0) { DPR("sndio_mainloop() cb err\n"); state = CUBEB_STATE_ERROR; break; } - s->swpos += nfr; - - /* was this last call-back invocation (aka end-of-stream) ? */ - if (nfr < s->nfr) { - - if (!(s->mode & SIO_PLAY) || nfr == 0) { - state = CUBEB_STATE_DRAINED; - break; - } - - /* need to write (aka drain) the partial play block we got */ - pend = nfr * s->pbpf; - eof = 1; - } - - if (prime > 0) - prime--; - - if (s->mode & SIO_PLAY) { - if (s->conv) - float_to_s16(s->pbuf, nfr * s->pchan, s->volume); - else - s16_setvol(s->pbuf, nfr * s->pchan, s->volume); - } - - if (s->mode & SIO_REC) - rstart = 0; - if (s->mode & SIO_PLAY) - pstart = 0; + if (s->conv) + float_to_s16(s->buf, nfr * s->pchan); + start = 0; + end = nfr * s->bpf; } - - events = 0; - if ((s->mode & SIO_REC) && rstart < rend && prime == 0) - events |= POLLIN; - if ((s->mode & SIO_PLAY) && pstart < pend) - events |= POLLOUT; - nfds = WRAP(sio_pollfd)(s->hdl, pfds, events); - + if (end == 0) + continue; + nfds = sio_pollfd(s->hdl, pfds, POLLOUT); if (nfds > 0) { pthread_mutex_unlock(&s->mtx); n = poll(pfds, nfds, -1); @@ -263,165 +125,88 @@ sndio_mainloop(void * arg) if (n < 0) continue; } - - revents = WRAP(sio_revents)(s->hdl, pfds); - - if (revents & POLLHUP) { - state = CUBEB_STATE_ERROR; + revents = sio_revents(s->hdl, pfds); + if (revents & POLLHUP) break; - } - if (revents & POLLOUT) { - n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart); - if (n == 0 && WRAP(sio_eof)(s->hdl)) { + n = sio_write(s->hdl, s->buf + start, end - start); + if (n == 0) { DPR("sndio_mainloop() werr\n"); state = CUBEB_STATE_ERROR; break; } - pstart += n; + s->wrpos += n; + start += n; } - - if (revents & POLLIN) { - n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart); - if (n == 0 && WRAP(sio_eof)(s->hdl)) { - DPR("sndio_mainloop() rerr\n"); - state = CUBEB_STATE_ERROR; - break; - } - rstart += n; - } - - /* skip rec block, if not recording (yet) */ - if (prime > 0 && (s->mode & SIO_REC)) - rstart = rend; } - WRAP(sio_stop)(s->hdl); - s->hwpos = s->swpos; + sio_stop(s->hdl); + s->rdpos = s->wrpos; pthread_mutex_unlock(&s->mtx); s->state_cb(s, s->arg, state); - free(pfds); return NULL; } /*static*/ int -sndio_init(cubeb ** context, char const * context_name) +sndio_init(cubeb **context, char const *context_name) { - void * libsndio = NULL; - struct sio_hdl * hdl; - - assert(context); - -#ifndef DISABLE_LIBSNDIO_DLOPEN - libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY); - if (!libsndio) { - libsndio = dlopen("libsndio.so", RTLD_LAZY); - if (!libsndio) { - DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name); - return CUBEB_ERROR; - } - } - -#define LOAD(x) \ - { \ - cubeb_##x = dlsym(libsndio, #x); \ - if (!cubeb_##x) { \ - DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \ - dlclose(libsndio); \ - return CUBEB_ERROR; \ - } \ - } - - LIBSNDIO_API_VISIT(LOAD); -#undef LOAD -#endif - - /* test if sndio works */ - hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1); - if (hdl == NULL) { - return CUBEB_ERROR; - } - WRAP(sio_close)(hdl); - DPR("sndio_init(%s)\n", context_name); - *context = malloc(sizeof(**context)); - if (*context == NULL) - return CUBEB_ERROR; - (*context)->libsndio = libsndio; + *context = malloc(sizeof(*context)); (*context)->ops = &sndio_ops; (void)context_name; return CUBEB_OK; } static char const * -sndio_get_backend_id(cubeb * context) +sndio_get_backend_id(cubeb *context) { return "sndio"; } static void -sndio_destroy(cubeb * context) +sndio_destroy(cubeb *context) { DPR("sndio_destroy()\n"); - if (context->libsndio) - dlclose(context->libsndio); free(context); } static int -sndio_stream_init(cubeb * context, cubeb_stream ** stream, - char const * stream_name, cubeb_devid input_device, +sndio_stream_init(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, cubeb_stream_params * input_stream_params, cubeb_devid output_device, cubeb_stream_params * output_stream_params, unsigned int latency_frames, cubeb_data_callback data_callback, - cubeb_state_callback state_callback, void * user_ptr) + cubeb_state_callback state_callback, + void *user_ptr) { - cubeb_stream * s; + cubeb_stream *s; struct sio_par wpar, rpar; - cubeb_sample_format format; - int rate; - size_t bps; - DPR("sndio_stream_init(%s)\n", stream_name); + size_t size; + + assert(!input_stream_params && "not supported."); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } s = malloc(sizeof(cubeb_stream)); if (s == NULL) return CUBEB_ERROR; - memset(s, 0, sizeof(cubeb_stream)); - s->mode = 0; - if (input_stream_params) { - if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - DPR("sndio_stream_init(), loopback not supported\n"); - goto err; - } - s->mode |= SIO_REC; - format = input_stream_params->format; - rate = input_stream_params->rate; - } - if (output_stream_params) { - if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - DPR("sndio_stream_init(), loopback not supported\n"); - goto err; - } - s->mode |= SIO_PLAY; - format = output_stream_params->format; - rate = output_stream_params->rate; - } - if (s->mode == 0) { - DPR("sndio_stream_init(), neither playing nor recording\n"); - goto err; - } s->context = context; - s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1); + s->hdl = sio_open(NULL, SIO_PLAY, 1); if (s->hdl == NULL) { + free(s); DPR("sndio_stream_init(), sio_open() failed\n"); - goto err; + return CUBEB_ERROR; } - WRAP(sio_initpar)(&wpar); + sio_initpar(&wpar); wpar.sig = 1; wpar.bits = 16; - switch (format) { + switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: wpar.le = 1; break; @@ -433,70 +218,53 @@ sndio_stream_init(cubeb * context, cubeb_stream ** stream, break; default: DPR("sndio_stream_init() unsupported format\n"); - goto err; + return CUBEB_ERROR_INVALID_FORMAT; } - wpar.rate = rate; - if (s->mode & SIO_REC) - wpar.rchan = input_stream_params->channels; - if (s->mode & SIO_PLAY) - wpar.pchan = output_stream_params->channels; + wpar.rate = output_stream_params->rate; + wpar.pchan = output_stream_params->channels; wpar.appbufsz = latency_frames; - if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) { + if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) { + sio_close(s->hdl); + free(s); DPR("sndio_stream_init(), sio_setpar() failed\n"); - goto err; + return CUBEB_ERROR; } - if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig || - rpar.rate != wpar.rate || - ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) || - ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) { + if (rpar.bits != wpar.bits || rpar.le != wpar.le || + rpar.sig != wpar.sig || rpar.rate != wpar.rate || + rpar.pchan != wpar.pchan) { + sio_close(s->hdl); + free(s); DPR("sndio_stream_init() unsupported params\n"); - goto err; + return CUBEB_ERROR_INVALID_FORMAT; } - WRAP(sio_onmove)(s->hdl, sndio_onmove, s); + sio_onmove(s->hdl, sndio_onmove, s); s->active = 0; s->nfr = rpar.round; - s->rbpf = rpar.bps * rpar.rchan; - s->pbpf = rpar.bps * rpar.pchan; - s->rchan = rpar.rchan; + s->bpf = rpar.bps * rpar.pchan; s->pchan = rpar.pchan; - s->nblks = rpar.bufsz / rpar.round; s->data_cb = data_callback; s->state_cb = state_callback; s->arg = user_ptr; s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; - s->hwpos = s->swpos = 0; - if (format == CUBEB_SAMPLE_FLOAT32LE) { + s->rdpos = s->wrpos = 0; + if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { s->conv = 1; - bps = sizeof(float); + size = rpar.round * rpar.pchan * sizeof(float); } else { s->conv = 0; - bps = rpar.bps; + size = rpar.round * rpar.pchan * rpar.bps; } - if (s->mode & SIO_PLAY) { - s->pbuf = malloc(bps * rpar.pchan * rpar.round); - if (s->pbuf == NULL) - goto err; - } - if (s->mode & SIO_REC) { - s->rbuf = malloc(bps * rpar.rchan * rpar.round); - if (s->rbuf == NULL) - goto err; + s->buf = malloc(size); + if (s->buf == NULL) { + sio_close(s->hdl); + free(s); + return CUBEB_ERROR; } - s->volume = 1.; *stream = s; DPR("sndio_stream_init() end, ok\n"); (void)context; (void)stream_name; return CUBEB_OK; -err: - if (s->hdl) - WRAP(sio_close)(s->hdl); - if (s->pbuf) - free(s->pbuf); - if (s->rbuf) - free(s->pbuf); - free(s); - return CUBEB_ERROR; } static int @@ -512,41 +280,31 @@ sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) static int sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { - /* - * We've no device-independent prefered rate; any rate will work if - * sndiod is running. If it isn't, 48kHz is what is most likely to - * work as most (but not all) devices support it. - */ - *rate = 48000; + // XXX Not yet implemented. + *rate = 44100; + return CUBEB_OK; } static int -sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, - uint32_t * latency_frames) +sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) { - /* - * We've no device-independent minimum latency. - */ + // XXX Not yet implemented. *latency_frames = 2048; return CUBEB_OK; } static void -sndio_stream_destroy(cubeb_stream * s) +sndio_stream_destroy(cubeb_stream *s) { DPR("sndio_stream_destroy()\n"); - WRAP(sio_close)(s->hdl); - if (s->mode & SIO_PLAY) - free(s->pbuf); - if (s->mode & SIO_REC) - free(s->rbuf); + sio_close(s->hdl); free(s); } static int -sndio_stream_start(cubeb_stream * s) +sndio_stream_start(cubeb_stream *s) { int err; @@ -561,9 +319,9 @@ sndio_stream_start(cubeb_stream * s) } static int -sndio_stream_stop(cubeb_stream * s) +sndio_stream_stop(cubeb_stream *s) { - void * dummy; + void *dummy; DPR("sndio_stream_stop()\n"); if (s->active) { @@ -574,25 +332,21 @@ sndio_stream_stop(cubeb_stream * s) } static int -sndio_stream_get_position(cubeb_stream * s, uint64_t * p) +sndio_stream_get_position(cubeb_stream *s, uint64_t *p) { pthread_mutex_lock(&s->mtx); - DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos); - *p = s->hwpos; + DPR("sndio_stream_get_position() %lld\n", s->rdpos); + *p = s->rdpos / s->bpf; pthread_mutex_unlock(&s->mtx); return CUBEB_OK; } static int -sndio_stream_set_volume(cubeb_stream * s, float volume) +sndio_stream_set_volume(cubeb_stream *s, float volume) { DPR("sndio_stream_set_volume(%f)\n", volume); pthread_mutex_lock(&s->mtx); - if (volume < 0.) - volume = 0.; - else if (volume > 1.0) - volume = 1.; - s->volume = volume; + sio_setvol(s->hdl, SIO_MAXVOL * volume); pthread_mutex_unlock(&s->mtx); return CUBEB_OK; } @@ -602,68 +356,28 @@ sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) { // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open // in the "Measuring the latency and buffers usage" paragraph. - *latency = stm->swpos - stm->hwpos; - return CUBEB_OK; -} - -static int -sndio_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection) -{ - static char dev[] = SIO_DEVANY; - cubeb_device_info * device; - - device = malloc(sizeof(cubeb_device_info)); - if (device == NULL) - return CUBEB_ERROR; - - device->devid = dev; /* passed to stream_init() */ - device->device_id = dev; /* printable in UI */ - device->friendly_name = dev; /* same, but friendly */ - device->group_id = dev; /* actual device if full-duplex */ - device->vendor_name = NULL; /* may be NULL */ - device->type = type; /* Input/Output */ - device->state = CUBEB_DEVICE_STATE_ENABLED; - device->preferred = CUBEB_DEVICE_PREF_ALL; - device->format = CUBEB_DEVICE_FMT_S16NE; - device->default_format = CUBEB_DEVICE_FMT_S16NE; - device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8; - device->default_rate = 48000; - device->min_rate = 4000; - device->max_rate = 192000; - device->latency_lo = 480; - device->latency_hi = 9600; - collection->device = device; - collection->count = 1; - return CUBEB_OK; -} - -static int -sndio_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection) -{ - free(collection->device); + *latency = (stm->wrpos - stm->rdpos) / stm->bpf; return CUBEB_OK; } static struct cubeb_ops const sndio_ops = { - .init = sndio_init, - .get_backend_id = sndio_get_backend_id, - .get_max_channel_count = sndio_get_max_channel_count, - .get_min_latency = sndio_get_min_latency, - .get_preferred_sample_rate = sndio_get_preferred_sample_rate, - .enumerate_devices = sndio_enumerate_devices, - .device_collection_destroy = sndio_device_collection_destroy, - .destroy = sndio_destroy, - .stream_init = sndio_stream_init, - .stream_destroy = sndio_stream_destroy, - .stream_start = sndio_stream_start, - .stream_stop = sndio_stream_stop, - .stream_get_position = sndio_stream_get_position, - .stream_get_latency = sndio_stream_get_latency, - .stream_set_volume = sndio_stream_set_volume, - .stream_set_name = NULL, - .stream_get_current_device = NULL, - .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL, - .register_device_collection_changed = NULL}; + .init = sndio_init, + .get_backend_id = sndio_get_backend_id, + .get_max_channel_count = sndio_get_max_channel_count, + .get_min_latency = sndio_get_min_latency, + .get_preferred_sample_rate = sndio_get_preferred_sample_rate, + .enumerate_devices = NULL, + .destroy = sndio_destroy, + .stream_init = sndio_stream_init, + .stream_destroy = sndio_stream_destroy, + .stream_start = sndio_stream_start, + .stream_stop = sndio_stream_stop, + .stream_get_position = sndio_stream_get_position, + .stream_get_latency = sndio_stream_get_latency, + .stream_set_volume = sndio_stream_set_volume, + .stream_set_panning = NULL, + .stream_get_current_device = NULL, + .stream_device_destroy = NULL, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL +}; |