分享

C#音频采集

 ylw527 2010-12-13
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4.   
  5. using System.IO;  
  6. using System.Threading;  
  7. using Microsoft.DirectX;  
  8. using Microsoft.DirectX.DirectSound;  
  9. using System.Net.Sockets;  
  10. using System.Net;  
  11.   
  12. namespace MatureVoice  
  13. {  
  14.     class VoiceCapture  
  15.     {  
  16.         private MemoryStream memstream;//内存流  
  17.         private SecondaryBuffer secBuffer;//辅助缓冲区  
  18.         private int iNotifySize = 0;//通知大小  
  19.         private int iBufferSize = 0;//捕捉缓冲区大小   
  20.         private CaptureBuffer capturebuffer;//捕捉缓冲区对象  
  21.         private AutoResetEvent notifyEvent;//通知事件  
  22.         private Thread notifyThread;//通知线程  
  23.         private int iNotifyNum=0;//通知个数  
  24.         private Notify myNotify;//通知对象  
  25.         private Capture capture;//捕捉设备对象  
  26.         private Device PlayDev;//播放设备对象  
  27.         private BufferDescription buffDiscript;  
  28.         private Socket Client;  
  29.         private EndPoint epServer;  
  30.         private int iBufferOffset=0;//捕捉缓冲区位移  
  31.         private IntPtr intptr;//窗口句柄  
  32.   
  33.         public IntPtr Intptr  
  34.         {  
  35.             set  
  36.             {  
  37.                 intptr = value;  
  38.             }  
  39.         }  
  40.   
  41.         public int NotifySize  
  42.         {  
  43.             set  
  44.             {  
  45.                 iNotifySize = value;  
  46.             }  
  47.   
  48.         }  
  49.   
  50.         public int NotifyNum  
  51.         {  
  52.             set  
  53.             {  
  54.                 iNotifyNum = value;  
  55.             }  
  56.         }  
  57.   
  58.         public Socket LocalSocket  
  59.         {  
  60.             set  
  61.             {  
  62.                 Client = value;  
  63.             }  
  64.         }  
  65.   
  66.         public EndPoint RemoteEndPoint  
  67.         {  
  68.             set  
  69.             {  
  70.                 epServer = value;  
  71.             }  
  72.         }  
  73.   
  74.   
  75.         /// <summary>  
  76.         /// 初始化相关操作  
  77.         /// </summary>  
  78.         public void InitVoice()  
  79.         {//初始化声音相关设置:(1)捕捉缓冲区(2)播放缓冲区  
  80.             if (!CreateCaputerDevice())  
  81.             {  
  82.                 throw new Exception();  
  83.             }//建立设备对象  
  84.             CreateCaptureBuffer();//建立缓冲区对象  
  85.             CreateNotification();//设置通知及事件  
  86.             //======(2)==============  
  87.             if (!CreatePlayDevice())  
  88.             {  
  89.                 throw new Exception();  
  90.             }  
  91.             CreateSecondaryBuffer();  
  92.         }  
  93.   
  94.         /// <summary>  
  95.         /// 启动声音采集  
  96.         /// </summary>  
  97.         public void StartVoiceCapture()  
  98.         {  
  99.             capturebuffer.Start(true);//true表示设置缓冲区为循环方式,开始捕捉  
  100.         }  
  101.   
  102.         /// <summary>  
  103.         /// 创建用于播放的音频设备对象  
  104.         /// </summary>  
  105.         /// <returns>创建成功返回true</returns>  
  106.         private bool CreatePlayDevice()  
  107.         {  
  108.             DevicesCollection dc = new DevicesCollection();  
  109.             Guid g;  
  110.             if (dc.Count > 0)  
  111.             {  
  112.                 g = dc[0].DriverGuid;  
  113.             }  
  114.             else  
  115.             { return false; }  
  116.             PlayDev = new Device(g);  
  117.             PlayDev.SetCooperativeLevel(intptr, CooperativeLevel.Normal);  
  118.             return true;  
  119.         }  
  120.   
  121.         /// <summary>  
  122.         /// 创建辅助缓冲区  
  123.         /// </summary>  
  124.         private void CreateSecondaryBuffer()  
  125.         {  
  126.             buffDiscript = new BufferDescription();  
  127.             WaveFormat mWavFormat = SetWaveFormat();  
  128.             buffDiscript.Format = mWavFormat;  
  129.             iNotifySize = mWavFormat.AverageBytesPerSecond / iNotifyNum;//设置通知大小  
  130.             iBufferSize = iNotifyNum * iNotifySize;  
  131.             buffDiscript.BufferBytes = iBufferSize;  
  132.             buffDiscript.ControlPan = true;  
  133.             buffDiscript.ControlFrequency = true;  
  134.             buffDiscript.ControlVolume = true;  
  135.             buffDiscript.GlobalFocus = true;  
  136.             secBuffer = new SecondaryBuffer(buffDiscript, PlayDev);  
  137.             byte[] bytMemory = new byte[100000];  
  138.             memstream = new MemoryStream(bytMemory, 0, 100000, truetrue);  
  139.             //g729 = new G729();  
  140.             //g729.InitalizeEncode();  
  141.             //g729.InitalizeDecode();  
  142.         }  
  143.   
  144.         /// <summary>  
  145.         /// 创建捕捉设备对象  
  146.         /// </summary>  
  147.         /// <returns>如果创建成功返回true</returns>  
  148.         private bool CreateCaputerDevice()  
  149.         {  
  150.             //首先要玫举可用的捕捉设备  
  151.             CaptureDevicesCollection capturedev = new CaptureDevicesCollection();  
  152.             Guid devguid;  
  153.             if (capturedev.Count > 0)  
  154.             {  
  155.                 devguid = capturedev[0].DriverGuid;  
  156.             }  
  157.             else  
  158.             {  
  159.                 System.Windows.Forms.MessageBox.Show("当前没有可用于音频捕捉的设备""系统提示");  
  160.                 return false;  
  161.             }  
  162.             //利用设备GUID来建立一个捕捉设备对象  
  163.             capture = new Capture(devguid);  
  164.             return true;  
  165.         }  
  166.   
  167.         /// <summary>  
  168.         /// 创建捕捉缓冲区对象  
  169.         /// </summary>  
  170.         private void CreateCaptureBuffer()  
  171.         {  
  172.             //想要创建一个捕捉缓冲区必须要两个参数:缓冲区信息(描述这个缓冲区中的格式等),缓冲设备。  
  173.             WaveFormat mWavFormat = SetWaveFormat();  
  174.             CaptureBufferDescription bufferdescription = new CaptureBufferDescription();  
  175.             bufferdescription.Format = mWavFormat;//设置缓冲区要捕捉的数据格式  
  176.             iNotifySize = mWavFormat.AverageBytesPerSecond / iNotifyNum;//1秒的数据量/设置的通知数得到的每个通知大小小于0.2s的数据量,话音延迟小于200ms为优质话音  
  177.             iBufferSize = iNotifyNum * iNotifySize;  
  178.             bufferdescription.BufferBytes = iBufferSize;  
  179.             bufferdescription.ControlEffects = true;  
  180.             bufferdescription.WaveMapped = true;  
  181.             capturebuffer = new CaptureBuffer(bufferdescription, capture);//建立设备缓冲区对象  
  182.   
  183.         }  
  184.   
  185.         //设置通知  
  186.         private void CreateNotification()  
  187.         {  
  188.             BufferPositionNotify[] bpn = new BufferPositionNotify[iNotifyNum];//设置缓冲区通知个数  
  189.             //设置通知事件  
  190.             notifyEvent = new AutoResetEvent(false);  
  191.             notifyThread = new Thread(RecoData);//通知触发事件  
  192.             notifyThread.IsBackground = true;  
  193.             notifyThread.Start();  
  194.             for (int i = 0; i < iNotifyNum; i++)  
  195.             {  
  196.                 bpn[i].Offset = iNotifySize + i * iNotifySize - 1;//设置具体每个的位置  
  197.                 bpn[i].EventNotifyHandle = notifyEvent.Handle;  
  198.             }  
  199.             myNotify = new Notify(capturebuffer);  
  200.             myNotify.SetNotificationPositions(bpn);  
  201.   
  202.         }  
  203.   
  204.         //线程中的事件  
  205.         private void RecoData()  
  206.         {  
  207.             while (true)  
  208.             {  
  209.                 // 等待缓冲区的通知消息  
  210.                 notifyEvent.WaitOne(Timeout.Infinite, true);  
  211.                 // 录制数据  
  212.                 RecordCapturedData(Client,epServer);  
  213.             }  
  214.         }  
  215.   
  216.         //真正转移数据的事件,其实就是把数据传送到网络上去。  
  217.         private void RecordCapturedData(Socket Client,EndPoint epServer )  
  218.         {  
  219.             byte[] capturedata = null;  
  220.             int readpos = 0, capturepos = 0, locksize = 0;  
  221.             capturebuffer.GetCurrentPosition(out capturepos, out readpos);  
  222.             locksize = readpos - iBufferOffset;//这个大小就是我们可以安全读取的大小  
  223.             if (locksize == 0)  
  224.             {  
  225.                 return;  
  226.             }  
  227.             if (locksize < 0)  
  228.             {//因为我们是循环的使用缓冲区,所以有一种情况下为负:当文以载读指针回到第一个通知点,而Ibuffeoffset还在最后一个通知处  
  229.                 locksize += iBufferSize;  
  230.             }  
  231.             capturedata = (byte[])capturebuffer.Read(iBufferOffset, typeof(byte), LockFlag.FromWriteCursor, locksize);  
  232.             //capturedata = g729.Encode(capturedata);//语音编码  
  233.             try  
  234.             {  
  235.                 Client.SendTo(capturedata, epServer);//传送语音  
  236.             }  
  237.             catch  
  238.             {  
  239.                 throw new Exception();  
  240.             }  
  241.             iBufferOffset += capturedata.Length;  
  242.             iBufferOffset %= iBufferSize;//取模是因为缓冲区是循环的。  
  243.         }  
  244.   
  245.   
  246.         private int intPosWrite = 0;//内存流中写指针位移  
  247.         private int intPosPlay = 0;//内存流中播放指针位移  
  248.         private int intNotifySize = 5000;//设置通知大小  
  249.   
  250.         /// <summary>  
  251.         /// 从字节数组中获取音频数据,并进行播放  
  252.         /// </summary>  
  253.         /// <param name="intRecv">字节数组长度</param>  
  254.         /// <param name="bytRecv">包含音频数据的字节数组</param>  
  255.         public void GetVoiceData(int intRecv, byte[] bytRecv)  
  256.         {  
  257.             //intPosWrite指示最新的数据写好后的末尾。intPosPlay指示本次播放开始的位置。  
  258.             if (intPosWrite + intRecv <= memstream.Capacity)  
  259.             {//如果当前写指针所在的位移+将要写入到缓冲区的长度小于缓冲区总大小  
  260.                 if ((intPosWrite - intPosPlay >= 0 && intPosWrite - intPosPlay < intNotifySize) || (intPosWrite - intPosPlay < 0 && intPosWrite - intPosPlay + memstream.Capacity < intNotifySize))  
  261.                 {  
  262.                     memstream.Write(bytRecv, 0, intRecv);  
  263.                     intPosWrite += intRecv;  
  264.   
  265.                 }  
  266.                 else if (intPosWrite - intPosPlay >= 0)  
  267.                 {//先存储一定量的数据,当达到一定数据量时就播放声音。  
  268.                     buffDiscript.BufferBytes = intPosWrite - intPosPlay;//缓冲区大小为播放指针到写指针之间的距离。  
  269.                     SecondaryBuffer sec = new SecondaryBuffer(buffDiscript, PlayDev);//建立一个合适的缓冲区用于播放这段数据。  
  270.                     memstream.Position = intPosPlay;//先将memstream的指针定位到这一次播放开始的位置  
  271.                     sec.Write(0, memstream, intPosWrite - intPosPlay, LockFlag.FromWriteCursor);  
  272.                     sec.Play(0, BufferPlayFlags.Default);  
  273.                     memstream.Position = intPosWrite;//写完后重新将memstream的指针定位到将要写下去的位置。  
  274.                     intPosPlay = intPosWrite;  
  275.                 }  
  276.                 else if (intPosWrite - intPosPlay < 0)  
  277.                 {  
  278.                     buffDiscript.BufferBytes = intPosWrite - intPosPlay + memstream.Capacity;//缓冲区大小为播放指针到写指针之间的距离。  
  279.                     SecondaryBuffer sec = new SecondaryBuffer(buffDiscript, PlayDev);//建立一个合适的缓冲区用于播放这段数据。  
  280.                     memstream.Position = intPosPlay;  
  281.                     sec.Write(0, memstream, memstream.Capacity - intPosPlay, LockFlag.FromWriteCursor);  
  282.                     memstream.Position = 0;  
  283.                     sec.Write(memstream.Capacity - intPosPlay, memstream, intPosWrite, LockFlag.FromWriteCursor);  
  284.                     sec.Play(0, BufferPlayFlags.Default);  
  285.                     memstream.Position = intPosWrite;  
  286.                     intPosPlay = intPosWrite;  
  287.                 }  
  288.             }  
  289.             else  
  290.             {//当数据将要大于memstream可容纳的大小时  
  291.                 int irest = memstream.Capacity - intPosWrite;//memstream中剩下的可容纳的字节数。  
  292.                 memstream.Write(bytRecv, 0, irest);//先写完这个内存流。  
  293.                 memstream.Position = 0;//然后让新的数据从memstream的0位置开始记录  
  294.                 memstream.Write(bytRecv, irest, intRecv - irest);//覆盖旧的数据  
  295.                 intPosWrite = intRecv - irest;//更新写指针位置。写指针指示下一个开始写入的位置而不是上一次结束的位置,因此不用减一  
  296.             }  
  297.         }  
  298.   
  299.         /// <summary>  
  300.         /// 设置音频格式,如采样率等  
  301.         /// </summary>  
  302.         /// <returns>设置完成后的格式</returns>  
  303.         private WaveFormat SetWaveFormat()  
  304.         {  
  305.             WaveFormat format = new WaveFormat();  
  306.             format.FormatTag = WaveFormatTag.Pcm;//设置音频类型  
  307.             format.SamplesPerSecond = 11025;//采样率(单位:赫兹)典型值:11025、22050、44100Hz  
  308.             format.BitsPerSample = 16;//采样位数  
  309.             format.Channels = 1;//声道  
  310.             format.BlockAlign = (short)(format.Channels * (format.BitsPerSample / 8));//单位采样点的字节数  
  311.             format.AverageBytesPerSecond = format.BlockAlign * format.SamplesPerSecond;  
  312.   
  313.             return format;  
  314.             //按照以上采样规格,可知采样1秒钟的字节数为22050*2=44100B 约为 43K  
  315.         }  
  316.   
  317.         /// <summary>  
  318.         /// 停止语音采集  
  319.         /// </summary>  
  320.         public void Stop()  
  321.         {  
  322.             capturebuffer.Stop();  
  323.             if (notifyEvent != null)  
  324.             {  
  325.                 notifyEvent.Set();  
  326.             }  
  327.             if (notifyThread != null && notifyThread.IsAlive == true)  
  328.             {  
  329.                 notifyThread.Abort();  
  330.             }  
  331.         }  
  332.     }  
  333. }  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多