diff options
Diffstat (limited to 'libaudio/alsa_pcm.c')
-rw-r--r-- | libaudio/alsa_pcm.c | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/libaudio/alsa_pcm.c b/libaudio/alsa_pcm.c new file mode 100644 index 0000000..5673391 --- /dev/null +++ b/libaudio/alsa_pcm.c @@ -0,0 +1,405 @@ +/* +** Copyright 2010, The Android Open-Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "alsa_pcm" +//#define LOG_NDEBUG 0 +#include <cutils/log.h> +#include <cutils/config_utils.h> + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/time.h> + +#include <linux/ioctl.h> + +#include "alsa_audio.h" + +#define __force +#define __bitwise +#define __user +#include "asound.h" + +#define DEBUG 0 + +/* alsa parameter manipulation cruft */ + +#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 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 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 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 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; + } +} + +/* debugging gunk */ + +#if DEBUG +static const char *param_name[PARAM_MAX+1] = { + [SNDRV_PCM_HW_PARAM_ACCESS] = "access", + [SNDRV_PCM_HW_PARAM_FORMAT] = "format", + [SNDRV_PCM_HW_PARAM_SUBFORMAT] = "subformat", + + [SNDRV_PCM_HW_PARAM_SAMPLE_BITS] = "sample_bits", + [SNDRV_PCM_HW_PARAM_FRAME_BITS] = "frame_bits", + [SNDRV_PCM_HW_PARAM_CHANNELS] = "channels", + [SNDRV_PCM_HW_PARAM_RATE] = "rate", + [SNDRV_PCM_HW_PARAM_PERIOD_TIME] = "period_time", + [SNDRV_PCM_HW_PARAM_PERIOD_SIZE] = "period_size", + [SNDRV_PCM_HW_PARAM_PERIOD_BYTES] = "period_bytes", + [SNDRV_PCM_HW_PARAM_PERIODS] = "periods", + [SNDRV_PCM_HW_PARAM_BUFFER_TIME] = "buffer_time", + [SNDRV_PCM_HW_PARAM_BUFFER_SIZE] = "buffer_size", + [SNDRV_PCM_HW_PARAM_BUFFER_BYTES] = "buffer_bytes", + [SNDRV_PCM_HW_PARAM_TICK_TIME] = "tick_time", +}; + +static void param_dump(struct snd_pcm_hw_params *p) +{ + int n; + + 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); + LOGV("%s = %08x%08x\n", param_name[n], + m->bits[1], m->bits[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); + LOGV("%s = (%d,%d) omin=%d omax=%d int=%d empty=%d\n", + param_name[n], i->min, i->max, i->openmin, + i->openmax, i->integer, i->empty); + } + LOGV("info = %08x\n", p->info); + LOGV("msbits = %d\n", p->msbits); + LOGV("rate = %d/%d\n", p->rate_num, p->rate_den); + LOGV("fifo = %d\n", (int) p->fifo_size); +} + +static void info_dump(struct snd_pcm_info *info) +{ + LOGV("device = %d\n", info->device); + LOGV("subdevice = %d\n", info->subdevice); + LOGV("stream = %d\n", info->stream); + LOGV("card = %d\n", info->card); + LOGV("id = '%s'\n", info->id); + LOGV("name = '%s'\n", info->name); + LOGV("subname = '%s'\n", info->subname); + LOGV("dev_class = %d\n", info->dev_class); + LOGV("dev_subclass = %d\n", info->dev_subclass); + LOGV("subdevices_count = %d\n", info->subdevices_count); + LOGV("subdevices_avail = %d\n", info->subdevices_avail); +} +#else +static void param_dump(struct snd_pcm_hw_params *p) {} +static void info_dump(struct snd_pcm_info *info) {} +#endif + +#define PCM_ERROR_MAX 128 + +struct pcm { + int fd; + unsigned flags; + int running:1; + int underruns; + unsigned buffer_size; + char error[PCM_ERROR_MAX]; +}; + +unsigned pcm_buffer_size(struct pcm *pcm) +{ + return pcm->buffer_size; +} + +const char* pcm_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 count) +{ + struct snd_xferi x; + + if (pcm->flags & PCM_IN) + return -EINVAL; + + x.buf = data; + x.frames = (pcm->flags & PCM_MONO) ? (count / 2) : (count / 4); + + 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 count) +{ + struct snd_xferi x; + + if (!(pcm->flags & PCM_IN)) + return -EINVAL; + + x.buf = data; + x.frames = (pcm->flags & PCM_MONO) ? (count / 2) : (count / 4); + +// LOGV("read() %d frames", x.frames); + 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"); + } +// LOGV("read() got %d frames", x.frames); + 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; +} + +struct pcm *pcm_open(unsigned flags) +{ + const char *dname; + struct pcm *pcm; + struct snd_pcm_info info; + struct snd_pcm_hw_params params; + struct snd_pcm_sw_params sparams; + unsigned period_sz; + unsigned period_cnt; + + LOGV("pcm_open(0x%08x)",flags); + + pcm = calloc(1, sizeof(struct pcm)); + if (!pcm) + return &bad_pcm; + + if (flags & PCM_IN) { + dname = "/dev/snd/pcmC0D0c"; + } else { + dname = "/dev/snd/pcmC0D0p"; + } + + LOGV("pcm_open() period sz multiplier %d", + ((flags & PCM_PERIOD_SZ_MASK) >> PCM_PERIOD_SZ_SHIFT) + 1); + period_sz = 128 * (((flags & PCM_PERIOD_SZ_MASK) >> PCM_PERIOD_SZ_SHIFT) + 1); + LOGV("pcm_open() period cnt %d", + ((flags & PCM_PERIOD_CNT_MASK) >> PCM_PERIOD_CNT_SHIFT) + PCM_PERIOD_CNT_MIN); + period_cnt = ((flags & PCM_PERIOD_CNT_MASK) >> PCM_PERIOD_CNT_SHIFT) + PCM_PERIOD_CNT_MIN; + + pcm->flags = flags; + pcm->fd = open(dname, O_RDWR); + if (pcm->fd < 0) { + oops(pcm, errno, "cannot open device '%s'"); + return pcm; + } + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { + oops(pcm, errno, "cannot get info - %s"); + goto fail; + } + info_dump(&info); + + LOGV("pcm_open() period_cnt %d period_sz %d channels %d", + period_cnt, period_sz, (flags & PCM_MONO) ? 1 : 2); + + 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, + SNDRV_PCM_FORMAT_S16_LE); + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT, + SNDRV_PCM_SUBFORMAT_STD); + param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, period_sz); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 16); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS, + (flags & PCM_MONO) ? 16 : 32); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS, + (flags & PCM_MONO) ? 1 : 2); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, period_cnt); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, 44100); + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { + oops(pcm, errno, "cannot set hw params"); + goto fail; + } + param_dump(¶ms); + + memset(&sparams, 0, sizeof(sparams)); + sparams.tstamp_mode = SNDRV_PCM_TSTAMP_NONE; + sparams.period_step = 1; + sparams.avail_min = 1; + sparams.start_threshold = period_cnt * period_sz; + sparams.stop_threshold = period_cnt * period_sz; + sparams.xfer_align = period_sz / 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 = period_cnt * period_sz; + pcm->underruns = 0; + return pcm; + +fail: + close(pcm->fd); + pcm->fd = -1; + return pcm; +} + +int pcm_ready(struct pcm *pcm) +{ + return pcm->fd >= 0; +} |