Open Sound System (OSS) 研究笔记

我们知道,在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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 打开设备文件 */
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. 录音

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#include <time.h>
 
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<ei.nvalues;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<ei.nvalues;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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* 打开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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/soundcard.h>
 
#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:http://manuals.opensound.com
MIDI: http://www.borg.com/~jglatt/tech/midispec.htm

From: http://blog.csdn.net/absurd/archive/2006/10/05/1321922.aspx

Leave a Reply

Your email address will not be published. Required fields are marked *