Unity 百度SDK 之 在线语音合成 TTS WebAPI 功能的实现
Unity 百度SDK 之 在线语音合成 TTS WebAPI 功能的实现
目录
一、简单介绍
Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。
本节使用Baidu API 进行语音合成功能的简单的实现。
二、百度官网关于在线语音合成的介绍
网址:
百度语音合成服务,基于HTTP请求的REST API接口,将文本转换为可以播放的音频文件。
合成的文件格式为 mp3,pcm(8k及16k),wav(16k),具体见aue参数。 若您需要其它格式,音频文件的转换方法请参考一节
本文档描述了使用语音合成服务REST API的方法。
多音字可以通过标注自行定义发音。格式如:重(chong2)报集团。
目前只有中英文混合这一种语言,优先中文发音。示例: " I bought 3 books” 发音 “three”; “ 3 books are bought” 发音 “three”; “我们买了 3 books” 发音“三”
语音合成示例代码:
三、 在线识别 Access Token 的获取
webAPI 获取的方式
四、注意事项
1、如果你的是安卓Android 系统版本过高的话,可能会报错,而不能实现语音合成功能
java.io.IOException: Cleartext HTTP traffic to tsn.baidu.com not permitted
2、正对上面过高版本Android的报错,只要在 AndroidMainfest.xml ,添加 android:usesCleartextTraffic="true"即可
(Unity AndroidManifest.xml路径:Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Apk 路径下 )
2、注意添加 litjson 和 NAudio 插件
五、效果预览
六、实现步骤
1、打开Unity,新建一个工程
2、在工程中添加一个脚本
3、编写脚本,获取输入文本,传给 TTS 语音合成,在把合成的转为Unity能播放的格式,然后播放出来,记得合成之前,获取Access Token
4、在场景中,添加一个按钮和输入框
5、把脚本,添加到场景中,并且把按钮添加监听事件
6、运行场景,即可测试效果
七、关键代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LitJson;
using System.Text;
using System;
using UnityEngine.UI;
using System.IO;
using NAudio;
using NAudio.Wave;
/// <summary>
/// 用来转换语音,将文字转成语音。
/// </summary>
public class BaiduTTS : MonoBehaviour
{
#region 字段、属性
private string tex;
private string lan = "zh";
private string tok;
private string ctp = "1";
//用户唯一标识,这里建议使用机器 MAC 地址或 IMEI 码
private string cuid = "00-12-7B-16-74-8D"; //待加入的功能
//语速 0-9 5为中语速
public string spd = "5";
//音调 0-9 5为中语调
public string pit = "5";
//音量 0-9 5为中音量
public string vol = "5";
//发音 0-4 发音人选择, 0为女声,1为男声,3为情感合成-度逍遥,4为情感合成-度丫丫,默认为普通女声
public string per = "103";
//上传数据的url,
private string url;
//所需要转成语音的信息文本
private string Speak = "欢迎来到百度语音合成";
private string grant_Type = "client_credentials";
//百度appkey
[Header("Please input baidu API Key")]
public string client_ID = "你的 API Key";
//百度Secret Key
[Header("Please input baidu Secret Key")]
public string client_Secret = "你的 Secret Key";
//获取百度令牌的url
private string getTokenAPIPath = "https://aip.baidubce.com/oauth/2.0/token?";
#endregion
//测试
public InputField inputField;
AudioSource aud;
/// <summary>
/// 将所需要说的话进行编码
/// </summary>
/// <returns>The to encoding UT f8.</returns>
/// <param name="str">String.输入您想说的话</param>
private void StringToEncodingUTF8(string str)
{
byte[] tempByte = Encoding.UTF8.GetBytes(str);
for (int i = 0; i < tempByte.Length; i++)
{
//UrlEncode编码规则
tex += (@"%" + Convert.ToString(tempByte[i], 16));
}
//拼接上传的url
url = "http://tsn.baidu.com/text2audio?tex=" + tex + "&lan=zh&cuid=" + cuid + "&ctp=1&tok=" + tok + "&per=" + per + "&spd=" + spd + "&pit=" + pit + "&vol=" + vol + "";
Debug.Log("Token:" + tok);
}
/// <summary>
/// 获取百度用户令牌,否则无法使用API
/// </summary>
/// <param name="url">获取的url</param>
/// <returns></returns>
private IEnumerator GetToken(string url)
{
WWWForm TokenForm = new WWWForm();
TokenForm.AddField("grant_type", grant_Type);
TokenForm.AddField("client_id", client_ID);
TokenForm.AddField("client_secret", client_Secret);
WWW getTW = new WWW(url, TokenForm);
yield return getTW;
if (getTW.isDone)
{
//Debug.Log (getTW.text);
if (getTW.error == null)
{
tok = JsonMapper.ToObject(getTW.text)["access_token"].ToString();
}
else
{
Debug.LogError(getTW.error);
}
}
}
/// <summary>
/// 上传和下载
/// </summary>
/// <param name="url">URL.</param>
private IEnumerator Loading(string url)
{
WWW loadingAudio = new WWW(url);
yield return loadingAudio;
if (loadingAudio.error == null)
{
if (loadingAudio.isDone)
{
//下载该音频 /* PC下需要对MP3格式转码,手机端则使用MP3*/
#if UNITY_EDITOR_WIN
aud.clip = FromMp3Data(loadingAudio.bytes);
aud.Play();
#elif UNITY_STANDALONE_WIN
aud.clip = FromMp3Data(loadingAudio.bytes);
aud.Play ();
#elif UNITY_ANDROID
aud.clip = loadingAudio.GetAudioClip (false,true,AudioType.MPEG);
aud.Play ();
#endif
}
else
{
Debug.LogError(loadingAudio.error);
}
}
}
void Awake()
{
if (GetComponent<AudioSource>() == null)
{
aud = gameObject.AddComponent<AudioSource>();
}
else
{
aud = gameObject.GetComponent<AudioSource>();
}
aud.playOnAwake = false;
StartCoroutine(GetToken(getTokenAPIPath));
}
//Button响应事件
public void StartStringToAudio()
{
tex = "";
Speak = inputField.text;
Debug.Log(Speak);
//文本编码
StringToEncodingUTF8(Speak);
//Debug.Log ("编码后得到的信息:"+tex);
StartCoroutine(Loading(url));
} //MP3 --- wav
public static AudioClip FromMp3Data(byte[] data)
{
//加载数据进入流
MemoryStream mp3stream = new MemoryStream(data);
//流中的数据转换为WAV格式
Mp3FileReader mp3audio = new Mp3FileReader(mp3stream);
WaveStream waveStream = WaveFormatConversionStream.CreatePcmStream(mp3audio);
//转换WAV数据
WAV wav = new WAV(AudioMemStream(waveStream).ToArray());
AudioClip audioClip = AudioClip.Create("testSound", wav.SampleCount, 1, wav.Frequency, false);
audioClip.SetData(wav.LeftChannel, 0);
return audioClip;
}
private static MemoryStream AudioMemStream(WaveStream waveStream)
{
MemoryStream outputStream = new MemoryStream();
using (WaveFileWriter waveFileWriter = new WaveFileWriter(outputStream, waveStream.WaveFormat))
{
byte[] bytes = new byte[waveStream.Length];
waveStream.Position = 0;
waveStream.Read(bytes, 0, Convert.ToInt32(waveStream.Length));
waveFileWriter.Write(bytes, 0, bytes.Length);
waveFileWriter.Flush();
}
return outputStream;
}
}
public class WAV
{
// 两个字节转换为一个浮动范围在-1到1
static float bytesToFloat(byte firstByte, byte secondByte)
{
//两个字节转换为一个短(小端字节序)
short s = (short)((secondByte << 8) | firstByte);
//将范围从-1到1
return s / 32768.0F;
}
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;
}
// 属性
public float[] LeftChannel { get; internal set; }
public float[] RightChannel { get; internal set; }
public int ChannelCount { get; internal set; }
public int SampleCount { get; internal set; }
public int Frequency { get; internal set; } /// <summary>
/// 自定义Wav格式
/// </summary>
/// <param name="wav">Wav.</param>
public WAV(byte[] wav)
{
//确定单声道或立体声
ChannelCount = wav[22]; // 23(99.999%)往后丢弃
//得到的频率
Frequency = bytesToInt(wav, 24);
int pos = 12; //第一个子块ID从12-16
// 继续迭代,直到找到数据块 (i.e. 64 61 74 61 ...... (i.e. 100 97 116 97 in decimal))
while (!(wav[pos] == 100 && wav[pos + 1] == 97 && wav[pos + 2] == 116 && wav[pos + 3] == 97))
{
pos += 4;
int chunkSize = wav[pos] + wav[pos + 1] * 256 + wav[pos + 2] * 65536 + wav[pos + 3] * 16777216;
pos += 4 + chunkSize;
}
pos += 8;
//定位实际声音开始的数据.
SampleCount = (wav.Length - pos) / 2; // 2字节/采样 (16 bit 单声道)
if (ChannelCount == 2) SampleCount /= 2; // 4字节/采样 (16 bit 音响)
//分配内存(右将null如果只有单声道声音)
LeftChannel = new float[SampleCount];
if (ChannelCount == 2) RightChannel = new float[SampleCount];
else RightChannel = null;
//写入双数组
int i = 0;
while (pos < wav.Length)
{
LeftChannel[i] = bytesToFloat(wav[pos], wav[pos + 1]);
pos += 2;
if (ChannelCount == 2)
{
RightChannel[i] = bytesToFloat(wav[pos], wav[pos + 1]);
pos += 2;
}
i++;
}
}
}