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算法加密后的signaturestring 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/// <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.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;}/// <summary>/// 组装生成鉴权/// </summary>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/// <summary>/// WebSocket Send/// </summary>/// <param name="text">文本内容</param>/// <param name="vcn">发音人</param>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);}}/// <summary>/// 按照官方API组装传输参数/// </summary>/// <returns></returns>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:未压缩的pcmbussinessJson["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;                     //数据状态,固定为2dataJson["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进制格式stringpublic 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;}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);elses = (short)((firstByte << 8) | secondByte);// convert to range from -1 to (just below) 1return s / 32768.0F;}private class XunFeiData{public string text;    //内容public string vcn;     //发音人public XunFeiData(string text, string vcn){this.text = text;this.vcn = vcn;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/655679.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UPS负载过大有什么危害性

UPS&#xff08;不间断电源&#xff09;是一种用于保护电子设备免受电力波动和突然停电影响的设备。然而&#xff0c;如果UPS负载过大&#xff0c;可能会对其性能和寿命产生严重影响。以下是UPS负载过大的一些危害性&#xff1a; 1. 降低UPS效率&#xff1a;当UPS负载过大时&am…

C++ 11新特性之week_ptr

概述 在C11 标准中&#xff0c;智能指针的引入极大地提升了内存管理的安全性和便利性。除了已经广为人知的shared_ptr和unique_ptr之外&#xff0c;还有一个重要但相对较少被单独提及的智能指针类型——std::weak_ptr。std::weak_ptr是C 11引入的一种弱引用智能指针&#xff0c…

【RT-DETR改进涨点】ResNet18、34、50、101等多个版本移植到ultralytics仓库(RT-DETR官方一比一移植)

👑欢迎大家订阅本专栏,一起学习RT-DETR👑 一、本文介绍 本文是本专栏的第一篇改进,我将RT-DETR官方版本中的ResNet18、ResNet34、ResNet50、ResNet101移植到ultralytics仓库,网上很多改进机制是将基础版本的也就是2015年发布的ResNet移植到ultralytics仓库中,但是其实…

JavaScript进阶教程 - 事件循环和回调队列

JavaScript的事件循环是一种执行模型&#xff0c;它允许JavaScript引擎执行异步任务&#xff0c;尽管JavaScript是单线程的。这个模型确保了即使在执行长时间运行的操作&#xff08;如从服务器获取数据&#xff09;时&#xff0c;JavaScript代码也能保持响应性。事件循环和回调…

百度云网盘下载速度如何提升到正常速度

引入问题 我们在下载代码学习资料的时候大多数都是百度云网盘&#xff0c;但是限速&#xff01;下载的十分的慢&#xff0c;有什么办法能让我们不开通会员就能享受正常速度呢&#xff1f; 当然有&#xff01; 解决百度云网盘下载速度过慢&#xff0c;提高到正常速度 点击右…

Element ui 的组件弹窗 el-dialog点击的时候全屏变灰问题解决

最近在使用Element UI 的弹窗组件的时候发现这个组件各种的应用都没有问题&#xff0c;数据和元素的应用都是正确的但是在点击显示这个弹窗的时候全屏幕都会变灰。 这也不是因为增加了modal 遮挡幕的问题&#xff0c;在经过不断的排查代码的时候基本排除了代码的问题&#xf…

算法笔记:地理探测器

1 空间分层异质性&#xff08;spatial stratified heterogeneity&#xff09; 空间分层异质性&#xff08;空间分异性/区异性&#xff09;&#xff1a;层内方差小于层间方差的地理现象例如气 候带、土地利用图、地貌图、生物区系、区际经济差异、城乡差异以及主体功能区等 等[…

mybatis 实现查询默认添加分页

前言 分页查询在日常开发中无法避免,但每次sql编写时,mybatis 中使用limit,oracle中使用rownum分页,业务场景少的情况下,可以接受.但是随着业务增加每次相同的功能做重复开发又不是那么方便,那么我们有什么方案去解决开发中出现的分页问题呢? 一、PageHelper 框架分页 首先我…

温酒读Qt:QObject中篇2 ——欲遮还羞的 QObjectPrivate

《妙法莲华经》曰&#xff1a;“佛道长远&#xff0c;久受勤苦&#xff0c;乃可得成。” 世事修炼&#xff0c;莫不如是&#xff0c;日拱一卒无有尽&#xff0c;功不唐捐终入海。 传送门: 《温酒读Qt&#xff1a;QObject 序篇》 《温酒读Qt&#xff1a;QObject中篇1—— Q_OBJ…

Neo4j在java中的使用

1.Neo4j访问的两种方式 嵌入式数据库服务器模式(通过REST的访问) 它是由应用程序的性质&#xff08;neo4j是独立服务器 还是和程序在一起),性能&#xff0c;监控和数据安全性来决定架构选择。 An embedded database&#xff08;嵌入式数据库&#xff09; 嵌入式Neo4j数据库…

前端框架---Vue2学习教程(上)

从HTML到现在一路跟过来的小伙伴们&#xff0c;坚持固然不容易&#xff0c;但我相信大家已经学到了不少&#xff0c;那么我们开始马不停蹄的进入前端的框架吧&#xff0c;下面讲的是Vue2&#xff0c;大家继续加油鸭&#xff01;&#xff01;&#xff01;&#xff01; Vue2 Vu…

装机打不开BIOS怎么办?如何进入Windows10的BIOS页面,如何关闭快速启动

电脑有快速启动&#xff0c;想进去BIOS页面非常困难&#xff0c;在临开机的页面&#xff0c;按触发按键不管用。 然后我看到了一种新的进入BIOS的方式&#xff1a; &#xff08;1&#xff09;win8以上的系统&#xff0c;按住shift&#xff0c;然后鼠标点击重启&#xff0c;再…

代码随想录算法训练营第34天(贪心算法03● 1005.K次取反后最大化的数组和 ● 134. 加油站 ● 135. 分发糖果

贪心算法 part03 1005.K次取反后最大化的数组和解题思路 134. 加油站解题思路注意点 135. 分发糖果解题思路 1005.K次取反后最大化的数组和 本题简单一些&#xff0c;估计大家不用想着贪心 &#xff0c;用自己直觉也会有思路。 题目链接&#xff1a; 1005.K次取反后最大化的数组…

蓝桥杯2024/1/26笔记-----基于PCF8591的电压采集装置

功能实现要求&#xff1a; 每次建好工程文件夹&#xff0c;里边包含User&#xff08;放工程文件&#xff0c;mian.c&#xff0c;可以在这里写如同我这个文章的文本文档&#xff09;、Driver&#xff08;存放底层文件如Led.c&#xff0c;Led.h等&#xff09; 新建的工程先搭建框…

推荐一款Linux、数据库、Redis、MongoDB统一管理平台!

官方演示 状态查看 ssh 终端 文件操作 数据库操作 sql 编辑器 在线增删改查数据 Redis 操作 Mongo 操作 系统管理 账号管理 角色管理 资源管理 一.安装 1.下载安装包 cd /opt wget https://gitee.com/dromara/mayfly-go/releases/download/v1.7.1/mayfly-go-linux-amd64.zi…

CES 2024:AI赋能机器人,国产机器人更亮眼

原创 | 文 BFT机器人 一年一度的国际消费电子展(CES)又与我们见面了。作为全球消费电子和科技创新的盛会&#xff0c;CES每年都吸引着无数目光。今年&#xff0c;AI赋能机器人成为展会的一大亮点&#xff0c;而国产机器人更是凭借其创新技术和实用功能&#xff0c;成为全场焦点…

使用QT实现播放gstreamer的命令(二)

一、前言 上一篇文章写到了&#xff0c;如何快速使用C来执行gstreamer的命令&#xff0c;如何在QT中显示gstreamer的画面&#xff0c;原文如下&#xff1a; https://blog.csdn.net/Alon1787/article/details/135107958 二、近期的其他发现&#xff1a; 1.gstreamer的画面显示在…

蓝桥杯AT24C02问题记录

问题1&#xff1a;从这个图片上可以看出这两个在IIC的.c文件里延时时间不一样&#xff0c;第一张图使用了15个_nop_(); 12M晶振机器周期是 1/12M*121uS&#xff1b;nop()要延时1个指令周期。延时时间不对会对时序产生影响&#xff0c;时序不对&#xff0c;则AT24C02有没被使用…

DAY34:贪心算法part、1005\134\135

Leetcode: 1005 K次取反后最大化的数组和 基本思路 这道题的思路比较简单&#xff0c;如果有负数&#xff0c;就先把最大的负数转化成正数&#xff0c;如果全部转换完之后还有k剩余&#xff0c;就将最小的正数反复正负变化。但是需要注意一点代码的写法。 代码注意点 定义绝…

关于Spring Boot和MyBatis常见的十道面试题

拦截器和过滤器有什么区别&#xff1f; 拦截器&#xff08;Interceptor&#xff09;和过滤器&#xff08;Filter&#xff09;都是用于在请求道道目标资源的之前或之后进行处理的组件。主要区别有以下几点&#xff1a; 依赖对象不同&#xff1a;过滤器是来时Servlet&#xff0…