chiark / gitweb /
3253a1d4f75db199a45684be1e54ba1940205196
[disorder] / lib / uaudio-thread.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2009 Richard Kettlewell
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 /** @file lib/uaudio-thread.c
19  * @brief Background thread for audio processing */
20 #include "common.h"
21
22 #include <pthread.h>
23 #include <unistd.h>
24
25 #include "uaudio.h"
26 #include "log.h"
27 #include "mem.h"
28
29 /** @brief Number of buffers
30  *
31  * Must be at least 2 and should normally be at least 3.  We maintain multiple
32  * buffers so that we can read new data into one while the previous is being
33  * played.
34  */
35 #define UAUDIO_THREAD_BUFFERS 4
36
37 /** @brief Buffer data structure */
38 struct uaudio_buffer {
39   /** @brief Pointer to sample data */
40   void *samples;
41
42   /** @brief Count of samples */
43   size_t nsamples;
44 };
45
46 /** @brief Input buffers
47  *
48  * This is actually a ring buffer, managed by @ref uaudio_collect_buffer and
49  * @ref uaudio_play_buffer.
50  *
51  * Initially both pointers are 0.  Whenever the pointers are equal, we
52  * interpreted this as meaning that there is no data stored at all.  A
53  * consequence of this is that maximal occupancy is when the collect point is
54  * just before the play point, so at least one buffer is always empty (hence it
55  * being good for @ref UAUDIO_THREAD_BUFFERS to be at least 3).
56  */
57 static struct uaudio_buffer uaudio_buffers[UAUDIO_THREAD_BUFFERS];
58
59 /** @brief Buffer to read into */
60 static unsigned uaudio_collect_buffer;
61
62 /** @brief Buffer to play from */
63 static unsigned uaudio_play_buffer;
64
65 /** @brief Collection thread ID */
66 static pthread_t uaudio_collect_thread;
67
68 /** @brief Playing thread ID */
69 static pthread_t uaudio_play_thread;
70
71 static uaudio_callback *uaudio_thread_collect_callback;
72 static uaudio_playcallback *uaudio_thread_play_callback;
73 static void *uaudio_thread_userdata;
74 static int uaudio_thread_started;
75 static int uaudio_thread_activated;
76 static int uaudio_thread_collecting;
77 static pthread_mutex_t uaudio_thread_lock = PTHREAD_MUTEX_INITIALIZER;
78 static pthread_cond_t uaudio_thread_cond = PTHREAD_COND_INITIALIZER;
79
80 /** @brief Minimum number of samples per chunk */
81 static size_t uaudio_thread_min;
82
83 /** @brief Maximum number of samples per chunk */
84 static size_t uaudio_thread_max;
85
86 /** @brief Return number of buffers currently in use */
87 static int uaudio_buffers_used(void) {
88   return (uaudio_collect_buffer - uaudio_play_buffer) % UAUDIO_THREAD_BUFFERS;
89 }
90
91 /** @brief Background thread for audio collection
92  *
93  * Collects data while activated and communicates its status via @ref
94  * uaudio_thread_collecting.
95  */
96 static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) {
97   pthread_mutex_lock(&uaudio_thread_lock);
98   while(uaudio_thread_started) {
99     /* Wait until we're activatd */
100     if(!uaudio_thread_activated) {
101       pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
102       continue;
103     }
104     /* We are definitely active now */
105     uaudio_thread_collecting = 1;
106     pthread_cond_broadcast(&uaudio_thread_cond);
107     while(uaudio_thread_activated) {
108       if(uaudio_buffers_used() < UAUDIO_THREAD_BUFFERS - 1) {
109         /* At least one buffer is available.  We release the lock while
110          * collecting data so that other already-filled buffers can be played
111          * without delay.  */
112         struct uaudio_buffer *const b = &uaudio_buffers[uaudio_collect_buffer];
113         pthread_mutex_unlock(&uaudio_thread_lock);
114         //fprintf(stderr, "C%d.", uaudio_collect_buffer);
115         
116         /* Keep on trying until we get the minimum required amount of data */
117         b->nsamples = 0;
118         while(b->nsamples < uaudio_thread_min) {
119           b->nsamples += uaudio_thread_collect_callback
120             ((char *)b->samples
121              + b->nsamples * uaudio_sample_size,
122              uaudio_thread_max - b->nsamples,
123              uaudio_thread_userdata);
124         }
125         pthread_mutex_lock(&uaudio_thread_lock);
126         /* Advance to next buffer */
127         uaudio_collect_buffer = (1 + uaudio_collect_buffer) % UAUDIO_THREAD_BUFFERS;
128         /* Awaken player */
129         pthread_cond_broadcast(&uaudio_thread_cond);
130       } else
131         /* No space, wait for player */
132         pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
133     }
134     uaudio_thread_collecting = 0;
135     pthread_cond_broadcast(&uaudio_thread_cond);
136   }
137   pthread_mutex_unlock(&uaudio_thread_lock);
138   return NULL;
139 }
140
141 /** @brief Background thread for audio playing 
142  *
143  * This thread plays data as long as there is something to play.  So the
144  * buffers will drain to empty before deactivation completes.
145  */
146 static void *uaudio_play_thread_fn(void attribute((unused)) *arg) {
147   int resync = 1;
148
149   pthread_mutex_lock(&uaudio_thread_lock);
150   while(uaudio_thread_started) {
151     const int used = uaudio_buffers_used();
152     int go;
153
154     if(resync)
155       go = (used == UAUDIO_THREAD_BUFFERS - 1);
156     else
157       go = (used > 0);
158     if(go) {
159       /* At least one buffer is filled.  We release the lock while playing so
160        * that more collection can go on. */
161       struct uaudio_buffer *const b = &uaudio_buffers[uaudio_play_buffer];
162       pthread_mutex_unlock(&uaudio_thread_lock);
163       //fprintf(stderr, "P%d.", uaudio_play_buffer);
164       size_t played = 0;
165       while(played < b->nsamples)
166         played += uaudio_thread_play_callback((char *)b->samples
167                                               + played * uaudio_sample_size,
168                                               b->nsamples - played);
169       pthread_mutex_lock(&uaudio_thread_lock);
170       /* Move to next buffer */
171       uaudio_play_buffer = (1 + uaudio_play_buffer) % UAUDIO_THREAD_BUFFERS;
172       /* Awaken collector */
173       pthread_cond_broadcast(&uaudio_thread_cond);
174       resync = 0;
175     } else {
176       /* Insufficient data to play, wait for collector */
177       pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
178       /* (Still) re-synchronizing */
179       resync = 1;
180     }
181   }
182   pthread_mutex_unlock(&uaudio_thread_lock);
183   return NULL;
184 }
185
186 /** @brief Create background threads for audio processing 
187  * @param callback Callback to collect audio data
188  * @param userdata Passed to @p callback
189  * @param playcallback Callback to play audio data
190  * @param min Minimum number of samples to play in a chunk
191  * @param max Maximum number of samples to play in a chunk
192  *
193  * @p callback will be called multiple times in quick succession if necessary
194  * to gather at least @p min samples.  Equally @p playcallback may be called
195  * repeatedly in quick succession to play however much was received in a single
196  * chunk.
197  */
198 void uaudio_thread_start(uaudio_callback *callback,
199                          void *userdata,
200                          uaudio_playcallback *playcallback,
201                          size_t min,
202                          size_t max) {
203   int e;
204   uaudio_thread_collect_callback = callback;
205   uaudio_thread_userdata = userdata;
206   uaudio_thread_play_callback = playcallback;
207   uaudio_thread_min = min;
208   uaudio_thread_max = max;
209   uaudio_thread_started = 1;
210   for(int n = 0; n < UAUDIO_THREAD_BUFFERS; ++n)
211     uaudio_buffers[n].samples = xcalloc(uaudio_thread_max, uaudio_sample_size);
212   uaudio_collect_buffer = uaudio_play_buffer = 0;
213   if((e = pthread_create(&uaudio_collect_thread,
214                          NULL,
215                          uaudio_collect_thread_fn,
216                          NULL)))
217     fatal(e, "pthread_create");
218   if((e = pthread_create(&uaudio_play_thread,
219                          NULL,
220                          uaudio_play_thread_fn,
221                          NULL)))
222     fatal(e, "pthread_create");
223 }
224
225 /** @brief Shut down background threads for audio processing */
226 void uaudio_thread_stop(void) {
227   void *result;
228
229   pthread_mutex_lock(&uaudio_thread_lock);
230   uaudio_thread_activated = 0;
231   uaudio_thread_started = 0;
232   pthread_cond_broadcast(&uaudio_thread_cond);
233   pthread_mutex_unlock(&uaudio_thread_lock);
234   pthread_join(uaudio_collect_thread, &result);
235   pthread_join(uaudio_play_thread, &result);
236   for(int n = 0; n < UAUDIO_THREAD_BUFFERS; ++n)
237     xfree(uaudio_buffers[n].samples);
238 }
239
240 /** @brief Activate audio output */
241 void uaudio_thread_activate(void) {
242   pthread_mutex_lock(&uaudio_thread_lock);
243   uaudio_thread_activated = 1;
244   pthread_cond_broadcast(&uaudio_thread_cond);
245   while(!uaudio_thread_collecting)
246     pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
247   pthread_mutex_unlock(&uaudio_thread_lock);
248 }
249
250 /** @brief Deactivate audio output */
251 void uaudio_thread_deactivate(void) {
252   pthread_mutex_lock(&uaudio_thread_lock);
253   uaudio_thread_activated = 0;
254   pthread_cond_broadcast(&uaudio_thread_cond);
255
256   while(uaudio_thread_collecting || uaudio_buffers_used())
257     pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
258   pthread_mutex_unlock(&uaudio_thread_lock);
259 }
260
261 /*
262 Local Variables:
263 c-basic-offset:2
264 comment-column:40
265 fill-column:79
266 indent-tabs-mode:nil
267 End:
268 */