最近我一直在做一个简单的屏幕共享程序.
实际上,该程序可以TCP protocol
使用桌面复制API - 一种很酷的服务,支持非常快速的屏幕捕获,还提供有关MovedRegions
(仅改变其在屏幕上的位置但仍然存在的UpdatedRegions
区域)和(更改区域)的信息.
桌面复制有2个重要属性 - 2个字节数组,一个数组previouspixels
和一个NewPixels
数组.每4个字节代表RGBA形式的一个像素,例如,如果我的屏幕是1920 x 1080,则缓冲区大小为1920 x 1080*4.
以下是我的策略的重要亮点
在初始状态(第一次)我发送整个像素缓冲区(在我的情况下它是1920 x 1080*3) - alpha组件在屏幕上总是255 :)
从现在开始,我遍历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]); } } }
我使用用c#编写的lz4包装器压缩缓冲区(参见lz4.NET@github).然后,我在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
请记住,您需要使用某种实时协议来传输数据.这背后的想法是,如果你的一个框架使其延迟到目的地机器,你可以放下接下来的几帧来追赶.否则你将陷入常年滞后的境地,我怀疑用户会喜欢.
你总是可以牺牲质量来提高性能.您在类似技术(如MS远程桌面,VNC等)中看到的最简单的此类机制是发送8位颜色(每个2位的ARGB)而不是您正在使用的3字节颜色.
改善您的情况的另一种方法是关注屏幕上您要流式传输的特定矩形,而不是流式传输整个桌面.这将减小框架本身的尺寸.
另一种方法是在传输之前将屏幕图像缩放为较小的图像,然后在显示之前将其缩放回正常状态.
发送初始屏幕后,您始终可以在newpixels
和之间发送差异previouspixels
.不用说原始屏幕和差异屏幕都将被LZ4压缩/解压缩.如果你使用一些有损算法来压缩差异,你应该经常发送完整的数组而不是diff.
UpdatedRegions是否有重叠区域?可以优化以不发送重复的像素信息吗?
上述想法可以一个接一个地应用,以获得更好的用户体验.最终,它取决于您的应用程序和最终用户的具体情况.
编辑:
颜色量化可用于减少用于颜色的位数.以下是颜色量化的具体实现的一些链接
优化图像颜色量化
nQuant图书馆
通常,量化的颜色存储在调色板中,并且只有该调色板的索引被提供给解码逻辑