OSS音频编程概述(DSP部分)
2011-03-30 10:25 Jason_Wang NUAA 阅读(3227) 评论(0) 编辑 收藏 举报一、 音频概念
音频信号是一种连续变化的模拟信号,但计算机只能处理和记录二进制的数字信号,由自然音源得到的音频信号必须经过一定的变换,成为数字音频信号之后,才能送到计算机中作进一步的处理。
对于OSS编程来说,需要掌握声音数字化的两个关键步骤:采样和量化。采样就是每隔一定时间就读一次声音信号的幅度,而量化则是将采样得到的声音信号幅度转换为数字值,从本质上讲,采样是时间上的数字化,而量化则是幅度上的数字化。
下面是音频编程时经常要使用到的技术指标:
1. 采样频率
采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数。正常人听觉的频率范围大约在20Hz~20kHz之间,根据奈奎斯特采样理论,为了保证声音不失真,采样频率应该在40kHz左右。常用的音频采样频率有8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等。
2. 量化位数
量化位数是对模拟音频信号的幅度进行数字化,它决定了模拟信号数字化以后的动态范围,常用的有8位、12位和16位。量化位越高,信号的动态范围越大,数字化后的音频信号就越可能接近原始信号,但所需要的存贮空间也越大。
3. 声道数
声道数是反映音频数字化质量的另一个重要因素,它有单声道和双声道之分。双声道又称为立体声,在硬件中有两条线路,音质和音色都要优于单声道,但数字化后占据的存储空间的大小要比单声道多一倍。
二、 声卡驱动(OSS)
目前Linux下常用的声卡驱动程序主要有两种:OSS和ALSA。
OSS(Open Sound System)部分代码开源,其他部分由4Front Technologies公司以二进制的形式提供。
ALSA(Advanced Linux Sound Architecture)是完全开放源代码的产品。因为OSS由商业公司提供,而ALSA由志愿者维护,所以OSS支持的声卡类型更多。
1. OSS的安装(Ubuntu环境)
当前OSS的版本是V4.2-build 2004
下载地址: http://www.4front-tech.com/download.cgi
根据Linux内核版本选择安装包。
P.S.:查看内核版本的命令为uname –r
ubuntu选择DEB包。安装命令dpkg -l oss-Linux_v4.2-2004_i686.deb。安装完成后,会在/dev下找到/dev/dsp,/dev/mixer等设备文件。也可以用osstest命令来测试oss是否正常工作。
2. 设备文件说明
l /dev/sndstat
设备文件/dev/sndstat的作用是用于汇报声卡当前的状态。Linux下的cat命令可以方便的获取声卡的当前状态。
l /dev/dsp
声卡驱动程序提供的/dev/dsp是用于数字采样(sampling)和数字录音(recording)的设备文件,它对于Linux下的音频编程来讲非常重要:向该设备写数据即意味着激活声卡上的D/A转换器进行放音,而向该设备读数据则意味着激活声卡上的A/D转换器进行录音。目前许多声卡都提供有多个数字采样设备,它们在Linux下可以通过/dev/dsp1等设备文件进行访问。
无论是从声卡读取数据,或是向声卡写入数据,事实上都具有特定的格式(format),默认为8位无符号数据、单声道、8KHz采样率,如果默认值无法达到要求,可以通过ioctl系统调用来改变它们。通常说来,在应用程序中打开设备文件/dev/dsp之后,接下去就应该为其设置恰当的格式,然后才能从声卡读取或者写入数据。
l /dev/mixer
在声卡的硬件电路中,混音器(mixer)是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不相同。运行在Linux内核中的声卡驱动程序一般都会提供/dev/mixer这一设备文件,它是应用程序对混音器进行操作的软件接口。
由于混音器的操作不符合典型的读/写操作模式,因此除了open和close两个系统调用之外,大部分的操作都是通过ioctl系统调用来完成的。与/dev/dsp不同,/dev/mixer允许多个应用程序同时访问,并且混音器的设置值会一直保持到对应的设备文件被关闭为止。
为了简化应用程序的设计,Linux上的声卡驱动程序大多都支持将混音器的ioctl操作直接应用到声音设备上,也就是说如果已经打开了/dev/dsp,那么就不用再打开/dev/mixer来对混音器进行操作,而是可以直接用打开/dev/dsp时得到的文件标识符来设置混音器。
l /dev/sequencer
目前大多数声卡驱动程序还会提供/dev/sequencer这一设备文件,用来对声卡内建的波表合成器进行操作,或者对MIDI总线上的乐器进行控制,一般只用于计算机音乐软件中。
三、 编程接口与框架(DSP)
无论是OSS还是ALSA,都是以内核驱动程序的形式运行在Linux内核空间中的,应用程序要想访问声卡这一硬件设备,必须借助于Linux内核所提供的系统调用(system call)。从程序员的角度来说,对声卡的操作在很大程度上等同于对磁盘文件的操作:首先使用open系统调用建立起与硬件间的联系,此时返回的文件描述符将作为随后操作的标识;接着使用read系统调用从设备接收数据,或者使用write系统调用向设备写入数据,而其它所有不符合读/写这一基本模式的操作都可以由ioctl系统调用来完成;最后,使用close系统调用告诉Linux内核不会再对该设备做进一步的处理。
1. 编程接口
l open系统调用
系统调用open可以获得对声卡的访问权,同时还能为随后的系统调用做好准备。
函数原型:
1: #include <fcntl.h>
2: #include <sys/types.h>
3: #include <sys/stat.h>
4: //严格说一般使用open系统调用不需要包含sys/types.h,sys/stat.h。
5: int open(const char *path, int oflags);
6: int open(const char *path, int oflags, mode_t mode);
参数path是将要被打开的设备文件的名称,对于声卡来讲一般是/dev/dsp。参数oflags用来指明应该以什么方式打开设备文件,格式是
(O_RDONLY | O_WRONLY | ORDWR)[ | O_APPEND | O_TRUNC | O_CREAT| O_EXCL]
分别表示以只读、只写或者读写的方式打开设备文件,后面参数可选;使用O_CREAT标志时,需要用3个参数的open调用,这时用mode设置文件的权限。
如果open系统调用能够成功完成,它将返回一个正整数作为文件标识符,在随后的系统调用中需要用到该标识符。如果open系统调用失败,它将返回-1,同时还会设置全局变量errno,指明是什么原因导致了错误的发生。
l read系统调用
系统调用read用来从声卡读取数据
函数原型:
1: #include <unistd.h>
2: size_t read(int fildes, void *buf, size_t nbytes);
参数fildes是设备文件的标识符,它是通过之前的open系统调用获得的;参数buf是指向缓冲区的字符指针,它用来保存从声卡获得的数据;参数nbytes则用来限定从声卡获得的最大字节数。如果read系统调用成功完成,它将返回从声卡实际读取的字节数,通常情况会比nbytes的值要小一些;如果read系统调用失败,它将返回-1,同时还会设置全局变量errno,来指明是什么原因导致了错误的发生。
l write系统调用
系统调用write用来向声卡写入数据
函数原型:
1: #include <unistd.h>
2: size_t write(int fildes, const void *buf, size_t nbytes);
系统调用write和系统调用read在很大程度是类似的,差别只在于write是向声卡写入数据,而read则是从声卡读入数据。参数fildes同样是设备文件的标识符,它也是通过之前的open系统调用获得的;参数buf是指向缓冲区的字符指针,它保存着即将向声卡写入的数据;参数nbytes则用来限定向声卡写入的最大字节数。
如果write系统调用成功完成,它将返回向声卡实际写入的字节数;如果read系统调用失败,它将返回-1,同时还会设置全局变量errno,来指明是什么原因导致了错误的发生。无论是read还是write,一旦调用之后Linux内核就会阻塞当前应用程序,直到数据成功地从声卡读出或者写入为止。
l ioctl系统调用
系统调用ioctl可以对声卡进行控制,凡是对设备文件的操作不符合读/写基本模式的,都是通过ioctl来完成的,它可以影响设备的行为,或者返回设备的状态。
函数原型:
1: #include <sys/ioctl.h>
2: int ioctl(int fildes, int request, ...);
参数fildes是设备文件的标识符,它是在设备打开时获得的;如果设备比较复杂,那么对它的控制请求相应地也会有很多种,参数request的目的就是用来区分不同的控制请求;通常说来,在对设备进行控制时还需要有其它参数,这要根据不同的控制请求才能确定,并且可能是与硬件设备直接相关的。
l close系统调用
当应用程序使用完声卡之后,需要用close系统调用将其关闭,以便及时释放占用的硬件资源。
函数原型:
1: #include <unistd.h>
2: int close(int fildes);
参数fildes是设备文件的标识符,它是在设备打开时获得的。一旦应用程序调用了close系统调用,Linux内核就会释放与之相关的各种资源,因此建议在不需要的时候尽量及时关闭已经打开的设备。
2. DSP编程框架
l 打开设备
对声卡进行编程时首先要做的是打开与之对应的硬件设备,这是借助于open系统调用来完成的,并且一般情况下使用的是/dev/dsp文件。采用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写的方式打开,并且还要依赖于驱动程序的具体实现。Linux允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之间进行切换,建议在进行音频编程时只要有可能就尽量使用只读或者只写的方式打开设备文件,因为这样不仅能够充分利用声卡的硬件资源,而且还有利于驱动程序的优化。
范例:只写方式(放音palyback)打开设备
1: int handle = open("/dev/dsp", O_WRONLY);
2:
3: if (handle == -1) {
4:
5: perror("open /dev/dsp");
6:
7: return -1;
8:
9: }
l 设置声道(channel)
根据硬件设备和驱动程序的具体情况,可以将其设置为1(单声道,mono)或者2(立体声,stereo)。
范例:设置声道
1: ioctl_val = chn;
2: if ((ioctl(fd, SNDCTL_DSP_CHANNELS, &ioctl_val)) == -1)
3: {
4: fprintf(stderr, "Set Audio Channels %d failed:%s\n", chn,
5: strerror(errno));
6: return (-1);
7: }
8: if (ioctl_val != chn)
9: {
10: fprintf(stderr, "do not support channel %d,supported %d\n", chn,ioctl_val);
11: return (-1);
12: }
l 设置采样格式
范例:
1: ioctl_val = bits;
2: if (ioctl(fd, SNDCTL_DSP_SETFMT, &ioctl_val) == -1)
3: {
4: fprintf(stderr, "Set fmt to bit %d failed:%s\n", bits,
5: strerror(errno));
6: return (-1);
7: }
8: if (ioctl_val != bits)
9: {
10: fprintf(stderr, "do not support bit %d, supported %d\n", bits,
11: ioctl_val);
12: return (-1);
13: }
14:
l 设置采样频率
1: ioctl_val = bits;
2: if (ioctl(fd, SNDCTL_DSP_SETFMT, &ioctl_val) == -1)
3: {
4: fprintf(stderr, "Set fmt to bit %d failed:%s\n", bits,
5: strerror(errno));
6: return (-1);
7: }
8: if (ioctl_val != bits)
9: {
10: fprintf(stderr, "do not support bit %d, supported %d\n", bits,
11: ioctl_val);
12: return (-1);
13: }
14:
调用ioctl时将第二个参数的值设置为SNDCTL_DSP_SPEED,同时在第三个参数中指定采样频率的数值。对于大多数声卡来说,其支持的采样频率范围一般为5kHz到44.1kHz或者48kHz,但并不意味着该范围内的所有频率都会被硬件支持,在Linux下进行音频编程时最常用到的几种采样频率是11025Hz、16000Hz、22050Hz、32000Hz和44100Hz。
范例:
1: ioctl_val = hz;
2: if (ioctl(fd, SNDCTL_DSP_SPEED, &ioctl_val) == -1)
3: {
4: fprintf(stderr, "Set speed to %d failed:%s\n", hz,
5: strerror(errno));
6: return (-1);
7: }
8: if (ioctl_val != hz)
9: {
10: fprintf(stderr, "do not support speed %d,supported is %d\n", hz,ioctl_val);
11: return (-1);
12: }
13:
l 录音、放音
1: ioctl_val = hz;
2: if (ioctl(fd, SNDCTL_DSP_SPEED, &ioctl_val) == -1)
3: {
4: fprintf(stderr, "Set speed to %d failed:%s\n", hz,
5: strerror(errno));
6: return (-1);
7: }
8: if (ioctl_val != hz)
9: {
10: fprintf(stderr, "do not support speed %d,supported is %d\n", hz,ioctl_val);
11: return (-1);
12: }
13:
对设备读操作即为录音,写操作即为放音。
范例:
1: nRD = read(s_fd, buff, BUFF_SIZE);
l 关闭设备
范例:
1: close(dev_fd);
四、 参考资料
1. Linux音频编程指南, http://www.ibm.com/developerworks/cn/linux/l-audio/, 肖文鹏 (xiaowp@263.net);
2. OSS--跨平台的音频接口简介, http://www.ibm.com/developerworks/cn/linux/l-ossapi/index.html, 汤凯 (tangk73@hotmail.com);
3. OSS安装帮助, http://www.opensound.com/release/oss-install.pdf, 4Front Technologies;
4. Linux下的OSS音频接口编程一例, http://blog.chinaunix.net/space.php?uid=7897183&do=blog&cuid=189502, rockins。