【问题标题】:C# Screen streaming programC# 屏幕流式传输程序
【发布时间】:2015-12-22 17:33:42
【问题描述】:

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

实际上,该程序在 TCP protocol 上运行,并使用 Desktop duplication API - 一项很酷的服务,支持非常快速的屏幕捕获并提供有关 MovedRegions 的信息(仅更改其位置的区域在屏幕上但仍然存在)和UpdatedRegions(更改区域)。

桌面复制有 2 个重要的属性 - 2 字节数组 一个数组用于 previouspixels 和一个 NewPixels 数组。每 4 个字节代表 RGBA 形式的一个像素,例如,如果我的屏幕为 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 速率观看,我将不得不在这方面做一点工作 - 尽量减少网络流量和带宽使用

我正在寻找提高程序效率的建议和创意 - 主要是在网络部分发送的数据(通过以其他方式或任何其他想法打包)我将不胜感激任何帮助和想法。谢谢.

【问题讨论】:

  • 你的问题有点含糊。您应该指定要优化的部分。目前,这个问题有太多潜在的答案,这可能会导致投反对票,并且由于过于广泛而被搁置。我将举一个例子来说明它的广泛性。您想优化代码、发送数据的方式、压缩方式还是更新屏幕的方式?
  • @dakre18 感谢您的关注,我主要寻找数据压缩 - 我需要专注于最小化网络流量 - 也许以其他方式打包图形数据......我不知道那是我写的问题:)
  • 你以前问过这个问题。
  • @harold 我问过类似的问题-您可能会注意到这里的数据组织方法发生了变化。
  • 它是通用的脚轮,还是用于流式传输的特定类型的应用程序/桌面?例如。当您知道会有很多相同颜色的补丁时,简单压缩可能是一种快速且良好的压缩选项。如果是 3D 游戏,其中所有像素都可能发生变化并且几乎没有任何相同颜色的补丁存在,那么 jpg 或 H.264 编码可能会更好。

标签: c# performance sockets


【解决方案1】:

对于 1920 x 1080、4 字节颜色的屏幕,每帧大约 8 MB。使用 20 FPS,您有 160 MB/s。所以从 8 MB 到 200 KB(4 MB/s @ 20 FPS)是一个很大的改进。

我想让您注意我不确定您是否关注的某些方面,希望对您有所帮助。

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

上述想法可以叠加应用以获得更好的用户体验。最终,这取决于您的应用程序和最终用户的具体情况。

编辑:

【讨论】:

  • 感谢您的帮助。几点作为评论。 1.我虽然为不同区域实施简单的rle压缩。 (在异或他们的缓冲区之后) - 你认为它可以得到好的结果吗? 2.关于质量部分——你真的是指每个通道 2 位吗? ?这意味着最多 2^2 个选项。 .像素将从它们的源头被破坏。
  • 3.我现在的目标是完整的桌面流。 ;)4. 缩放图像也会严重破坏质量——也许将区域转换为 Jpeg 是个好主意。8. 没有重叠区域 ;) 桌面 api 工作得很好——这是我唯一满意的.
  • 在你的情况下,你必须看看 rle 是否比 lz4 工作得更好,但我对此表示怀疑。如果您使用每种颜色 2 位,它看起来不如每种颜色 8 位,但是您将带宽需求减少了 4。您可以说每种颜色使用 4 位,看看是否可以接受。你必须打那个电话。正如我在帖子中提到的,为了减少带宽,您应该查看带有有损视频编码选项的 RTP
  • 实际上我认为使用 rle(非常基本的,而不是位级别)加上使用 lz4。 .I'll have to check this and let me ask,如果屏幕上有一个像素(45,220,85),它在位减少操作后会如何? (4,2,8)?它看起来会完全不同。 ..几个像素看起来不错,但更多的是它..图像会抽搐并导致大量损坏的数据。在这种情况下,我能做的最大的事情就是 lsb 编码...
  • 在压缩之上,您可以一次在每条水平线上发送一组像素,并将帧速率调整为仅允许每秒 15/30 帧。这将提升大量网络流量。
【解决方案2】:

犀利,

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

我建议看看类似https://msdn.microsoft.com/en-us/library/windows/desktop/dd797816(v=vs.85).aspx

【讨论】:

  • 我很久以前就听说过那个编码器,但我实际上如何在我的 c# 项目中实现它?感谢您的关注:)
  • 我在 C# 中使用 HiDef 视频流时遇到的问题是,当您谈论通过铜线传输大量实时数据时,该框架会变慢。我最近使用提供的 C# 库测试了通过非常昂贵的 HD/SDI 编码器卡输出 HD/SDI 数据的一些帧速率。我的最大帧速率约为 30fps,而我的代码中没有生成任何图形或叠加层。由于这个原因,大多数硬件供应商不提供 C# 库,这意味着您必须为 C++ 库编写 CLI 包装器。或者将编码过程分离成一个C++ App
  • @ AaronThomas 我明白了.. 但是“分离编码过程”是什么意思?我认为这里没有真正的性能问题,因为处理部分完成得相当快......
  • 您需要更多信息吗?我很乐意用更多示例和解释来编辑我的原始答案。
  • @Aaron Thomas CLI C++ 应用程序的运行速度与 C# 应用程序一样慢......老实说,我没有看到两者之间有很大的区别。当然,我们可以在 C++ 中做的比在 C# 中做的更多,但是 VC++ 和 C# 都使用 .NET 框架 CLI 和内存管理缓冲区。就像使用 C++ 和 COM,或者其他什么...
猜你喜欢
  • 1970-01-01
  • 2021-11-10
  • 1970-01-01
  • 1970-01-01
  • 2017-03-29
  • 2011-11-07
  • 2011-02-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多