web-dev-qa-db-fra.com

Unity: streaming vidéo en direct

J'essaie de diffuser une vidéo en direct d'une application à une autre.J'ai actuellement 2 applications. étaient l'application 1 est le serveur/expéditeur et l'application 2 est le client/récepteur. Dans l'application 1, j'ai réussi à envoyer les octets vidéo au client. et côté client, je reçois également tous les octets. Im utilisant des sockets et TCP. Le problème auquel je suis confronté est le suivant: lorsque je reçois les octets vidéo et les affecte à une texture d'image brute, l'image sur la texture semble trop zoomée et elle est tellement pixélisée.

Image mise à jour

enter image description here

C'est ce que je diffuse enter image description here

et c'est ce que j'obtiens sur le client.
This is what i see on the rawimage when i receive the bytes

C'est le 1er problème, mais je teste actuellement d'un bureau à l'autre, mon objectif est de diffuser un IPAD sur un bureau, et quand je le fais, c'est tellement lent et il tue l'application à la fois sur l'ipad et sur le bureau.

J'ai essayé jusqu'à présent quelques problèmes.

1: Je pense que cela se produit parce que j'ai 2 résolutions différentes parce que je diffuse de l'ipad vers le bureau

2: L'image de texture est trop grande, je la renvoie et elle renvoie 630. J'ai essayé de la redimensionner en utilisant Unity Texture2D.resize mais j'obtiens une texture grise car la fonction définit les pixels comme non identifiés

3: J'ai utilisé d'autres bibliothèques pour redimensionner les textures et j'obtiens ce que je veux, mais après 12 images, l'image brute commence à clignoter entre la vidéo et "?" la texture tellement puis elle se fige sur les deux applications (iPad et bureau)

4: je crois que la façon dont je lis la texture est à l'origine du problème car j'utilise à la fois les fonctions Setpixels et Getpixels et elles sont lourdes.

Mon code: côté serveur/expéditeur:

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



}

Côté client/récepteur

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

J'ai exécuté votre code et cela a fonctionné parfois et échoué parfois (environ 90% du temps). Il a fonctionné sur mon ordinateur avec 5 FPS . Cela ne fonctionnera pas bien sur un appareil mobile dont je suis sûr que vous ciblez l'iPad.

Il y a peu de problèmes dans votre code mais ce sont des problèmes très graves.


1.Votre image n'est pas complètement reçue avant de les charger.

C'est pourquoi votre image est si bizarre.

La plus grande erreur que les gens commettent en travaillant avec socket est de supposer que tout ce que vous envoyez sera reçu en même temps. Ce n'est pas vrai. C'est ainsi que votre client est codé. Veuillez lire ceci .

Voici la méthode que j'ai utilisée dans ma réponse:

[~ # ~] a [~ # ~] . Obtenez le tableau d'octets Texture2D.

[~ # ~] b [~ # ~] . Envoyez la longueur du tableau d'octets. Pas le tableau d'octets mais la longueur.

[~ # ~] c [~ # ~] . Le client lira d'abord la longueur.

[~ # ~] d [~ # ~] . Le client utilisera cette longueur pour lire l'intégralité des données de texture/pixel jusqu'à la fin.

[~ # ~] e [~ # ~] . Convertissez les octets reçus en tableau.

Vous pouvez consulter les fonctions private int readImageByteSize(int size) et private void readFrameByteArray(int size) pour savoir comment lire tous les octets.

Bien sûr, vous devez également connaître la longueur de la longueur des données envoyées en premier. La longueur est enregistrée dans le type de données int.

La valeur max int est 2,147,483,647 Et est composée de 10 Chiffres. Donc, j'ai fait la longueur de tableau du tableau qui est envoyé en premier pour être 15 Comme protocole. C'est une règle qui doit également être respectée côté client.

Voici comment cela fonctionne maintenant:

Lisez le tableau d'octets de Texture2D, Lisez la longueur de ce tableau, envoyez-le au client. Le client suit une règle selon laquelle les premiers 15 Octets sont simplement la longueur. Le client lirait alors que 15 Octets, le reconvertirait en longueur puis utiliserait cela longueur dans une boucle pour lire la totalité Texture2D à partir du serveur.

La conversion de longueur se fait avec les fonctions void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes) et int frameByteArrayToByteLength(byte[] frameBytesLength). Jetez un œil à ceux-ci pour les comprendre.


2.Performing socket operation in the Main Thread.

C'est pourquoi le [~ # ~] fps [~ # ~] est 5 Sur mon ordinateur.

Ne faites pas cela car cela rendra votre fréquence d'images faible, comme c'est déjà le cas. J'ai répondu à de nombreuses questions comme celle-ci, mais je n'entrerai pas dans les détails car il semble que vous sachiez ce que vous faites et que j'ai essayé d'utiliser Thread mais que je l'ai mal fait.

[~ # ~] a [~ # ~] . Vous lisiez à partir du Thread principal lorsque vous avez fait: serverStream.Read(readBuffer, 0, readBuffer.Length); dans la fonction Update.

Tu aurais dû faire ça à l'intérieur

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

[~ # ~] b [~ # ~] . Vous avez fait la même erreur dans la fonction SendPng, lorsque vous envoyiez des données avec la stream.Write(pngBytes, 0, pngBytes.Length); dans le

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

Tout ce que vous faites à l'intérieur de Loom.QueueOnMainThread Sera fait dans le Thread principal.

Vous êtes censé faire l'envoi dans une autre Thread.Loom.RunAsync(() =>{});


Enfin, listner = new TcpListener(port); est obsolète. Cela n'a pas posé de problème mais utilisez listner = new TcpListener(IPAddress.Any, port); dans votre code serveur qui devrait écouter nay IP.

La finale [~ # ~] fps [~ # ~] est terminée 50 Sur mon ordinateur après avoir fait toutes ces corrections. Le code ci-dessous peut être considérablement amélioré. Je vous laisse le soin de le faire.

Vous pouvez utiliser comparaison de code en ligne pour voir les choses qui ont changé dans chaque classe.

[~ # ~] serveur [~ # ~] :

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