blob: d58d6a34fd9cefbdd721a421375985e31977a7fa [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>
Liam Girdwood6be28f12011-10-13 12:59:51 -070036#include <poll.h>
Simon Wilson79d39652011-05-25 13:44:23 -070037
38#include <sys/ioctl.h>
39#include <sys/mman.h>
40#include <sys/time.h>
Liam Girdwood6be28f12011-10-13 12:59:51 -070041#include <limits.h>
Simon Wilson79d39652011-05-25 13:44:23 -070042
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
Liam Girdwood6be28f12011-10-13 12:59:51 -070052#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2)
Simon Wilson79d39652011-05-25 13:44:23 -070053
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
Simon Wilson43544882012-10-31 12:52:39 -070096static 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
Simon Wilson79d39652011-05-25 13:44:23 -0700114static 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
Liam Girdwood6be28f12011-10-13 12:59:51 -0700124static 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
Simon Wilson79d39652011-05-25 13:44:23 -0700134static void param_init(struct snd_pcm_hw_params *p)
135{
136 int n;
Simon Wilson98c1f162011-06-07 16:12:32 -0700137
Simon Wilson79d39652011-05-25 13:44:23 -0700138 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 }
Simon Wilson43544882012-10-31 12:52:39 -0700151 p->rmask = ~0U;
152 p->cmask = 0;
153 p->info = ~0U;
Simon Wilson79d39652011-05-25 13:44:23 -0700154}
155
156#define PCM_ERROR_MAX 128
157
158struct pcm {
159 int fd;
160 unsigned int flags;
161 int running:1;
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530162 int prepared:1;
Simon Wilson79d39652011-05-25 13:44:23 -0700163 int underruns;
164 unsigned int buffer_size;
Liam Girdwood6be28f12011-10-13 12:59:51 -0700165 unsigned int boundary;
Simon Wilson79d39652011-05-25 13:44:23 -0700166 char error[PCM_ERROR_MAX];
167 struct pcm_config config;
Eric Laurent40b018e2011-06-18 10:10:23 -0700168 struct snd_pcm_mmap_status *mmap_status;
169 struct snd_pcm_mmap_control *mmap_control;
170 struct snd_pcm_sync_ptr *sync_ptr;
Liam Girdwood6be28f12011-10-13 12:59:51 -0700171 void *mmap_buffer;
172 unsigned int noirq_frames_per_msec;
Hardik T Shah9ecb93f2014-04-10 18:03:52 +0530173 long pcm_delay;
David Wagner4cddf192014-04-02 15:12:54 +0200174 unsigned int subdevice;
Simon Wilson79d39652011-05-25 13:44:23 -0700175};
176
Simon Wilson851aa5c2011-05-30 21:18:26 -0700177unsigned int pcm_get_buffer_size(struct pcm *pcm)
Simon Wilson79d39652011-05-25 13:44:23 -0700178{
179 return pcm->buffer_size;
180}
181
182const char* pcm_get_error(struct pcm *pcm)
183{
184 return pcm->error;
185}
186
David Wagner4cddf192014-04-02 15:12:54 +0200187unsigned int pcm_get_subdevice(struct pcm *pcm)
188{
189 return pcm->subdevice;
190}
191
Simon Wilson79d39652011-05-25 13:44:23 -0700192static int oops(struct pcm *pcm, int e, const char *fmt, ...)
193{
194 va_list ap;
195 int sz;
196
197 va_start(ap, fmt);
198 vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap);
199 va_end(ap);
200 sz = strlen(pcm->error);
201
202 if (errno)
203 snprintf(pcm->error + sz, PCM_ERROR_MAX - sz,
204 ": %s", strerror(e));
205 return -1;
206}
207
Simon Wilsonbc03b622011-06-15 17:19:01 -0700208static unsigned int pcm_format_to_alsa(enum pcm_format format)
209{
210 switch (format) {
211 case PCM_FORMAT_S32_LE:
212 return SNDRV_PCM_FORMAT_S32_LE;
Gabriel M. Beddingfield2a274a12012-05-02 11:51:20 -0500213 case PCM_FORMAT_S8:
214 return SNDRV_PCM_FORMAT_S8;
215 case PCM_FORMAT_S24_LE:
216 return SNDRV_PCM_FORMAT_S24_LE;
Simon Wilsonbc03b622011-06-15 17:19:01 -0700217 default:
218 case PCM_FORMAT_S16_LE:
219 return SNDRV_PCM_FORMAT_S16_LE;
220 };
221}
222
Simon Wilson7136cf72013-07-17 10:30:35 -0700223unsigned int pcm_format_to_bits(enum pcm_format format)
Simon Wilsonbc03b622011-06-15 17:19:01 -0700224{
225 switch (format) {
226 case PCM_FORMAT_S32_LE:
Simon Wilson7136cf72013-07-17 10:30:35 -0700227 case PCM_FORMAT_S24_LE:
Simon Wilsonbc03b622011-06-15 17:19:01 -0700228 return 32;
229 default:
230 case PCM_FORMAT_S16_LE:
231 return 16;
232 };
233}
234
Liam Girdwood6be28f12011-10-13 12:59:51 -0700235unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes)
236{
237 return bytes / (pcm->config.channels *
238 (pcm_format_to_bits(pcm->config.format) >> 3));
239}
240
241unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames)
242{
243 return frames * pcm->config.channels *
244 (pcm_format_to_bits(pcm->config.format) >> 3);
245}
246
Eric Laurent40b018e2011-06-18 10:10:23 -0700247static int pcm_sync_ptr(struct pcm *pcm, int flags) {
248 if (pcm->sync_ptr) {
249 pcm->sync_ptr->flags = flags;
250 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0)
251 return -1;
252 }
253 return 0;
254}
255
256static int pcm_hw_mmap_status(struct pcm *pcm) {
257
258 if (pcm->sync_ptr)
259 return 0;
260
261 int page_size = sysconf(_SC_PAGE_SIZE);
262 pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED,
263 pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS);
264 if (pcm->mmap_status == MAP_FAILED)
265 pcm->mmap_status = NULL;
266 if (!pcm->mmap_status)
267 goto mmap_error;
268
269 pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
270 MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
271 if (pcm->mmap_control == MAP_FAILED)
272 pcm->mmap_control = NULL;
273 if (!pcm->mmap_control) {
274 munmap(pcm->mmap_status, page_size);
275 pcm->mmap_status = NULL;
276 goto mmap_error;
277 }
278 pcm->mmap_control->avail_min = 1;
279
280 return 0;
281
282mmap_error:
283
284 pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr));
285 if (!pcm->sync_ptr)
286 return -ENOMEM;
287 pcm->mmap_status = &pcm->sync_ptr->s.status;
288 pcm->mmap_control = &pcm->sync_ptr->c.control;
289 pcm->mmap_control->avail_min = 1;
290 pcm_sync_ptr(pcm, 0);
291
292 return 0;
293}
294
295static void pcm_hw_munmap_status(struct pcm *pcm) {
296 if (pcm->sync_ptr) {
297 free(pcm->sync_ptr);
298 pcm->sync_ptr = NULL;
299 } else {
300 int page_size = sysconf(_SC_PAGE_SIZE);
301 if (pcm->mmap_status)
302 munmap(pcm->mmap_status, page_size);
303 if (pcm->mmap_control)
304 munmap(pcm->mmap_control, page_size);
305 }
306 pcm->mmap_status = NULL;
307 pcm->mmap_control = NULL;
308}
309
Liam Girdwood6be28f12011-10-13 12:59:51 -0700310static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,
Eric Laurentbb7c5df2013-09-16 14:31:17 -0700311 char *buf, unsigned int src_offset,
Liam Girdwood6be28f12011-10-13 12:59:51 -0700312 unsigned int frames)
313{
314 int size_bytes = pcm_frames_to_bytes(pcm, frames);
315 int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset);
316 int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset);
317
318 /* interleaved only atm */
Eric Laurentbb7c5df2013-09-16 14:31:17 -0700319 if (pcm->flags & PCM_IN)
320 memcpy(buf + src_offset_bytes,
321 (char*)pcm->mmap_buffer + pcm_offset_bytes,
322 size_bytes);
323 else
324 memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,
325 buf + src_offset_bytes,
326 size_bytes);
Liam Girdwood6be28f12011-10-13 12:59:51 -0700327 return 0;
328}
329
Eric Laurentbb7c5df2013-09-16 14:31:17 -0700330static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf,
Liam Girdwood6be28f12011-10-13 12:59:51 -0700331 unsigned int offset, unsigned int size)
332{
333 void *pcm_areas;
334 int commit;
335 unsigned int pcm_offset, frames, count = 0;
336
337 while (size > 0) {
338 frames = size;
339 pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
Eric Laurentbb7c5df2013-09-16 14:31:17 -0700340 pcm_areas_copy(pcm, pcm_offset, buf, offset, frames);
Liam Girdwood6be28f12011-10-13 12:59:51 -0700341 commit = pcm_mmap_commit(pcm, pcm_offset, frames);
342 if (commit < 0) {
343 oops(pcm, commit, "failed to commit %d frames\n", frames);
344 return commit;
345 }
346
347 offset += commit;
348 count += commit;
349 size -= commit;
350 }
351 return count;
352}
353
Eric Laurent40b018e2011-06-18 10:10:23 -0700354int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,
355 struct timespec *tstamp)
356{
357 int frames;
358 int rc;
359 snd_pcm_uframes_t hw_ptr;
360
361 if (!pcm_is_ready(pcm))
362 return -1;
363
364 rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC);
365 if (rc < 0)
366 return -1;
367
Eric Laurent7db48582011-11-17 11:47:59 -0800368 if ((pcm->mmap_status->state != PCM_STATE_RUNNING) &&
369 (pcm->mmap_status->state != PCM_STATE_DRAINING))
Eric Laurentee9ba872011-11-15 19:04:03 -0800370 return -1;
371
Eric Laurent40b018e2011-06-18 10:10:23 -0700372 *tstamp = pcm->mmap_status->tstamp;
373 if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0)
374 return -1;
375
376 hw_ptr = pcm->mmap_status->hw_ptr;
377 if (pcm->flags & PCM_IN)
378 frames = hw_ptr - pcm->mmap_control->appl_ptr;
379 else
380 frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
381
382 if (frames < 0)
383 return -1;
384
385 *avail = (unsigned int)frames;
386
387 return 0;
388}
389
Mark Brown6bbe77a2012-02-10 22:07:09 +0000390int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
Simon Wilson79d39652011-05-25 13:44:23 -0700391{
392 struct snd_xferi x;
393
394 if (pcm->flags & PCM_IN)
395 return -EINVAL;
396
Mark Brown6bbe77a2012-02-10 22:07:09 +0000397 x.buf = (void*)data;
Simon Wilsonbc03b622011-06-15 17:19:01 -0700398 x.frames = count / (pcm->config.channels *
399 pcm_format_to_bits(pcm->config.format) / 8);
Simon Wilson79d39652011-05-25 13:44:23 -0700400
401 for (;;) {
402 if (!pcm->running) {
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530403 int prepare_error = pcm_prepare(pcm);
404 if (prepare_error)
405 return prepare_error;
Simon Wilson79d39652011-05-25 13:44:23 -0700406 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
407 return oops(pcm, errno, "cannot write initial data");
408 pcm->running = 1;
409 return 0;
410 }
411 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530412 pcm->prepared = 0;
Simon Wilson79d39652011-05-25 13:44:23 -0700413 pcm->running = 0;
414 if (errno == EPIPE) {
John Grossmanb6db70a2012-04-03 16:37:38 -0700415 /* we failed to make our window -- try to restart if we are
416 * allowed to do so. Otherwise, simply allow the EPIPE error to
417 * propagate up to the app level */
Simon Wilson79d39652011-05-25 13:44:23 -0700418 pcm->underruns++;
John Grossmanb6db70a2012-04-03 16:37:38 -0700419 if (pcm->flags & PCM_NORESTART)
420 return -EPIPE;
Simon Wilson79d39652011-05-25 13:44:23 -0700421 continue;
422 }
423 return oops(pcm, errno, "cannot write stream data");
424 }
425 return 0;
426 }
427}
428
429int pcm_read(struct pcm *pcm, void *data, unsigned int count)
430{
431 struct snd_xferi x;
432
433 if (!(pcm->flags & PCM_IN))
434 return -EINVAL;
435
436 x.buf = data;
Simon Wilsonbc03b622011-06-15 17:19:01 -0700437 x.frames = count / (pcm->config.channels *
438 pcm_format_to_bits(pcm->config.format) / 8);
Simon Wilson79d39652011-05-25 13:44:23 -0700439
440 for (;;) {
441 if (!pcm->running) {
Keunyoung2581a1e2012-05-10 10:50:00 -0700442 if (pcm_start(pcm) < 0) {
443 fprintf(stderr, "start error");
444 return -errno;
445 }
Simon Wilson79d39652011-05-25 13:44:23 -0700446 }
447 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530448 pcm->prepared = 0;
Simon Wilson79d39652011-05-25 13:44:23 -0700449 pcm->running = 0;
450 if (errno == EPIPE) {
451 /* we failed to make our window -- try to restart */
452 pcm->underruns++;
453 continue;
454 }
455 return oops(pcm, errno, "cannot read stream data");
456 }
457 return 0;
458 }
459}
460
461static struct pcm bad_pcm = {
462 .fd = -1,
463};
464
Simon Wilson43544882012-10-31 12:52:39 -0700465struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
466 unsigned int flags)
467{
468 struct snd_pcm_hw_params *params;
469 char fn[256];
470 int fd;
471
472 snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
473 flags & PCM_IN ? 'c' : 'p');
474
475 fd = open(fn, O_RDWR);
476 if (fd < 0) {
477 fprintf(stderr, "cannot open device '%s'\n", fn);
478 goto err_open;
479 }
480
481 params = calloc(1, sizeof(struct snd_pcm_hw_params));
482 if (!params)
483 goto err_calloc;
484
485 param_init(params);
486 if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) {
487 fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno);
488 goto err_hw_refine;
489 }
490
491 close(fd);
492
493 return (struct pcm_params *)params;
494
495err_hw_refine:
496 free(params);
497err_calloc:
498 close(fd);
499err_open:
500 return NULL;
501}
502
503void pcm_params_free(struct pcm_params *pcm_params)
504{
505 struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
506
507 if (params)
508 free(params);
509}
510
511static int pcm_param_to_alsa(enum pcm_param param)
512{
513 switch (param) {
Andy Hungad807622014-03-10 18:08:15 -0700514 case PCM_PARAM_ACCESS:
515 return SNDRV_PCM_HW_PARAM_ACCESS;
516 case PCM_PARAM_FORMAT:
517 return SNDRV_PCM_HW_PARAM_FORMAT;
518 case PCM_PARAM_SUBFORMAT:
519 return SNDRV_PCM_HW_PARAM_SUBFORMAT;
Simon Wilson43544882012-10-31 12:52:39 -0700520 case PCM_PARAM_SAMPLE_BITS:
521 return SNDRV_PCM_HW_PARAM_SAMPLE_BITS;
522 break;
523 case PCM_PARAM_FRAME_BITS:
524 return SNDRV_PCM_HW_PARAM_FRAME_BITS;
525 break;
526 case PCM_PARAM_CHANNELS:
527 return SNDRV_PCM_HW_PARAM_CHANNELS;
528 break;
529 case PCM_PARAM_RATE:
530 return SNDRV_PCM_HW_PARAM_RATE;
531 break;
532 case PCM_PARAM_PERIOD_TIME:
533 return SNDRV_PCM_HW_PARAM_PERIOD_TIME;
534 break;
535 case PCM_PARAM_PERIOD_SIZE:
536 return SNDRV_PCM_HW_PARAM_PERIOD_SIZE;
537 break;
538 case PCM_PARAM_PERIOD_BYTES:
539 return SNDRV_PCM_HW_PARAM_PERIOD_BYTES;
540 break;
541 case PCM_PARAM_PERIODS:
542 return SNDRV_PCM_HW_PARAM_PERIODS;
543 break;
544 case PCM_PARAM_BUFFER_TIME:
545 return SNDRV_PCM_HW_PARAM_BUFFER_TIME;
546 break;
547 case PCM_PARAM_BUFFER_SIZE:
548 return SNDRV_PCM_HW_PARAM_BUFFER_SIZE;
549 break;
550 case PCM_PARAM_BUFFER_BYTES:
551 return SNDRV_PCM_HW_PARAM_BUFFER_BYTES;
552 break;
553 case PCM_PARAM_TICK_TIME:
554 return SNDRV_PCM_HW_PARAM_TICK_TIME;
555 break;
556
557 default:
558 return -1;
559 }
560}
561
Andy Hungad807622014-03-10 18:08:15 -0700562struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params,
563 enum pcm_param param)
564{
565 int p;
566 struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
567 if (params == NULL) {
568 return NULL;
569 }
570
571 p = pcm_param_to_alsa(param);
572 if (p < 0 || !param_is_mask(p)) {
573 return NULL;
574 }
575
576 return (struct pcm_mask *)param_to_mask(params, p);
577}
578
Simon Wilson43544882012-10-31 12:52:39 -0700579unsigned int pcm_params_get_min(struct pcm_params *pcm_params,
580 enum pcm_param param)
581{
582 struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
583 int p;
584
585 if (!params)
586 return 0;
587
588 p = pcm_param_to_alsa(param);
589 if (p < 0)
590 return 0;
591
592 return param_get_min(params, p);
593}
594
595unsigned int pcm_params_get_max(struct pcm_params *pcm_params,
596 enum pcm_param param)
597{
598 struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
599 int p;
600
601 if (!params)
602 return 0;
603
604 p = pcm_param_to_alsa(param);
605 if (p < 0)
606 return 0;
607
608 return param_get_max(params, p);
609}
610
Simon Wilson79d39652011-05-25 13:44:23 -0700611int pcm_close(struct pcm *pcm)
612{
613 if (pcm == &bad_pcm)
614 return 0;
615
Eric Laurent40b018e2011-06-18 10:10:23 -0700616 pcm_hw_munmap_status(pcm);
617
Liam Girdwood6be28f12011-10-13 12:59:51 -0700618 if (pcm->flags & PCM_MMAP) {
619 pcm_stop(pcm);
620 munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
621 }
622
Simon Wilson79d39652011-05-25 13:44:23 -0700623 if (pcm->fd >= 0)
624 close(pcm->fd);
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530625 pcm->prepared = 0;
Simon Wilson79d39652011-05-25 13:44:23 -0700626 pcm->running = 0;
627 pcm->buffer_size = 0;
628 pcm->fd = -1;
Eric Laurent40b018e2011-06-18 10:10:23 -0700629 free(pcm);
Simon Wilson79d39652011-05-25 13:44:23 -0700630 return 0;
631}
632
Simon Wilson1bd580f2011-06-02 15:58:41 -0700633struct pcm *pcm_open(unsigned int card, unsigned int device,
634 unsigned int flags, struct pcm_config *config)
Simon Wilson79d39652011-05-25 13:44:23 -0700635{
Simon Wilson79d39652011-05-25 13:44:23 -0700636 struct pcm *pcm;
637 struct snd_pcm_info info;
638 struct snd_pcm_hw_params params;
639 struct snd_pcm_sw_params sparams;
Simon Wilson1bd580f2011-06-02 15:58:41 -0700640 char fn[256];
Eric Laurent40b018e2011-06-18 10:10:23 -0700641 int rc;
Simon Wilson79d39652011-05-25 13:44:23 -0700642
643 pcm = calloc(1, sizeof(struct pcm));
644 if (!pcm || !config)
645 return &bad_pcm; /* TODO: could support default config here */
646
647 pcm->config = *config;
648
Simon Wilson1bd580f2011-06-02 15:58:41 -0700649 snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
650 flags & PCM_IN ? 'c' : 'p');
Simon Wilson79d39652011-05-25 13:44:23 -0700651
652 pcm->flags = flags;
Simon Wilson1bd580f2011-06-02 15:58:41 -0700653 pcm->fd = open(fn, O_RDWR);
Simon Wilson79d39652011-05-25 13:44:23 -0700654 if (pcm->fd < 0) {
Simon Wilson1bd580f2011-06-02 15:58:41 -0700655 oops(pcm, errno, "cannot open device '%s'", fn);
Simon Wilson79d39652011-05-25 13:44:23 -0700656 return pcm;
657 }
658
659 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
Simon Wilson851aa5c2011-05-30 21:18:26 -0700660 oops(pcm, errno, "cannot get info");
Liam Girdwood6be28f12011-10-13 12:59:51 -0700661 goto fail_close;
Simon Wilson79d39652011-05-25 13:44:23 -0700662 }
David Wagner4cddf192014-04-02 15:12:54 +0200663 pcm->subdevice = info.subdevice;
Simon Wilson79d39652011-05-25 13:44:23 -0700664
665 param_init(&params);
Simon Wilson79d39652011-05-25 13:44:23 -0700666 param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
667 pcm_format_to_alsa(config->format));
668 param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
669 SNDRV_PCM_SUBFORMAT_STD);
670 param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
671 param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
672 pcm_format_to_bits(config->format));
673 param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
674 pcm_format_to_bits(config->format) * config->channels);
675 param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
676 config->channels);
677 param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
678 param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);
679
Liam Girdwood6be28f12011-10-13 12:59:51 -0700680 if (flags & PCM_NOIRQ) {
681
682 if (!(flags & PCM_MMAP)) {
683 oops(pcm, -EINVAL, "noirq only currently supported with mmap().");
684 goto fail;
685 }
686
687 params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
688 pcm->noirq_frames_per_msec = config->rate / 1000;
689 }
690
691 if (flags & PCM_MMAP)
692 param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
693 SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
694 else
695 param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
696 SNDRV_PCM_ACCESS_RW_INTERLEAVED);
697
Simon Wilson79d39652011-05-25 13:44:23 -0700698 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
699 oops(pcm, errno, "cannot set hw params");
Liam Girdwood6be28f12011-10-13 12:59:51 -0700700 goto fail_close;
Simon Wilson79d39652011-05-25 13:44:23 -0700701 }
702
Liam Girdwood6be28f12011-10-13 12:59:51 -0700703 /* get our refined hw_params */
704 config->period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
705 config->period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);
706 pcm->buffer_size = config->period_count * config->period_size;
707
708 if (flags & PCM_MMAP) {
709 pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
710 PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
711 if (pcm->mmap_buffer == MAP_FAILED) {
712 oops(pcm, -errno, "failed to mmap buffer %d bytes\n",
713 pcm_frames_to_bytes(pcm, pcm->buffer_size));
714 goto fail_close;
715 }
716 }
717
718
Simon Wilson79d39652011-05-25 13:44:23 -0700719 memset(&sparams, 0, sizeof(sparams));
Eric Laurent40b018e2011-06-18 10:10:23 -0700720 sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
Simon Wilson79d39652011-05-25 13:44:23 -0700721 sparams.period_step = 1;
722 sparams.avail_min = 1;
John Grossman3bb114a2011-07-21 10:59:55 -0700723
Eric Laurent93e7b672012-08-22 16:18:14 -0700724 if (!config->start_threshold) {
725 if (pcm->flags & PCM_IN)
726 pcm->config.start_threshold = sparams.start_threshold = 1;
727 else
728 pcm->config.start_threshold = sparams.start_threshold =
729 config->period_count * config->period_size / 2;
730 } else
John Grossman3bb114a2011-07-21 10:59:55 -0700731 sparams.start_threshold = config->start_threshold;
732
Liam Girdwood6be28f12011-10-13 12:59:51 -0700733 /* pick a high stop threshold - todo: does this need further tuning */
Eric Laurent35021132012-01-30 11:31:56 -0800734 if (!config->stop_threshold) {
735 if (pcm->flags & PCM_IN)
736 pcm->config.stop_threshold = sparams.stop_threshold =
737 config->period_count * config->period_size * 10;
738 else
739 pcm->config.stop_threshold = sparams.stop_threshold =
740 config->period_count * config->period_size;
741 }
John Grossman3bb114a2011-07-21 10:59:55 -0700742 else
743 sparams.stop_threshold = config->stop_threshold;
744
Simon Wilson79d39652011-05-25 13:44:23 -0700745 sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
746 sparams.silence_size = 0;
John Grossman3bb114a2011-07-21 10:59:55 -0700747 sparams.silence_threshold = config->silence_threshold;
Liam Girdwood6be28f12011-10-13 12:59:51 -0700748 pcm->boundary = sparams.boundary = pcm->buffer_size;
John Grossman3bb114a2011-07-21 10:59:55 -0700749
Gabriel M. Beddingfield80085d42012-02-08 16:53:32 -0600750 while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
Liam Girdwood6be28f12011-10-13 12:59:51 -0700751 pcm->boundary *= 2;
Simon Wilson79d39652011-05-25 13:44:23 -0700752
753 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
754 oops(pcm, errno, "cannot set sw params");
755 goto fail;
756 }
757
Eric Laurent40b018e2011-06-18 10:10:23 -0700758 rc = pcm_hw_mmap_status(pcm);
759 if (rc < 0) {
760 oops(pcm, rc, "mmap status failed");
761 goto fail;
762 }
763
Glenn Kasten81012402013-08-22 15:11:48 -0700764#ifdef SNDRV_PCM_IOCTL_TTSTAMP
765 if (pcm->flags & PCM_MONOTONIC) {
766 int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
767 rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
768 if (rc < 0) {
769 oops(pcm, rc, "cannot set timestamp type");
770 goto fail;
771 }
772 }
773#endif
774
Simon Wilson79d39652011-05-25 13:44:23 -0700775 pcm->underruns = 0;
776 return pcm;
777
778fail:
Liam Girdwood6be28f12011-10-13 12:59:51 -0700779 if (flags & PCM_MMAP)
780 munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
781fail_close:
Simon Wilson79d39652011-05-25 13:44:23 -0700782 close(pcm->fd);
783 pcm->fd = -1;
784 return pcm;
785}
786
787int pcm_is_ready(struct pcm *pcm)
788{
789 return pcm->fd >= 0;
790}
Simon Wilsond6458e62011-06-21 14:58:11 -0700791
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530792int pcm_prepare(struct pcm *pcm)
Simon Wilsond6458e62011-06-21 14:58:11 -0700793{
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530794 if (pcm->prepared)
795 return 0;
796
Simon Wilsond6458e62011-06-21 14:58:11 -0700797 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0)
798 return oops(pcm, errno, "cannot prepare channel");
Liam Girdwood6be28f12011-10-13 12:59:51 -0700799
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530800 pcm->prepared = 1;
801 return 0;
802}
803
804int pcm_start(struct pcm *pcm)
805{
806 int prepare_error = pcm_prepare(pcm);
807 if (prepare_error)
808 return prepare_error;
809
Liam Girdwood6be28f12011-10-13 12:59:51 -0700810 if (pcm->flags & PCM_MMAP)
811 pcm_sync_ptr(pcm, 0);
812
Simon Wilsond6458e62011-06-21 14:58:11 -0700813 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0)
814 return oops(pcm, errno, "cannot start channel");
815
Liam Girdwood6be28f12011-10-13 12:59:51 -0700816 pcm->running = 1;
Simon Wilsond6458e62011-06-21 14:58:11 -0700817 return 0;
818}
819
820int pcm_stop(struct pcm *pcm)
821{
822 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0)
823 return oops(pcm, errno, "cannot stop channel");
824
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +0530825 pcm->prepared = 0;
Liam Girdwood6be28f12011-10-13 12:59:51 -0700826 pcm->running = 0;
Simon Wilsond6458e62011-06-21 14:58:11 -0700827 return 0;
828}
829
Liam Girdwood6be28f12011-10-13 12:59:51 -0700830static inline int pcm_mmap_playback_avail(struct pcm *pcm)
831{
832 int avail;
833
834 avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
835
836 if (avail < 0)
837 avail += pcm->boundary;
838 else if (avail > (int)pcm->boundary)
839 avail -= pcm->boundary;
840
841 return avail;
842}
843
844static inline int pcm_mmap_capture_avail(struct pcm *pcm)
845{
846 int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr;
847 if (avail < 0)
848 avail += pcm->boundary;
849 return avail;
850}
851
852static inline int pcm_mmap_avail(struct pcm *pcm)
853{
854 pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);
855 if (pcm->flags & PCM_IN)
856 return pcm_mmap_capture_avail(pcm);
857 else
858 return pcm_mmap_playback_avail(pcm);
859}
860
861static void pcm_mmap_appl_forward(struct pcm *pcm, int frames)
862{
863 unsigned int appl_ptr = pcm->mmap_control->appl_ptr;
864 appl_ptr += frames;
865
866 /* check for boundary wrap */
867 if (appl_ptr > pcm->boundary)
868 appl_ptr -= pcm->boundary;
869 pcm->mmap_control->appl_ptr = appl_ptr;
870}
871
872int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset,
873 unsigned int *frames)
874{
875 unsigned int continuous, copy_frames, avail;
876
877 /* return the mmap buffer */
878 *areas = pcm->mmap_buffer;
879
880 /* and the application offset in frames */
881 *offset = pcm->mmap_control->appl_ptr % pcm->buffer_size;
882
883 avail = pcm_mmap_avail(pcm);
884 if (avail > pcm->buffer_size)
885 avail = pcm->buffer_size;
886 continuous = pcm->buffer_size - *offset;
887
888 /* we can only copy frames if the are availabale and continuos */
889 copy_frames = *frames;
890 if (copy_frames > avail)
891 copy_frames = avail;
892 if (copy_frames > continuous)
893 copy_frames = continuous;
894 *frames = copy_frames;
895
896 return 0;
897}
898
899int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames)
900{
901 /* update the application pointer in userspace and kernel */
902 pcm_mmap_appl_forward(pcm, frames);
903 pcm_sync_ptr(pcm, 0);
904
905 return frames;
906}
907
908int pcm_avail_update(struct pcm *pcm)
909{
910 pcm_sync_ptr(pcm, 0);
911 return pcm_mmap_avail(pcm);
912}
913
914int pcm_state(struct pcm *pcm)
915{
916 int err = pcm_sync_ptr(pcm, 0);
917 if (err < 0)
918 return err;
919
920 return pcm->mmap_status->state;
921}
922
923int pcm_wait(struct pcm *pcm, int timeout)
924{
925 struct pollfd pfd;
Liam Girdwood6be28f12011-10-13 12:59:51 -0700926 int err;
927
928 pfd.fd = pcm->fd;
Apelete Seketeli84889d02014-02-14 14:34:32 +0100929 pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
Liam Girdwood6be28f12011-10-13 12:59:51 -0700930
931 do {
932 /* let's wait for avail or timeout */
933 err = poll(&pfd, 1, timeout);
934 if (err < 0)
935 return -errno;
936
937 /* timeout ? */
938 if (err == 0)
939 return 0;
940
941 /* have we been interrupted ? */
942 if (errno == -EINTR)
943 continue;
944
945 /* check for any errors */
946 if (pfd.revents & (POLLERR | POLLNVAL)) {
947 switch (pcm_state(pcm)) {
948 case PCM_STATE_XRUN:
949 return -EPIPE;
950 case PCM_STATE_SUSPENDED:
951 return -ESTRPIPE;
952 case PCM_STATE_DISCONNECTED:
953 return -ENODEV;
954 default:
955 return -EIO;
956 }
957 }
958 /* poll again if fd not ready for IO */
959 } while (!(pfd.revents & (POLLIN | POLLOUT)));
960
961 return 1;
962}
963
Eric Laurentbb7c5df2013-09-16 14:31:17 -0700964int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes)
Liam Girdwood6be28f12011-10-13 12:59:51 -0700965{
966 int err = 0, frames, avail;
967 unsigned int offset = 0, count;
968
969 if (bytes == 0)
970 return 0;
971
972 count = pcm_bytes_to_frames(pcm, bytes);
973
974 while (count > 0) {
975
976 /* get the available space for writing new frames */
977 avail = pcm_avail_update(pcm);
978 if (avail < 0) {
979 fprintf(stderr, "cannot determine available mmap frames");
980 return err;
981 }
982
983 /* start the audio if we reach the threshold */
984 if (!pcm->running &&
985 (pcm->buffer_size - avail) >= pcm->config.start_threshold) {
986 if (pcm_start(pcm) < 0) {
987 fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n",
988 (unsigned int)pcm->mmap_status->hw_ptr,
989 (unsigned int)pcm->mmap_control->appl_ptr,
990 avail);
991 return -errno;
992 }
993 }
994
995 /* sleep until we have space to write new frames */
996 if (pcm->running &&
997 (unsigned int)avail < pcm->mmap_control->avail_min) {
998 int time = -1;
999
1000 if (pcm->flags & PCM_NOIRQ)
1001 time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min)
1002 / pcm->noirq_frames_per_msec;
1003
1004 err = pcm_wait(pcm, time);
1005 if (err < 0) {
Omair Mohammed Abdullahc9032a02013-01-31 16:35:39 +05301006 pcm->prepared = 0;
Liam Girdwood6be28f12011-10-13 12:59:51 -07001007 pcm->running = 0;
1008 fprintf(stderr, "wait error: hw 0x%x app 0x%x avail 0x%x\n",
1009 (unsigned int)pcm->mmap_status->hw_ptr,
1010 (unsigned int)pcm->mmap_control->appl_ptr,
1011 avail);
1012 pcm->mmap_control->appl_ptr = 0;
1013 return err;
1014 }
1015 continue;
1016 }
1017
1018 frames = count;
1019 if (frames > avail)
1020 frames = avail;
1021
1022 if (!frames)
1023 break;
1024
1025 /* copy frames from buffer */
Eric Laurentbb7c5df2013-09-16 14:31:17 -07001026 frames = pcm_mmap_transfer_areas(pcm, (void *)buffer, offset, frames);
Liam Girdwood6be28f12011-10-13 12:59:51 -07001027 if (frames < 0) {
1028 fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n",
1029 (unsigned int)pcm->mmap_status->hw_ptr,
1030 (unsigned int)pcm->mmap_control->appl_ptr,
1031 avail);
1032 return frames;
1033 }
1034
1035 offset += frames;
1036 count -= frames;
1037 }
1038
Liam Girdwood6be28f12011-10-13 12:59:51 -07001039 return 0;
1040}
Eric Laurentbb7c5df2013-09-16 14:31:17 -07001041
1042int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count)
1043{
1044 if ((~pcm->flags) & (PCM_OUT | PCM_MMAP))
1045 return -ENOSYS;
1046
1047 return pcm_mmap_transfer(pcm, (void *)data, count);
1048}
1049
1050int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count)
1051{
1052 if ((~pcm->flags) & (PCM_IN | PCM_MMAP))
1053 return -ENOSYS;
1054
1055 return pcm_mmap_transfer(pcm, data, count);
1056}
Hardik T Shah9ecb93f2014-04-10 18:03:52 +05301057
1058long pcm_get_delay(struct pcm *pcm)
1059{
1060 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DELAY, &pcm->pcm_delay) < 0)
1061 return -1;
1062
1063 return pcm->pcm_delay;
1064}