上一篇是写的在线语音合成,用的是科大讯飞的SDK,因为在线是需要网络的,所以离线也是有需求的,至于为什么不直接用离在线,下一篇再告诉大家,这里先卖个关子,这一篇就写离线语音合成,用的是云知声的SDK。

首先,打开云知声开放平台


然后,注册、登录(图略)

接着打开我的应用,添加新应用,选择通用解决方案


android 免费语音合成sdk 免费离线语音合成sdk_离线


添加新应用后,选择Android,离线语音合成,点击下载


android 免费语音合成sdk 免费离线语音合成sdk_android 免费语音合成sdk_02


android 免费语音合成sdk 免费离线语音合成sdk_语音合成_03


下载完成后,解压压缩包USCDemo文件夹

打开USCDemo-->assets-->OfflineTTSModels,选择复制2个文件(离线语音合成模型)到你项目中的assets资源目录下


android 免费语音合成sdk 免费离线语音合成sdk_android_04


打开USCDemo-->libs,复制jar包和.so文件到你的项目libs目录下


android 免费语音合成sdk 免费离线语音合成sdk_语音合成_05


注意:需要在build.gradle增加如下图所示代码(注意层级),不然编译时会报错找不到.so文件(如下图左上角箭头修改项目结构为Project,然后找到在app目录下的build.gradle文件进行修改)




android 免费语音合成sdk 免费离线语音合成sdk_android_06


repositories {
    flatDir {
        dir 'libs'
    }
}
sourceSets {
    main {
        jniLibs.srcDir 'libs'
    }
}


做完以上准备工作,就可以开始撸代码了


    首先,AndroidManifest.xml申请权限(6.0需要动态申请权限,碍于篇幅,请自行百度)


<uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE" />

    然后,直接封装成工具类,以供全局调用离线语音合成功能,大家可以直接复制进去用,其中APPKEY和SECRET在开放平台-->我的应用,点击你之前添加的应用可查看


android 免费语音合成sdk 免费离线语音合成sdk_免费_07


package com.cyf.ttsdemo.utils;

import android.content.Context;
import android.os.Environment;
import android.util.Log;

import com.cyf.ttsdemo.MyApplication;
import com.unisound.client.SpeechConstants;
import com.unisound.client.SpeechSynthesizer;
import com.unisound.client.SpeechSynthesizerListener;

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

/**
 * Created by As on 2017/8/7.
 */

public class TTSUtils implements SpeechSynthesizerListener {

    private static final String TAG = "TTSUtils";
    private static volatile TTSUtils instance = null;
    private boolean isInitSuccess = false;
    private SpeechSynthesizer mTTSPlayer;

    private static final String SAMPLE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/unisound/tts/";
    private static final String FRONTEND_MODEL = "frontend_model";
    private static final String BACKEND_MODEL = "backend_lzl";

    private static final String APPKEY = "wiouzjcsmxvqes7ibqqm5bcgqrzrvddsvtceiwa5";
    private static final String SECRET = "3e20d7aff586ffe7dce62b302e7cc378";

    private TTSUtils() {
    }

    public static TTSUtils getInstance() {
        if (instance == null) {
            synchronized (TTSUtils.class) {
                if (instance == null) {
                    instance = new TTSUtils();
                }
            }
        }
        return instance;
    }

    public void init() {
        Context context = MyApplication.getContext();
        mTTSPlayer = new SpeechSynthesizer(context, APPKEY, SECRET);
        mTTSPlayer.setOption(SpeechConstants.TTS_SERVICE_MODE, SpeechConstants.TTS_SERVICE_MODE_LOCAL); // 设置本地合成
	File file = new File(SAMPLE_DIR);
        if (!file.exists()) {
            file.mkdirs();
        }
        File _FrontendModelFile = new File(SAMPLE_DIR + FRONTEND_MODEL);
        if (!_FrontendModelFile.exists()) {
            copyAssetsFile2SDCard(context, FRONTEND_MODEL, SAMPLE_DIR + FRONTEND_MODEL);
        }
        File _BackendModelFile = new File(SAMPLE_DIR + BACKEND_MODEL);
        if (!_BackendModelFile.exists()) {
            copyAssetsFile2SDCard(context, BACKEND_MODEL, SAMPLE_DIR + BACKEND_MODEL);
        }
        mTTSPlayer.setOption(SpeechConstants.TTS_KEY_FRONTEND_MODEL_PATH, SAMPLE_DIR + FRONTEND_MODEL);// 设置前端模型
        mTTSPlayer.setOption(SpeechConstants.TTS_KEY_BACKEND_MODEL_PATH, SAMPLE_DIR + BACKEND_MODEL);// 设置后端模型
        mTTSPlayer.setTTSListener(this);// 设置回调监听
        mTTSPlayer.init(null);// 初始化合成引擎
    }

    public void speak(String msg) {
        if (isInitSuccess) {
            mTTSPlayer.playText(msg);
        }else {
            init();
        }
    }

    public void stop() {
        mTTSPlayer.stop();
    }

    public void pause() {
        mTTSPlayer.pause();
    }

    public void resume() {
        mTTSPlayer.resume();
    }

    public void release() {
        if (null != mTTSPlayer) {
            // 释放离线引擎
            mTTSPlayer.release(SpeechConstants.TTS_RELEASE_ENGINE, null);
        }
    }

    @Override
    public void onEvent(int type) {
        switch (type) {
            case SpeechConstants.TTS_EVENT_INIT:
                isInitSuccess = true;
                break;
            case SpeechConstants.TTS_EVENT_SYNTHESIZER_START:
                // 开始合成回调
                Log.i(TAG, "beginSynthesizer");
                break;
            case SpeechConstants.TTS_EVENT_SYNTHESIZER_END:
                // 合成结束回调
                Log.i(TAG, "endSynthesizer");
                break;
            case SpeechConstants.TTS_EVENT_BUFFER_BEGIN:
                // 开始缓存回调
                Log.i(TAG, "beginBuffer");
                break;
            case SpeechConstants.TTS_EVENT_BUFFER_READY:
                // 缓存完毕回调
                Log.i(TAG, "bufferReady");
                break;
            case SpeechConstants.TTS_EVENT_PLAYING_START:
                // 开始播放回调
                Log.i(TAG, "onPlayBegin");
                break;
            case SpeechConstants.TTS_EVENT_PLAYING_END:
                // 播放完成回调
                Log.i(TAG, "onPlayEnd");
                break;
            case SpeechConstants.TTS_EVENT_PAUSE:
                // 暂停回调
                Log.i(TAG, "pause");
                break;
            case SpeechConstants.TTS_EVENT_RESUME:
                // 恢复回调
                Log.i(TAG, "resume");
                break;
            case SpeechConstants.TTS_EVENT_STOP:
                // 停止回调
                Log.i(TAG, "stop");
                break;
            case SpeechConstants.TTS_EVENT_RELEASE:
                // 释放资源回调
                Log.i(TAG, "release");
                break;
            default:
                break;
        }
    }

    @Override
    public void onError(int type, String errorMSG) {
        Log.e(TAG, "语音合成错误回调: " + errorMSG);
    }

    public static void copyAssetsFile2SDCard(Context context, String fileName, String path) {
        try {
            InputStream is = context.getAssets().open(fileName);
            FileOutputStream fos = new FileOutputStream(new File(path));
            byte[] buffer = new byte[1024];
            int byteCount = 0;
            while ((byteCount = is.read(buffer)) != -1) {// 循环从输入流读取buffer字节
                fos.write(buffer, 0, byteCount);// 将读取的输入流写入到输出流
            }
            fos.flush();// 刷新缓冲区
            is.close();
            fos.close();
        } catch (IOException e) {
            Log.e(TAG, "copyAssetsFile2SDCard: " + e.toString());
        }
    }

}


    同样的需要新建MyApplication.java进行预初始化离线语音合成功能

package com.cyf.ttsdemo;

import android.app.Application;
import android.content.Context;

import com.cyf.ttsdemo.utils.TTSUtils;

/**
 * Created by As on 2017/8/7.
 */

public class MyApplication extends Application{

    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
        TTSUtils.getInstance().init();
    }

    public static Context getContext(){
        return context;
    }
}

    最后,别忘了在AndroidManifest.xml文件中注册该Application

android 免费语音合成sdk 免费离线语音合成sdk_语音合成_08


    好的,这样就大功告成了,在需要进行语音合成的地方调用TTSUtils.getInstance().speak("xxx")即可


android 免费语音合成sdk 免费离线语音合成sdk_离线_09


    注意:离线语音合成因为需要复制文件到SD卡中,所以初始化是比较耗时的,这就有可能造成有一些情况:初始化还未完成,但调用了语音合成方法没有声音的情况,而我在代码中并未处理,所以有遇到类似情况的,可以自己琢磨处理一下。