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