Linux 音频 ALSA 一 alsa-lib API

版权声明:本文为博主原创文章,未经博主允许不得转载。 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(&params);
	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)];
};

猜你喜欢

转载自blog.csdn.net/dongkun152/article/details/86487987