Unity接讯飞在线语音API

本文意在讲解如何利用讯飞官方提供的API通过讯飞服务实时的进行文字转语音。

先决条件:需要在讯飞官网注册自己的账号,拿到讯飞给的APPID、APISecret、APIKey,这三个字段是访问讯飞服务器时生成鉴权必备的。

一、点击这里进入讯飞语音合成官网,右上角登陆自己的账号(没有就注册)。点击右上角控制台创建自己的应用,应用名自己随便起,创建完之后讯飞会自动给这个应用创建APPID、APISecret、APIKey三个字段,如下图:

 

创建新应用后点击进入就能看到讯飞给该应用分配的APPID、APISecret、APIKey三个字段,如图:

 二、下面正式讲解讯飞API的使用方法。

 

按照上图标识位置进入讯飞WebAPI。

本人建议一步一步的阅读该API文档,不要跳读,不然会把自己搞晕,遇到读不懂的地方无需纠结,继续往下读就行,耐心读完之后前面不懂得地方自然而然就通了。

讯飞API文档有几点说的很清楚:(1)推荐的是WebSocket连接;(2)需要生成鉴权进行握手协议;(3)连接成功后即返回HTTP 101状态码,再进行文字转语音的请求

下面开始正式讲解:

1、生成鉴权

生成鉴权分三步:

(1)组装鉴权原始参数signature_origin

格式如下:

signature_origin="host: tts-api.xfyun.cn\ndate:Thu, 01, Aug  2019 01:53:21 GMT\nGET /v2/tts HTTP/1.1"

第一个host参数:就用 tts-api.xfyun.cn 不用变

第二个date参数:当前时间戳,官方要求RFC1123格式,DateTime.Now.ToString("r")这样就能获取该格式的当前时间戳

第三个request-line参数:就用 GET /v2/tts HTTP/1.1 不用变

(2)使用hmac-sha256算法对signature_origin进行加密,得到signature_sha

此步骤需要APISecrect一起做为参数进行加密
signature_sha = HmacSHA256(signature_origin, APISecret);

HmacSHA256方法如下:

    //加密算法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);
            //signRet = ToHexString(hash); ;
        }
        return signRet;
    }

(3)对signature_sha进行base64编码获取最终的signature

 signature = ToBase64String(signature_sha);

    /// <summary>
    /// 转成Base64编码
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string ToBase64String(string value)
    {
        if (value == null || value == "")
        {
            return "";
        }
        byte[] bytes = Encoding.UTF8.GetBytes(value);
        return Convert.ToBase64String(bytes);
    }

 (4)使用上面获取到的signature、APIKey等拼接authorization_origin参数

string auth = "api_key=\"{0}\", algorithm=\"{1}\", headers=\"{2}\", signature=\"{3}\"";
 authorization_origin = string.Format(auth, APIKey, "hmac-sha256", "host date request-line", signature);

api_Key:是你自己的APIKey

algorithm:是加密算法名,就用"hmac-sha256",不用变

headers:是参与签名的,是固定的无需变host date request-line

signature:就是上一步生成的signature

(5) 最后一步:使用base64编码对authorization_origin进行编码就能获取到最终的鉴权

authorization = ToBase64String(authorization_origin);

代码如下:

    /// <summary>
    /// 组装生成鉴权
    /// </summary>
    private void ComposeAuthUrl(Uri uri,string date)
    {        
        signature_origin = string.Format("host: " + uri.Host + "\ndate: " + date + "\nGET " + uri.AbsolutePath + " HTTP/1.1");
        Debug.Log("signature_origin: " + signature_origin);
        signature_sha = HmacSHA256(signature_origin, APISecret); //使用hmac - sha256算法结合apiSecret对signature_origin签名
        Debug.Log("signature_sha: " + signature_sha);
        signature = signature_sha;
        Debug.Log("signature: " + signature);
        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"),生成的签名
        Debug.Log("authorization_origin: " + authorization_origin);
        authorization = ToBase64String(authorization_origin);
        Debug.Log("authorization: " + authorization);
    }

进行到这里的同学已经完成了一半的工作量了。

2、利用生成的鉴权、RFC1123格式时间戳、host作为url参数进行websocket请求

void Init()
    {
        webSocket = new WebSocket(new Uri(GetUrl(url)));
        webSocket.OnOpen += OnOpen;
        webSocket.OnBinary += OnBinaryReceived;
        webSocket.OnMessage += OnMessage;
        webSocket.OnError += OnError;
        webSocket.OnClosed += OnClosed;
        Connect();
    }
    private void Connect()
    {
        webSocket.Open();
        Debug.Log("讯飞WebSocket打开成功!");
    }

    private void OnOpen(WebSocket ws)
    {
        Debug.Log("讯飞WebSocket连接成功!");
        isConnecting = true;
    }
    private void OnBinaryReceived(WebSocket ws, byte[] data)
    { 
    }
    private void OnError(WebSocket ws,Exception ex)
    {
        isConnecting = false;
        string sf = string.Format("编码{0},详细信息{1}", ws.InternalRequest.Response.StatusCode, ws.InternalRequest.Response.Message);
        Debug.Log("错误编码: "+sf);
    }
    private void OnClosed(WebSocket ws,UInt16 code,string message)
    {
        isConnecting = false;
        Debug.Log("讯飞WebSocket连接关闭!");
    }

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

 3、到这一步就实现了和讯飞服务器的连接,接下来把你要生成的语音内容传给服务器讯飞就会自动给你传回需要的语音,下面按照API组装参数即可

参数组装代码:

    /// <summary>
    /// 按照官方API组装传输参数
    /// </summary>
    /// <returns></returns>
    private JsonData CreateJsonData()
    {
        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";
        bussinessJson["vcn"] = "xiaoyan";
        bussinessJson["pitch"] = 50;
        bussinessJson["speed"] = 50;
        bussinessJson["tte"] = "UTF8";
        requestObj["business"] = bussinessJson;

        requestObj["data"] = new JsonData();
        JsonData dataJson = new JsonData();
        dataJson["status"] = 2;
        dataJson["text"] = ToBase64String(text);
        requestObj["data"] = dataJson;
        return requestObj;
    }

 通过websocket向讯飞服务器发送请求

 /// <summary>
    /// WebSocket Send
    /// </summary>
    private void SendMessage()
    {
        JsonData jsonData = CreateJsonData();
        webSocket.Send(JsonMapper.ToJson(jsonData));
    }

返回参数见API文档即可,怎么接收随你

至此讯飞API请求就算结束了,原代码如下:

using BestHTTP.WebSocket;
using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;

public class XunFeiAPIWebSocket : MonoBehaviour
{
    private string url = "wss://tts-api.xfyun.cn/v2/tts";
    WebSocket webSocket;
    bool isConnecting;
    string APPID = "xxxxxx"; //你自己的APPID
    string APISecret = "xxxxxxxx";
    string APIKey = "xxxxxxxx";
    string signature_origin = ""; //原始签名
    string signature_sha = ""; //使用hmac-sha256算法加密后的signature
    string signature; //最终编码后的签名
    string authorization_origin; //原始鉴权
    string authorization; //最终编码后的鉴权
    string text = "你好小薇,现在时间是多少呀!"; //要转换的文本
    int audioLength; //语音长度
    Queue<float> audionQue = new Queue<float>(); //转后的语音队列
    AudioSource audioSource;
    // Start is called before the first frame update
    void Start()
    {
        Init();
    }

    void Init()
    {
        audioLength = 0;
        audionQue.Clear();
        if (audioSource == null)
        {
            audioSource = gameObject.GetComponent<AudioSource>();
        }
        webSocket = new WebSocket(new Uri(GetUrl(url)));
        webSocket.OnOpen += OnOpen;
        webSocket.OnBinary += OnBinaryReceived;
        webSocket.OnMessage += OnMessage;
        webSocket.OnError += OnError;
        webSocket.OnClosed += OnClosed;
        Connect();
    }
    private void Connect()
    {
        webSocket.Open();
        Debug.Log("讯飞WebSocket打开成功!");
    }

    private void OnOpen(WebSocket ws)
    {
        Debug.Log("讯飞WebSocket连接成功!");
        isConnecting = true;
        if (isConnecting)
        {
            SendMessage();
        }
    }
    private void OnBinaryReceived(WebSocket ws, byte[] data)
    {
    }
    private void OnMessage(WebSocket ws, string message)
    {
        JsonData js = JsonMapper.ToObject(message);
        if (js["message"].ToString() == "success")
        {
            if (js["data"] != null)
            {
                if (js["data"]["audio"] != null)
                {
                    float[] fs = bytesToFloat(Convert.FromBase64String(js["data"]["audio"].ToString()));
                    audioLength += fs.Length;
                    foreach (float f in fs)
                    {
                        audionQue.Enqueue(f);
                    }
                    if ((int)js["data"]["status"] == 2) //2为结束标志符
                    {
                        //ws.Close();
                        audioSource.clip = AudioClip.Create("MySinusoid", 16000 * 60, 1, 16000, true, OnAudioRead); //要生成的音频名称、样本帧数(乘以60代表采样时长为1分钟)、每帧的声道数、剪辑采样频率、音频是否以流格式传输、调用该回调以生成样本数据块
                        AudioClip cp = audioSource.clip;
                        //audioSource.clip. = audioSource.time / 5;
                        audioSource.Play();
                    }
                }
            }
        }
    }
    /// <summary>
    /// 采样回调
    /// </summary>
    /// <param name="data"></param>
    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.IsOpen)
                    audioLength++;
                data[i] = 0;
            }
        }
    }
    private void OnError(WebSocket ws, Exception ex)
    {
        isConnecting = false;
        string sf = string.Format("编码{0},详细信息{1}", ws.InternalRequest.Response.StatusCode, ws.InternalRequest.Response.Message);
        Debug.Log("错误编码: " + sf);
    }
    private void OnClosed(WebSocket ws, UInt16 code, string message)
    {
        isConnecting = false;
        Debug.Log("讯飞WebSocket连接关闭!");
    }

    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;
    }
    /// <summary>
    /// 组装生成鉴权
    /// </summary>
    private void ComposeAuthUrl(Uri uri,string date)
    {        
        signature_origin = string.Format("host: " + uri.Host + "\ndate: " + date + "\nGET " + uri.AbsolutePath + " HTTP/1.1");
        Debug.Log("signature_origin: " + signature_origin);
        signature_sha = HmacSHA256(signature_origin, APISecret); //使用hmac - sha256算法结合apiSecret对signature_origin签名
        Debug.Log("signature_sha: " + signature_sha);
        signature = signature_sha;
        Debug.Log("signature: " + signature);
        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"),生成的签名
        Debug.Log("authorization_origin: " + authorization_origin);
        authorization = ToBase64String(authorization_origin);
        Debug.Log("authorization: " + authorization);
    }

    /// <summary>
    /// WebSocket Send
    /// </summary>
    private void SendMessage()
    {
        JsonData jsonData = CreateJsonData();
        webSocket.Send(JsonMapper.ToJson(jsonData));
    }
    /// <summary>
    /// 按照官方API组装传输参数
    /// </summary>
    /// <returns></returns>
    private JsonData CreateJsonData()
    {
        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";
        bussinessJson["vcn"] = "xiaoyan";
        bussinessJson["pitch"] = 50;
        bussinessJson["speed"] = 50;
        bussinessJson["tte"] = "UTF8";
        requestObj["business"] = bussinessJson;

        requestObj["data"] = new JsonData();
        JsonData dataJson = new JsonData();
        dataJson["status"] = 2;
        dataJson["text"] = ToBase64String(text);
        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);
    }

    /// <summary>
    /// byte[]数组转化为AudioClip可读取的float[]类型
    /// </summary>
    /// <param name="byteArray"></param>
    /// <returns></returns>
    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;
    }
    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;
    }
}

该代码里的注释很详细,如还有不懂评论区留言

新建一个空场景,场景里随便建一个物体,物体挂上Audio Source组件,然后将该脚本挂上去就能直接将  string text = "你好小薇,现在时间是多少呀!"; 这句话读出来

注:需要两个插件 com.bestHttp和LitJson,百度下载导入到项目里即可,需要的评论区留言

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值