chiark / gitweb /
infra: Clean up project setup
[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 /*----- 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
60 typedef 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
67 const char *const ausys_suffix = "wav";
68
69 /*----- Static data -------------------------------------------------------*/
70
71 static SDL_AudioSpec auspec;            /* Driver's selected parameters */
72 static qnode *qhead = 0, *qtail = 0;    /* Queue of samples to play */
73 static qnode *qfree = 0;                /* Queue of samples to free */
74
75 static pthread_t tid_free;              /* The sample-freeing thread */
76 static pthread_mutex_t mx_free;         /* Mutex for @qfree@ */
77 static pthread_cond_t cv_free;          /* More sample data to free */
78
79 static 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
116 static 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 --- */
143
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
195 static 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
231 void 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;
264   want.samples = 1024;
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
290 void 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
319 void 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
326 void 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
345 au_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
410 fail_2:
411   xfree(cvt.buf);
412   goto fail_0;
413 fail_1:
414   SDL_FreeWAV(buf);
415 fail_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
429 void 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
457 void 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 -------------------------------------------------*/