Chinaunix首页 | 论坛 | 博客
  • 博客访问: 554916
  • 博文数量: 99
  • 博客积分: 5015
  • 博客等级: 大校
  • 技术积分: 1209
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-28 23:08
文章存档

2011年(7)

2010年(6)

2009年(86)

我的朋友

分类: LINUX

2009-08-01 18:52:50

转载时请注明出处和作者联系方式:http://blog.csdn.net/absurd
作者联系方式: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~~
 
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/absurd/archive/2006/10/05/1321922.aspx
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/absurd/archive/2006/10/05/1321922.aspx
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/absurd/archive/2006/10/05/1321922.aspx
阅读(1159) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~