将流式传输的 PNG 数据转换为图像:使用 UnrealEngine 4.10 通过 TCP 传输的数据

Convert streamed PNG data to image: data Streamed via TCP using UnrealEngine 4.10

我使用 UnrealEngine 4.10 (UE4) 创建了一个小应用程序。在该应用程序中,我通过 ReadPixels 获取 colorBuffer。然后我将 colorBuffer 压缩为 PNG。最后,压缩的 colorBuffer 通过 TCP 发送到另一个应用程序。 UE4 应用程序是用 c++ 编写的,这无关紧要。 应用程序每隔 "tick" 发送压缩的 colorBuffer - 基本上每秒 30 次(大约)。

我的客户端,接收流式 PNG 的客户端是用 c# 编写的,并执行以下操作:

客户端实现:

private void Timer_Tick(object sender, EventArgs e)
{
   var connected = tcp.IsConnected();

    if (connected)
    {
       var stream = tcp.GetStream(); //simply returns client.GetStream();
       int BYTES_TO_READ = 16; 
       var buffer = new byte[BYTES_TO_READ];
       var totalBytesRead = 0;
       var bytesRead;
       do {
           // You have to do this in a loop because there's no            
           // guarantee that all the bytes you need will be ready when 
           // you call.
           bytesRead = stream.Read(buffer, totalBytesRead,   
                                   BYTES_TO_READ - totalBytesRead);
           totalBytesRead += bytesRead;
       } while (totalBytesRead < BYTES_TO_READ);

       Image x = byteArrayToImage(buffer);

    }
}

public Image byteArrayToImage(byte[] byteArrayIn)
{
   var converter = new ImageConverter();
   Image img = (Image)converter.ConvertFrom(byteArrayIn);
   return img;
 }

问题是Image img = (Image)converter.ConvertFrom(byteArrayIn);

抛出参数异常,告诉我“参数无效”。

发送的数据如下所示:

我的 byteArrayInbuffer 看起来像这样:

我也都试过了: Image.FromStream(stream);Image.FromStream(new MemoryStream(bytes));

Image.FromStream(stream); 导致它永远读取...并且 Image.FromStream(new MemoryStream(bytes)); 导致与上述相同的异常。

一些问题:

  1. 我应该将 BYTES_TO_READ 设置为多大?我设置为 16,因为当我检查在 UE4 应用程序中发送的字节数组的大小时(第一张图片中的 dataSize),它说长度是 16...不太确定将其设置为什么.

  2. 我遵循的流程是否正确?

我做错了什么?

更新

@RonBeyer 问我是否可以验证从服务器发送的数据是否与接收到的数据相匹配。我已经尝试这样做了,这就是我能说的:

发送的数据,据我所知是这样的(抱歉格式化):

正在接收的数据,如下所示:

var stream = tcp.GetStream();

int BYTES_TO_READ = 512;
var buffer = new byte[BYTES_TO_READ];

Int32 bytes = stream.Read(buffer, 0, buffer.Length);
var responseData = System.Text.Encoding.ASCII.GetString(buffer, 0, 
                                                        bytes);

//responseData looks like this (has been formatted for easier reading)
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR
//?PNG\r\n\u001a\n[=11=][=11=][=11=]\rIHDR

如果我尝试从 responseData 中取出一行并将其放入图像中:

  var stringdata = "?PNG\r\n\u001a\n[=12=][=12=][=12=]\rIHDR";
  var data = System.Text.Encoding.ASCII.GetBytes(stringdata);
  var ms = new MemoryStream(data);
  Image img = Image.FromStream(ms);

data 的长度为 16... 与服务器上的 dataSize 变量的长度相同。但是,我再次得到执行 "Parameter is not valid".

更新 2

@Darcara 通过建议我实际收到的是 PNG 文件的 header 并且我需要首先发送图像的大小来提供帮助。我现在已经做到了并且取得了进步:

for (TArray<class FSocket*>::TIterator ClientIt(Clients); ClientIt; 
                                                          ++ClientIt)
{
    FSocket *Client = *ClientIt;

    int32 SendSize = 2 * x * y;
    Client->SetNonBlocking(true);
    Client->SetSendBufferSize(SendSize, SendSize);
    Client->Send(data, SendSize, bytesSent);
}

有了这个,我现在可以在第一时间获取图像,但是,后续尝试都失败了,同样 "Parameter is not valid"。经过检查,我注意到流现在似乎缺少 header..."?PNG\r\n\u001a\n[=25=][=25=][=25=]\rIHDR"。当我使用 Encoding.ASCII.GetString(buffer, 0, bytes);

将缓冲区转换为字符串时,我得出了这个结论

知道为什么 header 现在只被发送到第一次而不再发送了吗?我能做些什么来修复它?

您只读取了流的前 16 个字节。我猜这不是故意的。

如果图像传输后流ends/connection关闭,请使用stream.CopyTo将其复制到MemoryStreamImage.FromStream(stream) 也可能有效

如果流没有结束,你需要事先知道传输对象的大小,所以你可以将它逐读复制到另一个数组/内存流或直接到磁盘。在那种情况下,应该使用更高的读取缓冲区(我认为默认值为 8192)。不过这要复杂得多。

要手动从流中读取,您需要在数据前加上大小。一个简单的 Int32 就足够了。您的客户端代码可能如下所示:

var stream = tcp.GetStream();
//this is our temporary read buffer
int BYTES_TO_READ = 8196; 
var buffer = new byte[BYTES_TO_READ];
var bytesRead;

//read size of data object
stream.Read(buffer, 0, 4);  //read  4 bytes into the beginning of the empty buffer
//TODO: check that we actually received 4 bytes.
var totalBytesExpected = BitConverter.ToInt32(buffer, 0)
//this will be the stream we will save our received bytes to
//could also be a file stream
var imageStream = new MemoryStream(totalBytesExpected);

var totalBytesRead = 0;
do {
    //read as much as the buffer can hold or the remaining bytes
    bytesRead = stream.Read(buffer, 0, Math.Min(BYTES_TO_READ, totalBytesExpected - totalBytesRead));
    totalBytesRead += bytesRead;
    //write bytes to image stream
    imageStream.Write(buffer, 0, bytesRead);
   } while (totalBytesRead < totalBytesExpected );

我在这里忽略了很多错误处理,但这应该能让您大致了解。

如果您想传输更复杂的对象,请查看适当的协议,例如 Google Protocol Buffers or MessagePack

首先感谢@Dacara 和@RonBeyer 的帮助。

我现在有一个解决方案:

服务器:

for (TArray<class FSocket*>::TIterator ClientIt(Clients); ClientIt; 
                                                          ++ClientIt)
{
    FSocket *Client = *ClientIt;

    int32 SendSize = x * y; // Image is 512 x 512 
    Client->SetSendBufferSize(SendSize, SendSize);
    Client->Send(data, SendSize, bytesSent);
}

第一个问题是图片尺寸需要正确:

 int32 SendSize = 2 * x * y;

上面一行是错误的。图片是 512 by 512 所以 SendSize 应该是 x * y 其中 x & y 都是 512.

另一个问题是我如何处理流客户端。

客户:

var connected = tcp.IsConnected();

if (connected)
{
    var stream = tcp.GetStream();

    var BYTES_TO_READ = (512 * 512)^2;
    var buffer = new byte[BYTES_TO_READ];

    var bytes = stream.Read(buffer, 0, BYTES_TO_READ);

    Image returnImage = Image.FromStream(new MemoryStream(buffer));

    //Apply the image to a picture box. Note, this is running on a separate 
    //thread.
    UpdateImageViewerBackgroundWorker.ReportProgress(0, returnImage);
 }

var BYTES_TO_READ = (512 * 512)^2; 现在是正确的尺寸。

我现在有 Unreal Engine 4 个流式传输它的帧。