Replace manual option handling by use of glib
[lcid-xwax.git] / alsa.c
blob3177106adf8ba94bd94cfc914d57ea8867a7e2e3
1 /*
2 * Copyright (C) 2008 Mark Hills <mark@pogo.org.uk>
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License version 2 for more details.
13 * You should have received a copy of the GNU General Public License
14 * version 2 along with this program; if not, write to the Free
15 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16 * MA 02110-1301, USA.
20 #include <stdio.h>
21 #include <string.h>
22 #include <sys/poll.h>
23 #include <alsa/asoundlib.h>
25 #include "device.h"
26 #include "timecoder.h"
27 #include "player.h"
29 #define DEFAULT_ALSA_BUFFER 8 /* milliseconds */
30 static guint alsa_buffer = DEFAULT_ALSA_BUFFER;
32 /* This structure doesn't have corresponding functions to be an
33 * abstraction of the ALSA calls; it is merely a container for these
34 * variables. */
36 struct alsa_pcm_t {
37 snd_pcm_t *pcm;
39 struct pollfd *pe;
40 int pe_count; /* number of pollfd entries */
42 signed short *buf;
43 snd_pcm_uframes_t period;
47 struct alsa_t {
48 struct alsa_pcm_t capture, playback;
52 static void alsa_error(const char *msg, int r)
54 fprintf(stderr, "ALSA %s: %s\n", msg, snd_strerror(r));
58 static int pcm_open(struct alsa_pcm_t *alsa, const char *device_name,
59 snd_pcm_stream_t stream, int buffer_time)
61 int r, dir;
62 unsigned int p;
63 size_t bytes;
64 snd_pcm_hw_params_t *hw_params;
66 r = snd_pcm_open(&alsa->pcm, device_name, stream, SND_PCM_NONBLOCK);
67 if(r < 0) {
68 alsa_error("open", r);
69 return -1;
72 r = snd_pcm_hw_params_malloc(&hw_params);
73 if(r < 0) {
74 alsa_error("hw_params_malloc", r);
75 return -1;
78 r = snd_pcm_hw_params_any(alsa->pcm, hw_params);
79 if(r < 0) {
80 alsa_error("hw_params_any", r);
81 return -1;
84 r = snd_pcm_hw_params_set_access(alsa->pcm, hw_params,
85 SND_PCM_ACCESS_RW_INTERLEAVED);
86 if(r < 0) {
87 alsa_error("hw_params_set_access", r);
88 return -1;
91 r = snd_pcm_hw_params_set_format(alsa->pcm, hw_params, SND_PCM_FORMAT_S16);
92 if(r < 0) {
93 alsa_error("hw_params_set_format", r);
94 fprintf(stderr, "16-bit signed format is not available. "
95 "You may need to use a 'plughw' device.\n");
96 return -1;
99 r = snd_pcm_hw_params_set_rate(alsa->pcm, hw_params, DEVICE_RATE, 0);
100 if(r < 0) {
101 alsa_error("hw_params_set_rate", r);
102 fprintf(stderr, "%dHz sample rate not available. You may need to use "
103 "a 'plughw' device.\n", DEVICE_RATE);
104 return -1;
107 r = snd_pcm_hw_params_set_channels(alsa->pcm, hw_params, DEVICE_CHANNELS);
108 if(r < 0) {
109 alsa_error("hw_params_set_channels", r);
110 fprintf(stderr, "%d channel audio not available on this device.\n",
111 DEVICE_CHANNELS);
112 return -1;
115 p = buffer_time * 1000; /* microseconds */
116 dir = -1;
117 r = snd_pcm_hw_params_set_buffer_time_max(alsa->pcm, hw_params, &p, &dir);
118 if(r < 0) {
119 alsa_error("hw_params_set_buffer_time_max", r);
120 fprintf(stderr, "Buffer of %dms may be too small for this hardware.\n",
121 buffer_time);
122 return -1;
125 r = snd_pcm_hw_params(alsa->pcm, hw_params);
126 if(r < 0) {
127 alsa_error("hw_params", r);
128 return -1;
131 r = snd_pcm_hw_params_get_period_size(hw_params, &alsa->period, &dir);
132 if(r < 0) {
133 alsa_error("get_period_size", r);
134 return -1;
137 snd_pcm_hw_params_free(hw_params);
139 bytes = alsa->period * DEVICE_CHANNELS * sizeof(signed short);
140 alsa->buf = malloc(bytes);
141 if(!alsa->buf) {
142 perror("malloc");
143 return -1;
146 /* snd_pcm_readi() returns uninitialised memory on first call,
147 * possibly caused by premature POLLIN. Keep valgrind happy. */
149 memset(alsa->buf, 0, bytes);
151 return 0;
155 static int pcm_close(struct alsa_pcm_t *alsa)
157 int r;
159 r = snd_pcm_close(alsa->pcm);
160 if(r < 0) {
161 alsa_error("close", r);
162 return -1;
164 free(alsa->buf);
166 return 0;
170 static int pcm_pollfds(struct alsa_pcm_t *alsa, struct pollfd *pe, int n)
172 int r, count;
174 count = snd_pcm_poll_descriptors_count(alsa->pcm);
175 if(count > n)
176 return -1;
178 if(count == 0)
179 alsa->pe = NULL;
180 else {
181 r = snd_pcm_poll_descriptors(alsa->pcm, pe, count);
183 if(r < 0) {
184 alsa_error("poll_descriptors", r);
185 return -1;
188 alsa->pe = pe;
191 alsa->pe_count = count;
192 return count;
196 static int pcm_revents(struct alsa_pcm_t *alsa, unsigned short *revents) {
197 int r;
199 r = snd_pcm_poll_descriptors_revents(alsa->pcm, alsa->pe, alsa->pe_count,
200 revents);
201 if(r < 0) {
202 alsa_error("poll_descriptors_revents", r);
203 return -1;
206 return 0;
211 /* Start the audio device capture and playback */
213 static int start(struct device_t *dv)
215 int r;
216 struct alsa_t *alsa = (struct alsa_t*)dv->local;
218 r = snd_pcm_start(alsa->capture.pcm);
219 if(r < 0) {
220 alsa_error("start", r);
221 return -1;
224 return 0;
228 /* Register this device's interest in a set of pollfd file
229 * descriptors */
231 static int pollfds(struct device_t *dv, struct pollfd *pe, int pe_size)
233 int total, r;
234 struct alsa_t *alsa = (struct alsa_t*)dv->local;
236 total = 0;
238 r = pcm_pollfds(&alsa->capture, pe, pe_size);
239 if(r < 0)
240 return -1;
242 pe += r;
243 pe_size -= r;
244 total += r;
246 r = pcm_pollfds(&alsa->playback, pe, pe_size);
247 if(r < 0)
248 return -1;
250 total += r;
252 return total;
256 /* Collect audio from the player and push it into the device's buffer,
257 * for playback */
259 static int playback(struct device_t *dv)
261 int r;
262 struct alsa_t *alsa = (struct alsa_t*)dv->local;
264 if(dv->player)
265 player_collect(dv->player, alsa->playback.buf, alsa->playback.period);
266 else {
267 memset(alsa->playback.buf, 0,
268 alsa->playback.period * DEVICE_CHANNELS * sizeof(short));
271 r = snd_pcm_writei(alsa->playback.pcm, alsa->playback.buf,
272 alsa->playback.period);
273 if(r < 0)
274 return r;
276 if(r < alsa->playback.period) {
277 fprintf(stderr, "alsa: playback underrun %d/%ld.\n", r,
278 alsa->playback.period);
281 return 0;
285 /* Pull audio from the device's buffer for capture, and pass it
286 * through to the timecoder */
288 static int capture(struct device_t *dv)
290 int r;
291 struct alsa_t *alsa = (struct alsa_t*)dv->local;
293 r = snd_pcm_readi(alsa->capture.pcm, alsa->capture.buf,
294 alsa->capture.period);
295 if(r < 0)
296 return r;
298 if(r < alsa->capture.period) {
299 fprintf(stderr, "alsa: capture underrun %d/%ld.\n",
300 r, alsa->capture.period);
303 if(dv->timecoder)
304 timecoder_submit(dv->timecoder, alsa->capture.buf, r);
306 return 0;
310 /* After poll() has returned, instruct a device to do all it can at
311 * the present time. Return zero if success, otherwise -1 */
313 static int handle(struct device_t *dv)
315 int r;
316 unsigned short revents;
317 struct alsa_t *alsa = (struct alsa_t*)dv->local;
319 /* Check input buffer for timecode capture */
321 r = pcm_revents(&alsa->capture, &revents);
322 if(r < 0)
323 return -1;
325 if(revents & POLLIN) {
326 r = capture(dv);
328 if(r < 0) {
329 if(r == -EPIPE) {
330 fputs("ALSA: capture xrun.\n", stderr);
331 r = snd_pcm_prepare(alsa->capture.pcm);
332 if(r < 0) {
333 alsa_error("prepare", r);
334 return -1;
337 r = snd_pcm_start(alsa->capture.pcm);
338 if(r < 0) {
339 alsa_error("start", r);
340 return -1;
343 } else {
344 alsa_error("capture", r);
345 return -1;
350 /* Check the output buffer for playback */
352 r = pcm_revents(&alsa->playback, &revents);
353 if(r < 0)
354 return -1;
356 if(revents & POLLOUT) {
357 r = playback(dv);
359 if(r < 0) {
360 if(r == -EPIPE) {
361 fputs("ALSA: playback xrun.\n", stderr);
363 r = snd_pcm_prepare(alsa->playback.pcm) < 0;
364 if(r < 0) {
365 alsa_error("prepare", r);
366 return -1;
369 /* The device starts when data is written. POLLOUT
370 * events are generated in prepared state. */
372 } else {
373 alsa_error("playback", r);
374 return -1;
379 return 0;
383 /* Close ALSA device and clear any allocations */
385 static int clear(struct device_t *dv)
387 struct alsa_t *alsa = (struct alsa_t*)dv->local;
389 pcm_close(&alsa->capture);
390 pcm_close(&alsa->playback);
391 free(dv->local);
393 return 0;
397 /* Open ALSA device. Do not operate on audio until device_start() */
399 int alsa_init(struct device_t *dv, const char *device_name, int buffer_time)
401 struct alsa_t *alsa;
403 alsa = malloc(sizeof(struct alsa_t));
404 if(!alsa) {
405 perror("malloc");
406 return -1;
409 if(pcm_open(&alsa->capture, device_name, SND_PCM_STREAM_CAPTURE,
410 buffer_time) < 0)
412 fputs("Failed to open device for capture.\n", stderr);
413 goto fail;
416 if(pcm_open(&alsa->playback, device_name, SND_PCM_STREAM_PLAYBACK,
417 buffer_time) < 0)
419 fputs("Failed to open device for playback.\n", stderr);
420 goto fail;
423 dv->local = alsa;
425 dv->pollfds = pollfds;
426 dv->handle = handle;
427 dv->start = start;
428 dv->stop = NULL;
429 dv->clear = clear;
431 return 0;
433 fail:
434 free(alsa);
435 return -1;
438 static gboolean parse_alsa_device(const gchar *option_name, const gchar *value, gpointer data, GError **error)
440 struct device_t* device = createDevice(error);
442 if(!device)
443 return FALSE;
445 return connectAudio(device, alsa_init(device, value, alsa_buffer));
448 static GOptionEntry alsaOptions[] =
450 { "alsa_device", 'a', 0, G_OPTION_ARG_CALLBACK, &parse_alsa_device, "Build a deck connected to ALSA audio device", "hw:1,0"},
451 // Set size of ALSA buffer for subsequence devices
452 { "buffer_time", 'm', 0, G_OPTION_ARG_INT, &alsa_buffer, "Buffer size", NULL},
453 { NULL }
456 GOptionGroup* get_alsa_option_group()
458 GOptionGroup* group = g_option_group_new
460 "alsa",
461 "ALSA device options",
462 "Show ALSA help options",
463 NULL,
464 NULL
467 if(alsaOptions[1].arg_description == NULL)
468 alsaOptions[1].arg_description = g_strdup_printf("%u", DEFAULT_ALSA_BUFFER);
470 g_option_group_add_entries(group, alsaOptions);
471 return group;