1. 程式人生 > >C#音訊採集 (筆記)

C#音訊採集 (筆記)

using System;  
using System.Collections.Generic;  
using System.Text;  
  
using System.IO;  
using System.Threading;  
using Microsoft.DirectX;  
using Microsoft.DirectX.DirectSound;  
using System.Net.Sockets;  
using System.Net;  
  
namespace MatureVoice  
{  
    class VoiceCapture  
    {  
        private MemoryStream memstream;//記憶體流  
        private SecondaryBuffer secBuffer;//輔助緩衝區  
        private int iNotifySize = 0;//通知大小  
        private int iBufferSize = 0;//捕捉緩衝區大小   
        private CaptureBuffer capturebuffer;//捕捉緩衝區物件  
        private AutoResetEvent notifyEvent;//通知事件  
        private Thread notifyThread;//通知執行緒  
        private int iNotifyNum=0;//通知個數  
        private Notify myNotify;//通知物件  
        private Capture capture;//捕捉裝置物件  
        private Device PlayDev;//播放裝置物件  
        private BufferDescription buffDiscript;  
        private Socket Client;  
        private EndPoint epServer;  
        private int iBufferOffset=0;//捕捉緩衝區位移  
        private IntPtr intptr;//視窗控制代碼  
  
        public IntPtr Intptr  
        {  
            set  
            {  
                intptr = value;  
            }  
        }  
  
        public int NotifySize  
        {  
            set  
            {  
                iNotifySize = value;  
            }  
  
        }  
  
        public int NotifyNum  
        {  
            set  
            {  
                iNotifyNum = value;  
            }  
        }  
  
        public Socket LocalSocket  
        {  
            set  
            {  
                Client = value;  
            }  
        }  
  
        public EndPoint RemoteEndPoint  
        {  
            set  
            {  
                epServer = value;  
            }  
        }  
  
  
        /// <summary>  
        /// 初始化相關操作  
        /// </summary>  
        public void InitVoice()  
        {//初始化聲音相關設定:(1)捕捉緩衝區(2)播放緩衝區  
            if (!CreateCaputerDevice())  
            {  
                throw new Exception();  
            }//建立裝置物件  
            CreateCaptureBuffer();//建立緩衝區物件  
            CreateNotification();//設定通知及事件  
            //======(2)==============  
            if (!CreatePlayDevice())  
            {  
                throw new Exception();  
            }  
            CreateSecondaryBuffer();  
        }  
  
        /// <summary>  
        /// 啟動聲音採集  
        /// </summary>  
        public void StartVoiceCapture()  
        {  
            capturebuffer.Start(true);//true表示設定緩衝區為迴圈方式,開始捕捉  
        }  
  
        /// <summary>  
        /// 建立用於播放的音訊裝置物件  
        /// </summary>  
        /// <returns>建立成功返回true</returns>  
        private bool CreatePlayDevice()  
        {  
            DevicesCollection dc = new DevicesCollection();  
            Guid g;  
            if (dc.Count > 0)  
            {  
                g = dc[0].DriverGuid;  
            }  
            else  
            { return false; }  
            PlayDev = new Device(g);  
            PlayDev.SetCooperativeLevel(intptr, CooperativeLevel.Normal);  
            return true;  
        }  
  
        /// <summary>  
        /// 建立輔助緩衝區  
        /// </summary>  
        private void CreateSecondaryBuffer()  
        {  
            buffDiscript = new BufferDescription();  
            WaveFormat mWavFormat = SetWaveFormat();  
            buffDiscript.Format = mWavFormat;  
            iNotifySize = mWavFormat.AverageBytesPerSecond / iNotifyNum;//設定通知大小  
            iBufferSize = iNotifyNum * iNotifySize;  
            buffDiscript.BufferBytes = iBufferSize;  
            buffDiscript.ControlPan = true;  
            buffDiscript.ControlFrequency = true;  
            buffDiscript.ControlVolume = true;  
            buffDiscript.GlobalFocus = true;  
            secBuffer = new SecondaryBuffer(buffDiscript, PlayDev);  
            byte[] bytMemory = new byte[100000];  
            memstream = new MemoryStream(bytMemory, 0, 100000, true, true);  
            //g729 = new G729();  
            //g729.InitalizeEncode();  
            //g729.InitalizeDecode();  
        }  
  
        /// <summary>  
        /// 建立捕捉裝置物件  
        /// </summary>  
        /// <returns>如果建立成功返回true</returns>  
        private bool CreateCaputerDevice()  
        {  
            //首先要玫舉可用的捕捉裝置  
            CaptureDevicesCollection capturedev = new CaptureDevicesCollection();  
            Guid devguid;  
            if (capturedev.Count > 0)  
            {  
                devguid = capturedev[0].DriverGuid;  
            }  
            else  
            {  
                System.Windows.Forms.MessageBox.Show("當前沒有可用於音訊捕捉的裝置", "系統提示");  
                return false;  
            }  
            //利用裝置GUID來建立一個捕捉裝置物件  
            capture = new Capture(devguid);  
            return true;  
        }  
  
        /// <summary>  
        /// 建立捕捉緩衝區物件  
        /// </summary>  
        private void CreateCaptureBuffer()  
        {  
            //想要建立一個捕捉緩衝區必須要兩個引數:緩衝區資訊(描述這個緩衝區中的格式等),緩衝裝置。  
            WaveFormat mWavFormat = SetWaveFormat();  
            CaptureBufferDescription bufferdescription = new CaptureBufferDescription();  
            bufferdescription.Format = mWavFormat;//設定緩衝區要捕捉的資料格式  
            iNotifySize = mWavFormat.AverageBytesPerSecond / iNotifyNum;//1秒的資料量/設定的通知數得到的每個通知大小小於0.2s的資料量,話音延遲小於200ms為優質話音  
            iBufferSize = iNotifyNum * iNotifySize;  
            bufferdescription.BufferBytes = iBufferSize;  
            bufferdescription.ControlEffects = true;  
            bufferdescription.WaveMapped = true;  
            capturebuffer = new CaptureBuffer(bufferdescription, capture);//建立裝置緩衝區物件  
  
        }  
  
        //設定通知  
        private void CreateNotification()  
        {  
            BufferPositionNotify[] bpn = new BufferPositionNotify[iNotifyNum];//設定緩衝區通知個數  
            //設定通知事件  
            notifyEvent = new AutoResetEvent(false);  
            notifyThread = new Thread(RecoData);//通知觸發事件  
            notifyThread.IsBackground = true;  
            notifyThread.Start();  
            for (int i = 0; i < iNotifyNum; i++)  
            {  
                bpn[i].Offset = iNotifySize + i * iNotifySize - 1;//設定具體每個的位置  
                bpn[i].EventNotifyHandle = notifyEvent.Handle;  
            }  
            myNotify = new Notify(capturebuffer);  
            myNotify.SetNotificationPositions(bpn);  
  
        }  
  
        //執行緒中的事件  
        private void RecoData()  
        {  
            while (true)  
            {  
                // 等待緩衝區的通知訊息  
                notifyEvent.WaitOne(Timeout.Infinite, true);  
                // 錄製資料  
                RecordCapturedData(Client,epServer);  
            }  
        }  
  
        //真正轉移資料的事件,其實就是把資料傳送到網路上去。  
        private void RecordCapturedData(Socket Client,EndPoint epServer )  
        {  
            byte[] capturedata = null;  
            int readpos = 0, capturepos = 0, locksize = 0;  
            capturebuffer.GetCurrentPosition(out capturepos, out readpos);  
            locksize = readpos - iBufferOffset;//這個大小就是我們可以安全讀取的大小  
            if (locksize == 0)  
            {  
                return;  
            }  
            if (locksize < 0)  
            {//因為我們是迴圈的使用緩衝區,所以有一種情況下為負:當文以載讀指標回到第一個通知點,而Ibuffeoffset還在最後一個通知處  
                locksize += iBufferSize;  
            }  
            capturedata = (byte[])capturebuffer.Read(iBufferOffset, typeof(byte), LockFlag.FromWriteCursor, locksize);  
            //capturedata = g729.Encode(capturedata);//語音編碼  
            try  
            {  
                Client.SendTo(capturedata, epServer);//傳送語音  
            }  
            catch  
            {  
                throw new Exception();  
            }  
            iBufferOffset += capturedata.Length;  
            iBufferOffset %= iBufferSize;//取模是因為緩衝區是迴圈的。  
        }  
  
  
        private int intPosWrite = 0;//記憶體流中寫指標位移  
        private int intPosPlay = 0;//記憶體流中播放指標位移  
        private int intNotifySize = 5000;//設定通知大小  
  
        /// <summary>  
        /// 從位元組陣列中獲取音訊資料,並進行播放  
        /// </summary>  
        /// <param name="intRecv">位元組陣列長度</param>  
        /// <param name="bytRecv">包含音訊資料的位元組陣列</param>  
        public void GetVoiceData(int intRecv, byte[] bytRecv)  
        {  
            //intPosWrite指示最新的資料寫好後的末尾。intPosPlay指示本次播放開始的位置。  
            if (intPosWrite + intRecv <= memstream.Capacity)  
            {//如果當前寫指標所在的位移+將要寫入到緩衝區的長度小於緩衝區總大小  
                if ((intPosWrite - intPosPlay >= 0 && intPosWrite - intPosPlay < intNotifySize) || (intPosWrite - intPosPlay < 0 && intPosWrite - intPosPlay + memstream.Capacity < intNotifySize))  
                {  
                    memstream.Write(bytRecv, 0, intRecv);  
                    intPosWrite += intRecv;  
  
                }  
                else if (intPosWrite - intPosPlay >= 0)  
                {//先儲存一定量的資料,當達到一定資料量時就播放聲音。  
                    buffDiscript.BufferBytes = intPosWrite - intPosPlay;//緩衝區大小為播放指標到寫指標之間的距離。  
                    SecondaryBuffer sec = new SecondaryBuffer(buffDiscript, PlayDev);//建立一個合適的緩衝區用於播放這段資料。  
                    memstream.Position = intPosPlay;//先將memstream的指標定位到這一次播放開始的位置  
                    sec.Write(0, memstream, intPosWrite - intPosPlay, LockFlag.FromWriteCursor);  
                    sec.Play(0, BufferPlayFlags.Default);  
                    memstream.Position = intPosWrite;//寫完後重新將memstream的指標定位到將要寫下去的位置。  
                    intPosPlay = intPosWrite;  
                }  
                else if (intPosWrite - intPosPlay < 0)  
                {  
                    buffDiscript.BufferBytes = intPosWrite - intPosPlay + memstream.Capacity;//緩衝區大小為播放指標到寫指標之間的距離。  
                    SecondaryBuffer sec = new SecondaryBuffer(buffDiscript, PlayDev);//建立一個合適的緩衝區用於播放這段資料。  
                    memstream.Position = intPosPlay;  
                    sec.Write(0, memstream, memstream.Capacity - intPosPlay, LockFlag.FromWriteCursor);  
                    memstream.Position = 0;  
                    sec.Write(memstream.Capacity - intPosPlay, memstream, intPosWrite, LockFlag.FromWriteCursor);  
                    sec.Play(0, BufferPlayFlags.Default);  
                    memstream.Position = intPosWrite;  
                    intPosPlay = intPosWrite;  
                }  
            }  
            else  
            {//當資料將要大於memstream可容納的大小時  
                int irest = memstream.Capacity - intPosWrite;//memstream中剩下的可容納的位元組數。  
                memstream.Write(bytRecv, 0, irest);//先寫完這個記憶體流。  
                memstream.Position = 0;//然後讓新的資料從memstream的0位置開始記錄  
                memstream.Write(bytRecv, irest, intRecv - irest);//覆蓋舊的資料  
                intPosWrite = intRecv - irest;//更新寫指標位置。寫指標指示下一個開始寫入的位置而不是上一次結束的位置,因此不用減一  
            }  
        }  
  
        /// <summary>  
        /// 設定音訊格式,如取樣率等  
        /// </summary>  
        /// <returns>設定完成後的格式</returns>  
        private WaveFormat SetWaveFormat()  
        {  
            WaveFormat format = new WaveFormat();  
            format.FormatTag = WaveFormatTag.Pcm;//設定音訊型別  
            format.SamplesPerSecond = 11025;//取樣率(單位:赫茲)典型值:11025、22050、44100Hz  
            format.BitsPerSample = 16;//取樣位數  
            format.Channels = 1;//聲道  
            format.BlockAlign = (short)(format.Channels * (format.BitsPerSample / 8));//單位取樣點的位元組數  
            format.AverageBytesPerSecond = format.BlockAlign * format.SamplesPerSecond;  
  
            return format;  
            //按照以上取樣規格,可知取樣1秒鐘的位元組數為22050*2=44100B 約為 43K  
        }  
  
        /// <summary>  
        /// 停止語音採集  
        /// </summary>  
        public void Stop()  
        {  
            capturebuffer.Stop();  
            if (notifyEvent != null)  
            {  
                notifyEvent.Set();  
            }  
            if (notifyThread != null && notifyThread.IsAlive == true)  
            {  
                notifyThread.Abort();  
            }  
        }  
    }