diff options
Diffstat (limited to 'firmware/target/hosted/android/dx50/tinyalsa/pcm.c')
-rw-r--r-- | firmware/target/hosted/android/dx50/tinyalsa/pcm.c | 973 |
1 files changed, 0 insertions, 973 deletions
diff --git a/firmware/target/hosted/android/dx50/tinyalsa/pcm.c b/firmware/target/hosted/android/dx50/tinyalsa/pcm.c deleted file mode 100644 index bd44dce52f..0000000000 --- a/firmware/target/hosted/android/dx50/tinyalsa/pcm.c +++ /dev/null | |||
@@ -1,973 +0,0 @@ | |||
1 | /* 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 | #include <poll.h> | ||
37 | |||
38 | #include <sys/ioctl.h> | ||
39 | #include <sys/mman.h> | ||
40 | #include <sys/time.h> | ||
41 | #include <limits.h> | ||
42 | |||
43 | #include <linux/ioctl.h> | ||
44 | #define __force | ||
45 | #define __bitwise | ||
46 | #define __user | ||
47 | #include <tinyalsa/asound.h> | ||
48 | |||
49 | #include <tinyalsa/asoundlib.h> | ||
50 | |||
51 | #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL | ||
52 | #define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2) | ||
53 | |||
54 | static inline int param_is_mask(int p) | ||
55 | { | ||
56 | return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && | ||
57 | (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); | ||
58 | } | ||
59 | |||
60 | static inline int param_is_interval(int p) | ||
61 | { | ||
62 | return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && | ||
63 | (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL); | ||
64 | } | ||
65 | |||
66 | static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n) | ||
67 | { | ||
68 | return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]); | ||
69 | } | ||
70 | |||
71 | static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n) | ||
72 | { | ||
73 | return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); | ||
74 | } | ||
75 | |||
76 | static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit) | ||
77 | { | ||
78 | if (bit >= SNDRV_MASK_MAX) | ||
79 | return; | ||
80 | if (param_is_mask(n)) { | ||
81 | struct snd_mask *m = param_to_mask(p, n); | ||
82 | m->bits[0] = 0; | ||
83 | m->bits[1] = 0; | ||
84 | m->bits[bit >> 5] |= (1 << (bit & 31)); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val) | ||
89 | { | ||
90 | if (param_is_interval(n)) { | ||
91 | struct snd_interval *i = param_to_interval(p, n); | ||
92 | i->min = val; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | static unsigned int param_get_min(struct snd_pcm_hw_params *p, int n) | ||
97 | { | ||
98 | if (param_is_interval(n)) { | ||
99 | struct snd_interval *i = param_to_interval(p, n); | ||
100 | return i->min; | ||
101 | } | ||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | static unsigned int param_get_max(struct snd_pcm_hw_params *p, int n) | ||
106 | { | ||
107 | if (param_is_interval(n)) { | ||
108 | struct snd_interval *i = param_to_interval(p, n); | ||
109 | return i->max; | ||
110 | } | ||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val) | ||
115 | { | ||
116 | if (param_is_interval(n)) { | ||
117 | struct snd_interval *i = param_to_interval(p, n); | ||
118 | i->min = val; | ||
119 | i->max = val; | ||
120 | i->integer = 1; | ||
121 | } | ||
122 | } | ||
123 | |||
124 | static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n) | ||
125 | { | ||
126 | if (param_is_interval(n)) { | ||
127 | struct snd_interval *i = param_to_interval(p, n); | ||
128 | if (i->integer) | ||
129 | return i->max; | ||
130 | } | ||
131 | return 0; | ||
132 | } | ||
133 | |||
134 | static void param_init(struct snd_pcm_hw_params *p) | ||
135 | { | ||
136 | int n; | ||
137 | |||
138 | memset(p, 0, sizeof(*p)); | ||
139 | for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK; | ||
140 | n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) { | ||
141 | struct snd_mask *m = param_to_mask(p, n); | ||
142 | m->bits[0] = ~0; | ||
143 | m->bits[1] = ~0; | ||
144 | } | ||
145 | for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; | ||
146 | n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { | ||
147 | struct snd_interval *i = param_to_interval(p, n); | ||
148 | i->min = 0; | ||
149 | i->max = ~0; | ||
150 | } | ||
151 | p->rmask = ~0U; | ||
152 | p->cmask = 0; | ||
153 | p->info = ~0U; | ||
154 | } | ||
155 | |||
156 | #define PCM_ERROR_MAX 128 | ||
157 | |||
158 | struct pcm { | ||
159 | int fd; | ||
160 | unsigned int flags; | ||
161 | int running:1; | ||
162 | int underruns; | ||
163 | unsigned int buffer_size; | ||
164 | unsigned int boundary; | ||
165 | char error[PCM_ERROR_MAX]; | ||
166 | struct pcm_config config; | ||
167 | struct snd_pcm_mmap_status *mmap_status; | ||
168 | struct snd_pcm_mmap_control *mmap_control; | ||
169 | struct snd_pcm_sync_ptr *sync_ptr; | ||
170 | void *mmap_buffer; | ||
171 | unsigned int noirq_frames_per_msec; | ||
172 | }; | ||
173 | |||
174 | unsigned int pcm_get_buffer_size(struct pcm *pcm) | ||
175 | { | ||
176 | return pcm->buffer_size; | ||
177 | } | ||
178 | |||
179 | const char* pcm_get_error(struct pcm *pcm) | ||
180 | { | ||
181 | return pcm->error; | ||
182 | } | ||
183 | |||
184 | static int oops(struct pcm *pcm, int e, const char *fmt, ...) | ||
185 | { | ||
186 | va_list ap; | ||
187 | int sz; | ||
188 | |||
189 | va_start(ap, fmt); | ||
190 | vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap); | ||
191 | va_end(ap); | ||
192 | sz = strlen(pcm->error); | ||
193 | |||
194 | if (errno) | ||
195 | snprintf(pcm->error + sz, PCM_ERROR_MAX - sz, | ||
196 | ": %s", strerror(e)); | ||
197 | return -1; | ||
198 | } | ||
199 | |||
200 | static unsigned int pcm_format_to_alsa(enum pcm_format format) | ||
201 | { | ||
202 | switch (format) { | ||
203 | case PCM_FORMAT_S32_LE: | ||
204 | return SNDRV_PCM_FORMAT_S32_LE; | ||
205 | case PCM_FORMAT_S8: | ||
206 | return SNDRV_PCM_FORMAT_S8; | ||
207 | case PCM_FORMAT_S24_LE: | ||
208 | return SNDRV_PCM_FORMAT_S24_LE; | ||
209 | default: | ||
210 | case PCM_FORMAT_S16_LE: | ||
211 | return SNDRV_PCM_FORMAT_S16_LE; | ||
212 | }; | ||
213 | } | ||
214 | |||
215 | unsigned int pcm_format_to_bits(enum pcm_format format) | ||
216 | { | ||
217 | switch (format) { | ||
218 | case PCM_FORMAT_S32_LE: | ||
219 | case PCM_FORMAT_S24_LE: | ||
220 | return 32; | ||
221 | default: | ||
222 | case PCM_FORMAT_S16_LE: | ||
223 | return 16; | ||
224 | }; | ||
225 | } | ||
226 | |||
227 | unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes) | ||
228 | { | ||
229 | return bytes / (pcm->config.channels * | ||
230 | (pcm_format_to_bits(pcm->config.format) >> 3)); | ||
231 | } | ||
232 | |||
233 | unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames) | ||
234 | { | ||
235 | return frames * pcm->config.channels * | ||
236 | (pcm_format_to_bits(pcm->config.format) >> 3); | ||
237 | } | ||
238 | |||
239 | static int pcm_sync_ptr(struct pcm *pcm, int flags) { | ||
240 | if (pcm->sync_ptr) { | ||
241 | pcm->sync_ptr->flags = flags; | ||
242 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) | ||
243 | return -1; | ||
244 | } | ||
245 | return 0; | ||
246 | } | ||
247 | |||
248 | static int pcm_hw_mmap_status(struct pcm *pcm) { | ||
249 | |||
250 | if (pcm->sync_ptr) | ||
251 | return 0; | ||
252 | |||
253 | int page_size = sysconf(_SC_PAGE_SIZE); | ||
254 | pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED, | ||
255 | pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); | ||
256 | if (pcm->mmap_status == MAP_FAILED) | ||
257 | pcm->mmap_status = NULL; | ||
258 | if (!pcm->mmap_status) | ||
259 | goto mmap_error; | ||
260 | |||
261 | pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE, | ||
262 | MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); | ||
263 | if (pcm->mmap_control == MAP_FAILED) | ||
264 | pcm->mmap_control = NULL; | ||
265 | if (!pcm->mmap_control) { | ||
266 | munmap(pcm->mmap_status, page_size); | ||
267 | pcm->mmap_status = NULL; | ||
268 | goto mmap_error; | ||
269 | } | ||
270 | pcm->mmap_control->avail_min = 1; | ||
271 | |||
272 | return 0; | ||
273 | |||
274 | mmap_error: | ||
275 | |||
276 | pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr)); | ||
277 | if (!pcm->sync_ptr) | ||
278 | return -ENOMEM; | ||
279 | pcm->mmap_status = &pcm->sync_ptr->s.status; | ||
280 | pcm->mmap_control = &pcm->sync_ptr->c.control; | ||
281 | pcm->mmap_control->avail_min = 1; | ||
282 | pcm_sync_ptr(pcm, 0); | ||
283 | |||
284 | return 0; | ||
285 | } | ||
286 | |||
287 | static void pcm_hw_munmap_status(struct pcm *pcm) { | ||
288 | if (pcm->sync_ptr) { | ||
289 | free(pcm->sync_ptr); | ||
290 | pcm->sync_ptr = NULL; | ||
291 | } else { | ||
292 | int page_size = sysconf(_SC_PAGE_SIZE); | ||
293 | if (pcm->mmap_status) | ||
294 | munmap(pcm->mmap_status, page_size); | ||
295 | if (pcm->mmap_control) | ||
296 | munmap(pcm->mmap_control, page_size); | ||
297 | } | ||
298 | pcm->mmap_status = NULL; | ||
299 | pcm->mmap_control = NULL; | ||
300 | } | ||
301 | |||
302 | static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset, | ||
303 | const char *src, unsigned int src_offset, | ||
304 | unsigned int frames) | ||
305 | { | ||
306 | int size_bytes = pcm_frames_to_bytes(pcm, frames); | ||
307 | int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset); | ||
308 | int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset); | ||
309 | |||
310 | /* interleaved only atm */ | ||
311 | memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes, | ||
312 | src + src_offset_bytes, size_bytes); | ||
313 | return 0; | ||
314 | } | ||
315 | |||
316 | static int pcm_mmap_write_areas(struct pcm *pcm, const char *src, | ||
317 | unsigned int offset, unsigned int size) | ||
318 | { | ||
319 | void *pcm_areas; | ||
320 | int commit; | ||
321 | unsigned int pcm_offset, frames, count = 0; | ||
322 | |||
323 | while (size > 0) { | ||
324 | frames = size; | ||
325 | pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); | ||
326 | pcm_areas_copy(pcm, pcm_offset, src, offset, frames); | ||
327 | commit = pcm_mmap_commit(pcm, pcm_offset, frames); | ||
328 | if (commit < 0) { | ||
329 | oops(pcm, commit, "failed to commit %d frames\n", frames); | ||
330 | return commit; | ||
331 | } | ||
332 | |||
333 | offset += commit; | ||
334 | count += commit; | ||
335 | size -= commit; | ||
336 | } | ||
337 | return count; | ||
338 | } | ||
339 | |||
340 | int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, | ||
341 | struct timespec *tstamp) | ||
342 | { | ||
343 | int frames; | ||
344 | int rc; | ||
345 | snd_pcm_uframes_t hw_ptr; | ||
346 | |||
347 | if (!pcm_is_ready(pcm)) | ||
348 | return -1; | ||
349 | |||
350 | rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC); | ||
351 | if (rc < 0) | ||
352 | return -1; | ||
353 | |||
354 | if ((pcm->mmap_status->state != PCM_STATE_RUNNING) && | ||
355 | (pcm->mmap_status->state != PCM_STATE_DRAINING)) | ||
356 | return -1; | ||
357 | |||
358 | *tstamp = pcm->mmap_status->tstamp; | ||
359 | if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0) | ||
360 | return -1; | ||
361 | |||
362 | hw_ptr = pcm->mmap_status->hw_ptr; | ||
363 | if (pcm->flags & PCM_IN) | ||
364 | frames = hw_ptr - pcm->mmap_control->appl_ptr; | ||
365 | else | ||
366 | frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; | ||
367 | |||
368 | if (frames < 0) | ||
369 | return -1; | ||
370 | |||
371 | *avail = (unsigned int)frames; | ||
372 | |||
373 | return 0; | ||
374 | } | ||
375 | |||
376 | int pcm_write(struct pcm *pcm, const void *data, unsigned int count) | ||
377 | { | ||
378 | struct snd_xferi x; | ||
379 | |||
380 | if (pcm->flags & PCM_IN) | ||
381 | return -EINVAL; | ||
382 | |||
383 | x.buf = (void*)data; | ||
384 | x.frames = count / (pcm->config.channels * | ||
385 | pcm_format_to_bits(pcm->config.format) / 8); | ||
386 | |||
387 | for (;;) { | ||
388 | if (!pcm->running) { | ||
389 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)) | ||
390 | return oops(pcm, errno, "cannot prepare channel"); | ||
391 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) | ||
392 | return oops(pcm, errno, "cannot write initial data"); | ||
393 | pcm->running = 1; | ||
394 | return 0; | ||
395 | } | ||
396 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) { | ||
397 | pcm->running = 0; | ||
398 | if (errno == EPIPE) { | ||
399 | /* we failed to make our window -- try to restart if we are | ||
400 | * allowed to do so. Otherwise, simply allow the EPIPE error to | ||
401 | * propagate up to the app level */ | ||
402 | pcm->underruns++; | ||
403 | if (pcm->flags & PCM_NORESTART) | ||
404 | return -EPIPE; | ||
405 | continue; | ||
406 | } | ||
407 | return oops(pcm, errno, "cannot write stream data"); | ||
408 | } | ||
409 | return 0; | ||
410 | } | ||
411 | } | ||
412 | |||
413 | int pcm_read(struct pcm *pcm, void *data, unsigned int count) | ||
414 | { | ||
415 | struct snd_xferi x; | ||
416 | |||
417 | if (!(pcm->flags & PCM_IN)) | ||
418 | return -EINVAL; | ||
419 | |||
420 | x.buf = data; | ||
421 | x.frames = count / (pcm->config.channels * | ||
422 | pcm_format_to_bits(pcm->config.format) / 8); | ||
423 | |||
424 | for (;;) { | ||
425 | if (!pcm->running) { | ||
426 | if (pcm_start(pcm) < 0) { | ||
427 | fprintf(stderr, "start error"); | ||
428 | return -errno; | ||
429 | } | ||
430 | } | ||
431 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) { | ||
432 | pcm->running = 0; | ||
433 | if (errno == EPIPE) { | ||
434 | /* we failed to make our window -- try to restart */ | ||
435 | pcm->underruns++; | ||
436 | continue; | ||
437 | } | ||
438 | return oops(pcm, errno, "cannot read stream data"); | ||
439 | } | ||
440 | return 0; | ||
441 | } | ||
442 | } | ||
443 | |||
444 | static struct pcm bad_pcm = { | ||
445 | .fd = -1, | ||
446 | }; | ||
447 | |||
448 | struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, | ||
449 | unsigned int flags) | ||
450 | { | ||
451 | struct snd_pcm_hw_params *params; | ||
452 | char fn[256]; | ||
453 | int fd; | ||
454 | |||
455 | snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, | ||
456 | flags & PCM_IN ? 'c' : 'p'); | ||
457 | |||
458 | fd = open(fn, O_RDWR); | ||
459 | if (fd < 0) { | ||
460 | fprintf(stderr, "cannot open device '%s'\n", fn); | ||
461 | goto err_open; | ||
462 | } | ||
463 | |||
464 | params = calloc(1, sizeof(struct snd_pcm_hw_params)); | ||
465 | if (!params) | ||
466 | goto err_calloc; | ||
467 | |||
468 | param_init(params); | ||
469 | if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) { | ||
470 | fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno); | ||
471 | goto err_hw_refine; | ||
472 | } | ||
473 | |||
474 | close(fd); | ||
475 | |||
476 | return (struct pcm_params *)params; | ||
477 | |||
478 | err_hw_refine: | ||
479 | free(params); | ||
480 | err_calloc: | ||
481 | close(fd); | ||
482 | err_open: | ||
483 | return NULL; | ||
484 | } | ||
485 | |||
486 | void pcm_params_free(struct pcm_params *pcm_params) | ||
487 | { | ||
488 | struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; | ||
489 | |||
490 | if (params) | ||
491 | free(params); | ||
492 | } | ||
493 | |||
494 | static int pcm_param_to_alsa(enum pcm_param param) | ||
495 | { | ||
496 | switch (param) { | ||
497 | case PCM_PARAM_SAMPLE_BITS: | ||
498 | return SNDRV_PCM_HW_PARAM_SAMPLE_BITS; | ||
499 | break; | ||
500 | case PCM_PARAM_FRAME_BITS: | ||
501 | return SNDRV_PCM_HW_PARAM_FRAME_BITS; | ||
502 | break; | ||
503 | case PCM_PARAM_CHANNELS: | ||
504 | return SNDRV_PCM_HW_PARAM_CHANNELS; | ||
505 | break; | ||
506 | case PCM_PARAM_RATE: | ||
507 | return SNDRV_PCM_HW_PARAM_RATE; | ||
508 | break; | ||
509 | case PCM_PARAM_PERIOD_TIME: | ||
510 | return SNDRV_PCM_HW_PARAM_PERIOD_TIME; | ||
511 | break; | ||
512 | case PCM_PARAM_PERIOD_SIZE: | ||
513 | return SNDRV_PCM_HW_PARAM_PERIOD_SIZE; | ||
514 | break; | ||
515 | case PCM_PARAM_PERIOD_BYTES: | ||
516 | return SNDRV_PCM_HW_PARAM_PERIOD_BYTES; | ||
517 | break; | ||
518 | case PCM_PARAM_PERIODS: | ||
519 | return SNDRV_PCM_HW_PARAM_PERIODS; | ||
520 | break; | ||
521 | case PCM_PARAM_BUFFER_TIME: | ||
522 | return SNDRV_PCM_HW_PARAM_BUFFER_TIME; | ||
523 | break; | ||
524 | case PCM_PARAM_BUFFER_SIZE: | ||
525 | return SNDRV_PCM_HW_PARAM_BUFFER_SIZE; | ||
526 | break; | ||
527 | case PCM_PARAM_BUFFER_BYTES: | ||
528 | return SNDRV_PCM_HW_PARAM_BUFFER_BYTES; | ||
529 | break; | ||
530 | case PCM_PARAM_TICK_TIME: | ||
531 | return SNDRV_PCM_HW_PARAM_TICK_TIME; | ||
532 | break; | ||
533 | |||
534 | default: | ||
535 | return -1; | ||
536 | } | ||
537 | } | ||
538 | |||
539 | unsigned int pcm_params_get_min(struct pcm_params *pcm_params, | ||
540 | enum pcm_param param) | ||
541 | { | ||
542 | struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; | ||
543 | int p; | ||
544 | |||
545 | if (!params) | ||
546 | return 0; | ||
547 | |||
548 | p = pcm_param_to_alsa(param); | ||
549 | if (p < 0) | ||
550 | return 0; | ||
551 | |||
552 | return param_get_min(params, p); | ||
553 | } | ||
554 | |||
555 | unsigned int pcm_params_get_max(struct pcm_params *pcm_params, | ||
556 | enum pcm_param param) | ||
557 | { | ||
558 | struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; | ||
559 | int p; | ||
560 | |||
561 | if (!params) | ||
562 | return 0; | ||
563 | |||
564 | p = pcm_param_to_alsa(param); | ||
565 | if (p < 0) | ||
566 | return 0; | ||
567 | |||
568 | return param_get_max(params, p); | ||
569 | } | ||
570 | |||
571 | int pcm_close(struct pcm *pcm) | ||
572 | { | ||
573 | if (pcm == &bad_pcm) | ||
574 | return 0; | ||
575 | |||
576 | pcm_hw_munmap_status(pcm); | ||
577 | |||
578 | if (pcm->flags & PCM_MMAP) { | ||
579 | pcm_stop(pcm); | ||
580 | munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); | ||
581 | } | ||
582 | |||
583 | if (pcm->fd >= 0) | ||
584 | close(pcm->fd); | ||
585 | pcm->running = 0; | ||
586 | pcm->buffer_size = 0; | ||
587 | pcm->fd = -1; | ||
588 | free(pcm); | ||
589 | return 0; | ||
590 | } | ||
591 | |||
592 | struct pcm *pcm_open(unsigned int card, unsigned int device, | ||
593 | unsigned int flags, struct pcm_config *config) | ||
594 | { | ||
595 | struct pcm *pcm; | ||
596 | struct snd_pcm_info info; | ||
597 | struct snd_pcm_hw_params params; | ||
598 | struct snd_pcm_sw_params sparams; | ||
599 | char fn[256]; | ||
600 | int rc; | ||
601 | |||
602 | pcm = calloc(1, sizeof(struct pcm)); | ||
603 | if (!pcm || !config) | ||
604 | return &bad_pcm; /* TODO: could support default config here */ | ||
605 | |||
606 | pcm->config = *config; | ||
607 | |||
608 | snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, | ||
609 | flags & PCM_IN ? 'c' : 'p'); | ||
610 | |||
611 | pcm->flags = flags; | ||
612 | pcm->fd = open(fn, O_RDWR); | ||
613 | if (pcm->fd < 0) { | ||
614 | oops(pcm, errno, "cannot open device '%s'", fn); | ||
615 | return pcm; | ||
616 | } | ||
617 | |||
618 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { | ||
619 | oops(pcm, errno, "cannot get info"); | ||
620 | goto fail_close; | ||
621 | } | ||
622 | |||
623 | param_init(¶ms); | ||
624 | param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT, | ||
625 | pcm_format_to_alsa(config->format)); | ||
626 | param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT, | ||
627 | SNDRV_PCM_SUBFORMAT_STD); | ||
628 | param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size); | ||
629 | param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, | ||
630 | pcm_format_to_bits(config->format)); | ||
631 | param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS, | ||
632 | pcm_format_to_bits(config->format) * config->channels); | ||
633 | param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS, | ||
634 | config->channels); | ||
635 | param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count); | ||
636 | param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate); | ||
637 | |||
638 | if (flags & PCM_NOIRQ) { | ||
639 | |||
640 | if (!(flags & PCM_MMAP)) { | ||
641 | oops(pcm, -EINVAL, "noirq only currently supported with mmap()."); | ||
642 | goto fail; | ||
643 | } | ||
644 | |||
645 | params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP; | ||
646 | pcm->noirq_frames_per_msec = config->rate / 1000; | ||
647 | } | ||
648 | |||
649 | if (flags & PCM_MMAP) | ||
650 | param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, | ||
651 | SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); | ||
652 | else | ||
653 | param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, | ||
654 | SNDRV_PCM_ACCESS_RW_INTERLEAVED); | ||
655 | |||
656 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { | ||
657 | oops(pcm, errno, "cannot set hw params"); | ||
658 | goto fail_close; | ||
659 | } | ||
660 | |||
661 | /* get our refined hw_params */ | ||
662 | config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); | ||
663 | config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS); | ||
664 | pcm->buffer_size = config->period_count * config->period_size; | ||
665 | |||
666 | if (flags & PCM_MMAP) { | ||
667 | pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size), | ||
668 | PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0); | ||
669 | if (pcm->mmap_buffer == MAP_FAILED) { | ||
670 | oops(pcm, -errno, "failed to mmap buffer %d bytes\n", | ||
671 | pcm_frames_to_bytes(pcm, pcm->buffer_size)); | ||
672 | goto fail_close; | ||
673 | } | ||
674 | } | ||
675 | |||
676 | |||
677 | memset(&sparams, 0, sizeof(sparams)); | ||
678 | sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE; | ||
679 | sparams.period_step = 1; | ||
680 | sparams.avail_min = 1; | ||
681 | |||
682 | if (!config->start_threshold) { | ||
683 | if (pcm->flags & PCM_IN) | ||
684 | pcm->config.start_threshold = sparams.start_threshold = 1; | ||
685 | else | ||
686 | pcm->config.start_threshold = sparams.start_threshold = | ||
687 | config->period_count * config->period_size / 2; | ||
688 | } else | ||
689 | sparams.start_threshold = config->start_threshold; | ||
690 | |||
691 | /* pick a high stop threshold - todo: does this need further tuning */ | ||
692 | if (!config->stop_threshold) { | ||
693 | if (pcm->flags & PCM_IN) | ||
694 | pcm->config.stop_threshold = sparams.stop_threshold = | ||
695 | config->period_count * config->period_size * 10; | ||
696 | else | ||
697 | pcm->config.stop_threshold = sparams.stop_threshold = | ||
698 | config->period_count * config->period_size; | ||
699 | } | ||
700 | else | ||
701 | sparams.stop_threshold = config->stop_threshold; | ||
702 | |||
703 | sparams.xfer_align = config->period_size / 2; /* needed for old kernels */ | ||
704 | sparams.silence_size = 0; | ||
705 | sparams.silence_threshold = config->silence_threshold; | ||
706 | pcm->boundary = sparams.boundary = pcm->buffer_size; | ||
707 | |||
708 | while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size) | ||
709 | pcm->boundary *= 2; | ||
710 | |||
711 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { | ||
712 | oops(pcm, errno, "cannot set sw params"); | ||
713 | goto fail; | ||
714 | } | ||
715 | |||
716 | rc = pcm_hw_mmap_status(pcm); | ||
717 | if (rc < 0) { | ||
718 | oops(pcm, rc, "mmap status failed"); | ||
719 | goto fail; | ||
720 | } | ||
721 | |||
722 | pcm->underruns = 0; | ||
723 | return pcm; | ||
724 | |||
725 | fail: | ||
726 | if (flags & PCM_MMAP) | ||
727 | munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); | ||
728 | fail_close: | ||
729 | close(pcm->fd); | ||
730 | pcm->fd = -1; | ||
731 | return pcm; | ||
732 | } | ||
733 | |||
734 | int pcm_is_ready(struct pcm *pcm) | ||
735 | { | ||
736 | return pcm->fd >= 0; | ||
737 | } | ||
738 | |||
739 | int pcm_start(struct pcm *pcm) | ||
740 | { | ||
741 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0) | ||
742 | return oops(pcm, errno, "cannot prepare channel"); | ||
743 | |||
744 | if (pcm->flags & PCM_MMAP) | ||
745 | pcm_sync_ptr(pcm, 0); | ||
746 | |||
747 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0) | ||
748 | return oops(pcm, errno, "cannot start channel"); | ||
749 | |||
750 | pcm->running = 1; | ||
751 | return 0; | ||
752 | } | ||
753 | |||
754 | int pcm_stop(struct pcm *pcm) | ||
755 | { | ||
756 | if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) | ||
757 | return oops(pcm, errno, "cannot stop channel"); | ||
758 | |||
759 | pcm->running = 0; | ||
760 | return 0; | ||
761 | } | ||
762 | |||
763 | static inline int pcm_mmap_playback_avail(struct pcm *pcm) | ||
764 | { | ||
765 | int avail; | ||
766 | |||
767 | avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; | ||
768 | |||
769 | if (avail < 0) | ||
770 | avail += pcm->boundary; | ||
771 | else if (avail > (int)pcm->boundary) | ||
772 | avail -= pcm->boundary; | ||
773 | |||
774 | return avail; | ||
775 | } | ||
776 | |||
777 | static inline int pcm_mmap_capture_avail(struct pcm *pcm) | ||
778 | { | ||
779 | int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr; | ||
780 | if (avail < 0) | ||
781 | avail += pcm->boundary; | ||
782 | return avail; | ||
783 | } | ||
784 | |||
785 | static inline int pcm_mmap_avail(struct pcm *pcm) | ||
786 | { | ||
787 | pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC); | ||
788 | if (pcm->flags & PCM_IN) | ||
789 | return pcm_mmap_capture_avail(pcm); | ||
790 | else | ||
791 | return pcm_mmap_playback_avail(pcm); | ||
792 | } | ||
793 | |||
794 | static void pcm_mmap_appl_forward(struct pcm *pcm, int frames) | ||
795 | { | ||
796 | unsigned int appl_ptr = pcm->mmap_control->appl_ptr; | ||
797 | appl_ptr += frames; | ||
798 | |||
799 | /* check for boundary wrap */ | ||
800 | if (appl_ptr > pcm->boundary) | ||
801 | appl_ptr -= pcm->boundary; | ||
802 | pcm->mmap_control->appl_ptr = appl_ptr; | ||
803 | } | ||
804 | |||
805 | int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, | ||
806 | unsigned int *frames) | ||
807 | { | ||
808 | unsigned int continuous, copy_frames, avail; | ||
809 | |||
810 | /* return the mmap buffer */ | ||
811 | *areas = pcm->mmap_buffer; | ||
812 | |||
813 | /* and the application offset in frames */ | ||
814 | *offset = pcm->mmap_control->appl_ptr % pcm->buffer_size; | ||
815 | |||
816 | avail = pcm_mmap_avail(pcm); | ||
817 | if (avail > pcm->buffer_size) | ||
818 | avail = pcm->buffer_size; | ||
819 | continuous = pcm->buffer_size - *offset; | ||
820 | |||
821 | /* we can only copy frames if the are availabale and continuos */ | ||
822 | copy_frames = *frames; | ||
823 | if (copy_frames > avail) | ||
824 | copy_frames = avail; | ||
825 | if (copy_frames > continuous) | ||
826 | copy_frames = continuous; | ||
827 | *frames = copy_frames; | ||
828 | |||
829 | return 0; | ||
830 | } | ||
831 | |||
832 | int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames) | ||
833 | { | ||
834 | (void)offset; | ||
835 | /* update the application pointer in userspace and kernel */ | ||
836 | pcm_mmap_appl_forward(pcm, frames); | ||
837 | pcm_sync_ptr(pcm, 0); | ||
838 | |||
839 | return frames; | ||
840 | } | ||
841 | |||
842 | int pcm_avail_update(struct pcm *pcm) | ||
843 | { | ||
844 | pcm_sync_ptr(pcm, 0); | ||
845 | return pcm_mmap_avail(pcm); | ||
846 | } | ||
847 | |||
848 | int pcm_state(struct pcm *pcm) | ||
849 | { | ||
850 | int err = pcm_sync_ptr(pcm, 0); | ||
851 | if (err < 0) | ||
852 | return err; | ||
853 | |||
854 | return pcm->mmap_status->state; | ||
855 | } | ||
856 | |||
857 | int pcm_wait(struct pcm *pcm, int timeout) | ||
858 | { | ||
859 | struct pollfd pfd; | ||
860 | int err; | ||
861 | |||
862 | pfd.fd = pcm->fd; | ||
863 | pfd.events = POLLOUT | POLLERR | POLLNVAL; | ||
864 | |||
865 | do { | ||
866 | /* let's wait for avail or timeout */ | ||
867 | err = poll(&pfd, 1, timeout); | ||
868 | if (err < 0) | ||
869 | return -errno; | ||
870 | |||
871 | /* timeout ? */ | ||
872 | if (err == 0) | ||
873 | return 0; | ||
874 | |||
875 | /* have we been interrupted ? */ | ||
876 | if (errno == -EINTR) | ||
877 | continue; | ||
878 | |||
879 | /* check for any errors */ | ||
880 | if (pfd.revents & (POLLERR | POLLNVAL)) { | ||
881 | switch (pcm_state(pcm)) { | ||
882 | case PCM_STATE_XRUN: | ||
883 | return -EPIPE; | ||
884 | case PCM_STATE_SUSPENDED: | ||
885 | return -ESTRPIPE; | ||
886 | case PCM_STATE_DISCONNECTED: | ||
887 | return -ENODEV; | ||
888 | default: | ||
889 | return -EIO; | ||
890 | } | ||
891 | } | ||
892 | /* poll again if fd not ready for IO */ | ||
893 | } while (!(pfd.revents & (POLLIN | POLLOUT))); | ||
894 | |||
895 | return 1; | ||
896 | } | ||
897 | |||
898 | int pcm_mmap_write(struct pcm *pcm, const void *buffer, unsigned int bytes) | ||
899 | { | ||
900 | int err = 0, frames, avail; | ||
901 | unsigned int offset = 0, count; | ||
902 | |||
903 | if (bytes == 0) | ||
904 | return 0; | ||
905 | |||
906 | count = pcm_bytes_to_frames(pcm, bytes); | ||
907 | |||
908 | while (count > 0) { | ||
909 | |||
910 | /* get the available space for writing new frames */ | ||
911 | avail = pcm_avail_update(pcm); | ||
912 | if (avail < 0) { | ||
913 | fprintf(stderr, "cannot determine available mmap frames"); | ||
914 | return err; | ||
915 | } | ||
916 | |||
917 | /* start the audio if we reach the threshold */ | ||
918 | if (!pcm->running && | ||
919 | (pcm->buffer_size - avail) >= pcm->config.start_threshold) { | ||
920 | if (pcm_start(pcm) < 0) { | ||
921 | fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n", | ||
922 | (unsigned int)pcm->mmap_status->hw_ptr, | ||
923 | (unsigned int)pcm->mmap_control->appl_ptr, | ||
924 | avail); | ||
925 | return -errno; | ||
926 | } | ||
927 | } | ||
928 | |||
929 | /* sleep until we have space to write new frames */ | ||
930 | if (pcm->running && | ||
931 | (unsigned int)avail < pcm->mmap_control->avail_min) { | ||
932 | int time = -1; | ||
933 | |||
934 | if (pcm->flags & PCM_NOIRQ) | ||
935 | time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min) | ||
936 | / pcm->noirq_frames_per_msec; | ||
937 | |||
938 | err = pcm_wait(pcm, time); | ||
939 | if (err < 0) { | ||
940 | pcm->running = 0; | ||
941 | fprintf(stderr, "wait error: hw 0x%x app 0x%x avail 0x%x\n", | ||
942 | (unsigned int)pcm->mmap_status->hw_ptr, | ||
943 | (unsigned int)pcm->mmap_control->appl_ptr, | ||
944 | avail); | ||
945 | pcm->mmap_control->appl_ptr = 0; | ||
946 | return err; | ||
947 | } | ||
948 | continue; | ||
949 | } | ||
950 | |||
951 | frames = count; | ||
952 | if (frames > avail) | ||
953 | frames = avail; | ||
954 | |||
955 | if (!frames) | ||
956 | break; | ||
957 | |||
958 | /* copy frames from buffer */ | ||
959 | frames = pcm_mmap_write_areas(pcm, buffer, offset, frames); | ||
960 | if (frames < 0) { | ||
961 | fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n", | ||
962 | (unsigned int)pcm->mmap_status->hw_ptr, | ||
963 | (unsigned int)pcm->mmap_control->appl_ptr, | ||
964 | avail); | ||
965 | return frames; | ||
966 | } | ||
967 | |||
968 | offset += frames; | ||
969 | count -= frames; | ||
970 | } | ||
971 | |||
972 | return 0; | ||
973 | } | ||