C#实现多人视频聊天
在 《C#实现多人语音聊天》一文发布后,很多朋友建议我也实现一个视频聊天室给他们参考一下,其实,视频聊天室与语音聊天室的原理是差不多的,由于加入了摄像头、视频的处理,逻辑会繁杂一些,本文就实现一个简单的多人视频聊天系统,让多个人可以进入同一个房间进行语音视频沟通。先看看3个人进行视频聊天的运行效果截图:
上面两张截图分别是:登录界面、标注了各个控件的视频聊天室的主界面。
一. C/S结构
很明显,我这个语音聊天室采用的是C/S结构,整个项目结构相对比较简单,如下所示:
同语音聊天室一样,该项目的底层也是基于OMCS语音视频框架构建的。这样,服务端就基本没写代码,直接把OMCS服务端拿过来用;客户端就比较麻烦些,下面我就重点讲客户端的开发。
二. 客户端控件式开发
客户端开发了多个自定义控件,然后将它们组装到一起,以完成视频聊天室的功能。为了便于讲解,我主界面的图做了标注,以指示出各个自定义控件。
现在我们分别介绍各个控件:
1. 分贝显示器
分贝显示器用于显示声音的大小,比如麦克风采集到的声音的大小,或扬声器播放的声音的大小。如上图中2标注的。
(1)傅立叶变换
将声音数据转换成分贝强度使用的是傅立叶变换。其对应的是客户端项目中的FourierTransformer静态类。源码比较简单,就不贴出来了,大家自己去看。
(2)声音强度显示控件 DecibelDisplayer
DecibelDisplayer 使用的是PrograssBar来显示声音强度的大小。
每当有声音数据交给DecibelDisplayer显示时,首先,DecibelDisplayer会调用上面的傅立叶变换将其转换为分贝,然后,将其映射为PrograssBar的对应的Value。
2.视频显示控件 VideoPanel
VideoPanel用于表示聊天室中的一个成员,如上图中1所示。它显示了成员的ID,成员的声音的强度(使用DecibelDisplayer控件),以及其麦克风的状态(启用、禁用)、摄像头的状态(不可用、正常、禁用)、成员的视频等。
这个控件很重要,我将其源码贴出来:
public partial class VideoPanel : UserControl{private IChatUnit chatUnit;private bool isMySelf = false;public VideoPanel(){InitializeComponent();} /// <summary>/// 初始化成员视频显示控件。/// </summary> public void Initialize(IChatUnit unit ,bool myself){ this.chatUnit = unit;this.isMySelf = myself;this.toolStripLabel_displayName.Text = unit.MemberID;this.decibelDisplayer1.Visible = !myself; //初始化麦克风连接器this.chatUnit.MicrophoneConnector.Mute = myself;this.chatUnit.MicrophoneConnector.SpringReceivedEventWhenMute = myself;this.chatUnit.MicrophoneConnector.ConnectEnded += new CbGeneric<ConnectResult>(MicrophoneConnector_ConnectEnded);this.chatUnit.MicrophoneConnector.OwnerOutputChanged += new CbGeneric(MicrophoneConnector_OwnerOutputChanged);this.chatUnit.MicrophoneConnector.AudioDataReceived += new CbGeneric<byte[]>(MicrophoneConnector_AudioDataReceived);this.chatUnit.MicrophoneConnector.BeginConnect(unit.MemberID);//初始化摄像头连接器this.chatUnit.DynamicCameraConnector.SetViewer(this.skinPanel1);this.chatUnit.DynamicCameraConnector.ConnectEnded += new CbGeneric<ConnectResult>(DynamicCameraConnector_ConnectEnded);this.chatUnit.DynamicCameraConnector.OwnerOutputChanged += new CbGeneric(DynamicCameraConnector_OwnerOutputChanged);this.chatUnit.DynamicCameraConnector.BeginConnect(unit.MemberID);}//好友启用或禁用摄像头void DynamicCameraConnector_OwnerOutputChanged(){if (this.InvokeRequired){this.BeginInvoke(new CbGeneric(this.DynamicCameraConnector_OwnerOutputChanged));}else{this.ShowCameraState();}}private ConnectResult connectCameraResult;//摄像头连接器尝试连接的结果void DynamicCameraConnector_ConnectEnded(ConnectResult res){if (this.InvokeRequired){this.BeginInvoke(new CbGeneric<ConnectResult>(this.DynamicCameraConnector_ConnectEnded), res);}else{this.label_tip.Visible = false;this.connectCameraResult = res;this.ShowCameraState();} }/// <summary>/// 综合显示摄像头的状态。/// </summary>private void ShowCameraState(){ if (this.connectCameraResult != OMCS.Passive.ConnectResult.Succeed){this.pictureBox_Camera.BackgroundImage = null;this.pictureBox_Camera.BackgroundImage = this.imageList2.Images[2];this.pictureBox_Camera.Visible = true;this.toolTip1.SetToolTip(this.pictureBox_Camera, this.connectCameraResult.ToString());}else{this.pictureBox_Camera.Visible = !this.chatUnit.DynamicCameraConnector.OwnerOutput; if (!this.chatUnit.DynamicCameraConnector.OwnerOutput){this.pictureBox_Camera.BackgroundImage = this.imageList2.Images[1];this.toolTip1.SetToolTip(this.pictureBox_Camera, "摄像头被主人禁用!");return;} }}//将接收到的声音数据交给分贝显示器显示void MicrophoneConnector_AudioDataReceived(byte[] data){this.decibelDisplayer1.DisplayAudioData(data);}//好友启用或禁用麦克风void MicrophoneConnector_OwnerOutputChanged(){if (this.InvokeRequired){this.BeginInvoke(new CbGeneric(this.MicrophoneConnector_OwnerOutputChanged));}else{this.ShowMicState();}}private ConnectResult connectMicResult;//麦克风连接器尝试连接的结果void MicrophoneConnector_ConnectEnded(ConnectResult res){if (this.InvokeRequired){this.BeginInvoke(new CbGeneric<ConnectResult>(this.MicrophoneConnector_ConnectEnded), res);}else{this.connectMicResult = res;this.ShowMicState();}}/// <summary>/// 综合显示麦克风的状态。/// </summary>private void ShowMicState(){ if (this.connectMicResult != OMCS.Passive.ConnectResult.Succeed){this.pictureBox_Mic.Visible = true;this.toolTip1.SetToolTip(this.pictureBox_Mic, this.connectMicResult.ToString());}else{ this.decibelDisplayer1.Working = false;this.pictureBox_Mic.Visible = !this.chatUnit.MicrophoneConnector.OwnerOutput;this.decibelDisplayer1.Visible = this.chatUnit.MicrophoneConnector.OwnerOutput && !this.isMySelf;if (!this.chatUnit.MicrophoneConnector.OwnerOutput){ this.pictureBox_Mic.BackgroundImage = this.imageList1.Images[1];this.toolTip1.SetToolTip(this.pictureBox_Mic, "麦克风被主人禁用!");return;}this.pictureBox_Mic.Visible = !isMySelf;if (this.chatUnit.MicrophoneConnector.Mute){this.pictureBox_Mic.BackgroundImage = this.imageList1.Images[1];this.toolTip1.SetToolTip(this.pictureBox_Mic, "静音");}else{this.pictureBox_Mic.Visible = false;this.pictureBox_Mic.BackgroundImage = this.imageList1.Images[0];this.toolTip1.SetToolTip(this.pictureBox_Mic, "正常");this.decibelDisplayer1.Working = true;}}}/// <summary>/// 展开或收起视频面板。/// </summary> private void toolStripButton1_Click(object sender, EventArgs e){try{if (this.Height > this.toolStrip1.Height){this.toolStripButton1.Text = "展开";this.toolStripButton1.Image = Resources.Hor;this.chatUnit.DynamicCameraConnector.SetViewer(null);this.Height = this.toolStrip1.Height;}else{this.toolStripButton1.Text = "收起";this.toolStripButton1.Image = Resources.Ver; this.chatUnit.DynamicCameraConnector.SetViewer(this.skinPanel1);}}catch (Exception ee){ MessageBox.Show(ee.Message);}} }
(1)在代码中,IChatUnit就代表当前这个聊天室中的成员。我们使用其MicrophoneConnector连接到目标成员的麦克风、使用其DynamicCameraConnector连接到目标成员的摄像头。
(2)预定MicrophoneConnector的AudioDataReceived事件,当收到语音数据时,将其交给DecibelDisplayer去显示声音的大小。
(3)预定MicrophoneConnector的ConnectEnded和OwnerOutputChanged事件,根据其结果来显示VideoPanel控件上麦克风图标的状态(对应ShowMicState方法)。
(4)预定DynamicCameraConnector的ConnectEnded和OwnerOutputChanged事件,根据其结果来显示VideoPanel控件上摄像头图标的状态(对应ShowCameraState方法)。
3. MultiVideoChatContainer 控件
MultiAudioChatContainer对应上图中3标注的控件,它主要做了以下几件事情:
(1)在初始化时,加入聊天室:通过调用IMultimediaManager的ChatGroupEntrance属性的Join方法。
(2)使用FlowLayoutPanel将聊天室中每个成员对应的VideoPanel罗列出来。
(3)当有成员加入或退出聊天室时(对应ChatGroup的SomeoneJoin和SomeoneExit事件),动态添加或移除对应的VideoPanel实例。
(4)通过CheckBox将自己设备(摄像头、麦克风、扬声器)的控制权暴露出来。我们可以启用或禁用我们自己的麦克风或扬声器。
(5)注意,其提供了Close方法,这意味着,在关闭包含了该控件的宿主窗体时,要调用其Close方法以释放其内部持有的麦克风连接器、摄像头连接器等资源。
在完成MultiAudioChatContainer后,我们这个聊天室的核心就差不多了。接下来就是弄个主窗体,然后把MultiVideoChatContainer拖上去,初始化IMultimediaManager,并传递给MultiVideoChatContainer就大功告成了。
三. 源码下载
上面只是讲了实现多人视频聊天室中的几个重点,并不全面,大家下载下面的源码可以更深入的研究。
VideoChatRoom.rar
最后,跟大家说说部署的步骤:
(1)将服务端部署在一台机器上,启动服务端。
(2)修改客户端配置文件中的ServerIP为刚才服务器的IP。
(3)在多台机器上运行客户端,以不同的帐号登录到同一个房间(如默认的R1000)。
(4)如此,多个用户就处于同一个聊天室进行视频聊天了。
C#实现多人视频聊天相关推荐
- 实践:《从头到脚撸一个多人视频聊天 — 前端 WebRTC 实战(一)》
2019独角兽企业重金招聘Python工程师标准>>> 请先阅读原文,链接:从头到脚撸一个多人视频聊天 - 前端 WebRTC 实战(一),本文只涉及实践过程中的问题 1.video ...
- Android 集成 Agora SDK 快速体验 RTC 版多人视频聊天|掘金技术征文
RTC (Real-Time Communication) 作为实时通讯领域的"新贵",在互动直播.远程控制.多人视频会议.屏幕共享等领域广受好评,如果你还不了解 RTC ,Tak ...
- WebRTC实现多人视频聊天
写在前面 实现房间内人员的视频聊天,由于并未很完善,所以需要严格按照步骤来,当然基于此完善,就是时间的问题了. 架构 整个设计架构如下: 图片来自于参考博文.我使用的是第一种Mesh 架构,无需任何流 ...
- 【游戏开发实战】Unity从零开发多人视频聊天功能,无聊了就和自己视频聊天(附源码 | Mirror | 多人视频 | 详细教程)
文章目录 一.前言 二.思考问题与解决方案 1.思考问题 2.解决方案 2.1.Unity中如何开启摄像头并对图像进行采样 2.2.图像如何中转给其他客户端 2.3.如何实现清晰度切换 2.4.客户端 ...
- 教你用WebRTC撸一个多人视频聊天
之前公司准备用 webRTC 来实现视频聊天,研究了几天,撸了个 demo 出来,(虽然最后并没有采用这项技术,囧),但是还是写一个出来吧! WebRTC简单介绍 WebRTC (Web Real-T ...
- (Agora声网)多人视频聊天应用的开发(三)多人聊天
转载于:Android多人视频聊天应用的开发(三)多人聊天-玖哥的书房-51CTO博客 http://blog.51cto.com/dongfeng9ge/2104587 本系列文章结合声网官方在Gi ...
- (Agora声网)多人视频聊天应用的开发(二)一对一聊天
转载于:Android多人视频聊天应用的开发(二)一对一聊天-玖哥的书房-51CTO博客 http://blog.51cto.com/dongfeng9ge/2095626 本系列文章结合声网官方在G ...
- (Agora声网)多人视频聊天应用的开发(一)快速集成
转载于:Android多人视频聊天应用的开发(一)快速集成-玖哥的书房-51CTO博客 http://blog.51cto.com/dongfeng9ge/2095621 本系列文章结合声网官方在Gi ...
- Android多人视频聊天应用的开发(三)多人聊天
在上一篇<Android多人视频聊天应用的开发(二)一对一聊天>中我们学习了如何使用声网Agora SDK进行一对一的聊天,本篇主要讨论如何使用Agora SDK进行多人聊天.主要需要实现 ...
- Android多人视频聊天应用的开发(二)一对一聊天
在上一篇<Android多人视频聊天应用的开发(一)快速集成>中我们讨论了如何配置Agora Android SDK,本文我们将探索使用Agora进行一对一视频聊天的奥秘. 鉴权 APP ...
最新文章
- h3c交换机端口加入vlan命令_华为交换机批量加入 Vlan 方法
- 乾颐堂安德HCIE课程3-OSPF的精华1、2类LSA,区域间的3类LSA和过滤策略
- 运行GPU出现CUDA_ERROR_LAUNCH_FAILED
- boost::fusion::reverse_view用法的测试程序
- GridView的一些常用属性:
- HTML+CSS+JS实现 ❤️翻页倒计时ui特效❤️
- 【redis】Redis简介
- java程序员面试题200题_Java程序员经典面试题集大全 (三十)
- 用AngularJS开发下一代Web应用pdf
- Could not autowire field: XXXXX.
- 如何提高意志力如何坚持每天学习
- 软件开发自学靠谱吗?
- java计算机毕业设计ssm基于SSM学生信息管理系统37myx(附源码、数据库)
- uniapp上下滑屏切换支持视频和图片轮播实现,类似抖音效果
- 第一周校内OI模拟赛总结(day1day2)
- 那个“炫酷狂拽”的数据可视化利器AntV 11.22版全新发布啦
- Unity - 官方2D动画(2D Animation Package)文档
- 感时花溅泪,恨别鸟惊心
- linux多重引导工具,Linux多重引导器
- 科目三: 济南章丘二号线
热门文章
- 华为防火墙默认密码是什么?
- 分布式自增序列id的实现(二) ---分布式序号生成器---基于Redis的自增功能
- Exif文件格式描述(转载)
- 如何判断一件事重要还是紧急?
- 联邦学习:加密算法Paillier,Affine,IterativeAffine
- Android App开发动画特效中插值器和估值器的讲解以及利用估值器实现弹幕动画实战(附源码和演示视频 可直接使用)
- 烽火2640路由器命令行手册-01-基础配置命令
- 数据库系统原理——实验四
- 在线rar压缩包解密软件,忘记rar压缩包密码如何找回?
- 分解质因数 java_java代码分解质因数