michael@0: /* michael@0: * Copyright (c) 2011 Alexandre Ratchov michael@0: * michael@0: * This program is made available under an ISC-style license. See the michael@0: * accompanying file LICENSE for details. michael@0: */ michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "cubeb/cubeb.h" michael@0: #include "cubeb-internal.h" michael@0: michael@0: #if defined(CUBEB_SNDIO_DEBUG) michael@0: #define DPR(...) fprintf(stderr, __VA_ARGS__); michael@0: #else michael@0: #define DPR(...) do {} while(0) michael@0: #endif michael@0: michael@0: static struct cubeb_ops const sndio_ops; michael@0: michael@0: struct cubeb { michael@0: struct cubeb_ops const * ops; michael@0: }; michael@0: michael@0: struct cubeb_stream { michael@0: cubeb * context; michael@0: pthread_t th; /* to run real-time audio i/o */ michael@0: pthread_mutex_t mtx; /* protects hdl and pos */ michael@0: struct sio_hdl *hdl; /* link us to sndio */ michael@0: int active; /* cubec_start() called */ michael@0: int conv; /* need float->s16 conversion */ michael@0: unsigned char *buf; /* data is prepared here */ michael@0: unsigned int nfr; /* number of frames in buf */ michael@0: unsigned int bpf; /* bytes per frame */ michael@0: unsigned int pchan; /* number of play channels */ michael@0: uint64_t rdpos; /* frame number Joe hears right now */ michael@0: uint64_t wrpos; /* number of written frames */ michael@0: cubeb_data_callback data_cb; /* cb to preapare data */ michael@0: cubeb_state_callback state_cb; /* cb to notify about state changes */ michael@0: void *arg; /* user arg to {data,state}_cb */ michael@0: }; michael@0: michael@0: static void michael@0: float_to_s16(void *ptr, long nsamp) michael@0: { michael@0: int16_t *dst = ptr; michael@0: float *src = ptr; michael@0: michael@0: while (nsamp-- > 0) michael@0: *(dst++) = *(src++) * 32767; michael@0: } michael@0: michael@0: static void michael@0: sndio_onmove(void *arg, int delta) michael@0: { michael@0: cubeb_stream *s = (cubeb_stream *)arg; michael@0: michael@0: s->rdpos += delta; michael@0: } michael@0: michael@0: static void * michael@0: sndio_mainloop(void *arg) michael@0: { michael@0: #define MAXFDS 8 michael@0: struct pollfd pfds[MAXFDS]; michael@0: cubeb_stream *s = arg; michael@0: int n, nfds, revents, state; michael@0: size_t start = 0, end = 0; michael@0: long nfr; michael@0: michael@0: DPR("sndio_mainloop()\n"); michael@0: s->state_cb(s, s->arg, CUBEB_STATE_STARTED); michael@0: pthread_mutex_lock(&s->mtx); michael@0: if (!sio_start(s->hdl)) { michael@0: pthread_mutex_unlock(&s->mtx); michael@0: return NULL; michael@0: } michael@0: DPR("sndio_mainloop(), started\n"); michael@0: michael@0: start = end = s->nfr; michael@0: for (;;) { michael@0: if (!s->active) { michael@0: DPR("sndio_mainloop() stopped\n"); michael@0: state = CUBEB_STATE_STOPPED; michael@0: break; michael@0: } michael@0: if (start == end) { michael@0: if (end < s->nfr) { michael@0: DPR("sndio_mainloop() drained\n"); michael@0: state = CUBEB_STATE_DRAINED; michael@0: break; michael@0: } michael@0: pthread_mutex_unlock(&s->mtx); michael@0: nfr = s->data_cb(s, s->arg, s->buf, s->nfr); michael@0: pthread_mutex_lock(&s->mtx); michael@0: if (nfr < 0) { michael@0: DPR("sndio_mainloop() cb err\n"); michael@0: state = CUBEB_STATE_ERROR; michael@0: break; michael@0: } michael@0: if (s->conv) michael@0: float_to_s16(s->buf, nfr * s->pchan); michael@0: start = 0; michael@0: end = nfr * s->bpf; michael@0: } michael@0: if (end == 0) michael@0: continue; michael@0: nfds = sio_pollfd(s->hdl, pfds, POLLOUT); michael@0: if (nfds > 0) { michael@0: pthread_mutex_unlock(&s->mtx); michael@0: n = poll(pfds, nfds, -1); michael@0: pthread_mutex_lock(&s->mtx); michael@0: if (n < 0) michael@0: continue; michael@0: } michael@0: revents = sio_revents(s->hdl, pfds); michael@0: if (revents & POLLHUP) michael@0: break; michael@0: if (revents & POLLOUT) { michael@0: n = sio_write(s->hdl, s->buf + start, end - start); michael@0: if (n == 0) { michael@0: DPR("sndio_mainloop() werr\n"); michael@0: state = CUBEB_STATE_ERROR; michael@0: break; michael@0: } michael@0: s->wrpos = 0; michael@0: start += n; michael@0: } michael@0: } michael@0: sio_stop(s->hdl); michael@0: s->rdpos = s->wrpos; michael@0: pthread_mutex_unlock(&s->mtx); michael@0: s->state_cb(s, s->arg, state); michael@0: return NULL; michael@0: } michael@0: michael@0: /*static*/ int michael@0: sndio_init(cubeb **context, char const *context_name) michael@0: { michael@0: DPR("sndio_init(%s)\n", context_name); michael@0: *context = malloc(sizeof(*context)); michael@0: (*context)->ops = &sndio_ops; michael@0: (void)context_name; michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static char const * michael@0: sndio_get_backend_id(cubeb *context) michael@0: { michael@0: return "sndio"; michael@0: } michael@0: michael@0: static void michael@0: sndio_destroy(cubeb *context) michael@0: { michael@0: DPR("sndio_destroy()\n"); michael@0: free(context); michael@0: } michael@0: michael@0: static int michael@0: sndio_stream_init(cubeb *context, michael@0: cubeb_stream **stream, michael@0: char const *stream_name, michael@0: cubeb_stream_params stream_params, unsigned int latency, michael@0: cubeb_data_callback data_callback, michael@0: cubeb_state_callback state_callback, michael@0: void *user_ptr) michael@0: { michael@0: cubeb_stream *s; michael@0: struct sio_par wpar, rpar; michael@0: DPR("sndio_stream_init(%s)\n", stream_name); michael@0: size_t size; michael@0: michael@0: s = malloc(sizeof(cubeb_stream)); michael@0: if (s == NULL) michael@0: return CUBEB_ERROR; michael@0: s->context = context; michael@0: s->hdl = sio_open(NULL, SIO_PLAY, 0); michael@0: if (s->hdl == NULL) { michael@0: free(s); michael@0: DPR("sndio_stream_init(), sio_open() failed\n"); michael@0: return CUBEB_ERROR; michael@0: } michael@0: sio_initpar(&wpar); michael@0: wpar.sig = 1; michael@0: wpar.bits = 16; michael@0: switch (stream_params.format) { michael@0: case CUBEB_SAMPLE_S16LE: michael@0: wpar.le = 1; michael@0: break; michael@0: case CUBEB_SAMPLE_S16BE: michael@0: wpar.le = 0; michael@0: break; michael@0: case CUBEB_SAMPLE_FLOAT32NE: michael@0: wpar.le = SIO_LE_NATIVE; michael@0: break; michael@0: default: michael@0: DPR("sndio_stream_init() unsupported format\n"); michael@0: return CUBEB_ERROR_INVALID_FORMAT; michael@0: } michael@0: wpar.rate = stream_params.rate; michael@0: wpar.pchan = stream_params.channels; michael@0: wpar.appbufsz = latency * wpar.rate / 1000; michael@0: if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) { michael@0: sio_close(s->hdl); michael@0: free(s); michael@0: DPR("sndio_stream_init(), sio_setpar() failed\n"); michael@0: return CUBEB_ERROR; michael@0: } michael@0: if (rpar.bits != wpar.bits || rpar.le != wpar.le || michael@0: rpar.sig != wpar.sig || rpar.rate != wpar.rate || michael@0: rpar.pchan != wpar.pchan) { michael@0: sio_close(s->hdl); michael@0: free(s); michael@0: DPR("sndio_stream_init() unsupported params\n"); michael@0: return CUBEB_ERROR_INVALID_FORMAT; michael@0: } michael@0: sio_onmove(s->hdl, sndio_onmove, s); michael@0: s->active = 0; michael@0: s->nfr = rpar.round; michael@0: s->bpf = rpar.bps * rpar.pchan; michael@0: s->pchan = rpar.pchan; michael@0: s->data_cb = data_callback; michael@0: s->state_cb = state_callback; michael@0: s->arg = user_ptr; michael@0: s->mtx = PTHREAD_MUTEX_INITIALIZER; michael@0: s->rdpos = s->wrpos = 0; michael@0: if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE) { michael@0: s->conv = 1; michael@0: size = rpar.round * rpar.pchan * sizeof(float); michael@0: } else { michael@0: s->conv = 0; michael@0: size = rpar.round * rpar.pchan * rpar.bps; michael@0: } michael@0: s->buf = malloc(size); michael@0: if (s->buf == NULL) { michael@0: sio_close(s->hdl); michael@0: free(s); michael@0: return CUBEB_ERROR; michael@0: } michael@0: *stream = s; michael@0: DPR("sndio_stream_init() end, ok\n"); michael@0: (void)context; michael@0: (void)stream_name; michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) michael@0: { michael@0: assert(ctx && max_channels); michael@0: michael@0: *max_channels = 8; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) michael@0: { michael@0: // XXX Not yet implemented. michael@0: *rate = 44100; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) michael@0: { michael@0: // XXX Not yet implemented. michael@0: latency_ms = 40; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static void michael@0: sndio_stream_destroy(cubeb_stream *s) michael@0: { michael@0: DPR("sndio_stream_destroy()\n"); michael@0: sio_close(s->hdl); michael@0: free(s); michael@0: } michael@0: michael@0: static int michael@0: sndio_stream_start(cubeb_stream *s) michael@0: { michael@0: int err; michael@0: michael@0: DPR("sndio_stream_start()\n"); michael@0: s->active = 1; michael@0: err = pthread_create(&s->th, NULL, sndio_mainloop, s); michael@0: if (err) { michael@0: s->active = 0; michael@0: return CUBEB_ERROR; michael@0: } michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: sndio_stream_stop(cubeb_stream *s) michael@0: { michael@0: void *dummy; michael@0: michael@0: DPR("sndio_stream_stop()\n"); michael@0: if (s->active) { michael@0: s->active = 0; michael@0: pthread_join(s->th, &dummy); michael@0: } michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: sndio_stream_get_position(cubeb_stream *s, uint64_t *p) michael@0: { michael@0: pthread_mutex_lock(&s->mtx); michael@0: DPR("sndio_stream_get_position() %lld\n", s->rdpos); michael@0: *p = s->rdpos; michael@0: pthread_mutex_unlock(&s->mtx); michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: sndio_stream_set_volume(cubeb_stream *s, float volume) michael@0: { michael@0: DPR("sndio_stream_set_volume(%f)\n", volume); michael@0: pthread_mutex_lock(&s->mtx); michael@0: sio_setvol(s->hdl, SIO_MAXVOL * volume); michael@0: pthread_mutex_unlock(&s->mtx); michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: int michael@0: sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) michael@0: { michael@0: // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open michael@0: // in the "Measuring the latency and buffers usage" paragraph. michael@0: *latency = stm->wrpos - stm->rdpos; michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static struct cubeb_ops const sndio_ops = { michael@0: .init = sndio_init, michael@0: .get_backend_id = sndio_get_backend_id, michael@0: .get_max_channel_count = sndio_get_max_channel_count, michael@0: .get_min_latency = sndio_get_min_latency, michael@0: .get_preferred_sample_rate = sndio_get_preferred_sample_rate, michael@0: .destroy = sndio_destroy, michael@0: .stream_init = sndio_stream_init, michael@0: .stream_destroy = sndio_stream_destroy, michael@0: .stream_start = sndio_stream_start, michael@0: .stream_stop = sndio_stream_stop, michael@0: .stream_get_position = sndio_stream_get_position, michael@0: .stream_get_latency = sndio_stream_get_latency michael@0: };