alsa.c 10.4 KB
Newer Older
Mark Hills's avatar
Mark Hills committed
1
/*
Mark Hills's avatar
Mark Hills committed
2
 * Copyright (C) 2014 Mark Hills <mark@xwax.org>
Mark Hills's avatar
Mark Hills committed
3
4
5
6
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2, as published by the Free Software Foundation.
Mark Hills's avatar
Mark Hills committed
7
 *
Mark Hills's avatar
Mark Hills committed
8
9
10
11
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details.
Mark Hills's avatar
Mark Hills committed
12
 *
Mark Hills's avatar
Mark Hills committed
13
14
15
16
17
18
19
 * You should have received a copy of the GNU General Public License
 * version 2 along with this program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 */

Mark Hills's avatar
Mark Hills committed
20
#include <stdbool.h>
Mark Hills's avatar
Mark Hills committed
21
22
23
24
25
#include <stdio.h>
#include <string.h>
#include <sys/poll.h>
#include <alsa/asoundlib.h>

26
#include "alsa.h"
Mark Hills's avatar
Mark Hills committed
27
28
29
30
31
32


/* This structure doesn't have corresponding functions to be an
 * abstraction of the ALSA calls; it is merely a container for these
 * variables. */

Mark Hills's avatar
Mark Hills committed
33
struct alsa_pcm {
Mark Hills's avatar
Mark Hills committed
34
35
36
    snd_pcm_t *pcm;

    struct pollfd *pe;
37
    size_t pe_count; /* number of pollfd entries */
Mark Hills's avatar
Mark Hills committed
38
39
40

    signed short *buf;
    snd_pcm_uframes_t period;
41
    int rate;
Mark Hills's avatar
Mark Hills committed
42
43
44
};


Mark Hills's avatar
Mark Hills committed
45
struct alsa {
Mark Hills's avatar
Mark Hills committed
46
    struct alsa_pcm capture, playback;
Mark Hills's avatar
Mark Hills committed
47
48
49
};


Mark Hills's avatar
Mark Hills committed
50
static void alsa_error(const char *msg, int r)
Mark Hills's avatar
Mark Hills committed
51
{
Mark Hills's avatar
Mark Hills committed
52
    fprintf(stderr, "ALSA %s: %s\n", msg, snd_strerror(r));
Mark Hills's avatar
Mark Hills committed
53
54
55
}


Mark Hills's avatar
Mark Hills committed
56
57
58
59
60
61
62
63
64
65
66
static bool chk(const char *s, int r)
{
    if (r < 0) {
        alsa_error(s, r);
        return false;
    } else {
        return true;
    }
}


Mark Hills's avatar
Mark Hills committed
67
static int pcm_open(struct alsa_pcm *alsa, const char *device_name,
68
                    snd_pcm_stream_t stream, int rate, int buffer_time)
Mark Hills's avatar
Mark Hills committed
69
{
70
71
    int r, dir;
    unsigned int p;
72
    size_t bytes;
Mark Hills's avatar
Mark Hills committed
73
74
75
    snd_pcm_hw_params_t *hw_params;
    
    r = snd_pcm_open(&alsa->pcm, device_name, stream, SND_PCM_NONBLOCK);
Mark Hills's avatar
Mark Hills committed
76
    if (!chk("open", r))
Mark Hills's avatar
Mark Hills committed
77
        return -1;
Mark Hills's avatar
Mark Hills committed
78

79
    snd_pcm_hw_params_alloca(&hw_params);
Mark Hills's avatar
Mark Hills committed
80

Mark Hills's avatar
Mark Hills committed
81
    r = snd_pcm_hw_params_any(alsa->pcm, hw_params);
Mark Hills's avatar
Mark Hills committed
82
    if (!chk("hw_params_any", r))
Mark Hills's avatar
Mark Hills committed
83
84
85
86
        return -1;
    
    r = snd_pcm_hw_params_set_access(alsa->pcm, hw_params,
                                     SND_PCM_ACCESS_RW_INTERLEAVED);
Mark Hills's avatar
Mark Hills committed
87
    if (!chk("hw_params_set_access", r))
Mark Hills's avatar
Mark Hills committed
88
89
        return -1;
    
90
    r = snd_pcm_hw_params_set_format(alsa->pcm, hw_params, SND_PCM_FORMAT_S16);
Mark Hills's avatar
Mark Hills committed
91
    if (!chk("hw_params_set_format", r)) {
92
93
        fprintf(stderr, "16-bit signed format is not available. "
                "You may need to use a 'plughw' device.\n");
Mark Hills's avatar
Mark Hills committed
94
95
96
        return -1;
    }

97
    r = snd_pcm_hw_params_set_rate(alsa->pcm, hw_params, rate, 0);
Mark Hills's avatar
Mark Hills committed
98
    if (!chk("hw_params_set_rate", r )) {
Mark Hills's avatar
Mark Hills committed
99
        fprintf(stderr, "%dHz sample rate not available. You may need to use "
100
                "a 'plughw' device.\n", rate);
Mark Hills's avatar
Mark Hills committed
101
102
        return -1;
    }
103
104
    alsa->rate = rate;

Mark Hills's avatar
Mark Hills committed
105
    r = snd_pcm_hw_params_set_channels(alsa->pcm, hw_params, DEVICE_CHANNELS);
Mark Hills's avatar
Mark Hills committed
106
    if (!chk("hw_params_set_channels", r)) {
Mark Hills's avatar
Mark Hills committed
107
108
        fprintf(stderr, "%d channel audio not available on this device.\n",
                DEVICE_CHANNELS);
Mark Hills's avatar
Mark Hills committed
109
110
111
        return -1;
    }

112
    p = buffer_time * 1000; /* microseconds */
Mark Hills's avatar
Mark Hills committed
113
    dir = -1;
114
115
    r = snd_pcm_hw_params_set_buffer_time_near(alsa->pcm, hw_params, &p, &dir);
    if (!chk("hw_params_set_buffer_time_near", r)) {
Mark Hills's avatar
Mark Hills committed
116
117
        fprintf(stderr, "Buffer of %dms may be too small for this hardware.\n",
                buffer_time);
118
119
        return -1;
    }
Mark Hills's avatar
Mark Hills committed
120

121
122
123
    p = 2; /* double buffering */
    dir = 1;
    r = snd_pcm_hw_params_set_periods_min(alsa->pcm, hw_params, &p, &dir);
Mark Hills's avatar
Mark Hills committed
124
    if (!chk("hw_params_set_periods_min", r)) {
125
126
127
128
129
        fprintf(stderr, "Buffer of %dms may be too small for this hardware.\n",
                buffer_time);
        return -1;
    }

Mark Hills's avatar
Mark Hills committed
130
    r = snd_pcm_hw_params(alsa->pcm, hw_params);
Mark Hills's avatar
Mark Hills committed
131
    if (!chk("hw_params", r))
Mark Hills's avatar
Mark Hills committed
132
133
134
        return -1;
    
    r = snd_pcm_hw_params_get_period_size(hw_params, &alsa->period, &dir);
Mark Hills's avatar
Mark Hills committed
135
    if (!chk("get_period_size", r))
Mark Hills's avatar
Mark Hills committed
136
137
        return -1;

138
139
    bytes = alsa->period * DEVICE_CHANNELS * sizeof(signed short);
    alsa->buf = malloc(bytes);
Mark Hills's avatar
Mark Hills committed
140
    if (!alsa->buf) {
Mark Hills's avatar
Mark Hills committed
141
142
143
144
        perror("malloc");
        return -1;
    }

145
146
147
148
149
    /* snd_pcm_readi() returns uninitialised memory on first call,
     * possibly caused by premature POLLIN. Keep valgrind happy. */

    memset(alsa->buf, 0, bytes);

Mark Hills's avatar
Mark Hills committed
150
151
152
153
    return 0;
}


Mark Hills's avatar
Mark Hills committed
154
static void pcm_close(struct alsa_pcm *alsa)
Mark Hills's avatar
Mark Hills committed
155
{
156
157
    if (snd_pcm_close(alsa->pcm) < 0)
        abort();
Mark Hills's avatar
Mark Hills committed
158
159
160
161
    free(alsa->buf);
}


Mark Hills's avatar
Mark Hills committed
162
static ssize_t pcm_pollfds(struct alsa_pcm *alsa, struct pollfd *pe,
163
			   size_t z)
Mark Hills's avatar
Mark Hills committed
164
165
166
167
{
    int r, count;

    count = snd_pcm_poll_descriptors_count(alsa->pcm);
Mark Hills's avatar
Mark Hills committed
168
    if (count > z)
Mark Hills's avatar
Mark Hills committed
169
170
        return -1;

Mark Hills's avatar
Mark Hills committed
171
    if (count == 0)
Mark Hills's avatar
Mark Hills committed
172
173
174
        alsa->pe = NULL;
    else {
        r = snd_pcm_poll_descriptors(alsa->pcm, pe, count);
Mark Hills's avatar
Mark Hills committed
175
        if (r < 0) {
Mark Hills's avatar
Mark Hills committed
176
            alsa_error("poll_descriptors", r);
Mark Hills's avatar
Mark Hills committed
177
178
179
180
181
182
183
184
185
186
            return -1;
        }
        alsa->pe = pe;
    }

    alsa->pe_count = count;
    return count;
}


Mark Hills's avatar
Mark Hills committed
187
static int pcm_revents(struct alsa_pcm *alsa, unsigned short *revents) {
Mark Hills's avatar
Mark Hills committed
188
189
190
191
    int r;

    r = snd_pcm_poll_descriptors_revents(alsa->pcm, alsa->pe, alsa->pe_count,
                                         revents);
Mark Hills's avatar
Mark Hills committed
192
    if (r < 0) {
Mark Hills's avatar
Mark Hills committed
193
        alsa_error("poll_descriptors_revents", r);
Mark Hills's avatar
Mark Hills committed
194
195
196
197
198
199
200
201
202
203
        return -1;
    }
    
    return 0;
}



/* Start the audio device capture and playback */

Mark Hills's avatar
Mark Hills committed
204
static void start(struct device *dv)
Mark Hills's avatar
Mark Hills committed
205
{
Mark Hills's avatar
Mark Hills committed
206
    struct alsa *alsa = (struct alsa*)dv->local;
207

208
209
    if (snd_pcm_start(alsa->capture.pcm) < 0)
        abort();
Mark Hills's avatar
Mark Hills committed
210
211
212
213
214
215
}


/* Register this device's interest in a set of pollfd file
 * descriptors */

Mark Hills's avatar
Mark Hills committed
216
static ssize_t pollfds(struct device *dv, struct pollfd *pe, size_t z)
Mark Hills's avatar
Mark Hills committed
217
218
{
    int total, r;
Mark Hills's avatar
Mark Hills committed
219
    struct alsa *alsa = (struct alsa*)dv->local;
Mark Hills's avatar
Mark Hills committed
220
221
222

    total = 0;

223
    r = pcm_pollfds(&alsa->capture, pe, z);
Mark Hills's avatar
Mark Hills committed
224
    if (r < 0)
Mark Hills's avatar
Mark Hills committed
225
226
227
        return -1;
    
    pe += r;
228
    z -= r;
Mark Hills's avatar
Mark Hills committed
229
230
    total += r;
    
231
    r = pcm_pollfds(&alsa->playback, pe, z);
Mark Hills's avatar
Mark Hills committed
232
    if (r < 0)
Mark Hills's avatar
Mark Hills committed
233
234
235
236
237
238
239
240
241
242
243
        return -1;
    
    total += r;
    
    return total;
}
    

/* Collect audio from the player and push it into the device's buffer,
 * for playback */

Mark Hills's avatar
Mark Hills committed
244
static int playback(struct device *dv)
Mark Hills's avatar
Mark Hills committed
245
246
{
    int r;
Mark Hills's avatar
Mark Hills committed
247
    struct alsa *alsa = (struct alsa*)dv->local;
Mark Hills's avatar
Mark Hills committed
248

249
    device_collect(dv, alsa->playback.buf, alsa->playback.period);
Mark Hills's avatar
Mark Hills committed
250
251
252

    r = snd_pcm_writei(alsa->playback.pcm, alsa->playback.buf,
                       alsa->playback.period);
Mark Hills's avatar
Mark Hills committed
253
    if (r < 0)
Mark Hills's avatar
Mark Hills committed
254
255
        return r;
        
Mark Hills's avatar
Mark Hills committed
256
    if (r < alsa->playback.period) {
257
        fprintf(stderr, "alsa: playback underrun %d/%ld.\n", r,
Mark Hills's avatar
Mark Hills committed
258
                alsa->playback.period);
259
    }
Mark Hills's avatar
Mark Hills committed
260
261
262
263
264
265
266
267

    return 0;
}


/* Pull audio from the device's buffer for capture, and pass it
 * through to the timecoder */

Mark Hills's avatar
Mark Hills committed
268
static int capture(struct device *dv)
Mark Hills's avatar
Mark Hills committed
269
270
{
    int r;
Mark Hills's avatar
Mark Hills committed
271
    struct alsa *alsa = (struct alsa*)dv->local;
Mark Hills's avatar
Mark Hills committed
272
273
274

    r = snd_pcm_readi(alsa->capture.pcm, alsa->capture.buf,
                      alsa->capture.period);
Mark Hills's avatar
Mark Hills committed
275
    if (r < 0)
Mark Hills's avatar
Mark Hills committed
276
277
        return r;
    
Mark Hills's avatar
Mark Hills committed
278
    if (r < alsa->capture.period) {
279
        fprintf(stderr, "alsa: capture underrun %d/%ld.\n",
Mark Hills's avatar
Mark Hills committed
280
281
                r, alsa->capture.period);
    }
282
283

    device_submit(dv, alsa->capture.buf, r);
Mark Hills's avatar
Mark Hills committed
284
285
286
287
288
289
290
291

    return 0;
}


/* After poll() has returned, instruct a device to do all it can at
 * the present time. Return zero if success, otherwise -1 */

Mark Hills's avatar
Mark Hills committed
292
static int handle(struct device *dv)
Mark Hills's avatar
Mark Hills committed
293
294
295
{
    int r;
    unsigned short revents;
Mark Hills's avatar
Mark Hills committed
296
    struct alsa *alsa = (struct alsa*)dv->local;
Mark Hills's avatar
Mark Hills committed
297
298
299
300

    /* Check input buffer for timecode capture */
    
    r = pcm_revents(&alsa->capture, &revents);
Mark Hills's avatar
Mark Hills committed
301
    if (r < 0)
Mark Hills's avatar
Mark Hills committed
302
303
        return -1;
    
Mark Hills's avatar
Mark Hills committed
304
    if (revents & POLLIN) {
Mark Hills's avatar
Mark Hills committed
305
306
        r = capture(dv);
        
Mark Hills's avatar
Mark Hills committed
307
308
        if (r < 0) {
            if (r == -EPIPE) {
Mark Hills's avatar
Mark Hills committed
309
                fputs("ALSA: capture xrun.\n", stderr);
Mark Hills's avatar
Mark Hills committed
310
311

                r = snd_pcm_prepare(alsa->capture.pcm);
Mark Hills's avatar
Mark Hills committed
312
                if (r < 0) {
Mark Hills's avatar
Mark Hills committed
313
                    alsa_error("prepare", r);
314
                    return -1;
Mark Hills's avatar
Mark Hills committed
315
316
317
                }

                r = snd_pcm_start(alsa->capture.pcm);
Mark Hills's avatar
Mark Hills committed
318
                if (r < 0) {
Mark Hills's avatar
Mark Hills committed
319
                    alsa_error("start", r);
320
                    return -1;
Mark Hills's avatar
Mark Hills committed
321
322
323
                }

            } else {
Mark Hills's avatar
Mark Hills committed
324
                alsa_error("capture", r);
Mark Hills's avatar
Mark Hills committed
325
326
327
328
329
330
331
332
                return -1;
            }
        } 
    }
    
    /* Check the output buffer for playback */
    
    r = pcm_revents(&alsa->playback, &revents);
Mark Hills's avatar
Mark Hills committed
333
    if (r < 0)
Mark Hills's avatar
Mark Hills committed
334
335
        return -1;
    
Mark Hills's avatar
Mark Hills committed
336
    if (revents & POLLOUT) {
Mark Hills's avatar
Mark Hills committed
337
338
        r = playback(dv);
        
Mark Hills's avatar
Mark Hills committed
339
340
        if (r < 0) {
            if (r == -EPIPE) {
Mark Hills's avatar
Mark Hills committed
341
                fputs("ALSA: playback xrun.\n", stderr);
Mark Hills's avatar
Mark Hills committed
342
                
343
                r = snd_pcm_prepare(alsa->playback.pcm);
Mark Hills's avatar
Mark Hills committed
344
                if (r < 0) {
Mark Hills's avatar
Mark Hills committed
345
                    alsa_error("prepare", r);
346
                    return -1;
Mark Hills's avatar
Mark Hills committed
347
348
                }

349
350
                /* The device starts when data is written. POLLOUT
                 * events are generated in prepared state. */
Mark Hills's avatar
Mark Hills committed
351
352

            } else {
Mark Hills's avatar
Mark Hills committed
353
                alsa_error("playback", r);
Mark Hills's avatar
Mark Hills committed
354
355
356
357
358
359
360
361
362
                return -1;
            }
        }
    }

    return 0;
}


Mark Hills's avatar
Mark Hills committed
363
static unsigned int sample_rate(struct device *dv)
364
{
Mark Hills's avatar
Mark Hills committed
365
    struct alsa *alsa = (struct alsa*)dv->local;
366
367
368
369
370

    return alsa->capture.rate;
}


Mark Hills's avatar
Mark Hills committed
371
372
/* Close ALSA device and clear any allocations */

Mark Hills's avatar
Mark Hills committed
373
static void clear(struct device *dv)
Mark Hills's avatar
Mark Hills committed
374
{
Mark Hills's avatar
Mark Hills committed
375
    struct alsa *alsa = (struct alsa*)dv->local;
Mark Hills's avatar
Mark Hills committed
376

Mark Hills's avatar
Mark Hills committed
377
378
    pcm_close(&alsa->capture);
    pcm_close(&alsa->playback);
Mark Hills's avatar
Mark Hills committed
379
380
381
382
    free(dv->local);
}


383
static struct device_ops alsa_ops = {
384
385
    .pollfds = pollfds,
    .handle = handle,
386
    .sample_rate = sample_rate,
387
388
389
390
391
    .start = start,
    .clear = clear
};


Mark Hills's avatar
Mark Hills committed
392
393
/* Open ALSA device. Do not operate on audio until device_start() */

Mark Hills's avatar
Mark Hills committed
394
int alsa_init(struct device *dv, const char *device_name,
395
              int rate, int buffer_time)
Mark Hills's avatar
Mark Hills committed
396
{
Mark Hills's avatar
Mark Hills committed
397
    struct alsa *alsa;
Mark Hills's avatar
Mark Hills committed
398

Mark Hills's avatar
Mark Hills committed
399
400
    alsa = malloc(sizeof *alsa);
    if (alsa == NULL) {
Mark Hills's avatar
Mark Hills committed
401
402
403
404
        perror("malloc");
        return -1;
    }

Mark Hills's avatar
Mark Hills committed
405
    if (pcm_open(&alsa->capture, device_name, SND_PCM_STREAM_CAPTURE,
406
                rate, buffer_time) < 0)
407
    {
Mark Hills's avatar
Mark Hills committed
408
409
410
411
        fputs("Failed to open device for capture.\n", stderr);
        goto fail;
    }
    
Mark Hills's avatar
Mark Hills committed
412
    if (pcm_open(&alsa->playback, device_name, SND_PCM_STREAM_PLAYBACK,
413
                rate, buffer_time) < 0)
414
    {
Mark Hills's avatar
Mark Hills committed
415
        fputs("Failed to open device for playback.\n", stderr);
Mark Hills's avatar
Mark Hills committed
416
        goto fail_capture;
Mark Hills's avatar
Mark Hills committed
417
418
    }

419
    device_init(dv, &alsa_ops);
Mark Hills's avatar
Mark Hills committed
420
421
422
423
    dv->local = alsa;

    return 0;

Mark Hills's avatar
Mark Hills committed
424
425
 fail_capture:
    pcm_close(&alsa->capture);
Mark Hills's avatar
Mark Hills committed
426
427
428
429
 fail:
    free(alsa);
    return -1;
}
Mark Hills's avatar
Mark Hills committed
430
431
432
433
434
435
436
437
438
439
440
441
442


/* ALSA caches information when devices are open. Provide a call
 * to clear these caches so that valgrind output is clean. */

void alsa_clear_config_cache(void)
{
    int r;

    r = snd_config_update_free_global();
    if (r < 0)
        alsa_error("config_update_free_global", r);
}