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

简介:

    [声明:本篇来源:http://www.cnblogs.com/stg609/archive/2008/11/19/1334544.html 作者:stg609]

      现在时下的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;
      ·当写指针的位置达到通知大小,则执行播放操作,然后移动播放指针到刚才的通知的位置。
      ·如果当前写指针的位移与将要写入到缓冲区的数据大小相加后超过缓冲容量的,则进行摩尔运算,实现循环的效果。

GetVoiceData()
      
       这样,基本上就可以实现语音聊天了。可是这样的效果还只能是初步的,而且由于回声的原因,相当影响音质,还可能产生嚣叫,为了解决这个问题,我本打算采用MS提供的AEC算法,可是由于不知道如何实现,一直无法得到效果,因此这也是比较遗憾的地方。
      
      可执行文件(注:要在安装了 .net framework 2.0 的平台上运行): MatureVoiceEXE.rar

      源文件:MatureVoice.rar









本文转自stg609博客园博客,原文链接:http://www.cnblogs.com/stg609/archive/2008/11/19/1334544.html,如需转载请自行联系原作者


目录
相关文章
|
28天前
|
C# 开发者
C# 一分钟浅谈:Socket 编程基础
【10月更文挑战第7天】本文介绍了Socket编程的基础知识、基本操作及常见问题,通过C#代码示例详细展示了服务器端和客户端的Socket通信过程,包括创建、绑定、监听、连接、数据收发及关闭等步骤,帮助开发者掌握Socket编程的核心技术和注意事项。
86 3
C# 一分钟浅谈:Socket 编程基础
|
3天前
|
监控 安全
公司上网监控:Mercury 在网络监控高级逻辑编程中的应用
在数字化办公环境中,公司对员工上网行为的监控至关重要。Mercury 作为一种强大的编程工具,展示了在公司上网监控领域的独特优势。本文介绍了使用 Mercury 实现网络连接监听、数据解析和日志记录的功能,帮助公司确保信息安全和工作效率。
69 51
|
16天前
|
消息中间件 网络协议 C#
C#使用Socket实现分布式事件总线,不依赖第三方MQ
`CodeWF.EventBus.Socket` 是一个轻量级的、基于Socket的分布式事件总线系统,旨在简化分布式架构中的事件通信。它允许进程之间通过发布/订阅模式进行通信,无需依赖外部消息队列服务。
C#使用Socket实现分布式事件总线,不依赖第三方MQ
|
1月前
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程
|
3月前
|
安全 Java 应用服务中间件
网络安全的护城河:漏洞防御与加密技术深入浅出Java并发编程
【8月更文挑战第31天】在数字世界的棋盘上,每一次点击都可能是一步棋。网络安全的战场无声却激烈,漏洞如同裂缝中的风,悄无声息地侵袭着数据的堡垒。本文将揭示网络漏洞的隐蔽角落,探讨如何通过加密技术筑起防线,同时提升个人和组织的安全意识,共同守护我们的数字家园。
|
3月前
|
物联网 C# Windows
看看如何使用 C# 代码让 MQTT 进行完美通信
看看如何使用 C# 代码让 MQTT 进行完美通信
543 0
|
3月前
|
安全 Java 网络安全
云计算时代下的网络安全挑战与应对策略Java编程中的异常处理:从基础到高级
在云服务不断深入各行各业的今天,网络安全问题也随之凸显。本文将探讨云计算环境下的安全风险,并提出相应的防护措施,以期为相关行业提供参考和指导。 在Java的世界里,异常处理是代码健壮性的守护神。它不仅保护程序免于意外崩溃,还提供了一种优雅的方式来响应错误。本文将带你领略异常处理的艺术,从简单的try-catch语句到复杂的自定义异常和finally块的神秘力量,我们将一起探索如何让Java程序在面对不确定性时,依然能够优雅地起舞。
|
7天前
|
存储 安全 算法
网络安全与信息安全:漏洞、加密技术及安全意识的重要性
如今的网络环境中,网络安全威胁日益严峻,面对此类问题,除了提升相关硬件的安全性、树立法律法规及行业准则,增强网民的网络安全意识的重要性也逐渐凸显。本文梳理了2000年以来有关网络安全意识的研究,综述范围为中国知网中篇名为“网络安全意识”的期刊、硕博论文、会议论文、报纸。网络安全意识的内涵是在“网络安全”“网络安全风险”等相关概念的发展中逐渐明确并丰富起来的,但到目前为止并未出现清晰的概念界定。此领域内的实证研究主要针对网络安全意识现状与问题,其研究对象主要是青少年。网络安全意识教育方面,很多学者总结了国外的成熟经验,但在具体运用上仍缺乏考虑我国的实际状况。 内容目录: 1 网络安全意识的相关
|
3天前
|
监控 安全 网络安全
企业网络安全:构建高效的信息安全管理体系
企业网络安全:构建高效的信息安全管理体系
19 5
|
3天前
|
存储 安全 网络安全
云计算与网络安全:探索云服务中的信息安全挑战与解决方案
【10月更文挑战第33天】在数字化时代的浪潮中,云计算以其灵活性、可扩展性和成本效益成为企业数字化转型的核心动力。然而,随之而来的网络安全问题也日益突出,成为制约云计算发展的关键因素。本文将深入探讨云计算环境中的网络安全挑战,分析云服务的脆弱性,并提出相应的信息安全策略和最佳实践。通过案例分析和代码示例,我们将展示如何在云计算架构中实现数据保护、访问控制和威胁检测,以确保企业在享受云计算带来的便利的同时,也能够维护其信息系统的安全和完整。
下一篇
无影云桌面