項目中使用了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才能正常訪問這些資源文件:
- 對資源文件進行特殊命名。以本地能力(tts.local.xiaokun)所需要的TTS資源文件為例痒留,以下示例了這種命名規(guī)則:
libXiaoKun.voclib.so(在原文件名前加lib前綴與.so后綴)
libLetter_XiaoKun.voclib.so(在原文件名前加lib前綴與.so后綴)
將重新命名過的本地資源文件拷貝到libs根目錄下谴麦。
在能力初始化時,指定dataPath參數為 /data/data/appname/lib (appname為應用包的名稱)
在能力初始化時伸头,指定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();
}
}
}
打完收工凳宙!