unity 讯飞webapi在线语音合成

websocker插件使用的unitywebsocker

讯飞webapi,连接后只能请求一次,所以每次使用时进行连接,连接成功后进行请求,请求完成后关闭连接。
为什么连接后只能请求一次呢,可能是方便统计使用量。

如何通过音频数据计算出时间呢?我这里通过 音频byte长度 / 采样率(16000) / 2 ,然后向上取整。

XunFeiAPIWebSocket .cs

using System;
using System.Text;
using System.Collections.Generic;
using System.Security.Cryptography;
using UnityEngine;
using UnityWebSocket;
using LitJson;
using System.Collections;

[RequireComponent(typeof(AudioSource))]
public class XunFeiAPIWebSocket : MYTOOL.MonoSingleton<XunFeiAPIWebSocket>
{
    [SerializeField] string url = "wss://tts-api.xfyun.cn/v2/tts";

    [Space, SerializeField] string APPID = "";     //你自己的APPID
    [SerializeField] string APISecret = "";
    [SerializeField] string APIKey = "";

    WebSocket webSocket;

    string signature_origin = "";   //原始签名
    string signature_sha = "";      //使用hmac-sha256算法加密后的signature
    string signature;               //最终编码后的签名
    string authorization_origin;    //原始鉴权
    string authorization;           //最终编码后的鉴权

    private readonly Queue<float> audionQue = new Queue<float>();   //转后的语音队列
    private int audioLength;                                        //语音长度

    AudioSource audioSource;

    private readonly Queue<XunFeiData> sendQue = new Queue<XunFeiData>();

    //统计
    private int total_data_audio_length = 0;

    private void Start()
    {
        OnStart();
    }

    private void OnDestroy()
    {
        if (webSocket != null && webSocket.ReadyState != WebSocketState.Closed)
        {
            webSocket.CloseAsync();
        }
    }

    void OnStart()
    {
        audioLength = 0;
        audionQue.Clear();
        if (audioSource == null)
        {
            audioSource = gameObject.GetComponent<AudioSource>();
        }

        webSocket = new WebSocket(GetUrl(url));
        webSocket.OnOpen += Socket_OnOpen;
        webSocket.OnMessage += Socket_OnMessage;
        webSocket.OnError += Socket_OnError;
        webSocket.OnClose += Socket_OnClose;

        //Connect();
    }

    private void Connect()
    {
        if (webSocket.ReadyState != WebSocketState.Open)
        {
            webSocket.ConnectAsync();
        }
    }

    #region >> websocker回调
    private void Socket_OnOpen(object sender, OpenEventArgs e)
    {
        Send();
        //Debug.Log("讯飞WebSocket连接成功!");
    }

    private void Socket_OnMessage(object sender, MessageEventArgs e)
    {
        if (e.IsText)
        {
            JsonData js = JsonMapper.ToObject(e.Data);
            if (js["message"].ToString() == "success")
            {
                if (js["data"] != null)
                {
                    if (js["data"]["audio"] != null)
                    {
                        string data_audio = js["data"]["audio"].ToString();
                        byte[] byte_data_audio = Convert.FromBase64String(data_audio);
                        float[] fs = bytesToFloat(byte_data_audio);
                        audioLength += fs.Length;

                        total_data_audio_length += byte_data_audio.Length;
                        foreach (float f in fs)
                        {
                            audionQue.Enqueue(f);
                        }
                        if ((int)js["data"]["status"] == 2) //2为结束标志符
                        {
                            webSocket.CloseAsync();//关闭
                            float audioLengthInSeconds = total_data_audio_length / 16000f / 2;
                            int audioLengthInSecondsCeiling = (int)Math.Ceiling(audioLengthInSeconds);

                            audioSource.clip = AudioClip.Create("MySinusoid", 16000 * audioLengthInSecondsCeiling, 1, 16000, true, OnAudioRead); //要生成的音频名称、样本帧数(乘以60代表采样时长为1分钟)、每帧的声道数、剪辑采样频率、音频是否以流格式传输、调用该回调以生成样本数据块
                            AudioClip cp = audioSource.clip;
                            audioSource.Play();
                            Debug.Log($"结束处理音频数据  {js["data"]["status"]} {js["sid"]} {total_data_audio_length} {audioLengthInSeconds} {audioLengthInSecondsCeiling}");
                            total_data_audio_length = 0;
                        }
                    }
                }
            }
        }
        else if (e.IsBinary)
        {

        }
    }

    private void Socket_OnClose(object sender, CloseEventArgs e)
    {
        Debug.Log($"讯飞WebSocket连接关闭!{e.StatusCode}, {e.Reason}");
    }

    private void Socket_OnError(object sender, ErrorEventArgs e)
    {
        Debug.Log($"错误信息: {e.Message}");
    }
    #endregion

    /// 
    /// 采样回调
    /// 
    /// 
    void OnAudioRead(float[] data) //经测试,它应该是运行在子线程中的。 测试方法:打印某个组件的值,出现报错信息,只能在主线程进行访问
    {
        for (int i = 0; i < data.Length; i++)
        {
            if (audionQue.Count > 0)
                data[i] = audionQue.Dequeue();
            else
            {
                if (webSocket == null || webSocket.ReadyState != WebSocketState.Open)
                    audioLength++;
                data[i] = 0;
            }
        }
    }
    
    #region >> 组装生成鉴权
    private string GetUrl(string url)
    {
        Uri uri = new Uri(url);
        string date = DateTime.Now.ToString("r"); //官方文档要求时间必须是UTC+0或GMT时区,RFC1123格式(Thu, 01 Aug 2019 01:53:21 GMT)。
        ComposeAuthUrl(uri, date);
        string uriStr = string.Format("{0}?authorization={1}&date={2}&host={3}", uri, authorization, date, uri.Host); //生成最终鉴权
        return uriStr;
    }
    /// 
    /// 组装生成鉴权
    /// 
    private void ComposeAuthUrl(Uri uri, string date)
    {
        signature_origin = string.Format("host: " + uri.Host + "\ndate: " + date + "\nGET " + uri.AbsolutePath + " HTTP/1.1");
        signature_sha = HmacSHA256(signature_origin, APISecret); //使用hmac - sha256算法结合apiSecret对signature_origin签名
        signature = signature_sha;

        string auth = "api_key=\"{0}\", algorithm=\"{1}\", headers=\"{2}\", signature=\"{3}\"";
        authorization_origin = string.Format(auth, APIKey, "hmac-sha256", "host date request-line", signature); //参数介绍:APIKey,加密算法名,headers是参与签名的参数(该参数名是固定的"host date request-line"),生成的签名
        authorization = ToBase64String(authorization_origin);
    }
    #endregion

    /// 
    /// WebSocket Send
    /// 
    /// 文本内容
    /// 发音人
    public void Send(string text, string vcn = "xiaoyan")
    {
        if (string.IsNullOrWhiteSpace(text))
        {
            //空白,不处理
            return;
        }
        XunFeiData data = new XunFeiData(text, vcn);
        sendQue.Clear();
        sendQue.Enqueue(data);

        StopAudioPlay();

        if (webSocket.ReadyState == WebSocketState.Open)
        {
            Send();
        }
        else
        {
            //重新连接,连接成功后会执行Send方法
            Connect();
        }
    }

    public void StopAudioPlay()
    {
        audionQue.Clear();
        audioLength = 0;

        if (AudioSourceCoroutine != null)
        {
            StopCoroutine(AudioSourceCoroutine);
        }
        if (audioSource != null && audioSource.isPlaying)
        {
            audioSource.Stop();
        }
    }

    private void Send()
    {
        if (sendQue.Count > 0)
        {
            XunFeiData data = sendQue.Dequeue();
            JsonData jsonData = CreateJsonData(data.text, data.vcn);
            string json = JsonMapper.ToJson(jsonData);
            webSocket.SendAsync(json);
        }
    }

    /// 
    /// 按照官方API组装传输参数
    /// 
    /// 
    private JsonData CreateJsonData(string text, string vcn)
    {
        JsonData requestObj = new JsonData();
        requestObj["common"] = new JsonData();
        JsonData commonJson = new JsonData();
        commonJson["app_id"] = APPID;
        requestObj["common"] = commonJson;

        requestObj["business"] = new JsonData();
        JsonData bussinessJson = new JsonData();
        bussinessJson["aue"] = "raw";           //raw:未压缩的pcm
        bussinessJson["vcn"] = vcn;             //发音人
        bussinessJson["speed"] = 80;            //语速
        bussinessJson["pitch"] = 50;            //音高
        bussinessJson["tte"] = "UTF8";
        requestObj["business"] = bussinessJson;

        requestObj["data"] = new JsonData();
        JsonData dataJson = new JsonData();
        dataJson["status"] = 2;                     //数据状态,固定为2
        dataJson["text"] = ToBase64String(text);    //文本内容,需进行base64编码。base64编码前最大长度需小于8000字节,约2000汉字
        requestObj["data"] = dataJson;
        return requestObj;
    }

    //加密算法HmacSHA256  
    private static string HmacSHA256(string secret, string signKey)
    {
        string signRet = string.Empty;
        using (HMACSHA256 mac = new HMACSHA256(Encoding.UTF8.GetBytes(signKey)))
        {
            byte[] hash = mac.ComputeHash(Encoding.UTF8.GetBytes(secret));
            signRet = Convert.ToBase64String(hash);
        }
        return signRet;
    }


    //byte[]转16进制格式string
    public static string ToHexString(byte[] bytes)
    {
        string hexString = string.Empty;
        if (bytes != null)
        {
            StringBuilder strB = new StringBuilder();
            foreach (byte b in bytes)
            {
                strB.AppendFormat("{0:x2}", b);
            }
            hexString = strB.ToString();
        }
        return hexString;
    }

    ///编码
    public static string EncodeBase64(string code_type, string code)
    {
        string encode = "";
        byte[] bytes = Encoding.GetEncoding(code_type).GetBytes(code);
        try
        {
            encode = Convert.ToBase64String(bytes);
        }
        catch
        {
            encode = code;
        }
        return encode;
    }

    public static string ToBase64String(string value)
    {
        if (value == null || value == "")
        {
            return "";
        }
        byte[] bytes = Encoding.UTF8.GetBytes(value);
        return Convert.ToBase64String(bytes);
    }

    /// 
    /// byte[]数组转化为AudioClip可读取的float[]类型
    /// 
    /// 
    /// 
    public static float[] bytesToFloat(byte[] byteArray)
    {
        float[] sounddata = new float[byteArray.Length / 2];
        for (int i = 0; i < sounddata.Length; i++)
        {
            sounddata[i] = bytesToFloat(byteArray[i * 2], byteArray[i * 2 + 1]);
        }
        return sounddata;
    }
    private static float bytesToFloat(byte firstByte, byte secondByte)
    {
        // convert two bytes to one short (little endian)
        //小端和大端顺序要调整
        short s;
        if (BitConverter.IsLittleEndian)
            s = (short)((secondByte << 8) | firstByte);
        else
            s = (short)((firstByte << 8) | secondByte);
        // convert to range from -1 to (just below) 1
        return s / 32768.0F;
    }


    private class XunFeiData
    {
        public string text;    //内容
        public string vcn;     //发音人

        public XunFeiData(string text, string vcn)
        {
            this.text = text;
            this.vcn = vcn;
        }
    }
}

你可能感兴趣的:(unity,游戏引擎)