你知道Linux 音频设备驱动架构及应用编程.docx
《你知道Linux 音频设备驱动架构及应用编程.docx》由会员分享,可在线阅读,更多相关《你知道Linux 音频设备驱动架构及应用编程.docx(9页珍藏版)》请在冰豆网上搜索。
![你知道Linux 音频设备驱动架构及应用编程.docx](https://file1.bdocx.com/fileroot1/2022-11/29/a7a05ad5-76e6-47b8-9f2e-e21e3d028d31/a7a05ad5-76e6-47b8-9f2e-e21e3d028d311.gif)
你知道Linux音频设备驱动架构及应用编程
你知道Linux音频设备驱动架构及应用编程?
最早出现在Linux上的音频编程接口是OSS(OpenSoundSystem),它由一套完整的内核驱动程序模块组成,可以为绝大多数声卡提供统一的编程接口。
OSS出现的历史相对较长,这些内核模块中的一部分(OSS/Free)是与Linux内核源码共同免费发布的,另外一些则以二进制的形式由4FrontTechnologies公司提供。
由于得到了商业公司的鼎力支持,OSS已经成为在Linux下进行音频编程的事实标准,支持OSS的应用程序能够在绝大多数声卡上工作良好。
虽然OSS已经非常成熟,但它毕竟是一个没有完全开放源代码的商业产品,ALSA(AdvancedLinuxSoundArchitecture)恰好弥补了这一空白,它是在Linux下进行音频编程时另一个可供选择的声卡驱动程序。
ALSA除了像OSS那样提供了一组内核驱动程序模块之外,还专门为简化应用程序的编写提供了相应的函数库,与OSS提供的基于ioctl的原始编程接口相比,ALSA函数库使用起来要更加方便一些。
ALSA的主要特点有:
1)支持多种声卡设备 2)模块化的内核驱动程序 3)支持SMP和多线程 4)提供应用开发函数库 5)兼容OSS应用程序
ALSA和OSS最大的不同之处在于ALSA是由志愿者维护的自由项目,而OSS则是由公司提供的商业产品,因此在对硬件的适应程度上OSS要优于ALSA,它能够支持的声卡种类更多。
ALSA虽然不及OSS运用得广泛,但却具有更加友好的编程接口,并且完全兼容于OSS,对应用程序员来讲无疑是一个更佳的选择。
两种音频编程接口驱动的组成如下:
1)LinuxOSS音频设备驱动的组成、mixer接口、dsp接口及用户空间编程方法。
2)LinuxALSA音频设备驱动的组成、card和组件管理、PCM设备、control接口、AC97API及用户空间编程方法。
1.数字音频设备
目前,手机、PDA、MP3等许多嵌入式设备中包含了数字音频设备,一个典型的数字音频系统的电路组成为:
嵌入式微控制器/DSP中集成了PCM、IIS或AC97音频接口,通过这些接口连接外部的音频编解码器即可实现声音的AD和DA转换,功放完成模拟信号的放大功能。
音频编解码器是数字音频系统的核心,衡量它的指标主要有:
•采样频率 采样的过程就是将通常的模拟音频信号的电信号转换成二进制码0和1的过程,这些0和1便构成了数字音频文件。
如图17.2中的正弦曲线代表原始音频曲线,方格代表采样后得到的结果,二者越吻合说明采样结果越好。
采样频率是每秒钟的采样次数,我们常说的44.1kHz采样频率就是每秒钟采样44100次。
理论上采样频率越高,转换精度越高,目前主流的采样频率是48kHz。
•量化精度 量化精度是指对采样数据分析的精度,比如24bit量化精度就是是将标准电平信号按照2的24次方进行分析,也就是说将图17.2中的纵坐标等分为224等分。
量化精度越高,声音就越逼真。
2.音频设备硬件接口2.1PCM接口 针对不同的数字音频子系统,出现了几种微处理器或DSP与音频器件间用于数字转换的接口。
最简单的音频接口是PCM(脉冲编码调制)接口,该接口由时钟脉冲(BCLK)、帧同步信号(FS)及接收数据(DR)和发送数据(DX)组成。
在FS信号的上升沿,数据传输从MSB(MostSignificantBit)字开始,FS频率等于采样率。
FS信号之后开始数据字的传输,单个的数据位按顺序进行传输,1个时钟周期传输1个数据字。
发送MSB时,信号的等级首先降到最低,以避免在不同终端的接口使用不同的数据方案时造成MSB的丢失。
PCM接口很容易实现,原则上能够支持任何数据方案和任何采样率,但需要每个音频通道获得一个独立的数据队列。
2.2IIS接口 IIS接口(Inter-ICSound)在20世纪80年代首先被飞利浦用于消费音频,并在一个称为LRCLK(Left/RightCLOCK)的信号机制中经过多路转换,将两路音频信号变成单一的数据队列。
当LRCLK为高时,左声道数据被传输;LRCLK为低时,右声道数据被传输。
与PCM相比,IIS更适合于立体声系统。
对于多通道系统,在同样的BCLK和LRCLK条件下,并行执行几个数据队列也是可能的。
2.3AC97接口 AC97(AudioCodec1997)是以Intel为首的五个PC厂商Intel、CreativeLabs、NS、Analog Device与Yamaha共同提出的规格标准。
与PCM和IIS不同,AC97不只是一种数据格式,用于音频编码的内部架构规格,它还具有控制功能。
AC97采用AC-Link与外部的编解码器相连,AC-Link接口包括位时钟(BITCLK)、同步信号校正(SYNC)和从编码到处理器及从处理器中解码(SDATDIN与SDATAOUT)的数据队列。
AC97数据帧以SYNC脉冲开始,包括12个20位时间段(时间段为标准中定义的不同的目的服务)及16位“tag”段,共计256个数据序列。
例如,时间段“1”和“2”用于访问编码的控制寄存器,而时间段“3”和“4”分别负载左、右两个音频通道。
“tag”段表示其他段中哪一个包含有效数据。
把帧分成时间段使传输控制信号和音频数据仅通过4根线到达9个音频通道或转换成其他数据流成为可能。
与具有分离控制接口的IIS方案相比,AC97明显减少了整体管脚数。
PCM、IIS和AC97各有其优点和应用范围,例如在CD、MD、MP3随身听多采用IIS接口,移动电话会采用PCM接口,具有音频功能的PDA则多使用和PC一样的AC97编码格式。
3.LinuxOSS音频设备驱动及应用编程3.1OSS驱动的组成 OSS标准中有2个最基本的音频设备:
mixer(混音器)和DSP(数字信号处理器)。
在声卡的硬件电路中,mixer是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不相同。
OSS驱动中,/dev/mixer设备文件是应用程序对mixer进行操作的软件接口。
混音器电路通常由两个部分组成:
输入混音器(inputmixer)和输出混音器(outputmixer)。
输入混音器负责从多个不同的信号源接收模拟信号,这些信号源有时也被称为混音通道或者混音设备。
模拟信号通过增益控制器和由软件控制的音量调节器后,在不同的混音通道中进行级别(level)调制,然后被送到输入混音器中进行声音的合成。
混音器上的电子开关可以控制哪些通道中有信号与混音器相连,有些声卡只允许连接一个混音通道作为录音的音源,而有些声卡则允许对混音通道做任意的连接。
经过输入混音器处理后的信号仍然为模拟信号,它们将被送到A/D转换器进行数字化处理。
输出混音器的工作原理与输入混音器类似,同样也有多个信号源与混音器相连,并且事先都经过了增益调节。
当输出混音器对所有的模拟信号进行了混合之后,通常还会有一个总控增益调节器来控制输出声音的大小,此外还有一些音调控制器来调节输出声音的音调。
经过输出混音器处理后的信号也是模拟信号,它们最终会被送给喇叭或者其它的模拟输出设备。
对混音器的编程包括如何设置增益控制器的级别,以及怎样在不同的音源间进行切换,这些操作通常来讲是不连续的,而且不会像录音或者放音那样需要占用大量的计算机资源。
由于混音器的操作不符合典型的读/写操作模式,因此除了open()和close()两个系统调用之外,大部分的操作都是通过ioctl()系统调用来完成的。
与/dev/dsp不同,/dev/mixer允许多个应用程序同时访问,并且混音器的设置值会一直保持到对应的设备文件被关闭为止。
DSP也称为编解码器,实现录音(录音)和放音(播放),其对应的设备文件是/dev/dsp或/dev/sound/dsp。
OSS声卡驱动程序提供的/dev/dsp是用于数字采样和数字录音的设备文件,向该设备写数据即意味着激活声卡上的D/A转换器进行放音,而向该设备读数据则意味着激活声卡上的A/D转换器进行录音。
在从DSP设备读取数据时,从声卡输入的模拟信号经过A/D转换器变成数字采样后的样本,保存在声卡驱动程序的内核缓冲区中,当应用程序通过read()系统调用从声卡读取数据时,保存在内核缓冲区中的数字采样结果将被复制到应用程序所指定的用户缓冲区中。
需要指出的是,声卡采样频率是由内核中的驱动程序所决定的,而不取决于应用程序从声卡读取数据的速度。
如果应用程序读取数据的速度过慢,以致低于声卡的采样频率,那么多余的数据将会被丢弃(即overflow);如果读取数据的速度过快,以致高于声卡的采样频率,那么声卡驱动程序将会阻塞那些请求数据的应用程序,直到新的数据到来为止。
在向DSP设备写入数据时,数字信号会经过D/A转换器变成模拟信号,然后产生出声音。
应用程序写入数据的速度应该至少等于声卡的采样频率,过慢会产生声音暂停或者停顿的现象(即underflow)。
如果用户写入过快的话,它会被内核中的声卡驱动程序阻塞,直到硬件有能力处理新的数据为止。
与其它设备有所不同,声卡通常不需要支持非阻塞(non-blocking)的I/O操作。
即便内核OSS驱动提供了非阻塞的I/O支持,用户空间也不宜采用。
无论是从声卡读取数据,或是向声卡写入数据,事实上都具有特定的格式(format),如无符号8位、单声道、8KHz采样率,如果默认值无法达到要求,可以通过ioctl()系统调用来改变它们。
通常说来,在应用程序中打开设备文件/dev/dsp之后,接下去就应该为其设置恰当的格式,然后才能从声卡读取或者写入数据。
3.2mixer接口 intregister_sound_mixer(structfile_operaTIons*fops,intdev); 上述函数用于注册1个混音器,第1个参数fops即是文件操作接口,第2个参数dev是设备编号,如果填入-1,则系统自动分配1个设备编号。
mixer是1个典型的字符设备,因此编码的主要工作是实现file_operaTIons中的open()、ioctl()等函数。
mixer接口file_operaTIons中的最重要函数是ioctl(),它实现混音器的不同IO控制命令,下面的代码清单给出了1个ioctl()的范例。
mixer()接口ioctl()函数范例:
[cpp] viewplain copy
staTIc int mixdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
...
switch (cmd)
{
case SOUND_MIXER_READ_MIC:
...
case SOUND_MIXER_WRITE_MIC:
...
case SOUND_MIXER_WRITE_RECSRC:
...
case SOUND_MIXER_WRITE_MUTE:
...
}
...
return mixer_ioctl(codec, cmd, arg);
}
3.3DSP接口 intregister_sound_dsp(structfile_operations*fops,intdev); 上述函数与register_sound_mixer()类似,它用于注册1个dsp设备,第1个参数fops即是文件操作接口,第2个参数dev是设备编号,如果填入-1,则系统自动分配1个设备编号。
dsp也是1个典型的字符设备,因此编码的主要工作是实现file_operations中的read()、write()、ioctl()等函数。
dsp接口file_operations中的read()和write()函数非常重要,read()函数从音频控制器中获取录音数据到缓冲区并拷贝到用户空间,write()函数从用户空间拷贝音频数据到内核空间缓冲区并最终发送到音频控制器。
dsp接口file_operations中的ioctl()函数处理对采样率、量化精度、DMA缓冲区块大小等参数设置IO控制命令的处理。
在数据从缓冲区拷贝到音频控制器的过程中,通常会使用DMA,DMA对声卡而言非常重要。
例如,在放音时,驱动设置完DMA控制器的源数据地址(内存中DMA缓冲区)、目的地址(音频控制器FIFO)和DMA的数据长度,DMA控制器会自动发送缓冲区的数据填充FIFO,直到发送完相应的数据长度后才中断一次。
在OSS驱动中,建立存放音频数据的环形缓冲区(ringbuffer)通常是值得推荐的方法。
此外,在OSS驱动中,一般会将1个较大的DMA缓冲区分成若干个大小相同的块(这些块也被称为“段”,即fragment),驱动程序使用DMA每次在声音缓冲区和声卡之间搬移一个fragment。
在用户空间,可以使用ioctl()系统调用来调整块的大小和个数。
除了read()、write()和ioctl()外,dsp接口的poll()函数通常也需要被实现,以向用户反馈目前能否读写DMA缓冲区。
在OSS驱动初始化过程中,会调用register_sound_dsp()和register_sound_mixer()注册dsp和mixer设备;在模块卸载的时候,调用的代码如下:
OSS驱动初始化注册dsp和mixer设备:
[cpp] viewplain copy
static int myoss_init(void)
{
struct oss_state *s =
...
//注册dsp 设备
if ((audio_dev_dsp = register_sound_dsp(">goto err_dev1;
//注册mixer 设备
if ((audio_dev_mixer = register_sound_mixer(">goto err_dev2;
...
}
void __exit xxx_exit(void)
{
//注销dsp 和mixer 设备接口
unregister_sound_dsp(audio_dev_dsp);
unregister_sound_mixer(audio_dev_mixer);
...
}
3.4OSS用户空间编程1、DSP编程对OSS驱动声卡的编程使用Linux文件接口函数,DSP接口的操作一般包括如下几个步骤:
①打开设备文件/dev/dsp 采用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写的方式打开,这还依赖于驱动程序的具体实现。
Linux允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之间进行切换。
②如果有需要,设置缓冲区大小 运行在Linux内核中的声卡驱动程序专门维护了一个缓冲区,其大小会影响到放音和录音时的效果,使用ioctl()系统调用可以对它的尺寸进行恰当的设置。
调节驱动程序中缓冲区大小的操作不是必须的,如果没有特殊的要求,一般采用默认的缓冲区大小也就可以了。
如果想设置缓冲区的大小,则通常应紧跟在设备文件打开之后,这是因为对声卡的其它操作有可能会导致驱动程序无法再修改其缓冲区的大小。
③设置声道(channel)数量 根据硬件设备和驱动程序的具体情况,可以设置为单声道或者立体声。
④设置采样格式和采样频率 采样格式包括AFMT_U8(无符号8位)、AFMT_S8(有符号8位)、AFMT_U16_LE(小端模式,无符号16位)、AFMT_U16_BE(大端模式,无符号16位)、AFMT_MPEG、AFMT_AC3等。
使用SNDCTL_DSP_SETFMTIO控制命令可以设置采样格式。
对于大多数声卡来说,其支持的采样频率范围一般为5kHz到44.1kHz或者48kHz,但并不意味着该范围内的所有连续频率都会被硬件支持,在Linux下进行音频编程时最常用到的几种采样频率是11025Hz、16000Hz、22050Hz、32000Hz和44100Hz。
使用SNDCTL_DSP_SPEEDIO控制命令可以设置采样频率。
⑤读写/dev/dsp实现播放或录音 下面代码实现了利用/dev/dsp接口进行声音录制和播放的过程,它的功能是先录制几秒钟音频数据,将其存放在内存缓冲区中,然后再进行放音。
[cpp] viewplain copy
#include
#include
#include
#include
#include
#include
#include
#define LENGTH 3 /* 存储秒数 */
#define RATE 8000 /* 采样频率 */
#define SIZE 8 /* 量化位数 */
#define CHANNELS 1 /* 声道数目 */
/* 用于保存数字音频数据的内存缓冲区 */
unsigned char buf[LENGTH *RATE * SIZE * CHANNELS / 8];
int main()
{
int fd; /* 声音设备的文件描述符 */
int arg; /* 用于ioctl 调用的参数 */
int status; /* 系统调用的返回值 */
/* 打开声音设备 */
fd = open("/dev/dsp", O_RDWR);
if (fd /* 只显示有效的混音设备 */
fprintf(stderr, "%s ", sound_device_names[i]);
fprintf(stderr, "\n");
exit
(1);
}
int main(int argc, char *argv[])
{
int left, right, level; /* 增益设置 */
int status; /* 系统调用的返回值 */
int device; /* 选用的混音设备 */
char *dev; /* 混音设备的名称 */
int i;
name = argv[0];
/* 以只读方式打开混音设备 */
fd = open("/dev/mixer", O_RDONLY);
if (fd == - 1)
{
perror("unable to open /dev/mixer");
exit
(1);
}
/* 获得所需要的信息 */
status = ioctl(fd, SOUND_MIXER_READ_DEVMASK,
if (status == - 1)
perror("SOUND_MIXER_READ_DEVMASK ioctl failed");
status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS,
if (status == - 1)
perror("SOUND_MIXER_READ_STEREODEVS ioctl failed");
/* 检查用户输入 */
if (argc !
= 3 ">usage();
/* 保存用户输入的混音器名称 */
dev = argv[1];
/* 确定即将用到的混音设备 */
for (i = 0; i break;
if (i == SOUND_MIXER_NRDEVICES)
{
/* 没有找到匹配项 */
fprintf(stderr, "%s is not a valid mixer device\n", dev);
usage();
}
/* 查找到有效的混音设备 */
device = i;
/* 获取增益值 */
if (argc == 4)
{
/* 左、右声道均给定 */
left = atoi(argv[2]);
right = atoi(argv[3]);
}
else
{
/* 左、右声道设为相等 */
left = atoi(argv[2]);
right = atoi(argv[2]);
}
/* 对非立体声设备给出警告信息 */
if ((left !
= right) ">{
fprintf(stderr, "warning:
%s is not a stereo device\n", dev);
}
/* 将两个声道的值合到同一变量中 */
level = (right << 8) + left;
/* 设置增益 */
status = ioctl(fd, MIXER_WRITE(device),
if (status == - 1)
{
perror("MIXER_WRITE ioctl failed");
exit
(1);
}
/* 获得从驱动返回的左右声道的增益 */
left = level
right = (level
/* 显示实际设置的增益 */
fprintf(stderr, "%s gain set to %d%% / %d%%\n", dev, left, right);
/* 关闭混音设备 */
close(fd);
return 0;
}
编译上述程序为可执行文件mixer,执行./mixer或./mixer可设置增益,device可以是vol、pcm、speaker、line、mic、cd、igain、line1、phin、video。