【问题标题】:Optimizing speed of updating changed key/value pairs and serializing to byte array优化更新更改的键/值对和序列化为字节数组的速度
【发布时间】:2022-01-21 21:47:06
【问题描述】:

我的项目涉及捕获桌面帧并通过 TCP 套接字发送到接收设备进行渲染,我希望尽可能减少帧之间的延迟,最好总共低于 50 毫秒。

发送的数据是一个字节数组的DirectX DataStream,它只是代表每个像素的颜色。

对于 1920x1080 的显示,这会产生一个长度为 8294400 的字节数组,使用 Stream.CopyTo 需要不到 10 毫秒,这是合理的。

使用 LZ4 压缩生成的字节数组将其长度减少到 631126 字节,需要额外的 10 毫秒左右。

我希望通过仅发送更改的像素来进一步减小此大小。

我的第一个想法是使用Dictionary<int,int> 来存储上一帧发送的像素的缓存,然后与新帧进行比较:

private Dictionary<int, int> CachedPixels = new Dictionary<int, int>();

...

DataRectangle dataRect = surface.Map(SharpDX.DXGI.MapFlags.Read, out DataStream dataStream);

using(MemoryStream ms = new MemoryStream()) {
using(BinaryReader br = new BinaryReader(dataStream))
    int pixels = (int)(dataStream.Length / 4); //4 bytes per BGRA colour
    Dictionary<int, int> changedPixels = new Dictionary<int, int>();

    for(int i = 0; i < pixels; i++) {
        int col = br.ReadInt32();

        if(!CachedPixels.ContainsKey(i)) {
            CachedPixels.Add(i, col);
            changedPixels.Add(i, col);
        } else {
            if(col != CachedPixels[i]) {
                CachedPixels[i] = col;
                changedPixels.Add(i, col);
            }
        }
    }

    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(ms, newCache);
    
    return LZ4.Compress(ms.ToArray());

但这真的很慢。

如果我使用StopWatch 计算所用时间:

所有 2073600 像素的第一帧循环耗时 170 毫秒,使用BinaryFormatter 进行序列化耗时 2100 毫秒。

然后下一帧更新 379902 像素,循环需要 75 毫秒,序列化需要 356 毫秒。

我该如何优化这个?

【问题讨论】:

  • 几年前,我做了类似的东西,但不是为了图像。我会将整个区域视为许多小块(例如,64x64 像素)。对于每一帧,计算每个块的哈希值,然后将它们保存在矩阵中。此时,您可以检测哈希是否更改并发送相关的块内容。不过,总的来说,这不是一件容易的事。
  • 为什么不直接让 ffmpeg 捕获您的桌面并进行流式传输呢?这样你就不会最终重新实现 ffmpeg
  • 简单来说,由于gdigrab/dshow的限制,我的用例不支持ffmpeg。

标签: c# arrays dictionary optimization binaryformatter


【解决方案1】:

使用字典确实会影响您的表现。相反,保留上次屏幕截图中的内存流,将其位置设置回 0,然后将其与新截图中的新流一起读取并进行比较。这将大大缩短您的比较时间。

我可以在使用计算机时添加示例代码。基本上:

  1. 对于第一次捕获,设置 var lastStream = data from screen capture 并发送所有更改后的数据
  2. 在此处开始您的捕获循环并设置 var currentStream = data from screen capture
  3. 为每个流实例化二进制读取器
  4. 使用二进制读取器对每个像素执行整数比较
  5. 在 lastStream 上运行 Dispose()
  6. 设置lastStream = currentStream(取胜的参考变量!)
  7. 使用单独的任务通过网络发送更改,这样您的下一个帧进程迭代就不会在网络 IO 上等待
  8. 继续循环读取下一个捕获,这将设置 currentStream 的值

这将比条件逻辑和通过两个字典树的运行速度快得多。

【讨论】:

    猜你喜欢
    • 2019-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-03
    • 2016-04-26
    • 1970-01-01
    • 2019-02-16
    • 1970-01-01
    相关资源
    最近更新 更多