首页 » 网站推广 » phppcm转换技巧_正点原子IMX6U嵌入式Linux C应用编程 第二十九章 音频应用编程 下篇

phppcm转换技巧_正点原子IMX6U嵌入式Linux C应用编程 第二十九章 音频应用编程 下篇

访客 2024-12-12 0

扫一扫用手机浏览

文章目录 [+]

示例代码笔者已经写好,如下所示。

本例程源码对应的路径为:开拓板光盘->11、Linux C运用编程例程源码->29_alsa-lib->pcm_playback_ctl.c。

phppcm转换技巧_正点原子IMX6U嵌入式Linux C应用编程 第二十九章 音频应用编程 下篇

示例代码 29.8.1 PCM播放示例程序(加入状态掌握)/Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.文件名 : pcm_playback_ctl.c作者 : 邓涛版本 : V1.0描述 : 一个大略地PCM播放示例代码--利用异步办法、用户可通过按键对播放过程进行掌握。
其他 : 无论坛 : www.openedv.com日志 : 初版 V1.0 2021/7/20 邓涛创建/#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <termios.h>#include <signal.h>#include <alsa/asoundlib.h>/宏定义/#define PCM_PLAYBACK_DEV "hw:0,0"/WAV音频文件解析干系数据构造申明/typedef struct WAV_RIFF {char ChunkID[4]; / "RIFF" /u_int32_t ChunkSize; / 从下一个地址开始到文件末端的总字节数 /char Format[4]; / "WAVE" /} __attribute__ ((packed)) RIFF_t;typedef struct WAV_FMT {char Subchunk1ID[4]; / "fmt " /u_int32_t Subchunk1Size; / 16 for PCM /u_int16_t AudioFormat; / PCM = 1/u_int16_t NumChannels; / Mono = 1, Stereo = 2, etc. /u_int32_t SampleRate; / 8000, 44100, etc. /u_int32_t ByteRate; / = SampleRate NumChannels BitsPerSample/8 /u_int16_t BlockAlign; / = NumChannels BitsPerSample/8 /u_int16_t BitsPerSample; / 8bits, 16bits, etc. /} __attribute__ ((packed)) FMT_t;static FMT_t wav_fmt;typedef struct WAV_DATA {char Subchunk2ID[4]; / "data" /u_int32_t Subchunk2Size; / data size /} __attribute__ ((packed)) DATA_t;/static静态全局变量定义/static snd_pcm_t pcm = NULL; //pcm句柄static unsigned int buf_bytes; //运用程序缓冲区的大小(字节为单位)static void buf = NULL; //指向运用程序缓冲区的指针static int fd = -1; //指向WAV音频文件的文件描述符static snd_pcm_uframes_t period_size = 1024; //周期大小(单位: 帧)static unsigned int periods = 16; //周期数(设备驱动层buffer的大小)static struct termios old_cfg; //用于保存终端当前的配置参数/static静态函数/static void snd_playback_async_callback(snd_async_handler_t handler){snd_pcm_t handle = snd_async_handler_get_pcm(handler);//获取PCM句柄snd_pcm_sframes_t avail;int ret;avail = snd_pcm_avail_update(handle);//获取环形缓冲区中有多少帧数据须要添补while (avail >= period_size) { //我们一次写入一个周期memset(buf, 0x00, buf_bytes); //buf清零ret = read(fd, buf, buf_bytes);if (0 >= ret)goto out;ret = snd_pcm_writei(handle, buf, period_size);if (0 > ret) {fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));goto out;}else if (ret < period_size) {//实际写入的帧数小于指定的帧数//此时我们须要调度下音频文件的读位置//将读位置向后移动(往回移)(period_size-ret)frame_bytes个字节//frame_bytes表示一帧的字节大小if (0 > lseek(fd, (ret-period_size) wav_fmt.BlockAlign, SEEK_CUR)) {perror("lseek error");goto out;}}avail = snd_pcm_avail_update(handle); //再次获取、更新avail}return;out:snd_pcm_drain(pcm); //停滞PCMsnd_pcm_close(handle); //关闭pcm设备tcsetattr(STDIN_FILENO, TCSANOW, &old_cfg); //退出前规复终真个状态free(buf);close(fd); //关闭打开的音频文件exit(EXIT_FAILURE); //退出程序}static int snd_pcm_init(void){snd_pcm_hw_params_t hwparams = NULL;snd_async_handler_t async_handler = NULL;int ret;/ 打开PCM设备 /ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_open error: %s: %s\n",PCM_PLAYBACK_DEV, snd_strerror(ret));return -1;}/ 实例化hwparams工具 /snd_pcm_hw_params_malloc(&hwparams);/ 获取PCM设备当前硬件配置,对hwparams进行初始化 /ret = snd_pcm_hw_params_any(pcm, hwparams);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));goto err2;}/设置参数// 设置访问类型: 交错模式 /ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));goto err2;}/ 设置数据格式: 有符号16位、小端模式 /ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));goto err2;}/ 设置采样率 /ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));goto err2;}/ 设置声道数: 双声道 /ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));goto err2;}/ 设置周期大小: period_size /ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));goto err2;}/ 设置周期数(驱动层环形缓冲区buffer的大小): periods /ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));goto err2;}/ 使配置生效 /ret = snd_pcm_hw_params(pcm, hwparams);snd_pcm_hw_params_free(hwparams); //开释hwparams工具占用的内存if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));goto err1;}buf_bytes = period_size wav_fmt.BlockAlign; //变量赋值,一个周期的字节大小/ 注册异步处理函数 /ret = snd_async_add_pcm_handler(&async_handler, pcm, snd_playback_async_callback, NULL);if (0 > ret) {fprintf(stderr, "snd_async_add_pcm_handler error: %s\n", snd_strerror(ret));goto err1;}return 0;err2:snd_pcm_hw_params_free(hwparams); //开释内存err1:snd_pcm_close(pcm); //关闭pcm设备return -1;}static int open_wav_file(const char file){RIFF_t wav_riff;DATA_t wav_data;int ret;fd = open(file, O_RDONLY);if (0 > fd) {fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));return -1;}/ 读取RIFF chunk /ret = read(fd, &wav_riff, sizeof(RIFF_t));if (sizeof(RIFF_t) != ret) {if (0 > ret)perror("read error");elsefprintf(stderr, "check error: %s\n", file);close(fd);return -1;}if (strncmp("RIFF", wav_riff.ChunkID, 4) ||//校验strncmp("WAVE", wav_riff.Format, 4)) {fprintf(stderr, "check error: %s\n", file);close(fd);return -1;}/ 读取sub-chunk-fmt /ret = read(fd, &wav_fmt, sizeof(FMT_t));if (sizeof(FMT_t) != ret) {if (0 > ret)perror("read error");elsefprintf(stderr, "check error: %s\n", file);close(fd);return -1;}if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4)) {//校验fprintf(stderr, "check error: %s\n", file);close(fd);return -1;}/ 打印音频文件的信息 /printf("<<<<音频文件格式信息>>>>\n\n");printf(" file name: %s\n", file);printf(" Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);printf(" AudioFormat: %u\n", wav_fmt.AudioFormat);printf(" NumChannels: %u\n", wav_fmt.NumChannels);printf(" SampleRate: %u\n", wav_fmt.SampleRate);printf(" ByteRate: %u\n", wav_fmt.ByteRate);printf(" BlockAlign: %u\n", wav_fmt.BlockAlign);printf(" BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);/ sub-chunk-data /if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,SEEK_SET)) {perror("lseek error");close(fd);return -1;}while(sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t))) {/ 找到sub-chunk-data /if (!strncmp("data", wav_data.Subchunk2ID, 4))//校验return 0;if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR)) {perror("lseek error");close(fd);return -1;}}fprintf(stderr, "check error: %s\n", file);return -1;}/main主函数/int main(int argc, char argv[]){snd_pcm_sframes_t avail;struct termios new_cfg;sigset_t sset;int ret;if (2 != argc) {fprintf(stderr, "Usage: %s <audio_file>\n", argv[0]);exit(EXIT_FAILURE);}/ 屏蔽SIGIO旗子暗记 /sigemptyset(&sset);sigaddset(&sset, SIGIO);sigprocmask(SIG_BLOCK, &sset, NULL);/ 打开WAV音频文件 /if (open_wav_file(argv[1]))exit(EXIT_FAILURE);/ 初始化PCM Playback设备 /if (snd_pcm_init())goto err1;/ 申请读缓冲区 /buf = malloc(buf_bytes);if (NULL == buf) {perror("malloc error");goto err2;}/ 终端配置 /tcgetattr(STDIN_FILENO, &old_cfg); //获取终端<标准输入-标准输出构成了一套终端>memcpy(&new_cfg, &old_cfg, sizeof(struct termios));//备份new_cfg.c_lflag &= ~ICANON; //将终端设置为非规范模式new_cfg.c_lflag &= ~ECHO; //禁用回显tcsetattr(STDIN_FILENO, TCSANOW, &new_cfg);//使配置生效/ 播放:先将环形缓冲区填满数据 /avail = snd_pcm_avail_update(pcm); //获取环形缓冲区中有多少帧数据须要添补while (avail >= period_size) { //我们一次写入一个周期memset(buf, 0x00, buf_bytes); //buf清零ret = read(fd, buf, buf_bytes);if (0 >= ret)goto err3;ret = snd_pcm_writei(pcm, buf, period_size);//向环形缓冲区中写入数据if (0 > ret) {fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));goto err3;}else if (ret < period_size) {//实际写入的帧数小于指定的帧数//此时我们须要调度下音频文件的读位置//将读位置向后移动(往回移)(period_size-ret)frame_bytes个字节//frame_bytes表示一帧的字节大小if (0 > lseek(fd, (ret-period_size) wav_fmt.BlockAlign, SEEK_CUR)) {perror("lseek error");goto err3;}}avail = snd_pcm_avail_update(pcm); //再次获取、更新avail}sigprocmask(SIG_UNBLOCK, &sset, NULL); //取消SIGIO旗子暗记屏蔽char ch;for ( ; ; ) {ch = getchar(); //获取用户输入的掌握字符switch (ch) {case 'q': //Q键退出程序sigprocmask(SIG_BLOCK, &sset, NULL);//屏蔽SIGIO旗子暗记goto err3;case ' ': //空格停息/规复switch (snd_pcm_state(pcm)) {case SND_PCM_STATE_PAUSED: //如果是停息状态则规复运行ret = snd_pcm_pause(pcm, 0);if (0 > ret)fprintf(stderr, "snd_pcm_pause error: %s\n", snd_strerror(ret));break;case SND_PCM_STATE_RUNNING: //如果是运行状态则停息ret = snd_pcm_pause(pcm, 1);if (0 > ret)fprintf(stderr, "snd_pcm_pause error: %s\n", snd_strerror(ret));break;}break;}}err3:snd_pcm_drop(pcm); //停滞PCMtcsetattr(STDIN_FILENO, TCSANOW, &old_cfg); //退出前规复终真个状态free(buf); //开释内存err2:snd_pcm_close(pcm); //关闭pcm设备err1:close(fd); //关闭打开的音频文件exit(EXIT_FAILURE);}

上述示例程序是在示例代码 29.6.1根本上进行修正了,加入了用户掌握单元,程序设定:

phppcm转换技巧_正点原子IMX6U嵌入式Linux C应用编程 第二十九章 音频应用编程 下篇
(图片来自网络侵删)
q:在终端按下q键退出运用程序;终端按下空格键停息播放,再次按下规复播放。

下面给大家大略地先容下上述示例代码的设计,在main()函数中,我们首先屏蔽了SIGIO旗子暗记,如下:

/ 屏蔽SIGIO旗子暗记 /sigemptyset(&sset);sigaddset(&sset, SIGIO);sigprocmask(SIG_BLOCK, &sset, NULL);

这紧张是为了程序设计上的安全考虑,等把环形缓冲区填满数据之后,再取消SIGIO旗子暗记屏蔽。
当然,你也可以不这样做。

接着打开用户传入的音频文件、初始化PCM播放设备、申请运用程序所需的缓冲区:

/ 打开WAV音频文件 /if (open_wav_file(argv[1]))exit(EXIT_FAILURE);/ 初始化PCM Playback设备 /if (snd_pcm_init())goto err1;/ 申请读缓冲区 /buf = malloc(buf_bytes);if (NULL == buf) {perror("malloc error");goto err2;}

接着对终端进行设置,将终端配置为非规范模式、取消回显,配置为非规范模式之后,用户输入的字符会直接被运用程序读取到,而无需按下回车键;取消回显,意味着用户输入的字符,在终端不会显示出来,这些内容在串口运用编程章节给大家详细先容过,这里就不再啰嗦!

/ 终端配置 /tcgetattr(STDIN_FILENO, &old_cfg); //获取终端<标准输入-标准输出构成了一套终端>memcpy(&new_cfg, &old_cfg, sizeof(struct termios));//备份new_cfg.c_lflag &= ~ICANON; //将终端设置为非规范模式new_cfg.c_lflag &= ~ECHO; //禁用回显tcsetattr(STDIN_FILENO, TCSANOW, &new_cfg);//使配置生效

接下来将数据写入环形缓冲区,开始播放。

取消SIGIO旗子暗记旗子暗记屏蔽。

最后进入for()循环中,通过getchar()读取用户输入的字符,用户输入q时退出程序,这里须要把稳,退出程序时须要调用tcsetattr()将终端配置参数规复到之前的状态,否则你的终端将可能会涌现下面这种情形:

这个时候你就只能重启了。

用户输入空格停息或规复,调用snd_pcm_pause()实现停息/规复。

代码比较大略,笔者也就不再多说了!

编译示例代码

实行命令编译运用程序:

${CC} -o testApp testApp.c -lasound

测试运用程序

将编译得到的可实行文件拷贝到开拓板Linux系统/home/root目录下,并准备一个WAV音频文件,接着我们实行测试程序:

图 29.8.2 运行测试程序

运行之后,开始播放音乐,此时我们可以通过空格键来停息播放、再按空格键规复播放,按q键退出程序,大家自己去测试。

Tips:本测试程序不能放在后台运行,一旦放入后台,程序将停滞(不是终止、是停息运行),由于这个程序在设计逻辑上就不符合放置在后台,由于程序中会读取用户从终端(标准输入)输入的字符,如果放入后台,那用户输入的字符就不可能被该程序所读取到,这是其一;其二,程序中修正了终真个配置。

snd_pcm_readi/snd_pcm_writei缺点处理

当snd_pcm_readi/snd_pcm_writei调用出错时,会返回一个小于0(负值)的缺点码,可调用snd_strerror()函数获取对应的缺点描述信息。
前面的示例代码中我们并没有对snd_pcm_readi/snd_pcm_writei的缺点返回做过多、细节的处理,而是大略地在出错之退却撤退出。

事实上,当调用snd_pcm_readi/snd_pcm_writei出错时,可根据不同的情形作进一步的处理,在alsa-lib文档中有先容到,snd_pcm_readi/snd_pcm_writei函数的不同缺点返回值,表示不同的含义,如下所示:

图 29.8.3 snd_pcm_writei函数的缺点返回值描述

snd_pcm_readi()函数与它相同。

当返回值即是-EBADFD,表示PCM设备的状态不对,由于实行snd_pcm_readi/snd_pcm_writei读取/写入数据须要PCM设备处于SND_PCM_STATE_PREPARED或SND_PCM_STATE_RUNNING状态,前面已经详细地给大家先容了PCM设备的状态间转换问题。

当返回值即是-EPIPE,表示发生了XRUN,此时可以怎么做呢?这个可以根据自己的实际须要进行处理,譬如调用snd_pcm_drop()停滞PCM设备,或者调用snd_pcm_prepare()使设备规复进入准备状态。

当返回值即是-ESTRPIPE,表示硬件发生了挂起,此时PCM设备处于SND_PCM_STATE_SUSPENDED状态,譬如你可以调用snd_pcm_resume()函数从挂起中精确规复,如果硬件不支持,还可调用snd_pcm_prepare()函数使设备进入准备状态,或者实行其它的处理,根据运用需求的进行相应的处理。

以上给大家先容了调用snd_pcm_readi/snd_pcm_writei函数出错时的一些情形以及可以采纳的一些方法!

混音器设置

前面给大家先容了alsa-utils供应的两个声卡配置工具:alsamixer和amixer。
这两个工具同样是基于alsa-lib库函数编写的,本小节我们来学习如何在自己的运用程序中通过调用alsa-lib库函数对声卡混音器进行配置,譬如音量调节。

混音器干系的接口在alsa-lib的Mixer Interface模块中有先容,点击图 29.2.2中“Mixer Interface”可查看混音器干系接口的先容,如下所示:

图 29.9.1 Mixer Interface模块

大家可以大略地浏览下该模块下供应了那些函数,点击函数名可以查看该函数的大略先容信息。

打开混音器:snd_mixer_open

在利用混音器之后,须要打开混音器,调用snd_mixer_open()函数打开一个空的混音器,其函数原型如下所示:

int snd_mixer_open(snd_mixer_t mixerp, int mode);

alsa-lib利用snd_mixer_t数据构造描述混音器,调用snd_mixer_open()函数会实例化一个snd_mixer_t工具,并将工具的指针(也便是混音器的句柄)通过mixerp返回出来。
参数mode指定了打开模式,常日设置为0利用默认模式即可!

函数调用成功返回0;失落败返回一个小于0的缺点码。

利用示例:

snd_mixer_t mixer = NULL;int ret;ret = snd_mixer_open(&mixer, 0);if (0 > ret)fprintf(stderr, "snd_mixer_open error: %s\n", snd_strerror(ret));Attach关联设备:snd_mixer_attach

调用snd_mixer_open()函数打开并实例化了一个空的混音器,接下来我们要去关联声卡掌握设备,调用snd_mixer_attach()函数进行关联,其函数原型如下所示:

int snd_mixer_attach(snd_mixer_t mixer, const char name);

参数mixer对应的是混音器的句柄,参数name指定了声卡掌握设备的名字,同样这里利用的也是逻辑设备名,而非设备节点的名字,命名办法为"hw:i",i表示声卡的卡号,常日一个声卡对应一个掌握设备;譬如"hw:0"表示声卡0的掌握设备,这实在就对应/dev/snd/controlC0设备。
与snd_pcm_open()函数中PCM设备的命名一样,snd_mixer_attach()函数中声卡掌握设备的命名也有其它办法,这里暂时先不管这个问题。

调用snd_mixer_open()函数会将参数name所指定的掌握设备与混音器mixer进行关联。

函数调用成功返回0;失落败返回一个小于0的缺点码。

利用示例:

ret = snd_mixer_attach(mixer, "hw:0");if (0 > ret)fprintf(stderr, "snd_mixer_attach error: %s\n", snd_strerror(ret));注册:snd_mixer_selem_register

调用snd_mixer_selem_register()函数注册混音器,其函数原型如下所示:

int snd_mixer_selem_register(snd_mixer_t mixer,struct snd_mixer_selem_regopt options,snd_mixer_class_t classp);

参数options和参数classp直接设置为NULL即可。

函数调用成功返回0;失落败返回一个小于0的缺点码。

利用示例:

ret = snd_mixer_selem_register(mixer, NULL, NULL);if (0 > ret)fprintf(stderr, "snd_mixer_selem_register error: %s\n", snd_strerror(ret));加载:snd_mixer_load

末了须要加载混音器,调用snd_mixer_load()函数完成加载,函数原型如下所示:

int snd_mixer_load(snd_mixer_t mixer);

函数调用成功返回0;失落败返回小于0的缺点码。

利用示例:

ret = snd_mixer_load(mixer);if (0 > ret)fprintf(stderr, "snd_mixer_load error: %s\n", snd_strerror(ret));查找元素

经由上面一系列步骤之后,接下来就可以利用混音器了,alsa-lib中把混音器的配置项称为元素(element),譬如耳机音量调节Headphone是一个元素、'Headphone Playback ZC'是一个元素、'Right Output Mixer PCM'也是一个元素。

snd_mixer_first_elem和snd_mixer_last_elem

alsa-lib利用数据构造snd_mixer_elem_t来描述一个元素,以是一个snd_mixer_elem_t工具便是一个元素。
混音器有很多的元素(也便是有很多配置项),通过snd_mixer_first_elem()函数可以找到混音器的第一个元素,其函数原型如下所示:

snd_mixer_elem_t snd_mixer_first_elem(snd_mixer_t mixer);

通过snd_mixer_last_elem()函数可找到混音器的末了一个元素,如下:

snd_mixer_elem_t snd_mixer_last_elem(snd_mixer_t mixer);

snd_mixer_elem_next和snd_mixer_elem_prev

调用snd_mixer_elem_next()和snd_mixer_elem_prev()函数可获取指定元素的下一个元素和上一个元素:

snd_mixer_elem_t snd_mixer_elem_next(snd_mixer_elem_t elem);snd_mixer_elem_t snd_mixer_elem_prev(snd_mixer_elem_t elem);

以是通过snd_mixer_first_elem和snd_mixer_elem_next()或者snd_mixer_last_elem()和snd_mixer_elem_prev()就可以遍历全体混音器中的所有元素,如下所示:

snd_mixer_elem_t elem = NULL;elem = snd_mixer_first_elem(mixer);//找到第一个元素while (elem) {............snd_mixer_elem_next(elem); //找到下一个元素}

snd_mixer_selem_get_name

调用snd_mixer_selem_get_name()函数可获取指定元素的名字,如下所示:

const char snd_mixer_selem_get_name(snd_mixer_elem_t elem);

获取元素的名字之后,进行比拟,以确定是否是我们要找的元素:

const char name = snd_mixer_selem_get_name(elem);if (!strcmp(name, "Headphone")) {//该配置项是"Headphone"}else {//该配置项不是"Headphone"}获取/变动元素的配置值

前面给大家提到了混音器的配置值有两种类型,第一种它的配置值是在一个范围内的数值,譬如音量大小的调节;另一种则是bool类型,用于掌握开启或关闭,譬如0表示关闭配置、1表示使能配置。

snd_mixer_selem_has_playback_volume/snd_mixer_selem_has_capture_volume

我们可以调用snd_mixer_selem_has_playback_volume(播放)或snd_mixer_selem_has_capture_volume(录音)函数来判断一个指定元素的配置值是否是volume类型,也便是上面说的第一种情形。
函数原型如下所示:

int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t elem);int snd_mixer_selem_has_capture_volume(snd_mixer_elem_t elem);

函数返回0表示不是volume类型;返回1表示是volume类型。

snd_mixer_selem_has_playback_switch/snd_mixer_selem_has_capture_switch

调用snd_mixer_selem_has_playback_switch(播放)snd_mixer_selem_has_capture_switch(录音)函数判断一个指定元素的配置值是否是switch类型,也便是上面说的第二种情形。
函数原型如下所示:

int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t elem);int snd_mixer_selem_has_capture_switch(snd_mixer_elem_t elem);

函数返回0表示不是switch类型;返回1表示是switch类型。

snd_mixer_selem_has_playback_channel/snd_mixer_selem_has_capture_channel

通过snd_mixer_selem_has_playback_channel(播放)或snd_mixer_selem_has_capture_channel(录音)函数可判断指定元素是否包含指定通道,其函数原型如下所示:

int snd_mixer_selem_has_playback_channel(snd_mixer_elem_t elem,snd_mixer_selem_channel_id_t channel);int snd_mixer_selem_has_capture_channel(snd_mixer_elem_t elem,snd_mixer_selem_channel_id_t channel);

参数channel用于指定一个通道,snd_mixer_selem_channel_id_t是一个列举类型,如下所示:

enum snd_mixer_selem_channel_id_t {SND_MIXER_SCHN_UNKNOWN = -1,SND_MIXER_SCHN_FRONT_LEFT = 0, //左前SND_MIXER_SCHN_FRONT_RIGHT, //右前SND_MIXER_SCHN_REAR_LEFT, //左后SND_MIXER_SCHN_REAR_RIGHT, //右后SND_MIXER_SCHN_FRONT_CENTER, //前中SND_MIXER_SCHN_WOOFER, //低音喇叭SND_MIXER_SCHN_SIDE_LEFT, //左侧SND_MIXER_SCHN_SIDE_RIGHT, //右侧SND_MIXER_SCHN_REAR_CENTER, //后中SND_MIXER_SCHN_LAST = 31,SND_MIXER_SCHN_MONO = SND_MIXER_SCHN_FRONT_LEFT //单声道};

如果元素是双声道元素,常日只包含左前(SND_MIXER_SCHN_FRONT_LEFT)和右前(SND_MIXER_SCHN_FRONT_RIGHT)两个声道。
如果是单声道设备,常日只包含SND_MIXER_SCHN_MONO,其数值即是SND_MIXER_SCHN_FRONT_LEFT。

可以调用snd_mixer_selem_is_playback_mono(播放)或snd_mixer_selem_is_capture_mono(录音)函数判断一个指定的元素是否是单声道元素,其函数原型如下所示:

int snd_mixer_selem_is_playback_mono(snd_mixer_elem_t elem);int snd_mixer_selem_is_capture_mono(snd_mixer_elem_t elem);

snd_mixer_selem_get_playback_volume/snd_mixer_selem_get_capture_volume

调用snd_mixer_selem_get_playback_volume(播放)或snd_mixer_selem_get_capture_volume(录音)获取指定元素的音量大小,其函数原型如下所示:

int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t elem,snd_mixer_selem_channel_id_t channel,long value);int snd_mixer_selem_get_capture_volume(snd_mixer_elem_t elem,snd_mixer_selem_channel_id_t channel,long value);

参数elem指定对应的元素,参数channel指定该元素的某个声道。
调用snd_mixer_selem_get_playback_volume()函数可获取elem元素的channel声道对应的音量大小,并将获取到的音量值通过value返回出来。

函数调用成功返回0,失落败返回一个小于0的缺点码。

譬如,获取左前声道的音量(播放):

long value;snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &value);

snd_mixer_selem_set_playback_volume/snd_mixer_selem_set_capture_volume

设置指定元素的音量值,其函数原型如下所示:

int snd_mixer_selem_set_playback_volume(snd_mixer_elem_t elem,snd_mixer_selem_channel_id_t channel,long value);int snd_mixer_selem_set_capture_volume(snd_mixer_elem_t elem,snd_mixer_selem_channel_id_t channel,long value);调用snd_mixer_selem_set_playback_volume(播放)或snd_mixer_selem_set_capture_volume(录音)设置元素的某个声道的音量,参数elem指定元素、参数channel指定该元素的某个声道,参数value指定音量值。

调用snd_mixer_selem_set_playback_volume_all/snd_mixer_selem_set_capture_volume_all可一次性设置指定元素所有声道的音量,函数原型如下所示:

int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t elem,long value);int snd_mixer_selem_set_capture_volume_all(snd_mixer_elem_t elem,long value);

snd_mixer_selem_get_playback_volume_range/snd_mixer_selem_get_capture_volume_range

获取指定元素的音量范围,其函数原型如下所示:

int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t elem,long min,long max);int snd_mixer_selem_get_capture_volume_range(snd_mixer_elem_t elem,long min,long max);示例程序

本小节我们将对示例代码 29.7.1进行修正,添加音量掌握,示例代码如下所示:

本例程源码对应的路径为:开拓板光盘->11、Linux C运用编程例程源码->29_alsa-lib->pcm_playback_mixer.c。

示例代码 29.9.1 PCM播放示例程序(加入状态掌握)/Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.文件名 : pcm_playback_mixer.c作者 : 邓涛版本 : V1.0描述 : 一个大略地PCM播放示例代码--利用异步办法、加入混音器设置其他 : 无论坛 : www.openedv.com日志 : 初版 V1.0 2021/7/20 邓涛创建/#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <termios.h>#include <signal.h>#include <alsa/asoundlib.h>/宏定义/#define PCM_PLAYBACK_DEV "hw:0,0"#define MIXER_DEV "hw:0"/WAV音频文件解析干系数据构造申明/typedef struct WAV_RIFF {char ChunkID[4]; / "RIFF" /u_int32_t ChunkSize; / 从下一个地址开始到文件末端的总字节数 /char Format[4]; / "WAVE" /} __attribute__ ((packed)) RIFF_t;typedef struct WAV_FMT {char Subchunk1ID[4]; / "fmt " /u_int32_t Subchunk1Size; / 16 for PCM /u_int16_t AudioFormat; / PCM = 1/u_int16_t NumChannels; / Mono = 1, Stereo = 2, etc. /u_int32_t SampleRate; / 8000, 44100, etc. /u_int32_t ByteRate; / = SampleRate NumChannels BitsPerSample/8 /u_int16_t BlockAlign; / = NumChannels BitsPerSample/8 /u_int16_t BitsPerSample; / 8bits, 16bits, etc. /} __attribute__ ((packed)) FMT_t;static FMT_t wav_fmt;typedef struct WAV_DATA {char Subchunk2ID[4]; / "data" /u_int32_t Subchunk2Size; / data size /} __attribute__ ((packed)) DATA_t;/static静态全局变量定义/static snd_pcm_t pcm = NULL; //pcm句柄static snd_mixer_t mixer = NULL; //混音器句柄static snd_mixer_elem_t playback_vol_elem = NULL; //播放<音量掌握>元素static unsigned int buf_bytes; //运用程序缓冲区的大小(字节为单位)static void buf = NULL; //指向运用程序缓冲区的指针static int fd = -1; //指向WAV音频文件的文件描述符static snd_pcm_uframes_t period_size = 1024; //周期大小(单位: 帧)static unsigned int periods = 16; //周期数(设备驱动层buffer的大小)static struct termios old_cfg; //用于保存终端当前的配置参数/static静态函数/static void snd_playback_async_callback(snd_async_handler_t handler){snd_pcm_t handle = snd_async_handler_get_pcm(handler);//获取PCM句柄snd_pcm_sframes_t avail;int ret;avail = snd_pcm_avail_update(handle);//获取环形缓冲区中有多少帧数据须要添补while (avail >= period_size) { //我们一次写入一个周期memset(buf, 0x00, buf_bytes); //buf清零ret = read(fd, buf, buf_bytes);if (0 >= ret)goto out;ret = snd_pcm_writei(handle, buf, period_size);if (0 > ret) {fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));goto out;}else if (ret < period_size) {//实际写入的帧数小于指定的帧数//此时我们须要调度下音频文件的读位置//将读位置向后移动(往回移)(period_size-ret)frame_bytes个字节//frame_bytes表示一帧的字节大小if (0 > lseek(fd, (ret-period_size) wav_fmt.BlockAlign, SEEK_CUR)) {perror("lseek error");goto out;}}avail = snd_pcm_avail_update(handle); //再次获取、更新avail}return;out:snd_pcm_drain(pcm); //停滞PCMsnd_mixer_close(mixer); //关闭混音器snd_pcm_close(handle); //关闭pcm设备tcsetattr(STDIN_FILENO, TCSANOW, &old_cfg); //退出前规复终真个状态free(buf);close(fd); //关闭打开的音频文件exit(EXIT_FAILURE); //退出程序}static int snd_pcm_init(void){snd_pcm_hw_params_t hwparams = NULL;snd_async_handler_t async_handler = NULL;int ret;/ 打开PCM设备 /ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_open error: %s: %s\n",PCM_PLAYBACK_DEV, snd_strerror(ret));return -1;}/ 实例化hwparams工具 /snd_pcm_hw_params_malloc(&hwparams);/ 获取PCM设备当前硬件配置,对hwparams进行初始化 /ret = snd_pcm_hw_params_any(pcm, hwparams);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));goto err2;}/设置参数// 设置访问类型: 交错模式 /ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));goto err2;}/ 设置数据格式: 有符号16位、小端模式 /ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));goto err2;}/ 设置采样率 /ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));goto err2;}/ 设置声道数: 双声道 /ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));goto err2;}/ 设置周期大小: period_size /ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));goto err2;}/ 设置周期数(驱动层环形缓冲区buffer的大小): periods /ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));goto err2;}/ 使配置生效 /ret = snd_pcm_hw_params(pcm, hwparams);snd_pcm_hw_params_free(hwparams); //开释hwparams工具占用的内存if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));goto err1;}buf_bytes = period_size wav_fmt.BlockAlign; //变量赋值,一个周期的字节大小/ 注册异步处理函数 /ret = snd_async_add_pcm_handler(&async_handler, pcm, snd_playback_async_callback, NULL);if (0 > ret) {fprintf(stderr, "snd_async_add_pcm_handler error: %s\n", snd_strerror(ret));goto err1;}return 0;err2:snd_pcm_hw_params_free(hwparams); //开释内存err1:snd_pcm_close(pcm); //关闭pcm设备return -1;}static int snd_mixer_init(void){snd_mixer_elem_t elem = NULL;const char elem_name;long minvol, maxvol;int ret;/ 打开混音器 /ret = snd_mixer_open(&mixer, 0);if (0 > ret) {fprintf(stderr, "snd_mixer_open error: %s\n", snd_strerror(ret));return -1;}/ 关联一个声卡掌握设备 /ret = snd_mixer_attach(mixer, MIXER_DEV);if (0 > ret) {fprintf(stderr, "snd_mixer_attach error: %s\n", snd_strerror(ret));goto err;}/ 注册混音器 /ret = snd_mixer_selem_register(mixer, NULL, NULL);if (0 > ret) {fprintf(stderr, "snd_mixer_selem_register error: %s\n", snd_strerror(ret));goto err;}/ 加载混音器 /ret = snd_mixer_load(mixer);if (0 > ret) {fprintf(stderr, "snd_mixer_load error: %s\n", snd_strerror(ret));goto err;}/ 遍历混音器中的元素 /elem = snd_mixer_first_elem(mixer);//找到第一个元素while (elem) {elem_name = snd_mixer_selem_get_name(elem);//获取元素的名称/ 针对开拓板出厂系统:WM8960声卡设备 /if(!strcmp("Speaker", elem_name) || //耳机音量<对喇叭外音输出有效>!strcmp("Headphone", elem_name) ||//喇叭音量<对耳机输出有效>!strcmp("Playback", elem_name)) {//播放音量<总的音量掌握,对喇叭和耳机输出都有效>if (snd_mixer_selem_has_playback_volume(elem)) {//是否是音量掌握元素snd_mixer_selem_get_playback_volume_range(elem, &minvol, &maxvol);//获取音量可设置范围snd_mixer_selem_set_playback_volume_all(elem, (maxvol-minvol)0.9 + minvol);//全部设置为90%if (!strcmp("Playback", elem_name))playback_vol_elem = elem;}}elem = snd_mixer_elem_next(elem);}return 0;err:snd_mixer_close(mixer);return -1;}static int open_wav_file(const char file){RIFF_t wav_riff;DATA_t wav_data;int ret;fd = open(file, O_RDONLY);if (0 > fd) {fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));return -1;}/ 读取RIFF chunk /ret = read(fd, &wav_riff, sizeof(RIFF_t));if (sizeof(RIFF_t) != ret) {if (0 > ret)perror("read error");elsefprintf(stderr, "check error: %s\n", file);close(fd);return -1;}if (strncmp("RIFF", wav_riff.ChunkID, 4) ||//校验strncmp("WAVE", wav_riff.Format, 4)) {fprintf(stderr, "check error: %s\n", file);close(fd);return -1;}/ 读取sub-chunk-fmt /ret = read(fd, &wav_fmt, sizeof(FMT_t));if (sizeof(FMT_t) != ret) {if (0 > ret)perror("read error");elsefprintf(stderr, "check error: %s\n", file);close(fd);return -1;}if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4)) {//校验fprintf(stderr, "check error: %s\n", file);close(fd);return -1;}/ 打印音频文件的信息 /printf("<<<<音频文件格式信息>>>>\n\n");printf(" file name: %s\n", file);printf(" Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);printf(" AudioFormat: %u\n", wav_fmt.AudioFormat);printf(" NumChannels: %u\n", wav_fmt.NumChannels);printf(" SampleRate: %u\n", wav_fmt.SampleRate);printf(" ByteRate: %u\n", wav_fmt.ByteRate);printf(" BlockAlign: %u\n", wav_fmt.BlockAlign);printf(" BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);/ sub-chunk-data /if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,SEEK_SET)) {perror("lseek error");close(fd);return -1;}while(sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t))) {/ 找到sub-chunk-data /if (!strncmp("data", wav_data.Subchunk2ID, 4))//校验return 0;if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR)) {perror("lseek error");close(fd);return -1;}}fprintf(stderr, "check error: %s\n", file);return -1;}static void show_help(void){printf("<<<<<<<基于alsa-lib音乐播放器>>>>>>>>>\n\n""操作菜单:\n"" q 退出程序\n"" space<空格> 停息播放/规复播放\n"" w 音量增加++\n"" s 音量减小--\n\n");}/main主函数/int main(int argc, char argv[]){snd_pcm_sframes_t avail;struct termios new_cfg;sigset_t sset;int ret;if (2 != argc) {fprintf(stderr, "Usage: %s <audio_file>\n", argv[0]);exit(EXIT_FAILURE);}/ 屏蔽SIGIO旗子暗记 /sigemptyset(&sset);sigaddset(&sset, SIGIO);sigprocmask(SIG_BLOCK, &sset, NULL);/ 打开WAV音频文件 /if (open_wav_file(argv[1]))exit(EXIT_FAILURE);/ 初始化PCM Playback设备 /if (snd_pcm_init())goto err1;/ 初始化混音器 /if (snd_mixer_init())goto err2;/ 申请读缓冲区 /buf = malloc(buf_bytes);if (NULL == buf) {perror("malloc error");goto err3;}/ 终端配置 /tcgetattr(STDIN_FILENO, &old_cfg); //获取终端<标准输入-标准输出构成了一套终端>memcpy(&new_cfg, &old_cfg, sizeof(struct termios));//备份new_cfg.c_lflag &= ~ICANON; //将终端设置为非规范模式new_cfg.c_lflag &= ~ECHO; //禁用回显tcsetattr(STDIN_FILENO, TCSANOW, &new_cfg);//使配置生效/ 播放:先将环形缓冲区填满数据 /avail = snd_pcm_avail_update(pcm); //获取环形缓冲区中有多少帧数据须要添补while (avail >= period_size) { //我们一次写入一个周期memset(buf, 0x00, buf_bytes); //buf清零ret = read(fd, buf, buf_bytes);if (0 >= ret)goto err4;ret = snd_pcm_writei(pcm, buf, period_size);//向环形缓冲区中写入数据if (0 > ret) {fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));goto err4;}else if (ret < period_size) {//实际写入的帧数小于指定的帧数//此时我们须要调度下音频文件的读位置//将读位置向后移动(往回移)(period_size-ret)frame_bytes个字节//frame_bytes表示一帧的字节大小if (0 > lseek(fd, (ret-period_size) wav_fmt.BlockAlign, SEEK_CUR)) {perror("lseek error");goto err4;}}avail = snd_pcm_avail_update(pcm); //再次获取、更新avail}sigprocmask(SIG_UNBLOCK, &sset, NULL); //取消SIGIO旗子暗记屏蔽/ 显示帮助信息 /show_help();/ 等待获取用户输入 /char ch;long vol;for ( ; ; ) {ch = getchar(); //获取用户输入的掌握字符switch (ch) {case 'q': //Q键退出程序sigprocmask(SIG_BLOCK, &sset, NULL);//屏蔽SIGIO旗子暗记goto err4;case ' ': //空格停息/规复switch (snd_pcm_state(pcm)) {case SND_PCM_STATE_PAUSED: //如果是停息状态则规复运行ret = snd_pcm_pause(pcm, 0);if (0 > ret)fprintf(stderr, "snd_pcm_pause error: %s\n", snd_strerror(ret));break;case SND_PCM_STATE_RUNNING: //如果是运行状态则停息ret = snd_pcm_pause(pcm, 1);if (0 > ret)fprintf(stderr, "snd_pcm_pause error: %s\n", snd_strerror(ret));break;}break;case 'w': //音量增加if (playback_vol_elem) {//获取音量snd_mixer_selem_get_playback_volume(playback_vol_elem,SND_MIXER_SCHN_FRONT_LEFT, &vol);vol++;//设置音量snd_mixer_selem_set_playback_volume_all(playback_vol_elem, vol);}break;case 's': //音量降落if (playback_vol_elem) {//获取音量snd_mixer_selem_get_playback_volume(playback_vol_elem,SND_MIXER_SCHN_FRONT_LEFT, &vol);vol--;//设置音量snd_mixer_selem_set_playback_volume_all(playback_vol_elem, vol);}break;}}err4:snd_pcm_drop(pcm); //停滞PCMtcsetattr(STDIN_FILENO, TCSANOW, &old_cfg); //退出前规复终真个状态free(buf); //开释内存err3:snd_mixer_close(mixer); //关闭混音器err2:snd_pcm_close(pcm); //关闭pcm设备err1:close(fd); //关闭打开的音频文件exit(EXIT_FAILURE);}

main()函数中调用了自定义函数snd_mixer_init()对声卡混音器进行了初始化,snd_mixer_init()函数中做的事情,也便是上面给大家所先容的流程:首先打开一个空的混音器、attach关联一个声卡掌握设备、注册混音器、加载混音器,全体这一套操作完成之后,就可以去利用混音器了;查找混音器中的元素,对元素进行配置。

在snd_mixer_init()函数中,我们对WM8960声卡的"Speaker"元素(喇叭输出音量)、"Headphone"元素(耳机输出音量)以及"Playback"元素(播放音量)进行了配置,将它们都设置为90%;之后将"Playback"元素的句柄赋值给全局静态变量playback_vol_elem。

回到main()函数,在for循环中,获取用户输入的掌握字符,在这里我们添加了w和s,当用户按下w键时增加音量、按下s键时降落音量,这里掌握的音量是WM8960的"Playback"音量(播放音量)。

编译运用程序

编译上述示例代码:

${CC} -o testApp testApp.c -lasound

图 29.9.2 编译示例代码

测试运用程序

将编译得到的可实行文件拷贝到开拓板Linux系统的/home/root目录下,准备一个WAV格式的音频文件,实行测试程序:

./testApp ./EXO-Overdose.wav

图 29.9.3 实行测试程序

命令实行之后会显示操作办法,大家可以根据提示自己测试!

回环测试例程

alsa-utils供应了一个用于回环测试的工具alsaloop,可以实现边录音、边播放,该程序用法比较大略,实行"alsaloop --help"可以查看alsaloop测试程序的利用帮助信息,如下所示:

图 29.10.1 alsaloop工具利用帮助信息

譬如直接运行"alsaloop -t 1000"可以进行测试,大家可以自己亲自测试下。

回环测试事理上很大略,录制音频、然后再播放出来,但是事实上并不如此,还须要考虑到很多的成分,由于对付录音和播放来说,录制一个周期和播放一个周期,硬件上处理这一个周期所花费的韶光并不相同,一个是ADC过程、而一个是DAC过程,以是每每很随意马虎涌现XRUN,以是如何有效合理地设计你的运用程序将变得很主要、以最大限度降落XRUN情形的发生。

笔者测试过alsaloop工具,虽然也会涌现XRUN,但比较少;如果对此有兴趣的读者,可以参考alsaloop程序的源代码,直接下载alsa-util源码包,在alsa-util源码包中就可以找到alsaloop程序的源码,如下所示:

图 29.10.2 alsaloop源码

除了alsaloop的源码之外,还包括前面所先容的aplay、alsamixer、amixer、alsactl等这些工具的源码都在这里,有兴趣的读者可以看看。

总结

本章我们学习了Linux下的音频运用编程,运用程序基于alsa-lib库实现播放、录音等功能,本章并没有做过多深入的学习,仅仅只是给大家先容了alsa-lib库函数中一些基本的API接口,个中还有绝大部分的接口并没有给大家先容,如果大家有兴趣,可以自己深入研究、学习!

本小节我们来聊一聊ALSA插件。

ALSA插件(plugin)

ALSA供应了一些PCM插件,以扩展PCM设备的功能和特性,插件卖力各种样本转换、通道之间的样本复制等。

调用snd_pcm_open()函数时,须要填写PCM设备名,alsa-lib库利用逻辑设备名而不是设备节点名。
前面编写的示例程序中,我们利用了"hw:i,j"这种格式的名字,这实在指定的是一个名为hw的插件,而冒号后面的两个数字i和j表示两个参数,也便是利用该插件时传入的两个参数(第一个参数表示声卡号,第二个参数表示设备号)。

开拓板Linux系统的/usr/share/alsa/目录下有一个名为alsa.conf的文件,如下所示:

图 29.11.1 alsa.conf文件

该文件是alsa-lib库的配置文件,调用snd_pcm_open()函数时会加载/usr/share/alsa/alsa.conf文件并解析,从上图中可知,/usr/share/alsa/alsa.conf文件中会加载并解析/etc/asound.conf和~/.asoundrc这两个配置文件,在我们的开拓板出厂系统中,有/etc/asound.conf配置文件、但并没有~/.asoundrc文件。

/usr/share/alsa/alsa.conf配置文件作为alsa-lib库函数的紧张入口点,对alsa-lib进行了配置并定义了一些基本、通用的PCM插件;而.asoundrc和asound.conf文件的引入供应用户定制化需求,用户可以在这两个文件中根据自己的需求定义插件。

关于插件的定义以及干系的阐明解释,大家可以参考以下两份ALSA供应的文档:

https://www.alsa-project.org/main/index.php/Asoundrc

https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html

譬如开拓板出厂系统/etc/asound.conf文件中定义很多的PCM插件,如下所示:

图 29.11.2 /etc/asound.conf文件中定义的插件

上图中的每一个pcm.name { }就定义了一个插件,name表示插件的名字,譬如dmix_48000、dmix_44100、dmix_32000等;而点号前面的pcm表示name是一个PCM插件,用于PCM设备;中括号{ }里边的内容则是对插件的属性定义。

中括号{ }中,type字段指定了插件的类型,alsa-lib支持多种不同的插件类型,譬如hw、plughw、mmap_emul、shm、linear、plug、multi、share、dmix、dsnoop、softvol等等,不同的类型的插件支持不同的功能、特性,下面给大家大略地进行先容。

hw插件

该插件直接与ALSA内核驱动程序通信,这是一种没有任何转换的原始通信。
运用程序调用alsa-lib库函数直接操作了底层音频硬件设置,譬如对PCM设备的配置、直接浸染于硬件。

plughw插件

该插件能够供应诸如采样率转换这样的软件特性,硬件本身并不支持这样的特性。
譬如,运用程序播放的音频文件是48000采样率,但是底层音频硬件本身并不支持这种采样率,以是调用snd_pcm_hw_params_set_rate()函数将PCM设备的采样率设置为48000时会导致缺点!

这时可以利用plughw插件,它支持采样率转换这样的软件特性。

dmix插件

该支持混音,将多个运用程序的音频数据进行稠浊。

softvol插件

支持软件音量。

关于这些插件更加的详细地先容解释,请查看ALSA供应的文档。

标签:

相关文章