chiark / gitweb /
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 -------------------------------------------------*/