驱动篇:音频设备驱动(一)

目前,手机、PDA、MP3 等许多嵌入式设备中包含了数字音频设备,一个典型的数字音频系统的电路组成如图 17.1 所示。图 17.1 中的嵌入式微控制器/DSP 中集成了PCM、IIS 或 AC97 音频接口,通过这些接口连接外部的音频编解码器即可实现声音的 AD 和 DA 转换,图中的功放完成模拟信号的放大功能。

android BSP音频驱动 手机音频驱动器_android BSP音频驱动

音频编解码器是数字音频系统的核心,衡量它的主要指标如下

1.采样频率

采样的过程就是将通常的模拟音频信号的电信号转换成二进制码 0 和 1 的过程,这些 0 和 1 便构成了数字音频文件。 图 17.2 中的正弦曲线代表原始音频曲线,方格代表采样后得到的结果,二者越吻合说明采样结果越好采样频率是每秒钟的采样次数,我们常说的 44.1kHz 采样频率就是每秒钟采样44100 次。理论上采样频率越高,转换精度越高,目前主流的采样频率是 48kHz。

android BSP音频驱动 手机音频驱动器_数据_02

2.量化精度

量化精度是指对采样数据分析的精度,比如 24bit 量化精度就是指将标准电平信号按照 2 的 24 次方进行分析, 也就是说将图 17.2 中的纵坐标等分为 2^24 等分。 量化精度越高,声音就越逼真。

音频设备硬件接口

1.PCM 接口

针对不同的数字音频子系统,出现了几种微处理器或 DSP 与音频器件间用于数字转换的接口。最简单的音频接口是 PCM(脉冲编码调制)接口,该接口由时钟脉冲(BCLK)、帧同步信号(FS)及接收数据(DR)和发送数据(DX)组成。在 FS 信号的上升沿,数据传输从 MSB(Most Significant Bit)开始,FS 频率等于采样率。FS 信号之后开始数据字的传输,单个的数据位按顺序进行传输,一个时钟周期传输一个数据字。发送MSB 时,信号的等级首先降到最低,以避免在不同终端的接口使用不同的数据方案时造成 MSB 的丢失。PCM 接口很容易实现, 原则上能够支持任何数据方案和任何采样率, 但需要每个音频通道获得一个独立的数据队列。

2.IIS 接口

IIS 接口(Inter-IC Sound)在 20 世纪 80 年代首先被 PHILIPS 用于消费音频产品,并在一个称为LRCLK(Left/Right CLOCK)的信号机制中经过多路转换,将两路音频信号变成单一的数据队列。当 LRCLK 为高时,左声道数据被传输;LRCLK 为低时,右声道数据被传输。 与 PCM 相比, IIS 更适合于立体声系统。对于多通道系统,在同样的 BCLK 和 LRCLK条件下,并行执行几个数据队列也是可能的。

3.AC97 接口

AC’97 (Audio Codec 1997) 是以 Intel 为首的 5 个 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 方案相比, AC97 明显减少了整体管脚数。一般来说, AC’97 编解码器采用 TQFP48 封装PCM、IIS 和 AC97 各有其优点和应用范围,例如在 CD、MD、MP3 随身听多采用 IIS 接口,移动电话会采用 PCM 接口,具有音频功能的 PDA 则多使用和 PC 一样的 AC’97 编码格式。

android BSP音频驱动 手机音频驱动器_嵌入式_03

OSS 驱动的组成

OSS 标准中有两个最基本的音频设备: mixer ( 混音器) 和 dsp (数字信号处理器)。在声卡的硬件电路中,mixer 是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不相同。OSS 驱动中,/dev/mixer 设备文件是应用程序对 mixer 进行操作的软件接口。

混音器电路通常由两部分组成:输入混音器(input mixer)和输出混音器(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 之后,接下去就应该为其设置恰当的格式,然后才能从声卡读取或者写入数据.

mixer 接口

int register_sound_mixer(struct file_operations *fops, int dev);

上述函数用于注册一个混音器,第一个参数 fops 即是文件操作接口,第二个参数
dev 是设备编号,如果填入-1,则系统自动分配一个设备编号。mixer 是一个典型的字
符设备,因此编码的主要工作是实现 file_operations 中的 open()、ioctl()等函数。

mixer()接口的 ioctl()函数范例

static int mixdev_ioctl(struct inode *inode, struct file *file, 
unsigned int cmd,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);
}

dsp 接口

int register_sound_dsp(struct file_operations *fops, int dev);

上述函数与 register_sound_mixer()类似,它用于注册一个 dsp 设备,第一个参数
fops 即是文件操作接口,第二个参数 dev 是设备编号,如果填入−1,则系统自动分
配一个设备编号。dsp 也是一个典型的字符设备,因此编码的主要工作是实现
file_operations 中的 read()、write()、ioctl()等函数。

dsp 接口 file_operations 中的 read()和 write()函数非常重要,read()函数从音频控制器中获取录音数据到缓冲区并复制到用户空间,write()函数从用户空间复制音频数据到内核空间缓冲区并最终发送到音频控制器。

dsp 接口 file_operations 中的 ioctl()函数处理对采样率、 量化精度、 DMA 缓冲区块大小等参数设置 I/O 控制命令的处理。在数据从缓冲区复制到音频控制器的过程中,通常会使用 DMA,DMA 对声卡而言非常重要。例如,在放音时,驱动设置完 DMA 控制器的源数据地址 (内存中的 DMA缓冲区)、目的地址(音频控制器 FIFO)和 DMA 的数据长度,DMA 控制器会自动发送缓冲区的数据填充 FIFO,直到发送完相应的数据长度后才中断一次。

在 OSS 驱动中,建立存放音频数据的环形缓冲区(ring buffer)通常是值得推荐的方法。此外,在 OSS 驱动中,一般会将一个较大的 DMA 缓冲区分成若干个大小相同的块(这些块也被称为“段”,即 fragment),驱动程序使用 DMA 每次在声音缓冲区和声卡之间搬移一个 fragment。在用户空间,可以使用 ioctl()系统调用来调整块的大小和个数。

除了 read()、write()和 ioctl()外,dsp 接口的 poll()函数通常也需要被实现,以向用户反馈目前能否读写 DMA 缓冲区

OSS 驱动初始化注册 dsp 和 mixer 设备

static int xxx_init(void)
{
struct xxx_state *s = &xxx_state;
...
//注册 dsp 设备
if ((audio_dev_dsp = register_sound_dsp(&xxx_audio_fops, - 1)) <0)
goto err_dev1;
//设备 mixer 设备
if ((audio_dev_mixer = register_sound_mixer(&xxx_mixer_fops, - 1))< 0)
goto err_dev2;
 ...
 }
 void _ _exit xxx_exit(void)
  {
 //注销 dsp 和 mixer 设备接口
 unregister_sound_dsp(audio_dev_dsp);
 unregister_sound_mixer(audio_dev_mixer);
 ...
 }

android BSP音频驱动 手机音频驱动器_嵌入式_04

对 OSS 驱动声卡的编程使用 Linux 文件接口函数,如图 17.5 所示,dsp 接口的操作一般包括如下几个步骤。

(1)打开设备文件/dev/dsp。采用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写的方式打开,这还依赖于驱动程序的具体实现。Linux 允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之间进行切换。

(2)如果有需要,设置缓冲区大小。运行在 Linux 内核中的声卡驱动程序专门维护了一个缓冲区,其大小会影响到播放和录音时的效果,使用 ioctl()系统调用可以对它的 图 17.5 OSS dsp 接口尺寸进行恰当设置。调节驱动程序中缓冲区大小的操作不是必须的,用户空间操作流程如果没有特殊的要求,一般采用默认的缓冲区大小也就可以了。如果想设置缓冲区的大小,则通常应紧跟在设备文件打开之后,这是因为对声卡的其他操作有可能会导致驱动程序无法再修改其缓冲区的大小。

(3)设置声道(channel)数量。根据硬件设备和驱动程序的具体情况,可以设置为单声道或者立体声。

(4)设置采样格式和采样频率采样格式包括 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 控制命令可以设置采样频率。

(5)读写/dev/dsp 实现播放或录音。

代码清单 17.3 的程序实现了利用/dev/dsp 接口进行声音录制和播放的过程,它的功能是先录制几秒钟音频数据,将其存放在内存缓冲区中,然后再进行播放。

android BSP音频驱动 手机音频驱动器_linux_05

OSS dsp 接口应用编程范例

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/soundcard.h>
#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 < 0)
 {
 perror("open of /dev/dsp failed");
 exit(1);
 }
 /* 设置采样时的量化位数 */
 arg = SIZE;
 status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);
 if (status == - 1)
perror("SOUND_PCM_WRITE_BITS ioctl failed");
if (arg != SIZE)
perror("unable to set sample size");
/* 设置采样时的通道数目 */
arg = CHANNELS;
status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg);
if(status == - 1)
perror("SOUND_PCM_WRITE_CHANNELS ioctl failed");
if(arg != CHANNELS)
perror("unable to set number of channels");
/* 设置采样率 */
arg = RATE;
status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg);
if(status == - 1)
perror("SOUND_PCM_WRITE_WRITE ioctl failed");
/* 循环,直到按下[ControltC]*/
while (1)
 {
 printf("Say something:\n");
 status = read(fd, buf, sizeof(buf)); /* 录音 */
 if(status != sizeof(buf))
perror("read wrong number of bytes");
 printf("You said:\n");
 status = write(fd, buf, sizeof(buf)); /* 放音 */
 if(status != sizeof(buf))
perror("wrote wrong number of bytes");
 /* 在继续录音前等待放音结束 */
 status = ioctl(fd, SOUND_PCM_SYNC, 0);
 if(status == - 1)
perror("SOUND_PCM_SYNC ioctl failed");
}
 }