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文件保存表的索引,文件…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...