Linux 音频设备驱动架构及应用编程

               

       最早出现在Linux上的音频编程接口是OSS(Open Sound System),它由一套完整的内核驱动程序模块组成,可以为绝大多数声卡提供统一的编程接口。OSS出现的历史相对较长,这些内核模块中的一部分(OSS/Free)是与Linux内核源码共同免费发布的,另外一些则以二进制的形式由4Front Technologies公司提供。由于得到了商业公司的鼎力支持,OSS已经成为在Linux下进行音频编程的事实标准,支持OSS的应用程序能够在绝大多数声卡上工作良好。

虽然OSS已经非常成熟,但它毕竟是一个没有完全开放源代码的商业产品,ALSA(Advanced Linux Sound Architecture)恰好弥补了这一空白,它是在Linux下进行音频编程时另一个可供选择的声卡驱动程序。ALSA除了像OSS那样提供了一组内核驱动程序模块之外,还专门为简化应用程序的编写提供了相应的函数库,与OSS提供的基于ioctl的原始编程接口相比,ALSA函数库使用起来要更加方便一些。ALSA的主要特点有:
1)支持多种声卡设备
2)模块化的内核驱动程序
3)支持SMP和多线程
4)提供应用开发函数库
5)兼容OSS应用程序

       ALSA和OSS最大的不同之处在于ALSA是由志愿者维护的*项目,而OSS则是由公司提供的商业产品,因此在对硬件的适应程度上OSS要优于ALSA,它能够支持的声卡种类更多。ALSA虽然不及OSS运用得广泛,但却具有更加友好的编程接口,并且完全兼容于OSS,对应用程序员来讲无疑是一个更佳的选择。

两种音频编程接口驱动的组成如下:

1) Linux OSS 音频设备驱动的组成、mixer 接口、dsp 接口及用户空间编程方法。
2) Linux ALSA 音频设备驱动的组成、card 和组件管理、PCM 设备、control 接口、AC97 API及用户空间编程方法。

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

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

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 明显减少了整体管脚数。

       PCM、IIS 和AC97 各有其优点和应用范围,例如在CD、MD、MP3 随身听多采用IIS 接口,移动电话会采用PCM 接口,具有音频功能的PDA 则多使用和PC 一样的AC'97 编码格式。

3. Linux OSS 音频设备驱动及应用编程
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 之后,接下去就应该为其设置恰当的格式,然后才能从声卡读取或者写入数据。

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 控制命令,下面的代码清单给出了1 个ioctl()的范例。
mixer()接口ioctl()函数范例:

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.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 设备;在模块卸载的时候,调用的代码如下:
    OSS 驱动初始化注册dsp 和mixer设备:

 static int myoss_init(void) {  struct oss_state *s = &myoss_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);  ... }


3.4 OSS 用户空间编程
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_SETFMT IO 控制命令可以设置采样格式。对于大多数声卡来说,其支持的采样频率范围一般为5kHz 到44.1kHz 或者48kHz,但并不意味着该范围内的所有连续频率都会被硬件支持,在Linux 下进行音频编程时最常用到的几种采样频率是11025Hz、16000Hz、22050Hz、32000Hz 和44100Hz。使用SNDCTL_DSP_SPEED IO 控制命令可以设置采样频率。
⑤ 读写/dev/dsp 实现播放或录音
      下面代码实现了利用/dev/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");    /* 循环,直到按下Control-C */   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");   }}


 2、mixer 编程
       声卡上的混音器由多个混音通道组成,它们可以通过驱动程序提供的设备文件/dev/mixer 进行编程。对混音器的操作一般都通过ioctl()系统调用来完成,所有控制命令都以SOUND_MIXER 或者MIXER 开头,下表列出了常用的混音器控制命令。

命 令              作 用
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 来获得。
以下代码实现了利用/dev/mixer 接口对混音器进行编程的过程,该程序可对各种混音通道的增益进行调节。

 

 #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/ioctl.h> #include <fcntl.h> #include <linux/soundcard.h> /* 用来存储所有可用混音设备的名称 */ const char *sound_device_names[] = SOUND_DEVICE_NAMES; int fd; /* 混音设备所对应的文件描述符 */ int devmask, stereodevs; /* 混音器信息对应的bit 掩码 */ char *name; /* 显示命令的使用方法及所有可用的混音设备 */ void usage() {    int i;    fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>\n"            "%s <device> <gain%%>\n\n""Where <device> is one of:\n", name, name);        for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)        if ((1 << i) &devmask)           /* 只显示有效的混音设备 */           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, &devmask);    if (status == - 1)       perror("SOUND_MIXER_READ_DEVMASK ioctl failed");        status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);    if (status == - 1)        perror("SOUND_MIXER_READ_STEREODEVS ioctl failed");    /* 检查用户输入 */    if (argc != 3 && argc != 4)    usage();        /* 保存用户输入的混音器名称 */    dev = argv[1];        /* 确定即将用到的混音设备 */    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)    if (((1 << i) &devmask) && !strcmp(dev, sound_device_names[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) && !((1 << i) &stereodevs))   {      fprintf(stderr, "warning: %s is not a stereo device\n", dev);   }   /* 将两个声道的值合到同一变量中 */   level = (right << 8) + left;   /* 设置增益 */   status = ioctl(fd, MIXER_WRITE(device), &level);   if (status == - 1)   {      perror("MIXER_WRITE ioctl failed");      exit(1);   }   /* 获得从驱动返回的左右声道的增益 */   left = level &0xff;   right = (level &0xff00) >> 8;   /* 显示实际设置的增益 */   fprintf(stderr, "%s gain set to %d%% / %d%%\n", dev, left, right);   /* 关闭混音设备 */   close(fd);   return 0; }

 
      编译上述程序为可执行文件mixer,执行./mixer <device> <left-gain%> <right-gain%>或./mixer<device> <gain%>可设置增益,device 可以是vol、pcm、speaker、line、mic、cd、igain、line1、phin、video。

 

           
上一篇:IELTS SPEAKING(SAMPLING)


下一篇:char device catch multiple(int)ioctl-arguments