C#中DirectSound錄音的使用
一.聲卡錄音的基本原理
為了實現一個錄音的基本過程,至少需要以下對象的支持:
1. 錄音設備,對我們的PC設備就是聲卡。這個錄音設備可以進行的操作應該有開始和關閉。
2. 緩沖區,也就是錄制的聲音放在哪里的問題。
二.DirectSound錄音的描述模型 (我裝的是directx_dec2005_redist.exe)
DirectSound錄音的支持類
ØCapture,設備對象,可以看作是聲卡的描述。
ØCaptureBuffer,緩沖區對象,存放錄入的音頻數據。
ØNotify,事件通知對象,由于錄音是一個長時間的過程,因此使用一個緩沖隊列(多個緩沖區)接收數據,每當一個緩沖區滿的時候,系統使用這個對象通知應用程序取走這個緩沖區,并繼續錄音。
以上三個對象是進行錄音操作的主要對象,由于在C++中對DirectSound的操作DirectX幫助文檔中已經有很詳細的說明,這里就不再贅述了。本文是針對Managed Code。除了以上三個主要的DirectSound類,還需要以下幾個輔助類。
ØWaveFormat,描述了進行錄制的聲音波形的格式,例如采樣率,單聲道還是立體聲,每個采樣點的長度等等。
ØThread,線程類,由于錄音的過程是需要不斷處理緩沖區滿的事件,因此新建一個線程對此進行單獨處理。
ØAutoResetEvent,通知的事件,當緩沖區滿的時候,使用該事件作為通知事件。
三.DirectSound錄音代碼解析(SoundRecord類)
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Windows.Forms;
- using System.IO;
- using System.Threading;
- using Microsoft.DirectX;
- using Microsoft.DirectX.DirectSound;
- namespace DirectSoundTest
- {
- class 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; // 通知事件
- /**//// < summary>
- /// 構造函數,設定錄音設備,設定錄音格式.
- /// < /summary>
- public SoundRecord()
- {
- // 初始化音頻捕捉設備
- InitCaptureDevice();
- // 設定錄音格式
- mWavFormat = CreateWaveFormat();
- }
- /**//// < summary>
- /// 設定錄音結束后保存的文件,包括路徑
- /// < /summary>
- /// < param name="filename">保存wav文件的路徑名< /param>
- public void SetFileName(string filename)
- {
- mFileName = filename;
- }
- /**//// < summary>
- /// 開始錄音
- /// < /summary>
- public void RecStart()
- {
- // 創建錄音文件
- CreateSoundFile();
- // 創建一個錄音緩沖區,并開始錄音
- CreateCaptureBuffer();
- // 建立通知消息,當緩沖區滿的時候處理方法
- InitNotifications();
- mRecBuffer.Start(true);
- }
- /**//// < summary>
- /// 停止錄音
- /// < /summary>
- 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.內部調用函數
- /**//// < summary>
- /// 初始化錄音設備,此處使用主錄音設備.
- /// < /summary>
- /// < 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 false;
- }
- return true;
- }
- /**//// < summary>
- /// 創建錄音格式,此處使用16bit,16KHz,Mono的錄音格式
- /// < /summary>
- /// < 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;
- }
- /**//// < summary>
- /// 創建錄音使用的緩沖區
- /// < /summary>
- 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;
- }
- /**//// < summary>
- /// 初始化通知事件,將原緩沖區分成16個緩沖隊列,在每個緩沖隊列的結束點設定通知點.
- /// < /summary>
- /// < returns>是否成功< /returns>
- private bool InitNotifications()
- {
- if (null == mRecBuffer)
- {
- MessageBox.Show("未創建錄音緩沖區");
- return false;
- }
- // 創建一個通知事件,當緩沖隊列滿了就激發該事件.
- 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);
- return true;
- }
- /**//// < summary>
- /// 將錄制的數據寫入wav文件
- /// < /summary>
- 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
- }
- /**//// < summary>
- /// 接收緩沖區滿消息的處理線程
- /// < /summary>
- private void WaitThread()
- {
- while (true)
- {
- // 等待緩沖區的通知消息
- mNotificationEvent.WaitOne(Timeout.Infinite, true);
- // 錄制數據
- RecordCapturedData();
- }
- }
- /**//// < summary>
- /// 創建保存的波形文件,并寫入必要的文件頭.
- /// < /summary>
- 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.
- }
- }
- }
四、DirectSound錄音外部窗體調用方式
聲明部分:
- 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;
- }
五、需要添加的外部引用文件
在系統的System32目錄下添加以下兩個引用文件,如果沒有,在DirectX的開發包內可以找到。
Microsoft.DirectX.dll
Microsoft.DirectX.DirectSound.dll
【編輯推薦】