zoukankan      html  css  js  c++  java
  • windows下实现屏幕分享(C#)

    采用UDP广播进行数据的传输,实现windows下进行低延迟的屏幕共享。

    开发语言:C#

    第三方组件:Redis

    1.实现思路

     

    总体流程图

    DGIS.DesktopShare实现windows下屏幕分享低延迟功能,按照服务执行位置由三部分构成:发起端、接收端、缓存端。

    通过UDP广播实现发起端和接收端的通讯,是为了尽量的减少通讯负载和降低延迟。众所周知UDP是所有通讯协议中延迟最低的(但也有受网络因素丢包的问题,这里作为局域网同屏,暂不考虑丢包问题),而采用广播的方式可以有效的降低发起端的性能负担。

    增加一个Redis服务,是为了减少UDP广播数据,按照1920*1080分辨率的截屏数据来算,单张图片已经超过了UDP单包的最大数据量1472字节,倘若直接使用UDP传输截屏图片,需要额外的进行封包拆包,这样不仅浪费了程序执行时间,也增加了发送端和接收端的代码复杂度。本着最低延迟的目的,将真实的图片数据存入Redis缓存,只通过UDP广播Redis中对应的UID信息即可,这也是本程序最核心的地方。接收端接收到UID的数据后,再自行去获取Redis中真实的数据进行解析。

    2.代码结构

     

    DGIS.DesktopShare.Service的代码结构分为Frm(窗体)、IService(接口)、Service(实现)三个部分。引用的第三方dll有Redis相关操作库、屏幕获取相关库和DGIS开头的辅助操作库。

    Frm:

    ImgDisplyFrm是接收端的默认显示界面,包含一个PictureBox控件,显示接收到的屏幕图像。

    IService:

    IDesktopShareService屏幕共享操作接口,主要方法有4个。

    注释的已经很明确了,这里不多说。

    Service:

    DesktopShareService:IDesktopShareService的实现类,里面是详细的实现方法。核心的代码有下面几个地方:

     1  public void Start(int frameRate, bool mouseDisplay, int port)
     2         {
     3             _udpService = new UdpBroadcastService(port,
     4                 bytes =>
     5                 {
     6                     //deskShareFrm.ShowImg(bytes);
     7                 });
     8 
     9             _desktopCapturer = CapturerFactory.CreateDesktopCapturer(frameRate, mouseDisplay);
    10             _desktopCapturer.ImageCaptured += ImageCaptured;
    11             _desktopCapturer.Start();
    12 
    13             //队列循环处理积压的图片
    14             //Task.Factory.StartNew(() =>
    15             //{
    16             //    while (true)
    17             //    {
    18             //        if (_bitmapQueue.Count > 0)
    19             //        {
    20             //            Bitmap bitmap = _bitmapQueue.Dequeue();
    21             //            SendCommand(bitmap);
    22             //        }
    23 
    24             //    }
    25             //});
    26         }

    开始屏幕分享,_udpService 初始化,屏幕抓取设置以及开启屏幕抓取。

    1  /// <summary>
    2         /// 屏幕截屏结果
    3         /// </summary>
    4         /// <param name="bitmap"></param>
    5         private void ImageCaptured(Bitmap bitmap)
    6         {
    7             //_bitmapQueue.Enqueue(bitmap);
    8             SendCommand(bitmap);
    9         }
     1  /// <summary>
     2         /// 发送命令
     3         /// </summary>
     4         /// <param name="bitmap"></param>
     5         private void SendCommand(Bitmap bitmap)
     6         {
     7             //Bitmap newBitmap = ESBasic.Helpers.ImageHelper.RoundSizeByNumber(bitmap, 4);
     8             //bitmap.Dispose();
     9             //bitmap = null;
    10 
    11             string uid = Guid.NewGuid().ToString();
    12             Console.WriteLine(string.Format("图片获取时间:{0}-{1}", DateTime.Now.ToString("mm:ss:ffff"), uid));
    13 
    14             byte[] bytes = ImgConvert.ConvertByte(bitmap);
    15             Console.WriteLine(string.Format("缓存转换时间:{0}-{1}", DateTime.Now.ToString("mm:ss:ffff"), uid));
    16             bitmap.Dispose();
    17             bitmap = null;
    18 
    19             _redisCacheHelper.Add<byte[]>(uid, bytes, TimeSpan.FromMilliseconds(5000));
    20             Console.WriteLine(string.Format("缓存插入时间:{0}-{1}", DateTime.Now.ToString("mm:ss:ffff"), uid));
    21 
    22             byte[] sendBytes = StringToBytes(uid);
    23             _udpService.Send(sendBytes, sendBytes.Length);
    24             Console.WriteLine(string.Format("图片发送时间:{0}-{1}", DateTime.Now.ToString("mm:ss:ffff"), uid));
    25 
    26             sendBytes = null;
    27             bytes = null;
    28         }

    抓取到屏幕图片的结果是Bitmap,这里需要将Bitmap转换为byte[],因为在测试中Redis直接存入Bitmap取出时候有些异常。待Redis存入图片数据后,再通过UDP发送图片数据。来看下测试数据,图片获取到发送的耗时情况。

     

    主要的耗时在图片转换这个地方,耗时约10毫秒,redis存入时间是很快的6毫秒左右,UDP发送时间基本为0.

     

     1 public void Recive(int port)
     2         {
     3             _imgDisplyFrm = new ImgDisplyFrm();
     4             _imgDisplyFrm.Closed += (e, o) =>
     5             {
     6                 if (_udpService != null)
     7                 {
     8                     _udpService.Dispose();
     9                     _udpService = null;
    10                 }
    11             };
    12 
    13             _udpService = new UdpBroadcastService(port,
    14                 bytes =>
    15                 {
    16                     ShowImg(bytes);
    17                 });
    18             _imgDisplyFrm.ShowDialog();
    19         }
     1 /// <summary>
     2         /// 显示图像
     3         /// </summary>
     4         /// <param name="reciveBytes"></param>
     5         private void ShowImg(byte[] reciveBytes)
     6         {
     7             Bitmap bitmap = ParseImg(reciveBytes);
     8 
     9             if (_imgDisplyFrm != null)
    10                 _imgDisplyFrm.ShowImg(bitmap);
    11         }
     1 /// <summary>
     2         /// 解析图片
     3         /// </summary>
     4         /// <param name="bytes"></param>
     5         /// <returns></returns>
     6         private Bitmap ParseImg(byte[] bytes)
     7         {
     8             string uid = BytesToString(bytes);
     9             Console.WriteLine(string.Format("消息接收时间:{0}-{1}", DateTime.Now.ToString("mm:ss:ffff"), uid));
    10 
    11             byte[] imgBytes = _redisCacheHelper.Get<byte[]>(uid);
    12             Console.WriteLine(string.Format("缓存获取时间:{0}-{1}", DateTime.Now.ToString("mm:ss:ffff"), uid));
    13 
    14             if (imgBytes != null)
    15             {
    16                 Bitmap bitmap = ImgConvert.ConvertImg(imgBytes);
    17                 Console.WriteLine(string.Format("转码完成时间:{0}-{1}", DateTime.Now.ToString("mm:ss:ffff"), uid));
    18 
    19                 return bitmap;
    20             }
    21             else
    22                 return null;
    23         }

    接收端同样也是开启一个_udpService ,初始化,进行消息监听,接收到消息后进入ShowImg(显示图像)方法,中间主要代码是ParseImg(图片获取解析)这部分,这部分实现将真实图片数据从redis中获取。看下耗时情况。

    这里能看到,消息的发送和接收之间耗时约为0(这里考虑本机没有经过网卡,实际应该有100毫秒左右延迟),redis缓存获取时间非常的快4毫秒左右,转码耗时30毫秒。

    以上综合计算,从屏幕采集到终端接收解析总共耗时50毫秒左右,加上UDP广播理论上的100毫秒延迟,150毫秒是能够达到的。

    下图是真实环境中两台电脑的屏幕分享延迟图片。

    从截图上看,延迟在170毫秒,由此看出这种方案是比较可行的。

    放一张测试效果图。

     

    3.源码

    源码放到了CSDN上,博客园不知道如何上传附件。万恶的CSDN居然至少需要设置2分了。

    GIS.DesktopShare 下载地址:http://download.csdn.net/download/jiangfei200809/10229866 

     要是没有分的请移步去百度网盘下载吧,载地址:https://pan.baidu.com/s/1i5XlLnZ

     

    代码开源地址https://gitee.com/JFly/DGIS.BigEye.git

    2018.1.30更新:增加了声卡声音采集同步传输

    时间仓促,代码和文章都写的不好,只希望这种思路的提出能为有这方面需求的同行提供点参考。

    更多个人工作中的项目请访问我的个人网站:www.88gis.cn

  • 相关阅读:
    单线制DS18B20温度传感器LED数码管显示当前的温度值
    AD转换器的主要指标
    关于swiper动态更改,无法更新的悖论
    在360的兼容模式下关于innerHTML=“”,引发的问题
    比较两个字符串的相似度
    WebSocket使用
    事件绑定addEventListener
    插件开发优缺点
    插件开发宗旨
    学会用博客
  • 原文地址:https://www.cnblogs.com/tracyjfly/p/8377961.html
Copyright © 2011-2022 走看看