本文节选自洪流学堂公众号技术专栏《大话Unity2019》,未经允许不可转载。
洪流学堂公众号回复语音识别
获取源码工程。
洪流学堂,让你快人几步。你好,我是郑洪智。
大智:“昨天我们实战了语音识别,在人工智能的语音领域,还有很大一块是语音合成,也就是Text to Speech,文字转语音。”
小新:“是不是就是我们经常听到的siri或者智能音箱那种声音?”
大智:“没错,那些声音都是用语音合成的技术合成音频文件,然后播放出来的。”
小新:“我们今天就来搞这个?”
大智:“对,这就开始”
HTTP实战:百度语音合成
首先做些准备工作,和昨天的语音识别的流程很像,大致如下:
- http://ai.baidu.com/,首先你需要一个百度账号
- 登陆进去以后,在https://console.bce.baidu.com/页面左侧点击百度语音。
- 创建一个新应用,不需要设置语音包名,因为我们要使用HTTP接口,而不是用SDK。
- 在应用详情中可以看到API Key和Secret Key,一会需要用到。
- 阅读文档!阅读文档!阅读文档!
语音合成:https://ai.baidu.com/docs#/TTS-API/top
语音识别
大智:“看完文档了没?”
小新:“看完了”
大智:“那我们就开始了。”
语音合成主要有两个过程:
- 鉴权认证:从百度获取一个令牌(token),请求的时候需要携带这个令牌,否则视为非法请求
- 在Unity中请求语音合成接口
第一步鉴权认证我们昨天已经实现了,可以拿来直接用。我们直接进入第二步,在Unity中请求语音合成接口。
REST API
小新:“我在文档中看到了这个词REST API,API我懂,就是应用程序接口嘛,这个REST是什么?休息接口么?”
大智:“哎嘿,什么休息接口!这个是Web开发中的一个技术,你不懂正常,我来简单解释一下。”
REST ( REpresentational State Transfer ),State Transfer 为 "状态传输" 或 "状态转移 ",Representational 中文有人翻译为"表征"、"具象",合起来就是 "表征状态传输" 或 "具象状态传输" 或 "表述性状态转移",不过,一般文章或技术文件都比较不会使用翻译后的中文来撰写,而是直接引用 REST 或 RESTful 来代表,因为 REST 一整个观念,想要只用六个中文字来完整表达真有难度。
REST 本身是设计风格而不是标准。REST 谈论一件非常重要的事,如何正确地使用 Web****标准,例如,HTTP 和 URI。想要了解 REST 最好的方式就是思索与了解****Web****及其工作方式。如果你设计的应用程序能符合 REST 原则 (REST principles),这些符合 REST 原则的 REST 服务可称为 "RESTful web service" 也称 "RESTful Web API"。"-ful" 字尾强调它们的设计完全符合 REST 论文里的建议内容。
如果你不需要做Web开发,了解到这就够了,否则建议你了解下REST的具体原则,RESTful的Web接口目前非常流程。
请求语音合成
百度语音合成支持两种方式请求:
- POST方式
- GET方式
百度文档中推荐使用POST方式,但是由于Unity的WebRequest类中,获取音频的现成接口是使用Get方法,所以我们下面的代码还是使用Get方法去获取。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
public class BaiduTts : MonoBehaviour
{
public string APIKey;
public string SecretKey;
public string Text = "洪流学堂,让你快人几步";
private string Token;
public AudioSource AudioSource { get; private set; }
// 用于解析返回的json
[Serializable]
class TokenResponse
{
public string access_token = null;
}
///
/// 语音合成结果
///
[Serializable]
public class TtsResponse
{
public int err_no;
public string err_msg;
public string sn;
public int idx;
public bool Success
{
get { return err_no == 0; }
}
public AudioClip clip;
}
IEnumerator Start()
{
// 拼接请求的URL
var uri = $"https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id={APIKey}&client_secret={SecretKey}";
var www = UnityWebRequest.Get(uri);
yield return www.SendWebRequest();
if (www.isHttpError || www.isNetworkError)
{
Debug.LogError("[BaiduAip]" + www.error);
Debug.LogError("[BaiduAip]Token was fetched failed. Please check your APIKey and SecretKey");
}
else
{
Debug.Log("[BaiduAip]" + www.downloadHandler.text);
var result = JsonUtility.FromJson(www.downloadHandler.text);
Token = result.access_token;
Debug.Log("[WitBaiduAip]Token has been fetched successfully");
}
AudioSource = gameObject.AddComponent();
}
void Update()
{
if (Input.GetKeyUp(KeyCode.A))
{
Debug.Log("[WitBaiduAip demo]开始合成");
StartCoroutine(Tts(Text, s =>
{
AudioSource.clip = s.clip;
AudioSource.Play();
}));
}
}
public IEnumerator Tts(string text, Action callback)
{
var url = "http://tsn.baidu.com/text2audio";
var param = new Dictionary();
param.Add("tex", text);
param.Add("tok", Token);
param.Add("cuid", SystemInfo.deviceUniqueIdentifier);
param.Add("ctp", "1");
param.Add("lan", "zh");
param.Add("spd", "5");
param.Add("pit", "5");
param.Add("vol", "10");
param.Add("per", "1");
#if UNITY_STANDALONE || UNITY_EDITOR || UNITY_UWP
param.Add("aue", "6"); //设置为wav格式,移动端需要mp3格式
#endif
int i = 0;
foreach (var p in param)
{
url += i != 0 ? "&" : "?";
url += p.Key + "=" + p.Value;
i++;
}
// 根据不同平台,获取不同类型的音频格式
#if UNITY_STANDALONE || UNITY_EDITOR || UNITY_UWP
var www = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.WAV);
#else
var www = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.MPEG);
#endif
Debug.Log("[WitBaiduAip]" + www.url);
yield return www.SendWebRequest();
if (www.isHttpError || www.isNetworkError)
Debug.LogError(www.error);
else
{
var type = www.GetResponseHeader("Content-Type");
Debug.Log("[WitBaiduAip]response type: " + type);
if (type.Contains("audio"))
{
var response = new TtsResponse { clip = DownloadHandlerAudioClip.GetContent(www) };
callback(response);
}
else
{
var textBytes = www.downloadHandler.data;
var errorText = Encoding.UTF8.GetString(textBytes);
Debug.LogError("[WitBaiduAip]" + errorText);
callback(JsonUtility.FromJson(errorText));
}
}
}
}
上面的代码写好以后,设置好APIKey和SecretKey就可以合成语音出来了。
总结
大智:“我们这两天通过实战学习了UnityWebRequest的具体用法,在请求Http时,结合接口说明,一般实现起来还是很容易的。
思考题
大智:“上面的语音合成中很有多参数可以设置,试试不同的参数看看有什么效果吧!”
小新:“好嘞!”
大智:“收获别忘了分享出来!也别忘了分享给你学Unity的朋友,也许能够帮到他。”
推荐阅读
- Unity开发《一起来捉妖》教程 | 2.用摄像头图像做背景
- Unity开发《一起来捉妖》教程 | 1.陀螺仪控制相机
- Unity 2019.1 中文更新日志速览版
- Unity中的HTTP通信
- UnityWebRequest详解
- Unity中编码Encoding脱坑指南
- Unity中的Git最佳实践
- Unity2019更新规划速览,将有官方的可视化编程!
- Unity运行时更新带来了什么?
- Unity2018.3新功能 | Prefab嵌套和变体
洪流学堂公众号回复语音识别
获取源码工程。
《大话Unity2019》,大智带小新学Unity2019的有趣经历,让你学Unity更简单。