DirectX编程:C#中利用Socket实现网络语音通信[初级版本]

#C 即时语音的实现问题 一篇关于即时语音实现的问题 阅读详情

 现在时下的VOIP软件很多,比较有名的就是Skype,还有其它诸如UUcall、快门等等。它们提供的功能除了网络上的语音通话外,还可以与固定电话、手机等通话。在本篇中主要介绍利用C#实现语音通信的基本方法。但是目前只实现了网络上语音传输的基本功能,而且比较粗糙,没有采用什么算法来优化,所以大家千万不要期望过高。我写这篇的目的除了记录自己的经历之外,更希望有高手能给出改进的意见或算法。

     开发平台:.NET Framework 2.0 ,VS 2005,Windows XP,DirectX SDK(June 2008)下载页面 。
     开发语言:C#。

     测试环境:Windows XP 、.net framework 2.0、普通局域网。
     测试结果:在多台安装了windows XP系统且配置不同的电脑上测试,均能正常运行。可以进行语音对话,但是有明显的杂音,沿时低。

     限于篇幅,在本文中会详细介绍本人认为比较关健的问题,其它部分只做大概介绍,为了便于大家理解,可以先阅读:
     1.DirectX编程:[初级]C# 中利用 DirectSound 录音
     2.C# Socket编程笔记

     在本文中打算按照以下顺序介绍:
     1.项目结果预览与说明
     2.实现方法概要
     3.语言采集
     4.语音传输
     5.语音播放

     项目结果预览与说明

      界面如下:
                   

      说明:界面很简单,只提供了一个选择或输入对方IP的功能,当选择合适局域网内IP之后,单击确定便激活了语音聊天的按钮。如果你想进行语音聊天就可以开始聊天了,聊天端口采用8000。本软件只适用于局域网内用户的聊天,另外因为没有增加用户认证的功能,所以只有在双方都启动了这款软件才能进行通信。如果只想在单机上测试,那只需要选择本机的IP便可。由于囧于技术水平,尝试N次之后,任不知如何才能正确地实现语音效果(如回声消除、降噪等)来保障音质,因此在单机测试会有回声干扰,嚣叫声比较严重,希望高手解囊。

      实现方法概要

      要想实现语音聊天,有几个步骤是必须的(就是我不说,相信你应该也能想得到一些):
      a 语音采集:采集的作用就是从你的麦克风中获取数据,我采用DirectSound类来实现这个技术。参考:C# 中利用 DirectSound 录音
   (b 语音编码:利用语音编码算法对采集到的话音进行压缩编码,进行编码的目的是为了减少网络带宽的压力。)
      c 语音传输:将采集到的声音传输到网络上的其它主机,我采用Socket UDP方式来实现。参考:C# Socket编程笔记
   (d 语音解码:如果所传输的语音进行过压缩编码,则必须对语音进行解码,否则无法得到原始语音数据。)
      e 语音播放:当对方通过网络传输到本机时(,如果需要解码则先执行d),进行实时播放。

      上面红色标记的步骤,可以省略。在本软件中,我并未采用这两个步骤,因为当我采用了这两个步骤后,发现语音时延异常的严重。我采用的编解码算法是G.729,利用的是g729.dll库文件,压缩效果不错,但是时延比较严重,可能是自己哪里没有设置好。如果有朋友使用过该算法,且时延低的,希望不吝赐教。

      接下来,重点介绍语音采集、语音传输、语音播放的实现。

      语音采集
      由于所实现的方法与录音方法一致,因此不会着墨过多,如果你不能很好的理解,请先参考:C# 中利用 DirectSound 录音

      与录音不同的是,录音我们需要建立一个WAVE文件来存储这些采集到的数据,而在语音聊天中,则不需要存储,当采集到一些数据后,就立刻发送出去,因此也不需要开辟很大的空间来存放PCM数据。
      我们先来回顾下采集的基本步骤:
      1. 设置PCM格式,设置相关的参数,如:采样频率、量化位数等。
      2. 建立采集用的设备对象,建立采集用的缓冲区对象。
      3. 设置缓冲区通知,设置通知被触发后的事件。通知是用于当缓冲区的读指针达到某预设位置时触发通知事件,提醒我们可以对某部分的数据进行传送了。
      4. 开始采集声音。
      5. 当通知被触发后,建立一个新的线程来处理数据传送的事件。(建立一个新的线程,就是为了防止采集过程被中断)。

主要代码

      上述代码可以很好的采集到声音数据,几乎与原始声音一致。如果你已经可以实现录音,那么以上对你来说应该并不陌生。

      语音传输
      这部分并不是很难,如果你熟悉socket编程,那么就可以PASS这一部分了,与以往传输不同的只是现在传输的是语音而已。如果你没接触过socket,那可以瞧瞧C# Socket编程笔记。

      感觉这部分叫“语音传输”并不是很恰当,因为其实真正用于传输的语句只有一句。除了语音传输之外,我们还需要对网络进行监听,从而能捕获对方发送给自己的语音信息。但是,也不知道叫什么好,就估且这么叫着吧。在这一部分,我主要讲下大致流程。
      1. 建立socket对象,在实例化这个对象的时候有一个参数是设置使用的协议,在本软件中,我采用的是UDP。
      为什么要采用UDP?建立TCP能不能传送语音,答案肯定是能的。在本软件中,我考虑的主要是语音延时问题, 采用TCP在建立连接和维护连接中对时间和系统资源的开销较大,因此会有明显的时延发生,严重影响了实时性。另外,因为UDP是无连接的,这使得采用UDP可以支持日后功能上的扩展(如:组播)。
      2. 绑定本机的IP和端口,因为一个主机可能会有不止一个IP地址,如回发地址:127.0.0.1 和局域网地址:192.168.#.#。为了增加可用性,我这里选择绑定到任何本机可用的IP地址(IPAddress.Any),而端口我们约定默认为8000。
      3. 启动监听线程,来监听网络。我采用异步的方式,以便获得更好的系统响应度。

主要代码


      4. 数据的发送因为只有一句话,所以我直接放在上一部分的语音采集中了。

Client.SendTo(capturedata, epServer); // 传送语音

 

      语音播放
      最麻烦的就是这部分了,而且感觉现在的实现方法仍然需要改进才好。

      当声音传输到本机后,该怎么样才能让这些数据经过音响设备放出声音来呢?因为声音播放是从缓冲区中获取声音数据的因此我们必须先将获取到的数据写入缓冲区,然后再调用相应的方法来播放。看起来似乎不复杂,可是实现起来远没有这么简单。
      我遇到的问题:
      大家可以看下语音采集部分,我是在每次通知后进行语音采集然后就将采集到的语音发送到网络上,如果运行正常的话,这一部分数据实际播放长度远小于1秒。也就是说对方每次接收到的语音长度为毫秒级。而且如果网络质量可以的话,那么连续两次接收到数据的时间间隔也是相当小的。这样就产生问题了,如果我在接收到第一次数据后,将它写入缓冲区,然后调用相应的播放方法,由于语音长度实际很短,因此几乎听不到什么效果,而且可能发生当第一次缓冲区中的数据还没播放完,就已经被第二次的数据覆盖,导致声音混乱。经测试,此种方法无法达到声音实时效果。期间我也曾修改过数据发送部分,希望当语音长度达到某一长度时在发送,可是问题依旧,看样子重要的是在接收端进行相应处理。
      直接缓冲播放的方法不行,那就换~~
     上网搜,可惜的是这方面的资料实在有限,C#的就更少了。参考一些文献,大家提到利用在缓冲区设置两个指针,一个播放指针,一个写指针(写指针用于表示当前从网络上接收到的数据从写指针所指示位置开始往下写,播放指针则表示当前所播放的数据末尾)。当播放指针达到某个位置时就播放某一部分数据,而不影响将被写入的缓冲区部分,这样就可以很好的解决数据覆盖的问题。除此之外,还要将缓冲区设置为循环缓冲区,也就是头尾相接,当到达尾部时,自己从部开始,此时将覆盖头部数据。
      看了这些,你是不是感觉很眼熟?是不是和语音采集很类似?是的,我们在捕捉缓冲区中就是这样设置的,我们利用通知来设置触发事件。不同的是我们接收语音用的缓冲区并不是捕捉缓冲区,MS为捕捉单独设置了一个捕捉缓冲区。我们利用的是另一个缓冲区,辅助缓冲区(SecondaryBuffer)。后来发现该缓冲区也有类似的通知,这意味什么?我当时很兴奋,可是~~相当郁闷的是,我不管怎么设置通知,编译时都会报错,到外询求答案,均无果。在 MS 相关网站上咨询后,有一位叫jwatte的答案,让我又高兴又失望:

      原话如下:

      Notify is broken in DirectSound, has been for a long time, and probably will never be fixed.

      The only way to know when you need to play the next piece of data is to check the play pointer each time through your main loop, and then lock the buffer and fill in whatever part has been played out.

      Also, DirectSound is now in "maintenance" mode, and won't be further developed by Microsoft. Instead, for game applications, they recommend you use XAudio2 to play sound.

      简单意思就是:Notify出问题已经很长时间了,而且MS可能永远都不会去修复这个问题。而且他也为播放声音提供了些建议,这些建议与上面所讲的基本一致。
      至于这个答案是否正确,因为无从考证,就不再讨论了。如果哪位高手曾经实现过,希望赐教。
      既然目前无法正常使用,就只能来手动写了。这个方法名就是:GetVoiceData()。

      思路如下:
      ·利用MemoryStream来代表这个接收缓冲区。
      ·设置两个表示指针位置的字段:
         private int intPosWrite = 0;//内存流中写指针位移
         private int intPosPlay = 0;//内存流中播放指针位移

      ·当接收到数据后,则移动写指针,移动的长度为接收到的数据长度。
      ·利用一个字段表示通知大小:private int intNotifySize = 5000;
      ·当写指针的位置达到通知大小,则执行播放操作,然后移动播放指针到刚才的通知的位置。
      ·如果当前写指针的位移与将要写入到缓冲区的数据大小相加后超过缓冲容量的,则进行摩尔运算,实现循环的效果。


        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;//更新写指针位置。写指针指示下一个开始写入的位置而不是上一次结束的位置,因此不用减一
            }
        }

      
       这样,基本上就可以实现语音聊天了。可是这样的效果还只能是初步的,而且由于回声的原因,相当影响音质,还可能产生嚣叫,为了解决这个问题,我本打算采用MS提供的AEC算法,可是由于不知道如何实现,一直无法得到效果,因此这也是比较遗憾的地方。
      

基于C#WinForm的语音通信系统设计 本文介绍了一种基于C#WinForm的语音通信系统设计,该系统可以实现用户之间的语音通话功能。通过该系统,用户可以方便地进行语音通信交流,并提供了简单而直观的界面。本文还附带了完整的源代码,读者可以根据自己的需求进行参考和二次开发。该系统可以实现用户之间的语音通话功能,并提供简单而直观的界面,使用户可以方便地进行语音通信交流。本文将详细描述系统的设计思路、功能实现以及附带完整的源代码。以上是对《毕业论文基于C#WinForm的语音通信系统设计.doc》标题内容修改后的文章。 阅读详情

相关推荐

C#基于UDP实现的P2P语音聊天工具(一个基于c#的网络通讯框架 使用UDP能够发送发数据 分包发送 )

要想发送语音信息,首先得获取语音,这里有几种方法,一种是使用DirectX的DirectXsound来录音,我为了简便使用一个开源的插件NAudio来实现语音录取。 首先在项目中引用NAudio.dll组件,具体的实现下载代码(一个基于c#的网络通讯框架 使用UDP能够发送发数据 分包发送 )

【愚公系列】2023年08月 .NET/C#知识点-即时语音的通信解析

即时语音是指通过网络或互联网实时通信的语音通话服务。通常使用VoIP(Voice over Internet Protocol)技术,即通过将语音转换为数字信号,通过互联网传输,接收方再将数字信号转换回语音。这种通信形式可以在任何具有互联网连接的设备上进行,如计算机、智能手机、平板电脑等。即时语音通常是一种免费或低成本的通信方式,已经成为人们经常使用的通信方式之一。即时语音通话广泛应用于各种场合和领域,包括但不限于:社交网络:如Facebook、微信、WhatsApp等;

愚公智库 1万+

国外开源视频监控iSpy源代码20180905

iSpy是国外的一款开源免费的视频监控,功能强大。支持电脑客户端,web端,手机端本地或者远程访问。 主要核心功能: 1.把摄像头通过代码嵌入到你自己的网站; 2.支持不同品牌摄像头,电脑usb摄像头,通过onvif协议,http,rtsp等方式添加摄像头; 3.实时监看,录像回放,电视墙功能; 4.支持云存储,ftp上传,上传录像到YouTube, Dropbox以及Google Drive等; 5.各种不同的录像方式,定时,手动,报警录像等; 6.支持Email, SMS等; 7.支持插件扩展,车牌识别(ALPR),人脸探测(Face Detection)等; 8.提供api,支持二次开发。 9.支持多语言(中文翻译的有点差)。 iSpy :http://www.ispyconnect.com/

C#中利用Socket实现网络语音通信[初级版本]

现在时下的VOIP软件很多,比较有名的就是Skype,还有其它诸如UUcall、快门等等。它们提供的功能除了网络上的语音通话外,还可以与固定电话、手机等通话。在本篇中主要介绍利用C#实现语音通信的基本方法。但是目前只实现了网络上语音传输的基本功能,而且比较粗糙,没有采用什么算法来优化,所以大家千万不要期望过高。我写这篇的目的除了记录自己的经历之外,更希望有高手能给出改进的意见或算法。    

菜鸟之路 1万+

MachineLearning-Library-开源

机器学习库,执行多种聚类算法(k-means、增量 k-means、DBSCAN、增量 DBSCAN、有丝分裂、增量有丝分裂、均值漂移和 SHC)并执行几种半监督机器学习方法(自学习和协同训练) )。 -------------------------------------------------- ------------------------- 要运行库,只需双击 jar 文件。 此外,您可以使用以下命令行: Java -Xms1500m -jar "ML Library.jar" 以确保 java 虚拟机有足够的空间来运行。 您可以在代码/测试下找到示例测试文件。 聚类部分的测试文件位于 code/Test/InputFile 下,其中包含两个变色龙数据集:http://glaros.dtc.umn.edu/gkhome/cluto/cluto/download 另外,您可以找到用于自学的示例测试文件并在其对应的文件夹下进行协同训练。 主要是基因/miRNAs的表达,下载地址:http://0-www.ncbi.nlm.nih.gov.elis.tmu.edu.tw/

C#语音和视频聊天全源代码.rar

以上是采用C#开发的,个人存放了很久,拿出来与喜欢C#的编程爱好者们分享。

DirectX编程:C#中利用Socket实现网络语音通信

<br />现在时下的VOIP软件很多,比较有名的就是Skype,还有其它诸如UUcall、快门等等。它们提供的功能除了网络上的语音通话外,还可以与固定电话、手机等通话。在本篇中主要介绍利用C#实现语音通信的基本方法。但是目前只实现了网络上语音传输的基本功能,而且比较粗糙,没有采用什么算法来优化,所以大家千万不要期望过高。我写这篇的目的除了记录自己的经历之外,更希望有高手能给出改进的意见或算法。   开发平台:.NET Framework 2.0 ,VS 2005,Windows XP,DirectX

黑色幽默 1380

调用avicap2视频和语音库

调用avicap2视频和语音库,labview 写的,

DirectX编程:C#中利用Socket实现网络语音通信

DirectX编程:C#中利用Socket实现网络语音通信 2010年06月15日   现在时下的VOIP软件很多,比较有名的就是Skype,还有其它诸如UUcall、快门等等。它们提供的功能除了网络上的语音通话外,还可以与固定电话、手机等通话。在本篇中主要介绍利用C#实现语音通信的基本方法。但是目前只实现了网络上语音传输的基本功能,而且比较粗糙,没有采用什么算法来优化,所以大家千万不要...

rbt359um 604

C#视频聊天_视频聊天_

C#视频聊天源工程;大家一起学习交流,分享进步

FPGA,关于安装使用libero IDE V9.2 及其gold floating license配置

3、license安装根据,下面的提示进行,跟随setting up the license server on a host教程操作。1、从官网下载Libero IDE v9.2(如下图中的Libero IDE v9.2 software)4、下载完成license解压包后还需要下载 Daemons license ,地址如下。(2)第一行并不是在内填写,而是直接用您电脑名替换掉(1)修改license.bat前四行是,其务必保持和案例位置相同(避免出错)............

JasonK的博客 4296
上一篇: c# GDI+简单绘图(四)
下一篇: C#仿QQ皮肤----基窗体FormBase与基用户控件FormBase1的实现
csdsuper
博客等级 码龄22年 35粉丝 41原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值