作者联系方式:Li XianJing
更新时间:2006-12-19
我们知道,在Win32下,用waveOut系列函数播放声音,用waveIn系列函数录制声音。这套函数设计简单易用,如果使用DirectX可能还会有更好用的接口。而在linux下,这个问题要麻烦不少,在很长一段时间里,我甚至不知道linux下是如何播放声音的。这几天在研究ALSA,我知道ALSA是OSS的替代品,而且兼容OSS。为了了解ALSA的优势所在,我决定先研究OSS。本文记录了一些在研究过程中所记的笔记。
据说Open Sound System (OSS)是第一个为统一unix下数字音频处理的尝试。当然这种尝试是非常成功的,Opensound的实现可以移植到绝大多数unix操作系统上,而且有大量的应用程序支持OSS,Mplayer就是其中之一。Opensound的实现不是开放源码的,linux上的实现可能并非是Opensound所提供的。不过OSS只是一套规范,这个规范是开放的,只要有时间和精力,我们自己也可以实现一套符合OSS的代码。
OSS并没有规定操作系统内部的实现方式,也没有为驱动程序的实现提供指南,而是只规定了操作系统与应用程序交互的接口。OSS采取unix惯用的方式,通过文件与应用程序交互,而不增加任何系统调用。使用传统的open/close/read/write/ioctl系列文件操作函数就足够了。数据通过read/write来传递数据,而通过ioctl来传递控制信息。OSS定义了下列这些设备文件:
1. /dev/mixer
这是所谓混音器,一个看似很专业的术语。它在这里的功能实际上很简单,主要是对声卡进行设置,比较设置speaker、mic和midi的音量等等。这些设置主要是通过ioctl进行,比如可以用SNDCTL_DSP_SETPLAYVOL设置speaker的音量,用SNDCTL_DSP_SETRECVOL设置mic的音量,用SNDCTL_DSP_SET_PLAYTGT选择输出的speaker(比如机身的speaker或者earphone)。
2. /dev/sndstat
这主要是用于debug的,cat /dev/sndstat可以输出一些OSS驱动程序检测的设备信息,这些信息是给人读的而不是程序使用的。在FC4上没有这个文件,可能FC4已经使用了ALSA吧,尽管ALSA兼容OSS,多半只是程序上的兼容,所以忽略了这个文件。
3. /dev/dsp 和/dev/audio
这是两个非常重要的文件,往该文件写的数据传向speaker,即完成播放过程。从该文件读数据,系统会从mic采用数据并返回给应用程序,即实现录音过程。这两个文件的功能非常类似,只是默认的编码方式有些差别。/dev/dsp默认采用线性编码,/dev/audio默认采用对数编码。/dev/audio 主要是用于和SunOS保持兼容性,可以通过ioctl设置使两者保持相同的行为。
4. /dev/sequencer
该文件主要是给电子(MIDI)音乐应用程序使用的,也利用它来实现游戏中的音效。通过该文件可以访问声卡内部和外部(即子卡)中的声音合成(synthesizer)设备。声音合成(synthesizer)设备的功能是把MIDI转成波型数据,一般通过调频(FM)和波表(wavetable)来实现。
5. /dev/music
该文件类似于/dev/sequencer,但它可以以同样的方式处理声音合成(synthesizer)设备和MIDI设备(可能是指MIDI键盘吧),而且具有更好的设备无关性,但是不能像/dev/sequencer那可以精确的控制单个音符(note)(?)。
6. /dev/midi
该文件是MIDI总线端口的比较底层的接口,它的工作方式很像一个TTY (character terminal),所有发送给它的数据立即传递到MIDI端口。
7. /dev/dmfm
该文件是调频合成器(FM synthesizers)的原始接口,通过它可以访问FM的一些底层寄存器。
8. /dev/dmmidi
该文件是MIDI设备(MIDI device)的原始接口,它提供直接TTY方式访问MIDI端口,主要是给一个特殊应用程序使用。(不知道它与/dev/midi的差别到底在哪里。)
几个术语和概念:
1. 关于PCM的
PCM是Pulse code modulation的缩写,它是对波形最直接的编码方式。它在音频中的地位可能和BMP在图片中的地位有点类似吧。
Sampling rate:从模拟信号到数字信号,即从连续信号到离散信号的转换都是通过离散采样完成的,Sampling rate就是每秒种采样的个数。根据香农采样定理,要保证信号不失真,Sampling rate要大于信号最高频率的两倍。我们知道人的耳朵能听到的频率范围是20hz – 20khz,所以Sampling rate达到40k就够了,再多了也只是浪费。但是有时为了节省带宽和存储资源,可以降低Sampling rate而损失声音的质量,所以我们常常见到小于40k采样率的声音数据。
Sample size:用来量化一个采样的幅度,一般为8 bits、16 bits和24 bits。8 bits只有早期的声卡支持,而24 bits只有专业的声卡才支持,我们用的一般都是16 bits的。
Number of channels:声音通道个数,单声道为一个,立体声为两个,还有更多的(如8个声道的7.1格式)。一般来说,每个声道都来源于一个独立的mic,所以声道多效果会更好(更真实),当然代价也更大。
Frame: Frame是指包含了所有通道的一次采样数据,比如对于16bits的双声道来说,一个frame的大小为4个字节(2 * 16)。
2. 关于MIDI
MIDI是Musical Instrument Digital Interface的缩写。说白了,MIDI只一个通信协议,用来在乐器之间进行通信的,让所有的乐器都说同一种语言。这种通信通常是通过一个高速串口来实现的,速率为31250bps,8bits的数据,外加一个起始位和一个停止位。下面简单介绍一下这个协议的内容:
MIDI的消息由状态(Status,或者称为命令更好)和数据两部分组成,状态由一个字节表示,所有状态值的最高位都为1,即大于等于128。数据由多个字节表示,数据长度视状态而定,但所有数据的最高位都为0,即小于128。状态的8bits,又分为两个4bits,高的4bits代表状态的类型,低四位代表通道。MIDI的状态有:
8 = Note Off
9 = Note On
A = AfterTouch (ie, key pressure)
B = Control Change
C = Program (patch) change
D = Channel Pressure
E = Pitch Wheel
F = System Exclusive
Note On: 它是一个抽象的动作,当演奏者按下钢琴的键,拉动小提琴的弦或者拨动吉它的弦,这个动作称为note on。
Note Off: 它是一个抽象的动作,当演奏者放开钢琴的键,停止拉动小提琴或者手指离开吉它的弦,这个动作称为note off。
AfterTouch: 同是按键动作,力度的差异产生效果也不一样,即使在保持按键的过程中,压力也会有变化,这由AfterTouch状态来调整。
Control Change:用来对MIDI设备进行设置,比如设置音量和立体声平衡值等等,它有128种取值(0-127)。
Program (patch) change:一个Program 一般与一种乐器对应,比如Piano、 Guitar和Trumpet,要换乐器就用这个状态。
Channel Pressure: 和AfterTouch的功能类似,但它不只影响一个note,而是影响一个通道,通常用来设置默认值。
Pitch Wheel:设置Pitch Wheel的值,好像是设置乐器的基准音调吧,不太懂。这里有篇文章讲得不错,大家可以看看。
下面我们看看编程实例:
1. 操作mixer
打开设备文件:
if ((mixerfd = open ("/dev/mixer", O_RDWR, 0)) == -1)
{
perror ("/dev/mixer");
exit (-1);
}
通过ioctl控件设备:
oss_sysinfo info;
if (ioctl (mixerfd, SNDCTL_SYSINFO, &info) == -1)
{
perror ("SNDCTL_SYSINFO");
if (errno == EINVAL)
fprintf (stderr, "Error: OSS version 4.0 or later is required\n");
exit (-1);
}
2. 录音
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd, i, src;
oss_mixer_enuminfo ei;
if (argc < 2)
{
fprintf(stderr, "Usage: %s dspdev\n", argv[0]);
exit(-1);
}
if ((fd=open(argv[1], O_RDONLY, 0))==-1)
{
perror(argv[1]);
exit(-1);
}
if (ioctl(fd, SNDCTL_DSP_GET_RECSRC_NAMES, &ei)==-1)
{
perror("SNDCTL_DSP_GET_RECSRC_NAMES");
exit(-1);
}
if (argc == 2)
{
for (i=0;i
printf("Rec source #%d = '%s'\n", i, ei.strings+ei.strindex[i]);
exit(0);
}
if (strcmp(argv[2], "?")==0 || strcmp(argv[2], "-")==0)
{
if (ioctl(fd, SNDCTL_DSP_GET_RECSRC, &src)==-1)
{
perror("SNDCTL_DSP_GET_RECSRC");
exit(-1);
}
printf("Current recording source is #%d\n", src);
printf("Current recording source is #%d (%s)\n",
src, ei.strings+ei.strindex[src]);
exit(0);
}
src=0;
for (i=0;i
{
if (strcmp(argv[2], ei.strings+ei.strindex[i])==0)
{
if (ioctl(fd, SNDCTL_DSP_SET_RECSRC, &src)==-1)
{
perror("SNDCTL_DSP_SET_RECSRC");
exit(-1);
}
exit(0);
}
src++;
}
fprintf(stderr, "What?\n");
exit(-1);
}
3. 播放PCM
打开dsp设备文件
if ((fd = open (name, mode, 0)) == -1)
{
perror (name);
exit (-1);
}
设置格式
tmp = AFMT_S16_NE; /* Native 16 bits */
if (ioctl (fd, SNDCTL_DSP_SETFMT, &tmp)==-1)
{
perror("SNDCTL_DSP_SETFMT");
exit(-1);
}
设置声道个数
tmp = 1;
if (ioctl (fd, SNDCTL_DSP_CHANNELS, &tmp)==-1)
{
perror("SNDCTL_DSP_CHANNELS");
exit(-1);
}
设置采用率
sample_rate = 48000;
if (ioctl (fd, SNDCTL_DSP_SPEED, &sample_rate)==-1)
{
perror("SNDCTL_DSP_SPEED");
exit(-1);
}
播放
if (write (fd_out, buf, sizeof (buf)) != sizeof (buf))
{
perror ("Audio write");
exit (-1);
}
4. 播放midi
#include
#include
#include
#include
#include
#define DEVICE "/dev/midi"
int
main ()
{
int fd;
unsigned char note_on[] = { 0xc0, 0, /* Program change */
0x90, 60, 60
}; /* Note on */
unsigned char note_off[] = { 0x80, 60, 60 }; /* Note off */
if ((fd = open (DEVICE, O_WRONLY, 0)) == -1)
{
perror ("open " DEVICE);
exit (-1);
}
if (write (fd, note_on, sizeof (note_on)) == -1)
{
perror ("write " DEVICE);
exit (-1);
}
sleep (1); /* Delay one second */
if (write (fd, note_off, sizeof (note_off)) == -1)
{
perror ("write " DEVICE);
exit (-1);
}
close (fd);
exit (0);
}
以上是这两天阅读OSS规范、MIDI和音频相关资料的笔记和总结,有些内容不是很确信,若有错误,希望大家指正。谢谢
参考资料:
OSS:
MIDI:
~~end~~
阅读(1159) | 评论(0) | 转发(0) |