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

C# 将流式PNG数据转换为图像:使用UnrealEngine 4.10通过TCP流式传输的数据,c#,tcp,streaming,unreal-engine4,C#,Tcp,Streaming,Unreal Engine4,我使用UnrealEngine 4.10(UE4)创建了一个小应用程序。在该应用程序中,我通过ReadPixels获取colorBuffer。然后我将colorBuffer压缩为PNG。最后,压缩的colorBuffer通过TCP发送到另一个应用程序。UE4应用程序是用C++编写的,而这并不重要。strong>压缩的colorBuffer在应用程序的每一个“滴答声”中发送一次,基本上每秒发送30次(大约30次)。 我的客户机,接收流式PNG的客户机是用c#编写的,并执行以下操作: 连接到服务器

我使用UnrealEngine 4.10(UE4)创建了一个小应用程序。在该应用程序中,我通过ReadPixels获取colorBuffer。然后我将colorBuffer压缩为PNG。最后,压缩的colorBuffer通过TCP发送到另一个应用程序。UE4应用程序是用C++编写的,而这并不重要。strong>压缩的colorBuffer在应用程序的每一个“滴答声”中发送一次,基本上每秒发送30次(大约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;
 }
如果我尝试从响应数据中提取一行并将其放入图像中:

  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);
数据的长度为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);
}
for(TArray::滴度仪ClientIt(客户端);ClientIt;
++(客户)
{
FSocket*Client=*ClientIt;
int32 SendSize=2*x*y;
客户端->设置非阻塞(true);
客户端->设置SendBufferSize(SendSize,SendSize);
客户端->发送(数据、发送大小、bytesSent);
}
有了这个,我现在第一次就得到了图像,但是,随后的尝试失败了,并且出现了相同的“参数无效”。经过检查,我注意到流现在似乎缺少标题<代码>“?PNG\r\n\u001a\n\0\0\0\rIHDR”
。当我使用
Encoding.ASCII.GetString(buffer,0,bytes)将缓冲区转换为字符串时,我得出了这个结论


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

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

如果图像传输后流结束/连接关闭,请使用
stream.CopyTo
将其复制到
MemoryStream
<代码>图像。FromStream(stream)
也可以工作

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

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

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
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 );
var-stream=tcp.GetStream();
//这是我们的临时读取缓冲区
int BYTES_至_READ=8196;
var buffer=新字节[字节读取];
var字节读取;
//数据对象的读取大小
读取(缓冲区,0,4)//将4个字节读入空缓冲区的开头
//TODO:检查我们是否实际收到了4个字节。
var totalBytesExpected=BitConverter.ToInt32(缓冲区,0)
//这将是我们将接收到的字节保存到的流
//也可以是文件流
var imageStream=新的内存流(预计为TotalBytes);
var totalBytesRead=0;
做{
//尽可能多地读取缓冲区中的数据或剩余字节
bytesRead=stream.Read(缓冲区,0,Math.Min(字节从0到0读取,totalBytesExpected-totalBytesRead));
totalBytesRead+=字节读取;
//将字节写入图像流
写入(缓冲区,0,字节读取);
}而(totalBytesRead
我在这里忽略了很多错误处理,但这应该给你一个大致的想法


如果您想传输更复杂的对象,请查看适当的协议,如or,首先感谢@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);
}
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);
 }
上面这行是错的。图像是
512 x 512
,因此
SendSize
应该是
x*y
,其中x和y都是512

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

客户端:

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);
}
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”流式传输其帧。

我不确定这是否正确使用了
ImageConverter
。你试过仅仅从字节数组构造的
内存流
创建
图像
吗?@RonBeyer你能详细说明一下吗?如上所述,我尝试了Image.FromStream。这就是你的意思吗?我向FromStream传递了一个内存流。是的,这就是我的意思。当你试着那样做的时候,你有没有遇到一个例外?另外,您是否验证了接收到的数据与发送的数据匹配,并且没有进行字节交换或其他操作?@RonBeyer我在尝试Image.FromStream(stream)时没有收到任何执行选项。断点命中它,然后什么也没有发生。。。尽管在计时器滴答声事件中,并且每100毫秒调用一次。我不确定如何验证发送的数据是否是接收的数据-我尝试过这样做,请查看更新的问题。@RonBeyer我忘了提到客户机,除了在断点后没有任何事情发生之外,变得没有响应。我尝试了您的建议,并且
var imageStream=new MemoryStream(totalBytesExpected)抛出OutOfMemoryException;(即,代码< ToByTeSeRealEngult> /COD>太大。您不是在将PNG从C++代码中发送之前直接发送PNG的大小,还是DE /序列化失败(DeNANESS?)。”代码> ToByTeSeRealsIuth> /Cuth>有什么值?@ DaCARI我尝试改变大小,但总是出错。