C#屏幕流媒体节目

最近我一直在做一个简单的屏幕共享程序。

实际上该程序适用于TCP protocol并使用桌面复制API – 一种支持非常快速的屏幕捕获的酷服务,还提供有关MovedRegions (仅改变其在屏幕上的位置但仍然存在的区域)和UpdatedRegions (更改区域)的信息。

桌面复制有2个重要属性 – 2个字节数组,一个用于previouspixels一个NewPixels数组和一个NewPixels数组。 每4个字节代表一个RGBAforms的像素,例如,如果我的屏幕是1920 x 1080,则缓冲区大小为1920 x 1080 * 4。

以下是我的策略的重要亮点

  1. 在初始状态(第一次)我发送整个像素缓冲区(在我的情况下它是1920 x 1080 * 3) – alpha组件在屏幕上始终为255 🙂
  2. 从现在开始,我遍历UpdatedRegions(它是一个矩形数组),我发送区域边界和Xo’r像素,如下所示:

      writer.Position = 0; var n = frame._newPixels; var w = 1920 * 4; //frame boundaries. var p = frame._previousPixels; foreach (var region in frame.UpdatedRegions) { writer.WriteInt(region.Top); writer.WriteInt(region.Height); writer.WriteInt(region.Left); writer.WriteInt(region.Width); for (int y = region.Top, yOffset = y * w; y < region.Bottom; y++, yOffset += w) { for (int x = region.Left, xOffset = x * 4, i = yOffset + xOffset; x < region.Right; x++, i += 4) { writer.WriteByte(n[i] ^ p[i]); //'n' is the newpixels buffer and 'p' is the previous.xoring for differences. writer.WriteByte(n[i+1] ^ p[i+1]); writer.WriteByte(n[i + 2] ^ p[i + 2]); } } } 
  3. 我使用用c#编写的lz4包装器压缩缓冲区(参见lz4.NET@github )。 然后,我在NetworkStream上写入数据。
  4. 我合并了接收器端的区域以获得更新的图像 – 这不是我们今天的问题:)

‘writer’是我编写的’QuickBinaryWriter’类的一个实例(只是为了再次重用相同的缓冲区)。

  public class QuickBinaryWriter { private readonly byte[] _buffer; private int _position; public QuickBinaryWriter(byte[] buffer) { _buffer = buffer; } public int Position { get { return _position; } set { _position = value; } } public void WriteByte(byte value) { _buffer[_position++] = value; } public void WriteInt(int value) { byte[] arr = BitConverter.GetBytes(value); for (int i = 0; i < arr.Length; i++) WriteByte(arr[i]); } } 

从许多方面来看,我已经看到发送的数据非常庞大,有时对于单帧更新,数据可能达到200kb(压缩后!)。 说实话 – 200kb真的没什么,但如果我想平滑地流式传输屏幕并且能够以高Fps速率观看,我将不得不对此进行一点点工作 – 最小化网络流量和带宽使用

我正在寻找建议和创意,以提高计划的效率 – 主要是在网络部分发送的数据(通过其他方式包装或任何其他想法)我会感激任何帮助和想法。谢谢。

对于屏幕为1920 x 1080的4字节颜色,​​您每帧大约需要8 MB。 使用20 FPS,您可以达到160 MB / s。 因此从8 MB到200 KB(4 MB / s @ 20 FPS)是一个很大的改进。

我想提醒你注意我不确定你关注的某些方面,希望它有所帮助。

  1. 压缩屏幕图像越多,可能需要的处理就越多
  2. 实际上,您需要专注于为一系列连续变化的图像设计的压缩机制,类似于video编解码器(尽管没有音频)。 例如:H.264
  3. 请记住,您需要使用某种实时协议来传输数据。 这背后的想法是,如果你的一个框架使其延迟到目的地机器,你可以放下接下来的几帧来追赶。 否则你将处于一个长期滞后的境地,我怀疑用户会喜欢。
  4. 你总是可以牺牲质量来提高性能。 您在类似技术(如MS远程桌面,VNC等)中看到的最简单的此类机制是发送8位颜色(每个2位的ARGB)而不是您正在使用的3字节颜色。
  5. 改善您的情况的另一种方法是关注屏幕上您要流式传输的特定矩形,而不是流式传输整个桌面。 这将减小框架本身的尺寸。
  6. 另一种方法是在传输之前将屏幕图像缩放为较小的图像,然后在显示之前将其缩放回正常。
  7. 发送初始屏幕后,您始终可以在newpixelspreviouspixels之间发送差异。 不用说原始屏幕和差异屏幕都将被LZ4压缩/解压缩。 如果你使用一些有损算法来压缩差异,你应该经常发送完整的数组而不是diff。
  8. UpdRegions是否有重叠区域? 可以优化以不发送重复的像素信息吗?

上述想法可以一个接一个地应用,以获得更好的用户体验。 最终,它取决于您的应用程序和最终用户的具体情况。

编辑:

  • 颜色量化可用于减少用于颜色的位数。 以下是颜色量化的具体实现的一些链接

    • 优化图像颜色量化
    • nQuant图书馆
  • 通常,量化的颜色存储在调色板中,并且只有该调色板的索引被提供给解码逻辑

Slashy,

由于您使用的是高分辨率帧,并且您希望获得良好的帧速率,因此您可能会考虑采用H.264编码。 我已经完成了HD / SDI广播video的一些工作,这完全取决于H.264,现在有点转向H.265。 广播中使用的大多数库都是用C ++编写的,以提高速度。

我建议看看这样的东西https://msdn.microsoft.com/en-us/library/windows/desktop/dd797816(v=vs.85).aspx