靈云TTS(語音合成)

項目中使用了TTS(語音合成功能)剛開始自己準備使用科大訊飛的TTS SDK 但是公司經過半天調研(省錢)決定使用靈云的SDK脖岛。但是靈云的文檔和Demo不是很完善而且網上資料很少,避免下次挖坑自己封裝了一個TtsManage贵试。
靈云的TTS分為在線模式和本地模式糟港,在線的可以通過修改配置更改發(fā)音人攀操,離線模式只能通過在項目中的發(fā)音人文件發(fā)音。

一着逐、引入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"/>

三县钥、制作本地發(fā)音人文件

下載地址

這里寫圖片描述

為了便于應用的發(fā)布與管理,靈云SDK也支持將本地資源文件隨應用安裝在內部存儲上慈迈, 但此種存放方法需要開發(fā)者滿足以下要求若贮,靈云SDK才能正常訪問這些資源文件:

  1. 對資源文件進行特殊命名。以本地能力(tts.local.xiaokun)所需要的TTS資源文件為例痒留,以下示例了這種命名規(guī)則:

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)

所有需要的本地資源文件都需要按以上規(guī)則進行名字的改動匾效,并拷貝到libs目錄下。 通過這種方式恤磷,這些資源文件會被打包在APK中面哼,并在安裝時,被放在 /data/data/appname/lib 下扫步。 通過指定fileFlag為android_so魔策,靈云SDK就會按照特殊的命名方式來讀取這些文件。


這里寫圖片描述

把這4個文件拷貝到Lib目標下河胎,我們的環(huán)境就算搭建好了闯袒!下面開始寫代碼。
1.配置信息

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

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

    /**
     * 開發(fā)者密鑰
     */
    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) {
            // 由于系統(tǒng)已經初始化成功,在結束前需要調用方法hciRelease()進行系統(tǒng)的反初始化
            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 系統(tǒng)初始化參數
     */
    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);
        // 開發(fā)者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=細節(jié),5=調試旭斥,SDK將輸出小于等于logLevel的日志信息
            initparam.addParam(InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5");
        }
    }

    private int checkAuthAndUpdateAuth() {
        // 獲取系統(tǒng)授權到期時間
        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");

        // 指定語音合成的能力(云端合成,發(fā)言人是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, "播放器內部狀態(tài)錯誤", 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閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件削葱,死亡現場離奇詭異奖亚,居然都是意外死亡,警方通過查閱死者的電腦和手機析砸,發(fā)現死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門昔字,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人首繁,你說我怎么就攤上這事作郭。” “怎么了弦疮?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵夹攒,是天一觀的道長。 經常有香客問我胁塞,道長咏尝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任啸罢,我火速辦了婚禮编检,結果婚禮上,老公的妹妹穿的比我還像新娘扰才。我一直安慰自己允懂,他們只是感情好,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布衩匣。 她就那樣靜靜地躺著蕾总,像睡著了一般粥航。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上生百,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天递雀,我揣著相機與錄音,去河邊找鬼置侍。 笑死映之,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蜡坊。 我是一名探鬼主播杠输,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秕衙!你這毒婦竟也來了蠢甲?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤据忘,失蹤者是張志新(化名)和其女友劉穎鹦牛,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體勇吊,經...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡曼追,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了汉规。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片礼殊。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖针史,靈堂內的尸體忽然破棺而出晶伦,到底是詐尸還是另有隱情,我是刑警寧澤啄枕,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布婚陪,位于F島的核電站,受9級特大地震影響频祝,放射性物質發(fā)生泄漏泌参。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一常空、第九天 我趴在偏房一處隱蔽的房頂上張望及舍。 院中可真熱鬧,春花似錦窟绷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攘残。三九已至,卻和暖如春为狸,著一層夾襖步出監(jiān)牢的瞬間歼郭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工辐棒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留病曾,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓漾根,卻偏偏與公主長得像泰涂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辐怕,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內容

  • 《ilua》速成開發(fā)手冊3.0 官方用戶交流:iApp開發(fā)交流(1) 239547050iApp開發(fā)交流(2) 1...
    葉染柒丶閱讀 10,715評論 0 11
  • 《ijs》速成開發(fā)手冊3.0 官方用戶交流:iApp開發(fā)交流(1) 239547050iApp開發(fā)交流(2) 10...
    葉染柒丶閱讀 5,127評論 0 7
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,085評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理逼蒙,服務發(fā)現,斷路器寄疏,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 文|空修 采一抹花香 點在風尖上 芳草綠了湖光 蟬鳴驚了遐想 躲開悶熱的暑氣 我往回憶里去找你 你的影子牽著藤蘿 ...
    空修閱讀 269評論 3 9