summaryrefslogtreecommitdiff
path: root/firmware/target/hosted/ibasso/tinyalsa/pcm.c
diff options
context:
space:
mode:
authorUdo Schläpfer <rockbox-2014.10@desktopwarrior.net>2015-02-02 21:44:29 +0100
committerUdo Schläpfer <rockbox-2014.10@desktopwarrior.net>2015-02-02 21:57:55 +0100
commitdbabd0d9c34a33bc0c51243ec37f230d117db955 (patch)
tree46de348929ce739702a230a2587fdb5539585753 /firmware/target/hosted/ibasso/tinyalsa/pcm.c
parentcef17e3d59ad93f766e8ee23b1610540a33dfe5e (diff)
downloadrockbox-dbabd0d9c34a33bc0c51243ec37f230d117db955.tar.gz
rockbox-dbabd0d9c34a33bc0c51243ec37f230d117db955.zip
iBasso DX50/DX90: Major code cleanup and reorganization.
Reorganization - Separated iBasso devices from PLATFORM_ANDROID. These are now standlone hosted targets. Most device specific code is in the firmware/target/hosted/ibasso directory. - No dependency on Android SDK, only the Android NDK is needed. 32 bit Android NDK and Android API Level 16. - Separate implementation for each device where feasible. Code cleanup - Rewrite of existing code, from simple reformat to complete reimplementation. - New backlight interface, seperating backlight from touchscreen. - Rewrite of device button handler, removing unneeded code and fixing memory leaks. - New Debug messages interface logging to Android adb logcat (DEBUGF, panicf, logf). - Rewrite of lcd device handler, removing unneeded code and fixing memory leaks. - Rewrite of audiohw device handler/pcm interface, removing unneeded code and fixing memory leaks, enabling 44.1/48kHz pthreaded playback. - Rewrite of power and powermng, proper shutdown, using batterylog results (see http://gerrit.rockbox.org/r/#/c/1047/). - Rewrite of configure (Android NDK) and device specific config. - Rewrite of the Android NDK specific Makefile. Misc - All plugins/games/demos activated. - Update tinyalsa to latest from https://github.com/tinyalsa/tinyalsa. Includes - http://gerrit.rockbox.org/r/#/c/993/ - http://gerrit.rockbox.org/r/#/c/1010/ - http://gerrit.rockbox.org/r/#/c/1035/ Does not include http://gerrit.rockbox.org/r/#/c/1007/ due to new backlight interface and new option for hold switch, touchscreen, physical button interaction. Rockbox needs the iBasso DX50/DX90 loader for startup, see http://gerrit.rockbox.org/r/#/c/1099/ The loader expects Rockbox to be installed in /mnt/sdcard/.rockbox/. If /mnt/sdcard/ is accessed as USB mass storage device, Rockbox will exit gracefully and the loader will restart Rockbox on USB disconnect. Tested on iBasso DX50. Compiled (not tested) for iBasso DX90. Compiled (not tested) for PLATFORM_ANDROID. Change-Id: I5f5e22e68f5b4cf29c28e2b40b2c265f2beb7ab7
Diffstat (limited to 'firmware/target/hosted/ibasso/tinyalsa/pcm.c')
-rw-r--r--firmware/target/hosted/ibasso/tinyalsa/pcm.c1049
1 files changed, 1049 insertions, 0 deletions
diff --git a/firmware/target/hosted/ibasso/tinyalsa/pcm.c b/firmware/target/hosted/ibasso/tinyalsa/pcm.c
new file mode 100644
index 0000000000..0d2f0adf08
--- /dev/null
+++ b/firmware/target/hosted/ibasso/tinyalsa/pcm.c
@@ -0,0 +1,1049 @@
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 <sound/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
54static 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
60static 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
66static 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
71static 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
76static 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
88static 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
96static 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
105static 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
114static 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
124static 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
134static 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
158struct pcm {
159 int fd;
160 unsigned int flags;
161 int running:1;
162 int prepared:1;
163 int underruns;
164 unsigned int buffer_size;
165 unsigned int boundary;
166 char error[PCM_ERROR_MAX];
167 struct pcm_config config;
168 struct snd_pcm_mmap_status *mmap_status;
169 struct snd_pcm_mmap_control *mmap_control;
170 struct snd_pcm_sync_ptr *sync_ptr;
171 void *mmap_buffer;
172 unsigned int noirq_frames_per_msec;
173};
174
175unsigned int pcm_get_buffer_size(struct pcm *pcm)
176{
177 return pcm->buffer_size;
178}
179
180const char* pcm_get_error(struct pcm *pcm)
181{
182 return pcm->error;
183}
184
185static int oops(struct pcm *pcm, int e, const char *fmt, ...)
186{
187 va_list ap;
188 int sz;
189
190 va_start(ap, fmt);
191 vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap);
192 va_end(ap);
193 sz = strlen(pcm->error);
194
195 if (errno)
196 snprintf(pcm->error + sz, PCM_ERROR_MAX - sz,
197 ": %s", strerror(e));
198 return -1;
199}
200
201static unsigned int pcm_format_to_alsa(enum pcm_format format)
202{
203 switch (format) {
204 case PCM_FORMAT_S32_LE:
205 return SNDRV_PCM_FORMAT_S32_LE;
206 case PCM_FORMAT_S8:
207 return SNDRV_PCM_FORMAT_S8;
208 case PCM_FORMAT_S24_LE:
209 return SNDRV_PCM_FORMAT_S24_LE;
210 default:
211 case PCM_FORMAT_S16_LE:
212 return SNDRV_PCM_FORMAT_S16_LE;
213 };
214}
215
216unsigned int pcm_format_to_bits(enum pcm_format format)
217{
218 switch (format) {
219 case PCM_FORMAT_S32_LE:
220 case PCM_FORMAT_S24_LE:
221 return 32;
222 default:
223 case PCM_FORMAT_S16_LE:
224 return 16;
225 };
226}
227
228unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes)
229{
230 return bytes / (pcm->config.channels *
231 (pcm_format_to_bits(pcm->config.format) >> 3));
232}
233
234unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames)
235{
236 return frames * pcm->config.channels *
237 (pcm_format_to_bits(pcm->config.format) >> 3);
238}
239
240static int pcm_sync_ptr(struct pcm *pcm, int flags) {
241 if (pcm->sync_ptr) {
242 pcm->sync_ptr->flags = flags;
243 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0)
244 return -1;
245 }
246 return 0;
247}
248
249static int pcm_hw_mmap_status(struct pcm *pcm) {
250
251 if (pcm->sync_ptr)
252 return 0;
253
254 int page_size = sysconf(_SC_PAGE_SIZE);
255 pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED,
256 pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS);
257 if (pcm->mmap_status == MAP_FAILED)
258 pcm->mmap_status = NULL;
259 if (!pcm->mmap_status)
260 goto mmap_error;
261
262 pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
263 MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
264 if (pcm->mmap_control == MAP_FAILED)
265 pcm->mmap_control = NULL;
266 if (!pcm->mmap_control) {
267 munmap(pcm->mmap_status, page_size);
268 pcm->mmap_status = NULL;
269 goto mmap_error;
270 }
271 pcm->mmap_control->avail_min = 1;
272
273 return 0;
274
275mmap_error:
276
277 pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr));
278 if (!pcm->sync_ptr)
279 return -ENOMEM;
280 pcm->mmap_status = &pcm->sync_ptr->s.status;
281 pcm->mmap_control = &pcm->sync_ptr->c.control;
282 pcm->mmap_control->avail_min = 1;
283 pcm_sync_ptr(pcm, 0);
284
285 return 0;
286}
287
288static void pcm_hw_munmap_status(struct pcm *pcm) {
289 if (pcm->sync_ptr) {
290 free(pcm->sync_ptr);
291 pcm->sync_ptr = NULL;
292 } else {
293 int page_size = sysconf(_SC_PAGE_SIZE);
294 if (pcm->mmap_status)
295 munmap(pcm->mmap_status, page_size);
296 if (pcm->mmap_control)
297 munmap(pcm->mmap_control, page_size);
298 }
299 pcm->mmap_status = NULL;
300 pcm->mmap_control = NULL;
301}
302
303static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,
304 char *buf, unsigned int src_offset,
305 unsigned int frames)
306{
307 int size_bytes = pcm_frames_to_bytes(pcm, frames);
308 int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset);
309 int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset);
310
311 /* interleaved only atm */
312 if (pcm->flags & PCM_IN)
313 memcpy(buf + src_offset_bytes,
314 (char*)pcm->mmap_buffer + pcm_offset_bytes,
315 size_bytes);
316 else
317 memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,
318 buf + src_offset_bytes,
319 size_bytes);
320 return 0;
321}
322
323static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf,
324 unsigned int offset, unsigned int size)
325{
326 void *pcm_areas;
327 int commit;
328 unsigned int pcm_offset, frames, count = 0;
329
330 while (size > 0) {
331 frames = size;
332 pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
333 pcm_areas_copy(pcm, pcm_offset, buf, offset, frames);
334 commit = pcm_mmap_commit(pcm, pcm_offset, frames);
335 if (commit < 0) {
336 oops(pcm, commit, "failed to commit %d frames\n", frames);
337 return commit;
338 }
339
340 offset += commit;
341 count += commit;
342 size -= commit;
343 }
344 return count;
345}
346
347int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,
348 struct timespec *tstamp)
349{
350 int frames;
351 int rc;
352 snd_pcm_uframes_t hw_ptr;
353
354 if (!pcm_is_ready(pcm))
355 return -1;
356
357 rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC);
358 if (rc < 0)
359 return -1;
360
361 if ((pcm->mmap_status->state != PCM_STATE_RUNNING) &&
362 (pcm->mmap_status->state != PCM_STATE_DRAINING))
363 return -1;
364
365 *tstamp = pcm->mmap_status->tstamp;
366 if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0)
367 return -1;
368
369 hw_ptr = pcm->mmap_status->hw_ptr;
370 if (pcm->flags & PCM_IN)
371 frames = hw_ptr - pcm->mmap_control->appl_ptr;
372 else
373 frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
374
375 if (frames < 0)
376 return -1;
377
378 *avail = (unsigned int)frames;
379
380 return 0;
381}
382
383int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
384{
385 struct snd_xferi x;
386
387 if (pcm->flags & PCM_IN)
388 return -EINVAL;
389
390 x.buf = (void*)data;
391 x.frames = count / (pcm->config.channels *
392 pcm_format_to_bits(pcm->config.format) / 8);
393
394 for (;;) {
395 if (!pcm->running) {
396 int prepare_error = pcm_prepare(pcm);
397 if (prepare_error)
398 return prepare_error;
399 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
400 return oops(pcm, errno, "cannot write initial data");
401 pcm->running = 1;
402 return 0;
403 }
404 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
405 pcm->prepared = 0;
406 pcm->running = 0;
407 if (errno == EPIPE) {
408 /* we failed to make our window -- try to restart if we are
409 * allowed to do so. Otherwise, simply allow the EPIPE error to
410 * propagate up to the app level */
411 pcm->underruns++;
412 if (pcm->flags & PCM_NORESTART)
413 return -EPIPE;
414 continue;
415 }
416 return oops(pcm, errno, "cannot write stream data");
417 }
418 return 0;
419 }
420}
421
422int pcm_read(struct pcm *pcm, void *data, unsigned int count)
423{
424 struct snd_xferi x;
425
426 if (!(pcm->flags & PCM_IN))
427 return -EINVAL;
428
429 x.buf = data;
430 x.frames = count / (pcm->config.channels *
431 pcm_format_to_bits(pcm->config.format) / 8);
432
433 for (;;) {
434 if (!pcm->running) {
435 if (pcm_start(pcm) < 0) {
436 fprintf(stderr, "start error");
437 return -errno;
438 }
439 }
440 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
441 pcm->prepared = 0;
442 pcm->running = 0;
443 if (errno == EPIPE) {
444 /* we failed to make our window -- try to restart */
445 pcm->underruns++;
446 continue;
447 }
448 return oops(pcm, errno, "cannot read stream data");
449 }
450 return 0;
451 }
452}
453
454static struct pcm bad_pcm = {
455 .fd = -1,
456};
457
458struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
459 unsigned int flags)
460{
461 struct snd_pcm_hw_params *params;
462 char fn[256];
463 int fd;
464
465 snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
466 flags & PCM_IN ? 'c' : 'p');
467
468 fd = open(fn, O_RDWR);
469 if (fd < 0) {
470 fprintf(stderr, "cannot open device '%s'\n", fn);
471 goto err_open;
472 }
473
474 params = calloc(1, sizeof(struct snd_pcm_hw_params));
475 if (!params)
476 goto err_calloc;
477
478 param_init(params);
479 if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) {
480 fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno);
481 goto err_hw_refine;
482 }
483
484 close(fd);
485
486 return (struct pcm_params *)params;
487
488err_hw_refine:
489 free(params);
490err_calloc:
491 close(fd);
492err_open:
493 return NULL;
494}
495
496void pcm_params_free(struct pcm_params *pcm_params)
497{
498 struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
499
500 if (params)
501 free(params);
502}
503
504static int pcm_param_to_alsa(enum pcm_param param)
505{
506 switch (param) {
507 case PCM_PARAM_ACCESS:
508 return SNDRV_PCM_HW_PARAM_ACCESS;
509 case PCM_PARAM_FORMAT:
510 return SNDRV_PCM_HW_PARAM_FORMAT;
511 case PCM_PARAM_SUBFORMAT:
512 return SNDRV_PCM_HW_PARAM_SUBFORMAT;
513 case PCM_PARAM_SAMPLE_BITS:
514 return SNDRV_PCM_HW_PARAM_SAMPLE_BITS;
515 break;
516 case PCM_PARAM_FRAME_BITS:
517 return SNDRV_PCM_HW_PARAM_FRAME_BITS;
518 break;
519 case PCM_PARAM_CHANNELS:
520 return SNDRV_PCM_HW_PARAM_CHANNELS;
521 break;
522 case PCM_PARAM_RATE:
523 return SNDRV_PCM_HW_PARAM_RATE;
524 break;
525 case PCM_PARAM_PERIOD_TIME:
526 return SNDRV_PCM_HW_PARAM_PERIOD_TIME;
527 break;
528 case PCM_PARAM_PERIOD_SIZE:
529 return SNDRV_PCM_HW_PARAM_PERIOD_SIZE;
530 break;
531 case PCM_PARAM_PERIOD_BYTES:
532 return SNDRV_PCM_HW_PARAM_PERIOD_BYTES;
533 break;
534 case PCM_PARAM_PERIODS:
535 return SNDRV_PCM_HW_PARAM_PERIODS;
536 break;
537 case PCM_PARAM_BUFFER_TIME:
538 return SNDRV_PCM_HW_PARAM_BUFFER_TIME;
539 break;
540 case PCM_PARAM_BUFFER_SIZE:
541 return SNDRV_PCM_HW_PARAM_BUFFER_SIZE;
542 break;
543 case PCM_PARAM_BUFFER_BYTES:
544 return SNDRV_PCM_HW_PARAM_BUFFER_BYTES;
545 break;
546 case PCM_PARAM_TICK_TIME:
547 return SNDRV_PCM_HW_PARAM_TICK_TIME;
548 break;
549
550 default:
551 return -1;
552 }
553}
554
555struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params,
556 enum pcm_param param)
557{
558 int p;
559 struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
560 if (params == NULL) {
561 return NULL;
562 }
563
564 p = pcm_param_to_alsa(param);
565 if (p < 0 || !param_is_mask(p)) {
566 return NULL;
567 }
568
569 return (struct pcm_mask *)param_to_mask(params, p);
570}
571
572unsigned int pcm_params_get_min(struct pcm_params *pcm_params,
573 enum pcm_param param)
574{
575 struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
576 int p;
577
578 if (!params)
579 return 0;
580
581 p = pcm_param_to_alsa(param);
582 if (p < 0)
583 return 0;
584
585 return param_get_min(params, p);
586}
587
588unsigned int pcm_params_get_max(struct pcm_params *pcm_params,
589 enum pcm_param param)
590{
591 struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
592 int p;
593
594 if (!params)
595 return 0;
596
597 p = pcm_param_to_alsa(param);
598 if (p < 0)
599 return 0;
600
601 return param_get_max(params, p);
602}
603
604int pcm_close(struct pcm *pcm)
605{
606 if (pcm == &bad_pcm)
607 return 0;
608
609 pcm_hw_munmap_status(pcm);
610
611 if (pcm->flags & PCM_MMAP) {
612 pcm_stop(pcm);
613 munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
614 }
615
616 if (pcm->fd >= 0)
617 close(pcm->fd);
618 pcm->prepared = 0;
619 pcm->running = 0;
620 pcm->buffer_size = 0;
621 pcm->fd = -1;
622 free(pcm);
623 return 0;
624}
625
626struct pcm *pcm_open(unsigned int card, unsigned int device,
627 unsigned int flags, struct pcm_config *config)
628{
629 struct pcm *pcm;
630 struct snd_pcm_info info;
631 struct snd_pcm_hw_params params;
632 struct snd_pcm_sw_params sparams;
633 char fn[256];
634 int rc;
635
636 pcm = calloc(1, sizeof(struct pcm));
637 if (!pcm || !config)
638 return &bad_pcm; /* TODO: could support default config here */
639
640 pcm->config = *config;
641
642 snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
643 flags & PCM_IN ? 'c' : 'p');
644
645 pcm->flags = flags;
646 pcm->fd = open(fn, O_RDWR);
647 if (pcm->fd < 0) {
648 oops(pcm, errno, "cannot open device '%s'", fn);
649 return pcm;
650 }
651
652 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
653 oops(pcm, errno, "cannot get info");
654 goto fail_close;
655 }
656
657 param_init(&params);
658 param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
659 pcm_format_to_alsa(config->format));
660 param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
661 SNDRV_PCM_SUBFORMAT_STD);
662 param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
663 param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
664 pcm_format_to_bits(config->format));
665 param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
666 pcm_format_to_bits(config->format) * config->channels);
667 param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
668 config->channels);
669 param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
670 param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);
671
672 if (flags & PCM_NOIRQ) {
673
674 if (!(flags & PCM_MMAP)) {
675 oops(pcm, -EINVAL, "noirq only currently supported with mmap().");
676 goto fail;
677 }
678
679 params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
680 pcm->noirq_frames_per_msec = config->rate / 1000;
681 }
682
683 if (flags & PCM_MMAP)
684 param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
685 SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
686 else
687 param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
688 SNDRV_PCM_ACCESS_RW_INTERLEAVED);
689
690 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
691 oops(pcm, errno, "cannot set hw params");
692 goto fail_close;
693 }
694
695 /* get our refined hw_params */
696 config->period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
697 config->period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);
698 pcm->buffer_size = config->period_count * config->period_size;
699
700 if (flags & PCM_MMAP) {
701 pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
702 PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
703 if (pcm->mmap_buffer == MAP_FAILED) {
704 oops(pcm, -errno, "failed to mmap buffer %d bytes\n",
705 pcm_frames_to_bytes(pcm, pcm->buffer_size));
706 goto fail_close;
707 }
708 }
709
710
711 memset(&sparams, 0, sizeof(sparams));
712 sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
713 sparams.period_step = 1;
714 sparams.avail_min = 1;
715
716 if (!config->start_threshold) {
717 if (pcm->flags & PCM_IN)
718 pcm->config.start_threshold = sparams.start_threshold = 1;
719 else
720 pcm->config.start_threshold = sparams.start_threshold =
721 config->period_count * config->period_size / 2;
722 } else
723 sparams.start_threshold = config->start_threshold;
724
725 /* pick a high stop threshold - todo: does this need further tuning */
726 if (!config->stop_threshold) {
727 if (pcm->flags & PCM_IN)
728 pcm->config.stop_threshold = sparams.stop_threshold =
729 config->period_count * config->period_size * 10;
730 else
731 pcm->config.stop_threshold = sparams.stop_threshold =
732 config->period_count * config->period_size;
733 }
734 else
735 sparams.stop_threshold = config->stop_threshold;
736
737 sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
738 sparams.silence_size = 0;
739 sparams.silence_threshold = config->silence_threshold;
740 pcm->boundary = sparams.boundary = pcm->buffer_size;
741
742 while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
743 pcm->boundary *= 2;
744
745 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
746 oops(pcm, errno, "cannot set sw params");
747 goto fail;
748 }
749
750 rc = pcm_hw_mmap_status(pcm);
751 if (rc < 0) {
752 oops(pcm, rc, "mmap status failed");
753 goto fail;
754 }
755
756#ifdef SNDRV_PCM_IOCTL_TTSTAMP
757 if (pcm->flags & PCM_MONOTONIC) {
758 int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
759 rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
760 if (rc < 0) {
761 oops(pcm, rc, "cannot set timestamp type");
762 goto fail;
763 }
764 }
765#endif
766
767 pcm->underruns = 0;
768 return pcm;
769
770fail:
771 if (flags & PCM_MMAP)
772 munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
773fail_close:
774 close(pcm->fd);
775 pcm->fd = -1;
776 return pcm;
777}
778
779int pcm_is_ready(struct pcm *pcm)
780{
781 return pcm->fd >= 0;
782}
783
784int pcm_prepare(struct pcm *pcm)
785{
786 if (pcm->prepared)
787 return 0;
788
789 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0)
790 return oops(pcm, errno, "cannot prepare channel");
791
792 pcm->prepared = 1;
793 return 0;
794}
795
796int pcm_start(struct pcm *pcm)
797{
798 int prepare_error = pcm_prepare(pcm);
799 if (prepare_error)
800 return prepare_error;
801
802 if (pcm->flags & PCM_MMAP)
803 pcm_sync_ptr(pcm, 0);
804
805 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0)
806 return oops(pcm, errno, "cannot start channel");
807
808 pcm->running = 1;
809 return 0;
810}
811
812int pcm_stop(struct pcm *pcm)
813{
814 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0)
815 return oops(pcm, errno, "cannot stop channel");
816
817 pcm->prepared = 0;
818 pcm->running = 0;
819 return 0;
820}
821
822static inline int pcm_mmap_playback_avail(struct pcm *pcm)
823{
824 int avail;
825
826 avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
827
828 if (avail < 0)
829 avail += pcm->boundary;
830 else if (avail > (int)pcm->boundary)
831 avail -= pcm->boundary;
832
833 return avail;
834}
835
836static inline int pcm_mmap_capture_avail(struct pcm *pcm)
837{
838 int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr;
839 if (avail < 0)
840 avail += pcm->boundary;
841 return avail;
842}
843
844static inline int pcm_mmap_avail(struct pcm *pcm)
845{
846 pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);
847 if (pcm->flags & PCM_IN)
848 return pcm_mmap_capture_avail(pcm);
849 else
850 return pcm_mmap_playback_avail(pcm);
851}
852
853static void pcm_mmap_appl_forward(struct pcm *pcm, int frames)
854{
855 unsigned int appl_ptr = pcm->mmap_control->appl_ptr;
856 appl_ptr += frames;
857
858 /* check for boundary wrap */
859 if (appl_ptr > pcm->boundary)
860 appl_ptr -= pcm->boundary;
861 pcm->mmap_control->appl_ptr = appl_ptr;
862}
863
864int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset,
865 unsigned int *frames)
866{
867 unsigned int continuous, copy_frames, avail;
868
869 /* return the mmap buffer */
870 *areas = pcm->mmap_buffer;
871
872 /* and the application offset in frames */
873 *offset = pcm->mmap_control->appl_ptr % pcm->buffer_size;
874
875 avail = pcm_mmap_avail(pcm);
876 if (avail > pcm->buffer_size)
877 avail = pcm->buffer_size;
878 continuous = pcm->buffer_size - *offset;
879
880 /* we can only copy frames if the are availabale and continuos */
881 copy_frames = *frames;
882 if (copy_frames > avail)
883 copy_frames = avail;
884 if (copy_frames > continuous)
885 copy_frames = continuous;
886 *frames = copy_frames;
887
888 return 0;
889}
890
891int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames)
892{
893 (void) offset;
894 /* update the application pointer in userspace and kernel */
895 pcm_mmap_appl_forward(pcm, frames);
896 pcm_sync_ptr(pcm, 0);
897
898 return frames;
899}
900
901int pcm_avail_update(struct pcm *pcm)
902{
903 pcm_sync_ptr(pcm, 0);
904 return pcm_mmap_avail(pcm);
905}
906
907int pcm_state(struct pcm *pcm)
908{
909 int err = pcm_sync_ptr(pcm, 0);
910 if (err < 0)
911 return err;
912
913 return pcm->mmap_status->state;
914}
915
916int pcm_wait(struct pcm *pcm, int timeout)
917{
918 struct pollfd pfd;
919 int err;
920
921 pfd.fd = pcm->fd;
922 pfd.events = POLLOUT | POLLERR | POLLNVAL;
923
924 do {
925 /* let's wait for avail or timeout */
926 err = poll(&pfd, 1, timeout);
927 if (err < 0)
928 return -errno;
929
930 /* timeout ? */
931 if (err == 0)
932 return 0;
933
934 /* have we been interrupted ? */
935 if (errno == -EINTR)
936 continue;
937
938 /* check for any errors */
939 if (pfd.revents & (POLLERR | POLLNVAL)) {
940 switch (pcm_state(pcm)) {
941 case PCM_STATE_XRUN:
942 return -EPIPE;
943 case PCM_STATE_SUSPENDED:
944 return -ESTRPIPE;
945 case PCM_STATE_DISCONNECTED:
946 return -ENODEV;
947 default:
948 return -EIO;
949 }
950 }
951 /* poll again if fd not ready for IO */
952 } while (!(pfd.revents & (POLLIN | POLLOUT)));
953
954 return 1;
955}
956
957int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes)
958{
959 int err = 0, frames, avail;
960 unsigned int offset = 0, count;
961
962 if (bytes == 0)
963 return 0;
964
965 count = pcm_bytes_to_frames(pcm, bytes);
966
967 while (count > 0) {
968
969 /* get the available space for writing new frames */
970 avail = pcm_avail_update(pcm);
971 if (avail < 0) {
972 fprintf(stderr, "cannot determine available mmap frames");
973 return err;
974 }
975
976 /* start the audio if we reach the threshold */
977 if (!pcm->running &&
978 (pcm->buffer_size - avail) >= pcm->config.start_threshold) {
979 if (pcm_start(pcm) < 0) {
980 fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n",
981 (unsigned int)pcm->mmap_status->hw_ptr,
982 (unsigned int)pcm->mmap_control->appl_ptr,
983 avail);
984 return -errno;
985 }
986 }
987
988 /* sleep until we have space to write new frames */
989 if (pcm->running &&
990 (unsigned int)avail < pcm->mmap_control->avail_min) {
991 int time = -1;
992
993 if (pcm->flags & PCM_NOIRQ)
994 time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min)
995 / pcm->noirq_frames_per_msec;
996
997 err = pcm_wait(pcm, time);
998 if (err < 0) {
999 pcm->prepared = 0;
1000 pcm->running = 0;
1001 fprintf(stderr, "wait error: hw 0x%x app 0x%x avail 0x%x\n",
1002 (unsigned int)pcm->mmap_status->hw_ptr,
1003 (unsigned int)pcm->mmap_control->appl_ptr,
1004 avail);
1005 pcm->mmap_control->appl_ptr = 0;
1006 return err;
1007 }
1008 continue;
1009 }
1010
1011 frames = count;
1012 if (frames > avail)
1013 frames = avail;
1014
1015 if (!frames)
1016 break;
1017
1018 /* copy frames from buffer */
1019 frames = pcm_mmap_transfer_areas(pcm, (void *)buffer, offset, frames);
1020 if (frames < 0) {
1021 fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n",
1022 (unsigned int)pcm->mmap_status->hw_ptr,
1023 (unsigned int)pcm->mmap_control->appl_ptr,
1024 avail);
1025 return frames;
1026 }
1027
1028 offset += frames;
1029 count -= frames;
1030 }
1031
1032 return 0;
1033}
1034
1035int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count)
1036{
1037 if ((~pcm->flags) & (PCM_OUT | PCM_MMAP))
1038 return -ENOSYS;
1039
1040 return pcm_mmap_transfer(pcm, (void *)data, count);
1041}
1042
1043int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count)
1044{
1045 if ((~pcm->flags) & (PCM_IN | PCM_MMAP))
1046 return -ENOSYS;
1047
1048 return pcm_mmap_transfer(pcm, data, count);
1049}