C#中利用DirectSound录音

  今天讲的用C#来编一个录音机,为什么我们要知道现实录音机的工作流程呢?我用《大学》里的观点来解释一下,《大学》里认为学习应该格物致知,致知在格物,是说我们想要达到认识的极致,在于接触事物并极力探求其中的道理,而计算机的编程是一种对现实世界的抽象,面向对象思想的出现就是最好的例子。

  我们就拿《C#利用DirectSound录音》来实践这个观点,首先在这个题目里已经有了这个DirectSound,对不对?那我们就来看看它。实际上我们必须得有录音机和磁带吧,我喜欢先买磁带,我们这里的磁带是*.wav文件,WAVE是录音时用的标准的WINDOWS文件格式,扩展名为“WAV”,我们使用DirectSound采集的WAV声音,其音频数据是按照PCM(脉冲编码调制,对连续变化的模拟信号进行抽样、量化和编码产生的数据,0和1的组合)调制后放入缓冲区的。

  WAVE文件格式采用RIFF文件格式结构,对PCM数据和其它一些音频信息进行相应的编排,从而最终形成的WAVE文件才能被音频播放器识别,才能进行播放。

然后我们买一个录音机,录音机要有配套mac和电子系统吧,不然怎么用呢?(举例子:我当想在装Sql2008,像平常Windows的产品一样的安装时,突然它说我没有Sql2008SP 1,没有就下吧(600M左右),在MS上找了个sp1就下,高高兴兴的安装了,结果Sql2008还是说我的sp1没安装,我心想电脑有时侯也挺笨的,我明明安装了,它竟然不知道,不过还是要找原因吧,找来找去,最后发现是我的sql2008的版本号和sp1的版本号不匹配。)所以我们在选择mac,也就是我们PPT上的采集设备的时侯,就要为后面的电路系统,也就是内存来铺路吧。采集在英语中是Capture,内存是Buffer,正好在MSDN上的一个capture类就符合,Contains properties and methods used to create sound capture buffers.这是既找到了设备又在为buffer铺路吧,然后再找到captureBuffer类,Contains properties and methods used to manipulate sound capture buffers.这是个可以操作captureBuffer的类。

  我们有必要来研究一下这个CaptureBuffer类,CaptureBuffer区是暂时存放音频数据的地方,并且它还提供了我们两个指针:读指针和捕捉指针。它们的位置按照相对于缓冲区起始位置的偏移量计算。读指针位于当前已经被完全捕捉到缓冲区的数据末尾。捕捉指针位于当前将要从硬件中复制的数据块的末尾。如果你想从缓冲区中读取数据,则只能从已经完全写入缓冲区的数据中读取,也就是说我们只能从偏移量小于读指针的地方读取。

  好!最重要的来了,我对着录音机唱歌,录音机本身没有能力存储我的声音,所以只能存在磁带里。

  录音机在这里干什么,它真正做的事是把我的声音信息转化成磁信息,再把磁信息放到磁带里吧。这里其实是两件事情吧。大家还记得吕老师给我们讲多线程时举的一个例子吗?一边向ListView中添加数据,一边读ListView中的数据吧。对!这里就应该用多线程.

  思想搞清楚了,怎么实现是大家最关心的吧,大家应该都知道时间相同的音频文件,WAVE文件会比其它格式的音频文件大得多,这是因为WAVE文件没有对数据进行压缩。如果录音的时候,不限制缓冲区大小,那么你录制很短的时间可能就会占用很多内存,说不定不过多久,你的1G内存就不够用了。因此我们必须对缓冲区的大小进行限制,而且当缓冲区满了之后,还可以重新从缓冲区起始处开始,用新的数据覆盖旧的数据。那旧的数据怎么办呢?如果你不想丢失旧的数据,那就得在旧的数据被覆盖之前,将它转移到其它地方。

  如何才能在旧的数据没有被覆盖之前,将它转移走呢?如果是你,你会采用什么办法?

  有人提出通过轮询的办法,经常询问缓冲区是否满,满了则进行转移操作。可是这样做会相当耗费性能。微软提供了我们一个解决办法:“通知”。我们可以在缓冲区中的某些位置处设置通知,当读指针到达通知位置的时候,就会触发相应的事件执行转移操作。是不是有点像操作系统中的“响应中断”呢?

  那么这里就要用到AutoResetEvent和Notify了。

  所以录音的大致过程是,

1.设置Pcm格式。

  //格式标记Retrieves and sets the waveform-audio format type, for the format type.

  FormatTag = WaveFormatTag.Pcm;

  //每个采样所占位

  BitsPerSample = 16;

  //单声道

  Channels = 1;

  //单位采样点的字节数//Bytespersample

  BlockAlign = (short)(format.Channels * (format.BitsPerSample / 8));

  //采样率

  SamplesPerSecond = 22050;

  //平均每秒采样字节数

  AverageBytesPerSecond = format.BlockAlign * SamplesPerSecond;

2.创建wave文件

  Here is where the file will be created.A wave file is a RIFF file,which has chunks of data that describe what the file contains.A wave RIFF file is put together like this:

The 12 byte RIFF chunk is constructed like this:

  Bytes0-3: 'R''I''F''F'

  Bytes4-7: Length of file,minus the first 8 bytes of the RIFF description.(4 bytes for "WAVE" +24 bytes for format chunk length+8 bytes for data chunk description + actual sample data size.)

  Bytes8-11:'W''A''V''E' The 24 byte FORMAT chunk is constructed like this:

  Bytes0-3:'f''m''t'''

  Bytes4-7:The format chunk length.This is always 16.

  Bytes8-9:Filepadding.Always1.

  Bytes10-11:Channels.Either 1 for mono,or 2 for stereo.

  Bytes12-15:Samplerate.

  Bytes16-19:bytespersecond.

  Bytes20-21:Bytespersample.1 for 8 bitmono,2 for 8 bit stereo or 16 bit mono,4 for 16 bit stereo.

  Bytes22-23:bitspersample.

TheDATAchunkisconstructedlikethis:

  Bytes0-3:'d''a''t''a'

  Bytes4-7:Length of data,inbytes.

  Bytes8-:Actual sampledata.

  依次填入即可。

3.创建设备对象,设置缓冲区对象

  创建设备对象的条件,CaptureDevicesCollection枚举可用的捕捉设备。然后获取Guid属性,用来创建Capture对象。

  New Capture(CaptureDeivcesCollection[0].Guid);创建一个Capture对象。

  创建设备缓冲对象的条件,CaptureBufferDescription对象,要设置它的BufferSize属性,用单个通知大小*总个数就是缓冲区的大小。它的Format属性是我们的Pcm格式化后的对象。

  New CaptureBuffer(CaptureBufferDescription,Capture);创建一个CaptureBuffer对象

4.设置缓冲区通知,设置通知被触发后的事件

  这个时侯要建立起一个新线程把磁信号存到磁带中。

  主线程中CaptureBuffer不断录入声音,用AutoResetEvent来启动新线程来存储数据。

  New AutoResetEvent(false);

5. 录音结束,写入WAV文件相应位置数据。这样一个可以播放的WAVE文件就OK了。

posted @ 2010-10-28 17:55  晓炜  阅读(1916)  评论(0编辑  收藏  举报