一.聲霸卡錄音的基本原理
為了實作一個錄音的基本過程,至少需要以下對象的支援:
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,如需轉載請自行聯系原作者