简要说明
利用在线的方式,实现在程序中将文本转换为语音貌似是非常简单的一个问题。可以参看这篇文章
但要注意的是,Unity中并不支持将mp3通过WWW加载并转换为AuidoClip,所以要实现转换效果还必须要转换格式为Wav或者是ogg,还真有人这么做了,看NAudio是怎么实现的。
我也试过这种方式,发现打包时会报没有system.window.form的问题。而且显然这种方式并不能用在其他客户端。(web版直接可以用js接口,这里主要针对客户端,什么Andriod和ios)。
然后,发现了一个非常用意义的网站,列出了现在12个免费的在线文字转语音的网站。毫无疑问,直接选择了第一个,但进去看了看,并没有提供Api的链接。后来才发现直接搜索Text2Speech API,就可以找到其API官方界面。(点这里快速进入在线API界面)进去后可以看到可以直接用get的方式,将相关参数和文本上传,就可以得到指定类型的音频文件。这可大大省去了下载SDK的便利(说明:也许SDK中自动集成了相关保存文件的功能,值得研究,但由于.net 的SDK,导入Unity中无法识别,应该是.net版本太高的原因。对于功能需求不太强的还是直接用unity程序内部使用在线文字转语音吧)。
一、获取API Key
现在一般的网站接入时都需要用户注册,才给APIkey ,应该是收集用户信息和法律问题追究的吧。进入API界面后点击Get API key
进入之后会经过一系列的注册和填写信息,就会得到一个可以在get请求中直接使用Api key,最好是自动申请在自己程序中使用用,当然用其他人的这里竟然也可以。因为不需要你上传自己的应用程序就可以给你一个APIKey.
二、在浏览器中测试
在浏览器中输入如官方文档指出的链接方式:(将获取的APIKey将其替换掉)
如果测试成功就可以听到一个女声对你说Hellow,World了
三、在Unity3D中下载并转换为AudioClip
using UnityEngine;
using System.Collections;
public class SayHellow : MonoBehaviour {
string word = "Hellow";
AudioSource audioSource;
string url;
void Start()
{
url = "http://api.voicerss.org/?key=ceb04420d03d434a91e81608de6d1576&c=OGG&hl=en-us&src=" + word;
audioSource = GetComponent<AudioSource>();
}
void OnGUI()
{
if (GUILayout.Button("优良效果"))
{
StartCoroutine(DownLandAudio());
}
}
IEnumerator DownLandAudio()
{
WWW www = new WWW(url);
yield return www;
if (www.error != null)
{
Debug.LogError(www.error);
}
AudioClip clip = www.GetAudioClip(false,true,AudioType.OGGVORBIS);
audioSource.PlayOneShot(clip);
}
}
利用以上代码,就可以实现在Unity中调用在线的API并播放出声音了。
四、将下载的声音进行缓存
细心就可以发现,第运行一次就需要下载一次,这个过程比较慢,特别是如果网速不好的话就呵呵了。
本地数据的保存可以将下载使用过的文字对应的md5当作键,对应下载后的路径记录为值,每次调用程序中需要转换为声音的文字就可以不用重新下载而是直接从本地来加载了。具体来说,要保存生成的键值对,可以用到yaml,xml,json,数据库等本例中要介绍一个比较高端大气的序列化方式,完全不使用第三方的库来序列化(虽然是多此一举,告诉你个秘密unity5.3.1以后都可以直接调用unityEngine.JsonUtility类来快速保存简单的对象到本地了)。这个方法就是csv文件,其实是表类型的文件,和sqite有类似,不过一个csv文件保存一个表可以用到的序列化数据,这样程序的可扩散性和稳定性大大提升,相对于json和xml,其可读性也非常的好,直接用excel进行编辑(注意的一个坑是要用notepad++或notepad将格式转换为utf8不然中文会乱码)。
假定你已经会用一个方式来保存键值数据了,保存本地并加载实现如下:
using System;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;
public class Text2AudioCtrl : ITextToAudio
{
//本地文件路径
public string wavFilePath;
public string md5FileName = "AudioMD5.csv";
FileMD5Table table;
public Dictionary<string, string> SavedWav = new Dictionary<string, string>();
//http://www.voicerss.org/api/documentation.aspx
//测试通过http://api.voicerss.org/?key=ceb04420d03d434a91e81608de6d1576&c=OGG&hl=en-us&src=你好Hello,%20world!123"
private static string tempAudio = "http://api.voicerss.org/?key=ceb04420d03d434a91e81608de6d1576&c=OGG&hl={0}&f={1}&src=";
public static class Languages
{
public static string china = "zh-cn";
public static string english = "en-us";
}
public static class Formats
{
public static string verylow = "11khz_8bit_mono";
public static string normal = "alaw_22khz_stereo";
public static string veryGood = "ulaw_44khz_stereo";
}
public static string GetConnectionString(string langluage, string formats)
{
return string.Format(tempAudio, langluage, formats);
}
public Text2AudioCtrl()
{
wavFilePath = Application.streamingAssetsPath + "/Audio";
CsvConfigManager.Instance.LoadConfigAsync<FileMD5Table>(md5FileName, (x) =>
{
table = x;
foreach (var item in x.GetRowList())
{
if (item.md5 == null)
{
continue;
}
SavedWav.Add(item.md5, item.fileName);
}
});
}
public IEnumerator GetAudioClip(string text, string format, UnityAction<AudioClip> OnGet)
{
WWW www;
string key = FileUtility.md5(text) + format;//!!!!!!!!!!!!!!!!!!!!!!增加权
string filePath = Path.Combine(wavFilePath, key + ".ogg");
if (!SavedWav.ContainsKey(key))
{
Debug.Log("没有文件");
string url = GetConnectionString(Languages.china, format) + text;
www = new WWW(url);
yield return www;
if (www.error != null)
{
Debug.LogError(www.error);
}
else
{
File.WriteAllBytes(filePath, www.bytes);
//更新csv文件
if (table != null)
{
FileMD5Table.Row rowx = new global::FileMD5Table.Row();
rowx.fileName = filePath;
rowx.md5 = key;
table.GetRowList().Add(rowx);
SavedWav.Add(rowx.md5, rowx.fileName);
CsvConfigManager.SaveConfig(md5FileName, table);
}
}
}
www = new WWW("file:///" + filePath);
yield return www;
if (www.error != null)
{
Debug.LogError(www.error);
}
if (www.audioClip != null && OnGet != null)
{
OnGet(www.audioClip);
}
else
{
Debug.LogError("转换失败");
}
}
}
外部调用这样一个携程,就可以实现AuidoClip的读取了。
五、Demo使用
用测试来说话,在如下窗口中输入任何不带空格的一段话(url不能有空格(!)),用如下脚本来测试
using UnityEngine;
using UnityEngine.UI;
public class SayHellowPlus : MonoBehaviour {
public InputField inputFiled;
public Button button;
ITextToAudio ctrl;
AudioSource audioSource;
void Start()
{
ctrl =new Text2AudioCtrl();
audioSource = GetComponent<AudioSource>();
button.onClick.AddListener(()=> {
if (!string.IsNullOrEmpty(inputFiled.text))
{
StartCoroutine(ctrl.GetAudioClip(inputFiled.text, Text2AudioCtrl.Formats.veryGood, (x) => {
audioSource.PlayOneShot(x, 1);
}));
}
});
}
}
由于声音实现没有办法展示了,具体工程见github