版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dongkun152/article/details/86487987
Linux 音频 ALSA 一 alsa-lib API
1. ALSA 架构简介
https://blog.csdn.net/DroidPhone/article/details/6271122
上面的链接应该是csdn中讲解alsa最好的一系列文章。在这里分类把他们链接过来,作为参考,感谢作者。
2. 通过alsa-lib 使用ALSA
我使用的开发板是一块很古老的arm9开发板,外接wolfson wm8960 codec。由于原先的kernel太旧,自己升级到了3.10,同时对音频部分进行了移植测试。准备从后往前理一遍。首先进行的是alsa-lib API的使用,通过API的使用可以了解alsa架构中需要维护的重要参数,进而可以理解驱动中的很多参数。
参考alsa-utils中arecord,aplay,amixer的实现以及接口使用说明 http://www.alsa-project.org/alsa-doc/alsa-lib/。
下面是一个sample,包含录音,播放和调节录音播放的音量功能。
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
#define error(...) do {\
fprintf(stderr, "%s: %s:%d: ", "audio_test", __FUNCTION__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
putc('\n', stderr); \
} while (0)
#else
#define error(args...) do {\
fprintf(stderr, "%s: %s:%d: ", "audio_test", __FUNCTION__, __LINE__); \
fprintf(stderr, ##args); \
putc('\n', stderr); \
} while (0)
#endif
struct audio_param {
snd_pcm_t *handle;
snd_pcm_stream_t stream;
snd_pcm_format_t format;
unsigned int rate;
unsigned int channel;
unsigned int period_time;
snd_pcm_uframes_t period_size;
unsigned int buffer_time;
snd_pcm_uframes_t buffer_size;
unsigned int bits_per_frame;
char * datafile;
char *databuf;
};
static const struct option long_options[] = {
{"capture", no_argument, NULL, '1'},
{"playback", no_argument, NULL, '2'},
{"rate", required_argument, NULL, 'r'},
{"channels", required_argument, NULL, 'c'},
{"format", required_argument, NULL, 'f'},
{"input", required_argument, NULL, 'i'},
{"output", required_argument, NULL, 'o'},
{"volume", required_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'}
};
const char *optionstr = "r:c:f:i:o:v:h";
static int g_exit = 0;
void sighandle(int signum)
{
g_exit = 1;
}
void print_usage(void)
{
printf("audio_test\n"
"\t-h\t\thelp\n"
"\t--capture\tcapture audio data\n"
"\t--playback\tplayback audio file\n"
"\t--rate\t\tsample rate\n"
"\t--format\taudio format\n"
"\t--channels\taudio channels\n"
"\t--input/-o\tinput file\n"
"\t--output/-i\toutput file\n"
"\t--volume\tcapture/playback volume 0~100\n");
}
void set_params(struct audio_param *ainfo)
{
int err = 0;
unsigned int rate = 0;
unsigned long period_bytes = 0;
unsigned long avail_min = 0;
unsigned long start_threshold = 0;
unsigned long stop_threshold = 0;
unsigned long start_delay = 200000;
unsigned long stop_delay = 900000;
unsigned long bits_per_sample = 0;
/*硬件参数*/
snd_pcm_hw_params_t *params;
/*软件参数*/
snd_pcm_sw_params_t *swparams;
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_sw_params_alloca(&swparams);
/*设置所有配置,相当与初始化硬件配置变量*/
err = snd_pcm_hw_params_any(ainfo->handle, params);
if (err < 0) {
error("set hw_params any fail");
exit(-1);
}
/*下面都是设置硬件相关参数*/
/*设置访问方式这里使用直接写入交错数据,即使用readi/writei*/
err = snd_pcm_hw_params_set_access(ainfo->handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
error(" set access type fail");
exit(-1);
}
err = snd_pcm_hw_params_set_format(ainfo->handle, params, ainfo->format);
if (err < 0) {
error("set format fail");
exit(-1);
}
err = snd_pcm_hw_params_set_channels(ainfo->handle, params, ainfo->channel);
if (err < 0) {
error("set channel fail");
exit(-1);
}
/*set nearest rate, return the rate in ainfo->rate*/
rate = ainfo->rate;
err = snd_pcm_hw_params_set_rate_near(ainfo->handle, params, &ainfo->rate, NULL);
if (err < 0) {
error("set rate near fail");
exit(-1);
}
if (ainfo->rate > rate * 1.05 || ainfo->rate < rate * 0.95) {
fprintf(stderr, "rate is not near: %d\n", ainfo->rate);
exit(-1);
}
/*获取驱动的最大缓存时间参数, 驱动设置的128k, 就是dma ringbuffer的size*/
err = snd_pcm_hw_params_get_buffer_time_max(params,
&ainfo->buffer_time, NULL);
if (err < 0) {
error("get buffer time max fail");
exit(-1);
}
/*当输入参数 16k 1 channel 16bit时,buffer time 为4.096s*/
fprintf(stdout, "buffer time is %d\n", ainfo->buffer_time);
if (ainfo->buffer_time > 1000000)
ainfo->buffer_time = 1000000;
/*读写单元长度*/
ainfo->period_time = ainfo->buffer_time / 4;
err = snd_pcm_hw_params_set_period_time_near(ainfo->handle, params,
&ainfo->period_time, NULL);
if (err < 0) {
error("set period time near fail");
exit(-1);
}
err = snd_pcm_hw_params_set_buffer_time_near(ainfo->handle, params,
&ainfo->buffer_time, NULL);
if (err < 0) {
error("set buffer time near fail");
exit(-1);
}
/*设置硬件参数,在这个末尾会将codec的音频路径中的功能单元上电*/
err = snd_pcm_hw_params(ainfo->handle, params);
if (err < 0) {
error("fail to install hw params\n");
exit(-1);
}
/*以frame为单位*/
snd_pcm_hw_params_get_period_size(params, &ainfo->period_size, NULL);
snd_pcm_hw_params_get_buffer_size(params, &ainfo->buffer_size);
/*下面是软件参数*/
/*获取软件参数*/
snd_pcm_sw_params_current(ainfo->handle, swparams);
avail_min = ainfo->period_size;
/*最小可用数据帧数, 当buffer里边的数据小于这个值时,读写操作会阻塞等待*/
err = snd_pcm_sw_params_set_avail_min(ainfo->handle, swparams, avail_min);
if (err < 0) {
error("set avail min");
exit(-1);
}
/*数据启动门限,当输入的读写数据大于这个值时,读写操作才会启动*/
start_threshold = (double)ainfo->rate * start_delay /1000000;
err = snd_pcm_sw_params_set_start_threshold(ainfo->handle, swparams, start_threshold);
if (err < 0) {
error("set start threshold fail");
exit(-1);
}
/*停止门限,有效数据达到这个值时,说明处理过程出现问题,会报overrun*/
stop_threshold = (double)ainfo->rate * stop_delay /1000000;
err = snd_pcm_sw_params_set_stop_threshold(ainfo->handle, swparams, stop_threshold);
if (err < 0) {
error("set stop threshold fail");
exit(-1);
}
/*设置软件参数*/
err = snd_pcm_sw_params(ainfo->handle, swparams);
if (err < 0 ) {
error("fail to install sw params");
exit(-1);
}
bits_per_sample = snd_pcm_format_physical_width(ainfo->format);
ainfo->bits_per_frame = bits_per_sample * ainfo->channel;
period_bytes = ainfo->period_size * ainfo->bits_per_frame / 8;
ainfo->databuf = malloc(period_bytes);
if (NULL == ainfo->databuf) {
error("malloc databuf fail");
exit(-1);
}
}
void clr_params(struct audio_param *ainfo)
{
if (ainfo->databuf != NULL)
free(ainfo->databuf);
}
static void pcm_read(struct audio_param *ainfo, size_t count)
{
int ret = 0;
char *buf = ainfo->databuf;
while (count > 0 && !g_exit) {
ret = snd_pcm_readi(ainfo->handle, buf, count);
if (ret == -EAGAIN || (ret >= 0 && ret < count))
snd_pcm_wait(ainfo->handle, 200);
else if (ret == -EPIPE) {
error(" capture xrun: -EPIPE");
exit(-1);
} else if (ret == -ESTRPIPE) {
error(" capture suspend occurred");
exit(-1);
} else if (ret < 0) {
error("readi error");
exit(-1);
}
if (ret > 0) {
count -= ret;
buf += ret;
}
}
}
static void pcm_write(struct audio_param *ainfo, size_t count)
{
int ret = 0;
unsigned long period_bytes = 0;
char *buf = ainfo->databuf;
period_bytes = ainfo->period_size * ainfo->bits_per_frame / 8;
if (count < ainfo->period_size) {
snd_pcm_format_set_silence(ainfo->format,
ainfo->databuf + count * ainfo->bits_per_frame / 8,
(ainfo->period_size - count) * ainfo->channel);
count = ainfo->period_size;
}
while (count > 0 && !g_exit) {
//读写的count的单位是frame,一次写入和读出的大小是period_size,这里的while其实只执行一次
ret = snd_pcm_writei(ainfo->handle, buf, count);
if (ret == -EAGAIN || (ret >= 0 && ret < count))
snd_pcm_wait(ainfo->handle, 200);
else if (ret == -EPIPE) {
error(" playback xrun: -EPIPE");
exit(-1);
} else if (ret == -ESTRPIPE) {
error(" playbck suspend occurred");
exit(-1);
} else if (ret < 0) {
error("write error");
exit(-1);
}
if (ret > 0) {
count -= ret;
buf += ret;
}
}
}
void playback(struct audio_param *ainfo)
{
FILE *fp = NULL;
unsigned int count = 0;
size_t ret = 0;
unsigned long period_bytes = 0;
unsigned long frames = 0;
fp = fopen(ainfo->datafile, "r+");
if (NULL == fp) {
error("fail to open input file");
exit(-1);
}
period_bytes = ainfo->period_size * ainfo->bits_per_frame / 8;
while (!feof(fp) && !g_exit) {
ret = fread(ainfo->databuf, 1, period_bytes,fp);
if (ret > 0) {
frames = ret * 8 / ainfo->bits_per_frame;
pcm_write(ainfo, frames);
}
}
fclose(fp);
}
void capture(struct audio_param *ainfo)
{
FILE *fp = NULL;
unsigned long period_bytes = 0;
unsigned long frames = 0;
fp = fopen(ainfo->datafile, "w+");
if (NULL == fp) {
error("fail to open output file");
exit(-1);
}
while (!g_exit) {
pcm_read(ainfo, ainfo->period_size);
period_bytes = ainfo->period_size * ainfo->bits_per_frame / 8;
fwrite(ainfo->databuf, 1, period_bytes, fp);
}
fclose(fp);
}
/*录音的control id*/
static const char * capture_volume = "numid=1";
/*播放的control id, 可以运行amixer获得,也可以从alsa的codec驱动中获得*/
static const char * playback_volume = "numid=10";
void update_volume(char *volume, int playback)
{
int value = strtol(volume, NULL, 10);
char volume_set[8] = {0};
const char *id_str = NULL;
long max = 0, min = 0;
int err = 0;
snd_ctl_t *handle = NULL;
/*音量设置的三个重要结构*/
snd_ctl_elem_info_t *info;
snd_ctl_elem_id_t *id;
snd_ctl_elem_value_t *control;
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&control);
if (playback)
id_str = playback_volume;
else
id_str = capture_volume;
/*设置并检查id*/
err = snd_ctl_ascii_elem_id_parse(id, id_str);
if (err < 0) {
error("wrong control identifier");
exit(-1);
}
err = snd_ctl_open(&handle, "default", 0);
if (err < 0) {
error("open ctl device file fail");
exit(-1);
}
snd_ctl_elem_info_set_id(info, id);
/*获取control的info*/
err = snd_ctl_elem_info(handle, info);
if (err < 0) {
error("get control info fail");
exit(-1);
}
/*设置值的id*/
snd_ctl_elem_value_set_id(control, id);
/*获取control的值*/
err = snd_ctl_elem_read(handle, control);
if (err < 0) {
error("read control value fail");
exit(-1);
}
/*获取最大值最小值*/
min = snd_ctl_elem_info_get_min(info);
max = snd_ctl_elem_info_get_max(info);
/*映射设置值*/
sprintf(volume_set, "%d", value * (max - min) / 100);
/*检查设置值*/
err = snd_ctl_ascii_value_parse(handle, control, info, volume_set);
if (err < 0) {
error(" parse value set fail");
exit(-1);
}
/*写入设置值*/
err = snd_ctl_elem_write(handle, control);
if (err < 0) {
error("write control value fail");
exit(-1);
}
snd_ctl_close(handle);
}
int main(int argc, char *argv[])
{
int ret = 0, longindex = 0;
struct audio_param audio_info;
memset(&audio_info, 0, sizeof(struct audio_param));
audio_info.stream = SND_PCM_STREAM_CAPTURE;
audio_info.format = SND_PCM_FORMAT_S16_LE;
audio_info.rate = 16000;
audio_info.channel = 1;
audio_info.buffer_time = 1000000;
audio_info.period_time = 200000;
while ((ret = getopt_long(argc, argv, optionstr, long_options, &longindex)) > 0) {
switch (ret) {
case '1':
audio_info.stream = SND_PCM_STREAM_CAPTURE;
break;
case '2':
audio_info.stream = SND_PCM_STREAM_PLAYBACK;
break;
case 'r':
audio_info.rate = atoi(optarg);
break;
case 'c':
audio_info.channel = atoi(optarg);
break;
case 'f':
break;
case 'i':
audio_info.datafile = optarg;
break;
case 'o':
audio_info.datafile = optarg;
break;
case 'v':
strcpy(volume, optarg);
for (i = 0; i < strlen(volume); i++) {
if (i == 0 && volume[i] == '0') {
print_usage();
exit(-1);
}
if (!isdigit(volume[i])) {
print_usage();
exit(-1);
}
}
break;
case 'h':
print_usage();
exit(1);
case '?':
print_usage();
exit(1);
default:
print_usage();
exit(-1);
}
}
signal(SIGINT, sighandle);
signal(SIGTERM, sighandle);
if (audio_info.stream == SND_PCM_STREAM_PLAYBACK)
update_volume(volume, 1);
else
update_volume(volume, 0);
ret = snd_pcm_open(&audio_info.handle, "default", audio_info.stream, 0);
if (ret < 0) {
error("snd_pcm_open fail");
return -1;
}
set_params(&audio_info);
if (audio_info.stream == SND_PCM_STREAM_PLAYBACK)
playback(&audio_info);
else
capture(&audio_info);
clr_params(&audio_info);
snd_pcm_close(audio_info.handle);
return 1;
}
在设置参数时,最重要的两个结构是硬件参数结构struct snd_pcm_hw_params和软件参数结构struct snd_pcm_sw_params
#define SNDRV_PCM_HW_PARAM_ACCESS 0 /* Access type */
#define SNDRV_PCM_HW_PARAM_FORMAT 1 /* Format */
#define SNDRV_PCM_HW_PARAM_SUBFORMAT 2 /* Subformat */
#define SNDRV_PCM_HW_PARAM_FIRST_MASK SNDRV_PCM_HW_PARAM_ACCESS
#define SNDRV_PCM_HW_PARAM_LAST_MASK SNDRV_PCM_HW_PARAM_SUBFORMAT
#define SNDRV_PCM_HW_PARAM_SAMPLE_BITS 8 /* Bits per sample */
#define SNDRV_PCM_HW_PARAM_FRAME_BITS 9 /* Bits per frame */
#define SNDRV_PCM_HW_PARAM_CHANNELS 10 /* Channels */
#define SNDRV_PCM_HW_PARAM_RATE 11 /* Approx rate */
#define SNDRV_PCM_HW_PARAM_PERIOD_TIME 12 /* Approx distance between
* interrupts in us
*/
#define SNDRV_PCM_HW_PARAM_PERIOD_SIZE 13 /* Approx frames between
* interrupts
*/
#define SNDRV_PCM_HW_PARAM_PERIOD_BYTES 14 /* Approx bytes between
* interrupts
*/
#define SNDRV_PCM_HW_PARAM_PERIODS 15 /* Approx interrupts per
* buffer
*/
#define SNDRV_PCM_HW_PARAM_BUFFER_TIME 16 /* Approx duration of buffer
* in us
*/
#define SNDRV_PCM_HW_PARAM_BUFFER_SIZE 17 /* Size of buffer in frames */
#define SNDRV_PCM_HW_PARAM_BUFFER_BYTES 18 /* Size of buffer in bytes */
#define SNDRV_PCM_HW_PARAM_TICK_TIME 19 /* Approx tick duration in us */
#define SNDRV_PCM_HW_PARAM_FIRST_INTERVAL SNDRV_PCM_HW_PARAM_SAMPLE_BITS
#define SNDRV_PCM_HW_PARAM_LAST_INTERVAL SNDRV_PCM_HW_PARAM_TICK_TIME
#define SNDRV_PCM_HW_PARAMS_NORESAMPLE (1<<0) /* avoid rate resampling */
#define SNDRV_PCM_HW_PARAMS_EXPORT_BUFFER (1<<1) /* export buffer */
#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2) /* disable period wakeups */
struct snd_interval { //用区间描述的参数值用interval表示
unsigned int min, max; //最小值和最大值
unsigned int openmin:1, // 下限是开标志
openmax:1, //上限是开标志
integer:1,
empty:1;
};
#define SNDRV_MASK_MAX 256
struct snd_mask { //可用位图描述的参数用mask管理
__u32 bits[(SNDRV_MASK_MAX+31)/32];
};
//硬件参数有两种,一种用mask管理的位图表示,如访问类型access type,数据格式format。一种用区间表示如rate,channel,buffertime
struct snd_pcm_hw_params {
unsigned int flags;
struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK -
SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];//用位图管理的只有三个参数
struct snd_mask mres[5]; /* reserved masks */
struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];//用区间管理的参数就比较多了,如上的宏所示。
struct snd_interval ires[9]; /* reserved intervals */
unsigned int rmask; /* W: requested masks */
unsigned int cmask; /* R: changed masks */
unsigned int info; /* R: Info flags for returned setup */
unsigned int msbits; /* R: used most significant bits */
unsigned int rate_num; /* R: rate numerator */
unsigned int rate_den; /* R: rate denominator */
snd_pcm_uframes_t fifo_size; /* R: chip FIFO size in frames */
unsigned char reserved[64]; /* reserved for future */
};
enum {
SNDRV_PCM_TSTAMP_NONE = 0,
SNDRV_PCM_TSTAMP_ENABLE,
SNDRV_PCM_TSTAMP_LAST = SNDRV_PCM_TSTAMP_ENABLE,
};
//软件参数如下,一般设置的只有avail_min和start_threshhold, stop_threshold值,其他都默认
struct snd_pcm_sw_params {
int tstamp_mode; /* timestamp mode */
unsigned int period_step;
unsigned int sleep_min; /* min ticks to sleep */
snd_pcm_uframes_t avail_min; /* min avail frames for wakeup */
snd_pcm_uframes_t xfer_align; /* obsolete: xfer size need to be a multiple */
snd_pcm_uframes_t start_threshold; /* min hw_avail frames for automatic start */
snd_pcm_uframes_t stop_threshold; /* min avail frames for automatic stop */
snd_pcm_uframes_t silence_threshold; /* min distance from noise for silence filling */
snd_pcm_uframes_t silence_size; /* silence block size */
snd_pcm_uframes_t boundary; /* pointers wrap point */
unsigned char reserved[64]; /* reserved for future */
};
设置音量的时候就是对codec 驱动管理的每个kcontrol单元进行操作,每个control都有id,首先要知道需要操作的control的id或者name,就可以获取control的信息,读取control的当前值,最后可以设置control的值。关键的三个结构如下:
struct snd_ctl_elem_id {
unsigned int numid; /* numeric identifier, zero = invalid */
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
unsigned char name[44]; /* ASCII name of item */
unsigned int index; /* index of item */
};
//从info中可以看到control的类型和类型相关的参数
struct snd_ctl_elem_info {
struct snd_ctl_elem_id id; /* W: element ID */
snd_ctl_elem_type_t type; /* R: value type - SNDRV_CTL_ELEM_TYPE_* */
unsigned int access; /* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */
unsigned int count; /* count of values */
__kernel_pid_t owner; /* owner's PID of this control */
union {
struct {
long min; /* R: minimum value */
long max; /* R: maximum value */
long step; /* R: step (0 variable) */
} integer;
struct {
long long min; /* R: minimum value */
long long max; /* R: maximum value */
long long step; /* R: step (0 variable) */
} integer64;
struct {
unsigned int items; /* R: number of items */
unsigned int item; /* W: item number */
char name[64]; /* R: value name */
__u64 names_ptr; /* W: names list (ELEM_ADD only) */
unsigned int names_length;
} enumerated;
unsigned char reserved[128];
} value;
union {
unsigned short d[4]; /* dimensions */
unsigned short *d_ptr; /* indirect - obsoleted */
} dimen;
unsigned char reserved[64-4*sizeof(unsigned short)];
};
//value 保存control的值
struct snd_ctl_elem_value {
struct snd_ctl_elem_id id; /* W: element ID */
unsigned int indirect: 1; /* W: indirect access - obsoleted */
union {
union {
long value[128];
long *value_ptr; /* obsoleted */
} integer;
union {
long long value[64];
long long *value_ptr; /* obsoleted */
} integer64;
union {
unsigned int item[128];
unsigned int *item_ptr; /* obsoleted */
} enumerated;
union {
unsigned char data[512];
unsigned char *data_ptr; /* obsoleted */
} bytes;
struct snd_aes_iec958 iec958;
} value; /* RO */
struct timespec tstamp;
unsigned char reserved[128-sizeof(struct timespec)];
};