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.mzph.cn/news/43634.shtml

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

相关文章

Qt creator之对齐参考线——新增可视化缩进功能

Qt creator随着官方越来越重视&#xff0c;更新频率也在不断加快&#xff0c;今天无意中发现qt creator新版有了对齐参考线&#xff0c;也称可视化缩进Visualize Indent&#xff0c;默认为启用状态。 下图为旧版Qt Creator显示设置栏&#xff1a; 下图为新版本Qt Creator显示设…

Day14 01-Shell脚本编程详解

文章目录 第一章 Shell编程【重点】1.1. Shell的概念介绍1.1.1. 命令解释器4.1.1.2. Shell脚本 1.2. Shell编程规范1.2.1. 脚本文件的结构1.2.2. 脚本文件的执行 1.3. Shell的变量1.3.1. 变量的用法1.3.2. 变量的分类1.3.3. 局部变量1.3.4. 环境变量1.3.5. 位置参数变量1.3.6. …

Python入门【内存管理机制、Python缓存机制、垃圾回收机制、分代回收机制】(三十二)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

LeetCode150道面试经典题-- 存在重复元素 II(简单)

1.题目 给你一个整数数组 nums 和一个整数 k &#xff0c;判断数组中是否存在两个 不同的索引 i 和 j &#xff0c;满足 nums[i] nums[j] 且 abs(i - j) < k 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 2.示例 示例 1&#xff1a; 输…

CSS中的字体属性有哪些值,并分别描述它们的作用。

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ font-style⭐ font-weight⭐ font-size⭐ font-family⭐ font-variant⭐ line-height⭐ letter-spacing⭐ word-spacing⭐ font⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专…

JS中对象数组深拷贝方法

structuredClone() JavaScript 中提供了一个原生 API 来执行对象的深拷贝&#xff1a;structuredClone。它可以通过结构化克隆算法创建一个给定值的深拷贝&#xff0c;并且还可以传输原始值的可转移对象。 当对象中存在循环引用时&#xff0c;仍然可以通过 structuredClone()…

【声波】声波在硼酸、硫酸镁 (MgSO4) 和纯水中的吸收研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

c++ | 字节转换 | 字长 | 机器位数

为什么有的时候脑子转不过来&#xff1f;&#xff1f; 为什么要对字节、机器长啊、位啊都要门清 位数 一般的就是指计算机的位数&#xff0c;比如64位/32位&#xff0c;更简单的理解&#xff0c;计算机就是在不停的做二进制的计算&#xff0c;比如32位计算机&#xff0c;在长…

[保研/考研机试] KY26 10进制 VS 2进制 清华大学复试上机题 C++实现

题目链接&#xff1a; 10进制 VS 2进制http://www.nowcoder.com/share/jump/437195121691738172415 描述 对于一个十进制数A&#xff0c;将A转换为二进制数&#xff0c;然后按位逆序排列&#xff0c;再转换为十进制数B&#xff0c;我们称B为A的二进制逆序数。 例如对于十进制…

4.物联网LWIP之C/S编程

LWIP配置 服务器端实现 客户端实现 错误分析 一。LWIP配置&#xff08;FREERTOS配置&#xff0c;ETH配置&#xff0c;LWIP配置&#xff09; 1.FREERTOS配置 为什么要修改定时源为Tim1&#xff1f;不用systick&#xff1f; 原因&#xff1a;HAL库与FREERTOS都需要使用systi…

C语言好题解析(三)

目录 选择题一选择题二选择题三选择题四编程题一编程题二 选择题一 以下程序段的输出结果是&#xff08;&#xff09;#include<stdio.h> int main() { char s[] "\\123456\123456\t"; printf("%d\n", strlen(s)); return 0; }A: 12 B: 13 …

Lnton羚通关于【PyTorch】教程:torchvision 目标检测微调

torchvision 目标检测微调 本教程将使用Penn-Fudan Database for Pedestrian Detection and Segmentation 微调 预训练的Mask R-CNN 模型。 它包含 170 张图片&#xff0c;345 个行人实例。 定义数据集 用于训练目标检测、实例分割和人物关键点检测的参考脚本允许轻松支持添加…

暴力模拟入门+简单:零件组装、塔子的签到题、塔子哥考试、平均像素值、换座位

暴力模拟入门 P1038 小红书-2022.9.23-零件组装 #include <bits/stdc.h> #include <cstdint> using namespace std;typedef long long LL; const int N 100001; int num[4]; LL d; vector<vector<LL>> v(4, vector<LL>(N));int main() {for(in…

解决Pycharm的Settings中Project不见了也无法选择Python Interpreter的方法

目录 一、问题如下二、解决方法 一、问题如下 突然打开项目没有python解释器&#xff0c;也无法重新配置python Interpreter&#xff0c;而且整个文件夹是黄色高亮的形式&#xff0c;如下显示&#xff0c;而且重新安装了pycharm也没用甚至说打开File–>Setting–>Projec…

日常BUG——普通页面跳转tabbar页面报错

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 微信小程序页面跳转的时候出现下面的问题&#xff1a; wx.redirectTo({url: /pages/index/i…

Linux学习之基本指令二

-----紧接上文 在了解cat指令之前&#xff0c;我们首先要了解到Linux下一切皆文件&#xff0c;在学习c语言时我们就已经了解到了 对文件输入以及读入的操作&#xff08;向显示器打印&#xff0c;从键盘读取数据&#xff09;&#xff0c;对于Linux下文件的操作&#xff0c;也是…

算法与数据结构(二十三)动态规划设计:最长递增子序列

注&#xff1a;此文只在个人总结 labuladong 动态规划框架&#xff0c;仅限于学习交流&#xff0c;版权归原作者所有&#xff1b; 也许有读者看了前文 动态规划详解&#xff0c;学会了动态规划的套路&#xff1a;找到了问题的「状态」&#xff0c;明确了 dp 数组/函数的含义&a…

js简介以及在html中的2种使用方式(hello world)

简介 javascript &#xff1a;是一个跨平台的脚本语言&#xff1b;是一种轻量级的编程语言。 JavaScript 是 Web 的编程语言。所有现代的 HTML 页面都使用 JavaScript。 HTML&#xff1a; 结构 css&#xff1a; 表现 JS&#xff1a; 行为 HTMLCSS 只能称之为静态网页&#xff0…

github以及上传代码处理

最近在github上传代码的时候出现了&#xff1a; /video_parser# git push -u origin main Username for https://github.com: gtnyxxx Password for https://gtny2010github.com: remote: Support for password authentication was removed on August 13, 2021. remote: Plea…

ROS局部路径规划器插件teb_local_planner流程梳理(上)

在我之前的文章《ROS导航包Navigation中的 Movebase节点路径规划相关流程梳理》中已经介绍过Move_base节点调用局部路径规划器插件的接口函数是computeVelocityCommands&#xff0c;接下来&#xff0c;我们就从这个函数入手梳理一下teb_local_planner功能包的工作流程。 ☆注&a…