您的位置:首页 > 运维架构 > Linux

Linux音频编程指南

2010-05-21 00:34 344 查看
<!--
@page { margin: 2cm }
P { margin-bottom: 0.21cm }
H1 { margin-bottom: 0.21cm }
H1.western { font-family: "DejaVu Serif", serif }
H1.cjk { font-family: "AR PL UKai CN"; font-size: 24pt; font-style: normal; font-weight: bold }
H1.ctl { font-family: "AR PL UKai CN"; font-size: 24pt; font-weight: bold }
A:link { so-language: zxx }
-->

Linux
音频编程指南

虽然目前Linux
的优势主要体现在网络服务方面,但事实上同样也有着非常丰富的媒体功能,本文就是以多媒体应用中最基本的声音为对象,介绍如何在
Linux
平台下开发实际的音频应用程序,同时还给出了一些常用的音频编程框架。

  一、数字音频

  音频信号是一种连续变化的模拟信号,但计算机只能处理和记录二进制的数字信号,由自然音源得到的音频信号必须经过一定的变换,成为数字音频信号
之后,才能送到计算机中作进一步的处理。

  数字音频系统通过将声波的波型转换成一系列二进制数据,来实现对原始声音的重现,实现这一步骤的设备常被称为模/
数转换器(A/D
)。A/D

换器以每秒钟上万次的速率对声波进行采样,每个采样点都记录下了原始模拟声波在某一时刻的状态,通常称之为样本(sample
),而每一秒钟所采样的数目
则称为采样频率,通过将一串连续的样本连接起来,就可以在计算机中描述一段声音了。对于采样过程中的每一个样本来说,数字音频系统会分配一定存

位来记录声波的振幅,一般称之为采样分辩率或者采样精度,采样精度越高,声音还原时就会越细腻。

  数字音频涉及到的概念非常多,对于在Linux
下进行音频编程的程序员来说,最重要的是理解声音数字化的两个关键步骤:采样和量化。采样就是每
隔一定时间就读一次声音信号的幅度,而量化则是将采样得到的声音信号幅度转换为数字值,从本质上讲,采样是时间上的数字化,而量化则是幅度上的数字化。下
面介绍几个在进行音频编程时经常需要用到的技术指标:

  1.
采样频率

  采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数。采样频率的选择应该遵循奈奎斯特(Harry
Nyquist
)采样理论:如果对某一模拟信号进行采样,则采样后可还原的最高信号频率只有采样频率的一半,或者说只要采样频率高于输入信号最高频率的两
倍,就能从采样信号系列重构原始信号。正常人听觉的频率范围大约在20Hz~20kHz
之间,根据奈奎斯特采样理论,为了保证声音不失真,采样频率应该在
40kHz
左右。常用的音频采样频率有8kHz
、11.025kHz
、22.05kHz
、16kHz
、37.8kHz
、44.1kHz
、48kHz
等,如
果采用更高的采样频率,还可以达到DVD
的音质。

  2.
量化位数

  量化位数是对模拟音频信号的幅度进行数字化,它决定了模拟信号数字化以后的动态范围,常用的有8
位、12
位和16
位。量化位越高,信号的动态范
围越大,数字化后的音频信号就越可能接近原始信号,但所需要的存贮空间也越大。

  3.
声道数

  声道数是反映音频数字化质量的另一个重要因素,它有单声道和双声道之分。双声道又称为立体声,在硬件中有两条线路,音质和音色都要优于单声道,
但数字化后占据的存储
空间的大小要比单声道多一倍。

  二、声卡驱动

  出于对安全
性方面的考
虑,Linux
下的应用程序无法直接对声卡这类硬件设备进行操作,而是必须通过内核提供的驱动程序才能完成。在Linux
上进行音频编程的本质就是要借助
于驱动程序,来完成对声卡的各种操作。

  对硬件的控制涉及到寄存器中各个比特位的操作,通常这是与设备直接相关并且对时序的要求非常严格,如果这些工作都交由应用程序员来负责,那么对
声卡的编程将变得异常复杂而困难起来,驱动程序的作用正是要屏蔽硬件的这些底层细节,从而简化应用程序的编写。目前Linux
下常用的声卡驱动程序主要有
两种:OSS
和ALSA


  最早出现在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
的主要特点有:

  *
支持多种声卡设备

  *
模块化的内核驱动程序

  *
支持SMP
和多线程

  *
提供应用开发函数库

  *
兼容OSS
应用程序

  ALSA
和OSS
最大的不同之处在于ALSA
是由志愿者维护的自由项目,而OSS
则是由公司提供的商业产品,因此在对硬件的适应程度上OSS

优于
ALSA
,它能够支持的声卡种类更多。ALSA
虽然不及OSS
运用得广泛,但却具有更加友好的编程接口,并且完全兼容于OSS
,对应用程序员来讲无疑是一
个更佳的选择。

  三、编程接口

  如何对各种音频设备进行操作是在Linux
上进行音频编程的关键,通过内核提供的一组系统调用,应用程序能够访问声卡驱动程序提供的各种音频设
备接口,这是在Linux
下进行音频编程最简单也是最直接的方法。

  3.1
访问音频设备

  无论是OSS
还是ALSA
,都是以内核驱动程序的形式运行在Linux
内核空间中的,应用程序要想访问声卡这一硬件设备,必须借助于Linux

内核所提供的系统调用(system
call
)。从程序员的角度来说,对声卡的操作在很大程度上等同于对磁盘文件的操作:首先使用open
系统调用建立起与硬件间的联系,此时返回的文件描述
符将作为随后操作的标识;接着使用read
系统调用从设备接收数据,或者使用write
系统调用向设备写入数据,而其它所有不符合读/
写这一基本模式的操
作都可以由ioctl
系统调用来完成;最后,使用close
系统调用告诉Linux
内核不会再对该设备做进一步的处理。

  * open
系统调用

  系统调用open
可以获得对声卡的访问权,同时还能为随后的系统调用做好准备,其函数原型如下所示:

  int open(const char
*pathname, int flags, int mode);

  参数pathname
是将要被打开的设备文件的名称,对于声卡来讲一般是
/dev/dsp
。参数flags
用来指明应该以什么方式打开设备文件,它可以是O_RDONLY
、O_WRONLY
或者O_RDWR
,分别表示以只读、
只写或者读写的方式打开设备文件;参数mode
通常是可选的,它只有在指定的设备文件不存在时才会用到,指明新创建的文件应该具有怎样的权限。

  如果open
系统调用能够成功完成,它将返回一个正整数作为文件标识符,在随后的系统调用中需要用到该标识符。如果open
系统调用失败,它将
返回-1
,同时还会设置全局变量errno
,指明是什么原因导致了错误的发生。

  * read
系统调用

  系统调用read
用来从声卡读取数据,其函数原型如下所示:

  int read(int fd, char *buf,
size_t count);

  参数fd
是设备文件的标识符,它是通过之前的open
系统调用获得的;参数
buf
是指向缓冲区的字符指针,它用来保存从声卡获得的数据;参数count
则用来限定从声卡获得的最大字节数。如果read
系统调用成功完成,它将返回
从声卡实际读取的字节数,通常情况会比count
的值要小一些;如果read
系统调用失败,它将返回-1
,同时还会设置全局变量errno
,来指明是什么
原因导致了错误的发生。

  * write
系统调用

  系统调用write
用来向声卡写入数据,其函数原型如下所示:

  size_t write(int fd, const
char *buf, size_t count);

  系统调用write
和系统调用read
在很大程度是类似的,差别只在于
write
是向声卡写入数据,而read
则是从声卡读入数据。参数fd
同样是设备文件的标识符,它也是通过之前的open
系统调用获得的;参数buf
是指
向缓冲区的字符指针,它保存着即将向声卡写入的数据;参数count
则用来限定向声卡写入的最大字节数。

  如果write
系统调用成功完成,它将返回向声卡实际写入的字节数;如果read
系统调用失败,它将返回-1
,同时还会设置全局变量
errno
,来指明是什么原因导致了错误的发生。无论是
read
还是write
,一旦调用之后Linux
内核就会阻塞当前应用程序,直到数据成功地从声卡读出或者写入为止。

  * ioctl
系统调用

  系统调用ioctl
可以对声卡进行控制,凡是对设备文件的操作不符合读/
写基本模式的,都是通过ioctl
来完成的,它可以影响设备的行为,或
者返回设备的状态,其函数原型如下所示:

  int ioctl(int fd, int
request, ...);

  参数fd
是设备文件的标识符,它是在设备打开时获得的;如果设备比较复杂,那么对它的控制请求相应地也会有很多种,参数request
的目的就
是用来区分不同的控制请求;通常说来,在对设备进行控制时还需要有其它参数,这要根据不同的控制请求才能确定,并且可能是与硬件设备直接相关的。

  * close
系统调用

  当应用程序使用完声卡之后,需要用close
系统调用将其关闭,以便及时释放占用的硬件资源,其函数原型如下所示:

  int close(int fd);

  参数fd
是设备文件的标识符,它是在设备打开时获得的。一旦应用程序调用了close
系统调用,Linux
内核就会释放与之相关的各种资源,因
此建议在不需要的时候尽量及时关闭已经打开的设备。

3.2
音频设备文件

  对于Linux
应用程序员来讲,音频编程接口实际上就是一组音频设备文件,通过它们可以从声卡读取数据,或者向声卡写入数据,并且能够对声卡进
行控制,设置采样频率和声道数目等等。

  * /dev/sndstat

  设备文件/dev/sndstat
是声卡驱动程序提供的最简单的接口,通常它是一个只读文件,作用也仅仅只限于汇报声卡的当前状态。一般说来,
/dev/sndstat
是提供给最终用户来检测声卡的,不宜用于程序当中,因为所有的信息都可以通过
ioctl
系统调用来获得。
Linux
提供的cat
命令可以很方便地从/dev/sndstat
获得声卡的当前状态:
[xiaowp@linux
gam
sound]$ cat /dev/sndstat

  * /dev/dsp

  声卡驱动程序提供的/dev/dsp
是用于数字采样(sampling
)和数字录音(recording
)的设备文件,它对于Linux
下的音
频编程来讲非常重要:向该设备写数据即意味着激活声卡上的D/A
转换器进行放音,而向该设备读数据则意味着激活声卡上的A/D
转换器进行录音。目前许多声
卡都提供有多个数字采样设备,它们在Linux
下可以通过/dev/dsp1
等设备文件进行访问。

  DSP
是数字信号处理器(Digital
Signal
Processor
)的简称,它是用来进行数字信号处理的特殊芯片,声卡使用它来实现模拟信号和数字信号的转换。声卡中的DSP
设备实际上包含两个组成部
分:在以只读方式打开时,能够使用A/D
转换器进行声音的输入;而在以只写方式打开时,则能够使用D/A
转换器进行声音的输出。严格说来,Linux
下的
应用程序要么以只读方式打开/dev/dsp
输入声音,要么以只写方式打开/dev/dsp
输出声音,但事实上某些声卡驱动程序仍允许以读写的方式打开
/dev/dsp
,以便同时进行声音的输入和输出,这对于某些应用场合(如IP
电话)来讲是非常关键的。

  在从DSP
设备读取数据时,从声卡输入的模拟信号经过A/D
转换器变成数字采样后的样本(sample
),保存在声卡驱动程序的内核缓冲区中,
当应用程序通过read
系统调用从声卡读取数据时,保存在内核缓冲区中的数字采样结果将被复制到应用程序所指定的用户缓冲区中。需要指出的是,声卡采样频
率是由内核中的驱动程序所决定的,而不取决于应用程序从声卡读取数据的速度。如果应用程序读取数据的速度过慢,以致低于声卡的采样频率,那么多余的数据将
会被丢弃;如果读取数据的速度过快,以致高于声卡的采样频率,那么声卡驱动程序将会阻塞那些请求数据的应用程序,直到新的数据到来为止。

  在向DSP
设备写入数据时,数字信号会经过D/A
转换器变成模拟信号,然后产生出声音。应用程序写入数据的速度同样应该与声卡的采样频率相匹
配,否则过慢的话会产生声音暂停或者停顿的现象,过快的话又会被内核中的声卡驱动程序阻塞,直到硬件有能力处理新的数据为止。与其它设备有所不同,声卡通
常不会支持非阻塞(non-blocking
)的I/O
操作。

  无论是从声卡读取数据,或是向声卡写入数据,事实上都具有特定的格式(format
),默认为8
位无符号数据、单声道、8KHz
采样率,如果默
认值无法达到要求,可以通过ioctl
系统调用来改变它们。通常说来,在应用程序中打开设备文件/dev/dsp
之后,接下去就应该为其设置恰当的格式,
然后才能从声卡读取或者写入数据。

  * /dev/audio

  /dev/audio
类似于/dev/dsp
,它兼容于Sun
工作站上的音频设备,使用的是mu-law
编码方式。如果声卡驱动程序提供了对
/dev/audio
的支持,那么在Linux
上就可以通过cat
命令,来播放在Sun
工作站上用
mu-law
进行编码的音频文件:

  [xiaowp@linux
gam
sound]$ cat audio.au > /dev/audio

  由于设备文件/dev/audio
主要出于对兼容性的考虑,所以在新开发的应用程序中最好不要尝试用它,而应该以/dev/dsp
进行替代。对
于应用程序来说,同一时刻只能使用/dev/audio
或者/dev/dsp
其中之一,因为它们是相同硬件的不同软件接口。

  * /dev/mixer

  在声卡的硬件电路中,混音器(mixer
)是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的
作用可能各不相同。运行在Linux
内核中的声卡驱动程序一般都会提供/dev/mixer
这一设备文件,它是应用程序对混音器进行操作的软件接口。混音
器电路通常由两个部分组成:输入混音器(input
mixer
)和输出混音器(output
mixer
)。

  输入混音器负责从多个不同的信号源接收模拟信号,这些信号源有时也被称为混音通道或者混音设备。模拟信号通过增益控制器和由软件控制的音量调节
器后,在不同的混音通道中进行级别(level
)调制,然后被送到输入混音器中进行声音的合成。混音器上的电子开关可以控制哪些通道中有信号与混音器相
连,有些声卡只允许连接一个混音通道作为录音的音源,而有些声卡则允许对混音通道做任意的连接。经过输入混音器处理后的信号仍然为模拟信号,它们将被送到
A/D
转换器进行数字化处理。

  输出混音器的工作原理与输入混音器类似,同样也有多个信号源与混音器相连,并且事先都经过了增益调节。当输出混音器对所有的模拟信号进行了混合
之后,通常还会有一个总控增益调节器来控制输出声音的大小,此外还有一些音调控制器来调节输出声音的音调。经过输出混音器处理后的信号也是模拟信号,它们
最终会被送给喇叭或者其它的模拟输出设备。对混音器的编程包括如何设置增益控制器的级别,以及怎样在不同的音源间进行切换,这些操作通常来讲是不连续的,
而且不会像录音或者放音那样需要占用大量的计算机资源。由于混音器的操作不符合典型的读/
写操作模式,因此除了open
和close
两个系统调用之外,大
部分的操作都是通过ioctl
系统调用来完成的。与/dev/dsp
不同,/dev/mixer
允许多个应用程序同时访问,并且混音器的设置值会一直保持
到对应的设备文件被关闭为止。

  为了简化应用程序的设计,Linux
上的声卡驱动程序大多都支持将混音器的ioctl
操作直接应用到声音设备上,也就是说如果已经打开了
/dev/dsp
,那么就不用再打开/dev/mixer
来对混音器进行操作,而是可以直接用打开/dev/dsp
时得到的文件标识符来设置混音器。

  * /dev/sequencer

  目前大多数声卡驱动程序还会提供/dev/sequencer
这一设备文件,用来对声卡内建的波表合成器进行操作,或者对MIDI
总线上的乐器
进行控制,一般只用于计算机音乐软件中。

  四、应用框架

  在Linux
下进行音频编程时,重点在于如何正确地操作声卡驱动程序所提供的各种设备文件,由于涉及到的概念和因素比较多,所以遵循一个通用的
框架无疑将有助于简化应用程序的设计。

  4.1 DSP
编程

  对声卡进行编程时首先要做的是打开与之对应的硬件设备,这是借助于open
系统调用来完成的,并且一般情况下使用的是/dev/dsp
文件。采
用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写
的方式打开,并且还要依赖于驱动程序的具体实现。Linux
允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之
间进行切换,建议在进行音频编程时只要有可能就尽量使用只读或者只写的方式打开设备文件,因为这样不仅能够充分利用声卡的硬件资源,而且还有利于驱动程序
的优化。下面的代码示范了如何以只写方式打开声卡进行放音(playback
)操作:

  int handle =
open("/dev/dsp", O_WRONLY);

  if (handle == -1) {

  perror("open
/dev/dsp");

  return -1;

  }

  运行在Linux
内核中的声卡驱动程序专门维护了一个缓冲区,其大小会影响到放音和录音时的效果,使用ioctl
系统调用可以对它的尺寸进行恰
当的设置。调节驱动程序中缓冲区大小的操作不是必须的,如果没有特殊的要求,一般采用默认的缓冲区大小也就可以了。但需要注意的是,缓冲区大小的设置通常
应紧跟在设备文件打开之后,这是因为对声卡的其它操作有可能会导致驱动程序无法再修改其缓冲区的大小。下面的代码示范了怎样设置声卡驱动程序中的内核缓冲
区的大小:

  int setting = 0xnnnnssss;

  int result = ioctl(handle,
SNDCTL_DSP_SETFRAGMENT, &setting);

  if (result == -1) {

  perror("ioctl buffer
size");

  return -1;

  }

  //
检查设置值的正确性

  在设置缓冲区大小时,参数setting
实际上由两部分组成,其低16
位标明缓冲区的尺寸,相应的计算公式为buffer_size
=
2^ssss
,即若参数setting
低16
位的值为16
,那么相应的缓冲区的大小会被设置为65536
字节。参数setting
的高16
位则用来标明分
片(fragment
)的最大序号,它的取值范围从2
一直到0x7FFF
,其中0x7FFF
表示没有任何限制。

  接下来要做的是设置声卡工作时的声道(channel
)数目,根据硬件设备和驱动程序的具体情况,可以将其设置为0
(单声道,mono
)或者
1
(立体声,stereo
)。下面的代码示范了应该怎样设置声道数目:

  int channels = 0; // 0=mono
1=stereo

  int result = ioctl(handle,
SNDCTL_DSP_STEREO, &channels);

  if ( result == -1 ) {

  perror("ioctl channel
number");

  return -1;

  }

  if (channels != 0) {

  //
只支持立体声

  }

  采样格式和采样频率是在进行音频编程时需要考虑的另一个问题,声卡支持的所有采样格式可以在头文件soundcard.h
中找到,而通过
ioctl
系统调用则可以很方便地更改当前所使用的采样格式。下面的代码示范了如何设置声卡的采样格式:

  int format = AFMT_U8;

  int result = ioctl(handle,
SNDCTL_DSP_SETFMT, &format);

  if ( result == -1 ) {

  perror("ioctl sample
format");

  return -1;

  }

  //
检查设置值的正确性

  声卡采样频率的设置也非常容易,只需在调用ioctl
时将第二个参数的值设置为SNDCTL_DSP_SPEED
,同时在第三个参数中指定采样
频率的数值就行了。对于大多数声卡来说,其支持的采样频率范围一般为5kHz
到44.1kHz
或者48kHz
,但并不意味着该范围内的所有频率都会被硬件
支持,在Linux
下进行音频编程时最常用到的几种采样频率是11025Hz
、16000Hz
、22050Hz
、32000Hz
和44100Hz
。下面的
代码示范了如何设置声卡的采样频率:

  int rate = 22050;

  int result = ioctl(handle,
SNDCTL_DSP_SPEED, &rate);

  if ( result == -1 ) {

  perror("ioctl sample
format");

  return -1;

  }

  //
检查设置值的正确性

  4.2 Mixer
编程

  声卡上的混音器由多个混音通道组成,它们可以通过驱动程序提供的设备文件/dev/mixer
进行编程。对混音器的操作是通过ioctl
系统调
用来完成的,并且所有控制命令都由SOUND_MIXER
或者MIXER
开头,表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
输入

表1
混音器命令

  对声卡的输入增益和输出增益进行调节是混音器的一个主要作用,目前大部分声卡采用的是8
位或者16
位的增益控制器,但作为程序员来讲并不需要关
心这些,因为声卡驱动程序会负责将它们变换成百分比的形式,也就是说无论是输入增益还是输出增益,其取值范围都是从0
到100
。在进行混音器编程时,可以
使用 SOUND_MIXER_READ
宏来读取混音通道的增益大小,例如在获取麦克风的输入增益时,可以使用如下的代码:

  int vol;

  ioctl(fd,
SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);

  printf("Mic gain is at
%d %%/n", vol);

  对于只有一个混音通道的单声道设备来说,返回的增益大小保存在低位字节中。而对于支持多个混音通道的双声道设备来说,返回的增益大小实际上包括
两个部分,分别代表左、右两个声道的值,其中低位字节保存左声道的音量,而高位字节则保存右声道的音量。下面的代码可以从返回值中依次提取左右声道的增益
大小:

  int left, right;

  left = vol & 0xff;

  right = (vol & 0xff00)
>> 8;

  printf("Left gain is
%d %%, Right gain is %d %%/n", left, right);

  类似地,如果想设置混音通道的增益大小,则可以通过SOUND_MIXER_WRITE
宏来实现,此时遵循的原则与获取增益值时的原则基本相
同,例如下面的语句可以用来设置麦克风的输入增益:

  vol = (right << +
left;

  ioctl(fd,
SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);

  在编写实用的音频程序时,混音器是在涉及到兼容性时需要重点考虑的一个对象,这是因为不同的声卡所提供的混音器资源是有所区别的。声卡驱动程序
提供了多个ioctl
系统调用来获得混音器的信息,它们通常返回一个整型的位掩码(bitmask
),其中每一位分别代表一个特定的混音通道,如果相应的
位为
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");

  如果进一步还想知道其是否是一个有效的录音源,则可以使用如下语句:

  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
可以查询出当前正在使用的录音源,同一时刻能够使用几个录
音源是由声卡硬件决定的。类似地,使用SOUND_MIXER_WRITE_RECSRC
可以设置声卡当前使用的录音源,例如下面的代码可以将CD
输入作
为声卡的录音源使用:

  devmask = SOUND_MIXER_CD;

  ioctl(fd,
SOUND_MIXER_WRITE_DEVMASK, &devmask);

  此外,所有的混音通道都有单声道和双声道的区别,如果需要知道哪些混音通道提供了对立体声的支持,可以通过
SOUND_MIXER_READ_STEREODEVS
来获得。

  4.3
音频录放框架

  下面给出一个利用声卡上的DSP
设备进行声音录制和回放的基本框架,它的功能是先录制几秒种音频数据,将其存放在内存缓冲区中,然后再进行回
放,其所有的功能都是通过读写/dev/dsp
设备文件来完成的:

  /*

  * sound.c

  */

  #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");

  }

  }

4.4
混音器框架

  下面再给出一个对混音器进行编程的基本框架,利用它可以对各种混音通道的增益进行调节,其所有的功能都是通过读写/dev/mixer
设备文件
来完成的:

  /*

  * mixer.c

  */

  #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; /*

混音器信息对应的位图掩码 */

  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 << +
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;

  }

  编译好上面的程序之后,先不带任何参数执行一遍,此时会列出声卡上所有可用的混音通道:

  [xiaowp@linuxgam sound]$
./mixer

  usage: ./mixer <device>
<left-gain%> <right-gain%>

  ./mixer <device>
<gain%>

  Where <device> is one
of:

  vol pcm speaker line mic cd
igain line1 phin video

  之后就可以很方便地设置各个混音通道的增益大小了,例如下面的命令就能够将CD
输入的左、右声道的增益分别设置为80%
和90%


  [xiaowp@linuxgam sound]$
./mixer cd 80 90

  cd gain set to 80% / 90%

  五、小结

  随着Linux
平台下多媒体应用的逐渐深入,需要用到数字音频的场合必将越来越广泛。虽然数字音频牵涉到的概念非常多,但在Linux
下进行最
基本的音频编程却并不十分复杂,关键是掌握如何与OSS
或者ALSA
这类声卡驱动程序进行交互,以及如何充分利用它们提供的各种功能,熟悉一些最基本的音
频编程框架和模式对初学者来讲大有裨益。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: