在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)。假如用户写入过快的话,它会被内核中的声卡驱动程序堵塞,直到硬件有才能处理新的数据停止。
无论是从声卡读取数据,或是向声卡写入数据,事实上都具有特定的格局(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 {
16 int fd; /* 声响设备的文件描述符 */
17 int arg; /* 用于ioctl调用的参数 */
18 int status; /* 体系调用的回来值 */
19 /* 翻开声响设备 */
20 fd = open(“/dev/dsp”, O_RDWR);
21 if (fd 《 0)
22 {
23 perror(“open of /dev/dsp failed”);
24 exit(1);
25 }
26 /* 设置采样时的量化位数 */
27 arg = SIZE;
28 status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);
29 if (status == – 1)
30 perror(“SOUND_PCM_WRITE_BITS ioctl failed”);
31 if (arg != SIZE)
32 perror(“unable to set sample size”);
33 /* 设置采样时的通道数目 */
34 arg = CHANNELS;
35 status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg);
36 if (status == – 1)
37 perror(“SOUND_PCM_WRITE_CHANNELS ioctl failed”);
38 if (arg != CHANNELS)
39 perror(“unable to set number of channels”);
40 /* 设置采样率 */
41 arg = RATE;
42 status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg);
43 if (status == – 1)
44 perror(“SOUND_PCM_WRITE_WRITE ioctl failed”);
45 /* 循环,直到按下Control-C */
46 while (1)
47 {
48 printf(“Say something:/n”);
49 status = read(fd, buf, sizeof(buf)); /* 录音 */
50 if (status != sizeof(buf))
51 perror(“read wrong number of bytes”);
52 printf(“You said:/n”);
53 status = write(fd, buf, sizeof(buf)); /* 放音 */
54 if (status != sizeof(buf))
55 perror(“wrote wrong number of bytes”);
56 /* 在持续录音前等候放音完毕 */
57 status = ioctl(fd, SOUND_PCM_SYNC, 0);
58 if (status == – 1)
59 perror(“SOUND_PCM_SYNC ioctl failed”);
60 }
61 }
2、mixer编程
声卡上的混音器由多个混音通道组成,它们可以经过驱动程序供给的设备文件/dev/mixer进行编程。对混音器的操作一般都经过ioctl()体系调用来完结,一切操控指令都以SOUND_MIXER或许MIXER最初,表17.1列出了常用的混音器操控指令。
表17.1 混音器常用指令
命 令 作 用
SOUND_MIXER_VOLUME 主音量调理
SOUND_MIXER_BASS 低声操控
SOUND_MIXER_TREBLE 高音操控
SOUND_MIXER_SYNTH FM组成器
SOUND_MIXER_PCM 主D/A转化器
SOUND_MIXER_SPEAKER PC喇叭
SOUND_MIXER_LINE 音频线输入
SOUND_MIXER_MIC 麦克风输入
SOUND_MIXER_CD CD输入
SOUND_MIXER_IMIX 放音音量
SOUND_MIXER_ALTPCM 从D/A 转化器
SOUND_MIXER_RECLEV 录音音量
SOUND_MIXER_IGAIN 输入增益
SOUND_MIXER_OGAIN 输出增益
SOUND_MIXER_LINE1 声卡的第1输入
SOUND_MIXER_LINE2 声卡的第2输入
SOUND_MIXER_LINE3 声卡的第3输入
对声卡的输入增益和输出增益进行调理是混音器的一个首要效果,现在大部分声卡选用的是8位或许16位的增益操控器,声卡驱动程序会将它们改换成百分比的办法,也便是说无论是输入增益仍是输出增益,其取值规模都是从0到100。
? SOUND_MIXER_READ宏
在进行混音器编程时,可以运用 SOUND_MIXER_READ宏来读取混音通道的增益巨细,例如如下代码可以取得麦克风的输入增益:
ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);
关于只要一个混音通道的单声道设备来说,回来的增益巨细保存在低位字节中。而关于支撑多个混音通道的双声道设备来说,回来的增益巨细实践上包括两个部分,别离代表左、右两个声道的值,其间低位字节保存左声道的音量,而高位字节则保存右声道的音量。下面的代码可以从回来值中顺次提取左右声道的增益巨细:
int left, right;
left = vol & 0xff;
right = (vol & 0xff00) 》》 8;
? SOUND_MIXER_WRITE宏
假如想设置混音通道的增益巨细,则可以经过SOUND_MIXER_WRITE宏来完结,例如下面的句子可以用来设置麦克风的输入增益:
vol = (right 《《 8) + left;
ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);
? 查询Mixer信息
声卡驱动程序供给了多个ioctl()体系调用来取得混音器的信息,它们一般回来一个整型的位掩码,其间每一位别离代表一个特定的混音通道,假如相应的位为1,则阐明与之对应的混音通道是可用的。
经过 SOUND_MIXER_READ_DEVMASK回来的位掩码查询出可以被声卡支撑的每一个混音通道,而经过 SOUND_MIXER_READ_RECMAS回来的位掩码则可以查询出可以被当作录音源的每一个通道。例如,如下代码可用来查看CD输入是否是一个有用的混音通道:
ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
if (devmask & SOUND_MIXER_CD)
printf(“The CD input is supported”);
如下代码可用来查看CD输入是否是一个有用的录音源:
ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask);
if (recmask & SOUND_MIXER_CD)
printf(“The CD input can be a recording source”);
大多数声卡供给了多个录音源,经过 SOUND_MIXER_READ_RECSRC可以查询出当时正在运用的录音源,同一时间可运用2个或2个以上的录音源,详细由声卡硬件自身决议。相应地,运用 SOUND_MIXER_WRITE_RECSRC可以设置声卡当时运用的录音源,如下代码可以将CD输入作为声卡的录音源运用:
devmask = SOUND_MIXER_CD;
ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &devmask);
此外,一切的混音通道都有单声道和双声道的差异,假如需求知道哪些混音通道供给了对立体声的支撑,可以经过SOUND_MIXER_READ_STEREODEVS来取得。
代码清单17.4的程序完结了运用/dev/mixer接口对混音器进行编程的进程,该程序可对各种混音通道的增益进行调理。
代码清单17.4 OSS mixer接口运用编程典范
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 /* 用来存储一切可用混音设备的称号 */
8 const char *sound_device_names[] = SOUND_DEVICE_NAMES;
9 int fd; /* 混音设备所对应的文件描述符 */
10 int devmask, stereodevs; /* 混音器信息对应的bit掩码 */
11 char *name;
12 /* 显现指令的运用办法及一切可用的混音设备 */
13 void usage()
14 {
15 int i;
16 fprintf(stderr, “usage: %s /n”
17 “%s /n/n”“Where is one of:/n”, name, name);
18 for (i = 0; i 《 SOUND_MIXER_NRDEVICES; i++)
19 if ((1 《《 i) &devmask)
20 /* 只显现有用的混音设备 */
21 fprintf(stderr, “%s ”, sound_device_names[i]);
22 fprintf(stderr, “/n”);
23 exit(1);
24 }
25
26 int main(int argc, char *argv[])
27 {
28 int left, right, level; /* 增益设置 */
29 int status; /* 体系调用的回来值 */
30 int device; /* 选用的混音设备 */
31 char *dev; /* 混音设备的称号 */
32 int i;
33 name = argv[0];
34 /* 以只读办法翻开混音设备 */
35 fd = open(“/dev/mixer”, O_RDONLY);
36 if (fd == – 1)
37 {
38 perror(“unable to open /dev/mixer”);
39 exit(1);
40 }
41
42 /* 取得所需求的信息 */
43 status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
44 if (status == – 1)
45 perror(“SOUND_MIXER_READ_DEVMASK ioctl failed”);
46 status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);
47 if (status == – 1)
48 perror(“SOUND_MIXER_READ_STEREODEVS ioctl failed”);
49 /* 查看用户输入 */
50 if (argc != 3 && argc != 4)
51 usage();
52 /* 保存用户输入的混音器称号 */
53 dev = argv[1];
54 /* 确认行将用到的混音设备 */
55 for (i = 0; i 《 SOUND_MIXER_NRDEVICES; i++)
56 if (((1 《《 i) &devmask) && !strcmp(dev, sound_device_names[i]))
57 break;
58 if (i == SOUND_MIXER_NRDEVICES)
59 {
60 /* 没有找到匹配项 */
61 fprintf(stderr, “%s is not a valid mixer device/n”, dev);
62 usage();
63 }
64 /* 查找到有用的混音设备 */
65 device = i;
66 /* 获取增益值 */
67 if (argc == 4)
68 {
69 /* 左、右声道均给定 */
70 left = atoi(argv[2]);
71 right = atoi(argv[3]);
72 }
73 else
74 {
75 /* 左、右声道设为持平 */
76 left = atoi(argv[2]);
77 right = atoi(argv[2]);
78 }
79
80 /* 对非立体声设备给出正告信息 */
81 if ((left != right) && !((1 《《 i) &stereodevs))
82 {
83 fprintf(stderr, “warning: %s is not a stereo device/n”, dev);
84 }
85
86 /* 将两个声道的值合到同一变量中 */
87 level = (right 《《 8) + left;
88
89 /* 设置增益 */
90 status = ioctl(fd, MIXER_WRITE(device), &level);
91 if (status == – 1)
92 {
93 perror(“MIXER_WRITE ioctl failed”);
94 exit(1);
95 }
96 /* 取得从驱动回来的左右声道的增益 */
97 left = level &0xff;
98 right = (level &0xff00) 》》 8;
99 /* 显现实践设置的增益 */
100 fprintf(stderr, “%s gain set to %d%% / %d%%/n”, dev, left, right);
101 /* 封闭混音设备 */
102 close(fd);
103 return 0;
104 }
编译上述程序为可履行文件mixer,履行。/mixer 或。/mixer 可设置增益,device可以是vol、pcm、speaker、line、mic、cd、igain、line1、 phin、video。
17.4 Linux ALSA音频设备驱动
17.4.1 ALSA的组成
尽管OSS现已十分老练,但它毕竟是一个没有彻底开放源代码的商业产品,而ALSA (Advanced Linux Sound Architecture)刚好弥补了这一空白,它契合GPL,是在Linux下进行音频编程时另一种可供挑选的声卡驱动体系结构,其官方网站为 http://www.alsa-project.org/。ALSA除了像OSS那样供给了一组内核驱动程序模块之外,还专门为简化运用程序的编写供给了相应的函数库,与OSS供给的根据ioctl的原始编程接口比较,ALSA函数库运用起来要愈加便利一些。ALSA的首要特点有:
? 支撑多种声卡设备
? 模块化的内核驱动程序
? 支撑SMP和多线程
? 供给运用开发函数库(alsa-lib)以简化运用程序开发
? 支撑OSS API,兼容OSS运用程序
ALSA 具有愈加友爱的编程接口,而且彻底兼容于OSS,对运用程序员来讲无疑是一个更佳的挑选。ALSA体系包括驱动包alsa-driver、开发包 alsa-libs、开发包插件alsa-libplugins、设置办理东西包alsa-utils、其他声响相关处理小程序包alsa-tools、特别音频固件支撑包alsa- firmware、OSS接口兼容模仿层东西alsa-oss共7个子项目,其间只要驱动包是必需的。
alsa- driver指内核驱动程序,包括硬件相关的代码和一些公共代码,十分巨大,代码总量达数十万行;alsa-libs指用户空间的函数库,供给给运用程序运用,运用程序应包括头文件asoundlib.h,并运用同享库libasound.so;alsa-utils包括一些根据ALSA的用于操控声卡的运用程序,如alsaconf(侦测体系中声卡并写一个合适的ALSA配置文件)、alsactl(操控ALSA声卡驱动的高档设置)、 alsamixer(根据ncurses的混音器程序)、amidi(用于读写ALSA RawMIDI)、amixer(ALSA声卡混音器的指令行操控)、aplay(根据指令行的声响文件播映)、arecord(根据指令行的声响文件录制)等。
现在ALSA内核供给给用户空间的接口有:
? 信息接口(Information Interface,/proc/asound)
? 操控接口(Control Interface,/dev/snd/controlCX)
? 混音器接口(Mixer Interface,/dev/snd/mixerCXDX)
? PCM接口(PCM Interface,/dev/snd/pcmCXDX)
? Raw迷笛接口(Raw MIDI Interface,/dev/snd/midiCXDX)
? 音序器接口(Sequencer Interface,/dev/snd/seq)
? 定时器接口(Timer Interface,/dev/snd/timer)
和OSS相似,上述接口也以文件的办法被供给,不同的是这些接口被供给给alsa-lib运用,而不是直接给运用程序运用的。运用程序最好运用alsa-lib,或许更高档的接口,比方jack供给的接口。
图17.6给出了ALSA声卡驱动与用户空间体系结构的简图,从中可以看出ALSA内核驱动与用户空间库及OSS之间的联系。
图17.6 ALSA体系结构
17.4.1 card和组件办理
关于每个声卡而言,有必要创立1个“card”实例。card是声卡的“总部”,它办理这个声卡上的一切设备(组件),如PCM、mixers、MIDI、synthesizer等。因而,card和组件是ALSA声卡驱动中的首要组成元素。
1、创立card
struct snd_card *snd_card_new(int idx, const char *xid,
struct module *module, int extra_size);
idx是card索引号、xid是标识字符串、module一般为THIS_MODULE,extra_size是要分配的额定数据的巨细,分配的extra_size巨细的内存将作为card-》private_data。
2、创立组件
int snd_device_new(struct snd_card *card, snd_device_type_t type,
void *device_data, struct snd_device_ops *ops);
当 card被创立后,设备(组件)可以被创立并关联于该card。第1个参数是snd_card_new()创立的card指针,第2个参数type 指的是device-level即设备类型,办法为SNDRV_DEV_XXX,包括SNDRV_DEV_CODEC、 SNDRV_DEV_CONTROL、SNDRV_DEV_PCM、SNDRV_DEV_RAWMIDI等,用户自界说设备的device-level是 SNDRV_DEV_LOWLEVEL,ops参数是1个函数集(界说为snd_device_ops结构体)的指针,device_data是设备数据指针,留意函数snd_device_new()自身不会分配设备数据的内存,因而应事前分配。
3、组件开释
每个ALSA预界说的组件在结构时需调用snd_device_new(),而每个组件的析构办法则在函数集中被包括。关于PCM、AC97此类预界说组件,咱们不需关怀它们的析构,而关于自界说的组件,则需求填充snd_device_ops中的析构函数指针dev_free,这样,当 snd_card_free()被调用时,组件将主动被开释。
4、芯片特定的数据(Chip-Specific Data)
芯片特定的数据一般以struct xxxchip结构体办法安排,这个结构体中包括芯片相关的I/O端口地址、资源指针、中止号等,其含义等同于字符设备驱动中的 file-》private_data。界说芯片特定的数据首要有2种办法,一种办法是将sizeof(struct xxxchip)传入snd_card_new()的extra_size参数,它将主动成员snd_card的private_data成员,如代码清单17.5;