百度混合离线语音合成

  1. 首先注册百度开发者平台,建立应用

01.png
  1. 点击下一步,选择语音合成

02.png
  1. 点击下一步,选择下载语音合成SDK

03.png

07.png
  1. 下载完成以后输入包名,包名必须和程序的包名一致,包名可在清单文件中查看

![06.png](http://upload-images.jianshu.io/upload_images/3786250-26509e86ba22c610.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  1. 到此建立应用完成,下面向项目中导入资源

1,打开下载的文件,将libs目录下的.jar文件拷贝到项目中的libs目录,而后添加库依赖(必须,必须,必须)
2,在src/main下建立jniLibs目录(L是大写),将下载文件中libs目录下的.so文件拷贝在jniLIbs目录下
3,在src/main下建立assets目录,将下载文件中的data目录下的文件拷贝到assets中,同步一下工程,完成以后目录以下图
12.png


权限部分:AndroidManifest.xml
 
 
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.baidu.tts.sample">
    <!-- 集成时请添加下列权限 -->
    <!-- 测试完成后,您本身的appId appKey secretKey 请在 SynthActivity 和 MiniActivity 中修改 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
MixSpeakUtil.java

package com.baidu.tts.sample;


import android.Manifest;
import android.content.Context;

import android.content.pm.PackageManager;
import android.os.Environment;

import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;


import com.baidu.tts.auth.AuthInfo;
import com.baidu.tts.chainofresponsibility.logger.LoggerProxy;
import com.baidu.tts.client.SpeechError;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.client.TtsMode;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by lenovo on 2018/4/13.
 */

public class MixSpeakUtil implements SpeechSynthesizerListener {
    // ================== 初始化参数设置开始 ==========================
    /**
     * 发布时请替换成本身申请的appId appKey 和 secretKey。注意若是须要离线合成功能,请在您申请的应用中填写包名。
     * 本demo的包名是com.baidu.tts.sample,定义在build.gradle中。
     */

    private String appId = "1099**";//请更换为本身建立的应用
    private static final String appKey = "LSHj7b5z*****";//请更换为本身建立的应用
    private static final String secretKey = "pK*****";
    // TtsMode.MIX; 离在线融合,在线优先; TtsMode.ONLINE 纯在线; 没有纯离线
    private TtsMode ttsMode = TtsMode.MIX;

    private String mSampleDirPath;
    private static final String SAMPLE_DIR_NAME = "baiduTTS";
    private static final String SPEECH_FEMALE_MODEL_NAME = "bd_etts_common_speech_f7_mand_eng_high_am-mix_v3.0.0_20170512.dat";
    private static final String SPEECH_YYJW_MODEL_NAME = "bd_etts_common_speech_yyjw_mand_eng_high_am-mix_v3.0.0_20170512.dat";
    private static final String TEXT_MODEL_NAME = "bd_etts_text.dat";

    private String MODEL_FILENAME;
    private String TEXT_FILENAME;
    private String YYJW_MODEL_NAME;

    private Context context;

    // ===============初始化参数设置完毕,更多合成参数请至getParams()方法中设置 =================

    private SpeechSynthesizer mSpeechSynthesizer=null;

    private static final String TAG = "MixSpeakUtil";
    private static final String DESC = "精简版合成,仅给出示例集成合成的调用过程。能够测试离线合成功能,首次使用请联网。\n"
            + "其中initTTS方法须要在新线程调用,不然引发UI阻塞。\n"
            + "纯在线请修改代码里ttsMode为TtsMode.ONLINE, 没有纯离线。\n"
            + "离线功能须要手动将assets目录下的资源文件复制到TEMP_DIR =/baiduTTS \n"
            + "完整的SDK调用方式能够参见MainActivity\n\n";

    /**
     * 定义一个静态私有变量(不初始化,不使用final关键字,
     * 使用volatile保证了多线程访问时instance变量的可见性,
     * 避免了instance初始化时其余变量属性还没赋值完时,被另外线程调用
     * */
    private static volatile MixSpeakUtil instance;

    private MixSpeakUtil(Context context)
    {
        this.context = context;
        initTTs();
    }

    /**
     * 采用单例模式,内存占用地,效率高,线程安全,多线程操做原子性。
     * @param1 context为上下文环境
     * @return MixSpeakUtil实例
     *
     * */
    public static MixSpeakUtil getInstance(Context context) {
        if (instance == null) {
            //同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次建立后,再也不重复被建立)
            synchronized (MixSpeakUtil.class) {
                //未初始化,则初始instance变量
                if (instance == null) {
                    instance = new MixSpeakUtil(context);
                }
            }
        }
        return instance;
    }

    private void initTTs() {
        LoggerProxy.printable(true); // 日志打印在logcat中
        boolean isMix = ttsMode.equals(TtsMode.MIX);
        boolean isSuccess;
        if (isMix) {
            // 检查2个离线资源是否可读
            isSuccess = checkOfflineResources();
            if (!isSuccess) {
                return;
            } else {
                print("离线资源存在而且可读, 目录:" + mSampleDirPath);
            }
        }


        // 1. 获取实例
        mSpeechSynthesizer = SpeechSynthesizer.getInstance();
        mSpeechSynthesizer.setContext(context);


        // 3. 设置appId,appKey.secretKey
        int result = mSpeechSynthesizer.setAppId(appId);
        checkResult(result, "setAppId");
        result = mSpeechSynthesizer.setApiKey(appKey, secretKey);
        checkResult(result, "setApiKey");

        // 4. 支持离线的话,须要设置离线模型
        if (isMix) {
            // 检查离线受权文件是否下载成功,离线受权文件联网时SDK自动下载管理,有效期3年,3年后的最后一个月自动更新。
            isSuccess = checkAuth();
            if (!isSuccess) {
                return;
            }
            // 文本模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在而且可读
            mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME);
            // 声学模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在而且可读
            mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME);
        }

        // 5. 如下setParam 参数选填。不填写则默认值生效
        // 设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
        // 设置合成的音量,0-9 ,默认 5
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "9");
        // 设置合成的语速,0-9 ,默认 5
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5");
        // 设置合成的语调,0-9 ,默认 5
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5");

        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
        // 该参数设置为TtsMode.MIX生效。即纯在线模式不生效。
        // MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
        // MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
        // MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
        // MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线

        //mSpeechSynthesizer.setAudioStreamType(AudioManager.MODE_IN_CALL);

        // x. 额外 : 自动so文件是否复制正确及上面设置的参数
        Map<String, String> params = new HashMap<>();
        // 复制下上面的 mSpeechSynthesizer.setParam参数
        // 上线时请删除AutoCheck的调用
        if (isMix) {
            params.put(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME);
            params.put(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME);
        }


        // 6. 初始化
        result = mSpeechSynthesizer.initTts(ttsMode);
        checkResult(result, "initTts");

    }

    /**
     * 检查appId ak sk 是否填写正确,另外检查官网应用内设置的包名是否与运行时的包名一致。本demo的包名定义在build.gradle文件中
     *
     * @return
     */
    private boolean checkAuth() {
        AuthInfo authInfo = mSpeechSynthesizer.auth(ttsMode);
        if (!authInfo.isSuccess()) {
            // 离线受权须要网站上的应用填写包名。本demo的包名是com.baidu.tts.sample,定义在build.gradle中
            String errorMsg = authInfo.getTtsError().getDetailMessage();
            print("【error】鉴权失败 errorMsg=" + errorMsg);
            return false;
        } else {
            print("验证经过,离线正式受权文件存在。");
            return true;
        }
    }

    /**
     * 检查 TEXT_FILENAME, MODEL_FILENAME 这2个文件是否存在,不存在请自行从assets目录里手动复制
     *
     * @return
     */
    private boolean checkOfflineResources() {
        if (mSampleDirPath == null) {
            String sdcardPath = Environment.getExternalStorageDirectory().toString();
            mSampleDirPath = sdcardPath + "/" + SAMPLE_DIR_NAME;
        }
        File file = new File(mSampleDirPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        TEXT_FILENAME = mSampleDirPath + "/" + TEXT_MODEL_NAME;
        MODEL_FILENAME = mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME;
        YYJW_MODEL_NAME = mSampleDirPath + "/" + SPEECH_YYJW_MODEL_NAME;
        copyFromAssetsToSdcard(false, SPEECH_FEMALE_MODEL_NAME, MODEL_FILENAME);
        copyFromAssetsToSdcard(false, SPEECH_YYJW_MODEL_NAME, YYJW_MODEL_NAME);
        copyFromAssetsToSdcard(false, TEXT_MODEL_NAME, TEXT_FILENAME);
        String[] filenames = {TEXT_FILENAME, MODEL_FILENAME};
        for (String path : filenames) {
            File f = new File(path);
            if (!f.canRead()) {
                print("[ERROR] 文件不存在或者不可读取,请从assets目录复制同名文件到:" + path);
                print("[ERROR] 初始化失败!!!");
                return false;
            }
        }
        return true;
    }

    public void speak(String msg) {
        /* 如下参数每次合成时均可以修改
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
         *  设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "5"); 设置合成的音量,0-9 ,默认 5
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5"); 设置合成的语速,0-9 ,默认 5
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5"); 设置合成的语调,0-9 ,默认 5
         *
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
         *  MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
         *  MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
         *  MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
         *  MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
         */

        if (mSpeechSynthesizer == null) {
            print("[ERROR], 初始化失败");
            return;
        }
        int result = mSpeechSynthesizer.speak(msg);
        print("合成并播放 按钮已经点击");
        checkResult(result, "speak");
    }

    public void stop() {
        print("中止合成引擎 按钮已经点击");
        int result = mSpeechSynthesizer.stop();
        checkResult(result, "stop");
    }
    private void print(String message) {
        Log.i(TAG, message);
    }

    protected void onDestroy() {
        if (mSpeechSynthesizer != null) {
            mSpeechSynthesizer.stop();
            mSpeechSynthesizer.release();
            mSpeechSynthesizer = null;
            print("释放资源成功");
        }
    }

    private void checkResult(int result, String method) {
        if (result != 0) {
            print("error code :" + result + " method:" + method + ", 错误码文档:http://yuyin.baidu.com/docs/tts/122 ");
        }
    }



    /**
     * 将工程须要的资源文件拷贝到SD卡中使用(受权文件为临时受权文件,请注册正式受权)
     *
     * @param isCover 是否覆盖已存在的目标文件
     * @param source
     * @param dest
     */
    private void copyFromAssetsToSdcard(boolean isCover, String source, String dest) {
        File file = new File(dest);
        if (isCover || (!isCover && !file.exists())) {
            InputStream is = null;
            FileOutputStream fos = null;
            try {
                is = context.getAssets().open(source);
                String path = dest;
                fos = new FileOutputStream(path);
                byte[] buffer = new byte[1024];
                int size = 0;
                while ((size = is.read(buffer, 0, 1024)) >= 0) {
                    fos.write(buffer, 0, size);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }


    @Override
    public void onSynthesizeStart(String s) {

    }

    @Override
    public void onSynthesizeDataArrived(String s, byte[] bytes, int i) {

    }

    @Override
    public void onSynthesizeFinish(String s) {

    }

    @Override
    public void onSpeechStart(String s) {

    }

    @Override
    public void onSpeechProgressChanged(String s, int i) {

    }

    @Override
    public void onSpeechFinish(String s) {

    }

    @Override
    public void onError(String s, SpeechError speechError) {

    }
}
离线运行效果: 
 

整个demo完整代码以上传至github: https://github.com/liudongdong1/Sample 欢迎你们点赞!!!