1. 程式人生 > >實現一個簡單的視訊聊天室(原始碼)

實現一個簡單的視訊聊天室(原始碼)

       在 《》一文釋出後,很多朋友建議我也實現一個視訊聊天室給他們參考一下,其實,視訊聊天室與語音聊天室的原理是差不多的,由於加入了攝像頭、視訊的處理,邏輯會繁雜一些,本文就實現一個簡單的多人視訊聊天系統,讓多個人可以進入同一個房間進行語音視訊溝通。先看看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就大功告成了。

三. 原始碼下載

  上面只是講了實現多人視訊聊天室中的幾個重點,並不全面,大家下載下面的原始碼可以更深入的研究。

  最後,跟大家說說部署的步驟:

(1)將服務端部署在一臺機器上,啟動服務端。

(2)修改客戶端配置檔案中的ServerIP為剛才伺服器的IP。

(3)在多臺機器上執行客戶端,以不同的帳號登入到同一個房間(如預設的R1000)。

(4)如此,多個使用者就處於同一個聊天室進行視訊聊天了。

相關推薦

實現一個簡單視訊聊天原始碼

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

實現一個簡單的語音聊天原始碼

public partial class SpeakerPanel : UserControl ,IDisposable { private ChatUnit chatUnit; public SpeakerPanel()

使用ASP.NET SignalR實現一個簡單聊天

spl 記錄 歷史 undefine reat 語句 關鍵字 pda name  前言   距離我寫上一篇博客已經又過了一年半載了,時間過得很快,一眨眼,就把人變得滄桑了許多。青春是短暫的,知識是無限的。要用短暫的青春,去學無窮無盡的知識,及時當勉勵,歲月不待人。今天寫個隨

如何用WebSocket實現一個簡單聊天以及單聊功能

百度百科中這樣定義WebSocket:WebSocket協議是基於TCP的一種新的網路協議。它實現了瀏覽器與伺服器全雙工(full-duplex)通訊——允許伺服器主動傳送資訊給客戶端。簡單的說,WebSocket協議之前,雙工通訊是通過多個http連結來實現,這導致了效率低下,而WebSocket解決了這個

實現一個簡單的眾籌ICO合約

代幣的程式碼在我的另一篇文章:https://blog.csdn.net/qq_34493908/article/details/81842920,這裡的眾籌合約需要結合代幣合約,一個非常典型的眾籌專案是EOS,大家可以去搜一下。 實現一個眾籌合約需要分為以下幾步: 設定眾籌

使用akka實現一個簡單的RPC框架

一、概述 目前大多數的分散式架構底層通訊都是通過RPC實現的,RPC框架非常多,比如前我們學過的Hadoop專案的RPC通訊框架,但是Hadoop在設計之初就是為了執行長達數小時的批量而設計的,在某些極端的情況下,任務提交的延遲很高,所有Hadoop的RPC顯得有些笨重。

用純numpy實現一個簡單的神經網路理解

簡單的一個numpy神經網路示例: import numpy as np class NeuralNetwork(): def __init__(self): #將權重轉換為3×1矩陣,其值從-1到1,平均值為0 self.synaptic_we

PyQt實現一個簡單的License系統

    本文接著上一篇繼續講解“PyQt實現一個簡單的License系統”,主要包括: 3)如何用python建立一個GUI。 4)python如何調C DLL庫。 5)ctypes中型別處理。     上一篇文章只是簡單的將ui檔案轉換為py檔案,並執行,生成了一

仿照spring-boot實現一個簡單的ioc容器

前言 跳過廢話,直接看正文 仿照spring-boot的專案結構以及部分註解,寫一個簡單的ioc容器。 測試程式碼完成後,便正式開始這個ioc容器的開發工作。 正文 專案結構 simpleioc boot SimpleIocApplicat

【Android 網路資料解析實現一個簡單的新聞例項

      一般安卓在學到非同步任務AsyncTask之後都會有個安卓小專案的任務。得到(荔枝新聞,茶百科等)新聞網路介面來解析網路圖片或文字到ListView元件上顯示。其中要使用到的知識大概有:獲取網路資料(HttpUtil),解析網路資料(NewsParse),防止因

【遠端呼叫框架】如何實現一個簡單的RPC框架優化三:軟負載中心設計與實現

【如何實現一個簡單的RPC框架】系列文章: 1.前言 在部落格【遠端呼叫框架】如何實現一個簡單的RPC框架(一)想法與設計中我們介紹了“服務註冊查詢中心”,負責服務資訊的管理即服務的註冊以及查詢,在目前為止的實現中,我們採用web應用的方式,以

android硬編碼h264資料,並使用rtp推送資料流,實現一個簡單的直播-MediaCodec

  寫在前面:我並非專業做流媒體的coder,對流媒體行業無比崇拜,只是做了幾年安卓車載ROM,對安卓AV開發算是略懂。本篇部落格是我對MediaCodec編解碼和rtp推流的一次嘗試,希望能給有需要的朋友一些細微的幫助,不喜勿噴,如果有不對的地方希望大神指正共

【遠端呼叫框架】如何實現一個簡單的RPC框架優化一:利用動態代理改變使用者服務呼叫方式

【如何實現一個簡單的RPC框架】系列文章: 這篇部落格,在(一)(二)的基礎上,對第一版本實現的服務框架進行改善,不定期更新,每次更新都會增加一個優化的地方。 1、優化一:利用動態代理改變使用者服務呼叫方式 1.1 目的 改變使用者

java WebSocket實現簡單聊天包括群發和點對點聊天

今天突然看到了WebSocket然後就網上找了一個例子,然後修改了下,實現了簡單的聊天室,包括群聊和點對點聊天。 使用的程式碼如下 jsp程式碼: <%@ page language="java" import="java.util.*" pageEncoding="

Vue初體驗使用Vue實現一個簡單聊天

1、實踐是檢驗真理的唯一標準,現在我們做一個簡易的聊天視窗,有一個input框,用於使用者輸入,一個按鈕button,用於把使用者的輸入

實現簡易聊天

ima log body .com 麻煩 導入 定義 右鍵 正常 預備工作: (1)讀取文件的時候可能會遇到多個文件一起傳,可以用線程池。 (2)發送不同類型的請求時,如發送的是聊天信息,發送的是文件,發送的是好友上線請求等,但對於接受者來說都是字節流無法分別,這就需要我們

Netty - 一個簡單聊天小專案

經過一段時間對Netty的學習,我們對Netty各版本以及像ProtocolBuffers等技術應用都有了不少相關的瞭解, 我們就用這段時間學到的只是做一個簡單的聊天室的小專案來練習自己學到的技術。 做這個小專案之前我們先大致瞭解下我們需要用到的技術點,netty3.x/4.x/5.

實現一個簡單的Web伺服器C語言

Web伺服器 github地址 該專案的第二部分是在第一部分的基礎上繼續完善Web伺服器。 第二部分主要是完成兩個功能:記錄日誌和HTTP響應。 記錄日誌 該部分比較簡單,只要簡單地將一些伺服器的資訊新增到指定日誌檔案即可, 也不需要完成很複雜的功能,有了日誌功

Android端實現多人音視訊聊天應用

本文轉載於資深Android開發者“東風玖哥”的部落格。 本系列文章分享了基於Agora SDK 2.1實現多人視訊通話的實踐經驗。 轉載已經過原作者許可。原文地址 自從2016年,鼓吹“網際網路寒冬”的論調甚囂塵上,2017年亦有愈演愈烈之勢。但連麥直播、線上抓娃

使用netty實現一個多人聊天--failed: Error during WebSocket handshake: Unexpected response code: 200

初次接觸netty , 本文主要內容如下: 遇到的小bug 聊天室後端程式碼: 聊天室前端程式碼: 遇到的小bug 在使用netty進行websocket程式設計(實現一個簡單的聊天室)時,我遇到了這樣一個奇怪