OMCS Demo -- 实现视频聊天室(多人视频聊天)

  多人视频聊天,或视频聊天室,也是即时通信应用中常见的功能之一,比如,QQ的视频讨论组就是我们用得比较多的。

      本文将基于最新版本的OMCS(V3.5+)实现一个简单的视频聊天室,让多个人可以进入同一个房间进行语音视频沟通。当然,在此之前,您必须对OMCS有所了解,并且已经阅读、理解了OMCS 开发手册(08) -- 多人语音/视频 这篇文章的内容。先看看Demo运行效果截图: 

        

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

  

下载免费版本的OMCS以及 demo源码

阅读 更多OMCS开发手册系列文章

Q Q:168757008

官网:www.oraycn.com

导航

首页

官方网站

立即咨询 

站内搜索

ESFramework 通信框架

详细说明

SDK下载

ESFramework FAQ

版本变更记录

OMCS 语音视频框架

详细说明

SDK下载

OMCS FAQ

版本变更记录

OrayTalk 企业即时通讯系统

详细说明

客户端下载

傲瑞实用组件

SDK下载

NPush 消息推送组件

StriveEngine 轻量级的通信引擎

MFile 语音视频录制组件

MCapture 语音视频采集组件

MPlayer 语音视频播放组件

OAUS 自动升级系统

傲瑞组件 FAQ

授权

授权流程

产品授权说明

产品选购指南

授权SDK使用说明

其它

SDK使用技巧

联系我们

电话:027-87638960

Q Q:168757008

邮件:master@oraycn.com