DSP

Linux 音频设备驱动

2019-07-13 19:21发布

第十七章 Linux 音频设备驱动
本章导读
在Linux 中,先后出现了音频设备的两种框架OSS 和ALSA,本节将在介绍数字音频设备及音频设备硬件接
口的基础上,展现OSS 和ALSA 驱动的结构。
17.1~17.2 节讲解了音频设备及PCM、IIS 和AC97 硬件接口。
17.3 节阐述了Linux OSS 音频设备驱动的组成、mixer 接口、dsp 接口及用户空间编程方法。
17.4 节阐述了Linux ALSA 音频设备驱动的组成、card 和组件管理、PCM 设备、control 接口、AC97 API
及用户空间编程方法。
17.5 节以S3C2410 通过IIS 接口外接UDA1341 编解码器的实例讲解了OSS 驱动。
17.6 节以PXA255 通过AC97 接口外接AC97 编解码器的实例讲解了ALSA 驱动。
17.1 数字音频设备
目前,手机、PDA、MP3 等许多嵌入式设备中包含了数字音频设备,一个典型的数字音频系统的电路组成如
图17.1 所示。图17.1 中的嵌入式微控制器/DSP 中集成了PCM、IIS 或AC97 音频接口,通过这些接口连接
外部的音频编解码器即可实现声音的AD 和DA 转换,图中的功放完成模拟信号的放大功能。
图17.1 典型的数字音频系统电路
音频编解码器是数字音频系统的核心,衡量它的指标主要有:
• 采样频率
采样的过程就是将通常的模拟音频信号的电信号转换成二进制码0 和1 的过程,这些0 和1 便构成了数字
音频文件。如图17.2 中的正弦曲线代表原始音频曲线,方格代表采样后得到的结果,二者越吻合说明采样
结果越好。
采样频率是每秒钟的采样次数,我们常说的 44.1kHz 采样频率就是每秒钟采样44100 次。理论上采样频
率越高,转换精度越高,目前主流的采样频率是48kHz。
• 量化精度
量化精度是指对采样数据分析的精度,比如24bit 量化精度就是是将标准电平信号按照2 的24 次方进行分
析,也就是说将图17.2 中的纵坐标等分为224 等分。量化精度越高,声音就越逼真。
图17.2 数字音频采样
17.2 音频设备硬件接口
17.2.1 PCM 接口
针对不同的数字音频子系统,出现了几种微处理器或DSP 与音频器件间用于数字转换的接口。
最简单的音频接口是PCM(脉冲编码调制)接口,该接口由时钟脉冲(BCLK)、帧同步信号(FS)及接收
数据(DR)和发送数据(DX)组成。在FS 信号的上升沿,数据传输从MSB(Most Significant Bit)字开
始,FS 频率等于采样率。FS 信号之后开始数据字的传输,单个的数据位按顺序进行传输,1 个时钟周期传
输1 个数据字。发送MSB 时,信号的等级首先降到最低,以避免在不同终端的接口使用不同的数据方案时
造成MSB 的丢失。
PCM 接口很容易实现,原则上能够支持任何数据方案和任何采样率,但需要每个音频通道获得一个独立的
数据队列。
17.2.2 IIS 接口
IIS 接口(Inter-IC Sound)在20 世纪80 年代首先被飞利浦用于消费音频,并在一个称为LRCLK(Left/Right
CLOCK)的信号机制中经过多路转换,将两路音频信号变成单一的数据队列。当LRCLK 为高时,左声道数据
被传输;LRCLK 为低时,右声道数据被传输。与PCM 相比,IIS 更适合于立体声系统。对于多通道系统,在
同样的BCLK 和LRCLK 条件下,并行执行几个数据队列也是可能的。
17.2.3 AC97 接口
AC'97(Audio Codec 1997)是以Intel 为首的五个PC 厂商Intel、Creative Labs、NS、Analog Device
与Yamaha 共同提出的规格标准。与PCM 和IIS 不同,AC'97 不只是一种数据格式,用于音频编码的内部架
构规格,它还具有控制功能。AC'97 采用AC-Link 与外部的编解码器相连,AC-Link 接口包括位时钟(BITCLK)、
同步信号校正(SYNC)和从编码到处理器及从处理器中解码(SDATDIN 与SDATAOUT)的数据队列。AC'97
数据帧以SYNC 脉冲开始,包括12 个20 位时间段(时间段为标准中定义的不同的目的服务)及16 位“tag”
段,共计256 个数据序列。例如,时间段“1”和“2”用于访问编码的控制寄存器,而时间段“3”和“4”
分别负载左、右两个音频通道。“tag”段表示其他段中哪一个包含有效数据。把帧分成时间段使传输控制
信号和音频数据仅通过4 根线到达9 个音频通道或转换成其他数据流成为可能。与具有分离控制接口的IIS
方案相比,AC'97 明显减少了整体管脚数。一般来说,AC'97 编解码器采用TQFP48 封装,如图17.3 所示。
图17.3 AC97 Codec 芯片
PCM、IIS 和AC97 各有其优点和应用范围,例如在CD、MD、MP3 随身听多采用IIS 接口,移动电话会采用
PCM 接口,具有音频功能的PDA 则多使用和PC 一样的AC'97 编码格式。
17.3 Linux OSS 音频设备驱动
17.3.1 OSS 驱动的组成
OSS 标准中有2 个最基本的音频设备:mixer(混音器)和DSP(数字信号处理器)。
在声卡的硬件电路中,mixer 是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对
于不同的声卡来说,其混音器的作用可能各不相同。OSS 驱动中,/dev/mixer 设备文件是应用程序对mixer
进行操作的软件接口。
混音器电路通常由两个部分组成:输入混音器(input mixer)和输出混音器(output mixer)。输入混音
器负责从多个不同的信号源接收模拟信号,这些信号源有时也被称为混音通道或者混音设备。模拟信号通
过增益控制器和由软件控制的音量调节器后,在不同的混音通道中进行级别(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 之后,接下去就应该为其设置恰当的格式,然后才能从声卡读取或者写入
数据。
17.3.2 mixer 接口
int register_sound_mixer(struct file_operations *fops, int dev);
上述函数用于注册1 个混音器,第1 个参数fops 即是文件操作接口,第2 个参数dev 是设备编号,如果填
入-1,则系统自动分配1 个设备编号。mixer 是1 个典型的字符设备,因此编码的主要工作是实现
file_operations 中的open()、ioctl()等函数。
mixer 接口file_operations 中的最重要函数是ioctl(),它实现混音器的不同IO 控制命令,代码清单17.1
给出了1 个ioctl()的范例。
代码清单17.1 mixer()接口ioctl()函数范例
1 static int mixdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned
long arg)
2 {
3 ...
4 switch (cmd)
5 {
6 case SOUND_MIXER_READ_MIC:
7 ...
8 case SOUND_MIXER_WRITE_MIC:
9 ...
10 case SOUND_MIXER_WRITE_RECSRC:
11 ...
12 case SOUND_MIXER_WRITE_MUTE:
13 ...
14 }
15 //其它命令
16 return mixer_ioctl(codec, cmd, arg);
17 }
17.3.3 DSP 接口
int register_sound_dsp(struct file_operations *fops, int dev);
上述函数与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 驱动中,建立存放音频数据的环形缓冲区(ring buffer)通常是值得推荐的方法。此外,在OSS 驱
动中,一般会将1 个较大的DMA 缓冲区分成若干个大小相同的块(这些块也被称为“段”,即fragment),
驱动程序使用DMA 每次在声音缓冲区和声卡之间搬移一个fragment。在用户空间,可以使用ioctl()系统
调用来调整块的大小和个数。
除了read()、write()和ioctl()外,dsp 接口的poll()函数通常也需要被实现,以向用户反馈目前能否读
写DMA 缓冲区。
在OSS 驱动初始化过程中,会调用register_sound_dsp()和register_sound_mixer()注册dsp 和mixer 设
备;在模块卸载的时候,会调用如代码清单17.2。
代码清单17.2 OSS 驱动初始化注册dsp 和mixer 设备
1 static int xxx_init(void)
2 {
3 struct xxx_state *s = &xxx_state;
4 ...
5 //注册dsp 设备
6 if ((audio_dev_dsp = register_sound_dsp(&xxx_audio_fops, - 1)) < 0)
7 goto err_dev1;
8 //设备mixer 设备
9 if ((audio_dev_mixer = register_sound_mixer(&xxx_mixer_fops, - 1)) < 0)
10 goto err_dev2;
11 ...
12 }
13
14 void __exit xxx_exit(void)
15 {
16 //注销dsp 和mixer 设备接口
17 unregister_sound_dsp(audio_dev_dsp);
18 unregister_sound_mixer(audio_dev_mixer);
19 ...
20 }
根据17.3.2 和17.3.3 节的分析,可以画出一个Linux OSS 驱动结构的简图,如图17.4 所示。
图17.4 Linux OSS 驱动结构
17.3.4 OSS 用户空间编程
1、DSP 编程
对OSS 驱动声卡的编程使用Linux 文件接口函数,如图17.5,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_SETFMT IO 控制命
令可以设置采样格式。
对于大多数声卡来说,其支持的采样频率范围一般为5kHz 到44.1kHz 或者48kHz,但并不意味着该范围内
的所有连续频率都会被硬件支持,在Linux 下进行音频编程时最常用到的几种采样频率是11025Hz、
16000Hz、22050Hz、32000Hz 和44100Hz。使用SNDCTL_DSP_SPEED IO 控制命令可以设置采样频率。
⑤ 读写/dev/dsp 实现播放或录音。
图17.5 OSS dsp 接口用户空间操作流程
代码清单17.3 的程序实现了利用/dev/dsp 接口进行声音录制和播放的过程,它的功能是先录制几秒钟音
频数据,将其存放在内存缓冲区中,然后再进行放音。
代码清单17.3 OSS DSP 接口应用编程范例
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #define LENGTH 3 /* 存储秒数 */
9 #define RATE 8000 /* 采样频率 */
10 #define SIZE 8 /* 量化位数 */
11 #define CHANNELS 1 /* 声道数目 */
12 /* 用于保存数字音频数据的内存缓冲区 */
13 unsigned char buf[LENGTH *RATE * SIZE * CHANNELS / 8];
14 int main()
15 {