最近,我一直在开发一个简单的屏幕共享程序。
实际上,该程序基于 TCP 协议(protocol)
并使用 Desktop duplication API - 一项很酷的服务,支持非常快速的屏幕捕获,还提供有关 的信息MovedRegions
(只是改变了它们在屏幕上的位置但仍然存在的区域)和 UpdatedRegions
(已更改的区域)。
Desktop 副本有 2 个重要属性 - 2 字节数组,一个用于 previous-pixels
的数组和一个 NewPixels
数组。每 4 个字节代表 RGBA 形式的一个像素,例如,如果我的屏幕是 1920 x 1080,缓冲区大小是 1920 x 1080 * 4。
以下是我的策略的重要亮点
- 在初始状态(第一次)我发送整个像素缓冲区(在我的例子中是 1920 x 1080 * 3)- alpha 分量在屏幕上总是 255 :)
- 从现在开始,我遍历 UpdatedRegions(它是一个矩形数组),然后发送区域边界并对其中的像素进行 Xo,如下所示:
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]);
}
}
}
- 我使用用 c# 编写的 lz4 包装器压缩缓冲区(请参阅 lz4.NET。然后,我将数据写入 NetworkStream。
- 我合并接收方的区域以获得更新的图像 - 这不是我们今天的问题:)
'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)是一个很大的改进。
我想让你注意某些我不确定你关注的方面,希望它能有所帮助。
- 您压缩屏幕图像的次数越多,它可能需要的处理就越多
- 您实际上需要关注为一系列不断变化的图像设计的压缩机制,类似于视频编解码器(尽管没有音频)。例如:H.264
- 请记住,您需要使用某种实时协议(protocol)来传输数据。其背后的想法是,如果您的某一帧延迟到达目标机器,您不妨放弃接下来的几帧以进行追赶。否则你将处于长期滞后的状态,我怀疑用户会喜欢这种情况。
- 您总是可以为了性能而牺牲质量。您在类似技术(如 MS 远程桌面、VNC 等)中看到的最简单的此类机制是发送 8 位颜色(ARGB 每 2 位)而不是您正在使用的 3 字节颜色。
- 另一种改善情况的方法是将注意力集中在屏幕上您想要流式传输的特定矩形上,而不是流式传输整个桌面。这将减小框架本身的大小。
- 另一种方法是在传输之前将屏幕图像缩放为较小的图像,然后在显示之前将其缩放回正常图像。
- 发送初始屏幕后,您始终可以发送
newpixels
和previouspixels
之间的差异。不用说原始屏幕和差异屏幕都将被 LZ4 压缩/解压缩。如果您使用某种有损算法来压缩差异,您应该经常发送完整数组而不是差异。 - UpdatedRegions 是否有重叠区域?是否可以对其进行优化以不发送重复的像素信息?
上述想法可以一个接一个地应用,以获得更好的用户体验。最终,这取决于您的应用程序和最终用户的具体情况。
编辑:
Color Quantization可用于减少用于颜色的位数。下面是颜色量化具体实现的一些链接
通常量化后的颜色存储在 Color Palette and only the index into this palette 中交给解码逻辑
关于C# 屏幕流程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34421447/