d4d6dcf95b05adb954e399b7f4c096bf61b1aee4
[jog] / ausys-sdl.c
1 /* -*-c-*-
2 *
3 * $Id: ausys-sdl.c,v 1.2 2002/02/02 22:43:17 mdw Exp $
4 *
5 * Unix-specific (SDL) audio handling
6 *
7 * (c) 2002 Mark Wooding
8 */
9
10 /*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of Jog: Programming for a jogging machine.
13 *
14 * Jog is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * Jog is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Jog; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29 /*----- Revision history --------------------------------------------------*
30 *
31 * $Log: ausys-sdl.c,v $
32 * Revision 1.2 2002/02/02 22:43:17 mdw
33 * Remove bogus debugging. Reduce output buffer size for faster response.
34 *
35 * Revision 1.1 2002/02/02 19:16:28 mdw
36 * New audio subsystem.
37 *
38 */
39
40 /*----- Header files ------------------------------------------------------*/
41
42 #ifdef HAVE_CONFIG_H
43 # include "config.h"
44 #endif
45
46 #include <errno.h>
47 #include <stdio.h>
48 #include <string.h>
49
50 #include <sys/types.h>
51 #include <sys/time.h>
52 #include <unistd.h>
53 #include <pthread.h>
54
55 #include <SDL.h>
56
57 #include <mLib/alloc.h>
58 #include <mLib/bits.h>
59 #include <mLib/sub.h>
60 #include <mLib/trace.h>
61
62 #include "au.h"
63 #include "ausys.h"
64 #include "err.h"
65 #include "jog.h"
66
67 /*----- Data structures ---------------------------------------------------*/
68
69 /* --- Queue of samples to play --- */
70
71 typedef struct qnode {
72 struct qnode *next; /* Next item in the queue */
73 au_data *a; /* Pointer to sample data */
74 } qnode;
75
76 /*----- Global variables --------------------------------------------------*/
77
78 const char *const ausys_suffix = "wav";
79
80 /*----- Static data -------------------------------------------------------*/
81
82 static SDL_AudioSpec auspec; /* Driver's selected parameters */
83 static qnode *qhead = 0, *qtail = 0; /* Queue of samples to play */
84 static qnode *qfree = 0; /* Queue of samples to free */
85
86 static pthread_t tid_free; /* The sample-freeing thread */
87 static pthread_mutex_t mx_free; /* Mutex for @qfree@ */
88 static pthread_cond_t cv_free; /* More sample data to free */
89
90 static pthread_mutex_t mx_sub; /* Protects mLib @sub@ functions */
91
92 /*----- Thread structure --------------------------------------------------*
93 *
94 * SDL makes a buffer-stuffing thread that @fill@ is run from. This function
95 * reads samples to play from the queue at @qhead@. Hence @qhead@ must be
96 * locked when it's modified, using @SDL_LockAudio@.
97 *
98 * In order to keep latency down when new sample data is wanted, we don't do
99 * potentially tedious things like freeing sample data blocks in the @fill@
100 * thread. Instead, there's a queue of sample blocks which need freeing, and
101 * a separate thread @dofree@ which processes the queue. The queue, @qfree@
102 * is locked by @mx_free@. When new nodes are added to @qfree@, the
103 * condition @cv_free@ is signalled.
104 *
105 * Finally, the mLib `sub' module isn't thread-safe, so it's locked
106 * separately by @mx_sub@.
107 *
108 * There's an ordering on mutexes. If you want more than one mutex at a
109 * time, you must lock the greatest first. The ordering is:
110 *
111 * SDL_Audio > free > sub
112 */
113
114 /*----- Main code ---------------------------------------------------------*/
115
116 /* --- @fill@ --- *
117 *
118 * Arguments: @void *u@ = user data (ignored)
119 * @octet *q@ = pointer to buffer to fill in
120 * @int qsz@ = size of the buffer
121 *
122 * Returns: ---
123 *
124 * Use: Fills an audio buffer with precomputed stuff.
125 */
126
127 static void fill(void *u, octet *q, int qsz)
128 {
129 static const octet *p = 0; /* Current place in current sample */
130 static size_t sz = 0; /* How far to go in current sample */
131 qnode *qf = 0, **qft = &qf; /* Samples we've finished with */
132
133 /* --- If @p@ is null we need to get a new sample --- */
134
135 if (!p && qhead) {
136 T( trace(T_AUSYS, "ausys: begin playing `%s'", SYM_NAME(qhead->a->s)); )
137 p = qhead->a->p;
138 sz = qhead->a->sz;
139 }
140
141 /* --- Main queue-filling loop --- */
142
143 while (qsz) {
144 size_t n;
145
146 /* --- If the queue is empty, play silence --- */
147
148 if (!p) {
149 memset(q, auspec.silence, qsz);
150 break;
151 }
152
153 /* --- Fill the buffer with my sample --- */
154
155 n = qsz;
156 if (n > sz)
157 n = sz;
158 memcpy(q, p, n);
159 p += n; q += n;
160 sz -= n; qsz -= n;
161
162 /* --- If the sample is used up, throw it away --- */
163
164 if (!sz) {
165 qnode *qq = qhead;
166 qhead = qq->next;
167 if (!qhead)
168 qtail = 0;
169 T( trace(T_AUSYS, "ausys: put `%s' on free list",
170 SYM_NAME(qq->a->s)); )
171 *qft = qq;
172 qft = &qq->next;
173 if (qhead) {
174 T( trace(T_AUSYS, "ausys: begin playing `%s'",
175 SYM_NAME(qhead->a->s)); )
176 p = qhead->a->p;
177 sz = qhead->a->sz;
178 } else {
179 T( trace(T_AUSYS, "ausys: sample queue is empty"); )
180 p = 0;
181 sz = 0;
182 }
183 }
184 }
185
186 /* --- Finally dump freed samples, all in one go --- */
187
188 if (qf) {
189 pthread_mutex_lock(&mx_free);
190 *qft = qfree;
191 qfree = qf;
192 pthread_mutex_unlock(&mx_free);
193 pthread_cond_signal(&cv_free);
194 }
195 }
196
197 /* --- @dofree@ --- *
198 *
199 * Arguments: @void *u@ = unused pointer
200 *
201 * Returns: An unused pointer.
202 *
203 * Use: Frees unwanted sample queue nodes.
204 */
205
206 static void *dofree(void *u)
207 {
208 qnode *q, *qq;
209
210 pthread_mutex_lock(&mx_free);
211 for (;;) {
212 T( trace(T_AUSYS, "ausys: dofree sleeping"); )
213 pthread_cond_wait(&cv_free, &mx_free);
214 T( trace(T_AUSYS, "ausys: dofree woken: work to do"); )
215
216 while (qfree) {
217 q = qfree;
218 qfree = 0;
219 pthread_mutex_lock(&mx_sub);
220 while (q) {
221 qq = q->next;
222 T( trace(T_AUSYS, "ausys: freeing `%s'", SYM_NAME(q->a->s)); )
223 au_free_unlocked(q->a);
224 DESTROY(q);
225 q = qq;
226 }
227 pthread_mutex_unlock(&mx_sub);
228 }
229 }
230 }
231
232 /* --- @ausys_init@ --- *
233 *
234 * Arguments: ---
235 *
236 * Returns: ---
237 *
238 * Use: Does any initialization required by the system-specific audio
239 * handler.
240 */
241
242 void ausys_init(void)
243 {
244 SDL_AudioSpec want;
245 pthread_attr_t ta;
246 int e;
247
248 /* --- Crank up the sample-freeing thread --- */
249
250 if ((e = pthread_mutex_init(&mx_free, 0)) != 0 ||
251 (e = pthread_mutex_init(&mx_sub, 0)) != 0 ||
252 (e = pthread_cond_init(&cv_free, 0)) != 0 ||
253 (e = pthread_attr_init(&ta)) != 0 ||
254 (e = pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED)) != 0 ||
255 (e = pthread_create(&tid_free, &ta, dofree, 0)) != 0) {
256 err_report(ERR_AUDIO, ERRAU_INIT, e,
257 "couldn't create audio threads: %s", strerror(errno));
258 exit(EXIT_FAILURE);
259 }
260 pthread_attr_destroy(&ta);
261
262 /* --- Crank up the SDL audio subsystem --- */
263
264 if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE)) {
265 err_report(ERR_AUDIO, ERRAU_INIT, 0,
266 "couldn't initialize SDL audio: %s", SDL_GetError());
267 exit(EXIT_FAILURE);
268 }
269
270 /* --- Open the audio device --- */
271
272 want.freq = 8000;
273 want.format = AUDIO_U8;
274 want.channels = 1;
275 want.samples = 1024;
276 want.callback = fill;
277 want.userdata = 0;
278
279 if (SDL_OpenAudio(&want, &auspec)) {
280 err_report(ERR_AUDIO, ERRAU_INIT, 0,
281 "couldn't open audio device: %s", SDL_GetError());
282 exit(EXIT_FAILURE);
283 }
284
285 /* --- Prepare whatever needs to be done --- */
286
287 SDL_PauseAudio(0);
288
289 T( trace(T_AUSYS, "ausys: initalized ok"); )
290 }
291
292 /* --- @ausys_shutdown@ --- *
293 *
294 * Arguments: ---
295 *
296 * Returns: ---
297 *
298 * Use: Does any tidying up required.
299 */
300
301 void ausys_shutdown(void)
302 {
303 /* --- Busy-wait until sound buffer empties --- */
304
305 while (qhead) {
306 struct timeval tv = { 0, 100000 };
307 select(0, 0, 0, 0, &tv);
308 }
309
310 /* --- Shut stuff down --- */
311
312 pthread_cancel(tid_free);
313 SDL_CloseAudio();
314 SDL_Quit();
315
316 T( trace(T_AUSYS, "ausys: shut down ok"); )
317 }
318
319 /* --- @ausys_lock@, @ausys_unlock@ --- *
320 *
321 * Arguments: ---
322 *
323 * Returns: ---
324 *
325 * Use: Locks or unlocks the audio subsystem. This protects the
326 * audio queue from becoming corrupted during all the tedious
327 * asynchronous stuff.
328 */
329
330 void ausys_lock(void)
331 {
332 pthread_mutex_lock(&mx_free);
333 pthread_mutex_lock(&mx_sub);
334 T( trace(T_AUSYS, "ausys: acquired lock"); )
335 }
336
337 void ausys_unlock(void)
338 {
339 pthread_mutex_unlock(&mx_sub);
340 pthread_mutex_unlock(&mx_free);
341 T( trace(T_AUSYS, "ausys: released lock"); )
342 }
343
344 /* --- @ausys_decode@ --- *
345 *
346 * Arguments: @au_sample *s@ = pointer to sample block
347 * @const void *p@ = pointer to sample file contents
348 * @size_t sz@ = size of sample file contents
349 *
350 * Returns: Pointer to a sample data structure.
351 *
352 * Use: Decodes a WAV file into something the system-specific layer
353 * actually wants to deal with.
354 */
355
356 au_data *ausys_decode(au_sample *s, const void *p, size_t sz)
357 {
358 au_data *a;
359 SDL_AudioCVT cvt;
360 SDL_AudioSpec spec;
361 SDL_RWops *rw;
362 Uint8 *buf;
363 Uint32 len;
364 void *q;
365
366 /* --- Firstly, extract the audio data from the WAV file --- */
367
368 rw = SDL_RWFromMem((void *)p, sz);
369 if (!SDL_LoadWAV_RW(rw, 1, &spec, &buf, &len)) {
370 err_report(ERR_AUDIO, ERRAU_OPEN, 0,
371 "can't read WAV file for `%s': %s",
372 SYM_NAME(s), SDL_GetError());
373 goto fail_0;
374 }
375
376 /* --- Now convert that to the driver's expected format --- */
377
378 if (SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq,
379 auspec.format, auspec.channels, auspec.freq) == -1) {
380 err_report(ERR_AUDIO, ERRAU_OPEN, 0,
381 "can't build conversion structure for `%s': %s",
382 SYM_NAME(s), SDL_GetError());
383 goto fail_1;
384 }
385
386 cvt.buf = xmalloc(len * cvt.len_mult);
387 cvt.len = len;
388 memcpy(cvt.buf, buf, len);
389 SDL_FreeWAV(buf);
390
391 if (SDL_ConvertAudio(&cvt)) {
392 err_report(ERR_AUDIO, ERRAU_OPEN, 0,
393 "can't convert `%s': %s", SYM_NAME(s), SDL_GetError());
394 goto fail_2;
395 }
396
397 /* --- Now fiddle with buffers --- */
398
399 sz = len * cvt.len_ratio;
400 if (cvt.len_ratio >= 1)
401 q = cvt.buf;
402 else {
403 q = xmalloc(sz);
404 memcpy(q, cvt.buf, sz);
405 xfree(cvt.buf);
406 }
407
408 /* --- Construct a new node --- */
409
410 pthread_mutex_lock(&mx_sub);
411 a = CREATE(au_data);
412 pthread_mutex_unlock(&mx_sub);
413 a->p = q;
414 a->sz = sz;
415
416 T( trace(T_AUSYS, "ausys: decoded `%s' ok", SYM_NAME(s)); )
417 return (a);
418
419 /* --- Tidy up after errors --- */
420
421 fail_2:
422 xfree(cvt.buf);
423 goto fail_0;
424 fail_1:
425 SDL_FreeWAV(buf);
426 fail_0:
427 return (0);
428 }
429
430 /* --- @ausys_queue@ --- *
431 *
432 * Arguments: @au_data *a@ = an audio thingy to play
433 *
434 * Returns: ---
435 *
436 * Use: Queues an audio sample to be played. The sample should be
437 * freed (with @au_free@) when it's no longer wanted.
438 */
439
440 void ausys_queue(au_data *a)
441 {
442 qnode *q;
443
444 pthread_mutex_lock(&mx_sub);
445 q = CREATE(qnode);
446 pthread_mutex_unlock(&mx_sub);
447 q->next = 0;
448 q->a = a;
449 SDL_LockAudio();
450 if (qtail)
451 qtail->next = q;
452 else
453 qhead = q;
454 qtail = q;
455 SDL_UnlockAudio();
456 T( trace(T_AUSYS, "ausys: queuing `%s'", SYM_NAME(a->s)); )
457 }
458
459 /* --- @ausys_free@ --- *
460 *
461 * Arguments: @au_data *a@ = an audio thingy to free
462 *
463 * Returns: ---
464 *
465 * Use: Frees a decoded audio sample.
466 */
467
468 void ausys_free(au_data *a)
469 {
470 xfree(a->p);
471 DESTROY(a);
472 T( trace(T_AUSYS, "ausys: freeing data for `%s' ok", SYM_NAME(a->s)); )
473 }
474
475 /*----- That's all, folks -------------------------------------------------*/