灵云TTS(语音合成)

项目中使用了TTS(语音合成功能)刚开始自己准备使用科大讯飞的TTS SDK 但是公司经过半天调研(省钱)决定使用灵云的SDK。但是灵云的文档和Demo不是很完善而且网上资料很少,避免下次挖坑自己封装了一个TtsManage。
灵云的TTS分为在线模式和本地模式,在线的可以通过修改配置更改发音人,离线模式只能通过在项目中的发音人文件发音。

一、引入SDK和so文件

http://www.hcicloud.com/dev/appendix/evninstall

这里写图片描述

二、配置Manifest文件

在工程AndroidManifest.xml文件中添加如下权限:

 <!—如果使用录音机API时,需要RECORD_AUDIO权限,否则不需要 -->
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <!—通常需要设置一些sd卡路径(例如日志路径)为可写,因此需要能够写外部存储 -->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <!—以下访问网络的权限均需要打开-->
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <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.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

三、制作本地发音人文件

下载地址

这里写图片描述

为了便于应用的发布与管理,灵云SDK也支持将本地资源文件随应用安装在内部存储上, 但此种存放方法需要开发者满足以下要求,灵云SDK才能正常访问这些资源文件:

  1. 对资源文件进行特殊命名。以本地能力(tts.local.xiaokun)所需要的TTS资源文件为例,以下示例了这种命名规则:

libXiaoKun.voclib.so(在原文件名前加lib前缀与.so后缀)
libLetter_XiaoKun.voclib.so(在原文件名前加lib前缀与.so后缀)

  1. 将重新命名过的本地资源文件拷贝到libs根目录下。

  2. 在能力初始化时,指定dataPath参数为 /data/data/appname/lib (appname为应用包的名称)

  3. 在能力初始化时,指定fileFlag参数为android_so(默认为none)

所有需要的本地资源文件都需要按以上规则进行名字的改动,并拷贝到libs目录下。 通过这种方式,这些资源文件会被打包在APK中,并在安装时,被放在 /data/data/appname/lib 下。 通过指定fileFlag为android_so,灵云SDK就会按照特殊的命名方式来读取这些文件。


这里写图片描述

把这4个文件拷贝到Lib目标下,我们的环境就算搭建好了!下面开始写代码。
1.配置信息

/**
 * 灵云配置信息
 */
public final class ConfigUtil {

    /**
     * 灵云APP_KEY
     */
    public static final String APP_KEY = "005d5493";

    /**
     * 开发者密钥
     */
    public static final String DEVELOPER_KEY = "36599a64ff4cb08ffa916544f38c9002";

    /**
     * 灵云云服务的接口地址
     */
    public static final String CLOUD_URL = "test.api.hcicloud.com:8888";

    /**
     * 需要运行的灵云能力
     */
    // 离线语音合成
    public static final String CAP_KEY_TTS_LOCAL = "tts.local.synth";

    // 云端语音合成
    public static final String CAP_KEY_TTS_CLOUD = "tts.cloud.wangjing";

}

2.初始化TTS

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

import com.sinovoice.hcicloudsdk.api.HciCloudSys;
import com.sinovoice.hcicloudsdk.common.AuthExpireTime;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;
import com.sinovoice.hcicloudsdk.common.InitParam;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class TtsUtil {
    private final String TAG = this.getClass().getSimpleName();
    private  InitParam initParam;
    private final boolean isSaveLog=false;

    public TtsUtil(Context context) {
        setInitParam(context);
        checkStatus(context);
    }

    public boolean checkStatus(Context context) {
        // 初始化
        int errCode = HciCloudSys.hciInit(initParam.getStringConfig(), context);
        if (errCode != HciErrorCode.HCI_ERR_NONE && errCode != HciErrorCode.HCI_ERR_SYS_ALREADY_INIT) {
            Toast.makeText(context, "hciInit error: " + HciCloudSys.hciGetErrorInfo(errCode), Toast.LENGTH_SHORT).show();
            return false;
        }

        // 获取授权/更新授权文件 :
        errCode = checkAuthAndUpdateAuth();
        if (errCode != HciErrorCode.HCI_ERR_NONE) {
            // 由于系统已经初始化成功,在结束前需要调用方法hciRelease()进行系统的反初始化
            Toast.makeText(context, "CheckAuthAndUpdateAuth error: " + HciCloudSys.hciGetErrorInfo(errCode), Toast.LENGTH_SHORT).show();
            HciCloudSys.hciRelease();
            return false;
        }

        if (isSaveLog) {
            saveLog(context.getPackageName(), initParam);
        }
        return true;
    }

    /**
     * 加载初始化信息
     *
     * @return 系统初始化参数
     */
    private void setInitParam(Context context) {
        String authDirPath = context.getFilesDir().getAbsolutePath();
        // 前置条件:无
        initParam= new InitParam();
        // 授权文件所在路径,此项必填
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_AUTH_PATH, authDirPath);
        // 是否自动访问云授权,详见 获取授权/更新授权文件处注释
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_AUTO_CLOUD_AUTH, "no");
        // 灵云云服务的接口地址,此项必填
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_CLOUD_URL, ConfigUtil.CLOUD_URL);
        // 开发者Key,此项必填,由捷通华声提供
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_DEVELOPER_KEY, ConfigUtil.DEVELOPER_KEY);
        // 应用Key,此项必填,由捷通华声提供
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_APP_KEY, ConfigUtil.APP_KEY);
    }

    public void saveLog(String packageName, InitParam initparam) {
        // 配置日志参数
        String sdcardState = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(sdcardState)) {
            String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();
            String logPath = sdPath + File.separator + "sinovoice" + File.separator + packageName + File.separator + "log" + File.separator;
            // 日志文件地址
            File fileDir = new File(logPath);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            // 日志的路径,可选,如果不传或者为空则不生成日志
            initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_PATH, logPath);
            // 日志数目,默认保留多少个日志文件,超过则覆盖最旧的日志
            initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_COUNT, "5");
            // 日志大小,默认一个日志文件写多大,单位为K
            initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_SIZE, "1024");
            // 日志等级,0=无,1=错误,2=警告,3=信息,4=细节,5=调试,SDK将输出小于等于logLevel的日志信息
            initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5");
        }
    }

    private int checkAuthAndUpdateAuth() {
        // 获取系统授权到期时间
        int initResult;
        AuthExpireTime objExpireTime = new AuthExpireTime();
        initResult = HciCloudSys.hciGetAuthExpireTime(objExpireTime);
        if (initResult == HciErrorCode.HCI_ERR_NONE) {
            // 显示授权日期,如用户不需要关注该值,此处代码可忽略
            Date date = new Date(objExpireTime.getExpireTime() * 1000);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
            Log.i(TAG, "expire time: " + sdf.format(date));
            if (objExpireTime.getExpireTime() * 1000 > System.currentTimeMillis()) {
                // 已经成功获取了授权,并且距离授权到期有充足的时间(>7天)
                Log.i(TAG, "checkAuth success");
                return initResult;
            }
        }
        // 获取过期时间失败或者已经过期
        initResult = HciCloudSys.hciCheckAuth();
        if (initResult == HciErrorCode.HCI_ERR_NONE) {
            Log.i(TAG, "checkAuth success");
            return initResult;
        } else {
            Log.e(TAG, "checkAuth failed: " + initResult);
            return initResult;
        }
    }

    /**
     * 释放
     */
    public void hciRelease(){
        HciCloudSys.hciRelease();
    }
}

3.初始化语音播放

import android.app.Activity;
import android.util.Log;
import android.widget.Toast;

import com.sinovoice.hcicloudsdk.android.tts.player.TTSPlayer;
import com.sinovoice.hcicloudsdk.common.asr.AsrInitParam;
import com.sinovoice.hcicloudsdk.common.hwr.HwrInitParam;
import com.sinovoice.hcicloudsdk.common.tts.TtsConfig;
import com.sinovoice.hcicloudsdk.common.tts.TtsInitParam;
import com.sinovoice.hcicloudsdk.player.TTSCommonPlayer;
import com.sinovoice.hcicloudsdk.player.TTSPlayerListener;

/**
 * Created by kqw on 2016/8/12.
 * 初始化语音合成能力
 */
public class TtsPlayUtil {

    private static final String TAG = "HciUtil";
    private Activity mActivity;
    private TTSPlayer mTtsPlayer;

    public TtsPlayUtil(Activity activity) {
        mActivity = activity;
    }

    /**
     * 初始化播放器
     */
    public boolean initPlayer(TTSPlayerListener ttsPlayerListener) {
        // 构造Tts初始化的帮助类的实例
        TtsInitParam ttsInitParam = new TtsInitParam();
        // 获取App应用中的lib的路径
        String dataPath = mActivity.getBaseContext().getFilesDir().getAbsolutePath().replace("files", "lib");
        Log.e("path",dataPath);
        ttsInitParam.addParam(TtsInitParam.PARAM_KEY_DATA_PATH, dataPath);
        // 此处演示初始化的能力为tts.cloud.xiaokun, 用户可以根据自己可用的能力进行设置, 另外,此处可以传入多个能力值,并用;隔开
        ttsInitParam.addParam(AsrInitParam.PARAM_KEY_INIT_CAP_KEYS, ConfigUtil.CAP_KEY_TTS_LOCAL);
        // 如果使用本地能力,需要设置本地音库文件路径

        // 使用lib下的资源文件,需要添加android_so的标记
        ttsInitParam.addParam(HwrInitParam.PARAM_KEY_FILE_FLAG, HwrInitParam.VALUE_OF_PARAM_FILE_FLAG_ANDROID_SO);

        mTtsPlayer = new TTSPlayer();
        // 配置TTS初始化参数
        mTtsPlayer.init(ttsInitParam.getStringConfig(), ttsPlayerListener);

        return mTtsPlayer.getPlayerState() == TTSPlayer.PLAYER_STATE_IDLE;
    }


    // 云端合成,不启用编码传输(默认encode=none)
    public void synth(String text) {
        // 配置播放器的属性。包括:音频格式,音库文件,语音风格,语速等等。详情见文档。
        TtsConfig ttsConfig = new TtsConfig();
        // 音频格式
        ttsConfig.addParam(TtsConfig.BasicConfig.PARAM_KEY_AUDIO_FORMAT, "pcm16k16bit");

        // 指定语音合成的能力(云端合成,发言人是XiaoKun)
        ttsConfig.addParam(TtsConfig.SessionConfig.PARAM_KEY_CAP_KEY, ConfigUtil.CAP_KEY_TTS_LOCAL);
        //
        ttsConfig.addParam(TtsConfig.BasicConfig.PARAM_KEY_SPEED, "5");
        // property为私有云能力必选参数,公有云传此参数无效
//        ttsConfig.addParam("property", "cn_xiaokun_common");

        if (mTtsPlayer.getPlayerState() == TTSCommonPlayer.PLAYER_STATE_PLAYING || mTtsPlayer.getPlayerState() == TTSCommonPlayer.PLAYER_STATE_PAUSE) {
            mTtsPlayer.stop();
        }

        if (mTtsPlayer.getPlayerState() == TTSCommonPlayer.PLAYER_STATE_IDLE) {
            mTtsPlayer.play(text, ttsConfig.getStringConfig());
        } else {
            Toast.makeText(mActivity, "播放器内部状态错误", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 释放
     */
    public void release() {
        if (null != mTtsPlayer) {
            mTtsPlayer.release();
        }
    }

}

5.最后一步在UI进行交换

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.sinovoice.hcicloudsdk.player.TTSCommonPlayer;
import com.sinovoice.hcicloudsdk.player.TTSPlayerListener;

import net.yeah.liliLearn.utils.TtsPlayUtil;
import net.yeah.liliLearn.utils.TtsUtil;

public class MainActivity extends AppCompatActivity implements TTSPlayerListener {
    private TtsPlayUtil mTtsPlayUtil;
    private TtsUtil mInitTts;
    private boolean isInitPlayer;
    private EditText mInputMsgEdit;
    private Button mPlayerButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initTts();
    }
    private void initView(){
        mInputMsgEdit= (EditText) findViewById(R.id.input_msg_edit);
        mPlayerButton= (Button) findViewById(R.id.player_btn);
        mPlayerButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                synth(mInputMsgEdit.getText().toString());
            }
        });
    }
    private void initTts(){
        // 灵云语音工具类
        mInitTts = new TtsUtil(this);
        // 语音合成能力工具类
        mTtsPlayUtil = new TtsPlayUtil(this);
        // 初始化语音合成
        isInitPlayer = mTtsPlayUtil.initPlayer(this);
    }
    @Override
    public void onPlayerEventStateChange(TTSCommonPlayer.PlayerEvent playerEvent) {

    }

    @Override
    public void onPlayerEventProgressChange(TTSCommonPlayer.PlayerEvent playerEvent, int i, int i1) {

    }

    @Override
    public void onPlayerEventPlayerError(TTSCommonPlayer.PlayerEvent playerEvent, int i) {

    }

    public void synth(String msg) {
        if (!isInitPlayer) {
            Toast.makeText(this, "语音播报初始化失败", Toast.LENGTH_SHORT).show();
            return;
        }
        if (TextUtils.isEmpty(msg)) {
            Toast.makeText(this, "语音播报合成内容为空", Toast.LENGTH_SHORT).show();
            return;
        }
        mTtsPlayUtil.synth(msg);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mTtsPlayUtil != null) {
            mTtsPlayUtil.release();
        }
        if (null != mInitTts) {
            mInitTts.hciRelease();
        }
    }
}

打完收工!

Demo地址:https://github.com/liliLearn/TtsManage-master

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 《ilua》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 1...
    叶染柒丶阅读 10,500评论 0 11
  • 《ijs》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 10...
    叶染柒丶阅读 5,067评论 0 7
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,411评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 文|空修 采一抹花香 点在风尖上 芳草绿了湖光 蝉鸣惊了遐想 躲开闷热的暑气 我往回忆里去找你 你的影子牵着藤萝 ...
    空修阅读 263评论 3 9