Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 <poll.h>
8 #include <pthread.h>
9 #include <sndio.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <assert.h>
13 #include "cubeb/cubeb.h"
14 #include "cubeb-internal.h"
16 #if defined(CUBEB_SNDIO_DEBUG)
17 #define DPR(...) fprintf(stderr, __VA_ARGS__);
18 #else
19 #define DPR(...) do {} while(0)
20 #endif
22 static struct cubeb_ops const sndio_ops;
24 struct cubeb {
25 struct cubeb_ops const * ops;
26 };
28 struct cubeb_stream {
29 cubeb * context;
30 pthread_t th; /* to run real-time audio i/o */
31 pthread_mutex_t mtx; /* protects hdl and pos */
32 struct sio_hdl *hdl; /* link us to sndio */
33 int active; /* cubec_start() called */
34 int conv; /* need float->s16 conversion */
35 unsigned char *buf; /* data is prepared here */
36 unsigned int nfr; /* number of frames in buf */
37 unsigned int bpf; /* bytes per frame */
38 unsigned int pchan; /* number of play channels */
39 uint64_t rdpos; /* frame number Joe hears right now */
40 uint64_t wrpos; /* number of written frames */
41 cubeb_data_callback data_cb; /* cb to preapare data */
42 cubeb_state_callback state_cb; /* cb to notify about state changes */
43 void *arg; /* user arg to {data,state}_cb */
44 };
46 static void
47 float_to_s16(void *ptr, long nsamp)
48 {
49 int16_t *dst = ptr;
50 float *src = ptr;
52 while (nsamp-- > 0)
53 *(dst++) = *(src++) * 32767;
54 }
56 static void
57 sndio_onmove(void *arg, int delta)
58 {
59 cubeb_stream *s = (cubeb_stream *)arg;
61 s->rdpos += delta;
62 }
64 static void *
65 sndio_mainloop(void *arg)
66 {
67 #define MAXFDS 8
68 struct pollfd pfds[MAXFDS];
69 cubeb_stream *s = arg;
70 int n, nfds, revents, state;
71 size_t start = 0, end = 0;
72 long nfr;
74 DPR("sndio_mainloop()\n");
75 s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
76 pthread_mutex_lock(&s->mtx);
77 if (!sio_start(s->hdl)) {
78 pthread_mutex_unlock(&s->mtx);
79 return NULL;
80 }
81 DPR("sndio_mainloop(), started\n");
83 start = end = s->nfr;
84 for (;;) {
85 if (!s->active) {
86 DPR("sndio_mainloop() stopped\n");
87 state = CUBEB_STATE_STOPPED;
88 break;
89 }
90 if (start == end) {
91 if (end < s->nfr) {
92 DPR("sndio_mainloop() drained\n");
93 state = CUBEB_STATE_DRAINED;
94 break;
95 }
96 pthread_mutex_unlock(&s->mtx);
97 nfr = s->data_cb(s, s->arg, s->buf, s->nfr);
98 pthread_mutex_lock(&s->mtx);
99 if (nfr < 0) {
100 DPR("sndio_mainloop() cb err\n");
101 state = CUBEB_STATE_ERROR;
102 break;
103 }
104 if (s->conv)
105 float_to_s16(s->buf, nfr * s->pchan);
106 start = 0;
107 end = nfr * s->bpf;
108 }
109 if (end == 0)
110 continue;
111 nfds = sio_pollfd(s->hdl, pfds, POLLOUT);
112 if (nfds > 0) {
113 pthread_mutex_unlock(&s->mtx);
114 n = poll(pfds, nfds, -1);
115 pthread_mutex_lock(&s->mtx);
116 if (n < 0)
117 continue;
118 }
119 revents = sio_revents(s->hdl, pfds);
120 if (revents & POLLHUP)
121 break;
122 if (revents & POLLOUT) {
123 n = sio_write(s->hdl, s->buf + start, end - start);
124 if (n == 0) {
125 DPR("sndio_mainloop() werr\n");
126 state = CUBEB_STATE_ERROR;
127 break;
128 }
129 s->wrpos = 0;
130 start += n;
131 }
132 }
133 sio_stop(s->hdl);
134 s->rdpos = s->wrpos;
135 pthread_mutex_unlock(&s->mtx);
136 s->state_cb(s, s->arg, state);
137 return NULL;
138 }
140 /*static*/ int
141 sndio_init(cubeb **context, char const *context_name)
142 {
143 DPR("sndio_init(%s)\n", context_name);
144 *context = malloc(sizeof(*context));
145 (*context)->ops = &sndio_ops;
146 (void)context_name;
147 return CUBEB_OK;
148 }
150 static char const *
151 sndio_get_backend_id(cubeb *context)
152 {
153 return "sndio";
154 }
156 static void
157 sndio_destroy(cubeb *context)
158 {
159 DPR("sndio_destroy()\n");
160 free(context);
161 }
163 static int
164 sndio_stream_init(cubeb *context,
165 cubeb_stream **stream,
166 char const *stream_name,
167 cubeb_stream_params stream_params, unsigned int latency,
168 cubeb_data_callback data_callback,
169 cubeb_state_callback state_callback,
170 void *user_ptr)
171 {
172 cubeb_stream *s;
173 struct sio_par wpar, rpar;
174 DPR("sndio_stream_init(%s)\n", stream_name);
175 size_t size;
177 s = malloc(sizeof(cubeb_stream));
178 if (s == NULL)
179 return CUBEB_ERROR;
180 s->context = context;
181 s->hdl = sio_open(NULL, SIO_PLAY, 0);
182 if (s->hdl == NULL) {
183 free(s);
184 DPR("sndio_stream_init(), sio_open() failed\n");
185 return CUBEB_ERROR;
186 }
187 sio_initpar(&wpar);
188 wpar.sig = 1;
189 wpar.bits = 16;
190 switch (stream_params.format) {
191 case CUBEB_SAMPLE_S16LE:
192 wpar.le = 1;
193 break;
194 case CUBEB_SAMPLE_S16BE:
195 wpar.le = 0;
196 break;
197 case CUBEB_SAMPLE_FLOAT32NE:
198 wpar.le = SIO_LE_NATIVE;
199 break;
200 default:
201 DPR("sndio_stream_init() unsupported format\n");
202 return CUBEB_ERROR_INVALID_FORMAT;
203 }
204 wpar.rate = stream_params.rate;
205 wpar.pchan = stream_params.channels;
206 wpar.appbufsz = latency * wpar.rate / 1000;
207 if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) {
208 sio_close(s->hdl);
209 free(s);
210 DPR("sndio_stream_init(), sio_setpar() failed\n");
211 return CUBEB_ERROR;
212 }
213 if (rpar.bits != wpar.bits || rpar.le != wpar.le ||
214 rpar.sig != wpar.sig || rpar.rate != wpar.rate ||
215 rpar.pchan != wpar.pchan) {
216 sio_close(s->hdl);
217 free(s);
218 DPR("sndio_stream_init() unsupported params\n");
219 return CUBEB_ERROR_INVALID_FORMAT;
220 }
221 sio_onmove(s->hdl, sndio_onmove, s);
222 s->active = 0;
223 s->nfr = rpar.round;
224 s->bpf = rpar.bps * rpar.pchan;
225 s->pchan = rpar.pchan;
226 s->data_cb = data_callback;
227 s->state_cb = state_callback;
228 s->arg = user_ptr;
229 s->mtx = PTHREAD_MUTEX_INITIALIZER;
230 s->rdpos = s->wrpos = 0;
231 if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE) {
232 s->conv = 1;
233 size = rpar.round * rpar.pchan * sizeof(float);
234 } else {
235 s->conv = 0;
236 size = rpar.round * rpar.pchan * rpar.bps;
237 }
238 s->buf = malloc(size);
239 if (s->buf == NULL) {
240 sio_close(s->hdl);
241 free(s);
242 return CUBEB_ERROR;
243 }
244 *stream = s;
245 DPR("sndio_stream_init() end, ok\n");
246 (void)context;
247 (void)stream_name;
248 return CUBEB_OK;
249 }
251 static int
252 sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
253 {
254 assert(ctx && max_channels);
256 *max_channels = 8;
258 return CUBEB_OK;
259 }
261 static int
262 sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
263 {
264 // XXX Not yet implemented.
265 *rate = 44100;
267 return CUBEB_OK;
268 }
270 static int
271 sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
272 {
273 // XXX Not yet implemented.
274 latency_ms = 40;
276 return CUBEB_OK;
277 }
279 static void
280 sndio_stream_destroy(cubeb_stream *s)
281 {
282 DPR("sndio_stream_destroy()\n");
283 sio_close(s->hdl);
284 free(s);
285 }
287 static int
288 sndio_stream_start(cubeb_stream *s)
289 {
290 int err;
292 DPR("sndio_stream_start()\n");
293 s->active = 1;
294 err = pthread_create(&s->th, NULL, sndio_mainloop, s);
295 if (err) {
296 s->active = 0;
297 return CUBEB_ERROR;
298 }
299 return CUBEB_OK;
300 }
302 static int
303 sndio_stream_stop(cubeb_stream *s)
304 {
305 void *dummy;
307 DPR("sndio_stream_stop()\n");
308 if (s->active) {
309 s->active = 0;
310 pthread_join(s->th, &dummy);
311 }
312 return CUBEB_OK;
313 }
315 static int
316 sndio_stream_get_position(cubeb_stream *s, uint64_t *p)
317 {
318 pthread_mutex_lock(&s->mtx);
319 DPR("sndio_stream_get_position() %lld\n", s->rdpos);
320 *p = s->rdpos;
321 pthread_mutex_unlock(&s->mtx);
322 return CUBEB_OK;
323 }
325 static int
326 sndio_stream_set_volume(cubeb_stream *s, float volume)
327 {
328 DPR("sndio_stream_set_volume(%f)\n", volume);
329 pthread_mutex_lock(&s->mtx);
330 sio_setvol(s->hdl, SIO_MAXVOL * volume);
331 pthread_mutex_unlock(&s->mtx);
332 return CUBEB_OK;
333 }
335 int
336 sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
337 {
338 // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open
339 // in the "Measuring the latency and buffers usage" paragraph.
340 *latency = stm->wrpos - stm->rdpos;
341 return CUBEB_OK;
342 }
344 static struct cubeb_ops const sndio_ops = {
345 .init = sndio_init,
346 .get_backend_id = sndio_get_backend_id,
347 .get_max_channel_count = sndio_get_max_channel_count,
348 .get_min_latency = sndio_get_min_latency,
349 .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
350 .destroy = sndio_destroy,
351 .stream_init = sndio_stream_init,
352 .stream_destroy = sndio_stream_destroy,
353 .stream_start = sndio_stream_start,
354 .stream_stop = sndio_stream_stop,
355 .stream_get_position = sndio_stream_get_position,
356 .stream_get_latency = sndio_stream_get_latency
357 };