Единство: streamовое видео в реальном времени

Я пытаюсь передать streamовое видео из одного приложения в другое. В настоящее время у меня есть 2 приложения. было приложение 1 – сервер / отправитель, а приложение 2 – клиент / получатель. В приложении 1 я успешно отправляю видеобайты клиенту. и на стороне клиента я также получаю все байты. Im использует сокеты и TCP. Проблема, с которой я сталкиваюсь, заключается в следующем: когда я получаю видеобайты и назначаю их текстуре Raw Image, изображение на текстуре выглядит слишком большим, и оно настолько pixilated.

Обновленное изображение

введите описание изображения здесь

Это то, что я stream введите описание изображения здесь

и это то, что я получаю от клиента.
Это то, что я вижу в rawimage, когда я получаю байты

Это 1-я проблема, однако в настоящее время я тестирую с рабочего стола на другой, моя цель – передать форму IPAD на рабочий стол, и когда я делаю это так медленно, и это убивает приложение как на iPad, так и на рабочем столе.

Некоторое устранение неполадок, которые я пробовал,

1: Я думаю, что это происходит, потому что у меня есть 2 разных разрешения, потому что я перехожу от ipad к Desktop

2: Изображение текстуры слишком велико, я выводил его, и он возвращает 630. Я попытался изменить его размер с помощью Unity Texture2D.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 clients = new List(); 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) { // 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. Ваш образ не полностью получен, прежде чем загружать их.

Вот почему ваше изображение выглядит таким странным.

Самая большая ошибка, которую люди делают при работе с socket, – это предположить, что все, что вы отправляете, будет получено сразу. Это неправда. Так кодируется ваш клиент. Пожалуйста, прочтите это .

Это метод, который я использовал в своем ответе:

Массив массива .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 байт, преобразует его обратно в длину, а затем использует эту длину в цикле, чтобы полностью прочитать 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. Я оставлю это для вас.

Вы можете использовать онлайн-код, чтобы посмотреть, что изменилось в каждом classе.

СЕРВЕР :

 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 clients = new List(); //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(); } } 

КЛИЕНТ :

 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(); } } } 
  • Как обнаружить разъединение сокета TCP (с гнездом C Berkeley)
  • C # Установленное соединение было прервано программным обеспечением вашей хост-машины
  • Разрешены ли параллельные вызовы send / recv в одном и том же сокете?
  • C # Begin / EndReceive - как читать большие данные?
  • Мгновенно обнаруживать отключение клиента от серверного сокета
  • Почему писать закрытый TCP-разъем хуже, чем читать?
  • Невозможно присвоить запрошенный адрес: JVM_Bind
  • Сокеты Java поддерживают полный дуплекс?
  • Как проверить, закрыто ли соединение TcpClient?
  • Передача структуры через Sockets в C
  • Что представляет собой самый большой размер безопасного пакета UDP в Интернете?
  • Давайте будем гением компьютера.