欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity接入百度语音合成安卓(android)SDK

程序员文章站 2022-05-14 13:05:36
...

一、注册百度云账号

选择语音技术,并且创建一个安卓应用,选择语音合成,领取免费配额

Unity接入百度语音合成安卓(android)SDK

 

创建应用的时候,包名要跟Unity发布安卓时的包名保持一致,如果不一致,则会找不到我们写的脚本

Unity接入百度语音合成安卓(android)SDK

Unity接入百度语音合成安卓(android)SDK

二、下载百度语音合成Demo

我们所需的安卓库文件与jar包都在demo中

Unity接入百度语音合成安卓(android)SDK

下载地址:https://console.bce.baidu.com/ai/?_=1599524377675&fromai=1#/ai/speech/app/detail~appId=1886795

三、创建一个新的安卓项目

我们使用AndroidStudio最新版,目前是3.6.3

Unity接入百度语音合成安卓(android)SDK

如何创建自己的AndroidStudio项目,并且简单的与Unity交互,请看我的另一篇博文

地址:https://blog.csdn.net/weixin_43271060/article/details/90318254

四、导入库文件和jar包

我们已经创建好了自己项目,我们目前的情况是项目中已经存在了其他程序员编写的sdk,为了避免去别人的冲突,我们选择不继承成自UnityPlayerActivity,如何创建不继承UnityPlayerActivity的类?

请参考我的另一篇博文

https://blog.csdn.net/weixin_43271060/article/details/90370009

好了,创建完了我们的项目,开始导入安卓项目需要的库文件与jar包

jar目录:Demo目录/app/libs文件夹下的jar包

如何导入jar包已经在简单与安卓交互这篇博文中写了,所以不再赘述,

库文件目录:

Demo目录/app/src/main/下的jniLibs文件夹,我们将此文件夹导入我们项目中的src/main/目录,直接复制粘贴就好,

Unity接入百度语音合成安卓(android)SDK

五、编写代码

编写百度语音技术主类

package com.xxx.xxx;//自己的包名

import android.content.Context;
import com.cientx.tianguo.Recogn.RecognHandler;
import com.cientx.tianguo.Recogn.RecognListener;
import com.cientx.tianguo.Synthesizer.SpeechSynthesizerHandler;
import com.cientx.tianguo.Synthesizer.SynthesizerListener;
import com.cientx.tianguo.Synthesizer.FileSaveListener;
import com.cientx.tianguo.Wakeup.WakeupHandler;
import com.cientx.tianguo.Wakeup.WakeupListener;
//百度语音主类
public class CientBaiDuVoiceMainActivity {


    public static CientBaiDuVoiceMainActivity _instance;


    public static CientBaiDuVoiceMainActivity getInstance() {
        if (_instance == null) {
            _instance = new CientBaiDuVoiceMainActivity();
        }
        return _instance;
    }

  }

新建一个Package,名字叫Sythesizer,看下图我们已经创建好了

Unity接入百度语音合成安卓(android)SDK

编写一个语音识别监听类SynthesizerListener

package com.xxx.xxx.Synthesizer;//包名
import com.baidu.tts.client.SpeechError;

public class SynthesizerListener implements com.baidu.tts.client.SpeechSynthesizerListener {
    /**
     * 保存文件的目录
     */
    protected String mDestDir;
    protected String mTagDialog = "";
    /**
     * @param savePath 要保存的目录
     * @param tag      当前对话的标识,因为需要重复播放,所以需要标识每段对话
     */
    public void SetSynthesizePath(String savePath, String tag){
        mDestDir = savePath;
        mTagDialog = tag;
    }



    //合成开始
    @Override
    public void onSynthesizeStart(String utteranceId) {
    }
    //合成过程中的数据回调接口
    @Override
    public void onSynthesizeDataArrived(String utteranceId, byte[] bytes, int progress, int engineType) {
    }
    /**
     * 合成正常结束,每句合成正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口
     *
     * @param utteranceId
     */
    @Override
    public void onSynthesizeFinish(String utteranceId) {
    }
    //播放开始
    @Override
    public void onSpeechStart(String utteranceId) {
    }
    /**
     * 播放进度回调接口,分多次回调
     *
     * @param utteranceId
     * @param progress    如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法保证和合成到第几个字对应。
     */
    @Override
    public void onSpeechProgressChanged(String utteranceId, int progress) {
    }
    /**
     * 播放正常结束,每句播放正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口
     *
     * @param utteranceId
     */
    @Override
    public void onSpeechFinish(String utteranceId) {

    }
    /**
     * 当合成或者播放过程中出错时回调此接口
     *
     * @param utteranceId
     * @param speechError 包含错误码和错误信息
     */
    @Override
    public void onError(String utteranceId, SpeechError speechError) {
    }
}

编写一个合成完后将语音文件保存到手机本地的类

FileSaveListener
package com.xxx.xxx.Synthesizer;//包名


import com.baidu.tts.client.SpeechError;
import com.cientx.tianguo.Util.LogPrint;
import com.cientx.tianguo.Util.SendToUnity;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileSaveListener extends SynthesizerListener {
    /**
     * 保存的文件名 mTaseName +tag+"-"+ utteranceId, 通常是 output-10001-0.pcm
     */
    private String mTaseName = "output-";



    /**
     * 文件
     */
    private File ttsFile;


    /**
     * ttsFile 文件流
     */
    private FileOutputStream ttsFileOutputStream;

    /**
     * ttsFile 文件buffer流
     */
    private BufferedOutputStream ttsFileBufferedOutputStream;


    public FileSaveListener() {

    }

    @Override
    public void onSynthesizeStart(String utteranceId) {
        LogPrint.Log("准备开始合成,***:" + utteranceId);
        String filename = mTaseName + mTagDialog + "-" + utteranceId + ".pcm";
        // 保存的语音文件是 16K采样率 16bits编码 单声道 pcm文件。

        ttsFile = new File(mDestDir, filename);

        LogPrint.Log("合成开始,创建文件: " + ttsFile.getAbsolutePath());//这是日志类,自己创建

        try {


            if (ttsFile.exists()) {//如果文件存在,则删除
                ttsFile.delete();
            }
            ttsFile.createNewFile();
            // 创建FileOutputStream对象
            FileOutputStream ttsFileOutputStream = new FileOutputStream(ttsFile);
            // 创建BufferedOutputStream对象
            ttsFileBufferedOutputStream = new BufferedOutputStream(ttsFileOutputStream);
        } catch (IOException e) {
            // 请自行做错误处理
            e.printStackTrace();
            LogPrint.Log("创建文件失败:" + mDestDir + "/" + filename);
            throw new RuntimeException(e);
        }
    }
    @Override
    public void onSynthesizeDataArrived(String utteranceId, byte[] data, int progress, int engineType) {
       // super.onSynthesizeDataArrived(utteranceId, data, progress, engineType);
       // LogPrint.Log("合成进度回调, progress:" + progress + ";***:" + utteranceId);
        try {
            ttsFileBufferedOutputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 播放正常结束,每句播放正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口
     *
     * @param utteranceId
     */
    @Override
    public void onSynthesizeFinish(String utteranceId) {
       // super.onSynthesizeFinish(utteranceId);
        //LogPrint.Log("合成结束***:" + utteranceId);
        SendToUnity.SendUnity("SynthesizeResult","0"+"&"+mDestDir+mTaseName + mTagDialog + "-" + utteranceId + ".pcm");
        close();
    }
    //播放开始
    @Override
    public void onSpeechStart(String utteranceId) {
       // LogPrint.Log("onSpeechStart");
    }
    /**
     * 当合成或者播放过程中出错时回调此接口
     *
     * @param utteranceId
     * @param speechError 包含错误码和错误信息
     */
    @Override
    public void onError(String utteranceId, SpeechError speechError) {
        //LogPrint.Log("onError:" + speechError);
        SendToUnity.SendUnity("SynthesizeResult",speechError+"&"+mTaseName + mTagDialog + "-" + utteranceId + ".pcm");
        close();
    }

    /**
     * 关闭流,注意可能stop导致该方法没有被调用
     */
    private void close() {
        if (ttsFileBufferedOutputStream != null) {
            try {
                ttsFileBufferedOutputStream.flush();
                ttsFileBufferedOutputStream.close();
                ttsFileBufferedOutputStream = null;
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        if (ttsFileOutputStream != null) {
            try {
                ttsFileOutputStream.close();
                ttsFileOutputStream = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        LogPrint.Log("关闭文件成功");
    }
}

上方代码中LogPrint.Log()是我编写的日志类,这个可以自己创建一个,不需要跟我的一样

SendToUnity.SendUnity是发送到Unity中的类,参数是Unity类中的函数名,错误消息&语音文件完整路径,这个类在这里也不多做介绍

编写一个语音识别的处理类

package com.xxx.xxx.Synthesizer;//自己的包名

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.client.TtsMode;
import com.xxx.xxx.Util.LogPrint;//日志类,需要自己创建

import static com.cientx.tianguo.Util.GetActivity.getActivityByContext;

public class SpeechSynthesizerHandler {
    private Context mContext;
    private boolean mIsInit = false;
    SpeechSynthesizer mSpeechSynthesizer;
    public SpeechSynthesizerHandler(Context context, SpeechSynthesizerListener listener) {
        if (mIsInit) {
            listener = null;
            return;
        }
        mIsInit = true;
        mContext = context;
        initPermission(context);
        mSpeechSynthesizer = SpeechSynthesizer.getInstance();
        mSpeechSynthesizer.setContext(context);
        mSpeechSynthesizer.setSpeechSynthesizerListener(listener);
        mSpeechSynthesizer.setAppId("申请的appid");
        mSpeechSynthesizer.setApiKey("申请的apiKey", "申请的Secret Key");
        int result=  mSpeechSynthesizer.initTts(TtsMode.ONLINE); // 初始化在线模式
        if (result != 0) {
           LogPrint.Log("合成初始化失败:"+result);
        }
    }

    //开始合成
    /**
     * 开始说话
     *
     * @param speakStr  合成的文字
     * @param utteranceId  合成的序号
     * @param speakRole  合成后播放的发音人
     * @param speakVolume  合成后播放的音量
     * @param speakSpeed  合成后播放的语速
     */
    public void Speak(String speakStr,String utteranceId,String speakRole,String speakVolume,String speakSpeed) {
        LogPrint.Log("开始合成:"+speakStr);
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, speakRole);
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, speakVolume);
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, speakSpeed);
        int result=  mSpeechSynthesizer.speak(speakStr,utteranceId);//合成并播放
        if (result != 0) {
            LogPrint.Log("语音合成,合成失败:"+result);
        }
    }
    //暂停播放
    public void Pause() {
        mSpeechSynthesizer.pause();
    }

    //继续播放
    public void Resume() {
        mSpeechSynthesizer.resume();
    }

    //取消当前的合成。并停止播放。
    public void Stop() {
        mSpeechSynthesizer.stop();
    }

    //释放合成实例
    public void Release() {
        mSpeechSynthesizer.release();
        mSpeechSynthesizer = null;
    }

    private void initPermission(Context context) {
        String permissions[] = {
                Manifest.permission.INTERNET,
                Manifest.permission.ACCESS_NETWORK_STATE,
                Manifest.permission.MODIFY_AUDIO_SETTINGS,
                Manifest.permission.WRITE_SETTINGS,
                Manifest.permission.ACCESS_WIFI_STATE,
                Manifest.permission.CHANGE_WIFI_STATE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        };

        PackageManager pm = getActivityByContext(context).getPackageManager();
        boolean permission_accessWiFi = (PackageManager.PERMISSION_GRANTED ==
                pm.checkPermission("android.permission.ACCESS_WIFI_STATE", "com.cientx.tianguo"));
        boolean permission_network_state = (PackageManager.PERMISSION_GRANTED ==
                pm.checkPermission("android.permission.ACCESS_NETWORK_STATE", "com.cientx.tianguo"));
        boolean permission_internet = (PackageManager.PERMISSION_GRANTED ==
                pm.checkPermission("android.permission.INTERNET", "com.cientx.tianguo"));
        boolean permission_writeStorage = (PackageManager.PERMISSION_GRANTED ==
                pm.checkPermission("android.permission.MODIFY_AUDIO_SETTINGS", "com.cientx.tianguo"));
        boolean permission_changeWifi = (PackageManager.PERMISSION_GRANTED ==
                pm.checkPermission("android.permission.CHANGE_WIFI_STATE", "com.cientx.tianguo"));
        boolean permission_writeSettings = (PackageManager.PERMISSION_GRANTED ==
                pm.checkPermission("android.permission.WRITE_SETTINGS", "com.cientx.tianguo"));
        boolean permission_writeExternal = (PackageManager.PERMISSION_GRANTED ==
                pm.checkPermission("android.permission.WRITE_EXTERNAL_STORAGE", "com.cientx.tianguo"));
        if (!(permission_accessWiFi && permission_writeStorage && permission_network_state && permission_internet&& permission_changeWifi&& permission_writeSettings&& permission_writeExternal)) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                getActivityByContext(context).requestPermissions(permissions, 0x01);
            }
        }

    }
}

上面代码中.Util.GetActivity.getActivityByContext类代码如下

    public static Activity getActivityByContext(Context context){
        while(context instanceof ContextWrapper){
            if(context instanceof Activity){
                return (Activity) context;
            }
            context = ((ContextWrapper) context).getBaseContext();
        }
        return null;
    }

在主类中调用接口

package com.xxx.xxx;//自己的包名

import android.content.Context;
import com.xxx.xxx.Synthesizer.SpeechSynthesizerHandler;
import com.xxx.xxx.Synthesizer.SynthesizerListener;
import com.xxx.xxx.Synthesizer.FileSaveListener;
//百度语音主类
public class CientBaiDuVoiceMainActivity {


    public static CientBaiDuVoiceMainActivity _instance;


    public static CientBaiDuVoiceMainActivity getInstance() {
        if (_instance == null) {
            _instance = new CientBaiDuVoiceMainActivity();
        }
        return _instance;
    }
    //语音合成控制
    SpeechSynthesizerHandler mSynthesizerHandler;
    /**
     * 语音合成监听
     */
    SynthesizerListener mListener;

    //语音合成初始化
    public void InitSynthesizer(Context context) {
        mListener = new FileSaveListener();
        mSynthesizerHandler = new SpeechSynthesizerHandler(context, mListener);
    }

    //开始合成并播放
    /**@param speakStr 要合成的话,批量合成时,循环调用当前函数,SDK中有队列缓冲,官方推荐使用
     * @param utteranceId 当前对话的序号,单句话合成,序号默认为0
     * @param speakRole 发音人,在Unity中设置
     * @param speakVolume 音量
     * @param speakSpeed 语速
     * @param savePath 合成后语音文件要保存的目录
     * @param tag 当前对话的标识
     * */
    public void StartSynthesizer(String speakStr, String utteranceId, String speakRole, String speakVolume, String speakSpeed, String savePath, String tag) {
        mListener.SetSynthesizePath(savePath, tag);//设置合成后保存的路径
        mSynthesizerHandler.Speak(speakStr, utteranceId, speakRole, speakVolume, speakSpeed);
    }
    //取消当前的合成。并停止播放。
    public void StopSynthesizer() {
        mSynthesizerHandler.Stop();
    }

    //暂停播放当前正在播放的声音
    public void Pause() {
        mSynthesizerHandler.Pause();
    }

    //继续播放
    public void Resume() {
        mSynthesizerHandler.Resume();
    }

    //释放实例
    public void ReleaseSynthesizer() {
        mSynthesizerHandler.Release();
        mSynthesizerHandler = null;
    }
}

六、设置AndroidManifest.xml,发布aar包

为什么要发布aar包呢?因为.aar包内包含了这个sdk的AndroidManifest.xml文件与引用的库文件、引用的jar包,这样就不会与其他的sdk冲突,也不需要与其他的sdk中的Androidmanifest.xml合并

首先需要创建我们的签名,这里不再多说,创建签名的方法请在我的另一篇博文中查看

我们需要在创建的Module中的build.gradle中编写发布arr包的代码

task copyPlugin(type: Copy) {
    dependsOn assemble
    from('build/outputs/aar')
    into('../../Assets/Plugins/Android')
    include(project.name + '-release.aar')
}

发布按钮位于右上角的Gradle中

Unity接入百度语音合成安卓(android)SDK

Unity接入百度语音合成安卓(android)SDK

七、在Unity中编写交互代码

  void Start()
    {
        
        AndroidJNI.AttachCurrentThread();
        AndroidJavaClass _androidJC = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        if (_androidJC == null)
        {
            Debug.Log("JNI initialization failure.");
            return;
        }

        m_AndroidPluginObj = _androidJC.GetStatic<AndroidJavaObject>("currentActivity");

    }

    /// <summary>
    /// 初始化语音合成
    /// </summary>
    public void InitSynthesize()
    {
        AndroidJavaClass jc = new AndroidJavaClass("com.xxx.xxx.CientBaiDuVoiceMainActivity");//自己的包名
        AndroidJavaObject m_Android = jc.CallStatic<AndroidJavaObject>("getInstance");
        if (m_Android != null)
        {
            m_Android.Call("InitSynthesizer", m_AndroidPluginObj);
        }
        else
            Debug.Log("AndroidPlugin is Null");
    }

 /// <summary>
    /// 开始60字以内语音合成
    /// </summary>
    public void StartSynthesize()
    {
        AndroidJavaClass jc = new AndroidJavaClass("com.xxx.xxx.CientBaiDuVoiceMainActivity");//自己的包名
        AndroidJavaObject m_Android = jc.CallStatic<AndroidJavaObject>("getInstance");
        if (m_Android != null)
        {
            m_Android.Call("StartSynthesizer", "是一部生命的教科书,是献给生命的礼物", "0", "0", "5", "5", Application.persistentDataPath + "/voice/", "10000");
        }
        else
            Debug.Log("AndroidPlugin is Null");
    }


  List<string> superLongls = new List<string>();
    /// <summary>
    /// 开始超过60字的语音合成
    /// </summary>
    public void StartBatchSynthesize()
    {


        AndroidJavaClass jc = new AndroidJavaClass("com.cientx.tianguo.CientBaiDuVoiceMainActivity");
        AndroidJavaObject m_Android = jc.CallStatic<AndroidJavaObject>("getInstance");
        if (m_Android != null)
        {
            string speakStr =
                "天寿仪式通俗讲是一种祭祖行为,我们也叫它孝亲敬祖仪式。与普通祭祖行为相比,天寿仪式是遵循了天地、宇宙、自然规律的祭祖行为,对应天、地、人合一的理想状态。庄严的行为仪式、严谨的风水布局和包含美好祝福的丰盛供品,让天寿仪式具有强大的能量,集健康积极的生命状态、强大的正能量和良性向上的信息于一体,唤醒我们由祖辈传承至今的巨大潜力,助力目标实现,营造幸福生活。您还想了解有关天寿仪式的其他内容吗?1.天寿仪式操作步骤;2.组队天寿仪式操作步骤";

            List<string> douHaols = new List<string>();
            List<string> juHaols = new List<string>();
            List<string> tanHaols = new List<string>();
            List<string> fenHaols = new List<string>();
            List<string> wenHaols = new List<string>();
            //遇到,。!;?都拆分成单独的字符串,如果拆分到最后,单个的字符串还是超出了60个字的限制,则强制把超长的字符串拆分开
            SubDouHao(",", speakStr, 0, douHaols);

            for (int i = 0; i < douHaols.Count; i++)
            {
                SubDouHao("。", douHaols[i], 0, juHaols);
            }

            for (int i = 0; i < juHaols.Count; i++)
            {
                SubDouHao("!", juHaols[i], 0, tanHaols);
            }
            for (int i = 0; i < tanHaols.Count; i++)
            {
                SubDouHao(";", tanHaols[i], 0, fenHaols);
            }
            for (int i = 0; i < fenHaols.Count; i++)
            {
                SubDouHao("?", fenHaols[i], 0, wenHaols);
            }


            for (int i = 0; i < wenHaols.Count; i++)
            {
                SubStr(wenHaols[i], 0, superLongls);
            }

            for (int i = 0; i < superLongls.Count; i++)
            {
                m_Android.Call("StartSynthesizer", superLongls[i], i.ToString(), "0", "5", "5", Application.persistentDataPath + "/voice/", "10001");
            }

        }
        else
            Debug.Log("AndroidPlugin is Null");
    }
    void SubDouHao(string fuhao, string src, int startIndex, List<string> ls)
    {
        int index = src.IndexOf(fuhao);
        if (index < 0)
        {
            ls.Add(src);
            return;
        }
        string str;
        str = src.Substring(startIndex, index);
        ls.Add(str);
        string remainStr = src.Substring(index + 1, src.Length - (index + 1));
        SubDouHao(fuhao, remainStr, 0, ls);
    }
    private int SubIndex = 0;
    void SubStr(string src, int startIndex, List<string> ls)
    {
        if (src.Length - startIndex < 60)
        {
            ls.Add(src);
            return;
        }
        string str;
        str = src.Substring(startIndex, 60);
        ls.Add(str);
        SubIndex++;
        int len = src.Length - 60 * SubIndex;
        if (len >= 60)
        {
            SubStr(src, 60 * SubIndex, ls);
        }
        else
        {
            str = src.Substring(60 * SubIndex, len);
            ls.Add(str);
            return;
        }
    }



    Dictionary<string, Queue<string>> mDic = new Dictionary<string, Queue<string>>();
    Dictionary<string, AudioClip> mAudioDic = new Dictionary<string, AudioClip>();
    private int SynthesizeIndex = 0;
    public Button repeatPlayBtn;
    public AudioSource mAudio;
    Queue<string> mPathQueue = new Queue<string>();
    /// <summary>
    /// 百度语音合成结果回调
    /// </summary>
    /// <param name="res"></param>
    void SynthesizeResult(string res)
    {
        string[] ress = res.Split('&');
        //res    错误码+文件带路径  0&xxx/xxx/output-对话标识-序号.pcm
        if (ress[0] == "0")//合成成功
        {
            SynthesizeIndex++;
            if (SynthesizeIndex >= superLongls.Count)
            {
                repeatPlayBtn.interactable = true;
            }

            string fileName = ress[1];
          
            string[] splitStr = fileName.Split('-');
            if (!mDic.ContainsKey(splitStr[1]))
            {
                Debug.Log("添加音频文件:" + fileName);
                mPathQueue.Enqueue(fileName);
                mDic.Add(splitStr[1], mPathQueue);
                StartCoroutine(LoadLocalAudio(fileName));
            }
            else
            {
                Debug.Log("添加音频文件:"+ fileName);
                mDic[splitStr[1]].Enqueue(fileName);
            }
               
            mRecognRes.text = "合成的文件:" + fileName;
        }
    }

    IEnumerator LoadLocalAudio(string path)
    {
        UnityWebRequest _unityWebRequest = UnityWebRequestMultimedia.GetAudioClip(path, AudioType.WAV);
        yield return _unityWebRequest.SendWebRequest(); if (_unityWebRequest.isHttpError || _unityWebRequest.isNetworkError)
        {
            Debug.Log(_unityWebRequest.error.ToString());
        }
        else
        {
            AudioClip _audioClip = DownloadHandlerAudioClip.GetContent(_unityWebRequest);
            mAudioDic.Add(path, _audioClip);
        }
       
    }
    public void ChongFuBoFang()
    {
        repeatPlayBtn.interactable = false;
        StartCoroutine(AudioIsEnd(PlayEndCall));
    }

    private int playAudioIndex = 0;
    void PlayEndCall()
    {
        playAudioIndex++;
        if (playAudioIndex >= superLongls.Count)//全部播放完了
        {
            repeatPlayBtn.interactable = true;
        }
    }
    IEnumerator AudioIsEnd(UnityAction call)
    {
        while (mDic["10001"].Count > 0)
        {
            string filePath = mDic["10001"].Dequeue();
            AudioClip clip = mAudioDic[filePath];
            mAudio.clip = clip;
            yield return new WaitForSeconds(clip.length);
            if (call != null)
            {
                call();
            }
        }
    }

百度语音合成仅支持60个汉字以内的合成,所以,如果超过了60个字,则可以进行循环调用合成,合成sdk内部有缓冲队列

最后百度语音识别、语音合成、语音唤醒安卓端sdk与Unity交互的源码,请私信我