Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2132145
  • 博文数量: 288
  • 博客积分: 10594
  • 博客等级: 上将
  • 技术积分: 3469
  • 用 户 组: 普通用户
  • 注册时间: 2006-10-27 19:27
文章分类

全部博文(288)

文章存档

2012年(4)

2011年(30)

2010年(40)

2009年(32)

2008年(71)

2007年(79)

2006年(32)

分类: LINUX

2011-10-10 09:54:46

Open Sound System (OSS) 研究笔记

 

转载时请注明出处和作者联系方式:http://blog.csdn.net/absurd

作者联系方式:Li XianJing

更新时间:2006-12-19

 

我们知道,在Win32下,用waveOut系列函数播放声音,用waveIn系列函数录制声音。这套函数设计简单易用,如果使用DirectX可能还会有更好用的接口。而在linux下,这个问题要麻烦不少,在很长一段时间里,我甚至不知道linux下是如何播放声音的。这几天在研究ALSA,我知道ALSAOSS的替代品,而且兼容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 <stdio.h>

#include 

#include 

#include <string.h>

#include 

#include 

#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

  打开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 <stdio.h>

#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: 

阅读(1434) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~