Unity: live streaming video

Sto provando a trasmettere in streaming un video dal vivo da un’app all’altra, Attualmente ho 2 app. l’app 1 era il server / mittente e l’app 2 è il client / ricevitore. Nell’app 1 invio correttamente i byte video al client. e sul lato client sto ricevendo anche tutti i byte. Sto usando socket e TCP. Il problema che sto affrontando è, quando ricevo i byte del video e li assegno a una trama di immagini non elaborate, l’immagine sulla trama sembra ingrandita troppo ed è così pixellata.

Immagine aggiornata

inserisci la descrizione dell'immagine qui

Questo è ciò che io streaming inserisci la descrizione dell'immagine qui

e questo è ciò che ottengo sul client.
Questo è ciò che vedo su rawimage quando ricevo i byte

    Questo è il primo problema, ma al momento sto provando da desktop a altro, il mio objective è quello di eseguire lo streaming di un IPAD su un desktop, e quando lo faccio è così lento e uccide l’app su ipad e desktop.

    Alcuni problemi di risoluzione dei problemi che ho provato finora.

    1: Penso che questo stia succedendo perché ho 2 risoluzioni diverse perché sto trasmettendo da ipad a desktop

    2: L’immagine della trama è troppo grande, l’ho stampata e restituisce 630. Ho provato a ridimensionarla usando Unity Texture2D.resize ma ottengo una texture grigia perché la funzione imposta i pixel come non identificati

    3: Ho usato altre librerie per ridimensionare le trame e ottengo quello che voglio, ma dopo 12 fotogrammi l’immagine grezza inizia a sfarfallare tra il video e “?” trama così tanto poi si blocca su entrambe le app (ipad e desktop)

    4: Credo che il modo in cui sto leggendo la trama stia causando il problema perché utilizzo sia le funzioni Setpixels che Getpixels e sono pesanti.

    Il mio codice: lato server / mittente:

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

    Lato cliente / destinatario

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

    Ho eseguito il tuo codice e ha funzionato a volte e non è riuscito a volte (circa il 90% delle volte). Funzionava con il mio computer con 5 FPS . Questo non funzionerà bene sul dispositivo mobile, che sono sicuro che stai prendendo di mira iPad.

    Ci sono alcuni problemi nel codice ma sono problemi molto seri.


    1.L’immagine non viene completamente ricevuta prima di caricarla.

    Questo è il motivo per cui la tua immagine sembra così strana.

    L’errore più grande che le persone commettono quando si lavora con il socket è presumere che tutto ciò che si invia verrà ricevuto in una volta. Questo non è vero. Questo è il modo in cui il tuo cliente è codificato. Per favore, leggi questo .

    Questo è il metodo che ho usato nella mia risposta:

    A. Texture2D matrice di byte Texture2D .

    B. Invia la lunghezza dell’array di byte. Non la matrice di byte ma la lunghezza.

    C. Il cliente leggerà prima la lunghezza.

    D. Il client userà quella lunghezza per leggere l’intera trama dati / pixel fino al completamento.

    E. Converti i byte ricevuti nell’array.

    È ansible consultare le private int readImageByteSize(int size) e private void readFrameByteArray(int size) su come leggere tutti i byte.

    Ovviamente, è necessario conoscere anche la lunghezza della lunghezza dei dati che viene inviata per prima. La lunghezza viene salvata nel tipo di dati int.

    Il valore max int è 2,147,483,647 e vale 10 cifre. Quindi, ho reso la lunghezza dell’array dell’array che è stato inviato per primo come 15 come protocollo. Questa è una regola che deve essere rispettata anche dal lato del cliente.

    Ecco come funziona ora:

    Leggere l’array di byte da Texture2D , leggere la lunghezza di tale array, inviarlo al client. Il cliente segue una regola secondo cui i primi 15 byte sono semplicemente la lunghezza. Il client dovrebbe quindi leggere quei 15 byte, convertirli di nuovo in lunghezza, quindi usare quella lunghezza in un ciclo per leggere Texture2D completa dal server.

    La conversione della lunghezza viene eseguita con le void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes) e int frameByteArrayToByteLength(byte[] frameBytesLength) . Dai un’occhiata a quelli per capirli.


    2. Eseguire l’operazione di socket nella filettatura principale.

    Questo è il motivo per cui l’ FPS è 5 sul mio computer.

    Non farlo in quanto ciò renderà il frame rate basso proprio come lo è già. Ho risposto a molte domande come questa ma non approfondirò perché sembra che tu sappia cosa stai facendo e hai provato a usare Thread ma ha sbagliato.

    A. Stava leggendo dal Thread principale quando hai fatto: serverStream.Read(readBuffer, 0, readBuffer.Length); nella funzione di Update .

    Avresti dovuto farlo dentro

     Loom.RunAsync(() => { //your red code }); 

    B. Hai fatto lo stesso errore nella funzione SendPng , quando stavi inviando dati con lo stream.Write(pngBytes, 0, pngBytes.Length); nel

     Loom.QueueOnMainThread(() => {}); 

    Tutto ciò che fai all’interno di Loom.QueueOnMainThread sarà fatto nella Thread principale.

    Dovresti fare l’invio in un’altra Thread.Loom.RunAsync(() =>{});


    Infine, listner = new TcpListener(port); è obsoleto. Questo non ha causato alcun problema, ma usa listner = new TcpListener(IPAddress.Any, port); nel tuo codice server che dovrebbe ascoltare IP anonimo.

    L’ FPS finale supera i 50 sul mio computer dopo aver effettuato tutte queste correzioni. Il seguente codice può essere migliorato un sacco. Lascerò che tu faccia per te.

    Puoi utilizzare il confronto di codice online per vedere le cose che sono cambiate in ogni class.

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

    Cliente :

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