blob: 71849a7edb7a2b462473a7e2773f053454262f60 [file] [log] [blame]
/* 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 <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>
#define __force
#define __bitwise
#define __user
#include <sound/asound.h>
#include <tinyalsa/asoundlib.h>
#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_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'");
return pcm;
}
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
oops(pcm, errno, "cannot get info - %s");
goto fail;
}
param_init(&params);
param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
pcm_format_to_alsa(config->format));
param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
SNDRV_PCM_SUBFORMAT_STD);
param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
pcm_format_to_bits(config->format));
param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
pcm_format_to_bits(config->format) * config->channels);
param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
config->channels);
param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
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;
}