Unity: потоковое видео

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

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

Это то, что я поток

и это то, что я получаю на клиенте.
Это то, что я вижу на изображении

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

Некоторые проблемы, которые я пробовал до сих пор.

1: я думаю, что это происходит, потому что у меня есть 2 разных разрешения, потому что я транслирую с ipad на рабочий стол

2: Текстурное изображение слишком большое, я вывожу его, и оно возвращает 630. Я попытался изменить его размер с помощью Unity Texture2D.resize, но я получил серую текстуру, потому что функция устанавливает пиксели как неопознанные

3: я использовал другие библиотеки для изменения размера текстур, и я получаю то, что хочу, но после 12 кадров сырье начинает мигать между видео и "?" текстуру так много, что она зависает как в приложении (на 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();
}
}

1 ответ

Решение

Я запускал ваш код, и иногда он работал, а иногда и не получался (примерно в 90% случаев). Он работал на моем компьютере с 5 FPS. Это не будет хорошо работать на мобильном устройстве, которое, я уверен, вы ориентируетесь на iPad.

В вашем коде мало проблем, но это очень серьезные проблемы.


1. Ваше изображение не полностью получено, прежде чем загрузить их.

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

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

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

A. Получить 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 но сделал это неправильно.

Вы читали из основного Thread когда вы сделали: serverStream.Read(readBuffer, 0, readBuffer.Length); в Update функция.

Вы должны были сделать это внутри

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

Б. Вы сделали ту же ошибку в 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); в коде вашего сервера, который должен слушать NAY IP.

Окончательный FPS окончен 50 на моем компьютере после внесения всех этих исправлений. Код ниже может быть улучшен много. Я оставлю это для вас, чтобы сделать.

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

СЕРВЕР:

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

КЛИЕНТ:

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();
        }
    }
}
Другие вопросы по тегам