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


           在 《实现一个简单的语音聊天室》一文发布后,很多朋友建议我也实现一个视频聊天室给他们参考一下,其实,视频聊天室与语音聊天室的原理是差不多的,由于加入了摄像头、视频的处理,逻辑会繁杂一些,本文就实现一个简单的多人视频聊天系统,让多个人可以进入同一个房间进行语音视频沟通。先看看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)如此,多个用户就处于同一个聊天室进行视频聊天了。

  • 相关阅读:
    LeetCode 476 数字的补数
    MySQL与Java 整型数据映射
    TINYINT[M]、INT[M]和BIGINT[M]中M值的意义
    git删除本地分支
    git 初始化项目、创建本地分支、本地分支与远程分支关联
    Java 当文件不存在时自动创建文件目录和文件
    Java 在文件末尾追加内容
    免密自动登陆SAPGui
    SAP GUI770下载及安装
    notepad++格式化json,无法安装json插件
  • 原文地址:https://www.cnblogs.com/zhuweisky/p/4226265.html
Copyright © 2020-2023  润新知