Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理

Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理

目录

Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理

一、简单介绍

二、实现原理

三、实现步骤

四、关键代码


一、简单介绍

Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。

本节介绍,这里在使用微软的Azure 进行语音合成的两个方法的做简单整理,这里简单说明,如果你有更好的方法,欢迎留言交流。

语音合成标记语言 (SSML) 是一种基于 XML 的标记语言,可用于微调文本转语音输出属性,例如音调、发音、语速、音量等。 与纯文本输入相比,你拥有更大的控制权和灵活性。

可以使用 SSML 来执行以下操作:

  • 定义输入文本结构,用于确定文本转语音输出的结构、内容和其他特征。 例如,可以使用 SSML 来定义段落、句子、中断/暂停或静音。 可以使用事件标记(例如书签或视素)来包装文本,这些标记可以稍后由应用程序处理。
  • 选择语音、语言、名称、样式和角色。 可以在单个 SSML 文档中使用多个语音。 调整重音、语速、音调和音量。 还可以使用 SSML 插入预先录制的音频,例如音效或音符。
  • 控制输出音频的发音。 例如,可以将 SSML 与音素和自定义词典配合使用来改进发音。 还可以使用 SSML 定义单词或数学表达式的具体发音。
下面是 SSML 文档的基本结构和语法的子集:
 
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="string"><mstts:backgroundaudio src="string" volume="string" fadein="string" fadeout="string"/><voice name="string" effect="string"><audio src="string"></audio><bookmark mark="string"/><break strength="string" time="string" /><emphasis level="value"></emphasis><lang xml:lang="string"></lang><lexicon uri="string"/><math xmlns="http://www.w3.org/1998/Math/MathML"></math><mstts:audioduration value="string"/><mstts:express-as style="string" styledegree="value" role="string"></mstts:express-as><mstts:silence type="string" value="string"/><mstts:viseme type="string"/><p></p><phoneme alphabet="string" ph="string"></phoneme><prosody pitch="value" contour="value" range="value" rate="value" volume="value"></prosody><s></s><say-as interpret-as="string" format="string" detail="string"></say-as><sub alias="string"></sub></voice>
</speak>

 SSML 语音和声音
语音合成标记语言 (SSML) 的语音和声音 - 语音服务 - Azure AI services | Microsoft Learn

官网注册:

面向学生的 Azure - 免费帐户额度 | Microsoft Azure

官网技术文档网址:

技术文档 | Microsoft Learn

官网的TTS:

文本转语音快速入门 - 语音服务 - Azure Cognitive Services | Microsoft Learn

Azure Unity SDK  包官网:

安装语音 SDK - Azure Cognitive Services | Microsoft Learn

SDK具体链接:

https://aka.ms/csspeech/unitypackage
 

二、实现原理

1、官网申请得到语音合成对应的 SPEECH_KEY 和 SPEECH_REGION

2、然后对应设置 语言 和需要的声音 配置

3、使用 SSML 带有流式获取得到音频数据,在声源中播放或者保存即可,样例如下

public static async Task SynthesizeAudioAsync()
{var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");using var speechSynthesizer = new SpeechSynthesizer(speechConfig, null);var ssml = File.ReadAllText("./ssml.xml");var result = await speechSynthesizer.SpeakSsmlAsync(ssml);using var stream = AudioDataStream.FromResult(result);await stream.SaveToWaveFileAsync("path/to/write/file.wav");
}

三、实现步骤

基础的环境搭建参照:Unity 工具 之 Azure 微软语音合成普通方式和流式获取音频数据的简单整理_unity 语音合成

1、脚本实现,挂载对应脚本到场景中

2、运行场景,会使用 SSML方式合成TTS,并播放

 

四、关键代码

1、AzureTTSDataWithSSMLHandler

using Microsoft.CognitiveServices.Speech;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using UnityEngine;/// <summary>
/// 使用 SSML 方式语音合成
/// </summary>
public class AzureTTSDataWithSSMLHandler
{/// <summary>/// Azure TTS 合成 必要数据/// </summary>private const string SPEECH_KEY = "YOUR_SPEECH_KEY";private const string SPEECH_REGION = "YOUR_SPEECH_REGION";private const string SPEECH_RECOGNITION_LANGUAGE = "zh-CN";private string SPEECH_VOICE_NAME = "zh-CN-XiaoxiaoNeural";/// <summary>/// 创建 TTS 中的参数/// </summary>private CancellationTokenSource m_CancellationTokenSource;private AudioDataStream m_AudioDataStream;private Connection m_Connection;private SpeechConfig m_Config;private SpeechSynthesizer m_Synthesizer;/// <summary>/// 音频获取事件/// </summary>private Action<AudioDataStream> m_AudioStream;/// <summary>/// 开始播放TTS事件/// </summary>private Action m_StartTTSPlayAction;/// <summary>/// 停止播放TTS事件/// </summary>private Action m_StartTTSStopAction;/// <summary>/// 初始化/// </summary>public void Initialized(){m_Config = SpeechConfig.FromSubscription(SPEECH_KEY, SPEECH_REGION);m_Synthesizer = new SpeechSynthesizer(m_Config, null);m_Connection = Connection.FromSpeechSynthesizer(m_Synthesizer);m_Connection.Open(true);}/// <summary>/// 开始进行语音合成/// </summary>/// <param name="msg">合成的内容</param>/// <param name="stream">获取到的音频流数据</param>/// <param name="style"></param>public async void Start(string msg, Action<AudioDataStream> stream, string style = "chat"){this.m_AudioStream = stream;await SynthesizeAudioAsync(CreateSSML(msg, SPEECH_RECOGNITION_LANGUAGE, SPEECH_VOICE_NAME, style));}/// <summary>/// 停止语音合成/// </summary>public void Stop(){m_StartTTSStopAction?.Invoke();if (m_AudioDataStream != null){m_AudioDataStream.Dispose();m_AudioDataStream = null;}if (m_CancellationTokenSource != null){m_CancellationTokenSource.Cancel();}if (m_Synthesizer != null){m_Synthesizer.Dispose();m_Synthesizer = null;}if (m_Connection != null){m_Connection.Dispose();m_Connection = null;}}/// <summary>/// 设置语音合成开始播放事件/// </summary>/// <param name="onStartAction"></param>public void SetStartTTSPlayAction(Action onStartAction){if (onStartAction != null){m_StartTTSPlayAction = onStartAction;}}/// <summary>/// 设置停止语音合成事件/// </summary>/// <param name="onAudioStopAction"></param>public void SetStartTTSStopAction(Action onAudioStopAction){if (onAudioStopAction != null){m_StartTTSStopAction = onAudioStopAction;}}/// <summary>/// 开始异步请求合成 TTS 数据/// </summary>/// <param name="speakMsg"></param>/// <returns></returns>private async Task SynthesizeAudioAsync(string speakMsg){Cancel();m_CancellationTokenSource = new CancellationTokenSource();var result = m_Synthesizer.StartSpeakingSsmlAsync(speakMsg);await result;m_StartTTSPlayAction?.Invoke();m_AudioDataStream = AudioDataStream.FromResult(result.Result);m_AudioStream?.Invoke(m_AudioDataStream);}private void Cancel(){if (m_AudioDataStream != null){m_AudioDataStream.Dispose();m_AudioDataStream = null;}if (m_CancellationTokenSource != null){m_CancellationTokenSource.Cancel();}}/// <summary>/// 生成 需要的 SSML XML 数据/// (格式不唯一,可以根据需要自行在增加删减)/// </summary>/// <param name="msg">合成的音频内容</param>/// <param name="language">合成语音</param>/// <param name="voiceName">采用谁的声音合成音频</param>/// <param name="style">合成时的语气类型</param>/// <returns>ssml XML</returns>private string CreateSSML(string msg, string language, string voiceName, string style = "chat"){// XmlDocumentXmlDocument xmlDoc = new XmlDocument();// 设置 speak 基础元素XmlElement speakElem = xmlDoc.CreateElement("speak");speakElem.SetAttribute("version", "1.0");speakElem.SetAttribute("xmlns", "http://www.w3.org/2001/10/synthesis");speakElem.SetAttribute("xmlns:mstts", "http://www.w3.org/2001/mstts");speakElem.SetAttribute("xml:lang", language);// 设置 voice 元素XmlElement voiceElem = xmlDoc.CreateElement("voice");voiceElem.SetAttribute("name", voiceName);// 设置 mstts:viseme 元素XmlElement visemeElem = xmlDoc.CreateElement("mstts", "viseme", "http://www.w3.org/2001/mstts");visemeElem.SetAttribute("type", "FacialExpression");// 设置 语气 元素XmlElement styleElem = xmlDoc.CreateElement("mstts", "express-as", "http://www.w3.org/2001/mstts");styleElem.SetAttribute("style", style.ToString().Replace("_", "-"));// 创建文本节点,包含文本信息XmlNode textNode = xmlDoc.CreateTextNode(msg);// 设置好的元素添加到 xml 中voiceElem.AppendChild(visemeElem);styleElem.AppendChild(textNode);voiceElem.AppendChild(styleElem);speakElem.AppendChild(voiceElem);xmlDoc.AppendChild(speakElem);Debug.Log("[SSML  XML] Result : " + xmlDoc.OuterXml);return xmlDoc.OuterXml;}}

2、AzureTTSMono

using Microsoft.CognitiveServices.Speech;
using System;
using System.Collections.Concurrent;
using System.IO;
using UnityEngine;[RequireComponent(typeof(AudioSource))]
public class AzureTTSMono : MonoBehaviour
{private AzureTTSDataWithSSMLHandler m_AzureTTSDataWithSSMLHandler;/// <summary>/// 音源和音频参数/// </summary>private AudioSource m_AudioSource;private AudioClip m_AudioClip;/// <summary>/// 音频流数据/// </summary>private ConcurrentQueue<float[]> m_AudioDataQueue = new ConcurrentQueue<float[]>();private AudioDataStream m_AudioDataStream;/// <summary>/// 音频播放完的事件/// </summary>private Action m_AudioEndAction;/// <summary>/// 音频播放结束的布尔变量/// </summary>private bool m_NeedPlay = false;private bool m_StreamReadEnd = false;private const int m_SampleRate = 16000;//最大支持60s音频 private const int m_BufferSize = m_SampleRate * 60;//采样容量private const int m_UpdateSize = m_SampleRate;//audioclip 设置过的数据个数private int m_TotalCount = 0;private int m_DataIndex = 0;#region Lifecycle functionprivate void Awake(){m_AudioSource = GetComponent<AudioSource>();m_AzureTTSDataWithSSMLHandler = new AzureTTSDataWithSSMLHandler();m_AzureTTSDataWithSSMLHandler.SetStartTTSPlayAction(() => { Debug.Log(" Play TTS "); });m_AzureTTSDataWithSSMLHandler.SetStartTTSStopAction(() => { Debug.Log(" Stop TTS "); AudioPlayEndEvent(); });m_AudioEndAction = () => { Debug.Log(" End TTS "); };m_AzureTTSDataWithSSMLHandler.Initialized();}// Start is called before the first frame updatevoid Start(){m_AzureTTSDataWithSSMLHandler.Start("今朝有酒,今朝醉,人生几年百花春", OnGetAudioStream);}// Update is called once per frameprivate void Update(){UpdateAudio();}#endregion#region Audio handler/// <summary>/// 设置播放TTS的结束的结束事件/// </summary>/// <param name="act"></param>public void SetAudioEndAction(Action act){this.m_AudioEndAction = act;}/// <summary>/// 处理获取到的TTS流式数据/// </summary>/// <param name="stream">流数据</param>public async void OnGetAudioStream(AudioDataStream stream){m_StreamReadEnd = false;m_NeedPlay = true;m_AudioDataStream = stream;Debug.Log("[AzureTTSMono] OnGetAudioStream");MemoryStream memStream = new MemoryStream();byte[] buffer = new byte[m_UpdateSize * 2];uint bytesRead;m_DataIndex = 0;m_TotalCount = 0;m_AudioDataQueue.Clear();// 回到主线程进行数据处理Loom.QueueOnMainThread(() =>{m_AudioSource.Stop();m_AudioSource.clip = null;m_AudioClip = AudioClip.Create("SynthesizedAudio", m_BufferSize, 1, m_SampleRate, false);m_AudioSource.clip = m_AudioClip;});do{bytesRead = await System.Threading.Tasks.Task.Run(() => m_AudioDataStream.ReadData(buffer));if (bytesRead <= 0){break;}// 读取写入数据memStream.Write(buffer, 0, (int)bytesRead);{var tempData = memStream.ToArray();var audioData = new float[memStream.Length / 2];for (int i = 0; i < audioData.Length; ++i){audioData[i] = (short)(tempData[i * 2 + 1] << 8 | tempData[i * 2]) / 32768.0F;}try{m_TotalCount += audioData.Length;// 把数据添加到队列中m_AudioDataQueue.Enqueue(audioData);// new 获取新的地址,为后面写入数据memStream = new MemoryStream();}catch (Exception e){Debug.LogError(e.ToString());}}} while (bytesRead > 0);m_StreamReadEnd = true;}/// <summary>/// Update 播放音频/// </summary>private void UpdateAudio() {if (!m_NeedPlay) return;//数据操作if (m_AudioDataQueue.TryDequeue(out float[] audioData)){m_AudioClip.SetData(audioData, m_DataIndex);m_DataIndex = (m_DataIndex + audioData.Length) % m_BufferSize;}//检测是否停止if (m_StreamReadEnd && m_AudioSource.timeSamples >= m_TotalCount){AudioPlayEndEvent();}if (!m_NeedPlay) return;//由于网络,可能额有些数据还没有过来,所以根据需要判断是否暂停播放if (m_AudioSource.timeSamples >= m_DataIndex && m_AudioSource.isPlaying){m_AudioSource.timeSamples = m_DataIndex;//暂停Debug.Log("[AzureTTSMono] Pause");m_AudioSource.Pause();}//由于网络,可能有些数据过来比较晚,所以这里根据需要判断是否继续播放if (m_AudioSource.timeSamples < m_DataIndex && !m_AudioSource.isPlaying){//播放Debug.Log("[AzureTTSMono] Play");m_AudioSource.Play();}}/// <summary>/// TTS 播放结束的事件/// </summary>private void AudioPlayEndEvent(){Debug.Log("[AzureTTSMono] End");m_NeedPlay = false;m_AudioSource.timeSamples = 0;m_AudioSource.Stop();m_AudioEndAction?.Invoke();}#endregion
}

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

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

相关文章

企事业数字培训及知识库平台

前言 随着信息化的进一步推进&#xff0c;目前各行各业都在进行数字化转型&#xff0c;本人从事过医疗、政务等系统的研发&#xff0c;和客户深入交流过日常办公中“知识”的重要性&#xff0c;再加上现在倡导的互联互通、数据安全、无纸化办公等概念&#xff0c;所以无论是企业…

jvs-rules API数据源配置说明(含配置APIdemo视频)

在JVS中&#xff0c;多数据源支持多种形态的数据接入&#xff0c;其中API是企业生产过程中常见的数据形态。使用数据源的集成配置&#xff0c;以统一的方式管理和集成多个API的数据。这些平台通常提供各种数据转换和处理功能&#xff0c;使得从不同数据源获取和处理数据变得更加…

tomcat设置PermSize

最近tomcat老是报错,查看了日志出现PermGen 内存不够用,重启tomcat后查询使用情况 通过启动参数发现没有设置 PermGen,继续通过jmap查看 jmap -heap 21179 发现99%已使用,而且默认是30.5M,太小了,这里设置成256M 1. 创建setenv.sh文件 在/usr/local/tomcat/bin目录下创建一个…

Gradio部署应用到服务器不能正常访问

用Gradio部署一个基于ChatGLM-6B的应用&#xff0c;发布到团队的服务器上&#xff08;局域网&#xff0c;公网不能访问&#xff09;&#xff0c;我将gradio应用发布到服务器的9001端口 import gradio as gr with gr.Blocks() as demo:......demo.queue().launch(server_port90…

leetcode228. 汇总区间

题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围 [a,b]…

BUUCTF pwn1_sctf_2016解题思路

题目代码 Welcome to index.php <?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier {protected $var;publi…

04 qt功能类、对话框类和文件操作

一 QT中时间和日期 时间 ---- QTime日期 ---- QDate对于Qt而言,在实际的开发过程中, 1)开发者可能知道所要使用的类 ---- >帮助手册 —>索引 -->直接输入类名进行查找 2)开发者可能不知道所要使用的类,只知道开发需求文档 ----> 帮助 手册,按下图操作: 1 …

Arcgis中直接通过sde更新sqlserver空间数据库失败

问题 背景 不知道有没有人经历过这样一个情况,我们直接在Arcgis中通过sde更新serserver数据库会失败,就是虽然在sde更新sqlserver数据库,但是在Navicat中通过sql语句来查询,发现数据并没有更新,如:上图中,更新数据库后,第一张图是sde打开的sqlserver数据库,它的数据库…

解析固态光耦的独特特点和优势

固态光耦概述及其重要性 固态光耦是一种电子元件&#xff0c;具有独特的光电隔离功能&#xff0c;广泛应用于电气控制、通信和电力系统等领域。本文将深入探讨固态光耦的特点和优势&#xff0c;介绍它在市场中的重要性以及如何提高收录和首页排名。 高速、高精度的信号传输 …

Leetcode-每日一题【剑指 Offer 26. 树的子结构】

题目 输入两棵二叉树A和B&#xff0c;判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构&#xff0c; 即 A中有出现和B相同的结构和节点值。 例如: 给定的树 A: 3 / \ 4 5 / \ 1 2 给定的树 B&#xff1a; 4 / 1 返回 true&#xff0…

Springboot整合RabbitMq,详细使用步骤

Springboot整合RabbitMq&#xff0c;详细使用步骤 1 添加springboot-starter依赖2 添加连接配置3 在启动类上添加开启注解EnableRabbit4 创建RabbitMq的配置类&#xff0c;用于创建交换机&#xff0c;队列&#xff0c;绑定关系等基础信息。5 生产者推送消息6 消费者接收消息7 生…

nodejs+vue+elementui招聘求职网站系统的设计与实现-173lo

&#xff08;1&#xff09;管理员的功能是最高的&#xff0c;可以对系统所在功能进行查看&#xff0c;修改和删除&#xff0c;包括企业和用户功能。管理员用例如下&#xff1a; 图3-1管理员用例图 &#xff08;2&#xff09;企业关键功能包含个人中心、岗位类型管理、招聘信息…

分布式 - 消息队列Kafka:Kafka消费者和消费者组

文章目录 1. Kafka 消费者是什么&#xff1f;2. Kafka 消费者组的概念&#xff1f;3. Kafka 消费者和消费者组有什么关系&#xff1f;4. Kafka 多个消费者如何同时消费一个分区&#xff1f; 1. Kafka 消费者是什么&#xff1f; 消费者负责订阅Kafka中的主题&#xff0c;并且从…

7.5.tensorRT高级(2)-RAII接口模式下的生产者消费者多batch实现

目录 前言1. RAII接口模式封装生产者消费者2. 问答环节总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-RAI…

【福建事业单位-公共基础-】01哲学基本概述和唯物论

【福建事业单位-公共基础-】01哲学基本概述和唯物论 一、哲学基本概述二、辩证唯物论&#xff08;1题&#xff09; 相关考点 一、哲学基本概述 向导、导向、指导&#xff0c;都是中性词&#xff0c;都可以&#xff1b;但是先导是褒义词&#xff0c;要跟上真正的哲学&#xff1…

gitee上传一个本地项目到一个空仓库

gitee上传一个本地项目到一个空仓库 引入 比如&#xff0c;你现在本地下载了一个半成品的框架&#xff0c;现在想要把这个本地项目放到gitee的仓库上&#xff0c;这时就需要我们来做到把这个本地项目上传到gitee上了。 具体步骤 1. 登录码云 地址&#xff1a;https://gite…

tkinter自定义控件:通过继承Frame实现Expander

文章目录 继承Frame点击事件Add函数 tkinter系列&#xff1a; GUI初步&#x1f48e;布局&#x1f48e;绑定变量&#x1f48e;绑定事件&#x1f48e;消息框&#x1f48e;文件对话框Frame控件&#x1f48e;PanedWindow和notebook控件扫雷小游戏&#x1f48e;强行表白神器 和其他…

Keil开发STM32单片机项目的三种方式

STM32单片机相比51单片机&#xff0c;内部结构复杂很多&#xff0c;因此直接对底层寄存器编码&#xff0c;相对复杂&#xff0c;这个需要我们了解芯片手册&#xff0c;对于复杂项目&#xff0c;这些操作可能需要反复编写&#xff0c;因此出现了标准库的方式&#xff0c;对寄存器…

嵌入式编译FFmpeg6.0版本并且组合x264

下载直通车:我用的是6.0版本的 1.准备编译: 2.进入ffmpeg源码目录&#xff0c;修改Makefile&#xff0c;添加编译选项&#xff1a; CFLAGS -fPIC 不加会报错 3.使用命令直接编译 ./configure --cross-prefix/home/xxx/bin/arm-linux-gnueabihf- --enable-cross-compile --targ…

【Redis】Redis三种集群模式-主从、哨兵、集群各自架构的优点和缺点对比

文章目录 前言1. 单机模式2. 主从架构3. 哨兵4. 集群模式总结 前言 如果Redis的读写请求量很大&#xff0c;那么单个实例很有可能承担不了这么大的请求量&#xff0c;如何提高Redis的性能呢&#xff1f;你也许已经想到了&#xff0c;可以部署多个副本节点&#xff0c;业务采用…