C# 實現 Linux 視訊會議(原始碼,支援信創環境,銀河麒麟,統信UOS)

2023-05-16 12:01:38

      信創是現階段國家發展的重要戰略之一,面對這一趨勢,所有的軟體應用只有支援信創國產化的基礎軟硬體設施,在未來才不會被淘汰。那麼,如何可以使用C#來實現支援信創環境的視訊會議系統嗎?答案是肯定的。

      本文講述如何使用C#來實現視訊會議系統的Linux伺服器端與Linux使用者端,並讓其支援國產作業系統(如銀河麒麟,統信UOS)和國產CPU(如鯤鵬、龍芯、海光、兆芯、飛騰等)。 

      先看看該Demo在統信UOS上的執行效果:  

       

一.功能介紹

1.基本功能

(1)主持人:當進入同一房間的第一個使用者預設成為主持人,預設開啟麥克風。

(2)當進入會議房間的每個人,都能自由選擇是否開啟攝像頭、揚聲器和麥克風。

(3)當同一房間內無人開啟桌面共用時,所有使用者均可開啟桌面共用,供其他使用者觀看其桌面,同一時間內只允許一個使用者開啟桌面共用。

(4)當用戶為主持人時,可以選擇是否開啟電子白板;當主持人開啟電子白板後,所有使用者均可自由切換電子白板和會議視訊。

(5)每個使用者的視訊視窗上方均顯示聲音分貝條,根據聲音大小自動渲染。

(6)當用戶關閉攝像頭或者使用者數量超過9個,不顯示視訊。

(7)所有使用者均可收發文字訊息,包括帶表情的文字訊息。 

2.功能演示

     在銀河麒麟上執行: 

      

3.佈局風格

(1)當只有一個人開啟視訊時,採用大視窗顯示 

(2)當2~4人開啟視訊時,使用2x2佈局

(3)當超過4人開啟視訊時,使用3x3佈局

 

二.開發環境

1.開發工具:

Visual Studio 2022 

2. 開發框架: 

.NET Core 3.1,.NET 6,.NET 7 

3.開發語言:

C#

4.其它框架:

CPF.net UI 框架、OMCS 語音視訊框架 

三.具體實現

1. 新使用者進入會議房間

(1)視訊顯示視窗控制元件VideoPanel 

  預定SomeoneJoin事件,當新的使用者加入房間時,將觸發該事件:

this.chatGroup.SomeoneJoin += new CbGeneric<IChatUnit>(chatGroup_SomeoneJoin);
void chatGroup_SomeoneJoin(IChatUnit unit)
{
    if (!Dispatcher.CheckAccess())
    {
        Dispatcher.BeginInvoke(new CbGeneric<IChatUnit>(this.chatGroup_SomeoneJoin), unit);
    }
    else
    {
        VideoPanel panel = new VideoPanel();
        panel.Initialize(unit, false);
        VideoHidePanel videoHidePanel = new VideoHidePanel();
        videoHidePanel.Initialize(false, unit,false);
        VideoAllPanel videoAllPanel;
        videoAllPanel.videoPanel = panel;
        videoAllPanel.videoHidePanel = videoHidePanel;
        if (panel.ConnectCameraResult != ConnectResult.Succeed)
        {
            this.flowLayoutPanel2.Children.Insert(0, videoHidePanel);
            videoHidePanel.cameraVisibilityChange(false);
        }
        else
        {
            if (this.isVideoShowBeyond)
            {
                this.flowLayoutPanel2.Children.Insert(0, videoHidePanel);
                videoHidePanel.cameraVisibilityChange(true);
            }
            else
            {
                this.cameraViewbox.FlowLayoutPanel.Children.Insert(this.videoShowCount, panel);
            }
        }
        this.TotalNumChange(this.chatGroup.GetOtherMembers().Count + 1);
        panel.ChangeState += Panel_ChangeState;
        panel.CameraStateChange += Panel_CameraStateChange;
        panel.VideoByCount += Panel_VideoByCount;
        panel.VideoConnect += Panel_VideoConnect;
        unit.Tag = videoAllPanel;
        this.VideoNumBeyond();
        this.VideoSizeChange(new System.Windows.Size(this.cameraViewbox.FlowLayoutPanel.Width, this.cameraViewbox.FlowLayoutPanel.Height));
    }
}

      其中 VideoPanel 是視訊視窗顯示控制元件,初始化如下:

public void Initialize(IChatUnit unit, bool myself)
{

    this.pictureBox_Mic.RenderSize = new System.Windows.Size(24, 24);
    this.chatUnit = unit;
    this.isMySelf = myself;
    this.toolStrip1.Text = chatUnit.MemberID;
    //初始化麥克風聯結器            
    this.chatUnit.MicrophoneConnector.ConnectEnded += new CbGeneric<string, ConnectResult>(MicrophoneConnector_ConnectEnded);
    this.chatUnit.MicrophoneConnector.OwnerOutputChanged += new CbGeneric<string>(MicrophoneConnector_OwnerOutputChanged);
    
    if (!this.isMySelf)
    {
        this.chatUnit.MicrophoneConnector.BeginConnect(unit.MemberID);
    }
    if (this.isMySelf)
    {
        this.videoSizeShow.Visibility = Visibility.Collapsed;
    }
    //初始化攝像頭聯結器
    this.chatUnit.DynamicCameraConnector.SetViewer(this.cameraPanel1);
    this.chatUnit.DynamicCameraConnector.VideoDrawMode = VideoDrawMode.Scale;
    this.chatUnit.DynamicCameraConnector.ConnectEnded += new CbGeneric<string, ConnectResult>(DynamicCameraConnector_ConnectEnded);
    this.chatUnit.DynamicCameraConnector.OwnerOutputChanged += new CbGeneric<string>(DynamicCameraConnector_OwnerOutputChanged);
    this.chatUnit.DynamicCameraConnector.Disconnected += new CbGeneric<string, ConnectorDisconnectedType>(DynamicCameraConnector_Disconnected);
    this.chatUnit.DynamicCameraConnector.OwnerVideoSizeChanged += DynamicCameraConnector_OwnerVideoSizeChanged;
    this.chatUnit.DynamicCameraConnector.BeginConnect(unit.MemberID);
}

      當新使用者進入房間時,房間內其他使用者通過MicrophoneConnector麥克風聯結器和DynamicCameraConnector攝像頭聯結器連線到該使用者的麥克風和攝像頭

(2)開啟或關閉攝像頭、麥克風、揚聲器

  以開啟或關閉攝像頭為例:

private void skinCheckBox_camera_MouseDown(object sender, MouseButtonEventArgs e)
{
    this.isMyselfVideo = !this.isMyselfVideo;
    System.Windows.Controls.Image imageVideo = sender as System.Windows.Controls.Image;
    imageVideo.Source = this.isMyselfVideo ? ResourceManager.Singleton.CameraOpenImage : ResourceManager.Singleton.CameraCloseImage;
    imageVideo.ToolTip = this.isMyselfVideo ? "攝像頭:開" : "攝像頭:關";
    VideoPanel myPanel = ((VideoAllPanel)this.chatGroup.GetMember(this.loginId).Tag).videoPanel;
    VideoHidePanel myHidePanel = ((VideoAllPanel)this.chatGroup.GetMember(this.loginId).Tag).videoHidePanel;
    this.VideoNumBeyond();
    if (this.isVideoShowBeyond)
    {
        myHidePanel.cameraVisibilityChange(this.isMyselfVideo);
    }
    else
    {
        if (!isMyselfVideo)
        {
            this.cameraViewbox.FlowLayoutPanel.Children.Remove(myPanel);
            myPanel.cameraPanel1.ClearImage();
            this.flowLayoutPanel2.Children.Insert(0, myHidePanel);
            myHidePanel.cameraVisibilityChange(false);
        }
        else
        {
            this.flowLayoutPanel2.Children.Remove(myHidePanel);
            this.cameraViewbox.FlowLayoutPanel.Children.Insert(this.videoShowCount, myPanel);
        }
    }
    
    if (IsInitialized)
    {
        this.multimediaManager.OutputVideo = this.isMyselfVideo;
        this.VideoSizeChange(new System.Windows.Size(this.cameraViewbox.FlowLayoutPanel.Width, this.cameraViewbox.FlowLayoutPanel.Height));
    }
}

其中通過多媒體管理器multimediaManager的OutputVideo屬性,設定是否將採集到的視訊輸出,進而控制攝像頭的開啟或關閉。

2. 佈局切換

(1)根據開啟視訊的使用者數量可分為 1x1、2x2、3x3三種佈局格式

(2)根據不同的佈局格式以及外部控制元件容器的寬高,手動計算視訊控制元件的寬高。

private void VideoSizeChange(Size size)
{
    if (this.cameraViewbox.FlowLayoutPanel.Children.Count > 4)
    {
        foreach (VideoPanel panel in this.cameraViewbox.FlowLayoutPanel.Children)
        {
            panel.Height = (size.Height - 6) / 3;
            panel.Width = (size.Width - 12) / 3;
        }
    }
    else if (this.cameraViewbox.FlowLayoutPanel.Children.Count <= 4 && this.cameraViewbox.FlowLayoutPanel.Children.Count > 1)
    {
        foreach (VideoPanel panel in this.cameraViewbox.FlowLayoutPanel.Children)
        {
            panel.Height = (size.Height - 4) / 2;
            panel.Width = (size.Width - 8) / 2;
        }
    }
    else if (this.cameraViewbox.FlowLayoutPanel.Children.Count == 1)
    {
        foreach (VideoPanel panel in this.cameraViewbox.FlowLayoutPanel.Children)
        {
            panel.Height = size.Height - 2;
            panel.Width = size.Width - 4;
        }
    }
}

  通過流式佈局控制元件的特性:超過流式控制元件的寬度,子控制元件將自動換行,修改視訊控制元件的寬高;

 外部容器實際容納所有視訊控制元件的寬高為:外部容器的寬高減去所有視訊控制元件的外邊距;

 當只有一個使用者開啟視訊,即將使用1x1佈局時,視訊控制元件寬高即為外部容器實際容納所有視訊控制元件的寬高;

 當2~4人開啟視訊,即將使用2x2佈局時,視訊控制元件寬高即為外部容器實際容納所有視訊控制元件的寬高的1/2,此時每個視訊控制元件將佔外部控制元件的1/4;

  當超過4人開啟視訊,即將使用3x3佈局時,視訊控制元件寬高即為外部容器實際容納所有視訊控制元件的寬高的1/3,此時每個視訊控制元件將佔外部控制元件的1/9

 3.自定義訊息型別

public static class InformationTypes
{
    #region 廣播訊息
    /// <summary>
    /// 廣播聊天資訊
    /// </summary>
    public const int BroadcastChat = 0;
    /// <summary>
    /// 廣播共用桌面
    /// </summary>
    public const int BroadcastShareDesk = 1;
    /// <summary>
    /// 廣播白板
    /// </summary>
    public const int BroadcastWhiteBoard = 2;
    #endregion

    #region 給新加入成員傳送訊息
   /// <summary>
    /// 共用桌面
    /// </summary>
    public const int ShareDesk = 53;
    /// <summary>
    /// 電子白板
    /// </summary>
    public const int WhiteBoard = 54;
    #endregion

    /// <summary>
    /// 獲取伺服器端 組擴充套件資訊
    /// </summary>
    public const int GetGroupExtension = 101;
}

(1)當用戶傳送聊天訊息時,將通過BroadcastChat向所有線上使用者廣播聊天訊息;當用戶開啟桌面共用時,將通過BroadcastShareDesk向所有線上使用者廣播桌面共用訊息;當主持人開啟電子白板時,將通過BroadcastWhiteBoard 

向所有線上使用者廣播電子白板訊息。

(2)當用戶上線時,如果有使用者開啟桌面共用,就將通過ShareDesk 向新使用者傳送桌面共用訊息;如果主持人開啟電子白板,就將通過WhiteBoard向新使用者傳送電子白板訊息。

(3)使用者將通過GetGroupExtension向伺服器端獲取組擴充套件資訊。  

4.組擴充套件資訊

public class GroupExtension
{               
    /// <summary>
    /// 主持人ID
    /// </summary>
    public string ModeratorID { get; set; }
    
    /// <summary>
    /// 正在共用遠端桌面的使用者ID
    /// </summary>
    public string DesktopSharedUserID { get; set; }

    /// <summary>
    /// 主持人是否開啟白板
    /// </summary>
    public bool IsModeratorWhiteBoardNow { get; set; }
}

(1)ModeratorID 表示當前房間主持人的ID;

(2)DesktopSharedUserID 正在桌面共用的使用者ID;若值為null,表示當前房間內無人開啟桌面共用,使用者端通過該值判斷當前是否有使用者開啟桌面共用;當用戶開啟或關閉桌面共用時,都將手動修改該值;

(3)IsModeratorWhiteBoardNow表示當前主持人是否開啟電子白板;當主持人開啟或關閉電子白板時,都將手動修改該值。

 

四.原始碼下載

1. 原始碼專案說明

      原始碼下載

(1)OVCS.ServerLinux :視訊會議 Linux 伺服器端

(2)OVCS.ClientLinux :視訊會議 Linux 使用者端

         注: Linux使用者端內建的是x86/x64非託管so庫,若需要其它架構的so,請聯絡QQ:2027224508 獲取。  

(3)另附上Android使用者端的原始碼:Android端  。

2. 部署執行說明

在部署之前,需要在linux伺服器端和使用者端上分別安裝 .Net core 3.1版本,命令列安裝命令如下:

yum install dotnet-sdk-3.1

檢查版本安裝情況

 dotnet --version

執行:

(1)在CentOS上啟動OVCS.ServerLinux伺服器端:拷貝OVCS.ServerLinux專案下的Debug資料夾,到CentOS作業系統上,開啟Debug -> netcoreapp3.1目錄 ,在目錄下開啟終端,執行以下命令啟動伺服器端

dotnet OVCS.ServerLinux.dll

 (2)在麒麟或統信UOS、Ubuntu上執行OVCS.ClientLinux使用者端:拷貝OVCS.ClientLinux專案下的Debug資料夾,到麒麟或統信UOS、Ubuntu作業系統上,開啟Debug -> netcoreapp3.1目錄 ,在目錄下開啟終端,執行以下命令啟動使用者端

dotnet OVCS.ClientLinux.dll