/* 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; }; 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; } 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 * 2); /* TODO: handle 32bit */ 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 * 2); /* TODO: handle 32bit */ 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; if (pcm->fd >= 0) close(pcm->fd); pcm->running = 0; pcm->buffer_size = 0; pcm->fd = -1; return 0; } 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; }; } struct pcm *pcm_open(unsigned int device, unsigned int flags, struct pcm_config *config) { const char *dname; struct pcm *pcm; struct snd_pcm_info info; struct snd_pcm_hw_params params; struct snd_pcm_sw_params sparams; pcm = calloc(1, sizeof(struct pcm)); if (!pcm || !config) return &bad_pcm; /* TODO: could support default config here */ pcm->config = *config; if (flags & PCM_IN) { dname = "/dev/snd/pcmC0D0c"; } else { dname = "/dev/snd/pcmC0D0p"; } pcm->flags = flags; pcm->fd = open(dname, O_RDWR); if (pcm->fd < 0) { oops(pcm, errno, "cannot open device '%s'", dname); 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_NONE; 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; } 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; }