本文节选自洪流学堂公众号技术专栏《大话Unity2019》,未经允许不可转载。

洪流学堂公众号回复语音识别获取源码工程。


洪流学堂,让你快人几步。你好,我是郑洪智。

大智:“昨天我们实战了语音识别,在人工智能的语音领域,还有很大一块是语音合成,也就是Text to Speech,文字转语音。”
小新:“是不是就是我们经常听到的siri或者智能音箱那种声音?”
大智:“没错,那些声音都是用语音合成的技术合成音频文件,然后播放出来的。”
小新:“我们今天就来搞这个?”
大智:“对,这就开始”

HTTP实战:百度语音合成

首先做些准备工作,和昨天的语音识别的流程很像,大致如下:

  1. http://ai.baidu.com/,首先你需要一个百度账号
  2. 登陆进去以后,在https://console.bce.baidu.com/页面左侧点击百度语音。

  1. 创建一个新应用,不需要设置语音包名,因为我们要使用HTTP接口,而不是用SDK。

  1. 在应用详情中可以看到API Key和Secret Key,一会需要用到。

  1. 阅读文档!阅读文档!阅读文档!
    语音合成:https://ai.baidu.com/docs#/TTS-API/top

语音识别

大智:“看完文档了没?”
小新:“看完了”
大智:“那我们就开始了。”

语音合成主要有两个过程:

  1. 鉴权认证:从百度获取一个令牌(token),请求的时候需要携带这个令牌,否则视为非法请求
  2. 在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;
    }

    /// <summary>
    ///     语音合成结果
    /// </summary>
    [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<TokenResponse>(www.downloadHandler.text);
            Token = result.access_token;
            Debug.Log("[WitBaiduAip]Token has been fetched successfully");
        }


        AudioSource = gameObject.AddComponent<AudioSource>();
    }

    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<TtsResponse> callback)
    {
        var url = "http://tsn.baidu.com/text2audio";

        var param = new Dictionary<string, string>();
        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<TtsResponse>(errorText));
            }
        }
    }
}

上面的代码写好以后,设置好APIKey和SecretKey就可以合成语音出来了。

总结

大智:“我们这两天通过实战学习了UnityWebRequest的具体用法,在请求Http时,结合接口说明,一般实现起来还是很容易的。

思考题

大智:“上面的语音合成中很有多参数可以设置,试试不同的参数看看有什么效果吧!”
小新:“好嘞!”
大智:“收获别忘了分享出来!也别忘了分享给你学Unity的朋友,也许能够帮到他。”

推荐阅读


洪流学堂公众号回复语音识别获取源码工程。

《大话Unity2019》,大智带小新学Unity2019的有趣经历,让你学Unity更简单。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐