我的專案涉及捕獲桌面幀并通過 TCP 套接字發送到接收設備進行渲染,我想盡可能減少幀之間的延遲,最好總共低于 50 毫秒。
發送的資料是一個位元組陣列形式的 DirectX DataStream,它只表示每個像素的顏色。
對于 1920x1080 的顯示幕,這會產生一個長度為 8294400 的位元組陣列,使用Stream.CopyTo時間不到 10 毫秒,這是合理的。
使用 LZ4 壓縮生成的位元組陣列將其長度減少到 631126 位元組,需要額外的 10 毫秒左右。
我寧愿通過僅發送更改的像素來進一步減小此大小。
我的第一個想法是使用 aDictionary<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 毫秒。
我該如何優化呢?
uj5u.com熱心網友回復:
使用字典確實會阻礙您的表現。相反,保留上次螢屏截圖中的記憶體流,將其位置設定回 0,然后將其與新截圖中的新流一起讀取并進行比較。這將大大縮短您的比較時間。
我可以在使用計算機時添加示例代碼。基本上:
- 對于第一次捕獲,設定 var lastStream = data from screen capture 并發送所有已更改的資料
- 在此處開始您的捕獲回圈并設定 var currentStream = data from screen capture
- 為每個流實體化二進制閱讀器
- 使用二進制閱讀器對每個像素執行整數比較
- 在 lastStream 上運行 Dispose()
- 設定 lastStream = currentStream (win的參考變數!)
- 使用單獨的任務通過網路發送更改,這樣您的下一個幀行程迭代就不會等待網路 IO
- 繼續回圈讀取下一個捕獲,這將設定 currentStream 的值
這將比有條件邏輯和通過兩個字典樹化運行得快得多。
uj5u.com熱心網友回復:
我嘗試了約翰的答案,但它仍然效率低下,無論出于何種原因,讀/寫流都非常慢,因此將讀取量加倍會增加大量開銷。
我還需要存盤更改像素的索引,它將結果位元組陣列加倍(4 位元組用于 int 索引 4 位元組 bgra)。
雖然我意識到我可以洗掉 alpha 組件,因為對于我的用例來說它總是 255,所以我在那里獲得了一些效率。
我完全洗掉了 MemoryStream/BinaryReader 的使用,并提出了下面看起來更丑陋的代碼,它的效率要高得多。隨著 60fps 螢屏的顯著變化,它在每幀 <10ms 內完成,平均原始大小為 2MB,壓縮到 150KB。
byte[] dataBuffer = new byte[dataStream.Length];
dataStream.Read(dataBuffer, 0, dataBuffer.Length);
dataStream.Dispose();
int changedPixels = 0;
if(CachedColours != null) {
for(int i = 0; i < dataBuffer.Length / 4; i = 4) {
if(dataBuffer[i] != CachedColours[i] && dataBuffer[i 1] != CachedColours[i 1] && dataBuffer[i 2] != CachedColours[i 2]) {
changedPixels ;
}
}
}
byte[] newCols;
if(changedPixels > 0) {
newCols = new byte[(changedPixels * 3) (changedPixels * sizeof(int))];
int n = 0;
for(int i = 0; i < dataBuffer.Length / 4; i = 4) {
if(dataBuffer[i] != CachedColours[i] && dataBuffer[i 1] != CachedColours[i 1] && dataBuffer[i 2] != CachedColours[i 2]) {
newCols[n] = (byte)(i >> 24);
newCols[n 1] = (byte)(i >> 16);
newCols[n 2] = (byte)(i >> 8);
newCols[n 3] = (byte)i;
newCols[n 4] = dataBuffer[i];
newCols[n 5] = dataBuffer[i 1];
newCols[n 6] = dataBuffer[i 2];
n = 7;
}
}
} else if(CachedColours == null) {
newCols = new byte[dataBuffer.Length - (dataBuffer.Length / 4)];
int n = 0;
for(int i = 0; i < dataBuffer.Length / 4; i = 4) {
newCols[n] = dataBuffer[i];
newCols[n 1] = dataBuffer[i 1];
newCols[n 2] = dataBuffer[i 2];
n = 3;
}
} else {
return null; //No change, wait for new capture
}
CachedColours = dataBuffer;
return new BitmapData() { Size = new System.Drawing.Size(bmp.PixelSize.Width, bmp.PixelSize.Height), Pitch = dataRect.Pitch, Data = newCols };
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/417725.html
標籤:
