失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Unity3D HoloLens2 中文文字转语音即语音合成(语音提示)功能

Unity3D HoloLens2 中文文字转语音即语音合成(语音提示)功能

时间:2023-12-01 06:05:16

相关推荐

Unity3D HoloLens2 中文文字转语音即语音合成(语音提示)功能

文字转语音

UNITY_WSA平台处理过程和实现原理中文需要设置声音上代码使用方法一、部署好Unity3d HoloLens项目工程二、新建节点三、添加TTS组件并配置四、调用TTS功能五、测试、打包和运行 参考地址源码地址:

UNITY_WSA平台

HoloLens2 是微软通用平台,使用的类主要是Windows.Media.SpeechSynthesis;

处理过程和实现原理

是把string内容转换成语音文件,并转换为audio clip 通过audio source组件进行播放。具体请看 代码,注释比较详细,请自行研究。

中文需要设置声音

声音如下TextToSpeechVoice枚举:

public enum TextToSpeechVoice{/// <summary>///系统默认声音./// </summary>Default,/// <summary>/// 大卫 移动/// </summary>David,/// <summary>///马克 移动/// </summary>Mark,/// <summary>/// 兹拉 移动/// </summary>Zira,/// <summary>/// 瑶瑶(谐音) 中文/// </summary>Yaoyao,/// <summary>/// 灰灰(谐音) 中文/// </summary>Huihui,/// <summary>/// 康康(谐音) 中文/// </summary>Kangkang,}

中文选用Yaoyao,HuiHui或者KangKang。

上代码

using System;using UnityEngine;#if !UNITY_EDITOR && UNITY_WSAusing Windows.Foundation;using Windows.Media.SpeechSynthesis;using Windows.Storage.Streams;using System.Linq;using System.Threading.Tasks;#endifnamespace HoloToolkit.Unity{/// <summary>/// 知名的可用声音./// </summary>public enum TextToSpeechVoice{/// <summary>///系统默认声音./// </summary>Default,/// <summary>/// 大卫 移动/// </summary>David,/// <summary>///马克 移动/// </summary>Mark,/// <summary>/// 兹拉 移动/// </summary>Zira,/// <summary>/// 瑶瑶(谐音) 中文/// </summary>Yaoyao,/// <summary>/// 灰灰(谐音) 中文/// </summary>Huihui,/// <summary>/// 康康(谐音) 中文/// </summary>Kangkang,}/// <remarks>/// <see cref="SpeechSynthesizer"/>生成语音<see cref="SpeechSynthesisStream"/>. /// 这个类将流转换为UnityAudioClip 并使用你在inspector中提供的AudioSource播放/// 这可以让你在3D空间中定位声音。推荐的方法是将AudioSource放置在空对象上,/// 并将它设为主摄像头的子对象,将其放置在相机上方的大约0.6个单位。/// 这个听起来类似于Cortana在操作系统中的讲话。/// </remarks>[RequireComponent(typeof(AudioSource))]public class TextToSpeech : MonoBehaviour{[Tooltip("播放语音的AudioSource")][SerializeField]private AudioSource audioSource;public static TextToSpeech Instance = null;/// <summary>/// 获取或设置播放语音的AudioSource./// </summary>public AudioSource AudioSource {get {return audioSource; } set {audioSource = value; } }/// <summary>///获取或设置用于生成语音的声音./// </summary>public TextToSpeechVoice Voice {get {return voice; } set {voice = value; } }[Tooltip("生成语音的声音")][SerializeField]private TextToSpeechVoice voice;#if !UNITY_EDITOR && UNITY_WSAprivate SpeechSynthesizer synthesizer;private VoiceInformation voiceInfo;private bool speechTextInQueue = false;#endif/// <summary>/// 转换2个字节为-1至1的浮点数/// </summary>/// <param name="firstByte">第一个字节</param>/// <param name="secondByte">第二个字节</param>/// <returns>转换的值</returns>private static float BytesToFloat(byte firstByte, byte secondByte){// 转换两个字节为short(从小到大)short s = (short)((secondByte << 8) | firstByte);// 转换为 -1 至 (略低于) 1return s / 32768.0F;}/// <summary>/// 转换字节数组为int./// </summary>/// <param name="bytes"> 字节数组</param>/// <param name="offset"> 读取偏移.</param>/// <returns>转换后的int.</returns>private static int BytesToInt(byte[] bytes, int offset = 0){int value = 0;for (int i = 0; i < 4; i++){value |= ((int)bytes[offset + i]) << (i * 8);}return value;}/// <summary>/// 动态创建一个AudioClip音频数据。/// </summary>/// <param name="name"> 动态生成的AudioClip的名称。</param>/// <param name="audioData">音频数据.</param>/// <param name="sampleCount">音频数据中的样本数。</param>/// <param name="frequency">音频数据的频率。</param>/// <returns>AudioClip</returns>private static AudioClip ToClip(string name, float[] audioData, int sampleCount, int frequency){var clip = AudioClip.Create(name, sampleCount, 1, frequency, false);clip.SetData(audioData, 0);return clip;}/// <summary>/// 转换原始WAV数据为统一格式的音频数据。/// </summary>/// <param name="wavAudio">WAV数据.</param>/// <param name="sampleCount">音频数据中的样本数.</param>/// <param name="frequency">音频数据的频率.</param>/// <returns>统一格式的音频数据. </returns>private static float[] ToUnityAudio(byte[] wavAudio, out int sampleCount, out int frequency){// 确定是单声道还是立体声int channelCount = wavAudio[22];// 获取频率frequency = BytesToInt(wavAudio, 24);// 通过所有其他子块,以获得数据子块:int pos = 12; // 第一个子块ID从12到16// 不断迭代,直到找到数据块 (即 64 61 74 61 ...... (即 100 97 116 97 十进制))while (!(wavAudio[pos] == 100 && wavAudio[pos + 1] == 97 && wavAudio[pos + 2] == 116 && wavAudio[pos + 3] == 97)){pos += 4;int chunkSize = wavAudio[pos] + wavAudio[pos + 1] * 256 + wavAudio[pos + 2] * 65536 + wavAudio[pos + 3] * 16777216;pos += 4 + chunkSize;}pos += 8;// Pos现在被定位为开始实际声音数据。sampleCount = (wavAudio.Length - pos) / 2; // 每个样本2字节(16位单声道)if (channelCount == 2) {sampleCount /= 2; } // 每个样本4字节(16位立体声)// 分配内存(仅支持左通道)var unityData = new float[sampleCount];//写入数组:int i = 0;while (pos < wavAudio.Length){unityData[i] = BytesToFloat(wavAudio[pos], wavAudio[pos + 1]);pos += 2;if (channelCount == 2){pos += 2;}i++;}return unityData;}#if !UNITY_EDITOR && UNITY_WSA/// <summary>/// 执行一个生成语音流的函数,然后在Unity中转换并播放它。/// </summary>/// <param name="text">/// 内容./// </param>/// <param name="speakFunc">/// 执行以生成语音的实际函数/// </param>private void PlaySpeech(string text, Func<IAsyncOperation<SpeechSynthesisStream>> speakFunc){//确保有内容if (speakFunc == null) throw new ArgumentNullException(nameof(speakFunc));if (synthesizer != null){try{speechTextInQueue = true;// 需要await,因此大部分将作为一个新任务在自己的线程中运行。// 这是件好事,因为它解放了Unity,让它可以继续运行。Task.Run(async () =>{// 换声?if (voice != TextToSpeechVoice.Default){// 获得名称var voiceName = Enum.GetName(typeof(TextToSpeechVoice), voice);// 查看它是一直没被找到还是有改变if ((voiceInfo == null) || (!voiceInfo.DisplayName.Contains(voiceName))){// 搜索声音信息voiceInfo = SpeechSynthesizer.AllVoices.Where(v => v.DisplayName.Contains(voiceName)).FirstOrDefault();// 如果找到则选中if (voiceInfo != null){synthesizer.Voice = voiceInfo;}else{Debug.LogErrorFormat("TTS 无法找到声音 {0}。", voiceName);}}}// 播放语音并获得流var speechStream = await speakFunc();// 获取原始流的大小var size = speechStream.Size;// 创建 bufferbyte[] buffer = new byte[(int)size];// 获取输入流和原始流的大小using (var inputStream = speechStream.GetInputStreamAt(0)){// 关闭原始的语音流,释放内存speechStream.Dispose();// 从输入流创建一个新的DataReaderusing (var dataReader = new DataReader(inputStream)){//将所有字节加载到readerawait dataReader.LoadAsync((uint)size);// 复制reader到bufferdataReader.ReadBytes(buffer);}}// 转换原始WAV数据为统一格式的音频数据int sampleCount = 0;int frequency = 0;var unityData = ToUnityAudio(buffer, out sampleCount, out frequency);// 剩下的工作须在Unity的主线程中完成UnityEngine.WSA.Application.InvokeOnAppThread(() =>{// 转换为audio clipvar clip = ToClip("Speech", unityData, sampleCount, frequency);// 设置audio clip的语音audioSource.clip = clip;// 播放声音audioSource.Play();speechTextInQueue = false;}, false);});}catch (Exception ex){speechTextInQueue = false;Debug.LogErrorFormat("语音生成错误: \"{0}\"", ex.Message);}}else{Debug.LogErrorFormat("语音合成器未初始化. \"{0}\"", text);}}#endifprivate void Awake(){try{if (audioSource == null){audioSource = GetComponent<AudioSource>();}#if !UNITY_EDITOR && UNITY_WSAsynthesizer = new SpeechSynthesizer();#endifInstance = this;}catch (Exception ex){Debug.LogError("不能开始语音合成: " + ex.Message);}}// 公共方法/// <summary>/// 播放指定SSML标记语音./// </summary>/// <param name="ssml">SSML标记</param>public void SpeakSsml(string ssml){// 确保内容不为空if (string.IsNullOrEmpty(ssml)) {return; }// 传递给辅助方法#if !UNITY_EDITOR && UNITY_WSAPlaySpeech(ssml, () => synthesizer.SynthesizeSsmlToStreamAsync(ssml));#elseDebug.LogWarningFormat("文字转语音在编辑器下不支持.\n\"{0}\"", ssml);#endif}/// <summary>/// 播放指定文本语音./// </summary>/// <param name="text">文本内容</param>public void StartSpeaking(string text){// 确保内容不为空if (string.IsNullOrEmpty(text)) {return; }// 传递给辅助方法#if !UNITY_EDITOR && UNITY_WSAPlaySpeech(text, ()=> synthesizer.SynthesizeTextToStreamAsync(text));#elseDebug.LogWarningFormat("文字转语音在编辑器下不支持.\n\"{0}\"", text);#endif}/// <summary>/// 返回一个文本是否被提交并被PlaySpeech方法处理/// 方便避免当文本提交,但音频剪辑还没有准备好,因为音频源还没有播放的情况。/// </summary>/// <returns></returns>public bool SpeechTextInQueue(){#if !UNITY_EDITOR && UNITY_WSAreturn speechTextInQueue;#elsereturn false;#endif}/// <summary>/// 是否在播放语音./// </summary>/// <returns>/// True, 在播放. False,未播放./// </returns>public bool IsSpeaking(){if (audioSource != null){return audioSource.isPlaying;}return false;}/// <summary>/// 停止播放语音./// </summary>public void StopSpeaking(){if (IsSpeaking()){audioSource.Stop();}}}}

使用方法

一、部署好Unity3d HoloLens项目工程

这一步请自行完成

二、新建节点

如上图 在camera节点下新建节点,并添加audio source组件,将position.y设置为 0.6,这样听起来类似于Cortana在操作系统中的讲话。

三、添加TTS组件并配置

选择voice 为YaoYao

四、调用TTS功能

if (TextToSpeech.Instance)TextToSpeech.Instance.StartSpeaking("这是一条测试语音");

五、测试、打包和运行

上图是编辑器下运行的效果图

参考地址

//05/04/1601/text-to-speech-for-hololens/

源码地址:

/microsoft/MixedRealityToolkit-Unity/blob/htk_release/Assets/HoloToolkit/Utilities/Scripts/TextToSpeech.cs

如果觉得《Unity3D HoloLens2 中文文字转语音即语音合成(语音提示)功能》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。