你卡了 發表於 2023-5-16 09:07:00

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

<p>&nbsp; &nbsp; &nbsp; 信创是现阶段国家发展的重要战略之一,面对这一趋势,所有的软件应用只有支持信创国产化的基础软硬件设施,在未来才不会被淘汰。那么,如何可以使用C#来实现支持信创环境的视频会议系统吗?答案是肯定的。</p>
<p class="p">&nbsp; &nbsp; &nbsp; 本文讲述如何使用C#来实现视频会议系统的Linux服务端与Linux客户端,并让其支持国产操作系统(如银河麒麟,统信UOS)和国产CPU(如鲲鹏、龙芯、海光、兆芯、飞腾等)。&nbsp;</p>
<p class="p">&nbsp; &nbsp; &nbsp; 先看看该Demo在统信UOS上的运行效果:&nbsp;&nbsp;</p>
<p class="p">&nbsp; &nbsp; &nbsp; &nbsp;<img src="https://img2023.cnblogs.com/blog/513369/202305/513369-20230505103341413-2141774866.png"></p>
<h2><span style="font-size: 18pt; font-family: &quot;Microsoft YaHei&quot;">一.<strong>功能介绍</strong></span></h2>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;"><strong>1.基本功能</strong></span></h3>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(1)主持人:当进入同一房间的第一个用户默认成为主持人,默认打开麦克风。</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(2)当进入会议房间的每个人,都能自由选择是否开启摄像头、扬声器和麦克风。</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(3)当同一房间内无人开启桌面共享时,所有用户均可开启桌面共享,供其他用户观看其桌面,同一时间内只允许一个用户开启桌面共享。</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(4)当用户为主持人时,可以选择是否开启电子白板;当主持人开启电子白板后,所有用户均可自由切换电子白板和会议视频。</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(5)每个用户的视频窗口上方均显示声音分贝条,根据声音大小自动渲染。</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(6)当用户关闭摄像头或者用户数量超过9个,不显示视频。</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(7)所有用户均可收发文字消息,包括带表情的文字消息。</span><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span></p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">2.功能演示</span></h3>
<p class="p"><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px">&nbsp; &nbsp; &nbsp;在银河麒麟上运行:&nbsp;</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">&nbsp; &nbsp; &nbsp;<img src="https://img2023.cnblogs.com/blog/513369/202305/513369-20230505103531536-656662149.png"><span style="font-size: 14px">&nbsp;</span></span></p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">3.布局风格</span></h3>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(1)当只有一个人开启视频时,采用大视窗显示&nbsp;</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(2)当2~4人开启视频时,使用2x2布局</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(3)当超过4人开启视频时,使用3x3布局</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span></p>
<h2><span style="font-size: 18pt; font-family: &quot;Microsoft YaHei&quot;">二.<strong>开发环境</strong></span></h2>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">1.开发工具:</span></h3>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">Visual Studio 2022</span>&nbsp;</p>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">2. 开发框架:</span>&nbsp;</h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">.NET Core 3.1,.NET 6,.NET 7</span>&nbsp;</p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">3.开发语言:</span></h3>
<p><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;"><span style="font-size: 16px">C#</span></span></p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">4.其它框架:</span></h3>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">CPF.net UI 框架、OMCS 语音视频框架</span><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span></p>
<h2><span style="font-size: 18pt; font-family: &quot;Microsoft YaHei&quot;">三.<strong>具体实现</strong></span></h2>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">1. 新用户进入会议房间</span></h3>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(1)视频显示窗口控件VideoPanel&nbsp;</span></p>
<p class="p"><span style="font-family: &quot;Microsoft YaHei&quot;"><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: &quot;Microsoft YaHei&quot;"><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>&lt;<span style="color: rgba(0, 128, 128, 1)">IChatUnit</span>&gt;(chatGroup_SomeoneJoin);</span></pre>
</div>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;"><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>&lt;<span style="color: rgba(0, 128, 128, 1)">IChatUnit</span>&gt;(<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: &quot;Microsoft YaHei&quot;">&nbsp; &nbsp; &nbsp; 其中&nbsp;VideoPanel 是视频窗口显示控件,初始化如下:</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;"><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>&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 128, 128, 1)">ConnectResult</span>&gt;<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>&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>&gt;<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>&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 128, 128, 1)">ConnectResult</span>&gt;<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>&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>&gt;<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>&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 128, 128, 1)">ConnectorDisconnectedType</span>&gt;<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: &quot;Microsoft YaHei&quot;"><span style="font-size: 16px">&nbsp; &nbsp; &nbsp; 当新用户进入房间时,房间内其他用户通过</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: &quot;Microsoft YaHei&quot;">(2)开启或关闭摄像头、麦克风、扬声器<br></span></p>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">  以开启或关闭摄像头为例:</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;"><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: &quot;Microsoft YaHei&quot;"><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: &quot;Microsoft YaHei&quot;">2.&nbsp;布局切换</span></h3>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(1)根据开启视频的用户数量可分为 1x1、2x2、3x3三种布局格式</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(2)根据不同的布局格式以及外部控件容器的宽高,手动计算视频控件的宽高。</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;"><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 &gt; <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 &lt;= <span style="color: rgba(128, 0, 128, 1)">4</span> &amp;&amp; <span style="color: rgba(0, 0, 255, 1)">this</span>.cameraViewbox.FlowLayoutPanel.Children.Count &gt; <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: &quot;Microsoft YaHei&quot;">&nbsp; <span style="font-size: 16px">通过流式布局控件的特性:超过流式控件的宽度,子控件将自动换行,修改视频控件的宽高;</span></span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;"> 外部容器实际容纳所有视频控件的宽高为:外部容器的宽高减去所有视频控件的外边距;</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;"> 当只有一个用户开启视频,即将使用1x1布局时,视频控件宽高即为外部容器实际容纳所有视频控件的宽高;</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;"> 当2~4人开启视频,即将使用2x2布局时,视频控件宽高即为外部容器实际容纳所有视频控件的宽高的1/2,此时每个视频控件将占外部控件的1/4;</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp; <span style="font-size: 16px">当超过4人开启视频,即将使用3x3布局时,视频控件宽高即为外部容器实际容纳所有视频控件的宽高的1/3,此时每个视频控件将占外部控件的1/9</span></span></p>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">3.自定义消息类型</span></h3>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;"><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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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: &quot;Microsoft YaHei&quot;"><span style="font-size: 16px">(1)当用户发送聊天消息时,将通过BroadcastChat向所有在线用户广播聊天消息;当用户开启桌面共享时,将通过BroadcastShareDesk向所有在线用户广播桌面共享消息;当主持人开启电子白板时,将通过</span><span style="font-size: 16px">BroadcastWhiteBoard&nbsp;</span></span></p>
<p class="p"><span style="font-family: &quot;Microsoft YaHei&quot;"><span style="font-size: 16px">向所有在线用户</span><span style="font-size: 16px">广播电子白板消息。</span></span></p>
<p class="p"><span style="font-family: &quot;Microsoft YaHei&quot;"><span style="font-size: 16px">(2)当用户上线时,如果有用户开启桌面共享,就将通过</span><span style="font-size: 16px">ShareDesk&nbsp;</span><span style="font-size: 16px">向新用户发送桌面共享消息;如果主持人开启电子白板,就将通过</span>WhiteBoard<span style="font-size: 16px">向新用户发送电子白板消息。</span></span></p>
<p class="p"><span style="font-family: &quot;Microsoft YaHei&quot;"><span style="font-size: 16px">(3)用户将通过</span><span style="font-size: 16px">GetGroupExtension向服务端获取组扩展信息。&nbsp;</span></span><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span></p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">4.组扩展信息</span></h3>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;"><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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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: &quot;Microsoft YaHei&quot;">(1)ModeratorID 表示当前房间主持人的ID;</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(2)DesktopSharedUserID 正在桌面共享的用户ID;若值为null,表示当前房间内无人开启桌面共享,客户端通过该值判断当前是否有用户开启桌面共享;当用户开启或关闭桌面共享时,都将手动修改该值;</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(3)IsModeratorWhiteBoardNow表示当前主持人是否开启电子白板;当主持人开启或关闭电子白板时,都将手动修改该值。</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span></p>
<h2><span style="font-family: &quot;Microsoft YaHei&quot;"><span style="font-size: 18pt">四.<strong>源码下载</strong></span></span></h2>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">1. 源码项目说明</span></h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp; &nbsp;<span style="font-size: 16px"> &nbsp;&nbsp;<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: &quot;Microsoft YaHei&quot;">(1)OVCS.ServerLinux :视频会议 Linux 服务端</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(2)OVCS.ClientLinux :视频会议 Linux 客户端</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;注:&nbsp;Linux客户端内置的是x86/x64非托管so库,若需要其它架构的so,请联系QQ:2027224508 获取。&nbsp;&nbsp;</span></p>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(3)另附上Android客户端的源码:<span style="color: rgba(0, 0, 255, 1)"><span style="color: rgba(0, 0, 255, 1)">Android端</span></span>&nbsp; 。</span></p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">2.&nbsp;部署运行说明</span></h3>
<p><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">在部署之前,需要在linux服务端和客户端上分别安装 .Net core 3.1版本,命令行安装命令如下:</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;">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: &quot;Microsoft YaHei&quot;">检查版本安装情况</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;"> dotnet --version</span></pre>
</div>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">运行:</span></p>
<p class="p"><span style="font-family: &quot;Microsoft YaHei&quot;"><span style="font-size: 16px">(1)在CentOS上启动OVCS.ServerLinux服务端:</span><span style="font-size: 16px">拷贝OVCS.ServerLinux项目下的Debug文件夹,到CentOS操作系统上,打开Debug -&gt; netcoreapp3.1目录 ,在目录下打开终端,执行以下命令启动服务端</span></span></p>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;">dotnet OVCS.ServerLinux.dll</span></pre>
</div>
<p class="p"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;"><img src="https://img2023.cnblogs.com/blog/513369/202305/513369-20230507092928179-680327353.png"></span></p>
<p class="p"><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(2)在麒麟或统信UOS、Ubuntu上运行OVCS.ClientLinux客户端:拷贝OVCS.ClientLinux项目下的Debug文件夹,到麒麟或统信UOS、Ubuntu操作系统上,打开Debug -&gt; netcoreapp3.1目录 ,在目录下打开终端,执行以下命令启动客户端</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: &quot;Microsoft YaHei&quot;">dotnet OVCS.ClientLinux.dll</span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span></p><br><br>
来源:https://www.cnblogs.com/shawshank/p/17390248.html
頁: [1]
查看完整版本: C# 实现 Linux 视频会议(源码,支持信创环境,银河麒麟,统信UOS)