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