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

时间:2015-12-22 15:45:47

标签: c# tcp streaming unreal-engine4

我使用UnrealEngine 4.10(UE4)创建了一个小应用程序。在该应用程序中,我通过ReadPixels抓取colorBuffer。然后我将colorBuffer压缩为PNG。最后,压缩的colorBuffer通过TCP发送到另一个应用程序。 UE4应用程序是用c ++编写的,而不是重要的。 压缩的colorBuffer在应用程序的每个“tick”发送 - 基本上每秒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);

引发争论异常,告诉我“ Parmeter无效”。

发送的数据如下所示: enter image description here

我的byteArrayInbuffer如下所示: enter image description here

我也试过了两个: 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. 我所遵循的流程是否正确?

  3. 我做错了什么?

    更新

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

    据我所知,发送的数据看起来像这样(抱歉格式化): enter image description here

    收到的数据如下:

    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\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    //?PNG\r\n\u001a\n\0\0\0\rIHDR
    

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

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

    data的长度为16 ...与服务器上的dataSize变量长度相同。但是,我再次获得“参数无效”的执行。

    更新2

    @Darcara帮助建议我实际收到的是PNG文件的标题,我需要首先发送图像的大小。我现在已经做到了并取得了进展:

    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);
    }
    

    有了这个,我现在正在第一次获取图像,但是,后续尝试失败并且相同的“参数无效”。检查后,我注意到流现在似乎缺少标题... "?PNG\r\n\u001a\n\0\0\0\rIHDR"。当我使用Encoding.ASCII.GetString(buffer, 0, bytes);

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

    知道为什么标题现在只是第一次发送而不再发送?我该怎么做才能解决它?

2 个答案:

答案 0 :(得分:0)

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

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

如果流没有结束,您需要事先知道传输对象的大小,因此您可以将其read-by-read复制到另一个阵列/内存流或直接复制到磁盘。在这种情况下,应该使用更高的读缓冲区(我认为默认值是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 BuffersMessagePack

答案 1 :(得分:0)

首先,感谢@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&amp; 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;现在的大小正确。

我现在让虚幻引擎4流式传输它的帧。