将流式传输的 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);
抛出参数异常,告诉我“参数无效”。
发送的数据如下所示:
我的 byteArrayIn
和 buffer
看起来像这样:
我也都试过了:
Image.FromStream(stream);
和 Image.FromStream(new MemoryStream(bytes));
Image.FromStream(stream);
导致它永远读取...并且 Image.FromStream(new MemoryStream(bytes));
导致与上述相同的异常。
一些问题:
我应该将 BYTES_TO_READ
设置为多大?我设置为 16,因为当我检查在 UE4 应用程序中发送的字节数组的大小时(第一张图片中的 dataSize
),它说长度是 16...不太确定将其设置为什么.
我遵循的流程是否正确?
我做错了什么?
更新
@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
将其复制到MemoryStream
。 Image.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 个流式传输它的帧。
我使用 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);
抛出参数异常,告诉我“参数无效”。
发送的数据如下所示:
我的 byteArrayIn
和 buffer
看起来像这样:
我也都试过了:
Image.FromStream(stream);
和 Image.FromStream(new MemoryStream(bytes));
Image.FromStream(stream);
导致它永远读取...并且 Image.FromStream(new MemoryStream(bytes));
导致与上述相同的异常。
一些问题:
我应该将
BYTES_TO_READ
设置为多大?我设置为 16,因为当我检查在 UE4 应用程序中发送的字节数组的大小时(第一张图片中的dataSize
),它说长度是 16...不太确定将其设置为什么.我遵循的流程是否正确?
我做错了什么?
更新
@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
将其复制到MemoryStream
。 Image.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 个流式传输它的帧。