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)如此,多个用户就处于同一个聊天室进行视频聊天了。

时间: 2024-10-19 05:13:21

C#实现多人视频聊天的相关文章

开个多人视频聊天软件需要多少钱?

首先,不知道你开发的是哪一种产品,因为多人视频聊天软件很多种,像我们雅顾合作就起码有几十种产品,这边给你们推荐下我们最主要的三种: 一.人气娱乐视频聊天软件 雅顾推荐人气娱乐视频聊天软件.以房间为单位,有共同兴趣爱好的网友可以欢聚一室.房间设计可供多元化选择,如经典款竖二屏,经典款竖三屏:清新版横三屏:风尚版竖二屏等, 让您娱乐更加随心所欲!清晰的视频画面,丰富的视频特效,专业的音响效果,网友可通过音视频的方式尽情展示个性风采,观看精彩互动节目. 二.人气会员视频聊天软件 所谓人气会员就是和qq

Android多人视频聊天应用的开发(三)多人聊天

在上一篇<Android多人视频聊天应用的开发(二)一对一聊天>中我们学习了如何使用声网Agora SDK进行一对一的聊天,本篇主要讨论如何使用Agora SDK进行多人聊天.主要需要实现以下功能: 1.上一篇已经实现过的聊天功能 2.随着加入人数和他们的手机摄像头分辨率的变化,显示不同的UI,即所谓的"分屏" 3.点击分屏中的小窗,可以放大显示该聊天窗 分屏 根据前期技术调研,分屏显示最好的方式是采用瀑布流结合动态聊天窗实现,这样比较方便的能够适应UI的变化.所谓瀑布流,

WebRTC实现网页版多人视频聊天室

因为产品中要加入网页中网络会议的功能,这几天都在倒腾 WebRTC,现在分享下工作成果. 话说 WebRTC Real Time Communication 简称 RTC,是谷歌若干年前收购的一项技术,后来把这项技术应用到浏览器中并开源出来,而且搞了一套标准提交给W3C,称为WebRTC,官方地址是:http://www.webrtc.org/.WebRTC要求浏览器内置实时传输音视频的功能,并提供一致的API供JS使用.目前实现这套标准的浏览器有:Chrome.FireFox.Opera.微软

WebRTC-多人视频聊天流程

最近在做WebRTC相关的移动端视频会议,看了一下相关文档和一些Demo,总结一下构建webRtc应用流程. 相关文档连接: 1.一篇不错的中文文档,好像网上好多demo都是基于这个的http://segmentfault.com/a/1190000000439103 2.htmlRock里也有一些Sample,http://www.html5rocks.com/en/tutorials/webrtc/basics/ 通过这2篇文档的学习,基本就差不多了解了相关内容 webRtc应用流程(基于大

实现一个简单的视频聊天室(源码)

在 <实现一个简单的语音聊天室>一文发布后,很多朋友建议我也实现一个视频聊天室给他们参考一下,其实,视频聊天室与语音聊天室的原理是差不多的,由于加入了摄像头.视频的处理,逻辑会繁杂一些,本文就实现一个简单的多人视频聊天系统,让多个人可以进入同一个房间进行语音视频沟通.先看看3个人进行视频聊天的运行效果截图:       上面两张截图分别是:登录界面.标注了各个控件的视频聊天室的主界面. 一. C/S结构 很明显,我这个语音聊天室采用的是C/S结构,整个项目结构相对比较简单,如下所示: 同语音聊

C#录制视频聊天

前段时间做个项目,客户需要将视频对话的整个过程录制下来,这样,以后就可以随时观看.想来录制整个视频聊天的过程这样的功能应该是个比较常见的需求,比如,基于网络语音视频的1:1的英语口语辅导,如果能将辅导的整个过程录制下来生成一个标准的MP4文件,就是一份难得的资料,便于以后复习和分享.我将1:1的视频对话录制的功能实现为了一个组件VideoChatRecorder,方便大家复用.并且,我在GG的最新版本4.3中使用了它,这样GG也有了视频聊天录制的功能. 如果大家已经做过类似录制单个人的摄像头和麦

3分钟实现网页版多人文本、视频聊天室 (含完整源码)

基于SimpleWebRTC快速实现网页版的多人文本.视频聊天室. 1 实现方法 复制下面的代码,保存为一个html文件 <!DOCTYPE html> <html> <head> <script src="https://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://simplewebrtc.com/latest.js"

Fms3和Flex打造在线多人视频会议和视频聊天(附原代码)

Flex,Fms3系列文章导航 Flex,Fms3相关文章索引 本篇是视频聊天,会议开发实例系列文章的第3篇,该系列所有文章链接如下: http://www.cnblogs.com/aierong/archive/2008/12/30/Flex.html#sp 1.工作原理NetStream.publish方法的应用publish () 方法:将音频流.视频流和文本消息流从客户端发送到 Flash Media Server,并可选择在传输期间录制该流. 此方法仅供指定的流的发布者使用.第1个参数

免费美女视频聊天,多人视频会议功能加强版本(Fms3和Flex开发(附源码))

Flex,Fms3系列文章导航 Flex,Fms3相关文章索引 本篇是视频聊天,会议开发实例系列文章的第4篇,该系列所有文章链接如下: http://www.cnblogs.com/aierong/archive/2008/12/30/Flex.html#sp 本软件只是一个简单的应用,主要是为了总结一下学习思路和开发方向,和大家交流.本软件实现的功能也比较简单,主要是视频,在线聊天,用户列表等等.代码可能写得比较乱,功能还不完善计划下步做以下事情的开发:一对一聊天,包括私聊聊天记录的保存和查询