ライブビデオをあるアプリから別のアプリにストリーミングしようとしています。現在、2つのアプリがあります。アプリ1がサーバー/送信者であり、アプリ2がクライアント/受信者である場合。アプリ1では、ビデオバイトをクライアントに正常に送信します。また、クライアント側では、すべてのバイトを受信しています。ソケットとTCPを使用しています。私が直面している問題は、ビデオバイトを受け取ってRaw Imageテクスチャに割り当てると、テクスチャの画像が非常にズームインされて見えるので、ピクセル化されているということです。
更新された画像
これは最初の問題ですが、現在、デスクトップから別のデスクトップにテストしています。私の目標は、IPADをデスクトップにストリーミングすることです。それを行うと、非常に遅くなり、iPadとデスクトップの両方でアプリが終了します。
これまでに試したトラブルシューティング。
1:iPadからデスクトップにストリーミングするため、2つの異なる解像度があるため、これが起こっていると思います
2:テクスチャ画像が大きすぎるため、出力して630を返します。UnityTexture2D.resizeを使用してサイズを変更しようとしましたが、関数がピクセルを未確認として設定するため、グレーのテクスチャが表示されます。
3:テクスチャのサイズを変更するために他のライブラリを使用し、必要なものを取得しましたが、12フレーム後にrawimageがビデオと「?」の間でちらつき始めました。テクスチャが多すぎると、アプリ(ipadとデスクトップ)の両方でフリーズします
4:SetpixelsとGetpixelsの両方の関数を使用しているため、テクスチャを読み取る方法が問題の原因であると考えています。
私のコード:サーバー/送信者側:
using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.UI;
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
public class Connecting : MonoBehaviour
{
WebCamTexture webCam;
public RawImage myImage;
Texture2D currentTexture;
private TcpListener listner;
private const int port = 8010;
private bool stop = false;
private List<TcpClient> clients = new List<TcpClient>();
private void Start()
{
// Open the Camera on the desired device, in my case IPAD pro
webCam = new WebCamTexture();
// Get all devices , front and back camera
webCam.deviceName = WebCamTexture.devices[WebCamTexture.devices.Length - 1].name;
// request the lowest width and heigh possible
webCam.requestedHeight = 10;
webCam.requestedWidth = 10;
webCam.Play();
/
currentTexture = new Texture2D(webCam.width, webCam.height);
// Connect to the server
listner = new TcpListener(port);
listner.Start();
// Create Seperate thread for requesting from client
Loom.RunAsync(() => {
while (!stop)
{
// Wait for client approval
var client = listner.AcceptTcpClient();
// We are connected
clients.Add(client);
Loom.RunAsync(() =>
{
while (!stop)
{
var stremReader = client.GetStream();
if (stremReader.CanRead)
{
// we need storage for data
using (var messageData = new MemoryStream())
{
Byte[] buffer = new Byte[client.ReceiveBufferSize];
while (stremReader.DataAvailable)
{
int bytesRead = stremReader.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)
break;
// Writes to the data storage
messageData.Write(buffer, 0, bytesRead);
}
if (messageData.Length > 0)
{
// send pngImage
SendPng(client);
}
}
}
}
});
}
});
}
private void Update()
{
myImage.texture = webCam;
}
// Read video pixels and send them to the client
private void SendPng (TcpClient client)
{
Loom.QueueOnMainThread(() =>
{
// Get the webcame texture pixels
currentTexture.SetPixels(webCam.GetPixels());
var pngBytes = currentTexture.EncodeToPNG();
// Want to Write
var stream = client.GetStream();
// Write the image bytes
stream.Write(pngBytes, 0, pngBytes.Length);
// send it
stream.Flush();
});
}
// stop everything
private void OnApplicationQuit()
{
webCam.Stop();
stop = true;
listner.Stop();
foreach (TcpClient c in clients)
c.Close();
}
}
クライアント/レシーバー側
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Net.Sockets;
using System.Net;
using System.IO;
public class reciver : MonoBehaviour
{
public RawImage image;
const int port = 8010;
public string IP = "";
TcpClient client;
Texture2D tex;
// Use this for initialization
void Start()
{
client = new TcpClient();
// connect to server
Loom.RunAsync(() => {
Debug.LogWarning("Connecting to server...");
// if on desktop
client.Connect(IPAddress.Loopback, port);
// if using the IPAD
//client.Connect(IPAddress.Parse(IP), port);
Debug.LogWarning("Connected!");
});
}
float lastTimeRequestedTex = 0;
// Update is called once per frame
void Update()
{
//if (Time.time - lastTimeRequestedTex < 0.1f)
// return;
lastTimeRequestedTex = Time.time;
if (!client.Connected)
return;
// Send 1 byte to server
var serverStream = client.GetStream();
// request the texture from the server
if (serverStream.CanWrite)
{
// Texture request
// send request
serverStream.WriteByte(byte.MaxValue);
serverStream.Flush();
Debug.Log("Succesfully send 1 byte");
}
if (serverStream.CanRead)
{
// Read the bytes
using (var writer = new MemoryStream())
{
var readBuffer = new byte[client.ReceiveBufferSize];
while (serverStream.DataAvailable)
{
int numberOfBytesRead = serverStream.Read(readBuffer, 0, readBuffer.Length);
if (numberOfBytesRead <= 0)
{
break;
}
writer.Write(readBuffer, 0, numberOfBytesRead);
}
if (writer.Length > 0)
{
// got whole data in writer
// Get the bytes and apply them to the texture
var tex = new Texture2D(0, 0);
tex.LoadImage(writer.ToArray());
Debug.Log(tex.width + tex.height);
image.texture = tex;
}
}
}
}
void OnApplicationQuit()
{
Debug.LogWarning("OnApplicationQuit");
client.Close();
}
}
私はあなたのコードを実行しましたが、それは時々動作し、時々失敗しました(約90%の時間)。私のコンピューターでは5 FPSで実行されました。これは、iPadをターゲットにしていると確信しているモバイルデバイスではうまく機能しません。
コードにはほとんど問題はありませんが、非常に深刻な問題です。
1。画像を読み込む前に完全に受信されていない
これがあなたの画像がとても奇妙に見える理由です。
ソケットを扱う際に人々が犯す最大の間違いは、送信するすべてのものが一度に受信されると想定することです。本当じゃない。これがクライアントのコーディング方法です。 this をお読みください。
これは私の答えで使用した方法です:
[〜#〜] a [〜#〜]。Get _Texture2D
_バイト配列。
[〜#〜] b [〜#〜]。バイト配列の長さを送信します。バイト配列ではなく長さ。
[〜#〜] c [〜#〜]。クライアントは最初に長さを読み取ります。
[〜#〜] d [〜#〜]。クライアントはその長さを使用して、完了するまでテクスチャデータ/ピクセル全体を読み取ります。
[〜#〜] e [〜#〜]。受信したバイトを配列に変換します。
すべてのバイトを読み取る方法については、private int readImageByteSize(int size)
およびprivate void readFrameByteArray(int size)
関数を参照してください。
もちろん、最初に送信されるデータの長さも知っている必要があります。長さはintデータ型で保存されます。
int
の最大値は_2,147,483,647
_であり、それは_10
_桁の長さです。そこで、最初に送信される配列の配列の長さをプロトコルとして_15
_に設定しました。これは、クライアント側でも従わなければならないルールです。
これが今どのように機能するか:
_Texture2D
_からバイト配列を読み取り、その配列の長さを読み取り、クライアントに送信します。クライアントは、最初の_15
_バイトが単に長さであるという規則に従います。クライアントは、その_15
_バイトを読み取り、それをlengthに変換してから、そのlengthを使用して読み取りますサーバーから_Texture2D
_を完了します。
長さの変換は、void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes)
およびint frameByteArrayToByteLength(byte[] frameBytesLength)
関数を使用して行われます。それらを理解するためにそれらを見てください。
2.メインスレッドでソケット操作を実行する
これが、[〜#〜] fps [〜#〜]がコンピューター上で_5
_である理由です。
これをしないでください。これにより、フレームレートが既にあるように低くなります。私はこのような多くの質問に答えましたが、あなたが何をしているかを知っているように見え、Thread
を使用しようとしましたが、間違っていたので深く入りません。
[〜#〜] a [〜#〜]。メインThread
から読み込んでいたとき:serverStream.Read(readBuffer, 0, readBuffer.Length);
Update
関数内。
あなたはそれを内部でやるべきだった
_Loom.RunAsync(() =>
{ //your red code });
_
[〜#〜] b [〜#〜]。 SendPng
関数で同じ間違いを犯したとき、stream.Write(pngBytes, 0, pngBytes.Length);
でデータを送信していたとき
_Loom.QueueOnMainThread(() =>
{});
_
_Loom.QueueOnMainThread
_内で行うことはすべて、メインThread
で行われます。
別のThread.Loom.RunAsync(() =>{});
で送信することになっています
最後に、listner = new TcpListener(port);
は廃止されました。これは問題を引き起こしませんでしたが、サーバーコードでlistner = new TcpListener(IPAddress.Any, port);
を使用して、IPをリッスンする必要があります。
最後の[〜#〜] fps [〜#〜]は、これらすべての修正を行った後、コンピューター上で_50
_を超えています。以下のコードはa-lotで改善できます。私はそれをあなたに任せます。
online code compare を使用して、各クラスで変更されたものを確認できます。
[〜#〜] server [〜#〜]:
_using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.UI;
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
public class Connecting : MonoBehaviour
{
WebCamTexture webCam;
public RawImage myImage;
public bool enableLog = false;
Texture2D currentTexture;
private TcpListener listner;
private const int port = 8010;
private bool stop = false;
private List<TcpClient> clients = new List<TcpClient>();
//This must be the-same with SEND_COUNT on the client
const int SEND_RECEIVE_COUNT = 15;
private void Start()
{
Application.runInBackground = true;
//Start WebCam coroutine
StartCoroutine(initAndWaitForWebCamTexture());
}
//Converts the data size to byte array and put result to the fullBytes array
void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes)
{
//Clear old data
Array.Clear(fullBytes, 0, fullBytes.Length);
//Convert int to bytes
byte[] bytesToSendCount = BitConverter.GetBytes(byteLength);
//Copy result to fullBytes
bytesToSendCount.CopyTo(fullBytes, 0);
}
//Converts the byte array to the data size and returns the result
int frameByteArrayToByteLength(byte[] frameBytesLength)
{
int byteLength = BitConverter.ToInt32(frameBytesLength, 0);
return byteLength;
}
IEnumerator initAndWaitForWebCamTexture()
{
// Open the Camera on the desired device, in my case IPAD pro
webCam = new WebCamTexture();
// Get all devices , front and back camera
webCam.deviceName = WebCamTexture.devices[WebCamTexture.devices.Length - 1].name;
// request the lowest width and heigh possible
webCam.requestedHeight = 10;
webCam.requestedWidth = 10;
myImage.texture = webCam;
webCam.Play();
currentTexture = new Texture2D(webCam.width, webCam.height);
// Connect to the server
listner = new TcpListener(IPAddress.Any, port);
listner.Start();
while (webCam.width < 100)
{
yield return null;
}
//Start sending coroutine
StartCoroutine(senderCOR());
}
WaitForEndOfFrame endOfFrame = new WaitForEndOfFrame();
IEnumerator senderCOR()
{
bool isConnected = false;
TcpClient client = null;
NetworkStream stream = null;
// Wait for client to connect in another Thread
Loom.RunAsync(() =>
{
while (!stop)
{
// Wait for client connection
client = listner.AcceptTcpClient();
// We are connected
clients.Add(client);
isConnected = true;
stream = client.GetStream();
}
});
//Wait until client has connected
while (!isConnected)
{
yield return null;
}
LOG("Connected!");
bool readyToGetFrame = true;
byte[] frameBytesLength = new byte[SEND_RECEIVE_COUNT];
while (!stop)
{
//Wait for End of frame
yield return endOfFrame;
currentTexture.SetPixels(webCam.GetPixels());
byte[] pngBytes = currentTexture.EncodeToPNG();
//Fill total byte length to send. Result is stored in frameBytesLength
byteLengthToFrameByteArray(pngBytes.Length, frameBytesLength);
//Set readyToGetFrame false
readyToGetFrame = false;
Loom.RunAsync(() =>
{
//Send total byte count first
stream.Write(frameBytesLength, 0, frameBytesLength.Length);
LOG("Sent Image byte Length: " + frameBytesLength.Length);
//Send the image bytes
stream.Write(pngBytes, 0, pngBytes.Length);
LOG("Sending Image byte array data : " + pngBytes.Length);
//Sent. Set readyToGetFrame true
readyToGetFrame = true;
});
//Wait until we are ready to get new frame(Until we are done sending data)
while (!readyToGetFrame)
{
LOG("Waiting To get new frame");
yield return null;
}
}
}
void LOG(string messsage)
{
if (enableLog)
Debug.Log(messsage);
}
private void Update()
{
myImage.texture = webCam;
}
// stop everything
private void OnApplicationQuit()
{
if (webCam != null && webCam.isPlaying)
{
webCam.Stop();
stop = true;
}
if (listner != null)
{
listner.Stop();
}
foreach (TcpClient c in clients)
c.Close();
}
}
_
[〜#〜] client [〜#〜]:
_using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System;
public class reciver : MonoBehaviour
{
public RawImage image;
public bool enableLog = false;
const int port = 8010;
public string IP = "192.168.1.165";
TcpClient client;
Texture2D tex;
private bool stop = false;
//This must be the-same with SEND_COUNT on the server
const int SEND_RECEIVE_COUNT = 15;
// Use this for initialization
void Start()
{
Application.runInBackground = true;
tex = new Texture2D(0, 0);
client = new TcpClient();
//Connect to server from another Thread
Loom.RunAsync(() =>
{
LOGWARNING("Connecting to server...");
// if on desktop
client.Connect(IPAddress.Loopback, port);
// if using the IPAD
//client.Connect(IPAddress.Parse(IP), port);
LOGWARNING("Connected!");
imageReceiver();
});
}
void imageReceiver()
{
//While loop in another Thread is fine so we don't block main Unity Thread
Loom.RunAsync(() =>
{
while (!stop)
{
//Read Image Count
int imageSize = readImageByteSize(SEND_RECEIVE_COUNT);
LOGWARNING("Received Image byte Length: " + imageSize);
//Read Image Bytes and Display it
readFrameByteArray(imageSize);
}
});
}
//Converts the data size to byte array and put result to the fullBytes array
void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes)
{
//Clear old data
Array.Clear(fullBytes, 0, fullBytes.Length);
//Convert int to bytes
byte[] bytesToSendCount = BitConverter.GetBytes(byteLength);
//Copy result to fullBytes
bytesToSendCount.CopyTo(fullBytes, 0);
}
//Converts the byte array to the data size and returns the result
int frameByteArrayToByteLength(byte[] frameBytesLength)
{
int byteLength = BitConverter.ToInt32(frameBytesLength, 0);
return byteLength;
}
/////////////////////////////////////////////////////Read Image SIZE from Server///////////////////////////////////////////////////
private int readImageByteSize(int size)
{
bool disconnected = false;
NetworkStream serverStream = client.GetStream();
byte[] imageBytesCount = new byte[size];
var total = 0;
do
{
var read = serverStream.Read(imageBytesCount, total, size - total);
//Debug.LogFormat("Client recieved {0} bytes", total);
if (read == 0)
{
disconnected = true;
break;
}
total += read;
} while (total != size);
int byteLength;
if (disconnected)
{
byteLength = -1;
}
else
{
byteLength = frameByteArrayToByteLength(imageBytesCount);
}
return byteLength;
}
/////////////////////////////////////////////////////Read Image Data Byte Array from Server///////////////////////////////////////////////////
private void readFrameByteArray(int size)
{
bool disconnected = false;
NetworkStream serverStream = client.GetStream();
byte[] imageBytes = new byte[size];
var total = 0;
do
{
var read = serverStream.Read(imageBytes, total, size - total);
//Debug.LogFormat("Client recieved {0} bytes", total);
if (read == 0)
{
disconnected = true;
break;
}
total += read;
} while (total != size);
bool readyToReadAgain = false;
//Display Image
if (!disconnected)
{
//Display Image on the main Thread
Loom.QueueOnMainThread(() =>
{
displayReceivedImage(imageBytes);
readyToReadAgain = true;
});
}
//Wait until old Image is displayed
while (!readyToReadAgain)
{
System.Threading.Thread.Sleep(1);
}
}
void displayReceivedImage(byte[] receivedImageBytes)
{
tex.LoadImage(receivedImageBytes);
image.texture = tex;
}
// Update is called once per frame
void Update()
{
}
void LOG(string messsage)
{
if (enableLog)
Debug.Log(messsage);
}
void LOGWARNING(string messsage)
{
if (enableLog)
Debug.LogWarning(messsage);
}
void OnApplicationQuit()
{
LOGWARNING("OnApplicationQuit");
stop = true;
if (client != null)
{
client.Close();
}
}
}
_