/* pcm.c ** ** Copyright 2011, The Android Open Source Project ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** * Neither the name of The Android Open Source Project nor the names of ** its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH ** DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #define __force #define __bitwise #define __user #include #include #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL static inline int param_is_mask(int p) { return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); } static inline int param_is_interval(int p) { return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL); } static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n) { return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]); } static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n) { return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); } static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit) { if (bit >= SNDRV_MASK_MAX) return; if (param_is_mask(n)) { struct snd_mask *m = param_to_mask(p, n); m->bits[0] = 0; m->bits[1] = 0; m->bits[bit >> 5] |= (1 << (bit & 31)); } } static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val) { if (param_is_interval(n)) { struct snd_interval *i = param_to_interval(p, n); i->min = val; } } static void param_set_max(struct snd_pcm_hw_params *p, int n, unsigned int val) { if (param_is_interval(n)) { struct snd_interval *i = param_to_interval(p, n); i->max = val; } } static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val) { if (param_is_interval(n)) { struct snd_interval *i = param_to_interval(p, n); i->min = val; i->max = val; i->integer = 1; } } static void param_init(struct snd_pcm_hw_params *p) { int n; memset(p, 0, sizeof(*p)); for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK; n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) { struct snd_mask *m = param_to_mask(p, n); m->bits[0] = ~0; m->bits[1] = ~0; } for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { struct snd_interval *i = param_to_interval(p, n); i->min = 0; i->max = ~0; } } #define PCM_ERROR_MAX 128 struct pcm { int fd; unsigned int flags; int running:1; int underruns; unsigned int buffer_size; char error[PCM_ERROR_MAX]; struct pcm_config config; struct snd_pcm_mmap_status *mmap_status; struct snd_pcm_mmap_control *mmap_control; struct snd_pcm_sync_ptr *sync_ptr; }; unsigned int pcm_get_buffer_size(struct pcm *pcm) { return pcm->buffer_size; } const char* pcm_get_error(struct pcm *pcm) { return pcm->error; } static int oops(struct pcm *pcm, int e, const char *fmt, ...) { va_list ap; int sz; va_start(ap, fmt); vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap); va_end(ap); sz = strlen(pcm->error); if (errno) snprintf(pcm->error + sz, PCM_ERROR_MAX - sz, ": %s", strerror(e)); return -1; } static unsigned int pcm_format_to_alsa(enum pcm_format format) { switch (format) { case PCM_FORMAT_S32_LE: return SNDRV_PCM_FORMAT_S32_LE; default: case PCM_FORMAT_S16_LE: return SNDRV_PCM_FORMAT_S16_LE; }; } static unsigned int pcm_format_to_bits(enum pcm_format format) { switch (format) { case PCM_FORMAT_S32_LE: return 32; default: case PCM_FORMAT_S16_LE: return 16; }; } static int pcm_sync_ptr(struct pcm *pcm, int flags) { if (pcm->sync_ptr) { pcm->sync_ptr->flags = flags; if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) return -1; } return 0; } static int pcm_hw_mmap_status(struct pcm *pcm) { if (pcm->sync_ptr) return 0; int page_size = sysconf(_SC_PAGE_SIZE); pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); if (pcm->mmap_status == MAP_FAILED) pcm->mmap_status = NULL; if (!pcm->mmap_status) goto mmap_error; pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); if (pcm->mmap_control == MAP_FAILED) pcm->mmap_control = NULL; if (!pcm->mmap_control) { munmap(pcm->mmap_status, page_size); pcm->mmap_status = NULL; goto mmap_error; } pcm->mmap_control->avail_min = 1; return 0; mmap_error: pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr)); if (!pcm->sync_ptr) return -ENOMEM; pcm->mmap_status = &pcm->sync_ptr->s.status; pcm->mmap_control = &pcm->sync_ptr->c.control; pcm->mmap_control->avail_min = 1; pcm_sync_ptr(pcm, 0); return 0; } static void pcm_hw_munmap_status(struct pcm *pcm) { if (pcm->sync_ptr) { free(pcm->sync_ptr); pcm->sync_ptr = NULL; } else { int page_size = sysconf(_SC_PAGE_SIZE); if (pcm->mmap_status) munmap(pcm->mmap_status, page_size); if (pcm->mmap_control) munmap(pcm->mmap_control, page_size); } pcm->mmap_status = NULL; pcm->mmap_control = NULL; } int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, struct timespec *tstamp) { int frames; int rc; snd_pcm_uframes_t hw_ptr; if (!pcm_is_ready(pcm)) return -1; rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC); if (rc < 0) return -1; *tstamp = pcm->mmap_status->tstamp; if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0) return -1; hw_ptr = pcm->mmap_status->hw_ptr; if (pcm->flags & PCM_IN) frames = hw_ptr - pcm->mmap_control->appl_ptr; else frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; if (frames < 0) return -1; *avail = (unsigned int)frames; return 0; } int pcm_write(struct pcm *pcm, void *data, unsigned int count) { struct snd_xferi x; if (pcm->flags & PCM_IN) return -EINVAL; x.buf = data; x.frames = count / (pcm->config.channels * pcm_format_to_bits(pcm->config.format) / 8); for (;;) { if (!pcm->running) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)) return oops(pcm, errno, "cannot prepare channel"); if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) return oops(pcm, errno, "cannot write initial data"); pcm->running = 1; return 0; } if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) { pcm->running = 0; if (errno == EPIPE) { /* we failed to make our window -- try to restart */ pcm->underruns++; continue; } return oops(pcm, errno, "cannot write stream data"); } return 0; } } int pcm_read(struct pcm *pcm, void *data, unsigned int count) { struct snd_xferi x; if (!(pcm->flags & PCM_IN)) return -EINVAL; x.buf = data; x.frames = count / (pcm->config.channels * pcm_format_to_bits(pcm->config.format) / 8); for (;;) { if (!pcm->running) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)) return oops(pcm, errno, "cannot prepare channel"); if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) return oops(pcm, errno, "cannot start channel"); pcm->running = 1; } if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) { pcm->running = 0; if (errno == EPIPE) { /* we failed to make our window -- try to restart */ pcm->underruns++; continue; } return oops(pcm, errno, "cannot read stream data"); } return 0; } } static struct pcm bad_pcm = { .fd = -1, }; int pcm_close(struct pcm *pcm) { if (pcm == &bad_pcm) return 0; pcm_hw_munmap_status(pcm); if (pcm->fd >= 0) close(pcm->fd); pcm->running = 0; pcm->buffer_size = 0; pcm->fd = -1; free(pcm); return 0; } struct pcm *pcm_open(unsigned int card, unsigned int device, unsigned int flags, struct pcm_config *config) { struct pcm *pcm; struct snd_pcm_info info; struct snd_pcm_hw_params params; struct snd_pcm_sw_params sparams; char fn[256]; int rc; pcm = calloc(1, sizeof(struct pcm)); if (!pcm || !config) return &bad_pcm; /* TODO: could support default config here */ pcm->config = *config; snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p'); pcm->flags = flags; pcm->fd = open(fn, O_RDWR); if (pcm->fd < 0) { oops(pcm, errno, "cannot open device '%s'", fn); return pcm; } if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { oops(pcm, errno, "cannot get info"); goto fail; } param_init(¶ms); param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, SNDRV_PCM_ACCESS_RW_INTERLEAVED); param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT, pcm_format_to_alsa(config->format)); param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT, SNDRV_PCM_SUBFORMAT_STD); param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size); param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, pcm_format_to_bits(config->format)); param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS, pcm_format_to_bits(config->format) * config->channels); param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS, config->channels); param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count); param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate); if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { oops(pcm, errno, "cannot set hw params"); goto fail; } memset(&sparams, 0, sizeof(sparams)); sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE; sparams.period_step = 1; sparams.avail_min = 1; sparams.start_threshold = config->period_count * config->period_size; sparams.stop_threshold = config->period_count * config->period_size; sparams.xfer_align = config->period_size / 2; /* needed for old kernels */ sparams.silence_size = 0; sparams.silence_threshold = 0; if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { oops(pcm, errno, "cannot set sw params"); goto fail; } rc = pcm_hw_mmap_status(pcm); if (rc < 0) { oops(pcm, rc, "mmap status failed"); goto fail; } pcm->buffer_size = config->period_count * config->period_size; pcm->underruns = 0; return pcm; fail: close(pcm->fd); pcm->fd = -1; return pcm; } int pcm_is_ready(struct pcm *pcm) { return pcm->fd >= 0; } int pcm_start(struct pcm *pcm) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0) return oops(pcm, errno, "cannot prepare channel"); if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0) return oops(pcm, errno, "cannot start channel"); return 0; } int pcm_stop(struct pcm *pcm) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) return oops(pcm, errno, "cannot stop channel"); return 0; }