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