chiark / gitweb /
Doxygen for C test infrastructure
[disorder] / lib / alsabg.c
CommitLineData
4dadf1a2
RK
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2008 Richard Kettlewell
4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
4dadf1a2 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
4dadf1a2 8 * (at your option) any later version.
e7eb3a27
RK
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 *
4dadf1a2 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
4dadf1a2
RK
17 */
18/** @file alsabg.c
19 * @brief Background-thread interface to ALSA
20 */
21
05b75f8d 22#include "common.h"
4dadf1a2
RK
23
24#if HAVE_ALSA_ASOUNDLIB_H
4dadf1a2
RK
25#include <alsa/asoundlib.h>
26#include <pthread.h>
27
28#include "alsabg.h"
29#include "log.h"
30
31/** @brief Output handle */
32static snd_pcm_t *pcm;
33
34/** @brief Called to get audio data */
35static alsa_bg_supply *supplyfn;
36
37static pthread_t alsa_bg_collect_tid, alsa_bg_play_tid;
38
39/** @brief Set to shut down the background threads */
40static int alsa_bg_shutdown = 0;
41
42/** @brief Number of channels (samples per frame) */
43#define CHANNELS 2
44
45/** @brief Number of bytes per samples */
46#define BYTES_PER_SAMPLE 2
47
48/** @brief Number of bytes per frame */
49#define BYTES_PER_FRAME (CHANNELS * BYTES_PER_SAMPLE)
50
51/** @brief Buffer size in bytes */
52#define BUFFER_BYTES 65536
53
54/** @brief Buffer size in frames */
55#define BUFFER_FRAMES (BUFFER_BYTES / BYTES_PER_FRAME)
56
57/** @brief Buffer size in samples */
58#define BUFFER_SAMPLES (BUFFER_BYTES / BYTES_PER_SAMPLE)
59
60/** @brief Audio buffer */
61static uint8_t alsa_bg_buffer[BUFFER_BYTES];
62
63/** @brief First playable byte in audio buffer */
64static unsigned alsa_bg_start;
65
66/** @brief Number of playable bytes in audio buffer */
67static unsigned alsa_bg_count;
68
69/** @brief Current enable status */
70static int alsa_bg_enabled;
71
72/** @brief Lock protecting audio buffer pointers */
73static pthread_mutex_t alsa_bg_lock = PTHREAD_MUTEX_INITIALIZER;
74
75/** @brief Signaled when buffer contents changes */
76static pthread_cond_t alsa_bg_cond = PTHREAD_COND_INITIALIZER;
77
78/** @brief Call a pthread_ function and fatal() on exit */
79#define ep(x) do { \
80 int prc; \
81 \
82 if((prc = (x))) \
83 fatal(prc, "%s", #x); \
84} while(0)
85
86/** @brief Data collection thread
87 *
88 * This thread collects audio data to play and stores it in the ring
89 * buffer.
90 */
91static void *alsa_bg_collect(void attribute((unused)) *arg) {
92 unsigned avail_start, avail_count;
93 int count;
94
95 ep(pthread_mutex_lock(&alsa_bg_lock));
96 for(;;) {
97 /* If we're shutting down, quit straight away */
98 if(alsa_bg_shutdown)
99 break;
100 /* While we're disabled or the buffer is full, just wait */
101 if(!alsa_bg_enabled || alsa_bg_count == BUFFER_BYTES) {
102 ep(pthread_cond_wait(&alsa_bg_cond, &alsa_bg_lock));
103 continue;
104 }
105 /* Figure out where and how big the gap we can write into is */
106 avail_start = alsa_bg_start + alsa_bg_count;
107 if(avail_start < BUFFER_BYTES)
108 avail_count = BUFFER_BYTES - avail_start;
109 else {
110 avail_start %= BUFFER_BYTES;
111 avail_count = alsa_bg_start - avail_start;
112 }
113 assert(avail_start < BUFFER_BYTES);
114 assert(avail_count <= BUFFER_BYTES);
115 assert(avail_count + alsa_bg_count <= BUFFER_BYTES);
116 ep(pthread_mutex_unlock(&alsa_bg_lock));
117 count = supplyfn(alsa_bg_buffer + avail_start,
118 avail_count / BYTES_PER_SAMPLE);
119 ep(pthread_mutex_lock(&alsa_bg_lock));
120 alsa_bg_count += count * BYTES_PER_SAMPLE;
121 assert(alsa_bg_start < BUFFER_BYTES);
122 assert(alsa_bg_count <= BUFFER_BYTES);
123 ep(pthread_cond_signal(&alsa_bg_cond));
124 }
125 ep(pthread_mutex_unlock(&alsa_bg_lock));
126 return 0;
127}
128
129/** @brief Playback thread
130 *
131 * This thread reads audio data out of the ring buffer and plays it back
132 */
133static void *alsa_bg_play(void attribute((unused)) *arg) {
134 int prepared = 1, err;
135 int start, nbytes, nframes, rframes;
136
137 ep(pthread_mutex_lock(&alsa_bg_lock));
138 for(;;) {
139 /* If we're shutting down, quit straight away */
140 if(alsa_bg_shutdown)
141 break;
142 /* Wait for some data to be available. (If we are marked disabled
143 * we keep on playing what we've got.) */
144 if(alsa_bg_count == 0) {
145 if(prepared) {
146 if((err = snd_pcm_drain(pcm)))
147 fatal(0, "snd_pcm_drain: %d", err);
148 prepared = 0;
149 }
150 ep(pthread_cond_wait(&alsa_bg_cond, &alsa_bg_lock));
151 continue;
152 }
153 /* Calculate how much we can play */
154 start = alsa_bg_start;
155 if(start + alsa_bg_count <= BUFFER_BYTES)
156 nbytes = alsa_bg_count;
157 else
158 nbytes = BUFFER_BYTES - start;
159 /* Limit how much of the buffer we play. The effect is that we return from
160 * _writei earlier, and therefore free up more buffer space to read fresh
161 * data into. /2 works fine, /4 is just conservative. /1 (i.e. abolishing
162 * the heuristic) produces noticably noisy output. */
163 if(nbytes > BUFFER_BYTES / 4)
164 nbytes = BUFFER_BYTES / 4;
165 assert((unsigned)nbytes <= alsa_bg_count);
166 nframes = nbytes / BYTES_PER_FRAME;
167 ep(pthread_mutex_unlock(&alsa_bg_lock));
168 /* Make sure the PCM is prepared */
169 if(!prepared) {
170 if((err = snd_pcm_prepare(pcm)))
171 fatal(0, "snd_pcm_prepare: %d", err);
172 prepared = 1;
173 }
174 /* Play what we can */
175 rframes = snd_pcm_writei(pcm, alsa_bg_buffer + start, nframes);
176 ep(pthread_mutex_lock(&alsa_bg_lock));
177 if(rframes < 0) {
4dadf1a2
RK
178 switch(rframes) {
179 case -EPIPE:
2956aeb4
RK
180 error(0, "underrun detected");
181 if((err = snd_pcm_prepare(pcm)))
182 fatal(0, "snd_pcm_prepare: %d", err);
4dadf1a2 183 break;
2956aeb4
RK
184 default:
185 fatal(0, "snd_pcm_writei: %d", rframes);
4dadf1a2
RK
186 }
187 } else {
188 const int rbytes = rframes * BYTES_PER_FRAME;
189 /*fprintf(stderr, "%5d -> %5d\n", nbytes, rbytes);*/
190 /* Update the buffer pointers */
191 alsa_bg_count -= rbytes;
192 alsa_bg_start += rbytes;
193 if(alsa_bg_start >= BUFFER_BYTES)
194 alsa_bg_start -= BUFFER_BYTES;
195 assert(alsa_bg_start < BUFFER_BYTES);
196 assert(alsa_bg_count <= BUFFER_BYTES);
197 /* Let the collector know we've opened up some space */
198 ep(pthread_cond_signal(&alsa_bg_cond));
199 }
200 }
201 ep(pthread_mutex_unlock(&alsa_bg_lock));
202 return 0;
203}
204
205/** @brief Enable ALSA play */
206void alsa_bg_enable(void) {
207 ep(pthread_mutex_lock(&alsa_bg_lock));
208 alsa_bg_enabled = 1;
209 ep(pthread_cond_broadcast(&alsa_bg_cond));
210 ep(pthread_mutex_unlock(&alsa_bg_lock));
211}
212
213/** @brief Disable ALSA play */
214void alsa_bg_disable(void) {
215 ep(pthread_mutex_lock(&alsa_bg_lock));
216 alsa_bg_enabled = 0;
217 ep(pthread_cond_broadcast(&alsa_bg_cond));
218 ep(pthread_mutex_unlock(&alsa_bg_lock));
219}
220
221/** @brief Initialize background ALSA playback
222 * @param device Target device or NULL to use default
223 * @param supply Function to call to get audio data to play
224 *
225 * Playback is not initially enabled; see alsa_bg_enable(). When playback is
226 * enabled, @p supply will be called in a background thread to request audio
227 * data. It should return in a timely manner, but playback happens from a
228 * further thread and delays in @p supply will not delay transfer of data to
229 * the sound device (provided it doesn't actually run out).
230 */
231void alsa_bg_init(const char *device,
232 alsa_bg_supply *supply) {
233 snd_pcm_hw_params_t *hwparams;
234 /* Only support one format for now */
235 const int sample_format = SND_PCM_FORMAT_S16_BE;
236 unsigned rate = 44100;
237 int err;
238
239 if((err = snd_pcm_open(&pcm,
240 device ? device : "default",
241 SND_PCM_STREAM_PLAYBACK,
242 0)))
243 fatal(0, "error from snd_pcm_open: %d", err);
244 /* Set up 'hardware' parameters */
245 snd_pcm_hw_params_alloca(&hwparams);
246 if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
247 fatal(0, "error from snd_pcm_hw_params_any: %d", err);
248 if((err = snd_pcm_hw_params_set_access(pcm, hwparams,
249 SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
250 fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
251 if((err = snd_pcm_hw_params_set_format(pcm, hwparams,
252 sample_format)) < 0)
253
254 fatal(0, "error from snd_pcm_hw_params_set_format (%d): %d",
255 sample_format, err);
256 if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0)
257 fatal(0, "error from snd_pcm_hw_params_set_rate (%d): %d",
258 rate, err);
259 if((err = snd_pcm_hw_params_set_channels(pcm, hwparams,
260 CHANNELS)) < 0)
261 fatal(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
262 CHANNELS, err);
263 if((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
264 fatal(0, "error calling snd_pcm_hw_params: %d", err);
265
266 /* Record the audio supply function */
267 supplyfn = supply;
268
269 /* Create the audio output thread */
270 alsa_bg_shutdown = 0;
271 alsa_bg_enabled = 0;
272 ep(pthread_create(&alsa_bg_collect_tid, 0, alsa_bg_collect, 0));
273 ep(pthread_create(&alsa_bg_play_tid, 0, alsa_bg_play, 0));
274}
275
276void alsa_bg_close(void) {
277 void *r;
278
279 /* Notify background threads that we're shutting down */
280 ep(pthread_mutex_lock(&alsa_bg_lock));
281 alsa_bg_enabled = 0;
282 alsa_bg_shutdown = 1;
283 ep(pthread_cond_signal(&alsa_bg_cond));
284 ep(pthread_mutex_unlock(&alsa_bg_lock));
285 /* Join background threads when they're done */
286 ep(pthread_join(alsa_bg_collect_tid, &r));
287 ep(pthread_join(alsa_bg_play_tid, &r));
288 /* Close audio device */
289 snd_pcm_close(pcm);
290 pcm = 0;
291}
292
293#endif
294
295/*
296Local Variables:
297c-basic-offset:2
298comment-column:40
299fill-column:79
300indent-tabs-mode:nil
301End:
302*/