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