当前位置:   article > 正文

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

unity 接入tts

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

目录

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

一、简单介绍

二、实现原理

三、实现步骤

四、关键代码


一、简单介绍

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

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

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

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

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

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

三、实现步骤

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

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

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

 

四、关键代码

1、AzureTTSDataWithSSMLHandler

  1. using Microsoft.CognitiveServices.Speech;
  2. using System;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using System.Xml;
  6. using UnityEngine;
  7. /// <summary>
  8. /// 使用 SSML 方式语音合成
  9. /// </summary>
  10. public class AzureTTSDataWithSSMLHandler
  11. {
  12. /// <summary>
  13. /// Azure TTS 合成 必要数据
  14. /// </summary>
  15. private const string SPEECH_KEY = "YOUR_SPEECH_KEY";
  16. private const string SPEECH_REGION = "YOUR_SPEECH_REGION";
  17. private const string SPEECH_RECOGNITION_LANGUAGE = "zh-CN";
  18. private string SPEECH_VOICE_NAME = "zh-CN-XiaoxiaoNeural";
  19. /// <summary>
  20. /// 创建 TTS 中的参数
  21. /// </summary>
  22. private CancellationTokenSource m_CancellationTokenSource;
  23. private AudioDataStream m_AudioDataStream;
  24. private Connection m_Connection;
  25. private SpeechConfig m_Config;
  26. private SpeechSynthesizer m_Synthesizer;
  27. /// <summary>
  28. /// 音频获取事件
  29. /// </summary>
  30. private Action<AudioDataStream> m_AudioStream;
  31. /// <summary>
  32. /// 开始播放TTS事件
  33. /// </summary>
  34. private Action m_StartTTSPlayAction;
  35. /// <summary>
  36. /// 停止播放TTS事件
  37. /// </summary>
  38. private Action m_StartTTSStopAction;
  39. /// <summary>
  40. /// 初始化
  41. /// </summary>
  42. public void Initialized()
  43. {
  44. m_Config = SpeechConfig.FromSubscription(SPEECH_KEY, SPEECH_REGION);
  45. m_Synthesizer = new SpeechSynthesizer(m_Config, null);
  46. m_Connection = Connection.FromSpeechSynthesizer(m_Synthesizer);
  47. m_Connection.Open(true);
  48. }
  49. /// <summary>
  50. /// 开始进行语音合成
  51. /// </summary>
  52. /// <param name="msg">合成的内容</param>
  53. /// <param name="stream">获取到的音频流数据</param>
  54. /// <param name="style"></param>
  55. public async void Start(string msg, Action<AudioDataStream> stream, string style = "chat")
  56. {
  57. this.m_AudioStream = stream;
  58. await SynthesizeAudioAsync(CreateSSML(msg, SPEECH_RECOGNITION_LANGUAGE, SPEECH_VOICE_NAME, style));
  59. }
  60. /// <summary>
  61. /// 停止语音合成
  62. /// </summary>
  63. public void Stop()
  64. {
  65. m_StartTTSStopAction?.Invoke();
  66. if (m_AudioDataStream != null)
  67. {
  68. m_AudioDataStream.Dispose();
  69. m_AudioDataStream = null;
  70. }
  71. if (m_CancellationTokenSource != null)
  72. {
  73. m_CancellationTokenSource.Cancel();
  74. }
  75. if (m_Synthesizer != null)
  76. {
  77. m_Synthesizer.Dispose();
  78. m_Synthesizer = null;
  79. }
  80. if (m_Connection != null)
  81. {
  82. m_Connection.Dispose();
  83. m_Connection = null;
  84. }
  85. }
  86. /// <summary>
  87. /// 设置语音合成开始播放事件
  88. /// </summary>
  89. /// <param name="onStartAction"></param>
  90. public void SetStartTTSPlayAction(Action onStartAction)
  91. {
  92. if (onStartAction != null)
  93. {
  94. m_StartTTSPlayAction = onStartAction;
  95. }
  96. }
  97. /// <summary>
  98. /// 设置停止语音合成事件
  99. /// </summary>
  100. /// <param name="onAudioStopAction"></param>
  101. public void SetStartTTSStopAction(Action onAudioStopAction)
  102. {
  103. if (onAudioStopAction != null)
  104. {
  105. m_StartTTSStopAction = onAudioStopAction;
  106. }
  107. }
  108. /// <summary>
  109. /// 开始异步请求合成 TTS 数据
  110. /// </summary>
  111. /// <param name="speakMsg"></param>
  112. /// <returns></returns>
  113. private async Task SynthesizeAudioAsync(string speakMsg)
  114. {
  115. Cancel();
  116. m_CancellationTokenSource = new CancellationTokenSource();
  117. var result = m_Synthesizer.StartSpeakingSsmlAsync(speakMsg);
  118. await result;
  119. m_StartTTSPlayAction?.Invoke();
  120. m_AudioDataStream = AudioDataStream.FromResult(result.Result);
  121. m_AudioStream?.Invoke(m_AudioDataStream);
  122. }
  123. private void Cancel()
  124. {
  125. if (m_AudioDataStream != null)
  126. {
  127. m_AudioDataStream.Dispose();
  128. m_AudioDataStream = null;
  129. }
  130. if (m_CancellationTokenSource != null)
  131. {
  132. m_CancellationTokenSource.Cancel();
  133. }
  134. }
  135. /// <summary>
  136. /// 生成 需要的 SSML XML 数据
  137. /// (格式不唯一,可以根据需要自行在增加删减)
  138. /// </summary>
  139. /// <param name="msg">合成的音频内容</param>
  140. /// <param name="language">合成语音</param>
  141. /// <param name="voiceName">采用谁的声音合成音频</param>
  142. /// <param name="style">合成时的语气类型</param>
  143. /// <returns>ssml XML</returns>
  144. private string CreateSSML(string msg, string language, string voiceName, string style = "chat")
  145. {
  146. // XmlDocument
  147. XmlDocument xmlDoc = new XmlDocument();
  148. // 设置 speak 基础元素
  149. XmlElement speakElem = xmlDoc.CreateElement("speak");
  150. speakElem.SetAttribute("version", "1.0");
  151. speakElem.SetAttribute("xmlns", "http://www.w3.org/2001/10/synthesis");
  152. speakElem.SetAttribute("xmlns:mstts", "http://www.w3.org/2001/mstts");
  153. speakElem.SetAttribute("xml:lang", language);
  154. // 设置 voice 元素
  155. XmlElement voiceElem = xmlDoc.CreateElement("voice");
  156. voiceElem.SetAttribute("name", voiceName);
  157. // 设置 mstts:viseme 元素
  158. XmlElement visemeElem = xmlDoc.CreateElement("mstts", "viseme", "http://www.w3.org/2001/mstts");
  159. visemeElem.SetAttribute("type", "FacialExpression");
  160. // 设置 语气 元素
  161. XmlElement styleElem = xmlDoc.CreateElement("mstts", "express-as", "http://www.w3.org/2001/mstts");
  162. styleElem.SetAttribute("style", style.ToString().Replace("_", "-"));
  163. // 创建文本节点,包含文本信息
  164. XmlNode textNode = xmlDoc.CreateTextNode(msg);
  165. // 设置好的元素添加到 xml 中
  166. voiceElem.AppendChild(visemeElem);
  167. styleElem.AppendChild(textNode);
  168. voiceElem.AppendChild(styleElem);
  169. speakElem.AppendChild(voiceElem);
  170. xmlDoc.AppendChild(speakElem);
  171. Debug.Log("[SSML XML] Result : " + xmlDoc.OuterXml);
  172. return xmlDoc.OuterXml;
  173. }
  174. }

2、AzureTTSMono

  1. using Microsoft.CognitiveServices.Speech;
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.IO;
  5. using UnityEngine;
  6. [RequireComponent(typeof(AudioSource))]
  7. public class AzureTTSMono : MonoBehaviour
  8. {
  9. private AzureTTSDataWithSSMLHandler m_AzureTTSDataWithSSMLHandler;
  10. /// <summary>
  11. /// 音源和音频参数
  12. /// </summary>
  13. private AudioSource m_AudioSource;
  14. private AudioClip m_AudioClip;
  15. /// <summary>
  16. /// 音频流数据
  17. /// </summary>
  18. private ConcurrentQueue<float[]> m_AudioDataQueue = new ConcurrentQueue<float[]>();
  19. private AudioDataStream m_AudioDataStream;
  20. /// <summary>
  21. /// 音频播放完的事件
  22. /// </summary>
  23. private Action m_AudioEndAction;
  24. /// <summary>
  25. /// 音频播放结束的布尔变量
  26. /// </summary>
  27. private bool m_NeedPlay = false;
  28. private bool m_StreamReadEnd = false;
  29. private const int m_SampleRate = 16000;
  30. //最大支持60s音频
  31. private const int m_BufferSize = m_SampleRate * 60;
  32. //采样容量
  33. private const int m_UpdateSize = m_SampleRate;
  34. //audioclip 设置过的数据个数
  35. private int m_TotalCount = 0;
  36. private int m_DataIndex = 0;
  37. #region Lifecycle function
  38. private void Awake()
  39. {
  40. m_AudioSource = GetComponent<AudioSource>();
  41. m_AzureTTSDataWithSSMLHandler = new AzureTTSDataWithSSMLHandler();
  42. m_AzureTTSDataWithSSMLHandler.SetStartTTSPlayAction(() => { Debug.Log(" Play TTS "); });
  43. m_AzureTTSDataWithSSMLHandler.SetStartTTSStopAction(() => { Debug.Log(" Stop TTS "); AudioPlayEndEvent(); });
  44. m_AudioEndAction = () => { Debug.Log(" End TTS "); };
  45. m_AzureTTSDataWithSSMLHandler.Initialized();
  46. }
  47. // Start is called before the first frame update
  48. void Start()
  49. {
  50. m_AzureTTSDataWithSSMLHandler.Start("今朝有酒,今朝醉,人生几年百花春", OnGetAudioStream);
  51. }
  52. // Update is called once per frame
  53. private void Update()
  54. {
  55. UpdateAudio();
  56. }
  57. #endregion
  58. #region Audio handler
  59. /// <summary>
  60. /// 设置播放TTS的结束的结束事件
  61. /// </summary>
  62. /// <param name="act"></param>
  63. public void SetAudioEndAction(Action act)
  64. {
  65. this.m_AudioEndAction = act;
  66. }
  67. /// <summary>
  68. /// 处理获取到的TTS流式数据
  69. /// </summary>
  70. /// <param name="stream">流数据</param>
  71. public async void OnGetAudioStream(AudioDataStream stream)
  72. {
  73. m_StreamReadEnd = false;
  74. m_NeedPlay = true;
  75. m_AudioDataStream = stream;
  76. Debug.Log("[AzureTTSMono] OnGetAudioStream");
  77. MemoryStream memStream = new MemoryStream();
  78. byte[] buffer = new byte[m_UpdateSize * 2];
  79. uint bytesRead;
  80. m_DataIndex = 0;
  81. m_TotalCount = 0;
  82. m_AudioDataQueue.Clear();
  83. // 回到主线程进行数据处理
  84. Loom.QueueOnMainThread(() =>
  85. {
  86. m_AudioSource.Stop();
  87. m_AudioSource.clip = null;
  88. m_AudioClip = AudioClip.Create("SynthesizedAudio", m_BufferSize, 1, m_SampleRate, false);
  89. m_AudioSource.clip = m_AudioClip;
  90. });
  91. do
  92. {
  93. bytesRead = await System.Threading.Tasks.Task.Run(() => m_AudioDataStream.ReadData(buffer));
  94. if (bytesRead <= 0)
  95. {
  96. break;
  97. }
  98. // 读取写入数据
  99. memStream.Write(buffer, 0, (int)bytesRead);
  100. {
  101. var tempData = memStream.ToArray();
  102. var audioData = new float[memStream.Length / 2];
  103. for (int i = 0; i < audioData.Length; ++i)
  104. {
  105. audioData[i] = (short)(tempData[i * 2 + 1] << 8 | tempData[i * 2]) / 32768.0F;
  106. }
  107. try
  108. {
  109. m_TotalCount += audioData.Length;
  110. // 把数据添加到队列中
  111. m_AudioDataQueue.Enqueue(audioData);
  112. // new 获取新的地址,为后面写入数据
  113. memStream = new MemoryStream();
  114. }
  115. catch (Exception e)
  116. {
  117. Debug.LogError(e.ToString());
  118. }
  119. }
  120. } while (bytesRead > 0);
  121. m_StreamReadEnd = true;
  122. }
  123. /// <summary>
  124. /// Update 播放音频
  125. /// </summary>
  126. private void UpdateAudio() {
  127. if (!m_NeedPlay) return;
  128. //数据操作
  129. if (m_AudioDataQueue.TryDequeue(out float[] audioData))
  130. {
  131. m_AudioClip.SetData(audioData, m_DataIndex);
  132. m_DataIndex = (m_DataIndex + audioData.Length) % m_BufferSize;
  133. }
  134. //检测是否停止
  135. if (m_StreamReadEnd && m_AudioSource.timeSamples >= m_TotalCount)
  136. {
  137. AudioPlayEndEvent();
  138. }
  139. if (!m_NeedPlay) return;
  140. //由于网络,可能额有些数据还没有过来,所以根据需要判断是否暂停播放
  141. if (m_AudioSource.timeSamples >= m_DataIndex && m_AudioSource.isPlaying)
  142. {
  143. m_AudioSource.timeSamples = m_DataIndex;
  144. //暂停
  145. Debug.Log("[AzureTTSMono] Pause");
  146. m_AudioSource.Pause();
  147. }
  148. //由于网络,可能有些数据过来比较晚,所以这里根据需要判断是否继续播放
  149. if (m_AudioSource.timeSamples < m_DataIndex && !m_AudioSource.isPlaying)
  150. {
  151. //播放
  152. Debug.Log("[AzureTTSMono] Play");
  153. m_AudioSource.Play();
  154. }
  155. }
  156. /// <summary>
  157. /// TTS 播放结束的事件
  158. /// </summary>
  159. private void AudioPlayEndEvent()
  160. {
  161. Debug.Log("[AzureTTSMono] End");
  162. m_NeedPlay = false;
  163. m_AudioSource.timeSamples = 0;
  164. m_AudioSource.Stop();
  165. m_AudioEndAction?.Invoke();
  166. }
  167. #endregion
  168. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/1010444
推荐阅读
相关标签
  

闽ICP备14008679号