实现 Linux 视频会议(源码,支持信创环境,银河麒麟,统信UOS)

news2024/11/27 8:36:52

       信创是现阶段国家发展的重要战略之一,面对这一趋势,所有的软件应用只有支持信创国产化的基础软硬件设施,在未来才不会被淘汰。那么,可以使用C#来实现支持信创环境的视频会议系统吗?答案是肯定的。

      本文讲述如何使用C#来实现视频会议系统的Linux服务端与Linux客户端,并让其支持国产操作系统(如银河麒麟,统信UOS)和国产CPU(如鲲鹏、龙芯、海光、兆芯、飞腾等)。 

      先看看该Demo在统信UOS上的运行效果:  

OVCS视频会议在统信UOS上的运行效果(飞腾CPU) 

一.功能介绍

1.基本功能

(1)主持人:当进入同一房间的第一个用户默认成为主持人,默认打开麦克风。

(2)当进入会议房间的每个人,都能自由选择是否开启摄像头、扬声器和麦克风。

(3)当同一房间内无人开启桌面共享时,所有用户均可开启桌面共享,供其他用户观看其桌面,同一时间内只允许一个用户开启桌面共享。

(4)当用户为主持人时,可以选择是否开启电子白板;当主持人开启电子白板后,所有用户均可自由切换电子白板和会议视频。

(5)每个用户的视频窗口上方均显示声音分贝条,根据声音大小自动渲染。

(6)当用户关闭摄像头或者用户数量超过9个,不显示视频。

(7)所有用户均可收发文字消息,包括带表情的文字消息。 

2.功能演示

     在银河麒麟上运行: 

OVCS视频会议在银河麒麟上的运行效果(鲲鹏CPU) 

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事件,当新的用户加入房间时,将触发该事件:

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

客户端的运行界面将如本文开始的截图所示。大功告成!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/615725.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

django 快速入门

快速开始 安装Django 首先安装Django包&#xff0c;现在Django已经到了2.0版本&#xff0c;如果还在使用1.11请尽快升级。旧版本以后只修复bug&#xff0c;不会添加新功能。 pip install django 复制 创建项目 Django安装好之后&#xff0c;会附带一个命令行工具django-a…

uCOSii_任务栈检测和任务栈清除

1、任务栈检测和任务栈清除 在创建任务时&#xff0c;也需要设置OSTaskCreateExt()传入opt参数。 当opt (INT16U)(OS_TASK_OPT_STK_CLR | OS_TASK_OPT_STK_CHK)&#xff0c;可以使用OSTaskStkChk()检查的任务栈的剩余空间&#xff0c;也可以使用OS_TaskStkClr()清除任务栈。 …

使用Graalvm+Swing搓了个原生桌面应用的轮子:文件差异对比工具,附轮子源码

文章目录 1、DFDiff介绍2、软件架构3、安装教程3.1、编译为jar包运行3.2、编译为原生应用运行 4、运行效果图5、项目源码地址 1、DFDiff介绍 当前已实现的功能比较两个文件夹内的文件差异&#xff0c;已支持文件差异对比。 2、软件架构 软件架构说明 开发环境是在OpenJDK17&…

docker 安装gitlab jenkins git maven

jenkins 配置git提示 问题1、Error performing git command: /usr/local/git ls-remote -h 问题2、stdout: stderr: Host key verification failed. fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repositor…

【数据结构】一文带你掌握二叉树的构造与应用

文章目录 1. 构造二叉树2. 前序遍历2.1 前序遍历递归2.2 前序遍历非递归 3. 中序遍历3.1 中序遍历递归3.2 中序遍历非递归 4. 后序遍历4.1 后序遍历递归4.2 后序遍历非递归 5. 层序遍历6. 节点个数6.1 所有节点个数6.2 获得叶子节点个数 7. 检测值为value的元素是否存在8.总结 …

数据库SQL2000最基本的安装和操作教程

Chengg0769 2012年 转载请保留以下版权来源 www.mis2erp.com http://blog.csdn.net/chengg0769 http://www.haojiaocheng.cc 题外话&#xff1a; 之前写了一个项目&#xff0c;因为是国企&#xff0c;各种文档都要给到他们。当时就写了一个简单的维护管理SQL2000的文档。这…

【2352. 相等行列对】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个下标从 0 开始、大小为 n x n 的整数矩阵 grid &#xff0c;返回满足 Ri 行和 Cj 列相等的行列对 (Ri, Cj) 的数目。 如果行和列以相同的顺序包含相同的元素&#xff08;即相等的数组&#…

DMBOK知识梳理for CDGA/CDGP——第四章 数据架构(附常考知识点)

关 注ghz“大数据食铁兽”&#xff0c;回复“知识点”获取《DMBOK知识梳理for CDGA/CDGP》常考知识点&#xff08;第四章 数据架构&#xff09; 第四章 数据架构 第四章是CDGA|CDGP考试的重点考核章节之一&#xff0c;分值占比高&#xff0c;知识点比较密集&#xff0c;重点…

CAN message 属性DLC和DataLength,极易混淆

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

STM32开发(CubeMX+HAL)——点灯(轮询、中断)

目录 1.CubeMX生成工程文件 2.点灯1——轮询 3.点灯2——中断 3.1CubeMX配置时钟 3.2中断和事件简介 3.3配置GPIO口 3.4使能中断 3.5配置工程 3.6中断函数编写 1.CubeMX生成工程文件 1.1新建工程 1.2输入STM32C8T6选择芯片 1.3双击芯片 1.4 选择System Core—SYS——…

DAY09_JQueryBootStrap实现省略号效果

目录 1 JQuery简介2 引入JQuery方式3 JS与JQ之间的转换4 选择器4.1 基本选择器4.2 层级选择器4.2.1 层级选择器相关方法 4.3 过滤选择器4.4 内容选择器4.5 可见选择器4.6 隐藏显示的相关方法4.7 属性选择器4.8 子元素选择器4.9 表单选择器 5 JQ中新增的遍历方式6 JQ中相关方法7…

vue解决浏览器中跳转新页面缓存上一页表单等内容方法

在工作中&#xff0c;有可能会遇到需要缓存页面或组件的功能。 情况1&#xff1a;比如在h5中有个一个50个表单&#xff0c;在填到第40个表单时&#xff0c;需要你去另一个新页面去选择列表项&#xff0c;然后把数据带回来。需要我们不仅把数据带回来还要保留前面已经填好的40个…

GIS地图:解读未知的地理空间之谜

在这个信息爆炸的时代&#xff0c;如何有效地理解和利用地理空间数据成为各行各业追求的目标。而GIS地图作为一种强大的工具&#xff0c;能够帮助我们连接世界的空间智慧。 GIS地图的魅力在于它能够将庞大的地理数据转化为直观、可视化的地图表达。通过GIS地图&#xff0c;我们…

管理平台|智慧工地将成为施工界的“扛把子”!

大家都知道&#xff0c;建筑业是一个安全事故频发的高危行业&#xff0c;特别在施工环节&#xff0c;由于施工现场人员复杂、环境混乱、地点分散、多工序交叉等现象&#xff0c;如何进行现场施工管理就显得格外重要。 但是&#xff0c;依赖于现场管理的施工模式总是存在着很多…

数据结构-图结构

图是最为复杂的数据结构。如果数据元素之间存在一对多或者多对多的关系&#xff0c;那么这种数据的组织结构就叫作图结构。 图的基本概念 图的定义 图Graph是由顶点&#xff08;图中的节点被称为图的顶点&#xff09;的非空有限集合V与边的集合E&#xff08;顶点之间的关系&a…

什么AC+AP组网?什么是mesh组网?

一、什么是ACAP组网&#xff1f; ACAP组网是一种基于集中式管理的无线局域网&#xff08;WLAN&#xff09;组网架构&#xff0c;主要由AC&#xff08;Access Controller&#xff09;和多个AP&#xff08;Access Point&#xff09;组成。AC作为网络管理中心&#xff0c;负责控制…

别乱分层,PO、VO、DAO、BO、DTO、POJO 到底应该用在哪里,你知道吗

一、PO :&#xff08;persistant object&#xff09;&#xff0c;持久对象 二、VO :&#xff08;value object&#xff09;&#xff0c;值对象 三、DAO :&#xff08;Data Access Objects&#xff09;&#xff0c;数据访问对象接口 四、BO :&#xff08;Business Object&…

30天从入门到精通TensorFlow1.x 第六天,可视化工具 TensorBoard

文章目录 一、接前一天二、TensorBoard&#xff08;1&#xff09;. 什么是TensorBoard&#xff08;2&#xff09;. TensorBoard有什么用&#xff08;3&#xff09;. TensorBoard怎么安装 三、tf.summary模块&#xff08;1&#xff09;.如何使用tensorboard&#xff08;2&#x…

数据结构之二叉树(Binary Tree)详解

目录 1、什么是二叉树&#xff1f; 2、二叉树的遍历&#xff1a;深度优先和广度优先 &#xff08;1&#xff09;深度优先搜索(DFS)算法 &#xff08;2&#xff09;广度优先搜索(BFS)算法 3、二叉树的性质详解 4、二叉树的类型 &#xff08;1&#xff09;满二叉树 &…

IT服务台追踪的关键故障指标

指标是 IT 服务管理的核心&#xff0c;可提供运营见解并帮助确定持续改进的领域。通常的服务台指标有助于展示内部运营效率。为 例如&#xff0c;衡量在指定时间内解决的工单数量的 SLA 是展示服务台效率的关键因素。另一方面&#xff0c;故障指标可帮助团队识别 IT 基础架构中…