C#中DirectSound錄音的使用
一.聲卡錄音的基本原理
為了實(shí)現(xiàn)一個(gè)錄音的基本過(guò)程,至少需要以下對(duì)象的支持:
1. 錄音設(shè)備,對(duì)我們的PC設(shè)備就是聲卡。這個(gè)錄音設(shè)備可以進(jìn)行的操作應(yīng)該有開(kāi)始和關(guān)閉。
2. 緩沖區(qū),也就是錄制的聲音放在哪里的問(wèn)題。
二.DirectSound錄音的描述模型 (我裝的是directx_dec2005_redist.exe)
DirectSound錄音的支持類
ØCapture,設(shè)備對(duì)象,可以看作是聲卡的描述。
ØCaptureBuffer,緩沖區(qū)對(duì)象,存放錄入的音頻數(shù)據(jù)。
ØNotify,事件通知對(duì)象,由于錄音是一個(gè)長(zhǎng)時(shí)間的過(guò)程,因此使用一個(gè)緩沖隊(duì)列(多個(gè)緩沖區(qū))接收數(shù)據(jù),每當(dāng)一個(gè)緩沖區(qū)滿的時(shí)候,系統(tǒng)使用這個(gè)對(duì)象通知應(yīng)用程序取走這個(gè)緩沖區(qū),并繼續(xù)錄音。
以上三個(gè)對(duì)象是進(jìn)行錄音操作的主要對(duì)象,由于在C++中對(duì)DirectSound的操作DirectX幫助文檔中已經(jīng)有很詳細(xì)的說(shuō)明,這里就不再贅述了。本文是針對(duì)Managed Code。除了以上三個(gè)主要的DirectSound類,還需要以下幾個(gè)輔助類。
ØWaveFormat,描述了進(jìn)行錄制的聲音波形的格式,例如采樣率,單聲道還是立體聲,每個(gè)采樣點(diǎn)的長(zhǎng)度等等。
ØThread,線程類,由于錄音的過(guò)程是需要不斷處理緩沖區(qū)滿的事件,因此新建一個(gè)線程對(duì)此進(jìn)行單獨(dú)處理。
ØAutoResetEvent,通知的事件,當(dāng)緩沖區(qū)滿的時(shí)候,使用該事件作為通知事件。
三.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; // 緩沖隊(duì)列的數(shù)目
- private int mNextCaptureOffset = 0; // 該次錄音緩沖區(qū)的起始點(diǎn)
- private int mSampleCount = 0; // 錄制的樣本數(shù)目
- private int mNotifySize = 0; // 每次通知大小
- private int mBufferSize = 0; // 緩沖隊(duì)列大小
- private string mFileName = string.Empty; // 文件名
- private FileStream mWaveFile = null; // 文件流
- private BinaryWriter mWriter = null; // 寫(xiě)文件
- private Capture mCapDev = null; // 音頻捕捉設(shè)備
- private CaptureBuffer mRecBuffer = null; // 緩沖區(qū)對(duì)象
- private Notify mNotify = null; // 消息通知對(duì)象
- private WaveFormat mWavFormat; // 錄音的格式
- private Thread mNotifyThread = null; // 處理緩沖區(qū)消息的線程
- private AutoResetEvent mNotificationEvent = null; // 通知事件
- /**//// < summary>
- /// 構(gòu)造函數(shù),設(shè)定錄音設(shè)備,設(shè)定錄音格式.
- /// < /summary>
- public SoundRecord()
- {
- // 初始化音頻捕捉設(shè)備
- InitCaptureDevice();
- // 設(shè)定錄音格式
- mWavFormat = CreateWaveFormat();
- }
- /**//// < summary>
- /// 設(shè)定錄音結(jié)束后保存的文件,包括路徑
- /// < /summary>
- /// < param name="filename">保存wav文件的路徑名< /param>
- public void SetFileName(string filename)
- {
- mFileName = filename;
- }
- /**//// < summary>
- /// 開(kāi)始錄音
- /// < /summary>
- public void RecStart()
- {
- // 創(chuàng)建錄音文件
- CreateSoundFile();
- // 創(chuàng)建一個(gè)錄音緩沖區(qū),并開(kāi)始錄音
- CreateCaptureBuffer();
- // 建立通知消息,當(dāng)緩沖區(qū)滿的時(shí)候處理方法
- InitNotifications();
- mRecBuffer.Start(true);
- }
- /**//// < summary>
- /// 停止錄音
- /// < /summary>
- public void RecStop()
- {
- // 關(guān)閉通知消息
- if (null != mNotificationEvent)
- mNotificationEvent.Set();
- // 停止錄音
- mRecBuffer.Stop();
- // 寫(xiě)入緩沖區(qū)最后的數(shù)據(jù)
- RecordCapturedData();
- // 回寫(xiě)長(zhǎng)度信息
- mWriter.Seek(4, SeekOrigin.Begin);
- mWriter.Write((int)(mSampleCount + 36)); // 寫(xiě)文件長(zhǎng)度
- mWriter.Seek(40, SeekOrigin.Begin);
- mWriter.Write(mSampleCount); // 寫(xiě)數(shù)據(jù)長(zhǎng)度
- mWriter.Close();
- mWaveFile.Close();
- mWriter = null;
- mWaveFile = null;
- }
- //4.內(nèi)部調(diào)用函數(shù)
- /**//// < summary>
- /// 初始化錄音設(shè)備,此處使用主錄音設(shè)備.
- /// < /summary>
- /// < returns>調(diào)用成功返回true,否則返回false< /returns>
- private bool InitCaptureDevice()
- {
- // 獲取默認(rèn)音頻捕捉設(shè)備
- CaptureDevicesCollection devices = new CaptureDevicesCollection(); // 枚舉音頻捕捉設(shè)備
- Guid deviceGuid = Guid.Empty; // 音頻捕捉設(shè)備的ID
- if (devices.Count > 0)
- deviceGuid = devices[0].DriverGuid;
- else
- {
- MessageBox.Show("系統(tǒng)中沒(méi)有音頻捕捉設(shè)備");
- return false;
- }
- // 用指定的捕捉設(shè)備創(chuàng)建Capture對(duì)象
- try
- {
- mCapDev = new Capture(deviceGuid);
- }
- catch (DirectXException e)
- {
- MessageBox.Show(e.ToString());
- return false;
- }
- return true;
- }
- /**//// < summary>
- /// 創(chuàng)建錄音格式,此處使用16bit,16KHz,Mono的錄音格式
- /// < /summary>
- /// < returns>WaveFormat結(jié)構(gòu)體< /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>
- /// 創(chuàng)建錄音使用的緩沖區(qū)
- /// < /summary>
- private void CreateCaptureBuffer()
- {
- // 緩沖區(qū)的描述對(duì)象
- CaptureBufferDescription bufferdescription = new CaptureBufferDescription();
- if (null != mNotify)
- {
- mNotify.Dispose();
- mNotify = null;
- }
- if (null != mRecBuffer)
- {
- mRecBuffer.Dispose();
- mRecBuffer = null;
- }
- // 設(shè)定通知的大小,默認(rèn)為1s鐘
- mNotifySize = (1024 > mWavFormat.AverageBytesPerSecond / 8) ? 1024 : (mWavFormat.AverageBytesPerSecond / 8);
- mNotifySize -= mNotifySize % mWavFormat.BlockAlign;
- // 設(shè)定緩沖區(qū)大小
- mBufferSize = mNotifySize * cNotifyNum;
- // 創(chuàng)建緩沖區(qū)描述
- bufferdescription.BufferBytes = mBufferSize;
- bufferdescription.Format = mWavFormat; // 錄音格式
- // 創(chuàng)建緩沖區(qū)
- mRecBuffer = new CaptureBuffer(bufferdescription, mCapDev);
- mNextCaptureOffset = 0;
- }
- /**//// < summary>
- /// 初始化通知事件,將原緩沖區(qū)分成16個(gè)緩沖隊(duì)列,在每個(gè)緩沖隊(duì)列的結(jié)束點(diǎn)設(shè)定通知點(diǎn).
- /// < /summary>
- /// < returns>是否成功< /returns>
- private bool InitNotifications()
- {
- if (null == mRecBuffer)
- {
- MessageBox.Show("未創(chuàng)建錄音緩沖區(qū)");
- return false;
- }
- // 創(chuàng)建一個(gè)通知事件,當(dāng)緩沖隊(duì)列滿了就激發(fā)該事件.
- mNotificationEvent = new AutoResetEvent(false);
- // 創(chuàng)建一個(gè)線程管理緩沖區(qū)事件
- if (null == mNotifyThread)
- {
- mNotifyThread = new Thread(new ThreadStart(WaitThread));
- mNotifyThread.Start();
- }
- // 設(shè)定通知的位置
- 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>
- /// 將錄制的數(shù)據(jù)寫(xiě)入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;
- // 對(duì)齊緩沖區(qū)邊界,實(shí)際上由于開(kāi)始設(shè)定完整,這個(gè)操作是多余的.
- LockSize -= (LockSize % mNotifySize);
- if (0 == LockSize)
- return;
- // 讀取緩沖區(qū)內(nèi)的數(shù)據(jù)
- CaptureData = (byte[])mRecBuffer.Read(mNextCaptureOffset, typeof(byte), LockFlag.None, LockSize);
- // 寫(xiě)入Wav文件
- mWriter.Write(CaptureData, 0, CaptureData.Length);
- // 更新已經(jīng)錄制的數(shù)據(jù)長(zhǎng)度.
- mSampleCount += CaptureData.Length;
- // 移動(dòng)錄制數(shù)據(jù)的起始點(diǎn),通知消息只負(fù)責(zé)指示產(chǎn)生消息的位置,并不記錄上次錄制的位置
- mNextCaptureOffset += CaptureData.Length;
- mNextCaptureOffset %= mBufferSize; // Circular buffer
- }
- /**//// < summary>
- /// 接收緩沖區(qū)滿消息的處理線程
- /// < /summary>
- private void WaitThread()
- {
- while (true)
- {
- // 等待緩沖區(qū)的通知消息
- mNotificationEvent.WaitOne(Timeout.Infinite, true);
- // 錄制數(shù)據(jù)
- RecordCapturedData();
- }
- }
- /**//// < summary>
- /// 創(chuàng)建保存的波形文件,并寫(xiě)入必要的文件頭.
- /// < /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.
- // 一個(gè)樣本點(diǎn)的字節(jié)數(shù)目
- 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);
- // 數(shù)據(jù)塊
- mWriter.Write(ChunkData);
- mWriter.Write((int)0); // The sample length will be written in later.
- }
- }
- }
四、DirectSound錄音外部窗體調(diào)用方式
聲明部分:
- private SoundRecord recorder = null; // 錄音
窗體構(gòu)造函數(shù):
- recorder = new SoundRecord();
啟動(dòng)錄音按鈕:
- private void btnStart_Click(object sender, System.EventArgs e)
- {
- //
- // 錄音設(shè)置
- //
- string wavfile = null;
- wavfile = “test.wav”;
- recorder.SetFileName(wavfile);
- recorder.RecStart();
- }
中止錄音按鈕:
- private void btnStop_Click(object sender, System.EventArgs e)
- {
- recorder.RecStop();
- recorder = null;
- }
五、需要添加的外部引用文件
在系統(tǒng)的System32目錄下添加以下兩個(gè)引用文件,如果沒(méi)有,在DirectX的開(kāi)發(fā)包內(nèi)可以找到。
Microsoft.DirectX.dll
Microsoft.DirectX.DirectSound.dll
【編輯推薦】