|
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" |
|
15 |
|
16 #if defined(CUBEB_SNDIO_DEBUG) |
|
17 #define DPR(...) fprintf(stderr, __VA_ARGS__); |
|
18 #else |
|
19 #define DPR(...) do {} while(0) |
|
20 #endif |
|
21 |
|
22 static struct cubeb_ops const sndio_ops; |
|
23 |
|
24 struct cubeb { |
|
25 struct cubeb_ops const * ops; |
|
26 }; |
|
27 |
|
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 }; |
|
45 |
|
46 static void |
|
47 float_to_s16(void *ptr, long nsamp) |
|
48 { |
|
49 int16_t *dst = ptr; |
|
50 float *src = ptr; |
|
51 |
|
52 while (nsamp-- > 0) |
|
53 *(dst++) = *(src++) * 32767; |
|
54 } |
|
55 |
|
56 static void |
|
57 sndio_onmove(void *arg, int delta) |
|
58 { |
|
59 cubeb_stream *s = (cubeb_stream *)arg; |
|
60 |
|
61 s->rdpos += delta; |
|
62 } |
|
63 |
|
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; |
|
73 |
|
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"); |
|
82 |
|
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 } |
|
139 |
|
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 } |
|
149 |
|
150 static char const * |
|
151 sndio_get_backend_id(cubeb *context) |
|
152 { |
|
153 return "sndio"; |
|
154 } |
|
155 |
|
156 static void |
|
157 sndio_destroy(cubeb *context) |
|
158 { |
|
159 DPR("sndio_destroy()\n"); |
|
160 free(context); |
|
161 } |
|
162 |
|
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; |
|
176 |
|
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 } |
|
250 |
|
251 static int |
|
252 sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) |
|
253 { |
|
254 assert(ctx && max_channels); |
|
255 |
|
256 *max_channels = 8; |
|
257 |
|
258 return CUBEB_OK; |
|
259 } |
|
260 |
|
261 static int |
|
262 sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) |
|
263 { |
|
264 // XXX Not yet implemented. |
|
265 *rate = 44100; |
|
266 |
|
267 return CUBEB_OK; |
|
268 } |
|
269 |
|
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; |
|
275 |
|
276 return CUBEB_OK; |
|
277 } |
|
278 |
|
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 } |
|
286 |
|
287 static int |
|
288 sndio_stream_start(cubeb_stream *s) |
|
289 { |
|
290 int err; |
|
291 |
|
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 } |
|
301 |
|
302 static int |
|
303 sndio_stream_stop(cubeb_stream *s) |
|
304 { |
|
305 void *dummy; |
|
306 |
|
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 } |
|
314 |
|
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 } |
|
324 |
|
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 } |
|
334 |
|
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 } |
|
343 |
|
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 }; |