C# 实现 Linux 视频会议(源码,支持信创环境,银河麒麟,统信UOS)
<p> 信创是现阶段国家发展的重要战略之一,面对这一趋势,所有的软件应用只有支持信创国产化的基础软硬件设施,在未来才不会被淘汰。那么,如何可以使用C#来实现支持信创环境的视频会议系统吗?答案是肯定的。</p><p class="p"> 本文讲述如何使用C#来实现视频会议系统的Linux服务端与Linux客户端,并让其支持国产操作系统(如银河麒麟,统信UOS)和国产CPU(如鲲鹏、龙芯、海光、兆芯、飞腾等)。 </p>
<p class="p"> 先看看该Demo在统信UOS上的运行效果: </p>
<p class="p"> <img src="https://img2023.cnblogs.com/blog/513369/202305/513369-20230505103341413-2141774866.png"></p>
<h2><span style="font-size: 18pt; font-family: "Microsoft YaHei"">一.<strong>功能介绍</strong></span></h2>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei""><strong>1.基本功能</strong></span></h3>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(1)主持人:当进入同一房间的第一个用户默认成为主持人,默认打开麦克风。</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(2)当进入会议房间的每个人,都能自由选择是否开启摄像头、扬声器和麦克风。</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(3)当同一房间内无人开启桌面共享时,所有用户均可开启桌面共享,供其他用户观看其桌面,同一时间内只允许一个用户开启桌面共享。</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(4)当用户为主持人时,可以选择是否开启电子白板;当主持人开启电子白板后,所有用户均可自由切换电子白板和会议视频。</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(5)每个用户的视频窗口上方均显示声音分贝条,根据声音大小自动渲染。</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(6)当用户关闭摄像头或者用户数量超过9个,不显示视频。</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(7)所有用户均可收发文字消息,包括带表情的文字消息。</span><span style="font-family: "Microsoft YaHei""> </span></p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">2.功能演示</span></h3>
<p class="p"><span style="font-family: "Microsoft YaHei"; font-size: 16px"> 在银河麒麟上运行: </span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei""> <img src="https://img2023.cnblogs.com/blog/513369/202305/513369-20230505103531536-656662149.png"><span style="font-size: 14px"> </span></span></p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">3.布局风格</span></h3>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(1)当只有一个人开启视频时,采用大视窗显示 </span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(2)当2~4人开启视频时,使用2x2布局</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(3)当超过4人开启视频时,使用3x3布局</span></p>
<p><span style="font-family: "Microsoft YaHei""> </span></p>
<h2><span style="font-size: 18pt; font-family: "Microsoft YaHei"">二.<strong>开发环境</strong></span></h2>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">1.开发工具:</span></h3>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">Visual Studio 2022</span> </p>
<h3><span style="font-family: "Microsoft YaHei"">2. 开发框架:</span> </h3>
<p><span style="font-family: "Microsoft YaHei"">.NET Core 3.1,.NET 6,.NET 7</span> </p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">3.开发语言:</span></h3>
<p><span style="font-size: 18px; font-family: "Microsoft YaHei""><span style="font-size: 16px">C#</span></span></p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">4.其它框架:</span></h3>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">CPF.net UI 框架、OMCS 语音视频框架</span><span style="font-family: "Microsoft YaHei""> </span></p>
<h2><span style="font-size: 18pt; font-family: "Microsoft YaHei"">三.<strong>具体实现</strong></span></h2>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">1. 新用户进入会议房间</span></h3>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(1)视频显示窗口控件VideoPanel </span></p>
<p class="p"><span style="font-family: "Microsoft YaHei""><span style="font-size: 16px"> 预定</span><span style="font-size: 16px">SomeoneJoin事件,</span><span style="font-size: 16px">当新的用户加入房间时,将触发该事件:</span></span></p>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei""><span style="color: rgba(0, 0, 255, 1)">this</span>.chatGroup.SomeoneJoin += <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 128, 128, 1)">CbGeneric</span><<span style="color: rgba(0, 128, 128, 1)">IChatUnit</span>>(chatGroup_SomeoneJoin);</span></pre>
</div>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei""><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> chatGroup_SomeoneJoin(<span style="color: rgba(0, 128, 128, 1)">IChatUnit</span> unit)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke(</span><span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 128, 128, 1)">CbGeneric</span><<span style="color: rgba(0, 128, 128, 1)">IChatUnit</span>>(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.chatGroup_SomeoneJoin), unit);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
<span style="color: rgba(0, 128, 128, 1)">VideoPanel</span> panel </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(0, 128, 128, 1)"> VideoPanel</span>();
panel.Initialize(unit, </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
<span style="color: rgba(0, 128, 128, 1)">VideoHidePanel</span> videoHidePanel </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(0, 128, 128, 1)"> VideoHidePanel</span>();
videoHidePanel.Initialize(</span><span style="color: rgba(0, 0, 255, 1)">false</span>, unit,<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
<span style="color: rgba(0, 128, 128, 1)">VideoAllPanel</span> videoAllPanel;
videoAllPanel.videoPanel </span>=<span style="color: rgba(0, 0, 0, 1)"> panel;
videoAllPanel.videoHidePanel </span>=<span style="color: rgba(0, 0, 0, 1)"> videoHidePanel;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (panel.ConnectCameraResult !=<span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(0, 128, 128, 1)"> ConnectResult</span>.Succeed)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.flowLayoutPanel2.Children.Insert(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, videoHidePanel);
videoHidePanel.cameraVisibilityChange(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isVideoShowBeyond)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.flowLayoutPanel2.Children.Insert(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, videoHidePanel);
videoHidePanel.cameraVisibilityChange(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Children.Insert(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.videoShowCount, panel);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.TotalNumChange(<span style="color: rgba(0, 0, 255, 1)">this</span>.chatGroup.GetOtherMembers().Count + <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);
panel.ChangeState </span>+=<span style="color: rgba(0, 0, 0, 1)"> Panel_ChangeState;
panel.CameraStateChange </span>+=<span style="color: rgba(0, 0, 0, 1)"> Panel_CameraStateChange;
panel.VideoByCount </span>+=<span style="color: rgba(0, 0, 0, 1)"> Panel_VideoByCount;
panel.VideoConnect </span>+=<span style="color: rgba(0, 0, 0, 1)"> Panel_VideoConnect;
unit.Tag </span>=<span style="color: rgba(0, 0, 0, 1)"> videoAllPanel;
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.VideoNumBeyond();
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.VideoSizeChange(<span style="color: rgba(0, 0, 255, 1)">new</span> System.Windows.Size(<span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Width, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.cameraViewbox.FlowLayoutPanel.Height));
}
}</span></span></pre>
</div>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei""> 其中 VideoPanel 是视频窗口显示控件,初始化如下:</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei""><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Initialize(IChatUnit unit, <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> myself)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.pictureBox_Mic.RenderSize = <span style="color: rgba(0, 0, 255, 1)">new</span> System.Windows.Size(<span style="color: rgba(128, 0, 128, 1)">24</span>, <span style="color: rgba(128, 0, 128, 1)">24</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit =<span style="color: rgba(0, 0, 0, 1)"> unit;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.isMySelf =<span style="color: rgba(0, 0, 0, 1)"> myself;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.toolStrip1.Text =<span style="color: rgba(0, 0, 0, 1)"> chatUnit.MemberID;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">初始化麦克风连接器 </span>
<span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit.MicrophoneConnector.ConnectEnded += <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 128, 128, 1)">CbGeneric</span><<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 128, 128, 1)">ConnectResult</span>><span style="color: rgba(0, 0, 0, 1)">(MicrophoneConnector_ConnectEnded);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit.MicrophoneConnector.OwnerOutputChanged += <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 128, 128, 1)">CbGeneric</span><<span style="color: rgba(0, 0, 255, 1)">string</span>><span style="color: rgba(0, 0, 0, 1)">(MicrophoneConnector_OwnerOutputChanged);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isMySelf)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.chatUnit.MicrophoneConnector.BeginConnect(unit.MemberID);
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isMySelf)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.videoSizeShow.Visibility =<span style="color: rgba(0, 0, 0, 1)"> Visibility.Collapsed;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">初始化摄像头连接器</span>
<span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit.DynamicCameraConnector.SetViewer(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.cameraPanel1);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit.DynamicCameraConnector.VideoDrawMode =<span style="color: rgba(0, 0, 0, 1)"> VideoDrawMode.Scale;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit.DynamicCameraConnector.ConnectEnded += <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 128, 128, 1)">CbGeneric</span><<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 128, 128, 1)">ConnectResult</span>><span style="color: rgba(0, 0, 0, 1)">(DynamicCameraConnector_ConnectEnded);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit.DynamicCameraConnector.OwnerOutputChanged += <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 128, 128, 1)">CbGeneric</span><<span style="color: rgba(0, 0, 255, 1)">string</span>><span style="color: rgba(0, 0, 0, 1)">(DynamicCameraConnector_OwnerOutputChanged);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit.DynamicCameraConnector.Disconnected += <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 128, 128, 1)">CbGeneric</span><<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 128, 128, 1)">ConnectorDisconnectedType</span>><span style="color: rgba(0, 0, 0, 1)">(DynamicCameraConnector_Disconnected);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.chatUnit.DynamicCameraConnector.OwnerVideoSizeChanged +=<span style="color: rgba(0, 0, 0, 1)"> DynamicCameraConnector_OwnerVideoSizeChanged;
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.chatUnit.DynamicCameraConnector.BeginConnect(unit.MemberID);
}</span></span></pre>
</div>
<p><span style="font-family: "Microsoft YaHei""><span style="font-size: 16px"> 当新用户进入房间时,房间内其他用户通过</span><span style="font-size: 16px">MicrophoneConnector麦克风连接器和</span><span style="font-size: 16px">DynamicCameraConnector摄像头连接器连接到该用户的麦克风和摄像头</span></span></p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">(2)开启或关闭摄像头、麦克风、扬声器<br></span></p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei""> 以开启或关闭摄像头为例:</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei""><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> skinCheckBox_camera_MouseDown(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, MouseButtonEventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.isMyselfVideo = !<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isMyselfVideo;
System.Windows.Controls.Image imageVideo </span>= sender <span style="color: rgba(0, 0, 255, 1)">as</span><span style="color: rgba(0, 0, 0, 1)"> System.Windows.Controls.Image;
imageVideo.Source </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.isMyselfVideo ?<span style="color: rgba(0, 0, 0, 1)"> ResourceManager.Singleton.CameraOpenImage : ResourceManager.Singleton.CameraCloseImage;
imageVideo.ToolTip </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.isMyselfVideo ? <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">摄像头:开</span><span style="color: rgba(128, 0, 0, 1)">"</span> : <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">摄像头:关</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
<span style="color: rgba(0, 128, 128, 1)">VideoPanel</span> myPanel </span>= ((<span style="color: rgba(0, 128, 128, 1)">VideoAllPanel</span>)<span style="color: rgba(0, 0, 255, 1)">this</span>.chatGroup.GetMember(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loginId).Tag).videoPanel;
<span style="color: rgba(0, 128, 128, 1)">VideoHidePanel</span> myHidePanel </span>= ((<span style="color: rgba(0, 128, 128, 1)">VideoAllPanel</span>)<span style="color: rgba(0, 0, 255, 1)">this</span>.chatGroup.GetMember(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loginId).Tag).videoHidePanel;
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.VideoNumBeyond();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isVideoShowBeyond)
{
myHidePanel.cameraVisibilityChange(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isMyselfVideo);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">isMyselfVideo)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.cameraViewbox.FlowLayoutPanel.Children.Remove(myPanel);
myPanel.cameraPanel1.ClearImage();
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.flowLayoutPanel2.Children.Insert(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, myHidePanel);
myHidePanel.cameraVisibilityChange(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.flowLayoutPanel2.Children.Remove(myHidePanel);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Children.Insert(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.videoShowCount, myPanel);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (IsInitialized)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.multimediaManager.OutputVideo = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isMyselfVideo;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.VideoSizeChange(<span style="color: rgba(0, 0, 255, 1)">new</span> System.Windows.Size(<span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Width, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.cameraViewbox.FlowLayoutPanel.Height));
}
}</span></span></pre>
</div>
<p><span style="font-family: "Microsoft YaHei""><span style="font-size: 16px">其中通过多媒体管理器</span><span style="font-size: 16px">multimediaManager的OutputVideo属性,设置是否将采集到的视频输出,进而控制摄像头的开启或关闭。<br></span></span></p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">2. 布局切换</span></h3>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(1)根据开启视频的用户数量可分为 1x1、2x2、3x3三种布局格式</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(2)根据不同的布局格式以及外部控件容器的宽高,手动计算视频控件的宽高。</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei""><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> VideoSizeChange(<span style="color: rgba(0, 128, 128, 1)">Size</span> size)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Children.Count > <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 128, 128, 1)">VideoPanel</span> panel <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.cameraViewbox.FlowLayoutPanel.Children)
{</span>
panel.Height = (size.Height - <span style="color: rgba(128, 0, 128, 1)">6</span>) / <span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">;
panel.Width </span>= (size.Width - <span style="color: rgba(128, 0, 128, 1)">12</span>) / <span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Children.Count <= <span style="color: rgba(128, 0, 128, 1)">4</span> && <span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Children.Count > <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 128, 128, 1)">VideoPanel</span> panel <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.cameraViewbox.FlowLayoutPanel.Children)
{
panel.Height </span>= (size.Height - <span style="color: rgba(128, 0, 128, 1)">4</span>) / <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
panel.Width </span>= (size.Width - <span style="color: rgba(128, 0, 128, 1)">8</span>) / <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Children.Count == <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 128, 128, 1)">VideoPanel</span> panel <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.cameraViewbox.FlowLayoutPanel.Children)
{
panel.Height </span>= size.Height - <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
panel.Width </span>= size.Width - <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}</span></span></pre>
</div>
<p class="p"><span style="font-family: "Microsoft YaHei""> <span style="font-size: 16px">通过流式布局控件的特性:超过流式控件的宽度,子控件将自动换行,修改视频控件的宽高;</span></span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei""> 外部容器实际容纳所有视频控件的宽高为:外部容器的宽高减去所有视频控件的外边距;</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei""> 当只有一个用户开启视频,即将使用1x1布局时,视频控件宽高即为外部容器实际容纳所有视频控件的宽高;</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei""> 当2~4人开启视频,即将使用2x2布局时,视频控件宽高即为外部容器实际容纳所有视频控件的宽高的1/2,此时每个视频控件将占外部控件的1/4;</span></p>
<p><span style="font-family: "Microsoft YaHei""> <span style="font-size: 16px">当超过4人开启视频,即将使用3x3布局时,视频控件宽高即为外部容器实际容纳所有视频控件的宽高的1/3,此时每个视频控件将占外部控件的1/9</span></span></p>
<h3><span style="font-family: "Microsoft YaHei""> </span><span style="font-size: 18px; font-family: "Microsoft YaHei"">3.自定义消息类型</span></h3>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei""><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(0, 128, 128, 1)"> InformationTypes</span>
{
</span><span style="color: rgba(0, 0, 255, 1)">#region</span> <span style="color: rgba(0, 128, 0, 1)">广播消息</span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 广播聊天信息
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> BroadcastChat = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 广播共享桌面
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> BroadcastShareDesk = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 广播白板
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> BroadcastWhiteBoard = <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">#endregion</span>
<span style="color: rgba(0, 0, 255, 1)">#region</span> <span style="color: rgba(0, 128, 0, 1)">给新加入成员发送消息</span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 共享桌面
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> ShareDesk = <span style="color: rgba(128, 0, 128, 1)">53</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 电子白板
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> WhiteBoard = <span style="color: rgba(128, 0, 128, 1)">54</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">#endregion</span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 获取服务端 组扩展信息
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> GetGroupExtension = <span style="color: rgba(128, 0, 128, 1)">101</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></span></pre>
</div>
<p class="p"><span style="font-family: "Microsoft YaHei""><span style="font-size: 16px">(1)当用户发送聊天消息时,将通过BroadcastChat向所有在线用户广播聊天消息;当用户开启桌面共享时,将通过BroadcastShareDesk向所有在线用户广播桌面共享消息;当主持人开启电子白板时,将通过</span><span style="font-size: 16px">BroadcastWhiteBoard </span></span></p>
<p class="p"><span style="font-family: "Microsoft YaHei""><span style="font-size: 16px">向所有在线用户</span><span style="font-size: 16px">广播电子白板消息。</span></span></p>
<p class="p"><span style="font-family: "Microsoft YaHei""><span style="font-size: 16px">(2)当用户上线时,如果有用户开启桌面共享,就将通过</span><span style="font-size: 16px">ShareDesk </span><span style="font-size: 16px">向新用户发送桌面共享消息;如果主持人开启电子白板,就将通过</span>WhiteBoard<span style="font-size: 16px">向新用户发送电子白板消息。</span></span></p>
<p class="p"><span style="font-family: "Microsoft YaHei""><span style="font-size: 16px">(3)用户将通过</span><span style="font-size: 16px">GetGroupExtension向服务端获取组扩展信息。 </span></span><span style="font-family: "Microsoft YaHei""> </span></p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">4.组扩展信息</span></h3>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei""><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(0, 128, 128, 1)"> GroupExtension</span>
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 主持人ID
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> ModeratorID { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 正在共享远程桌面的用户ID
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> DesktopSharedUserID { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 主持人是否开启白板
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsModeratorWhiteBoardNow { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></span></pre>
</div>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(1)ModeratorID 表示当前房间主持人的ID;</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(2)DesktopSharedUserID 正在桌面共享的用户ID;若值为null,表示当前房间内无人开启桌面共享,客户端通过该值判断当前是否有用户开启桌面共享;当用户开启或关闭桌面共享时,都将手动修改该值;</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(3)IsModeratorWhiteBoardNow表示当前主持人是否开启电子白板;当主持人开启或关闭电子白板时,都将手动修改该值。</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei""> </span></p>
<h2><span style="font-family: "Microsoft YaHei""><span style="font-size: 18pt">四.<strong>源码下载</strong></span></span></h2>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">1. 源码项目说明</span></h3>
<p><span style="font-family: "Microsoft YaHei""> <span style="font-size: 16px"> <span style="color: rgba(0, 0, 255, 1)"><span style="color: rgba(0, 0, 255, 1)">源码下载</span></span></span></span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(1)OVCS.ServerLinux :视频会议 Linux 服务端</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(2)OVCS.ClientLinux :视频会议 Linux 客户端</span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei""> 注: Linux客户端内置的是x86/x64非托管so库,若需要其它架构的so,请联系QQ:2027224508 获取。 </span></p>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">(3)另附上Android客户端的源码:<span style="color: rgba(0, 0, 255, 1)"><span style="color: rgba(0, 0, 255, 1)">Android端</span></span> 。</span></p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">2. 部署运行说明</span></h3>
<p><span style="font-size: 18px; font-family: "Microsoft YaHei"">在部署之前,需要在linux服务端和客户端上分别安装 .Net core 3.1版本,命令行安装命令如下:</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei"">yum install dotnet-sdk-<span style="color: rgba(128, 0, 128, 1)">3.1</span></span></pre>
</div>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">检查版本安装情况</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei""> dotnet --version</span></pre>
</div>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei"">运行:</span></p>
<p class="p"><span style="font-family: "Microsoft YaHei""><span style="font-size: 16px">(1)在CentOS上启动OVCS.ServerLinux服务端:</span><span style="font-size: 16px">拷贝OVCS.ServerLinux项目下的Debug文件夹,到CentOS操作系统上,打开Debug -> netcoreapp3.1目录 ,在目录下打开终端,执行以下命令启动服务端</span></span></p>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei"">dotnet OVCS.ServerLinux.dll</span></pre>
</div>
<p class="p"><span style="font-size: 16px; font-family: "Microsoft YaHei""><img src="https://img2023.cnblogs.com/blog/513369/202305/513369-20230507092928179-680327353.png"></span></p>
<p class="p"><span style="font-family: "Microsoft YaHei""> </span><span style="font-size: 16px; font-family: "Microsoft YaHei"">(2)在麒麟或统信UOS、Ubuntu上运行OVCS.ClientLinux客户端:拷贝OVCS.ClientLinux项目下的Debug文件夹,到麒麟或统信UOS、Ubuntu操作系统上,打开Debug -> netcoreapp3.1目录 ,在目录下打开终端,执行以下命令启动客户端</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: "Microsoft YaHei"">dotnet OVCS.ClientLinux.dll</span></pre>
</div>
<p><span style="font-family: "Microsoft YaHei""> </span></p><br><br>
来源:https://www.cnblogs.com/shawshank/p/17390248.html
頁:
[1]