diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_sndio.c')
-rw-r--r-- | media/libcubeb/src/cubeb_sndio.c | 416 |
1 files changed, 333 insertions, 83 deletions
diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index 793789765..4a05bd845 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -4,6 +4,7 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ +#include <inttypes.h> #include <math.h> #include <poll.h> #include <pthread.h> @@ -11,6 +12,7 @@ #include <stdbool.h> #include <stdlib.h> #include <stdio.h> +#include <dlfcn.h> #include <assert.h> #include "cubeb/cubeb.h" #include "cubeb-internal.h" @@ -21,39 +23,87 @@ #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_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 +#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; - 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 */ + 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 */ - void *arg; /* user arg to {data,state}_cb */ + float volume; /* current volume */ }; static void -float_to_s16(void *ptr, long nsamp) +s16_setvol(void *ptr, long nsamp, float volume) +{ + 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; int s; while (nsamp-- > 0) { - s = lrintf(*(src++) * 32768); + s = lrintf(*(src++) * mult); if (s < -32768) s = -32768; else if (s > 32767) @@ -63,11 +113,23 @@ float_to_s16(void *ptr, long nsamp) } static void +s16_to_float(void *ptr, long nsamp) +{ + int16_t *src = ptr; + float *dst = ptr; + + src += nsamp; + dst += nsamp; + while (nsamp-- > 0) + *(--dst) = (1. / 32768) * *(--src); +} + +static void sndio_onmove(void *arg, int delta) { cubeb_stream *s = (cubeb_stream *)arg; - s->rdpos += delta * s->bpf; + s->hwpos += delta; } static void * @@ -76,48 +138,99 @@ sndio_mainloop(void *arg) #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; + int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED; + size_t pstart = 0, pend = 0, rstart = 0, rend = 0; long nfr; DPR("sndio_mainloop()\n"); s->state_cb(s, s->arg, CUBEB_STATE_STARTED); pthread_mutex_lock(&s->mtx); - if (!sio_start(s->hdl)) { + if (!WRAP(sio_start)(s->hdl)) { pthread_mutex_unlock(&s->mtx); return NULL; } DPR("sndio_mainloop(), started\n"); - start = end = s->nfr; + 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; + } + for (;;) { if (!s->active) { DPR("sndio_mainloop() stopped\n"); state = CUBEB_STATE_STOPPED; break; } - if (start == end) { - if (end < s->nfr) { + + /* do we have a complete block? */ + if ((!(s->mode & SIO_PLAY) || pstart == pend) && + (!(s->mode & SIO_REC) || rstart == rend)) { + + if (eof) { 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, NULL, s->buf, s->nfr); + nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr); pthread_mutex_lock(&s->mtx); if (nfr < 0) { DPR("sndio_mainloop() cb err\n"); state = CUBEB_STATE_ERROR; break; } - if (s->conv) - float_to_s16(s->buf, nfr * s->pchan); - start = 0; - end = nfr * s->bpf; + 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 (end == 0) - continue; - nfds = sio_pollfd(s->hdl, pfds, POLLOUT); + + 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 (nfds > 0) { pthread_mutex_unlock(&s->mtx); n = poll(pfds, nfds, -1); @@ -125,22 +238,40 @@ sndio_mainloop(void *arg) if (n < 0) continue; } - revents = sio_revents(s->hdl, pfds); - if (revents & POLLHUP) + + revents = WRAP(sio_revents)(s->hdl, pfds); + + if (revents & POLLHUP) { + state = CUBEB_STATE_ERROR; break; + } + if (revents & POLLOUT) { - n = sio_write(s->hdl, s->buf + start, end - start); - if (n == 0) { + n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart); + if (n == 0 && WRAP(sio_eof)(s->hdl)) { DPR("sndio_mainloop() werr\n"); state = CUBEB_STATE_ERROR; break; } - s->wrpos += n; - start += n; + pstart += 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; } - sio_stop(s->hdl); - s->rdpos = s->wrpos; + WRAP(sio_stop)(s->hdl); + s->hwpos = s->swpos; pthread_mutex_unlock(&s->mtx); s->state_cb(s, s->arg, state); return NULL; @@ -149,8 +280,34 @@ sndio_mainloop(void *arg) /*static*/ int sndio_init(cubeb **context, char const *context_name) { + void * libsndio = NULL; + +#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 + DPR("sndio_init(%s)\n", context_name); *context = malloc(sizeof(*context)); + (*context)->libsndio = libsndio; (*context)->ops = &sndio_ops; (void)context_name; return CUBEB_OK; @@ -166,6 +323,8 @@ static void sndio_destroy(cubeb *context) { DPR("sndio_destroy()\n"); + if (context->libsndio) + dlclose(context->libsndio); free(context); } @@ -184,29 +343,49 @@ sndio_stream_init(cubeb * context, { cubeb_stream *s; struct sio_par wpar, rpar; - DPR("sndio_stream_init(%s)\n", stream_name); - size_t size; + cubeb_sample_format format; + int rate; + size_t bps; - assert(!input_stream_params && "not supported."); - if (input_device || output_device) { - /* Device selection not yet implemented. */ - return CUBEB_ERROR_DEVICE_UNAVAILABLE; - } + DPR("sndio_stream_init(%s)\n", stream_name); 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 = sio_open(NULL, SIO_PLAY, 1); + s->hdl = WRAP(sio_open)(NULL, s->mode, 1); if (s->hdl == NULL) { - free(s); DPR("sndio_stream_init(), sio_open() failed\n"); - return CUBEB_ERROR; + goto err; } - sio_initpar(&wpar); + WRAP(sio_initpar)(&wpar); wpar.sig = 1; wpar.bits = 16; - switch (output_stream_params->format) { + switch (format) { case CUBEB_SAMPLE_S16LE: wpar.le = 1; break; @@ -218,53 +397,70 @@ sndio_stream_init(cubeb * context, break; default: DPR("sndio_stream_init() unsupported format\n"); - return CUBEB_ERROR_INVALID_FORMAT; + goto err; } - wpar.rate = output_stream_params->rate; - wpar.pchan = output_stream_params->channels; + 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.appbufsz = latency_frames; - if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) { - sio_close(s->hdl); - free(s); + if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) { DPR("sndio_stream_init(), sio_setpar() failed\n"); - return CUBEB_ERROR; + goto err; } 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); + ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) || + ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) { DPR("sndio_stream_init() unsupported params\n"); - return CUBEB_ERROR_INVALID_FORMAT; + goto err; } - sio_onmove(s->hdl, sndio_onmove, s); + WRAP(sio_onmove)(s->hdl, sndio_onmove, s); s->active = 0; s->nfr = rpar.round; - s->bpf = rpar.bps * rpar.pchan; + s->rbpf = rpar.bps * rpar.rchan; + s->pbpf = rpar.bps * rpar.pchan; + s->rchan = rpar.rchan; 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_INITIALIZER; - s->rdpos = s->wrpos = 0; - if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { + s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + s->hwpos = s->swpos = 0; + if (format == CUBEB_SAMPLE_FLOAT32LE) { s->conv = 1; - size = rpar.round * rpar.pchan * sizeof(float); + bps = sizeof(float); } else { s->conv = 0; - size = rpar.round * rpar.pchan * rpar.bps; + bps = rpar.bps; } - s->buf = malloc(size); - if (s->buf == NULL) { - sio_close(s->hdl); - free(s); - return CUBEB_ERROR; + 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->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 @@ -280,16 +476,21 @@ sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) static int sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { - // XXX Not yet implemented. - *rate = 44100; - + /* + * 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; return CUBEB_OK; } static int sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) { - // XXX Not yet implemented. + /* + * We've no device-independent minimum latency. + */ *latency_frames = 2048; return CUBEB_OK; @@ -299,7 +500,11 @@ static void sndio_stream_destroy(cubeb_stream *s) { DPR("sndio_stream_destroy()\n"); - sio_close(s->hdl); + WRAP(sio_close)(s->hdl); + if (s->mode & SIO_PLAY) + free(s->pbuf); + if (s->mode & SIO_REC) + free(s->rbuf); free(s); } @@ -335,8 +540,8 @@ static int sndio_stream_get_position(cubeb_stream *s, uint64_t *p) { pthread_mutex_lock(&s->mtx); - DPR("sndio_stream_get_position() %lld\n", s->rdpos); - *p = s->rdpos / s->bpf; + DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos); + *p = s->hwpos; pthread_mutex_unlock(&s->mtx); return CUBEB_OK; } @@ -346,7 +551,11 @@ sndio_stream_set_volume(cubeb_stream *s, float volume) { DPR("sndio_stream_set_volume(%f)\n", volume); pthread_mutex_lock(&s->mtx); - sio_setvol(s->hdl, SIO_MAXVOL * volume); + if (volume < 0.) + volume = 0.; + else if (volume > 1.0) + volume = 1.; + s->volume = volume; pthread_mutex_unlock(&s->mtx); return CUBEB_OK; } @@ -356,7 +565,47 @@ 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->wrpos - stm->rdpos) / stm->bpf; + *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 = 16; + 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); return CUBEB_OK; } @@ -366,16 +615,17 @@ static struct cubeb_ops const sndio_ops = { .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, + .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_reset_default_device = NULL, .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, |