Потоковая передача WebCamTexture с новым API транспортного уровня Unity 5.1

Как говорится в вопросе, я пытался передать WebCamTexture от клиента с веб-камерой на сервер. Обе стороны (клиент и сервер) находятся в Unity. Позже клиент будет развернут на Android, а сервер станет десктопным приложением.

В настоящее время я получаю пиксели текстуры, используя:

tex.GetPixels32();

и их сериализация с помощью специального сериализатора (для оптимизации его размера). В настоящее время у меня есть несжатый массив байтов размером около 3,5 МБ на кадр, готовый к отправке. Я знаю, что он огромен, но я хотел, чтобы он был передан до начала с части сжатия и части реального времени.

Последней частью процесса должна быть отправка с использованием нового статического класса Unity NetworkTransport. Прошло много времени с тех пор, как я в последний раз использовал сокеты, и я действительно гнилой. В настоящее время я не могу заставить его работать.

Вот мой код для серверной части (для ясности опуская код сериализации):

void Start()
{
    webcamTexture = new WebCamTexture();
    Background.texture = webcamTexture;
    Background.material.mainTexture = webcamTexture;
    webcamTexture.Play();

    if (!_isStarted)
    {
        _isStarted = true;

        NetworkTransport.Init();
        m_Config = new ConnectionConfig();
        m_CommunicationChannel  = m_Config.AddChannel(QosType.ReliableFragmented);
        HostTopology topology = new HostTopology(m_Config, 12);
        m_GenericHostId = NetworkTransport.AddHost(topology, 0);
        byte error;
        m_ConnectionId = NetworkTransport.Connect(m_GenericHostId, ip, port, 0, out error);
    }
}

void Update()
{
    if (!_isStarted)
        return;

    NetworkEventType recData = NetworkTransport.Receive(out recHostId, out connectionId, out channelId, recBuffer, bufferSize, out dataSize, out error);

    switch (recData)
    {
        case NetworkEventType.Nothing:         //1
            break;
        case NetworkEventType.ConnectEvent:    //2
            Debug.Log("Received connection confirmation");
            _readyToSend = true;
            break;
        case NetworkEventType.DataEvent:       //3

            break;
        case NetworkEventType.DisconnectEvent: //4
            //one of the established connection has been disconnected
            Debug.Log(String.Format("Disconnect from host {0} connection {1}", recHostId, connectionId));

            break;
    }

    if (_readyToSend)
    {
        _readyToSend = false; // To send just the first frame

        byte[] colourArray = SerializeObject(MakeSerializable(GetRenderTexturePixels(webcamTexture))); // Serialize the webcam texture

        // Sending total size
        byte[] sizeToSend = BitConverter.GetBytes(colourArray.Length);
        NetworkTransport.Send(m_GenericHostId, m_ConnectionId, m_CommunicationChannel, sizeToSend, sizeToSend.Length, out error);

        byte[] bytes = new byte[bufferLenght];
        int remainingBytes = colourArray.Length;
        int index = 0;
        int i = 1;

        while (remainingBytes >= bufferLenght)
        {
            System.Buffer.BlockCopy(colourArray, index, bytes, 0, bufferLenght);
            NetworkTransport.Send(m_GenericHostId, m_ConnectionId, m_CommunicationChannel, bytes, bytes.Length, out error);
            remainingBytes -= bufferLenght;
            Debug.Log(i++ + "Remaining bytes: " + remainingBytes + " - Error: "+error);
            index += bufferLenght;
        }

        if (remainingBytes > 0) // Send the last fragment below bufferLenght bytes
        {
            System.Buffer.BlockCopy(colourArray, index, bytes, 0, remainingBytes);
            NetworkTransport.Send(m_GenericHostId, m_ConnectionId, m_CommunicationChannel, bytes, remainingBytes, out error);
            Debug.Log("Error: "+error);
        }
    }
}

А это клиентская сторона:

void Start()
{
    if (!_isStarted)
    {
        _isStarted = true;

        NetworkTransport.Init();

        m_Config = new ConnectionConfig();

        m_CommunicationChannel  = m_Config.AddChannel(QosType.ReliableFragmented);

        HostTopology topology = new HostTopology(m_Config, 12);

        m_GenericHostId = NetworkTransport.AddHost(topology, port, null);
    }
}

void Update()
{
    if (!_isStarted)
        return;

    int recHostId; 
    int connectionId; 
    int channelId; 
    byte[] recBuffer = new byte[bufferLenght]; 
    int bufferSize = bufferLenght;
    int dataSize;
    byte error;

    NetworkEventType recData = NetworkTransport.Receive(out recHostId, out connectionId, out channelId, recBuffer, bufferSize, out dataSize, out error);

    switch (recData)
    {
        case NetworkEventType.Nothing:         //1
            break;

        case NetworkEventType.ConnectEvent:    //2

                //somebody else connect to me
            Log.text += string.Format("Connect from host {0} connection {1}\n", recHostId, connectionId);
            break;

        case NetworkEventType.DataEvent:       //3

            if (!sizeReceived)
            {
                sizeReceived = true;

                if (dataSize == 2)
                {
                    bytesToReceive = BitConverter.ToInt16(recBuffer, 0);
                }
                else if (dataSize == 4)
                {
                    bytesToReceive = BitConverter.ToInt32(recBuffer, 0);
                }

                Debug.Log("We will receive: "+bytesToReceive);
            }
            else
            {
                Log.text = string.Format("Received event host {0} connection {1} channel {2} message length {3}\n", recHostId, connectionId, channelId, dataSize);

                Log.text += "Received " + bufferSize + " bytes\n";
                bytesToReceive -= bufferSize;
                Log.text += "Remaining " + bytesToReceive + " bytes\n";
            }
            break;

        case NetworkEventType.DisconnectEvent: //4

            break;

    }
}

Я знаю, что это заблокирует функцию обновления до отправки, но сейчас это не важно для меня, так как я просто пытаюсь передать кадр, чтобы понять, как работает эта новая система, и продолжить оттуда. В настоящее время я получаю эту ошибку после отправки первого пакета с буфером 32768 байт:

no free events for long message
UnityEngine.Networking.NetworkTransport:Send(Int32, Int32, Int32, Byte[], Int32, Byte&)
CameraStreamer:Update() (at Assets/Scripts/Client/CameraStreamer.cs:112)

Я также пытался использовать буфер 1024, и для отображения сообщения требуется больше времени (после более чем 100 успешно отправленных пакетов).

Согласно этой теме, это связано со слишком быстрой отправкой сообщений и заполнением очереди, но ни одно из предложенных решений мне не помогло. Я был бы признателен за любую помощь или ориентацию, поскольку документация Unity очень плохая.


person Aernarion    schedule 07.07.2015    source источник
comment
Я работаю над аналогичным проектом, и мне было бы очень интересно увидеть содержимое вашего метода сериализации. В любом случае, спасибо, что поделились своим ответом на эту проблему.   -  person Mario    schedule 29.02.2016


Ответы (2)


Этот конкретный тестовый код/пример по-прежнему будет иметь проблемы даже с патчем Unity 5.2.

Запустив код в Unity 5.3.4f1, я смог увидеть, что ошибка 4 (NetworkError.NoResource) возникает примерно после 200 пакетов и вскоре после этого прекращает отправку. Я предполагаю, что причина в том, что это была блокирующая отправка, и поэтому очередь сообщений никогда не очищается должным образом.

Я переписал код, чтобы захватить изображение веб-камеры с текстуры после 2-секундной задержки (поскольку веб-камере требуется время для инициализации, иначе вы отправите пустое изображение).

После отправки через изображение с веб-камеры один пакет на выполнение обновления теперь выглядит нормально. Возможно потому, что Update успел запуститься и не заблокировался. Поэтому отправьте пакет и выйдите из функции обновления, переместив индекс и другие переменные из функции обновления.

Надеюсь, это поможет кому-нибудь еще посмотреть на это, мне потребовалось 3 дня, чтобы понять.

РЕДАКТИРОВАТЬ: Для тестирования более простым методом является удаление блокировки отправки (пока бит) и добавление ее в сопрограмму. Кажется, так тоже работает.

person MidnightDev    schedule 10.04.2016

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

Все, что мне нужно было сделать, это отправить ответ с сервера клиенту с одним байтом после каждого полученного сообщения. Клиент ожидает такого типа ACK для отправки следующего сообщения. С этим все начало работать как шарм.

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

С уважением.

РЕДАКТИРОВАТЬ: это оказалось ошибкой Unity, и она была решена в Unity 5.2. (если я правильно помню).

person Aernarion    schedule 08.07.2015