gpt4 book ai didi

c# - C#通过套接字高效传输屏幕的改进方法

转载 作者:行者123 更新时间:2023-11-30 20:22:28 25 4
gpt4 key购买 nike

这就是我编写漂亮代码的方式(一些简单的更改对我来说更容易理解)

     private void Form1_Load(object sender, EventArgs e)
{

prev = GetDesktopImage();//get a screenshot of the desktop;
cur = GetDesktopImage();//get a screenshot of the desktop;


var locked1 = cur.LockBits(new Rectangle(0, 0, cur.Width, cur.Height),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var locked2 = prev.LockBits(new Rectangle(0, 0, prev.Width, prev.Height),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
ApplyXor(locked1, locked2);
compressionBuffer = new byte[1920* 1080 * 4];

// Compressed buffer -- where the data goes that we'll send.
int backbufSize = LZ4.LZ4Codec.MaximumOutputLength(this.compressionBuffer.Length) + 4;

backbuf = new CompressedCaptureScreen(backbufSize);

MessageBox.Show(compressionBuffer.Length.ToString());
int length = Compress();

MessageBox.Show(backbuf.Data.Length.ToString());//prints the new buffer size

}

压缩缓冲区的长度例如是 8294400 并且backbuff.Data.length是 8326947

最佳答案

我不喜欢压缩建议,所以这是我会做的。

您不想压缩视频流(因此MPEG,AVI等都不是必需的-这些不必是实时的),并且您不想压缩单个图片(因为这很愚蠢) )。

基本上,您要做的是检测事物是否发生变化并发送差异。您正朝着正确的方向前进;大多数视频压缩器都可以做到这一点。您还需要一种快速压缩/解压缩算法;尤其是当您使用更多FPS时,它将变得更加相关。

差异。首先,消除代码中的所有分支,并确保内存访问是顺序的(例如,在内部循环中迭代x)。后者将为您提供缓存位置。至于差异,我可能会使用64位XOR。简单,无分支且快速。

如果您想要性能,最好在C++中执行:当前的C#实现不会向量化您的代码,这将在很大程度上为您提供帮助。

做这样的事情(我假设是32位像素格式):

for (int y=0; y<height; ++y) // change to PFor if you like
{
ulong* row1 = (ulong*)(image1BasePtr + image1Stride * y);
ulong* row2 = (ulong*)(image2BasePtr + image2Stride * y);
for (int x=0; x<width; x += 2)
row2[x] ^= row1[x];
}

快速压缩和解压缩通常意味着更简单的压缩算法。 https://code.google.com/p/lz4/是一种这样的算法,并且还有一个适用的.NET端口。您可能还想了解它是如何工作的。 LZ4中有一个流功能,如果您可以使其处理2张图像而不是1张图像,则可能会给您带来不错的压缩效果。

总而言之,如果您要压缩白噪声,它将根本无法工作,并且帧速率会下降。解决此问题的一种方法是,如果帧中的“随机性”过多,则减少颜色。随机性的量度是熵,有几种方法可以测量图片的熵( https://en.wikipedia.org/wiki/Entropy_(information_theory))。我会坚持一个非常简单的方法:检查压缩图片的大小-如果超出一定限制,请减少位数。如果低于,则增加位数。

注意,在这种情况下,递增和递减位不能通过移位来完成。您不需要删除位,只需要压缩即可更好地工作。使用带有位掩码的简单“AND”可能同样好。例如,如果要丢弃2位,则可以这样操作:
for (int y=0; y<height; ++y) // change to PFor if you like
{
ulong* row1 = (ulong*)(image1BasePtr + image1Stride * y);
ulong* row2 = (ulong*)(image2BasePtr + image2Stride * y);
ulong mask = 0xFFFCFCFCFFFCFCFC;
for (int x=0; x<width; x += 2)
row2[x] = (row2[x] ^ row1[x]) & mask;
}

PS:我不确定我将如何使用alpha组件,我将把它留给您进行试验。

祝你好运!

详细答案

我有一些空闲时间,因此我只是测试了这种方法。这是一些代码来支持所有这些。

这段代码通常以超过130 FPS的速度运行,并且在我的笔记本电脑上具有恒定的内存压力,因此瓶颈不再存在。请注意,您需要LZ4才能使它正常工作,并且LZ4的目标是高速,而不是高压缩比。稍后再说。

首先,我们需要一些可以用来保存我们将要发送的所有数据的东西。我不是在这里实现套接字本身(尽管使用它作为开始应该很简单),我主要集中在获取发送某些内容所需的数据上。
// The thing you send over a socket
public class CompressedCaptureScreen
{
public CompressedCaptureScreen(int size)
{
this.Data = new byte[size];
this.Size = 4;
}

public int Size;
public byte[] Data;
}

我们还需要一个可以容纳所有魔法的类:
public class CompressScreenCapture
{

接下来,如果我运行的是高性能代码,则养成先预先分配所有缓冲区的习惯。这样可以节省实际算法时间。 1080p的4个缓冲区约为33 MB,这很好-我们来分配它。
public CompressScreenCapture()
{
// Initialize with black screen; get bounds from screen.
this.screenBounds = Screen.PrimaryScreen.Bounds;

// Initialize 2 buffers - 1 for the current and 1 for the previous image
prev = new Bitmap(screenBounds.Width, screenBounds.Height, PixelFormat.Format32bppArgb);
cur = new Bitmap(screenBounds.Width, screenBounds.Height, PixelFormat.Format32bppArgb);

// Clear the 'prev' buffer - this is the initial state
using (Graphics g = Graphics.FromImage(prev))
{
g.Clear(Color.Black);
}

// Compression buffer -- we don't really need this but I'm lazy today.
compressionBuffer = new byte[screenBounds.Width * screenBounds.Height * 4];

// Compressed buffer -- where the data goes that we'll send.
int backbufSize = LZ4.LZ4Codec.MaximumOutputLength(this.compressionBuffer.Length) + 4;
backbuf = new CompressedCaptureScreen(backbufSize);
}

private Rectangle screenBounds;
private Bitmap prev;
private Bitmap cur;
private byte[] compressionBuffer;

private int backbufSize;
private CompressedCaptureScreen backbuf;

private int n = 0;

首先要做的是捕获屏幕。这是简单的部分:只需填充当前屏幕的位图即可:
private void Capture()
{
// Fill 'cur' with a screenshot
using (var gfxScreenshot = Graphics.FromImage(cur))
{
gfxScreenshot.CopyFromScreen(screenBounds.X, screenBounds.Y, 0, 0, screenBounds.Size, CopyPixelOperation.SourceCopy);
}
}

如我所说,我不想压缩“原始”像素。相反,我宁愿压缩先前图像和当前图像的XOR掩码。在大多数情况下,这将为您提供很多0,这很容易压缩:
private unsafe void ApplyXor(BitmapData previous, BitmapData current)
{
byte* prev0 = (byte*)previous.Scan0.ToPointer();
byte* cur0 = (byte*)current.Scan0.ToPointer();

int height = previous.Height;
int width = previous.Width;
int halfwidth = width / 2;

fixed (byte* target = this.compressionBuffer)
{
ulong* dst = (ulong*)target;

for (int y = 0; y < height; ++y)
{
ulong* prevRow = (ulong*)(prev0 + previous.Stride * y);
ulong* curRow = (ulong*)(cur0 + current.Stride * y);

for (int x = 0; x < halfwidth; ++x)
{
*(dst++) = curRow[x] ^ prevRow[x];
}
}
}
}

对于压缩算法,我只是将缓冲区传递给LZ4,然后让它发挥作用。
private int Compress()
{
// Grab the backbuf in an attempt to update it with new data
var backbuf = this.backbuf;

backbuf.Size = LZ4.LZ4Codec.Encode(
this.compressionBuffer, 0, this.compressionBuffer.Length,
backbuf.Data, 4, backbuf.Data.Length-4);

Buffer.BlockCopy(BitConverter.GetBytes(backbuf.Size), 0, backbuf.Data, 0, 4);

return backbuf.Size;
}

这里要注意的一件事是,我习惯将所有需要通过TCP/IP套接字发送的缓冲区放入缓冲区。如果我可以轻松避免数据移动,则不想移动数据,因此我只是将所需的所有内容放在另一侧。

至于套接字本身,您可以在此处使用异步TCP套接字(我会这样做),但是如果这样做,则需要添加一个额外的缓冲区。

剩下的唯一事情就是将所有内容粘合在一起并在屏幕上显示一些统计信息:
public void Iterate()
{
Stopwatch sw = Stopwatch.StartNew();

// Capture a screen:
Capture();

TimeSpan timeToCapture = sw.Elapsed;

// Lock both images:
var locked1 = cur.LockBits(new Rectangle(0, 0, cur.Width, cur.Height),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var locked2 = prev.LockBits(new Rectangle(0, 0, prev.Width, prev.Height),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
try
{
// Xor screen:
ApplyXor(locked2, locked1);

TimeSpan timeToXor = sw.Elapsed;

// Compress screen:
int length = Compress();

TimeSpan timeToCompress = sw.Elapsed;

if ((++n) % 50 == 0)
{
Console.Write("Iteration: {0:0.00}s, {1:0.00}s, {2:0.00}s " +
"{3} Kb => {4:0.0} FPS \r",
timeToCapture.TotalSeconds, timeToXor.TotalSeconds,
timeToCompress.TotalSeconds, length / 1024,
1.0 / sw.Elapsed.TotalSeconds);
}

// Swap buffers:
var tmp = cur;
cur = prev;
prev = tmp;
}
finally
{
cur.UnlockBits(locked1);
prev.UnlockBits(locked2);
}
}

请注意,我减少了Console输出,以确保这不是瓶颈。 :-)

简单改进

压缩所有0有点浪费,对吧?使用简单的 bool 值来跟踪具有数据的最小和最大y位置非常容易。
ulong tmp = curRow[x] ^ prevRow[x];
*(dst++) = tmp;

hasdata |= tmp != 0;

如果没有必要,您可能也不想调用 Compress

添加此功能后,您将在屏幕上看到以下内容:

Iteration: 0.00s, 0.01s, 0.01s 1 Kb => 152.0 FPS



使用其他压缩算法也可能会有所帮助。我坚持使用LZ4,因为它使用简单,运行迅速且压缩效果很好-不过,还有其他一些选项可能会更好。请参阅 http://fastcompression.blogspot.nl/进行比较。

如果您的连接不良或通过远程连接流式传输视频,则所有这些都将无法使用。最好在这里减少像素值。这很简单:在xor上对上一张和当前图片应用一个简单的64位掩码。您还可以尝试使用索引颜色-无论如何,您可以在这里尝试很多不同的操作。我只是保持简单,因为这可能已经足够了。

您也可以将 Parallel.For用于xor循环;我个人并不是很在乎这个。

更具挑战性的

如果您有一台服务器为多个客户端提供服务,则事情将变得更具挑战性,因为它们将以不同的速率进行刷新。我们希望最快的刷新客户端来确定服务器速度-而不是最慢。 :-)

为了实现这一点,必须更改 prevcur之间的关系。如果我们像这样简单地“异或”离开,最终将给较慢的客户带来一堆乱码。

为了解决这个问题,我们不再需要交换 prev,因为它应该包含关键帧(当压缩数据变得太大时您将刷新),并且 cur将保留来自“异或”结果的增量数据。这意味着,只要 prev位图是最新的,您基本上就可以抓取任意的“异或”红色帧并通过线发送它。

关于c# - C#通过套接字高效传输屏幕的改进方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31543940/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com