mirror of
https://github.com/openharmony/third_party_tinyalsa.git
synced 2026-07-19 15:24:22 -04:00
Initial contribution
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_C_INCLUDES:= external/tinyalsa/include
|
||||
LOCAL_SRC_FILES:= mixer.c pcm.c
|
||||
LOCAL_MODULE := libtinyalsa
|
||||
LOCAL_SHARED_LIBRARIES:= libcutils libutils
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -0,0 +1,115 @@
|
||||
/* asoundlib.h
|
||||
**
|
||||
** 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.
|
||||
*/
|
||||
|
||||
#ifndef ASOUNDLIB_H
|
||||
#define ASOUNDLIB_H
|
||||
|
||||
|
||||
/*
|
||||
* PCM API
|
||||
*/
|
||||
|
||||
struct pcm;
|
||||
|
||||
#define PCM_OUT 0x00000000
|
||||
#define PCM_IN 0x10000000
|
||||
|
||||
/* Bit formats */
|
||||
enum pcm_format {
|
||||
PCM_FORMAT_S16_LE = 0,
|
||||
PCM_FORMAT_S32_LE,
|
||||
|
||||
PCM_FORMAT_MAX,
|
||||
};
|
||||
|
||||
/* Configuration for a stream */
|
||||
struct pcm_config {
|
||||
unsigned int channels;
|
||||
unsigned int rate;
|
||||
unsigned int period_size;
|
||||
unsigned int period_count;
|
||||
enum pcm_format format;
|
||||
};
|
||||
|
||||
/* Open and close a stream */
|
||||
struct pcm *pcm_open(unsigned int device, unsigned int flags, struct pcm_config *config);
|
||||
int pcm_close(struct pcm *pcm);
|
||||
int pcm_is_ready(struct pcm *pcm);
|
||||
|
||||
/* Set and get config */
|
||||
int pcm_get_config(struct pcm *pcm, struct pcm_config *config);
|
||||
int pcm_set_config(struct pcm *pcm, struct pcm_config *config);
|
||||
|
||||
/* Returns a human readable reason for the last error */
|
||||
const char *pcm_get_error(struct pcm *pcm);
|
||||
|
||||
/* Returns the buffer size (int bytes) that should be used for pcm_write.
|
||||
* This will be 1/2 of the actual fifo size.
|
||||
*/
|
||||
unsigned int pcm_get_buffer_size(struct pcm *pcm);
|
||||
|
||||
/* Returns the pcm latency in ms */
|
||||
unsigned int pcm_get_latency(struct pcm *pcm);
|
||||
|
||||
/* Write data to the fifo.
|
||||
* Will start playback on the first write or on a write that
|
||||
* occurs after a fifo underrun.
|
||||
*/
|
||||
int pcm_write(struct pcm *pcm, void *data, unsigned int count);
|
||||
int pcm_read(struct pcm *pcm, void *data, unsigned int count);
|
||||
|
||||
|
||||
/*
|
||||
* MIXER API
|
||||
*/
|
||||
|
||||
struct mixer;
|
||||
struct mixer_ctl;
|
||||
|
||||
/* Open and close a mixer */
|
||||
struct mixer *mixer_open(unsigned int device);
|
||||
void mixer_close(struct mixer *mixer);
|
||||
|
||||
/* Obtain mixer controls */
|
||||
int mixer_get_num_ctls(struct mixer *mixer);
|
||||
struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id);
|
||||
struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name);
|
||||
|
||||
/* Set and get mixer controls */
|
||||
int mixer_ctl_get_percent(struct mixer_ctl *ctl);
|
||||
int mixer_ctl_set_percent(struct mixer_ctl *ctl, int percent);
|
||||
|
||||
int mixer_ctl_get_int(struct mixer_ctl *ctl);
|
||||
int mixer_ctl_set_int(struct mixer_ctl *ctl, int);
|
||||
int mixer_ctl_get_step(struct mixer_ctl *ctl);
|
||||
|
||||
int mixer_ctl_get_enum(struct mixer_ctl *ctl, const char *string, unsigned int size);
|
||||
int mixer_ctl_set_enum(struct mixer_ctl *ctl, unsigned int id);
|
||||
int mixer_ctl_set_enum_by_name(struct mixer_ctl *ctl, const char *string);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,332 @@
|
||||
/* mixer.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 <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#define __force
|
||||
#define __bitwise
|
||||
#define __user
|
||||
#include <sound/asound.h>
|
||||
|
||||
#include <tinyalsa/asoundlib.h>
|
||||
|
||||
static const char *elem_iface_name(snd_ctl_elem_iface_t n)
|
||||
{
|
||||
switch (n) {
|
||||
case SNDRV_CTL_ELEM_IFACE_CARD: return "CARD";
|
||||
case SNDRV_CTL_ELEM_IFACE_HWDEP: return "HWDEP";
|
||||
case SNDRV_CTL_ELEM_IFACE_MIXER: return "MIXER";
|
||||
case SNDRV_CTL_ELEM_IFACE_PCM: return "PCM";
|
||||
case SNDRV_CTL_ELEM_IFACE_RAWMIDI: return "MIDI";
|
||||
case SNDRV_CTL_ELEM_IFACE_TIMER: return "TIMER";
|
||||
case SNDRV_CTL_ELEM_IFACE_SEQUENCER: return "SEQ";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *elem_type_name(snd_ctl_elem_type_t n)
|
||||
{
|
||||
switch (n) {
|
||||
case SNDRV_CTL_ELEM_TYPE_NONE: return "NONE";
|
||||
case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return "BOOL";
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER: return "INT32";
|
||||
case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return "ENUM";
|
||||
case SNDRV_CTL_ELEM_TYPE_BYTES: return "BYTES";
|
||||
case SNDRV_CTL_ELEM_TYPE_IEC958: return "IEC958";
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER64: return "INT64";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct mixer_ctl {
|
||||
struct mixer *mixer;
|
||||
struct snd_ctl_elem_info *info;
|
||||
char **ename;
|
||||
};
|
||||
|
||||
struct mixer {
|
||||
int fd;
|
||||
struct snd_ctl_elem_info *info;
|
||||
struct mixer_ctl *ctl;
|
||||
unsigned int count;
|
||||
};
|
||||
|
||||
void mixer_close(struct mixer *mixer)
|
||||
{
|
||||
unsigned int n,m;
|
||||
|
||||
if (mixer->fd >= 0)
|
||||
close(mixer->fd);
|
||||
|
||||
if (mixer->ctl) {
|
||||
for (n = 0; n < mixer->count; n++) {
|
||||
if (mixer->ctl[n].ename) {
|
||||
unsigned int max = mixer->ctl[n].info->value.enumerated.items;
|
||||
for (m = 0; m < max; m++)
|
||||
free(mixer->ctl[n].ename[m]);
|
||||
free(mixer->ctl[n].ename);
|
||||
}
|
||||
}
|
||||
free(mixer->ctl);
|
||||
}
|
||||
|
||||
if (mixer->info)
|
||||
free(mixer->info);
|
||||
|
||||
free(mixer);
|
||||
|
||||
/* TODO: verify frees */
|
||||
}
|
||||
|
||||
struct mixer *mixer_open(unsigned int device)
|
||||
{
|
||||
struct snd_ctl_elem_list elist;
|
||||
struct snd_ctl_elem_info tmp;
|
||||
struct snd_ctl_elem_id *eid = NULL;
|
||||
struct mixer *mixer = NULL;
|
||||
unsigned int n, m;
|
||||
int fd;
|
||||
|
||||
fd = open("/dev/snd/controlC0", O_RDWR);
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
|
||||
memset(&elist, 0, sizeof(elist));
|
||||
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
|
||||
goto fail;
|
||||
|
||||
mixer = calloc(1, sizeof(*mixer));
|
||||
if (!mixer)
|
||||
goto fail;
|
||||
|
||||
mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
|
||||
mixer->info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
|
||||
if (!mixer->ctl || !mixer->info)
|
||||
goto fail;
|
||||
|
||||
eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id));
|
||||
if (!eid)
|
||||
goto fail;
|
||||
|
||||
mixer->count = elist.count;
|
||||
mixer->fd = fd;
|
||||
elist.space = mixer->count;
|
||||
elist.pids = eid;
|
||||
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
|
||||
goto fail;
|
||||
|
||||
for (n = 0; n < mixer->count; n++) {
|
||||
struct snd_ctl_elem_info *ei = mixer->info + n;
|
||||
ei->id.numid = eid[n].numid;
|
||||
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
|
||||
goto fail;
|
||||
mixer->ctl[n].info = ei;
|
||||
mixer->ctl[n].mixer = mixer;
|
||||
if (ei->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
|
||||
char **enames = calloc(ei->value.enumerated.items, sizeof(char*));
|
||||
if (!enames)
|
||||
goto fail;
|
||||
mixer->ctl[n].ename = enames;
|
||||
for (m = 0; m < ei->value.enumerated.items; m++) {
|
||||
memset(&tmp, 0, sizeof(tmp));
|
||||
tmp.id.numid = ei->id.numid;
|
||||
tmp.value.enumerated.item = m;
|
||||
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
|
||||
goto fail;
|
||||
enames[m] = strdup(tmp.value.enumerated.name);
|
||||
if (!enames[m])
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(eid);
|
||||
return mixer;
|
||||
|
||||
fail:
|
||||
/* TODO: verify frees in failure case */
|
||||
if (eid)
|
||||
free(eid);
|
||||
if (mixer)
|
||||
mixer_close(mixer);
|
||||
else if (fd >= 0)
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mixer_get_num_ctls(struct mixer *mixer)
|
||||
{
|
||||
return mixer->count;
|
||||
}
|
||||
|
||||
struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
|
||||
{
|
||||
if (id < mixer->count)
|
||||
return mixer->ctl + id;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name)
|
||||
{
|
||||
unsigned int n;
|
||||
for (n = 0; n < mixer->count; n++)
|
||||
if (!strcmp(name, (char*) mixer->info[n].id.name))
|
||||
return mixer->ctl + n;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int percent_to_int(struct snd_ctl_elem_info *ei, int percent)
|
||||
{
|
||||
int range;
|
||||
|
||||
if (percent > 100)
|
||||
percent = 100;
|
||||
else if (percent < 0)
|
||||
percent = 0;
|
||||
|
||||
range = (ei->value.integer.max - ei->value.integer.min);
|
||||
|
||||
return ei->value.integer.min + (range * percent) / 100;
|
||||
}
|
||||
|
||||
static int int_to_percent(struct snd_ctl_elem_info *ei, int value)
|
||||
{
|
||||
int range = (ei->value.integer.max - ei->value.integer.min);
|
||||
|
||||
if (range == 0)
|
||||
return 0;
|
||||
|
||||
return ((value - ei->value.integer.min) / range) * 100;
|
||||
}
|
||||
|
||||
int mixer_ctl_get_percent(struct mixer_ctl *ctl)
|
||||
{
|
||||
struct snd_ctl_elem_value ev;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.id.numid = ctl->info->id.numid;
|
||||
if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev))
|
||||
return -1;
|
||||
|
||||
switch (ctl->info->type) {
|
||||
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
||||
return !!ev.value.integer.value[0]; /* TODO: handle multiple return values */
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER: {
|
||||
return int_to_percent(ctl->info, ev.value.integer.value[0]);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mixer_ctl_set_percent(struct mixer_ctl *ctl, int percent)
|
||||
{
|
||||
struct snd_ctl_elem_value ev;
|
||||
unsigned int n;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.id.numid = ctl->info->id.numid;
|
||||
switch (ctl->info->type) {
|
||||
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
||||
for (n = 0; n < ctl->info->count; n++)
|
||||
ev.value.integer.value[n] = !!percent; /* TODO: handle multiple set values */
|
||||
break;
|
||||
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER: {
|
||||
int value = percent_to_int(ctl->info, percent);
|
||||
for (n = 0; n < ctl->info->count; n++)
|
||||
ev.value.integer.value[n] = value;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
|
||||
}
|
||||
|
||||
int mixer_ctl_set_enum(struct mixer_ctl *ctl, unsigned int id)
|
||||
{
|
||||
struct snd_ctl_elem_value ev;
|
||||
|
||||
if ((ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) ||
|
||||
(id >= ctl->info->value.enumerated.items)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.value.enumerated.item[0] = id;
|
||||
ev.id.numid = ctl->info->id.numid;
|
||||
if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mixer_ctl_set_enum_by_name(struct mixer_ctl *ctl, const char *string)
|
||||
{
|
||||
unsigned int n, max;
|
||||
struct snd_ctl_elem_value ev;
|
||||
|
||||
if (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
max = ctl->info->value.enumerated.items;
|
||||
for (n = 0; n < max; n++) {
|
||||
if (!strcmp(string, ctl->ename[n])) {
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.value.enumerated.item[0] = n;
|
||||
ev.id.numid = ctl->info->id.numid;
|
||||
if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
/* 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(¶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;
|
||||
}
|
||||
Reference in New Issue
Block a user