實現一個簡單的視頻聊天室(源碼)

       在 《實現一個簡單的語音聊天室》一文發佈後,不少朋友建議我也實現一個視頻聊天室給他們參考一下,其實,視頻聊天室與語音聊天室的原理是差很少的,因爲加入了攝像頭、視頻的處理,邏輯會繁雜一些,本文就實現一個簡單的多人視頻聊天系統,讓多我的能夠進入同一個房間進行語音視頻溝通。先看看3我的進行視頻聊天的運行效果截圖:html

       

    上面兩張截圖分別是:登陸界面、標註了各個控件的視頻聊天室的主界面。  服務器

一. C/S結構

  很明顯,我這個語音聊天室採用的是C/S結構,整個項目結構相對比較簡單,以下所示:閉包

      

  同語音聊天室同樣,該項目的底層也是基於OMCS構建的。這樣,服務端就基本沒寫代碼,直接把OMCS服務端拿過來用;客戶端就比較麻煩些,下面我就重點講客戶端的開發。ide

二. 客戶端控件式開發

  客戶端開發了多個自定義控件,而後將它們組裝到一塊兒,以完成視頻聊天室的功能。爲了便於講解,我主界面的圖作了標註,以指示出各個自定義控件。  ui

  如今咱們分別介紹各個控件:this

1. 分貝顯示器 

  分貝顯示器用於顯示聲音的大小,好比麥克風採集到的聲音的大小,或揚聲器播放的聲音的大小。如上圖中2標註的。spa

(1)傅立葉變換code

  將聲音數據轉換成分貝強度使用的是傅立葉變換。其對應的是客戶端項目中的FourierTransformer靜態類。源碼比較簡單,就不貼出來了,你們本身去看。orm

(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)如此,多個用戶就處於同一個聊天室進行視頻聊天了。

相關文章
相關標籤/搜索