最近在做Android语音播报功能(TTS),现总结如下:(ps:demo代码地址:https://github.com/giserlong/TTS_DEMO)
一.Android原生接口
用Android原生接口TextToSpeech,简单易用,但是一般情况下不支持中文,需自己下载讯飞语音+ 等中文引擎,并设置为系统默认tts,方可正常播报中文,关键代码如下:
@Override protected void onCreateBundle savedInstanceState) { super.onCreatesavedInstanceState); setContentViewR.layout.activity_native); //初始化TTS tts = new TextToSpeechthis, this); //获取控件 speechText = EditText)findViewByIdR.id.speechTextView); speechButton = Button)findViewByIdR.id.speechButton); //为button添加监听 speechButton.setOnClickListenernew OnClickListener){ @Override public void onClickView v){ // TODO Auto-generated method stub tts.speakspeechText.getText).toString), TextToSpeech.QUEUE_FLUSH, null); } }); } @Override public void onInitint status){ // 判断是否转化成功 if status == TextToSpeech.SUCCESS){ //tts.getCurrentEngine); //默认设定语言为中文,原生的android貌似不支持中文。 int result = tts.setLanguageLocale.CHINA); if result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED){ tts.setLanguageLocale.US); Toast.makeTextthis,"不支持中文,已自动设置为英文",Toast.LENGTH_SHORT).show); Log.d"ss",""); }else{ Toast.makeTextthis,"已自动设置为中文",Toast.LENGTH_SHORT).show); Log.d"ss",""); } } }
二.百度离在线融合SDK
注册百度智能云开发者账号后,添加语音合成应用,填写包名等相关信息后,生成key及APPID等信息:
激活SDK需此关键信息,还需下载对应SDK,并添加至项目中,引用相关jar包,添加对应so库至asset
关键代码如下:
package com.yupont.www.myapplication; import android.content.Context; import android.os.Environment; 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.FileOutputStream; import java.io.InputStream; /** * <p>文件描述:<p> * <p>作者:Mark<p> * <p>创建时间:2019/5/23<p> * <p>更改时间:2019/5/23<p> * <p>版本号:1<p> */ public class BaiDuSpeechUtil { private final String TAG = this.getClass).getSimpleName); private SpeechSynthesizer mSpeechSynthesizer; private String mSampleDirPath; private static final String SAMPLE_DIR_NAME = "baiduTTS"; //-------以下全是在assets下的文件,使用离线时必须全部copy到手机中方可使用----start-- 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_MALE_MODEL_NAME = "bd_etts_common_speech_m15_mand_eng_high_am-mix_v3.0.0_20170505.dat"; private static final String TEXT_MODEL_NAME = "bd_etts_text.dat"; private static final String ENGLISH_SPEECH_FEMALE_MODEL_NAME = "bd_etts_speech_female_en.dat"; private static final String ENGLISH_SPEECH_MALE_MODEL_NAME = "bd_etts_speech_male_en.dat"; private static final String ENGLISH_TEXT_MODEL_NAME = "bd_etts_text_en.dat"; //--------end------------------------------------------------------------- private static BaiDuSpeechUtil baiDuSpeechUtil = null; public static BaiDuSpeechUtil getInstance){ ifbaiDuSpeechUtil == null) { synchronized BaiDuSpeechUtil.class) { ifbaiDuSpeechUtil == null) { baiDuSpeechUtil = new BaiDuSpeechUtil); } } } return baiDuSpeechUtil; } /** * 初始化百度语音资源 * @param context */ public void setInitialEnvContext context) { initialEnvcontext); } /** * 初始化百度语音播报相关 * @param context */ public void setInitialTtsContext context, SpeechSynthesizerListener speechSynthesizerListener){ initialTtscontext,speechSynthesizerListener); } private void initialEnvContext context) { // long start_time= System.currentTimeMillis); if mSampleDirPath == null) { String sdcardPath = Environment.getExternalStorageDirectory).toString); mSampleDirPath = sdcardPath + "/" + SAMPLE_DIR_NAME; } makeDirmSampleDirPath); copyFromAssetsToSdcardcontext,false, SPEECH_FEMALE_MODEL_NAME, mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME); copyFromAssetsToSdcardcontext,false, SPEECH_MALE_MODEL_NAME, mSampleDirPath + "/" + SPEECH_MALE_MODEL_NAME); copyFromAssetsToSdcardcontext,false, TEXT_MODEL_NAME, mSampleDirPath + "/" + TEXT_MODEL_NAME); copyFromAssetsToSdcardcontext,false, "english/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME, mSampleDirPath + "/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME); copyFromAssetsToSdcardcontext,false, "english/" + ENGLISH_SPEECH_MALE_MODEL_NAME, mSampleDirPath + "/" + ENGLISH_SPEECH_MALE_MODEL_NAME); copyFromAssetsToSdcardcontext,false, "english/" + ENGLISH_TEXT_MODEL_NAME, mSampleDirPath + "/" + ENGLISH_TEXT_MODEL_NAME); // Log.dTAG,"initialEnv cost:"+ System.currentTimeMillis)-start_time)); } private void makeDirString dirPath) { File file = new FiledirPath); if !file.exists)) { file.mkdirs); } } /** * 将sample工程需要的资源文件拷贝到SD卡中使用(授权文件为临时授权文件,请注册正式授权) * 主要是在离线时候用到,只需执行一次即可,这里写的不严谨,应该去判断一下离线用的那些文件,sd卡是否存在,如果不存在,则copy,如果存在则无需在copy,可在子线程操作 * @param isCover 是否覆盖已存在的目标文件 * @param source * @param dest */ private void copyFromAssetsToSdcardContext context, boolean isCover, String source, String dest) { File file = new Filedest); if isCover || !isCover && !file.exists))) { InputStream is = null; FileOutputStream fos = null; try { is = context.getAssets).opensource); String path = dest; fos = new FileOutputStreampath); byte[] buffer = new byte[1024]; int size = 0; while size = is.readbuffer, 0, 1024)) != -1) { fos.writebuffer, 0, size); } fos.flush); } catch Exception e) { e.printStackTrace); } finally { if is != null) { try { is.close); } catch Exception e) { e.printStackTrace); } } if fos != null) { try { is.close); } catch Exception e) { e.printStackTrace); } } } } } //此方法可在子线程中操作,由于这个初始化过程比较费时,大概在1s左右,看项目需求而定,如果是进入界面就必须播放(伴随UI改变的)的,在UI线程,如无其他特殊要求,放在子线程中即可 private void initialTtsContext context,SpeechSynthesizerListener speechSynthesizerListener) { // long start_time= System.currentTimeMillis); mSpeechSynthesizer = SpeechSynthesizer.getInstance); mSpeechSynthesizer.setContextcontext); mSpeechSynthesizer.setSpeechSynthesizerListenerspeechSynthesizerListener); mSpeechSynthesizer.setApiKeyConfig.appKey_baidu, Config.secret_baidu); mSpeechSynthesizer.setAppIdConfig.appID_baidu); // 文本模型文件路径 离线引擎使用) mSpeechSynthesizer.setParamSpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, mSampleDirPath + "/" + TEXT_MODEL_NAME); mSpeechSynthesizer.setParamSpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME); // 本地授权文件路径,如未设置将使用默认路径.设置临时授权文件路径,LICENCE_FILE_NAME请替换成临时授权文件的实际路径,仅在使用临时license文件时需要进行设置,如果在[应用管理]中开通了正式离线授权,不需要设置该参数,建议将该行代码删除(离线引擎) // 如果合成结果出现临时授权文件将要到期的提示,说明使用了临时授权文件,请删除临时授权即可。 // 发音人(在线引擎),可用参数为0,1,2,3。。。(服务器端会动态增加,各值含义参考文档,以文档说明为准。0--普通女声,1--普通男声,2--特别男声,3--情感男声。。。) mSpeechSynthesizer.setParamSpeechSynthesizer.PARAM_SPEAKER, "0"); // 设置Mix模式的合成策略, //mix模式下,wifi使用在线合成,非wifi使用离线合成) mSpeechSynthesizer.setParamSpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI); // ifSystemUtil.isNetWorkConnectedgetCurrentActivity))) { // // AuthInfo接口用于测试开发者是否成功申请了在线或者离线授权,如果测试授权成功了,可以删除AuthInfo部分的代码(该接口首次验证时比较耗时),不会影响正常使用(合成使用时 // AuthInfo authInfo=this.mSpeechSynthesizer.authTtsMode.MIX); // // if authInfo.isSuccess)){ // toPrint"auth success"); // }else{ // String errorMsg=authInfo.getTtsError).getDetailMessage); // toPrint"auth failed errorMsg=" + errorMsg); // } // } // 初始化tts mSpeechSynthesizer.initTtsTtsMode.MIX); // 加载离线英文资源(提供离线英文合成功能) //int result = mSpeechSynthesizer.loadEnglishModelmSampleDirPath + "/" + ENGLISH_TEXT_MODEL_NAME, mSampleDirPath + mSampleDirPath + "/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME); // Log.dTAG,"initialTts cost:"+ System.currentTimeMillis)-start_time)); int result = mSpeechSynthesizer.loadModelmSampleDirPath + "/" + TEXT_MODEL_NAME, mSampleDirPath + mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME); ifresult<0){ result++; } } /** * 播报的文字 * @param content */ public void speakTextString content) { try{ ifmSpeechSynthesizer != null) { int result = mSpeechSynthesizer.speakcontent); if result < 0) { // Log.dTAG,"error,please look up error code in doc or URL:http://yuyin.baidu.com/docs/tts/122 "); } } }catch Exception e) { e.printStackTrace); } } /** * 暂停 */ public void pauseSpeechSynthesizer){ ifmSpeechSynthesizer != null) { mSpeechSynthesizer.pause); } } /** * 停止播放 */ public void stopSpeechSynthesizer){ ifmSpeechSynthesizer != null) { mSpeechSynthesizer.stop); } } /** * 接着停止后的地方播放 */ public void resumeSpeechSynthesizer){ ifmSpeechSynthesizer != null) { mSpeechSynthesizer.resume); } } /** * 释放mSpeechSynthesizer,在使用完之后必须调用,确保下个界面使用的时候资源已经释放掉了,否则下个界面将无法正常播放 */ public void releaseSpeechSynthesizer){ ifmSpeechSynthesizer != null) { mSpeechSynthesizer.release); } } public void setSpeechSynthesizerNull){ ifmSpeechSynthesizer != null) { mSpeechSynthesizer = null; } } public void endSpeechSynthesizer){ pauseSpeechSynthesizer); stopSpeechSynthesizer); releaseSpeechSynthesizer); setSpeechSynthesizerNull); } }
首次需联网,自动下载授权文件,以后离线也能用
三.云知声离线SDK
同百度,注册账号后,下载sdk,做好引用与配置
gradle中配置:
sourceSets {
main {
jniLibs.srcDirs = [‘libs’]
}
}
关键代码:
package com.yupont.www.myapplication; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.unisound.client.SpeechConstants; import com.unisound.client.SpeechSynthesizer; import com.unisound.client.SpeechSynthesizerListener; public class yzsTTSOfflineActivity extends Activity { private static boolean TTS_PLAY_FLAGE = false; private EditText mTTSText; private TextView mTextViewTip; private TextView mTextViewStatus; private Button mTTSPlayBtn; private SpeechSynthesizer mTTSPlayer; private final String mFrontendModel= Environment.getExternalStorageDirectory).toString)+"/Yupont/UAV/OfflineTTSModels/frontend_model"; private final String mBackendModel = Environment.getExternalStorageDirectory).toString)+"/Yupont/UAV/OfflineTTSModels/backend_lzl"; // private final String mFrontendModel= getClass).getClassLoader).getResource"assets/OfflineTTSModels/frontend_model").getPath).substring5); // private final String mBackendModel = getClass).getClassLoader).getResource"assets/OfflineTTSModels/backend_lzl").getPath); // @Override public void onCreateBundle savedInstanceState) { // requestWindowFeatureWindow.FEATURE_CUSTOM_TITLE); super.onCreatesavedInstanceState); setContentViewR.layout.activity_yzs_offline_tts); copyFilesFassetsthis,"OfflineTTSModels", Environment.getExternalStorageDirectory).toString)+"/Yupont/UAV/OfflineTTSModels"); //getWindow).setFeatureIntWindow.FEATURE_CUSTOM_TITLE, R.layout.status_bar_main); mTTSText = EditText) findViewByIdR.id.textViewResult); //mTextViewStatus = TextView) findViewByIdR.id.textViewStatus); //mTextViewTip = TextView) findViewByIdR.id.textViewTip); mTTSPlayBtn = Button) findViewByIdR.id.recognizer_btn); mTTSPlayBtn.setEnabledfalse); mTTSPlayBtn.setOnClickListenernew OnClickListener) { @Override public void onClickView arg0) { TTSPlay); } }); // 初始化本地TTS播报 initTts); } /** * 从assets目录中复制整个文件夹内容 * @param context Context 使用CopyFiles类的Activity * @param oldPath String 原文件路径 如:/aa * @param newPath String 复制后路径 如:xx:/bb/cc */ public void copyFilesFassetsContext context, String oldPath, String newPath) { try { String fileNames[] = context.getAssets).listoldPath);//获取assets目录下的所有文件及目录名 if fileNames.length > 0) {//如果是目录 File file = new FilenewPath); file.mkdirs);//如果文件夹不存在,则递归 for String fileName : fileNames) { copyFilesFassetscontext,oldPath + "/" + fileName,newPath+"/"+fileName); } } else {//如果是文件 ifnew FilenewPath).exists)){ return; } InputStream is = context.getAssets).openoldPath); FileOutputStream fos = new FileOutputStreamnew FilenewPath)); byte[] buffer = new byte[1024]; int byteCount=0; whilebyteCount=is.readbuffer))!=-1) {//循环从输入流读取 buffer字节 fos.writebuffer, 0, byteCount);//将读取的输入流写入到输出流 } fos.flush);//刷新缓冲区 is.close); fos.close); } } catch Exception e) { // TODO Auto-generated catch block e.printStackTrace); //如果捕捉到错误则通知UI线程 //MainActivity.handler.sendEmptyMessageCOPY_FALSE); } } /** * 初始化本地离线TTS */ private void initTts) { // 初始化语音合成对象 try { mTTSPlayer = new SpeechSynthesizerthis, Config.appKey, Config.secret); // 设置本地合成 mTTSPlayer.setOptionSpeechConstants.TTS_SERVICE_MODE, SpeechConstants.TTS_SERVICE_MODE_LOCAL); File _FrontendModelFile = new FilemFrontendModel); if !_FrontendModelFile.exists)) { toastMessage"文件:" + mFrontendModel + "不存在,请将assets下相关文件拷贝到SD卡指定目录!"); } File _BackendModelFile = new FilemBackendModel); if !_BackendModelFile.exists)) { toastMessage"文件:" + mBackendModel + "不存在,请将assets下相关文件拷贝到SD卡指定目录!"); } // 设置前端模型 mTTSPlayer.setOptionSpeechConstants.TTS_KEY_FRONTEND_MODEL_PATH, mFrontendModel); // 设置后端模型 mTTSPlayer.setOptionSpeechConstants.TTS_KEY_BACKEND_MODEL_PATH, mBackendModel); // 设置回调监听 mTTSPlayer.setTTSListenernew SpeechSynthesizerListener) { @Override public void onEventint type) { switch type) { case SpeechConstants.TTS_EVENT_INIT: // 初始化成功回调 log_i"onInitFinish"); mTTSPlayBtn.setEnabledtrue); break; case SpeechConstants.TTS_EVENT_SYNTHESIZER_START: // 开始合成回调 log_i"beginSynthesizer"); break; case SpeechConstants.TTS_EVENT_SYNTHESIZER_END: // 合成结束回调 log_i"endSynthesizer"); break; case SpeechConstants.TTS_EVENT_BUFFER_BEGIN: // 开始缓存回调 log_i"beginBuffer"); break; case SpeechConstants.TTS_EVENT_BUFFER_READY: // 缓存完毕回调 log_i"bufferReady"); break; case SpeechConstants.TTS_EVENT_PLAYING_START: // 开始播放回调 log_i"onPlayBegin"); break; case SpeechConstants.TTS_EVENT_PLAYING_END: // 播放完成回调 log_i"onPlayEnd"); setTTSButtonReady); break; case SpeechConstants.TTS_EVENT_PAUSE: // 暂停回调 log_i"pause"); break; case SpeechConstants.TTS_EVENT_RESUME: // 恢复回调 log_i"resume"); break; case SpeechConstants.TTS_EVENT_STOP: // 停止回调 log_i"stop"); break; case SpeechConstants.TTS_EVENT_RELEASE: // 释放资源回调 log_i"release"); break; default: break; } } @Override public void onErrorint type, String errorMSG) { // 语音合成错误回调 log_i"onError"); toastMessageerrorMSG); setTTSButtonReady); } }); // 初始化合成引擎 mTTSPlayer.init""); } catch Exception e) { e.printStackTrace); } } private void TTSPlay) { if !TTS_PLAY_FLAGE) { mTTSPlayer.playTextmTTSText.getText).toString)); setTTSButtonStop); } else { mTTSPlayer.stop); setTTSButtonReady); } } private void setTTSButtonStop) { TTS_PLAY_FLAGE = true; mTTSPlayBtn.setTextR.string.stop_tts); } private void setTTSButtonReady) { mTTSPlayBtn.setTextR.string.start_tts); TTS_PLAY_FLAGE = false; } protected void setTipTextString tip) { mTextViewTip.setTexttip); } protected void setStatusTextString status) { mTextViewStatus.setTextgetStringR.string.lable_status) + "" + status + ")"); } @Override public void onPause) { super.onPause); // 主动停止识别 if mTTSPlayer != null) { mTTSPlayer.stop); } } private void log_iString log) { Log.i"demo", log); } @Override protected void onDestroy) { // 主动释放离线引擎 if mTTSPlayer != null) { mTTSPlayer.releaseSpeechConstants.TTS_RELEASE_ENGINE, null); } super.onDestroy); } private void toastMessageString message) { Toast.makeTextthis, message, Toast.LENGTH_SHORT).show); } }