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;}}
}
相关文章:
unity 讯飞webapi在线语音合成
websocker插件使用的unitywebsocker 讯飞webapi,连接后只能请求一次,所以每次使用时进行连接,连接成功后进行请求,请求完成后关闭连接。 为什么连接后只能请求一次呢,可能是方便统计使用量。 如何通过音频数据计算出…...
[NCTF2019]Fake XML cookbook(特详解)
先试了一下弱口令,哈哈习惯了 查看页面源码发现xml function doLogin(){var username $("#username").val();var password $("#password").val();if(username "" || password ""){alert("Please enter the usern…...
腾讯云SDK并发调用优化方案
目录 一、概述 二、 网关的使用 2.1 核心代码 三、腾讯云SDK依赖包的改造 一、概述 此网关主要用于协调腾讯云SDK调用的QPS消耗,使得多个腾讯云用户资源能得到最大限度的利用。避免直接使用腾讯云SDK 时,在较大并发情况下导致接口调用异常。网关的工…...
【排序算法】C语言实现随机快排,巨详细讲解
文章目录 🚀前言🚀快排的核心过程partition(划分过程)🚀快排1.0🚀随机快速排序🚀稳定性 🚀前言 铁子们好啊!继续我们排序算法今天要讲的是快排,通常大家所说…...
Java强训day13(选择题编程题)
选择题 编程题 题目1 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String s sc.nextLine();char[] c s.toCharArray();int i 0;int t 0;while (i < c.length) {if (c[i] ! \") {…...
搭建WebGL开发环境
前言 本篇文章介绍如何搭建WebGL开发环境 WebGL WebGL的技术规范继承自免费和开源的OpenGL ES标准,从某种意义上说,WebGL就是Web版的OpenGL ES,而OpenGL ES是从OpenGL中派生出来的。他们的应用环境有区别,一般来说:…...
学习嵌入式第十五天之结构体
用变量a给出下面的定义 a) 一个整型数(An integer) //int a;b) 一个指向整型数的指针(A pointer to an integer) //int *a;c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a poin…...
【HDFS】一天一个RPC系列--updateBlockForPipeline
本文目标是: 弄清updateBlockForPipeline这个RPC的作用。弄清updateBlockForPipeline RPC的使用场景,代码里的调用点。一、updateBlockForPipeline的作用 其定义在ClientProtocol接口里,是Client与NameNode之间的接口。 看其代码注释描述: 为一个under construction状态下…...
测试面试题(0101设计测试用例关键)
1. 测试计划 测试范围,本次改动的模块,新增了哪些功能测试策略,包含测试依据,测试准入标准,准出标准,测试重点及方法(确认功能的优先级),测试工具的选择测试管理&#x…...
C++ 数论相关题目:高斯消元解异或线性方程组
输入一个包含 n 个方程 n 个未知数的异或线性方程组。 方程组中的系数和常数为 0 或 1 ,每个未知数的取值也为 0 或 1 。 求解这个方程组。 异或线性方程组示例如下: M[1][1]x[1] ^ M[1][2]x[2] ^ … ^ M[1][n]x[n] B[1] M[2][1]x[1] ^ M[2][2]x[2]…...
嵌入式学习第十四天
1.结构体(2): (1)结构体类型定义 (2)结构体变量的定义 (3)结构体元素的访问 (4)结构体的存储: 内存对齐: char 按照1字节对齐 …...
氢气泄漏检测仪使用方法:守护安全,从细节开始
随着科技的发展,我们的生活和工作环境中充满了各种潜在的危险。其中,氢气作为一种清洁能源,其使用日益广泛,但同时也带来了泄漏的风险。为了确保我们的安全,了解并正确使用氢气泄漏检测仪至关重要。下面将详细介绍氢气…...
【前端web入门第二天】01 html语法实现列表与表格_合并单元格
html语法实现列表与表格 文章目录: 1.列表 1.1 无序列表1.2 有序列表1.3 定义列表 2.表格 2.1 表格基本结构2.2 表格结构标签2.3 合并单元格 写在最前,第二天学习目标: 列表 表格 表单 元素为嵌套关系 1.列表 作用:布局内容排列整齐的区域。 列表分类:无序列表、有序列表…...
推荐系统|排序_MMOE
MMOE MMOE是指Multi-gate Mixture-of-Experts 注意看Expert后面加了s,说明了有多个专家。 而在MMOE中专家是指用来对输入特征计算的神经网络,每个神经网络根据输入计算出来的向量都会有所不同。 MMOE的低层 MMOE的上一层 通过MMOE的低层算出的向量和权…...
Redis拒绝连接的原因与解决方式
Redis拒绝连接的原因与解决方式 在某些情况下,当尝试从外部计算机连接到运行在保护模式下的Redis服务器时,您可能会遇到如下的错误信息: Caused by: org.redisson.client.RedisException: DENIED Redis is running in protected mode becau…...
Neo4j在java中的使用
1.Neo4j访问的两种方式 嵌入式数据库服务器模式(通过REST的访问) 它是由应用程序的性质(neo4j是独立服务器 还是和程序在一起),性能,监控和数据安全性来决定架构选择。 An embedded database(嵌入式数据库) 嵌入式Neo4j数据库…...
故障诊断 | 一文解决,CNN卷积神经网络故障诊断(Matlab)
文章目录 效果一览文章概述专栏介绍源码设计参考资料效果一览 文章概述 故障诊断 | 一文解决,CNN卷积神经网络故障诊断(Matlab) 专栏介绍 订阅【故障诊断】专栏,不定期更新机器学习和深度学习在故障诊断中的应用;订阅...
uniapp-app使用富文本编辑器editor
使用的是uniapp内置组件的表单组件editor:editor 组件 | uni-app官网 (dcloud.net.cn) editor 组件对应的 editorContext 实例:editorContext | uni-app官网 (dcloud.net.cn) 文档上写的也不是特别详细,还以为得npm,但没npm也能用…...
20240131 大模型快讯
//社区生态// 国内首个音视频多媒体大模型万兴“天幕”正式发布。万兴科技发布国内首个音视频多媒体大模型万兴“天幕”,支持多种语言,实现音视频创作闭环。 //行业落地// 全球首款搭载AI大模型的MPV智能座舱发布。江淮全新MPV瑞风RF8上市发布…...
MySQL原理(二)存储引擎(2)MyISAM
一、MyISAM介绍 1、介绍: MyISAM引擎是MySQL5.5版本之前的数据库所默认的数据表引擎。每一个采用MyISAM引擎的数据表在实际存储中都是由三个文件组成,分别是frm文件保存表的结构,MYD文件保存表的数据、MYI文件保存表的索引,文件…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
