Android開發(fā)筆記(一百零八)智能語(yǔ)音
來(lái)源:程序員人生 發(fā)布時(shí)間:2016-08-22 08:30:02 閱讀次數(shù):4690次
智能語(yǔ)音技術(shù)
如今愈來(lái)愈多的app用到了語(yǔ)音播報(bào)功能,例如地圖導(dǎo)航、天氣預(yù)報(bào)、文字瀏覽、口語(yǔ)訓(xùn)練等等。語(yǔ)音技術(shù)主要分兩塊,1塊是語(yǔ)音轉(zhuǎn)文字,即語(yǔ)音辨認(rèn);另外一塊是文字轉(zhuǎn)語(yǔ)音,即語(yǔ)音合成。
對(duì)中文來(lái)講,和語(yǔ)音播報(bào)相干的1個(gè)技術(shù)是漢字轉(zhuǎn)拼音,想一想看,拼音本身就是音節(jié)拼讀的標(biāo)記,每一個(gè)音節(jié)對(duì)應(yīng)1段音頻,那末1句的拼音便能用1連串的音頻流合成而來(lái)。漢字轉(zhuǎn)拼音的說(shuō)明參見《Android開發(fā)筆記(8103)多語(yǔ)言支持》。
語(yǔ)音合成通常也簡(jiǎn)稱為TTS,即TextToSpeech(從文本到語(yǔ)言)。語(yǔ)音合成技術(shù)把文字智能地轉(zhuǎn)化為自然語(yǔ)音流,固然為了不機(jī)械合成的呆板和停頓感,語(yǔ)音引擎還得對(duì)語(yǔ)音流進(jìn)行平滑處理,確保輸出的語(yǔ)音音律流暢、感覺自然。
TextToSpeech
Android從1.6開始,就內(nèi)置了語(yǔ)音合成引擎,即“Pico TTS”。該引擎支持英語(yǔ)、法語(yǔ)、德語(yǔ)、意大利語(yǔ),但不支持中文,幸虧Android從4.0開始允許接入第3方的語(yǔ)音引擎,因此只要我們安裝了中文引擎,就可以在代碼中使用中文語(yǔ)音合成服務(wù)。例如,在各大利用市場(chǎng)上下載并安裝科大訊飛+,然后在手機(jī)操作“系統(tǒng)設(shè)置”——“語(yǔ)言和輸入法”——“文字轉(zhuǎn)語(yǔ)音(TTS)輸出”,以下圖所示便可設(shè)置中文的語(yǔ)音引擎:
Android的語(yǔ)音合成控件類名是TextToSpeech,下面是該類經(jīng)常使用的方法說(shuō)明:
構(gòu)造函數(shù) : 第2個(gè)參數(shù)設(shè)置TTSListener對(duì)象,要重寫onInit方法(通常在這里調(diào)用setLanguage方法,由于初始化成功后才能設(shè)置語(yǔ)言)。第3個(gè)參數(shù)設(shè)置語(yǔ)音引擎,默許是系統(tǒng)自帶的pico,要獲得系統(tǒng)支持的所有引擎可調(diào)用getEngines方法。
setLanguage : 設(shè)置語(yǔ)言。英語(yǔ)為L(zhǎng)ocale.ENGLISH;法語(yǔ)為L(zhǎng)ocale.FRENCH;德語(yǔ)為L(zhǎng)ocale.GERMAN;意大利語(yǔ)為L(zhǎng)ocale.ITALIAN;漢語(yǔ)普通話為L(zhǎng)ocale.CHINA(需安裝中文引擎,如科大訊飛+)。該方法的返回值有3個(gè),0表示正常,⑴表示缺失數(shù)據(jù),⑵表示不支持該語(yǔ)言。
setSpeechRate : 設(shè)置語(yǔ)速。1.0正常語(yǔ)速;0.5慢1半的語(yǔ)速;2.0;快1倍的語(yǔ)速。
setPitch : 設(shè)置音調(diào)。1.0正常音調(diào);低于1.0的為低音;高于1.0的為高音。
speak : 開始對(duì)指定文本進(jìn)行語(yǔ)音朗誦。
synthesizeToFile : 把指定文本的朗誦語(yǔ)音輸出到文件。
stop : 停止朗誦。
shutdown : 關(guān)閉語(yǔ)音引擎。
isSpeaking : 判斷是不是在語(yǔ)音朗誦。
getLanguage : 獲得當(dāng)前的語(yǔ)言。
getCurrentEngine : 獲得當(dāng)前的語(yǔ)音引擎。
getEngines : 獲得系統(tǒng)支持的所有語(yǔ)音引擎。
下面是TextToSpeech處理語(yǔ)音合成的代碼示例:
import java.util.List;
import java.util.Locale;
import android.app.Activity;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.EngineInfo;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
public class TTSActivity extends Activity implements OnClickListener {
private TextToSpeech mSpeech;
private EditText et_tts_resource;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tts);
et_tts_resource = (EditText) findViewById(R.id.et_tts_resource);
Button btn_tts_start = (Button) findViewById(R.id.btn_tts_start);
btn_tts_start.setOnClickListener(this);
initLanguageSpinner();
mSpeech = new TextToSpeech(TTSActivity.this, new TTSListener());
}
private void initLanguageSpinner() {
ArrayAdapterstarAdapter = new ArrayAdapter(this,
R.layout.spinner_item, mLangArray);
starAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
Spinner sp = (Spinner) findViewById(R.id.sp_tts_language);
sp.setPrompt("請(qǐng)選擇語(yǔ)言");
sp.setAdapter(starAdapter);
sp.setOnItemSelectedListener(new LanguageSelectedListener());
sp.setSelection(0);
}
private String[] mEngineArray;
private int mEngine;
private void initEngineSpinner() {
mEngineArray = new String[mEngineList.size()];
for(int i=0; i arg0, View arg1, int arg2, long arg3) {
mLanguage = arg2;
if (mLocaleArray[mLanguage]==Locale.SIMPLIFIED_CHINESE
|| mLocaleArray[mLanguage]==Locale.TRADITIONAL_CHINESE) {
et_tts_resource.setText(mTextCN);
} else {
et_tts_resource.setText(mTextEN);
}
if (mEngineList != null) {
resetLanguage();
}
}
public void onNothingSelected(AdapterView arg0) {
}
}
private class EngineSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) {
mEngine = arg2;
recycleSpeech();
mSpeech = new TextToSpeech(TTSActivity.this, new TTSListener(),
mEngineList.get(mEngine).name);
}
public void onNothingSelected(AdapterView arg0) {
}
}
private void resetLanguage() {
int result = mSpeech.setLanguage(mLocaleArray[mLanguage]);
//如果打印為⑵,說(shuō)明不支持這類語(yǔ)言;⑴說(shuō)明缺失數(shù)據(jù)
Toast.makeText(TTSActivity.this, "您選擇的是"+mLangArray[mLanguage]
+",result="+result, Toast.LENGTH_SHORT).show();
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_tts_start) {
String content = et_tts_resource.getText().toString();
int result = mSpeech.speak(content, TextToSpeech.QUEUE_FLUSH, null);
Toast.makeText(TTSActivity.this, "speak result="+result, Toast.LENGTH_SHORT).show();
}
}
private ListmEngineList;
private class TTSListener implements OnInitListener {
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
if (mEngineList == null) {
mEngineList = mSpeech.getEngines();
initEngineSpinner();
} else {
resetLanguage();
}
}
}
}
}
科大訊飛語(yǔ)音
前面提到,只要安裝了中文引擎,便可在TextToSpeech中使用中文語(yǔ)音;可是我們沒(méi)法要求用戶再額外下載1個(gè)app,正確的做法是在自己app中集成語(yǔ)音sdk。目前中文環(huán)境常見的語(yǔ)音sdk主要有科大訊飛、百度語(yǔ)音、捷通華聲、云知聲等等,開發(fā)者可自行選擇1個(gè)。
sdk集成
科大訊飛語(yǔ)音sdk的集成步驟以下:
1、導(dǎo)入sdk包到libs目錄,包括libmsc.so、Msc.jar、Sunflower.jar;
2、到訊飛網(wǎng)站注冊(cè)并創(chuàng)建新利用,取得appid;
3、自定義1個(gè)Application類,在onCreate函數(shù)中加入下面代碼,注意appid值為第2步申請(qǐng)到的id:
SpeechUtility.createUtility(MainApplication.this, "appid=5763c4cf");
4、在AndroidManifest.xml中加入必要的權(quán)限,和自定義的Application類;
5、根據(jù)demo工程編寫代碼與布局文件;
6、如果使用了RecognizerDialog控件,則要把demo工程assets目錄下的文件原樣拷過(guò)來(lái);
7、在混淆打包的時(shí)候需要添加-keep class com.iflytek.**{*;},
語(yǔ)音辨認(rèn)
科大訊飛的語(yǔ)音辨認(rèn)用的是SpeechRecognizer類,主要方法以下:
createRecognizer : 創(chuàng)建語(yǔ)音辨認(rèn)對(duì)象。
setParameter : 設(shè)置語(yǔ)音辨認(rèn)的參數(shù)。經(jīng)常使用參數(shù)包括:
--SpeechConstant.ENGINE_TYPE : 設(shè)置聽寫引擎。TYPE_LOCAL表示本地,TYPE_CLOUD表示云端,TYPE_MIX表示混合。
--SpeechConstant.RESULT_TYPE : 設(shè)置返回結(jié)果格式。json表示json格式。
--SpeechConstant.LANGUAGE : 設(shè)置語(yǔ)言。zh_cn表示中文,en_us表示英文。
--SpeechConstant.ACCENT : 設(shè)置方言。mandarin表示普通話,cantonese表示粵語(yǔ),henanese表示河南話。
--SpeechConstant.VAD_BOS : 設(shè)置語(yǔ)音前端點(diǎn):靜音超時(shí)時(shí)間,即用戶多長(zhǎng)時(shí)間不說(shuō)話則當(dāng)作超時(shí)處理。
--SpeechConstant.VAD_EOS : 設(shè)置語(yǔ)音后端點(diǎn):后端點(diǎn)靜音檢測(cè)時(shí)間,即用戶停止說(shuō)話多長(zhǎng)時(shí)間內(nèi)即認(rèn)為不再輸入,自動(dòng)停止錄音。
--SpeechConstant.ASR_PTT : 設(shè)置標(biāo)點(diǎn)符號(hào)。0表示返回結(jié)果無(wú)標(biāo)點(diǎn),1表示返回結(jié)果有標(biāo)點(diǎn)。
--SpeechConstant.AUDIO_FORMAT : 設(shè)置音頻的保存格式。
--SpeechConstant.ASR_AUDIO_PATH : 設(shè)置音頻的保存路徑。
--SpeechConstant.AUDIO_SOURCE : 設(shè)置音頻的來(lái)源。⑴表示音頻流,與writeAudio配合使用;⑵表示外部文件,同時(shí)設(shè)置ASR_SOURCE_PATH指定文件路徑。
--SpeechConstant.ASR_SOURCE_PATH : 設(shè)置外部音頻文件的路徑。
startListening : 開始監(jiān)聽語(yǔ)音輸入。參數(shù)為RecognizerListener對(duì)象,該對(duì)象需重寫的方法包括:
--onBeginOfSpeech : 內(nèi)部錄音機(jī)已準(zhǔn)備好了,用戶可以開始語(yǔ)音輸入。
--onError : 毛病碼:10118(您沒(méi)有說(shuō)話),多是錄音機(jī)權(quán)限被禁,需要提示用戶打開利用的錄音權(quán)限。
--onEndOfSpeech : 檢測(cè)到了語(yǔ)音的尾端點(diǎn),已進(jìn)入辨認(rèn)進(jìn)程,不再接受語(yǔ)音輸入。
--onResult : 辨認(rèn)結(jié)束,返回結(jié)果串。
--onVolumeChanged : 語(yǔ)音輸入進(jìn)程中的音量大小變化。
--onEvent : 事件處理,1般是業(yè)務(wù)出錯(cuò)等異常。
stopListening : 結(jié)束監(jiān)聽語(yǔ)音。
writeAudio : 把指定的音頻流作為語(yǔ)音輸入。
cancel : 取消監(jiān)聽。
destroy : 回收語(yǔ)音辨認(rèn)對(duì)象。
下面是科大訊飛語(yǔ)音辨認(rèn)的運(yùn)行截圖:
下面是科大訊飛語(yǔ)音辨認(rèn)的代碼例子:
import java.util.HashMap;
import java.util.LinkedHashMap;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
import com.example.exmvoice.R;
import com.example.exmvoice.SettingsActivity;
import com.example.exmvoice.xunfei.util.FucUtil;
import com.example.exmvoice.xunfei.util.JsonParser;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.ui.RecognizerDialog;
import com.iflytek.cloud.ui.RecognizerDialogListener;
public class XFRecognizeActivity extends Activity implements OnClickListener {
private final static String TAG = XFRecognizeActivity.class.getSimpleName();
// 語(yǔ)音聽寫對(duì)象
private SpeechRecognizer mRecognize;
// 語(yǔ)音聽寫UI
private RecognizerDialog mRecognizeDialog;
// 用HashMap存儲(chǔ)聽寫結(jié)果
private HashMap mRecognizeResults = new LinkedHashMap();
private EditText mResultText;
private SharedPreferences mSharedPreferences;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_xunfei_recognize);
mResultText = ((EditText) findViewById(R.id.xf_recognize_text));
findViewById(R.id.xf_recognize_start).setOnClickListener(this);
findViewById(R.id.xf_recognize_stop).setOnClickListener(this);
findViewById(R.id.xf_recognize_cancel).setOnClickListener(this);
findViewById(R.id.xf_recognize_stream).setOnClickListener(this);
findViewById(R.id.xf_recognize_setting).setOnClickListener(this);
mSharedPreferences = getSharedPreferences(SettingsActivity.PREFER_NAME, Activity.MODE_PRIVATE);
// 初始化辨認(rèn)無(wú)UI辨認(rèn)對(duì)象,使用SpeechRecognizer對(duì)象,可根據(jù)回調(diào)消息自定義界面;
mRecognize = SpeechRecognizer.createRecognizer(this, mInitListener);
// 初始化聽寫Dialog,如果只使用有UI聽寫功能,無(wú)需創(chuàng)建SpeechRecognizer
// 使用UI聽寫功能,請(qǐng)將assets下文件拷貝到項(xiàng)目中
mRecognizeDialog = new RecognizerDialog(this, mInitListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 退出時(shí)釋放連接
mRecognize.cancel();
mRecognize.destroy();
}
@Override
public void onClick(View v) {
int ret = 0; // 函數(shù)調(diào)用返回值
int resid = v.getId();
if (resid == R.id.xf_recognize_setting) { // 進(jìn)入?yún)?shù)設(shè)置頁(yè)面
Intent intent = new Intent(this, SettingsActivity.class);
intent.putExtra("type", SettingsActivity.XF_RECOGNIZE);
startActivity(intent);
} else if (resid == R.id.xf_recognize_start) { // 開始聽寫。如何判斷1次聽寫結(jié)束:OnResult isLast=true 或 onError
mResultText.setText(null);// 清空顯示內(nèi)容
mRecognizeResults.clear();
// 設(shè)置參數(shù)
resetParam();
boolean isShowDialog = mSharedPreferences.getBoolean("show_dialog", true);
if (isShowDialog) {
// 顯示聽寫對(duì)話框
mRecognizeDialog.setListener(mRecognizeDialogListener);
mRecognizeDialog.show();
showTip("請(qǐng)開始說(shuō)話………");
} else {
// 不顯示聽寫對(duì)話框
ret = mRecognize.startListening(mRecognizeListener);
if (ret != ErrorCode.SUCCESS) {
showTip("聽寫失敗,毛病碼:" + ret);
} else {
showTip("請(qǐng)開始說(shuō)話…");
}
}
} else if (resid == R.id.xf_recognize_stop) { // 停止聽寫
mRecognize.stopListening();
showTip("停止聽寫");
} else if (resid == R.id.xf_recognize_cancel) { // 取消聽寫
mRecognize.cancel();
showTip("取消聽寫");
} else if (resid == R.id.xf_recognize_stream) { // 音頻流辨認(rèn)
mResultText.setText(null);// 清空顯示內(nèi)容
mRecognizeResults.clear();
// 設(shè)置參數(shù)
resetParam();
// 設(shè)置音頻來(lái)源為外部文件
mRecognize.setParameter(SpeechConstant.AUDIO_SOURCE, "⑴");
// 也能夠像以下這樣直接設(shè)置音頻文件路徑辨認(rèn)(要求設(shè)置文件在sdcard上的全路徑):
// mRecognize.setParameter(SpeechConstant.AUDIO_SOURCE, "⑵");
// mRecognize.setParameter(SpeechConstant.ASR_SOURCE_PATH, "sdcard/XXX/XXX.pcm");
ret = mRecognize.startListening(mRecognizeListener);
if (ret != ErrorCode.SUCCESS) {
showTip("辨認(rèn)失敗,毛病碼:" + ret);
} else {
byte[] audioData = FucUtil.readAudioFile(this, "retcognize_est.wav");
if (null != audioData) {
showTip("開始音頻流辨認(rèn)");
// 1次(也能夠分屢次)寫入音頻文件數(shù)據(jù),數(shù)據(jù)格式必須是采樣率為8KHz或16KHz(本地辨認(rèn)只支持16K采樣率,云端都支持),位長(zhǎng)16bit,單聲道的wav或pcm
// 寫入8KHz采樣的音頻時(shí),必須先調(diào)用setParameter(SpeechConstant.SAMPLE_RATE, "8000")設(shè)置正確的采樣率
// 注:當(dāng)音頻太長(zhǎng),靜音部份時(shí)長(zhǎng)超過(guò)VAD_EOS將致使靜音后臉部分不能辨認(rèn)
mRecognize.writeAudio(audioData, 0, audioData.length);
mRecognize.stopListening();
} else {
mRecognize.cancel();
showTip("讀取音頻流失敗");
}
}
}
}
//初始化監(jiān)聽器
private InitListener mInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
showTip("初始化失敗,毛病碼:" + code);
}
}
};
//聽寫監(jiān)聽器
private RecognizerListener mRecognizeListener = new RecognizerListener() {
@Override
public void onBeginOfSpeech() {
// 此回調(diào)表示:sdk內(nèi)部錄音機(jī)已準(zhǔn)備好了,用戶可以開始語(yǔ)音輸入
showTip("開始說(shuō)話");
}
@Override
public void onError(SpeechError error) {
// 毛病碼:10118(您沒(méi)有說(shuō)話),多是錄音機(jī)權(quán)限被禁,需要提示用戶打開利用的錄音權(quán)限。
// 如果使用本地功能(語(yǔ)記)需要提示用戶開啟語(yǔ)記的錄音權(quán)限。
showTip(error.getPlainDescription(true));
}
@Override
public void onEndOfSpeech() {
// 此回調(diào)表示:檢測(cè)到了語(yǔ)音的尾端點(diǎn),已進(jìn)入辨認(rèn)進(jìn)程,不再接受語(yǔ)音輸入
showTip("結(jié)束說(shuō)話");
}
@Override
public void onResult(RecognizerResult results, boolean isLast) {
Log.d(TAG, results.getResultString());
printResult(results);
if (isLast) {
// TODO 最后的結(jié)果
}
}
@Override
public void onVolumeChanged(int volume, byte[] data) {
showTip("當(dāng)前正在說(shuō)話,音量大小:" + volume);
Log.d(TAG, "返回音頻數(shù)據(jù):"+data.length);
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
// 以下代碼用于獲得與云真?zhèn)€會(huì)話id,當(dāng)業(yè)務(wù)出錯(cuò)時(shí)將會(huì)話id提供給技術(shù)支持人員,可用于查詢會(huì)話日志,定位出錯(cuò)緣由
// 若使用本地能力,會(huì)話id為null
// if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
// }
}
};
private void printResult(RecognizerResult results) {
String text = JsonParser.parseIatResult(results.getResultString());
String sn = null;
try {
JSONObject resultJson = new JSONObject(results.getResultString());
sn = resultJson.optString("sn");
} catch (JSONException e) {
e.printStackTrace();
return;
}
mRecognizeResults.put(sn, text);
StringBuffer resultBuffer = new StringBuffer();
for (String key : mRecognizeResults.keySet()) {
resultBuffer.append(mRecognizeResults.get(key));
}
mResultText.setText(resultBuffer.toString());
mResultText.setSelection(mResultText.length());
}
//聽寫UI監(jiān)聽器
private RecognizerDialogListener mRecognizeDialogListener = new RecognizerDialogListener() {
public void onResult(RecognizerResult results, boolean isLast) {
printResult(results);
}
//辨認(rèn)回調(diào)毛病
public void onError(SpeechError error) {
showTip(error.getPlainDescription(true));
}
};
private void showTip(final String str) {
Toast.makeText(this, str, Toast.LENGTH_LONG).show();
}
//參數(shù)設(shè)置
public void resetParam() {
// 清空參數(shù)
mRecognize.setParameter(SpeechConstant.PARAMS, null);
// 設(shè)置聽寫引擎。TYPE_LOCAL表示本地,TYPE_CLOUD表示云端,TYPE_MIX 表示混合
mRecognize.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
// 設(shè)置返回結(jié)果格式
mRecognize.setParameter(SpeechConstant.RESULT_TYPE, "json");
String lag = mSharedPreferences.getString("recognize_language_preference", "mandarin");
if (lag.equals("en_us")) { // 設(shè)置語(yǔ)言
mRecognize.setParameter(SpeechConstant.LANGUAGE, "en_us");
} else {
mRecognize.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
// 設(shè)置語(yǔ)言區(qū)域
mRecognize.setParameter(SpeechConstant.ACCENT, lag);
}
// 設(shè)置語(yǔ)音前端點(diǎn):靜音超時(shí)時(shí)間,即用戶多長(zhǎng)時(shí)間不說(shuō)話則當(dāng)作超時(shí)處理
mRecognize.setParameter(SpeechConstant.VAD_BOS, mSharedPreferences.getString("recognize_vadbos_preference", "4000"));
// 設(shè)置語(yǔ)音后端點(diǎn):后端點(diǎn)靜音檢測(cè)時(shí)間,即用戶停止說(shuō)話多長(zhǎng)時(shí)間內(nèi)即認(rèn)為不再輸入, 自動(dòng)停止錄音
mRecognize.setParameter(SpeechConstant.VAD_EOS, mSharedPreferences.getString("recognize_vadeos_preference", "1000"));
// 設(shè)置標(biāo)點(diǎn)符號(hào),設(shè)置為"0"返回結(jié)果無(wú)標(biāo)點(diǎn),設(shè)置為"1"返回結(jié)果有標(biāo)點(diǎn)
mRecognize.setParameter(SpeechConstant.ASR_PTT, mSharedPreferences.getString("recognize_punc_preference", "1"));
// 設(shè)置音頻保存路徑,保存音頻格式支持pcm、wav,設(shè)置路徑為sd卡請(qǐng)注意WRITE_EXTERNAL_STORAGE權(quán)限
// 注:AUDIO_FORMAT參數(shù)語(yǔ)記需要更新版本才能生效
mRecognize.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mRecognize.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/recognize.wav");
}
}
語(yǔ)音合成
科大訊飛的語(yǔ)音合成用的是SpeechSynthesizer類,主要方法以下:
createSynthesizer : 創(chuàng)建語(yǔ)音合成對(duì)象。
setParameter : 設(shè)置語(yǔ)音合成的參數(shù)。經(jīng)常使用參數(shù)包括:
--SpeechConstant.ENGINE_TYPE : 設(shè)置合成引擎。TYPE_LOCAL表示本地,TYPE_CLOUD表示云端,TYPE_MIX表示混合。
--SpeechConstant.VOICE_NAME : 設(shè)置朗誦者。默許xiaoyan(女青年,普通話)
--SpeechConstant.SPEED : 設(shè)置朗誦的語(yǔ)速。
--SpeechConstant.PITCH : 設(shè)置朗誦的音調(diào)。
--SpeechConstant.VOLUME : 設(shè)置朗誦的音量。
--SpeechConstant.STREAM_TYPE : 設(shè)置音頻流類型。默許是音樂(lè)。
--SpeechConstant.KEY_REQUEST_FOCUS : 設(shè)置是不是在播放合成音頻時(shí)打斷音樂(lè)播放,默許為true。
--SpeechConstant.AUDIO_FORMAT : 設(shè)置音頻的保存格式。
--SpeechConstant.TTS_AUDIO_PATH : 設(shè)置音頻的保存路徑。
startSpeaking : 開始語(yǔ)音朗誦。參數(shù)為SynthesizerListener對(duì)象,該對(duì)象需重寫的方法包括:
--onSpeakBegin : 朗誦開始。
--onSpeakPaused : 朗誦暫停。
--onSpeakResumed : 朗誦恢復(fù)。
--onBufferProgress : 合成進(jìn)度變化。
--onSpeakProgress : 朗誦進(jìn)度變化。
--onCompleted : 朗誦完成。
--onEvent : 事件處理,1般是業(yè)務(wù)出錯(cuò)等異常。
synthesizeToUri : 只保存音頻不進(jìn)行播放,調(diào)用該接口就不能調(diào)用startSpeaking。第1個(gè)參數(shù)是要合成的文本,第2個(gè)參數(shù)時(shí)要保存的音頻全路徑,第3個(gè)參數(shù)是SynthesizerListener回調(diào)接口。
pauseSpeaking : 暫停朗誦。
resumeSpeaking : 恢復(fù)朗誦。
stopSpeaking : 停止朗誦。
destroy : 回收語(yǔ)音合成對(duì)象。
下面是科大訊飛語(yǔ)音合成的代碼例子:
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
import com.example.exmvoice.R;
import com.example.exmvoice.SettingsActivity;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechSynthesizer;
import com.iflytek.cloud.SynthesizerListener;
public class XFComposeActivity extends Activity implements OnClickListener {
private static String TAG = XFComposeActivity.class.getSimpleName();
// 語(yǔ)音合成對(duì)象
private SpeechSynthesizer mCompose;
// 默許發(fā)音人
private String voicer = "xiaoyan";
private String[] mCloudVoicersEntries;
private String[] mCloudVoicersValue ;
// 緩沖進(jìn)度
private int mPercentForBuffering = 0;
// 播放進(jìn)度
private int mPercentForPlaying = 0;
private EditText mResourceText;
private SharedPreferences mSharedPreferences;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_xunfei_compose);
mResourceText = ((EditText) findViewById(R.id.xf_compose_text));
findViewById(R.id.xf_compose_play).setOnClickListener(this);
findViewById(R.id.xf_compose_cancel).setOnClickListener(this);
findViewById(R.id.xf_compose_pause).setOnClickListener(this);
findViewById(R.id.xf_compose_resume).setOnClickListener(this);
findViewById(R.id.xf_compose_setting).setOnClickListener(this);
findViewById(R.id.xf_compose_person).setOnClickListener(this);
mSharedPreferences = getSharedPreferences(SettingsActivity.PREFER_NAME, MODE_PRIVATE);
// 初始化合成對(duì)象
mCompose = SpeechSynthesizer.createSynthesizer(this, mComposeInitListener);
// 云端發(fā)音人名稱列表
mCloudVoicersEntries = getResources().getStringArray(R.array.voicer_cloud_entries);
mCloudVoicersValue = getResources().getStringArray(R.array.voicer_cloud_values);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 退出時(shí)釋放連接
mCompose.stopSpeaking();
mCompose.destroy();
}
@Override
public void onClick(View v) {
int resid = v.getId();
if (resid == R.id.xf_compose_setting) {
Intent intent = new Intent(this, SettingsActivity.class);
intent.putExtra("type", SettingsActivity.XF_COMPOSE);
startActivity(intent);
} else if (resid == R.id.xf_compose_play) { // 開始合成
//收到onCompleted 回調(diào)時(shí),合成結(jié)束、生成合成音頻。合成的音頻格式:只支持pcm格式
String text = mResourceText.getText().toString();
// 設(shè)置參數(shù)
setParam();
int code = mCompose.startSpeaking(text, mComposeListener);
if (code != ErrorCode.SUCCESS) {
showTip("語(yǔ)音合成失敗,毛病碼: " + code);
}
// //只保存音頻不進(jìn)行播放接口,調(diào)用此接口請(qǐng)注釋startSpeaking接口
// //text:要合成的文本,uri:需要保存的音頻全路徑,listener:回調(diào)接口
// String path = Environment.getExternalStorageDirectory()+"/compose.pcm";
// int code = mCompose.synthesizeToUri(text, path, mComposeListener);
} else if (resid == R.id.xf_compose_cancel) { // 取消合成
mCompose.stopSpeaking();
} else if (resid == R.id.xf_compose_pause) { // 暫停播放
mCompose.pauseSpeaking();
} else if (resid == R.id.xf_compose_resume) { // 繼續(xù)播放
mCompose.resumeSpeaking();
} else if (resid == R.id.xf_compose_person) { // 選擇發(fā)音人
showPresonSelectDialog();
}
}
private int selectedNum = 0;
//發(fā)音人選擇
private void showPresonSelectDialog() {
new AlertDialog.Builder(this).setTitle("在線合成發(fā)音人選項(xiàng)")
.setSingleChoiceItems(mCloudVoicersEntries, // 單選框有幾項(xiàng),各是甚么名字
selectedNum, // 默許的選項(xiàng)
new DialogInterface.OnClickListener() { // 點(diǎn)擊單選框后的處理
public void onClick(DialogInterface dialog, int which) { // 點(diǎn)擊了哪1項(xiàng)
voicer = mCloudVoicersValue[which];
if ("catherine".equals(voicer) || "henry".equals(voicer) || "vimary".equals(voicer)
|| "Mariane".equals(voicer) || "Allabent".equals(voicer) || "Gabriela".equals(voicer) || "Abha".equals(voicer) || "XiaoYun".equals(voicer)) {
mResourceText.setText(R.string.compose_source_en);
} else {
mResourceText.setText(R.string.compose_source);
}
selectedNum = which;
dialog.dismiss();
}
}).show();
}
//初始化監(jiān)聽
private InitListener mComposeInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "InitListener init() code = " + code);
if (code != ErrorCode.SUCCESS) {
showTip("初始化失敗,毛病碼:"+code);
} else {
// 初始化成功,以后可以調(diào)用startSpeaking方法
// 注:有的開發(fā)者在onCreate方法中創(chuàng)建完合成對(duì)象以后馬上就調(diào)用startSpeaking進(jìn)行合成,
// 正確的做法是將onCreate中的startSpeaking調(diào)用移至這里
}
}
};
//合成回調(diào)監(jiān)聽
private SynthesizerListener mComposeListener = new SynthesizerListener() {
@Override
public void onSpeakBegin() {
showTip("開始播放");
}
@Override
public void onSpeakPaused() {
showTip("暫停播放");
}
@Override
public void onSpeakResumed() {
showTip("繼續(xù)播放");
}
@Override
public void onBufferProgress(int percent, int beginPos, int endPos, String info) {
// 合成進(jìn)度
mPercentForBuffering = percent;
showTip(String.format(getString(R.string.xf_compose_toast_format),
mPercentForBuffering, mPercentForPlaying));
}
@Override
public void onSpeakProgress(int percent, int beginPos, int endPos) {
// 播放進(jìn)度
mPercentForPlaying = percent;
showTip(String.format(getString(R.string.xf_compose_toast_format),
mPercentForBuffering, mPercentForPlaying));
}
@Override
public void onCompleted(SpeechError error) {
if (error == null) {
showTip("播放完成");
} else if (error != null) {
showTip(error.getPlainDescription(true));
}
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
// 以下代碼用于獲得與云真?zhèn)€會(huì)話id,當(dāng)業(yè)務(wù)出錯(cuò)時(shí)將會(huì)話id提供給技術(shù)支持人員,可用于查詢會(huì)話日志,定位出錯(cuò)緣由
// 若使用本地能力,會(huì)話id為null
// if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
// }
}
};
private void showTip(final String str) {
Toast.makeText(this, str, Toast.LENGTH_LONG).show();
}
//參數(shù)設(shè)置
private void setParam(){
// 清空參數(shù)
mCompose.setParameter(SpeechConstant.PARAMS, null);
// 根據(jù)合成引擎設(shè)置相應(yīng)參數(shù)
mCompose.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
// 設(shè)置在線合成發(fā)音人
mCompose.setParameter(SpeechConstant.VOICE_NAME, voicer);
//設(shè)置合成語(yǔ)速
mCompose.setParameter(SpeechConstant.SPEED, mSharedPreferences.getString("speed_preference", "50"));
//設(shè)置合成音調(diào)
mCompose.setParameter(SpeechConstant.PITCH, mSharedPreferences.getString("pitch_preference", "50"));
//設(shè)置合成音量
mCompose.setParameter(SpeechConstant.VOLUME, mSharedPreferences.getString("volume_preference", "50"));
//設(shè)置播放器音頻流類型
mCompose.setParameter(SpeechConstant.STREAM_TYPE, mSharedPreferences.getString("stream_preference", "3"));
// 設(shè)置播放合成音頻打斷音樂(lè)播放,默許為true
mCompose.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");
// 設(shè)置音頻保存路徑,保存音頻格式支持pcm、wav,設(shè)置路徑為sd卡請(qǐng)注意WRITE_EXTERNAL_STORAGE權(quán)限
// 注:AUDIO_FORMAT參數(shù)語(yǔ)記需要更新版本才能生效
mCompose.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mCompose.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/compose.wav");
}
}
PreferenceFragment
科大訊飛的demo工程在設(shè)置頁(yè)面使用了PreferenceActivity,看起來(lái)代碼簡(jiǎn)練了許多,正好我們之前還沒(méi)接觸Preference的實(shí)際應(yīng)用,現(xiàn)在就來(lái)研究研究。看最新的sdk源碼,提示PreferenceActivity的許多方法都過(guò)時(shí)了,官方建議使用PreferenceFragment來(lái)代替。
下面是PreferenceFragment的經(jīng)常使用方法說(shuō)明
getPreferenceManager : 取得參數(shù)管理的PreferenceManager對(duì)象。該對(duì)象主要有兩個(gè)方法:getDefaultSharedPreferences返回系統(tǒng)默許的同享參數(shù)對(duì)象;setSharedPreferencesName為設(shè)置指定名稱的同享參數(shù);有關(guān)同享參數(shù)的說(shuō)明參見《Android開發(fā)筆記(2109)使用SharedPreferences存取數(shù)據(jù)》。
addPreferencesFromResource : 從xml資源文件中添加參數(shù)界面。
findPreference : 從xml資源文件中獲得指定id的元素。EditTextPreference表示該項(xiàng)參數(shù)為文本輸入;ListPreference表示該項(xiàng)參數(shù)為列表選擇;CheckBoxPreference表示該項(xiàng)參數(shù)為復(fù)選框勾選;PreferenceScreen是xml文件的根節(jié)點(diǎn)。
setPreferenceScreen : 設(shè)置參數(shù)屏幕(1般不使用)。
下面是PreferenceFragment的代碼示例:
import com.example.exmvoice.R;
import com.example.exmvoice.SettingsActivity;
import com.example.exmvoice.xunfei.util.SettingTextWatcher;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceFragment;
//語(yǔ)音辨認(rèn)設(shè)置界面
public class XFRecognizeSettingsFragment extends PreferenceFragment implements OnPreferenceChangeListener {
private EditTextPreference mVadbosPreference;
private EditTextPreference mVadeosPreference;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPreferenceManager().setSharedPreferencesName(SettingsActivity.PREFER_NAME);
addPreferencesFromResource(R.xml.xf_recognize_setting);
mVadbosPreference = (EditTextPreference) findPreference("recognize_vadbos_preference");
mVadbosPreference.getEditText().addTextChangedListener(
new SettingTextWatcher(getActivity(),mVadbosPreference,0,10000));
mVadeosPreference = (EditTextPreference) findPreference("recognize_vadeos_preference");
mVadeosPreference.getEditText().addTextChangedListener(
new SettingTextWatcher(getActivity(),mVadeosPreference,0,10000));
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return true;
}
}
下面是PreferenceFragment的布局示例:
百度語(yǔ)音
sdk集成
百度語(yǔ)音sdk的集成比較麻煩,主要步驟以下:
1、導(dǎo)入sdk包到libs目錄,包括語(yǔ)音辨認(rèn)和語(yǔ)音合成兩種庫(kù)
語(yǔ)音辨認(rèn)的庫(kù)有:
libbdEASRAndroid.so
libBDVoiceRecognitionClient_MFE_V1.so
VoiceRecognition⑵.0.1.jar
語(yǔ)音合成的庫(kù)有:
libbd_etts.so
libBDSpeechDecoder_V1.so
libbdtts.so
libgnustl_shared.so
com.baidu.tts_2.2.7.20160616_81bcb05_release.jar
galaxy-v2.0.jar
2、到百度注冊(cè)并創(chuàng)建新利用,取得APP_ID、API_KEY、SECRET_KEY;
3、在AndroidManifest.xml中加入必要的權(quán)限,和meta-data、service和activity設(shè)置,注意meta-data的參數(shù)值為第2步取得的APP_ID、API_KEY、SECRET_KEY。詳細(xì)的xml部份例子以下:
4、demo工程中assets目錄下的文件原樣拷過(guò)來(lái);
5、demo工程中res目錄下的drawable、layout、raw下面的資源原樣拷過(guò)來(lái);
6、根據(jù)demo工程編寫代碼與布局文件,注意在語(yǔ)音合成初始化時(shí),setAppId和setApiKey要把第2步取得的APP_ID、API_KEY、SECRET_KEY給填進(jìn)去;
下面是我在集成百度語(yǔ)音時(shí)遇到的幾個(gè)問(wèn)題及處理辦法:
1、語(yǔ)音合成運(yùn)行報(bào)錯(cuò),日志提示:
06⑵1 16:31:37.118: W/System.err(4595): Caused by: java.util.concurrent.ExecutionException: java.lang.Exception: #5, Other client side errors. request token failed, error: unknown, desc: unknown client id, used AK=this/this
緣由:setAppId和setApiKey方法沒(méi)有設(shè)置appkey。
2、語(yǔ)音合成運(yùn)行報(bào)錯(cuò),日志提示:
06⑵1 16:32:57.830: W/System.err(4769): java.lang.Exception: #5, Other client side errors. The AK can only be used for demo. AK=8MAxI5o7VjKSZOKeBzS4XtxO/Ge5GXVdGQpaxOmLzc8fOM8309ATCz9Ha
緣由:setAppId和setApiKey方法設(shè)置的值不對(duì),可能使用了demo的appkey,而不是自己申請(qǐng)的appkey。
3、語(yǔ)音合成運(yùn)行報(bào)錯(cuò),日志提示:
06⑵2 11:32:00.998: W/MainActivity(31928): onError error=(⑴5)(⑴5)online synthesize get was timeout[(cause)java.util.concurrent.TimeoutException]--utteranceId=0
緣由:網(wǎng)絡(luò)連不上,請(qǐng)檢查網(wǎng)絡(luò)連接。如果使用摹擬器測(cè)試,最好重啟摹擬器再試試
4、調(diào)用loadEnglishModel方法加載英語(yǔ)模塊時(shí),返回值是⑴1加載失敗(正常要返回5)。
緣由:加載離線英文資源需要在初始化時(shí)采取混合模式TtsMode.MIX,不可采取在線模式TtsMode.ONLINE。
語(yǔ)音辨認(rèn)
百度語(yǔ)音辨認(rèn)用的是SpeechRecognizer類,主要方法以下:
createSpeechRecognizer : 創(chuàng)建語(yǔ)音辨認(rèn)對(duì)象。
setRecognitionListener : 設(shè)置辨認(rèn)監(jiān)聽器。該監(jiān)聽器需重寫的方法包括:
--onReadyForSpeech : 準(zhǔn)備就緒,可以開始說(shuō)話
--onBeginningOfSpeech : 檢測(cè)到用戶已開始說(shuō)話
--onRmsChanged : 1般不用途理。
--onBufferReceived : 1般不用途理。
--onEndOfSpeech : 檢測(cè)到用戶已停止說(shuō)話
--onError : 辨認(rèn)出錯(cuò)。
--onResults : 辨認(rèn)完成,返回結(jié)果串。
--onPartialResults : 返回部份的辨認(rèn)結(jié)果。
--onEvent : 事件處理,1般是業(yè)務(wù)出錯(cuò)等異常。
startListening : 開始監(jiān)聽語(yǔ)音。
stopListening : 結(jié)束監(jiān)聽語(yǔ)音。
cancel : 取消監(jiān)聽。
destroy : 回收語(yǔ)音辨認(rèn)對(duì)象。
注意第1次辨認(rèn)時(shí)要跳到com.baidu.action.RECOGNIZE_SPEECH,后面才能調(diào)用startListening方法。辨認(rèn)時(shí)的參數(shù)設(shè)置是在activity跳轉(zhuǎn)時(shí)傳入的,經(jīng)常使用參數(shù)包括:
--Constant.EXTRA_LANGUAGE : 說(shuō)話的語(yǔ)言。cmn-Hans-CN表示普通話,sichuan-Hans-CN表示4川話,yue-Hans-CN表示粵語(yǔ),en-GB表示英語(yǔ)。
--Constant.EXTRA_NLU : 是不是開啟語(yǔ)義解析。
--Constant.EXTRA_VAD : 語(yǔ)音邊界檢測(cè)。search表示適用輸入搜索關(guān)鍵字(默許值),input表示適用于輸入短信、微博等長(zhǎng)句輸入。
--Constant.EXTRA_PROP : 語(yǔ)音的行業(yè)領(lǐng)域。
下面是百度語(yǔ)音辨認(rèn)的運(yùn)行截圖:
下面是百度語(yǔ)音辨認(rèn)的代碼例子:
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.speech.RecognitionListener;
import android.speech.SpeechRecognizer;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;
import com.baidu.speech.VoiceRecognitionService;
import com.example.exmvoice.R;
import com.example.exmvoice.SettingsActivity;
import com.example.exmvoice.baidu.setting.Constant;
import org.json.JSONObject;
import java.util.*;
public class BDRecognizeActivity extends Activity implements OnClickListener {
private static final String TAG = BDRecognizeActivity.class.getSimpleName();
private static final int REQUEST_UI = 1;
private TextView txtResult;
private TextView txtLog;
private Button btnStart;
public static final int STATUS_None = 0;
public static final int STATUS_WaitingReady = 2;
public static final int STATUS_Ready = 3;
public static final int STATUS_Speaking = 4;
public static final int STATUS_Recognition = 5;
private SpeechRecognizer speechRecognizer;
private int status = STATUS_None;
private long speechEndTime = ⑴;
private static final int EVENT_ERROR = 11;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_baidu_recognize);
txtResult = (TextView) findViewById(R.id.bd_recognize_text);
txtLog = (TextView) findViewById(R.id.bd_recognize_log);
btnStart = (Button) findViewById(R.id.bd_recognize_start);
btnStart.setOnClickListener(this);
findViewById(R.id.bd_recognize_setting).setOnClickListener(this);
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this,
new ComponentName(this, VoiceRecognitionService.class));
speechRecognizer.setRecognitionListener(mRecognitionListener);
}
@Override
protected void onDestroy() {
speechRecognizer.destroy();
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
mRecognitionListener.onResults(data.getExtras());
} else {
status = STATUS_None;
btnStart.setText("開始");
}
}
public void bindParams(Intent intent) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (sp.getBoolean("tips_sound", true)) {
intent.putExtra(Constant.EXTRA_SOUND_START, R.raw.bdspeech_recognition_start);
intent.putExtra(Constant.EXTRA_SOUND_END, R.raw.bdspeech_speech_end);
intent.putExtra(Constant.EXTRA_SOUND_SUCCESS, R.raw.bdspeech_recognition_success);
intent.putExtra(Constant.EXTRA_SOUND_ERROR, R.raw.bdspeech_recognition_error);
intent.putExtra(Constant.EXTRA_SOUND_CANCEL, R.raw.bdspeech_recognition_cancel);
}
if (sp.contains(Constant.EXTRA_INFILE)) {
String tmp = sp.getString(Constant.EXTRA_INFILE, "").replaceAll(",.*", "").trim();
intent.putExtra(Constant.EXTRA_INFILE, tmp);
}
if (sp.getBoolean(Constant.EXTRA_OUTFILE, false)) {
intent.putExtra(Constant.EXTRA_OUTFILE, "sdcard/outfile.pcm");
}
if (sp.contains(Constant.EXTRA_SAMPLE)) {
String tmp = sp.getString(Constant.EXTRA_SAMPLE, "").replaceAll(",.*", "").trim();
if (null != tmp && !"".equals(tmp)) {
intent.putExtra(Constant.EXTRA_SAMPLE, Integer.parseInt(tmp));
}
}
if (sp.contains(Constant.EXTRA_LANGUAGE)) {
String tmp = sp.getString(Constant.EXTRA_LANGUAGE, "").replaceAll(",.*", "").trim();
if (null != tmp && !"".equals(tmp)) {
intent.putExtra(Constant.EXTRA_LANGUAGE, tmp);
}
}
if (sp.contains(Constant.EXTRA_NLU)) {
String tmp = sp.getString(Constant.EXTRA_NLU, "").replaceAll(",.*", "").trim();
if (null != tmp && !"".equals(tmp)) {
intent.putExtra(Constant.EXTRA_NLU, tmp);
}
}
if (sp.contains(Constant.EXTRA_VAD)) {
String tmp = sp.getString(Constant.EXTRA_VAD, "").replaceAll(",.*", "").trim();
if (null != tmp && !"".equals(tmp)) {
intent.putExtra(Constant.EXTRA_VAD, tmp);
}
}
if (sp.contains(Constant.EXTRA_PROP)) {
String tmp = sp.getString(Constant.EXTRA_PROP, "").replaceAll(",.*", "").trim();
if (null != tmp && !"".equals(tmp)) {
intent.putExtra(Constant.EXTRA_PROP, Integer.parseInt(tmp));
}
}
}
private void start() {
btnStart.setText("取消");
txtLog.setText("");
status = STATUS_WaitingReady;
print("點(diǎn)擊了“開始”");
Intent intent = new Intent();
bindParams(intent);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
{
String args = sp.getString("args", "");
if (null != args) {
print("參數(shù)集:" + args);
intent.putExtra("args", args);
}
}
boolean api = sp.getBoolean("api", false);
if (api) {
speechEndTime = ⑴;
speechRecognizer.startListening(intent);
} else {
intent.setAction("com.baidu.action.RECOGNIZE_SPEECH");
startActivityForResult(intent, REQUEST_UI);
}
txtResult.setText("");
}
private void stop() {
speechRecognizer.stopListening();
status = STATUS_Recognition;
btnStart.setText("辨認(rèn)中");
print("點(diǎn)擊了“說(shuō)完了”");
}
private void cancel() {
speechRecognizer.cancel();
btnStart.setText("開始");
status = STATUS_None;
print("點(diǎn)擊了“取消”");
}
private RecognitionListener mRecognitionListener = new RecognitionListener() {
@Override
public void onReadyForSpeech(Bundle params) {
status = STATUS_Ready;
print("準(zhǔn)備就緒,可以開始說(shuō)話");
}
@Override
public void onBeginningOfSpeech() {
status = STATUS_Speaking;
btnStart.setText("說(shuō)完了");
print("檢測(cè)到用戶已開始說(shuō)話");
}
@Override
public void onRmsChanged(float rmsdB) {
}
@Override
public void onBufferReceived(byte[] buffer) {
}
@Override
public void onEndOfSpeech() {
speechEndTime = System.currentTimeMillis();
status = STATUS_Recognition;
print("檢測(cè)到用戶已停止說(shuō)話");
btnStart.setText("辨認(rèn)中");
}
@Override
public void onError(int error) {
status = STATUS_None;
StringBuilder sb = new StringBuilder();
switch (error) {
case SpeechRecognizer.ERROR_AUDIO:
sb.append("音頻問(wèn)題");
break;
case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
sb.append("沒(méi)有語(yǔ)音輸入");
break;
case SpeechRecognizer.ERROR_CLIENT:
sb.append("其它客戶端毛病");
break;
case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
sb.append("權(quán)限不足");
break;
case SpeechRecognizer.ERROR_NETWORK:
sb.append("網(wǎng)絡(luò)問(wèn)題");
break;
case SpeechRecognizer.ERROR_NO_MATCH:
sb.append("沒(méi)有匹配的辨認(rèn)結(jié)果");
break;
case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
sb.append("引擎忙");
break;
case SpeechRecognizer.ERROR_SERVER:
sb.append("服務(wù)端毛病");
break;
case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
sb.append("連接超時(shí)");
break;
}
sb.append(":" + error);
print("辨認(rèn)失敗:" + sb.toString());
btnStart.setText("開始");
}
@Override
public void onResults(Bundle results) {
long end2finish = System.currentTimeMillis() - speechEndTime;
ArrayListnbest = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
print("辨認(rèn)成功:" + Arrays.toString(nbest.toArray(new String[nbest.size()])));
String json_res = results.getString("origin_result");
try {
print("origin_result=\n" + new JSONObject(json_res).toString(4));
} catch (Exception e) {
print("origin_result=[warning: bad json]\n" + json_res);
}
String strEnd2Finish = "";
if (end2finish < 60 * 1000) { strEnd2Finish = "(waited " + end2finish + "ms)"; } txtResult.setText(nbest.get(0) + strEnd2Finish); status = STATUS_None; btnStart.setText("開始"); } @Override public void onPartialResults(Bundle partialResults) { ArrayListnbest = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
if (nbest.size() > 0) {
print("~臨時(shí)辨認(rèn)結(jié)果:" + Arrays.toString(nbest.toArray(new String[0])));
txtResult.setText(nbest.get(0));
}
}
@Override
public void onEvent(int eventType, Bundle params) {
switch (eventType) {
case EVENT_ERROR:
String reason = params.get("reason") + "";
print("EVENT_ERROR, " + reason);
status = STATUS_None;
btnStart.setText("開始");
break;
case VoiceRecognitionService.EVENT_ENGINE_SWITCH:
int type = params.getInt("engine_type");
print("*引擎切換至" + (type == 0 ? "在線" : "離線"));
break;
}
}
};
private void print(String msg) {
txtLog.append(msg + "\n");
ScrollView sv = (ScrollView) txtLog.getParent();
sv.smoothScrollTo(0, 1000000);
Log.d(TAG, "----" + msg);
}
@Override
public void onClick(View v) {
int resid = v.getId();
if (resid == R.id.bd_recognize_setting) {
Intent intent = new Intent(this, SettingsActivity.class);
intent.putExtra("type", SettingsActivity.BD_RECOGNIZE);
startActivity(intent);
} else if (resid == R.id.bd_recognize_start) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
boolean api = sp.getBoolean("api", false);
if (api) {
if (status == STATUS_None) {
start();
} else if (status == STATUS_WaitingReady ||
status == STATUS_Ready || status == STATUS_Recognition) {
cancel();
} else if (status == STATUS_Speaking) {
stop();
}
} else {
start();
}
}
}
}
語(yǔ)音合成
百度語(yǔ)音合成用的是SpeechSynthesizer類,主要方法以下:
getInstance : 取得語(yǔ)音合成的實(shí)例。
setContext : 設(shè)置語(yǔ)音合成的上下文。
setSpeechSynthesizerListener : 語(yǔ)音合成的監(jiān)聽器。該監(jiān)聽器需重寫的方法包括:
--onSynthesizeStart : 合成開始。
--onSynthesizeDataArrived : 1般不使用。
--onSynthesizeFinish : 合成結(jié)束。
--onSpeechStart : 朗誦開始。
--onSpeechProgressChanged : 朗誦進(jìn)度變化。
--onSpeechFinish : 朗誦結(jié)束。
--onError : 處理出錯(cuò)。
setAppId : 設(shè)置appid。
setApiKey : 設(shè)置apikey和secretkey。
auth : 對(duì)appid、apikey和secretkey進(jìn)行鑒權(quán)。
initTts : 初始化。TtsMode.ONLINE表示在線合成,TtsMode.MIX表示混合(即在線與離線結(jié)合)。
setAudioStreamType : 設(shè)置音頻流的類型。AudioManager.STREAM_MUSIC表示音樂(lè)。
setParam : 設(shè)置語(yǔ)音合成的參數(shù)。經(jīng)常使用參數(shù)包括:
--SpeechSynthesizer.PARAM_SPEAKER : 設(shè)置朗誦者。0表示普通女聲,1表示普通男聲,2表示特別男聲,3表示情感男聲。
--SpeechSynthesizer.PARAM_VOLUME : 設(shè)置音量。取值范圍為0⑼,默許5。
--SpeechSynthesizer.PARAM_SPEED : 設(shè)置語(yǔ)速。取值范圍為0⑼,默許5。
--SpeechSynthesizer.PARAM_PITCH : 設(shè)置音調(diào)。取值范圍為0⑼,默許5。
--SpeechSynthesizer.PARAM_AUDIO_ENCODE : 設(shè)置音頻的編碼類型。1般設(shè)置SpeechSynthesizer.AUDIO_ENCODE_AMR。
--SpeechSynthesizer.PARAM_AUDIO_RATE : 設(shè)置音頻的編碼速率。1般設(shè)置SpeechSynthesizer.AUDIO_BITRATE_AMR_15K85。
loadEnglishModel : 加載英語(yǔ)模塊。
speak : 開始合成并朗誦。
pause : 暫停朗誦。
resume : 恢復(fù)朗誦。
stop : 停止朗誦。
release : 釋放語(yǔ)音合成的實(shí)例。
下面是百度語(yǔ)音合成的代碼例子:
import java.io.File;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.Toast;
import com.baidu.tts.auth.AuthInfo;
import com.baidu.tts.client.SpeechError;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.client.TtsMode;
import com.example.exmvoice.R;
import com.example.exmvoice.SettingsActivity;
import com.example.exmvoice.baidu.util.AssetsUtil;
public class BDComposeActivity extends Activity implements OnClickListener,OnCheckedChangeListener {
private static String TAG = BDComposeActivity.class.getSimpleName();
private SpeechSynthesizer mSpeechSynthesizer;
private String mSampleDirPath;
private static final String SAMPLE_DIR_NAME = "baiduTTS";
private static final String SPEECH_FEMALE_MODEL_NAME = "bd_etts_speech_female.dat";
private static final String SPEECH_MALE_MODEL_NAME = "bd_etts_speech_male.dat";
private static final String TEXT_MODEL_NAME = "bd_etts_text.dat";
private static final String LICENSE_FILE_NAME = "bd_temp_license";
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";
private boolean bOnline = true;
private EditText mResourceText;
private SharedPreferences mSharedPreferences;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_baidu_compose);
mResourceText = ((EditText) findViewById(R.id.bd_compose_text));
((RadioGroup)findViewById(R.id.bd_compose_mode)).setOnCheckedChangeListener(this);
findViewById(R.id.bd_compose_play).setOnClickListener(this);
findViewById(R.id.bd_compose_cancel).setOnClickListener(this);
findViewById(R.id.bd_compose_pause).setOnClickListener(this);
findViewById(R.id.bd_compose_resume).setOnClickListener(this);
findViewById(R.id.bd_compose_setting).setOnClickListener(this);
mSharedPreferences = getSharedPreferences(SettingsActivity.PREFER_NAME, MODE_PRIVATE);
initialEnv();
initialEngine();
}
private void initialEnv() {
if (mSampleDirPath == null) {
String sdcardPath = Environment.getExternalStorageDirectory().toString();
mSampleDirPath = sdcardPath + "/" + SAMPLE_DIR_NAME;
}
File file = new File(mSampleDirPath);
if (!file.exists()) {
file.mkdirs();
}
AssetsUtil.copyFromAssetsToSdcard(this, false, SPEECH_FEMALE_MODEL_NAME, mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME);
AssetsUtil.copyFromAssetsToSdcard(this, false, SPEECH_MALE_MODEL_NAME, mSampleDirPath + "/" + SPEECH_MALE_MODEL_NAME);
AssetsUtil.copyFromAssetsToSdcard(this, false, TEXT_MODEL_NAME, mSampleDirPath + "/" + TEXT_MODEL_NAME);
AssetsUtil.copyFromAssetsToSdcard(this, false, LICENSE_FILE_NAME, mSampleDirPath + "/" + LICENSE_FILE_NAME);
AssetsUtil.copyFromAssetsToSdcard(this, false, "english/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME, mSampleDirPath + "/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME);
AssetsUtil.copyFromAssetsToSdcard(this, false, "english/" + ENGLISH_SPEECH_MALE_MODEL_NAME, mSampleDirPath + "/" + ENGLISH_SPEECH_MALE_MODEL_NAME);
AssetsUtil.copyFromAssetsToSdcard(this, false, "english/" + ENGLISH_TEXT_MODEL_NAME, mSampleDirPath + "/" + ENGLISH_TEXT_MODEL_NAME);
}
private void initialEngine() {
mSpeechSynthesizer = SpeechSynthesizer.getInstance();
mSpeechSynthesizer.setContext(this);
mSpeechSynthesizer.setSpeechSynthesizerListener(mSpeechListener);
ApplicationInfo appInfo = null;
try {
appInfo = this.getPackageManager().getApplicationInfo(
getPackageName(), PackageManager.GET_META_DATA);
String app_id = appInfo.metaData.getString("com.baidu.speech.APP_ID");
String api_key = appInfo.metaData.getString("com.baidu.speech.API_KEY");
String secret_key = appInfo.metaData.getString("com.baidu.speech.SECRET_KEY");
mSpeechSynthesizer.setAppId(app_id);
mSpeechSynthesizer.setApiKey(api_key, secret_key);
} catch (NameNotFoundException e) {
e.printStackTrace();
showTip("獲得appid失敗");
}
AuthInfo authInfo = mSpeechSynthesizer.auth(TtsMode.ONLINE);
if (authInfo.isSuccess()) {
showTip("auth success");
} else {
String errorMsg = authInfo.getTtsError().getDetailMessage();
showTip("auth failed errorMsg=" + errorMsg);
}
mSpeechSynthesizer.initTts(TtsMode.MIX);
bOnline = ((RadioButton) findViewById(R.id.bd_compose_online)).isChecked();
setParams(bOnline);
}
@Override
protected void onDestroy() {
// 退出時(shí)釋放連接
mSpeechSynthesizer.release();
super.onDestroy();
}
@Override
public void onClick(View v) {
int resid = v.getId();
if (resid == R.id.bd_compose_setting) {
Intent intent = new Intent(this, SettingsActivity.class);
intent.putExtra("type", SettingsActivity.BD_COMPOSE);
startActivity(intent);
} else if (resid == R.id.bd_compose_play) { // 開始合成
speak();
} else if (resid == R.id.bd_compose_cancel) { // 取消合成
mSpeechSynthesizer.stop();
} else if (resid == R.id.bd_compose_pause) { // 暫停播放
mSpeechSynthesizer.pause();
} else if (resid == R.id.bd_compose_resume) { // 繼續(xù)播放
mSpeechSynthesizer.resume();
}
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.bd_compose_online) {
bOnline = true;
} else if (checkedId == R.id.bd_compose_offline) {
bOnline = false;
}
Log.d(TAG, "bOnline="+bOnline);
setParams(bOnline);
}
private void setParams(boolean online) {
mSpeechSynthesizer.setAudioStreamType(AudioManager.STREAM_MUSIC);
//setVolumeControlStream(AudioManager.STREAM_MUSIC);
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, mSharedPreferences.getString("bd_person_preference", "0")); //0--普通女聲,1--普通男聲,2--特別男聲,3--情感男聲
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, mSharedPreferences.getString("bd_volume_preference", "5")); //音量,取值0⑼,默許為5中音量
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, mSharedPreferences.getString("bd_speed_preference", "5")); //語(yǔ)速,取值0⑼,默許為5中語(yǔ)速
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, mSharedPreferences.getString("bd_pitch_preference", "5")); //音調(diào),取值0⑼,默許為5中腔調(diào)
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_AUDIO_ENCODE,
SpeechSynthesizer.AUDIO_ENCODE_AMR);
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_AUDIO_RATE,
SpeechSynthesizer.AUDIO_BITRATE_AMR_15K85);
if (online == true) {
} else {
// 文本模型文件路徑 (離線引擎使用)
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, mSampleDirPath + "/" + TEXT_MODEL_NAME);
// 聲學(xué)模型文件路徑 (離線引擎使用)
mSpeechSynthesizer.setParam(SpeechSynthesizer.PA
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)