天天看點

C#中使用DirectSound錄音

一.聲霸卡錄音的基本原理

為了實作一個錄音的基本過程,至少需要以下對象的支援:

1. 錄音裝置,對我們的PC裝置就是聲霸卡。這個錄音裝置可以進行的操作應該有開始和關閉。

2. 緩沖區,也就是錄制的聲音放在哪裡的問題。

二.DirectSound對錄音的描述模型

1. DirectSound對錄音的支援類

Ø Capture,裝置對象,可以看作是聲霸卡的描述。

Ø CaptureBuffer,緩沖區對象,存放錄入的音頻資料。

Ø Notify,事件通知對象,由于錄音是一個長時間的過程,是以使用一個緩沖隊列(多個緩沖區)接收資料,每當一個緩沖區滿的時候,系統使用這個對象通知應用程式取走這個緩沖區,并繼續錄音。

以上三個對象是進行錄音操作的主要對象,由于在C++中對DirectSound的操作DirectX幫助文檔中已經有很詳細的說明,這裡就不再贅述了。本文是針對Managed Code。除了以上三個主要的DirectSound類,還需要以下幾個輔助類。

Ø WaveFormat,描述了進行錄制的聲音波形的格式,例如采樣率,單聲道還是立體聲,每個采樣點的長度等等。

Ø Thread,線程類,由于錄音的過程是需要不斷處理緩沖區滿的事件,是以建立一個線程對此進行單獨處理。

Ø AutoResetEvent,通知的事件,當緩沖區滿的時候,使用該事件作為通知事件。

三.代碼解析(SoundRecord類)

1.需要引用的程式集

using System;

using System.Windows.Forms;

using System.Threading;

using System.IO;

// 對DirectSound的支援

using Microsoft.DirectX;

using Microsoft.DirectX.DirectSound;

2. SoundRecord的成員資料

public const int cNotifyNum = 16; // 緩沖隊列的數目

private int mNextCaptureOffset = 0; // 該次錄音緩沖區的起始點

private int mSampleCount = 0; // 錄制的樣本數目

private int mNotifySize = 0; // 每次通知大小

private int mBufferSize = 0; // 緩沖隊列大小

private string mFileName = string.Empty; // 檔案名

private FileStream mWaveFile = null; // 檔案流

private BinaryWriter mWriter = null; // 寫檔案

private Capture mCapDev = null; // 音頻捕捉裝置

private CaptureBuffer mRecBuffer = null; // 緩沖區對象

private Notify mNotify = null; // 消息通知對象

private WaveFormat mWavFormat; // 錄音的格式

private Thread mNotifyThread = null; // 處理緩沖區消息的線程

private AutoResetEvent mNotificationEvent = null; // 通知事件

3. 對外操作的函數

/// <summary>

/// 構造函數,設定錄音裝置,設定錄音格式.

/// </summary>

public SoundRecord()

{

// 初始化音頻捕捉裝置

InitCaptureDevice();

// 設定錄音格式

mWavFormat = CreateWaveFormat();

}

/// 設定錄音結束後儲存的檔案,包括路徑

/// <param name="filename">儲存wav檔案的路徑名</param>

public void SetFileName(string filename)

mFileName = filename;

/// 開始錄音

public void RecStart()

// 建立錄音檔案

CreateSoundFile();

// 建立一個錄音緩沖區,并開始錄音

CreateCaptureBuffer();

// 建立通知消息,當緩沖區滿的時候處理方法

InitNotifications();

mRecBuffer.Start(true);

/// 停止錄音

public void RecStop()

// 關閉通知消息

if (null != mNotificationEvent)

mNotificationEvent.Set();

// 停止錄音

mRecBuffer.Stop();

// 寫入緩沖區最後的資料

RecordCapturedData();

// 回寫長度資訊

mWriter.Seek(4, SeekOrigin.Begin);

mWriter.Write((int)(mSampleCount + 36)); // 寫檔案長度

mWriter.Seek(40, SeekOrigin.Begin);

mWriter.Write(mSampleCount); // 寫資料長度

mWriter.Close();

mWaveFile.Close();

mWriter = null;

mWaveFile = null;

4.内部調用函數

/// 初始化錄音裝置,此處使用主錄音裝置.

/// <returns>調用成功傳回true,否則傳回false</returns>

private bool InitCaptureDevice()

// 擷取預設音頻捕捉裝置

CaptureDevicesCollection devices = new CaptureDevicesCollection(); // 枚舉音頻捕捉裝置

Guid deviceGuid = Guid.Empty; // 音頻捕捉裝置的ID

if (devices.Count>0)

deviceGuid = devices[0].DriverGuid;

else

MessageBox.Show("系統中沒有音頻捕捉裝置");

return false;

// 用指定的捕捉裝置建立Capture對象

try

mCapDev = new Capture(deviceGuid);

catch (DirectXException e)

MessageBox.Show(e.ToString());

return true;

/// 建立錄音格式,此處使用16bit,16KHz,Mono的錄音格式

/// <returns>WaveFormat結構體</returns>

private WaveFormat CreateWaveFormat()

WaveFormat format = new WaveFormat();

format.FormatTag = WaveFormatTag.Pcm; // PCM

format.SamplesPerSecond = 16000; // 16KHz

format.BitsPerSample = 16; // 16Bit

format.Channels = 1; // Mono

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

format.AverageBytesPerSecond = format.BlockAlign * format.SamplesPerSecond;

return format;

/// 建立錄音使用的緩沖區

private void CreateCaptureBuffer()

// 緩沖區的描述對象

CaptureBufferDescription bufferdescription = new CaptureBufferDescription();

if (null != mNotify)

mNotify.Dispose();

mNotify = null;

if (null != mRecBuffer)

mRecBuffer.Dispose();

mRecBuffer = null;

// 設定通知的大小,預設為1s鐘

mNotifySize = (1024 > mWavFormat.AverageBytesPerSecond / 8) ? 1024 : (mWavFormat.AverageBytesPerSecond / 8);

mNotifySize -= mNotifySize % mWavFormat.BlockAlign;

// 設定緩沖區大小

mBufferSize = mNotifySize * cNotifyNum;

// 建立緩沖區描述

bufferdescription.BufferBytes = mBufferSize;

bufferdescription.Format = mWavFormat; // 錄音格式

// 建立緩沖區

mRecBuffer = new CaptureBuffer(bufferdescription, mCapDev);

mNextCaptureOffset = 0;

/// 初始化通知事件,将原緩沖區分成16個緩沖隊列,在每個緩沖隊列的結束點設定通知點.

/// <returns>是否成功</returns>

private bool InitNotifications()

if (null == mRecBuffer)

MessageBox.Show("未建立錄音緩沖區");

// 建立一個通知事件,當緩沖隊列滿了就激發該事件.

mNotificationEvent = new AutoResetEvent(false);

// 建立一個線程管理緩沖區事件

if (null == mNotifyThread)

mNotifyThread = new Thread(new ThreadStart(WaitThread));

mNotifyThread.Start();

// 設定通知的位置

BufferPositionNotify[] PositionNotify = new BufferPositionNotify[cNotifyNum + 1];

for (int i = 0; i < cNotifyNum; i++)

PositionNotify[i].Offset = (mNotifySize * i) + mNotifySize - 1;

PositionNotify[i].EventNotifyHandle = mNotificationEvent.Handle;

mNotify = new Notify(mRecBuffer);

mNotify.SetNotificationPositions(PositionNotify, cNotifyNum);

/// 将錄制的資料寫入wav檔案

private void RecordCapturedData()

byte[] CaptureData = null;

int ReadPos;

int CapturePos;

int LockSize;

mRecBuffer.GetCurrentPosition(out CapturePos, out ReadPos);

LockSize = ReadPos - mNextCaptureOffset;

if (LockSize < 0)

LockSize += mBufferSize;

// 對齊緩沖區邊界,實際上由于開始設定完整,這個操作是多餘的.

LockSize -= (LockSize % mNotifySize);

if (0 == LockSize)

return;

// 讀取緩沖區内的資料

CaptureData = (byte[])mRecBuffer.Read(mNextCaptureOffset, typeof(byte), LockFlag.None, LockSize);

// 寫入Wav檔案

mWriter.Write(CaptureData, 0, CaptureData.Length);

// 更新已經錄制的資料長度.

mSampleCount += CaptureData.Length;

// 移動錄制資料的起始點,通知消息隻負責訓示産生消息的位置,并不記錄上次錄制的位置

mNextCaptureOffset += CaptureData.Length;

mNextCaptureOffset %= mBufferSize; // Circular buffer

/// 接收緩沖區滿消息的處理線程

private void WaitThread()

while(true)

// 等待緩沖區的通知消息

mNotificationEvent.WaitOne(Timeout.Infinite, true);

// 錄制資料

/// 建立儲存的波形檔案,并寫入必要的檔案頭.

private void CreateSoundFile()

/**************************************************************************

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:

Bytes 0 - 3 : 'R' 'I' 'F' 'F'

Bytes 4 - 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.)

Bytes 8 - 11: 'W' 'A' 'V' 'E'

The 24 byte FORMAT chunk is constructed like this:

Bytes 0 - 3 : 'f' 'm' 't' ' '

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

Bytes 8 - 9 : File padding. Always 1.

Bytes 10- 11: Number of channels. Either 1 for mono, or 2 for stereo.

Bytes 12- 15: Sample rate.

Bytes 16- 19: Number of bytes per second.

Bytes 20- 21: Bytes per sample. 1 for 8 bit mono, 2 for 8 bit stereo or

16 bit mono, 4 for 16 bit stereo.

Bytes 22- 23: Number of bits per sample.

The DATA chunk is constructed like this:

Bytes 0 - 3 : 'd' 'a' 't' 'a'

Bytes 4 - 7 : Length of data, in bytes.

Bytes 8 -...: Actual sample data.

***************************************************************************/

// Open up the wave file for writing.

mWaveFile = new FileStream(mFileName, FileMode.Create);

mWriter = new BinaryWriter(mWaveFile);

// Set up file with RIFF chunk info.

char[] ChunkRiff = {'R','I','F','F'};

char[] ChunkType = {'W','A','V','E'};

char[] ChunkFmt = {'f','m','t',' '};

char[] ChunkData = {'d','a','t','a'};

short shPad = 1; // File padding

int nFormatChunkLength = 0x10; // Format chunk length.

int nLength = 0; // File length, minus first 8 bytes of RIFF description. This will be filled in later.

short shBytesPerSample = 0; // Bytes per sample.

// 一個樣本點的位元組數目

if (8 == mWavFormat.BitsPerSample && 1 == mWavFormat.Channels)

shBytesPerSample = 1;

else if ((8 == mWavFormat.BitsPerSample && 2 == mWavFormat.Channels) || (16 == mWavFormat.BitsPerSample && 1 == mWavFormat.Channels))

shBytesPerSample = 2;

else if (16 == mWavFormat.BitsPerSample && 2 == mWavFormat.Channels)

shBytesPerSample = 4;

// RIFF 塊

mWriter.Write(ChunkRiff);

mWriter.Write(nLength);

mWriter.Write(ChunkType);

// WAVE塊

mWriter.Write(ChunkFmt);

mWriter.Write(nFormatChunkLength);

mWriter.Write(shPad);

mWriter.Write(mWavFormat.Channels);

mWriter.Write(mWavFormat.SamplesPerSecond);

mWriter.Write(mWavFormat.AverageBytesPerSecond);

mWriter.Write(shBytesPerSample);

mWriter.Write(mWavFormat.BitsPerSample);

// 資料塊

mWriter.Write(ChunkData);

mWriter.Write((int)0); // The sample length will be written in later.

5.外部窗體調用方式

聲明部分:

private SoundRecord recorder = null; // 錄音

窗體構造函數:

recorder = new SoundRecord();

啟動錄音按鈕:

private void btnStart_Click(object sender, System.EventArgs e)

//

// 錄音設定

string wavfile = null;

wavfile = “test.wav”;

recorder.SetFileName(wavfile);

recorder.RecStart();

中止錄音按鈕:

private void btnStop_Click(object sender, System.EventArgs e)

recorder.RecStop();

recorder = null;

6.需要添加的外部引用檔案

在系統的System32目錄下添加以下兩個引用檔案,如果沒有,在DirectX的開發包内可以找到。

Microsoft.DirectX.dll

Microsoft.DirectX.DirectSound.dll

本文轉自94cool部落格園部落格,原文連結:http://www.cnblogs.com/94cool/articles/1524818.html,如需轉載請自行聯系原作者