Unity通过网络发送和接收麦克风音频

时间:2019-04-21 02:50:28

标签: c# unity3d

我有两个应用程序,一个用于服务器,另一个用于客户端。在服务器中,我试图从麦克风接收音频,并通过Tcp / Ip将其流式传输到客户端。但是在客户端应用程序中,当我转换从服务器接收的byte []时,会出现以下错误“ NullReferenceException:对象引用未设置为对象的实例

服务器

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 AudioServer1 : MonoBehaviour
{

    public bool enableLog = false;

    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;


    int lastSample = 0;
    public int FREQUENCY = 44100;
    AudioClip mic;
    int lastPos, pos;
    byte[] AudioBytes = null;
    private void Start()
    {
        Application.runInBackground = true;

        mic = Microphone.Start(null, true, 10, FREQUENCY);

        AudioSource audio = GetComponent<AudioSource>();
        audio.clip = AudioClip.Create("test", 10 * FREQUENCY, mic.channels, FREQUENCY, false);
        audio.loop = true;

        //Start Mic coroutine
        StartCoroutine(initAndWaitForMicTexture());
    }


    //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 initAndWaitForMicTexture()
    {


        // Connect to the server
        listner = new TcpListener(IPAddress.Any, port);

        listner.Start();

        while (101 < 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;
            byte[] pngBytes = AudioBytes;
            //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 Audio byte Length: " + frameBytesLength.Length);

                //Send the Audio bytes
                stream.Write(pngBytes, 0, pngBytes.Length);
                LOG("Sending Audio 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()
    {
        if ((pos = Microphone.GetPosition(null)) > 0)
        {
            if (lastPos > pos) lastPos = 0;

            if (pos - lastPos > 0)
            {
                // Allocate the space for the sample.
                float[] sample = new float[(pos - lastPos) * mic.channels];

                // Get the data from microphone.
                mic.GetData(sample, 0);

                AudioBytes = ToByteArray(sample);
            }
        }
    }

    // stop everything
    private void OnApplicationQuit()
    {


        if (listner != null)
        {
            listner.Stop();
        }

        foreach (TcpClient c in clients)
            c.Close();
    }
    public byte[] ToByteArray(float[] floatArray)
    {
        int len = floatArray.Length * 4;
        byte[] byteArray = new byte[len];
        int pos = 0;
        foreach (float f in floatArray)
        {
            byte[] data = System.BitConverter.GetBytes(f);
            System.Array.Copy(data, 0, byteArray, pos, 4);
            pos += 4;
        }
        return byteArray;
    }

    public float[] ToFloatArray(byte[] byteArray)
    {
        int len = byteArray.Length / 4;
        float[] floatArray = new float[len];
        for (int i = 0; i < byteArray.Length; i += 4)
        {
            floatArray[i / 4] = System.BitConverter.ToSingle(byteArray, i);
        }
        return floatArray;
    }
}

客户

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System;

public class AudioClient1 : MonoBehaviour
{

    public bool enableLog = false;

    const int port = 8010;
    public string IP = "192.168.10.2";
    TcpClient client;



    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!");

            AudioReceiver();
        });
    }


    void AudioReceiver()
    {
        //While loop in another Thread is fine so we don't block main Unity Thread
        Loom.RunAsync(() =>
        {
            while (!stop)
            {
                //Read Audio Count
                int AudioSize = readAudioByteSize(SEND_RECEIVE_COUNT);
                LOGWARNING("Received Audio byte Length: " + AudioSize);

                //Read Audio Bytes and Display it
                readFrameByteArray(AudioSize);
            }
        });
    }


    //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 Audio SIZE from Server///////////////////////////////////////////////////
    private int readAudioByteSize(int size)
    {
        bool disconnected = false;

        NetworkStream serverStream = client.GetStream();
        byte[] AudioBytesCount = new byte[size];
        var total = 0;
        do
        {
            var read = serverStream.Read(AudioBytesCount, 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(AudioBytesCount);
        }
        return byteLength;
    }

    /////////////////////////////////////////////////////Read Audio Data Byte Array from Server///////////////////////////////////////////////////
    private void readFrameByteArray(int size)
    {
        bool disconnected = false;

        NetworkStream serverStream = client.GetStream();
        byte[] AudioBytes = new byte[size];
        var total = 0;
        do
        {
            var read = serverStream.Read(AudioBytes, total, size - total);
            //Debug.LogFormat("Client recieved {0} bytes", total);
            if (read == 0)
            {
                disconnected = true;
                break;
            }
            total += read;
        } while (total != size);

        bool readyToReadAgain = false;

        //Play Audio
        if (!disconnected)
        {
            //Play Audio on the main Thread
            Loom.QueueOnMainThread(() =>
            {
                displayReceivedAudio(AudioBytes);
                readyToReadAgain = true;
            });
        }

        //Wait until old Audio is displayed
        while (!readyToReadAgain)
        {
            System.Threading.Thread.Sleep(1);
        }
    }


    void displayReceivedAudio(byte[] receivedAudioBytes)
    {
        Debug.Log(receivedAudioBytes.Length);
        // Put the data in the audio source.
        AudioSource audio = GetComponent<AudioSource>();
        audio.clip.SetData(ToFloatArray(receivedAudioBytes), 0);

        if (!audio.isPlaying) audio.Play();
    }
    public byte[] ToByteArray(float[] floatArray)
    {
        int len = floatArray.Length * 4;
        byte[] byteArray = new byte[len];
        int pos = 0;
        foreach (float f in floatArray)
        {
            byte[] data = System.BitConverter.GetBytes(f);
            System.Array.Copy(data, 0, byteArray, pos, 4);
            pos += 4;
        }
        return byteArray;
    }

    public float[] ToFloatArray(byte[] byteArray)
    {
        int len = byteArray.Length / 4;
        float[] floatArray = new float[len];
        for (int i = 0; i < byteArray.Length; i += 4)
        {
            floatArray[i / 4] = System.BitConverter.ToSingle(byteArray, i);
        }
        return floatArray;
    }

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

我得到的错误是下面的代码

    void displayReceivedAudio(byte[] receivedAudioBytes)
    {
        Debug.Log(receivedAudioBytes.Length);
        // Put the data in the audio source.
        AudioSource audio = GetComponent<AudioSource>();
        audio.clip.SetData(ToFloatArray(receivedAudioBytes), 0);

        if (!audio.isPlaying) audio.Play();
    }

2 个答案:

答案 0 :(得分:0)

在设置音频剪辑的数据之前,需要AudioClip.Create()。

作为替代,您可以使用“ OnAudioFilterRead()”捕获游戏中的音频。一般情况下,应将麦克风声音捕获为游戏内音频的一部分。 “ OnAudioFilterRead()”事件将返回带有特定采样率和通道的float []。您可以将它们转换为byte []以进行Tcp流传输。

在客户端

,您可以解码它们并将其分配给空音频源。您需要在OnAudioFilterRead()上分配float []才能使其可播放。

但是,当服务器和客户端设备之间的采样率和通道号不同时,仍然需要优化它们。在每次发送之前添加一些缓冲区也有帮助。

在我从事项目工作时,我找不到太多的参考/资源。 Unity论坛中有一个相关参考。

https://forum.unity.com/threads/670270/

答案 1 :(得分:0)

此代码应进行更多优化,但首先请确定此行有错误:

audio.clip.SetData(ToFloatArray(receivedAudioBytes), 0);

因为您的剪辑为空,所以您应该使用创建检查此链接: https://docs.unity3d.com/ScriptReference/AudioClip.Create.html