Unity 百度SDK 之 在线语音合成 TTS WebAPI 功能的实现

47 篇文章 17 订阅

Unity 百度SDK 之 在线语音合成 TTS  WebAPI 功能的实现

 

目录

Unity 百度SDK 之 在线语音合成 TTS  WebAPI 功能的实现

一、简单介绍

二、百度官网关于在线语音合成的介绍

三、 在线识别 Access Token 的获取

四、注意事项

五、效果预览

六、实现步骤

七、关键代码


 

一、简单介绍

Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。

本节使用Baidu API 进行语音合成功能的简单的实现。

 

二、百度官网关于在线语音合成的介绍

网址:https://ai.baidu.com/ai-doc/SPEECH/Gk38y8lzk

百度语音合成服务,基于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” 发音“三”

语音合成示例代码: https://github.com/Baidu-AIP/speech-demo/tree/master/rest-api-tts

 

三、 在线识别 Access Token 的获取

网址介绍: https://ai.baidu.com/ai-doc/REFERENCE/Ck3dwjhhu

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++;
        }
    }

}

 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仙魁XAN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值