blob: 1fab6f47214f7939ec732de211de31a23ab579c7 [file] [log] [blame]
Simon Wilson79d39652011-05-25 13:44:23 -07001/* pcm.c
2**
3** Copyright 2011, The Android Open Source Project
4**
5** Redistribution and use in source and binary forms, with or without
6** modification, are permitted provided that the following conditions are met:
7** * Redistributions of source code must retain the above copyright
8** notice, this list of conditions and the following disclaimer.
9** * Redistributions in binary form must reproduce the above copyright
10** notice, this list of conditions and the following disclaimer in the
11** documentation and/or other materials provided with the distribution.
12** * Neither the name of The Android Open Source Project nor the names of
13** its contributors may be used to endorse or promote products derived
14** from this software without specific prior written permission.
15**
16** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
17** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
20** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26** DAMAGE.
27*/
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <fcntl.h>
32#include <stdarg.h>
33#include <string.h>
34#include <errno.h>
35#include <unistd.h>
36
37#include <sys/ioctl.h>
38#include <sys/mman.h>
39#include <sys/time.h>
40
41#include <linux/ioctl.h>
42#define __force
43#define __bitwise
44#define __user
45#include <sound/asound.h>
46
47#include <tinyalsa/asoundlib.h>
48
49#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL
50
51static inline int param_is_mask(int p)
52{
53 return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) &&
54 (p <= SNDRV_PCM_HW_PARAM_LAST_MASK);
55}
56
57static inline int param_is_interval(int p)
58{
59 return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) &&
60 (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL);
61}
62
63static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n)
64{
65 return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
66}
67
68static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n)
69{
70 return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]);
71}
72
73static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit)
74{
75 if (bit >= SNDRV_MASK_MAX)
76 return;
77 if (param_is_mask(n)) {
78 struct snd_mask *m = param_to_mask(p, n);
79 m->bits[0] = 0;
80 m->bits[1] = 0;
81 m->bits[bit >> 5] |= (1 << (bit & 31));
82 }
83}
84
85static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val)
86{
87 if (param_is_interval(n)) {
88 struct snd_interval *i = param_to_interval(p, n);
89 i->min = val;
90 }
91}
92
93static void param_set_max(struct snd_pcm_hw_params *p, int n, unsigned int val)
94{
95 if (param_is_interval(n)) {
96 struct snd_interval *i = param_to_interval(p, n);
97 i->max = val;
98 }
99}
100
101static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val)
102{
103 if (param_is_interval(n)) {
104 struct snd_interval *i = param_to_interval(p, n);
105 i->min = val;
106 i->max = val;
107 i->integer = 1;
108 }
109}
110
111static void param_init(struct snd_pcm_hw_params *p)
112{
113 int n;
Simon Wilson98c1f162011-06-07 16:12:32 -0700114
Simon Wilson79d39652011-05-25 13:44:23 -0700115 memset(p, 0, sizeof(*p));
116 for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK;
117 n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) {
118 struct snd_mask *m = param_to_mask(p, n);
119 m->bits[0] = ~0;
120 m->bits[1] = ~0;
121 }
122 for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
123 n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) {
124 struct snd_interval *i = param_to_interval(p, n);
125 i->min = 0;
126 i->max = ~0;
127 }
128}
129
130#define PCM_ERROR_MAX 128
131
132struct pcm {
133 int fd;
134 unsigned int flags;
135 int running:1;
136 int underruns;
137 unsigned int buffer_size;
138 char error[PCM_ERROR_MAX];
139 struct pcm_config config;
140};
141
Simon Wilson851aa5c2011-05-30 21:18:26 -0700142unsigned int pcm_get_buffer_size(struct pcm *pcm)
Simon Wilson79d39652011-05-25 13:44:23 -0700143{
144 return pcm->buffer_size;
145}
146
147const char* pcm_get_error(struct pcm *pcm)
148{
149 return pcm->error;
150}
151
152static int oops(struct pcm *pcm, int e, const char *fmt, ...)
153{
154 va_list ap;
155 int sz;
156
157 va_start(ap, fmt);
158 vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap);
159 va_end(ap);
160 sz = strlen(pcm->error);
161
162 if (errno)
163 snprintf(pcm->error + sz, PCM_ERROR_MAX - sz,
164 ": %s", strerror(e));
165 return -1;
166}
167
168int pcm_write(struct pcm *pcm, void *data, unsigned int count)
169{
170 struct snd_xferi x;
171
172 if (pcm->flags & PCM_IN)
173 return -EINVAL;
174
175 x.buf = data;
176 x.frames = count / (pcm->config.channels * 2); /* TODO: handle 32bit */
177
178 for (;;) {
179 if (!pcm->running) {
180 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE))
181 return oops(pcm, errno, "cannot prepare channel");
182 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
183 return oops(pcm, errno, "cannot write initial data");
184 pcm->running = 1;
185 return 0;
186 }
187 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
188 pcm->running = 0;
189 if (errno == EPIPE) {
190 /* we failed to make our window -- try to restart */
191 pcm->underruns++;
192 continue;
193 }
194 return oops(pcm, errno, "cannot write stream data");
195 }
196 return 0;
197 }
198}
199
200int pcm_read(struct pcm *pcm, void *data, unsigned int count)
201{
202 struct snd_xferi x;
203
204 if (!(pcm->flags & PCM_IN))
205 return -EINVAL;
206
207 x.buf = data;
208 x.frames = count / (pcm->config.channels * 2); /* TODO: handle 32bit */
209
210 for (;;) {
211 if (!pcm->running) {
212 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE))
213 return oops(pcm, errno, "cannot prepare channel");
214 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START))
215 return oops(pcm, errno, "cannot start channel");
216 pcm->running = 1;
217 }
218 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
219 pcm->running = 0;
220 if (errno == EPIPE) {
221 /* we failed to make our window -- try to restart */
222 pcm->underruns++;
223 continue;
224 }
225 return oops(pcm, errno, "cannot read stream data");
226 }
227 return 0;
228 }
229}
230
231static struct pcm bad_pcm = {
232 .fd = -1,
233};
234
235int pcm_close(struct pcm *pcm)
236{
237 if (pcm == &bad_pcm)
238 return 0;
239
240 if (pcm->fd >= 0)
241 close(pcm->fd);
242 pcm->running = 0;
243 pcm->buffer_size = 0;
244 pcm->fd = -1;
245 return 0;
246}
247
248static unsigned int pcm_format_to_alsa(enum pcm_format format)
249{
250 switch (format) {
251 case PCM_FORMAT_S32_LE:
252 return SNDRV_PCM_FORMAT_S32_LE;
253 default:
254 case PCM_FORMAT_S16_LE:
255 return SNDRV_PCM_FORMAT_S16_LE;
256 };
257}
258
259static unsigned int pcm_format_to_bits(enum pcm_format format)
260{
261 switch (format) {
262 case PCM_FORMAT_S32_LE:
263 return 32;
264 default:
265 case PCM_FORMAT_S16_LE:
266 return 16;
267 };
268}
269
Simon Wilson1bd580f2011-06-02 15:58:41 -0700270struct pcm *pcm_open(unsigned int card, unsigned int device,
271 unsigned int flags, struct pcm_config *config)
Simon Wilson79d39652011-05-25 13:44:23 -0700272{
Simon Wilson79d39652011-05-25 13:44:23 -0700273 struct pcm *pcm;
274 struct snd_pcm_info info;
275 struct snd_pcm_hw_params params;
276 struct snd_pcm_sw_params sparams;
Simon Wilson1bd580f2011-06-02 15:58:41 -0700277 char fn[256];
Simon Wilson79d39652011-05-25 13:44:23 -0700278
279 pcm = calloc(1, sizeof(struct pcm));
280 if (!pcm || !config)
281 return &bad_pcm; /* TODO: could support default config here */
282
283 pcm->config = *config;
284
Simon Wilson1bd580f2011-06-02 15:58:41 -0700285 snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
286 flags & PCM_IN ? 'c' : 'p');
Simon Wilson79d39652011-05-25 13:44:23 -0700287
288 pcm->flags = flags;
Simon Wilson1bd580f2011-06-02 15:58:41 -0700289 pcm->fd = open(fn, O_RDWR);
Simon Wilson79d39652011-05-25 13:44:23 -0700290 if (pcm->fd < 0) {
Simon Wilson1bd580f2011-06-02 15:58:41 -0700291 oops(pcm, errno, "cannot open device '%s'", fn);
Simon Wilson79d39652011-05-25 13:44:23 -0700292 return pcm;
293 }
294
295 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
Simon Wilson851aa5c2011-05-30 21:18:26 -0700296 oops(pcm, errno, "cannot get info");
Simon Wilson79d39652011-05-25 13:44:23 -0700297 goto fail;
298 }
299
300 param_init(&params);
301 param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
302 SNDRV_PCM_ACCESS_RW_INTERLEAVED);
303 param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
304 pcm_format_to_alsa(config->format));
305 param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
306 SNDRV_PCM_SUBFORMAT_STD);
307 param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
308 param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
309 pcm_format_to_bits(config->format));
310 param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
311 pcm_format_to_bits(config->format) * config->channels);
312 param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
313 config->channels);
314 param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
315 param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);
316
317 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
318 oops(pcm, errno, "cannot set hw params");
319 goto fail;
320 }
321
322 memset(&sparams, 0, sizeof(sparams));
323 sparams.tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
324 sparams.period_step = 1;
325 sparams.avail_min = 1;
326 sparams.start_threshold = config->period_count * config->period_size;
327 sparams.stop_threshold = config->period_count * config->period_size;
328 sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
329 sparams.silence_size = 0;
330 sparams.silence_threshold = 0;
331
332 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
333 oops(pcm, errno, "cannot set sw params");
334 goto fail;
335 }
336
337 pcm->buffer_size = config->period_count * config->period_size;
338 pcm->underruns = 0;
339 return pcm;
340
341fail:
342 close(pcm->fd);
343 pcm->fd = -1;
344 return pcm;
345}
346
347int pcm_is_ready(struct pcm *pcm)
348{
349 return pcm->fd >= 0;
350}