Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發筆記(一百零八)智能語音

Android開發筆記(一百零八)智能語音

編輯:關於Android編程

智能語音技術

如今越來越多的app用到了語音播報功能,例如地圖導航、天氣預報、文字閱讀、口語訓練等等。語音技術主要分兩塊,一塊是語音轉文字,即語音識別;另一塊是文字轉語音,即語音合成。

對中文來說,和語音播報相關的一個技術是漢字轉拼音,想想看,拼音本身就是音節拼讀的標記,每個音節對應一段音頻,那麼一句的拼音便能用一連串的音頻流合成而來。

語音合成通常也簡稱為TTS,即TextToSpeech(從文本到語言)。語音合成技術把文字智能地轉化為自然語音流,當然為了避免機械合成的呆板和停頓感,語音引擎還得對語音流進行平滑處理,確保輸出的語音音律流暢、感覺自然。

TextToSpeech

Android從1.6開始,就內置了語音合成引擎,即“Pico TTS”。該引擎支持英語、法語、德語、意大利語,但不支持中文,幸好Android從4.0開始允許接入第三方的語音引擎,因此只要我們安裝了中文引擎,就能在代碼中使用中文語音合成服務。例如,在各大應用市場上下載並安裝科大訊飛+,然後在手機操作“系統設置”——“語言和輸入法”——“文字轉語音(TTS)輸出”,如下圖所示即可設置中文的語音引擎:



Android的語音合成控件類名是TextToSpeech,下面是該類常用的方法說明:
構造函數 : 第二個參數設置TTSListener對象,要重寫onInit方法(通常在這裡調用setLanguage方法,因為初始化成功後才能設置語言)。第三個參數設置語音引擎,默認是系統自帶的pico,要獲取系統支持的所有引擎可調用getEngines方法。
setLanguage : 設置語言。英語為Locale.ENGLISH;法語為Locale.FRENCH;德語為Locale.GERMAN;意大利語為Locale.ITALIAN;漢語普通話為Locale.CHINA(需安裝中文引擎,如科大訊飛+)。該方法的返回值有三個,0表示正常,-1表示缺失數據,-2表示不支持該語言。
setSpeechRate : 設置語速。1.0正常語速;0.5慢一半的語速;2.0;快一倍的語速。
setPitch : 設置音調。1.0正常音調;低於1.0的為低音;高於1.0的為高音。
speak : 開始對指定文本進行語音朗讀。
synthesizeToFile : 把指定文本的朗讀語音輸出到文件。
stop : 停止朗讀。
shutdown : 關閉語音引擎。
isSpeaking : 判斷是否在語音朗讀。
getLanguage : 獲取當前的語言。
getCurrentEngine : 獲取當前的語音引擎。
getEngines : 獲取系統支持的所有語音引擎。


下面是TextToSpeech處理語音合成的代碼示例:
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() {
		ArrayAdapter starAdapter = 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("請選擇語言");
		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 starAdapter = new ArrayAdapter(this,
				R.layout.spinner_item, mEngineArray);
		starAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
		Spinner sp = (Spinner) findViewById(R.id.sp_tts_engine);
		sp.setPrompt("請選擇引擎");
		sp.setAdapter(starAdapter);
		sp.setOnItemSelectedListener(new EngineSelectedListener());
		sp.setSelection(0);
	}
	
	@Override
    protected void onDestroy() {
		recycleSpeech();
        super.onDestroy();
    }
	
	private void recycleSpeech() {
        if (mSpeech != null) {
            mSpeech.stop();
            mSpeech.shutdown();
            mSpeech = null;
        }
	}
	
	private String[] mLangArray = {"英語", "法語", "德語", "意大利語", "漢語普通話" };
	private Locale[] mLocaleArray = {
			Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.CHINA };
	private int mLanguage;
	private String mTextEN = "hello world. This is a TTS demo.";
	private String mTextCN = "白日依山盡,黃河入海流。欲窮千裡目,更上一層樓。";
	
	private class LanguageSelectedListener implements OnItemSelectedListener {
		public void onItemSelected(AdapterView 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]);
        //如果打印為-2,說明不支持這種語言;-1說明缺失數據
        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 List mEngineList;
	private class TTSListener implements OnInitListener {

        @Override  
        public void onInit(int status) {
            if (status == TextToSpeech.SUCCESS) {
            	if (mEngineList == null) {
                	mEngineList = mSpeech.getEngines();
                	initEngineSpinner();
            	} else {
                	resetLanguage();
            	}
            }
        }
        
    }
}


科大訊飛語音

前面提到,只要安裝了中文引擎,即可在TextToSpeech中使用中文語音;可是我們沒法要求用戶再額外下載一個app,正確的做法是在自己app中集成語音sdk。目前中文環境常見的語音sdk主要有科大訊飛、百度語音、捷通華聲、雲知聲等等,開發者可自行選擇一個。


sdk集成

科大訊飛語音sdk的集成步驟如下:
1、導入sdk包到libs目錄,包括libmsc.so、Msc.jar、Sunflower.jar;
2、到訊飛網站注冊並創建新應用,獲得appid;
3、自定義一個Application類,在onCreate函數中加入下面代碼,注意appid值為第二步申請到的id:
		SpeechUtility.createUtility(MainApplication.this, "appid=5763c4cf");
4、在AndroidManifest.xml中加入必要的權限,以及自定義的Application類;
5、根據demo工程編寫代碼與布局文件;
6、如果使用了RecognizerDialog控件,則要把demo工程assets目錄下的文件原樣拷過來;
7、在混淆打包的時候需要添加-keep class com.iflytek.**{*;},


語音識別

科大訊飛的語音識別用的是SpeechRecognizer類,主要方法如下:
createRecognizer : 創建語音識別對象。
setParameter : 設置語音識別的參數。常用參數包括:
--SpeechConstant.ENGINE_TYPE : 設置聽寫引擎。TYPE_LOCAL表示本地,TYPE_CLOUD表示雲端,TYPE_MIX表示混合。
--SpeechConstant.RESULT_TYPE : 設置返回結果格式。json表示json格式。
--SpeechConstant.LANGUAGE : 設置語言。zh_cn表示中文,en_us表示英文。
--SpeechConstant.ACCENT : 設置方言。mandarin表示普通話,cantonese表示粵語,henanese表示河南話。
--SpeechConstant.VAD_BOS : 設置語音前端點:靜音超時時間,即用戶多長時間不說話則當做超時處理。
--SpeechConstant.VAD_EOS : 設置語音後端點:後端點靜音檢測時間,即用戶停止說話多長時間內即認為不再輸入,自動停止錄音。
--SpeechConstant.ASR_PTT : 設置標點符號。0表示返回結果無標點,1表示返回結果有標點。
--SpeechConstant.AUDIO_FORMAT : 設置音頻的保存格式。
--SpeechConstant.ASR_AUDIO_PATH : 設置音頻的保存路徑。
--SpeechConstant.AUDIO_SOURCE : 設置音頻的來源。-1表示音頻流,與writeAudio配合使用;-2表示外部文件,同時設置ASR_SOURCE_PATH指定文件路徑。
--SpeechConstant.ASR_SOURCE_PATH : 設置外部音頻文件的路徑。
startListening : 開始監聽語音輸入。參數為RecognizerListener對象,該對象需重寫的方法包括:
--onBeginOfSpeech : 內部錄音機已經准備好了,用戶可以開始語音輸入。
--onError : 錯誤碼:10118(您沒有說話),可能是錄音機權限被禁,需要提示用戶打開應用的錄音權限。
--onEndOfSpeech : 檢測到了語音的尾端點,已經進入識別過程,不再接受語音輸入。
--onResult : 識別結束,返回結果串。
--onVolumeChanged : 語音輸入過程中的音量大小變化。
--onEvent : 事件處理,一般是業務出錯等異常。
stopListening : 結束監聽語音。
writeAudio : 把指定的音頻流作為語音輸入。
cancel : 取消監聽。
destroy : 回收語音識別對象。


下面是科大訊飛語音識別的運行截圖:
\


下面是科大訊飛語音識別的代碼例子:
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();
	// 語音聽寫對象
	private SpeechRecognizer mRecognize;
	// 語音聽寫UI
	private RecognizerDialog mRecognizeDialog;
	// 用HashMap存儲聽寫結果
	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);
		
		// 初始化識別無UI識別對象,使用SpeechRecognizer對象,可根據回調消息自定義界面;
		mRecognize = SpeechRecognizer.createRecognizer(this, mInitListener);
		// 初始化聽寫Dialog,如果只使用有UI聽寫功能,無需創建SpeechRecognizer
		// 使用UI聽寫功能,請將assets下文件拷貝到項目中
		mRecognizeDialog = new RecognizerDialog(this, mInitListener);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		// 退出時釋放連接
		mRecognize.cancel();
		mRecognize.destroy();
	}

	@Override
	public void onClick(View v) {
		int ret = 0; // 函數調用返回值
		int resid = v.getId();
		if (resid == R.id.xf_recognize_setting) {  // 進入參數設置頁面
			Intent intent = new Intent(this, SettingsActivity.class);
			intent.putExtra("type", SettingsActivity.XF_RECOGNIZE);
			startActivity(intent);
		} else if (resid == R.id.xf_recognize_start) {  // 開始聽寫。如何判斷一次聽寫結束:OnResult isLast=true 或者 onError
			mResultText.setText(null);// 清空顯示內容
			mRecognizeResults.clear();
			// 設置參數
			resetParam();
			boolean isShowDialog = mSharedPreferences.getBoolean("show_dialog", true);
			if (isShowDialog) {
				// 顯示聽寫對話框
				mRecognizeDialog.setListener(mRecognizeDialogListener);
				mRecognizeDialog.show();
				showTip("請開始說話………");
			} else {
				// 不顯示聽寫對話框
				ret = mRecognize.startListening(mRecognizeListener);
				if (ret != ErrorCode.SUCCESS) {
					showTip("聽寫失敗,錯誤碼:" + ret);
				} else {
					showTip("請開始說話…");
				}
			}
		} 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) {  // 音頻流識別
			mResultText.setText(null);// 清空顯示內容
			mRecognizeResults.clear();
			// 設置參數
			resetParam();
			// 設置音頻來源為外部文件
			mRecognize.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
			// 也可以像以下這樣直接設置音頻文件路徑識別(要求設置文件在sdcard上的全路徑):
			// mRecognize.setParameter(SpeechConstant.AUDIO_SOURCE, "-2");
			// mRecognize.setParameter(SpeechConstant.ASR_SOURCE_PATH, "sdcard/XXX/XXX.pcm");
			ret = mRecognize.startListening(mRecognizeListener);
			if (ret != ErrorCode.SUCCESS) {
				showTip("識別失敗,錯誤碼:" + ret);
			} else {
				byte[] audioData = FucUtil.readAudioFile(this, "retcognize_est.wav");
				if (null != audioData) {
					showTip("開始音頻流識別");
					// 一次(也可以分多次)寫入音頻文件數據,數據格式必須是采樣率為8KHz或16KHz(本地識別只支持16K采樣率,雲端都支持),位長16bit,單聲道的wav或者pcm
					// 寫入8KHz采樣的音頻時,必須先調用setParameter(SpeechConstant.SAMPLE_RATE, "8000")設置正確的采樣率
					// 注:當音頻過長,靜音部分時長超過VAD_EOS將導致靜音後面部分不能識別
					mRecognize.writeAudio(audioData, 0, audioData.length);
					mRecognize.stopListening();
				} else {
					mRecognize.cancel();
					showTip("讀取音頻流失敗");
				}
			}
		}
	}

	//初始化監聽器
	private InitListener mInitListener = new InitListener() {
		@Override
		public void onInit(int code) {
			Log.d(TAG, "SpeechRecognizer init() code = " + code);
			if (code != ErrorCode.SUCCESS) {
				showTip("初始化失敗,錯誤碼:" + code);
			}
		}
	};

	//聽寫監聽器
	private RecognizerListener mRecognizeListener = new RecognizerListener() {

		@Override
		public void onBeginOfSpeech() {
			// 此回調表示:sdk內部錄音機已經准備好了,用戶可以開始語音輸入
			showTip("開始說話");
		}

		@Override
		public void onError(SpeechError error) {
			// 錯誤碼:10118(您沒有說話),可能是錄音機權限被禁,需要提示用戶打開應用的錄音權限。
			// 如果使用本地功能(語記)需要提示用戶開啟語記的錄音權限。
			showTip(error.getPlainDescription(true));
		}

		@Override
		public void onEndOfSpeech() {
			// 此回調表示:檢測到了語音的尾端點,已經進入識別過程,不再接受語音輸入
			showTip("結束說話");
		}

		@Override
		public void onResult(RecognizerResult results, boolean isLast) {
			Log.d(TAG, results.getResultString());
			printResult(results);
			if (isLast) {
				// TODO 最後的結果
			}
		}

		@Override
		public void onVolumeChanged(int volume, byte[] data) {
			showTip("當前正在說話,音量大小:" + volume);
			Log.d(TAG, "返回音頻數據:"+data.length);
		}

		@Override
		public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
			// 以下代碼用於獲取與雲端的會話id,當業務出錯時將會話id提供給技術支持人員,可用於查詢會話日志,定位出錯原因
			// 若使用本地能力,會話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監聽器
	private RecognizerDialogListener mRecognizeDialogListener = new RecognizerDialogListener() {
		public void onResult(RecognizerResult results, boolean isLast) {
			printResult(results);
		}

		//識別回調錯誤
		public void onError(SpeechError error) {
			showTip(error.getPlainDescription(true));
		}
	};

	private void showTip(final String str) {
		Toast.makeText(this, str, Toast.LENGTH_LONG).show();
	}

	//參數設置
	public void resetParam() {
		// 清空參數
		mRecognize.setParameter(SpeechConstant.PARAMS, null);
		// 設置聽寫引擎。TYPE_LOCAL表示本地,TYPE_CLOUD表示雲端,TYPE_MIX 表示混合
		mRecognize.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
		// 設置返回結果格式
		mRecognize.setParameter(SpeechConstant.RESULT_TYPE, "json");

		String lag = mSharedPreferences.getString("recognize_language_preference", "mandarin");
		if (lag.equals("en_us")) {  // 設置語言
			mRecognize.setParameter(SpeechConstant.LANGUAGE, "en_us");
		} else {
			mRecognize.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
			// 設置語言區域
			mRecognize.setParameter(SpeechConstant.ACCENT, lag);
		}

		// 設置語音前端點:靜音超時時間,即用戶多長時間不說話則當做超時處理
		mRecognize.setParameter(SpeechConstant.VAD_BOS, mSharedPreferences.getString("recognize_vadbos_preference", "4000"));
		// 設置語音後端點:後端點靜音檢測時間,即用戶停止說話多長時間內即認為不再輸入, 自動停止錄音
		mRecognize.setParameter(SpeechConstant.VAD_EOS, mSharedPreferences.getString("recognize_vadeos_preference", "1000"));
		// 設置標點符號,設置為"0"返回結果無標點,設置為"1"返回結果有標點
		mRecognize.setParameter(SpeechConstant.ASR_PTT, mSharedPreferences.getString("recognize_punc_preference", "1"));
		// 設置音頻保存路徑,保存音頻格式支持pcm、wav,設置路徑為sd卡請注意WRITE_EXTERNAL_STORAGE權限
		// 注:AUDIO_FORMAT參數語記需要更新版本才能生效
		mRecognize.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
		mRecognize.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/recognize.wav");
	}

}


語音合成

科大訊飛的語音合成用的是SpeechSynthesizer類,主要方法如下:
createSynthesizer : 創建語音合成對象。
setParameter : 設置語音合成的參數。常用參數包括:
--SpeechConstant.ENGINE_TYPE : 設置合成引擎。TYPE_LOCAL表示本地,TYPE_CLOUD表示雲端,TYPE_MIX表示混合。
--SpeechConstant.VOICE_NAME : 設置朗讀者。默認xiaoyan(女青年,普通話)
--SpeechConstant.SPEED : 設置朗讀的語速。
--SpeechConstant.PITCH : 設置朗讀的音調。
--SpeechConstant.VOLUME : 設置朗讀的音量。
--SpeechConstant.STREAM_TYPE : 設置音頻流類型。默認是音樂。
--SpeechConstant.KEY_REQUEST_FOCUS : 設置是否在播放合成音頻時打斷音樂播放,默認為true。
--SpeechConstant.AUDIO_FORMAT : 設置音頻的保存格式。
--SpeechConstant.TTS_AUDIO_PATH : 設置音頻的保存路徑。
startSpeaking : 開始語音朗讀。參數為SynthesizerListener對象,該對象需重寫的方法包括:
--onSpeakBegin : 朗讀開始。
--onSpeakPaused : 朗讀暫停。
--onSpeakResumed : 朗讀恢復。
--onBufferProgress : 合成進度變化。
--onSpeakProgress : 朗讀進度變化。
--onCompleted : 朗讀完成。
--onEvent : 事件處理,一般是業務出錯等異常。
synthesizeToUri : 只保存音頻不進行播放,調用該接口就不能調用startSpeaking。第一個參數是要合成的文本,第二個參數時要保存的音頻全路徑,第三個參數是SynthesizerListener回調接口。
pauseSpeaking : 暫停朗讀。
resumeSpeaking : 恢復朗讀。
stopSpeaking : 停止朗讀。
destroy : 回收語音合成對象。


下面是科大訊飛語音合成的代碼例子:
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();
	// 語音合成對象
	private SpeechSynthesizer mCompose;
	// 默認發音人
	private String voicer = "xiaoyan";
	private String[] mCloudVoicersEntries;
	private String[] mCloudVoicersValue ;
	// 緩沖進度
	private int mPercentForBuffering = 0;
	// 播放進度
	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);
		
		// 初始化合成對象
		mCompose = SpeechSynthesizer.createSynthesizer(this, mComposeInitListener);
		// 雲端發音人名稱列表
		mCloudVoicersEntries = getResources().getStringArray(R.array.voicer_cloud_entries);
		mCloudVoicersValue = getResources().getStringArray(R.array.voicer_cloud_values);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		// 退出時釋放連接
		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 回調時,合成結束、生成合成音頻。合成的音頻格式:只支持pcm格式
			String text = mResourceText.getText().toString();
			// 設置參數
			setParam();
			int code = mCompose.startSpeaking(text, mComposeListener);
			if (code != ErrorCode.SUCCESS) {
				showTip("語音合成失敗,錯誤碼: " + code);
			}
//			//只保存音頻不進行播放接口,調用此接口請注釋startSpeaking接口
//			//text:要合成的文本,uri:需要保存的音頻全路徑,listener:回調接口
//			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) {  // 繼續播放
			mCompose.resumeSpeaking();
		} else if (resid == R.id.xf_compose_person) {  // 選擇發音人
			showPresonSelectDialog();
		}
	}
	
	private int selectedNum = 0;
	//發音人選擇
	private void showPresonSelectDialog() {
		new AlertDialog.Builder(this).setTitle("在線合成發音人選項")
		.setSingleChoiceItems(mCloudVoicersEntries, // 單選框有幾項,各是什麼名字
			selectedNum, // 默認的選項
			new DialogInterface.OnClickListener() { // 點擊單選框後的處理
				public void onClick(DialogInterface dialog, int which) { // 點擊了哪一項
					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();
	}

	//初始化監聽
	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 {
				// 初始化成功,之後可以調用startSpeaking方法
        		// 注:有的開發者在onCreate方法中創建完合成對象之後馬上就調用startSpeaking進行合成,
        		// 正確的做法是將onCreate中的startSpeaking調用移至這裡
			}		
		}
	};

	//合成回調監聽
	private SynthesizerListener mComposeListener = new SynthesizerListener() {
		
		@Override
		public void onSpeakBegin() {
			showTip("開始播放");
		}

		@Override
		public void onSpeakPaused() {
			showTip("暫停播放");
		}

		@Override
		public void onSpeakResumed() {
			showTip("繼續播放");
		}

		@Override
		public void onBufferProgress(int percent, int beginPos, int endPos, String info) {
			// 合成進度
			mPercentForBuffering = percent;
			showTip(String.format(getString(R.string.xf_compose_toast_format),
					mPercentForBuffering, mPercentForPlaying));
		}

		@Override
		public void onSpeakProgress(int percent, int beginPos, int endPos) {
			// 播放進度
			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) {
			// 以下代碼用於獲取與雲端的會話id,當業務出錯時將會話id提供給技術支持人員,可用於查詢會話日志,定位出錯原因
			// 若使用本地能力,會話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();
	}

	//參數設置
	private void setParam(){
		// 清空參數
		mCompose.setParameter(SpeechConstant.PARAMS, null);
		// 根據合成引擎設置相應參數
		mCompose.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
		// 設置在線合成發音人
		mCompose.setParameter(SpeechConstant.VOICE_NAME, voicer);
		//設置合成語速
		mCompose.setParameter(SpeechConstant.SPEED, mSharedPreferences.getString("speed_preference", "50"));
		//設置合成音調
		mCompose.setParameter(SpeechConstant.PITCH, mSharedPreferences.getString("pitch_preference", "50"));
		//設置合成音量
		mCompose.setParameter(SpeechConstant.VOLUME, mSharedPreferences.getString("volume_preference", "50"));
		//設置播放器音頻流類型
		mCompose.setParameter(SpeechConstant.STREAM_TYPE, mSharedPreferences.getString("stream_preference", "3"));
		// 設置播放合成音頻打斷音樂播放,默認為true
		mCompose.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");
		
		// 設置音頻保存路徑,保存音頻格式支持pcm、wav,設置路徑為sd卡請注意WRITE_EXTERNAL_STORAGE權限
		// 注:AUDIO_FORMAT參數語記需要更新版本才能生效
		mCompose.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
		mCompose.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/compose.wav");
	}
	
}


PreferenceFragment

科大訊飛的demo工程在設置頁面使用了PreferenceActivity,看起來代碼簡煉了許多,正好我們之前還沒接觸Preference的實際運用,現在就來研究研究。看最新的sdk源碼,提示PreferenceActivity的許多方法都過時了,官方建議使用PreferenceFragment來代替。


下面是PreferenceFragment的常用方法說明
getPreferenceManager : 獲得參數管理的PreferenceManager對象。該對象主要有兩個方法:getDefaultSharedPreferences返回系統默認的共享參數對象;setSharedPreferencesName為設置指定名稱的共享參數;有關共享參數的說明參見《Android開發筆記(二十九)使用SharedPreferences存取數據》。
addPreferencesFromResource : 從xml資源文件中添加參數界面。
findPreference : 從xml資源文件中獲取指定id的元素。EditTextPreference表示該項參數為文本輸入;ListPreference表示該項參數為列表選擇;CheckBoxPreference表示該項參數為復選框勾選;PreferenceScreen是xml文件的根節點。
setPreferenceScreen : 設置參數屏幕(一般不使用)。


下面是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;

//語音識別設置界面
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的布局示例:

 
    
    
    
    
    
    

    
    
	



百度語音

sdk集成

百度語音sdk的集成比較麻煩,主要步驟如下:
1、導入sdk包到libs目錄,包括語音識別和語音合成兩種庫
語音識別的庫有:
libbdEASRAndroid.so
libBDVoiceRecognitionClient_MFE_V1.so
VoiceRecognition-2.0.1.jar
語音合成的庫有:
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、到百度注冊並創建新應用,獲得APP_ID、API_KEY、SECRET_KEY;
3、在AndroidManifest.xml中加入必要的權限,以及meta-data、service和activity設置,注意meta-data的參數值為第二步獲得的APP_ID、API_KEY、SECRET_KEY。詳細的xml部分例子如下:
        
        
        
        
        
            
                
                
            
        
4、demo工程中assets目錄下的文件原樣拷過來;
5、demo工程中res目錄下的drawable、layout、raw下面的資源原樣拷過來;
6、根據demo工程編寫代碼與布局文件,注意在語音合成初始化時,setAppId和setApiKey要把第二步獲得的APP_ID、API_KEY、SECRET_KEY給填進去;


下面是我在集成百度語音時遇到的幾個問題及處理辦法:
1、語音合成運行報錯,日志提示:
06-21 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方法沒有設置appkey。
2、語音合成運行報錯,日志提示:
06-21 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方法設置的值不對,可能使用了demo的appkey,而不是自己申請的appkey。
3、語音合成運行報錯,日志提示:
06-22 11:32:00.998: W/MainActivity(31928): onError error=(-15)(-15)online synthesize get was timeout[(cause)java.util.concurrent.TimeoutException]--utteranceId=0
原因:網絡連不上,請檢查網絡連接。如果使用模擬器測試,最好重啟模擬器再試試
4、調用loadEnglishModel方法加載英語模塊時,返回值是-11加載失敗(正常要返回5)。
原因:加載離線英文資源需要在初始化時采用混合模式TtsMode.MIX,不可采用在線模式TtsMode.ONLINE。


語音識別

百度語音識別用的是SpeechRecognizer類,主要方法如下:
createSpeechRecognizer : 創建語音識別對象。
setRecognitionListener : 設置識別監聽器。該監聽器需重寫的方法包括:
--onReadyForSpeech : 准備就緒,可以開始說話
--onBeginningOfSpeech : 檢測到用戶已經開始說話
--onRmsChanged : 一般不用處理。
--onBufferReceived : 一般不用處理。
--onEndOfSpeech : 檢測到用戶已經停止說話
--onError : 識別出錯。
--onResults : 識別完成,返回結果串。
--onPartialResults : 返回部分的識別結果。
--onEvent : 事件處理,一般是業務出錯等異常。
startListening : 開始監聽語音。
stopListening : 結束監聽語音。
cancel : 取消監聽。
destroy : 回收語音識別對象。


注意第一次識別時要跳到com.baidu.action.RECOGNIZE_SPEECH,後面才能調用startListening方法。識別時的參數設置是在activity跳轉時傳入的,常用參數包括:
--Constant.EXTRA_LANGUAGE : 說話的語言。cmn-Hans-CN表示普通話,sichuan-Hans-CN表示四川話,yue-Hans-CN表示粵語,en-GB表示英語。
--Constant.EXTRA_NLU : 是否開啟語義解析。
--Constant.EXTRA_VAD : 語音邊界檢測。search表示適用輸入搜索關鍵字(默認值),input表示適用於輸入短信、微博等長句輸入。
--Constant.EXTRA_PROP : 語音的行業領域。


下面是百度語音識別的運行截圖:
\


下面是百度語音識別的代碼例子:
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 = -1;
    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("點擊了“開始”");
        Intent intent = new Intent();
        bindParams(intent);
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        {
            String args = sp.getString("args", "");
            if (null != args) {
                print("參數集:" + args);
                intent.putExtra("args", args);
            }
        }
        boolean api = sp.getBoolean("api", false);
        if (api) {
            speechEndTime = -1;
            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("識別中");
        print("點擊了“說完了”");
    }

    private void cancel() {
        speechRecognizer.cancel();
        btnStart.setText("開始");
        status = STATUS_None;
        print("點擊了“取消”");
    }
    
    private RecognitionListener mRecognitionListener = new RecognitionListener() {

		@Override
		public void onReadyForSpeech(Bundle params) {
	        status = STATUS_Ready;
	        print("准備就緒,可以開始說話");
		}

		@Override
		public void onBeginningOfSpeech() {
	        status = STATUS_Speaking;
	        btnStart.setText("說完了");
	        print("檢測到用戶已經開始說話");
		}

		@Override
		public void onRmsChanged(float rmsdB) {
		}

		@Override
		public void onBufferReceived(byte[] buffer) {
		}

		@Override
		public void onEndOfSpeech() {
	        speechEndTime = System.currentTimeMillis();
	        status = STATUS_Recognition;
	        print("檢測到用戶已經停止說話");
	        btnStart.setText("識別中");
		}

		@Override
		public void onError(int error) {
	        status = STATUS_None;
	        StringBuilder sb = new StringBuilder();
	        switch (error) {
	            case SpeechRecognizer.ERROR_AUDIO:
	                sb.append("音頻問題");
	                break;
	            case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
	                sb.append("沒有語音輸入");
	                break;
	            case SpeechRecognizer.ERROR_CLIENT:
	                sb.append("其它客戶端錯誤");
	                break;
	            case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
	                sb.append("權限不足");
	                break;
	            case SpeechRecognizer.ERROR_NETWORK:
	                sb.append("網絡問題");
	                break;
	            case SpeechRecognizer.ERROR_NO_MATCH:
	                sb.append("沒有匹配的識別結果");
	                break;
	            case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
	                sb.append("引擎忙");
	                break;
	            case SpeechRecognizer.ERROR_SERVER:
	                sb.append("服務端錯誤");
	                break;
	            case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
	                sb.append("連接超時");
	                break;
	        }
	        sb.append(":" + error);
	        print("識別失敗:" + sb.toString());
	        btnStart.setText("開始");
		}

		@Override
		public void onResults(Bundle results) {
	        long end2finish = System.currentTimeMillis() - speechEndTime;
	        ArrayList nbest = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
	        print("識別成功:" + 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) {
	        ArrayList nbest = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
	        if (nbest.size() > 0) {
	            print("~臨時識別結果:" + 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();
            }
		} 
	}
}


語音合成

百度語音合成用的是SpeechSynthesizer類,主要方法如下:
getInstance : 獲得語音合成的實例。
setContext : 設置語音合成的上下文。
setSpeechSynthesizerListener : 語音合成的監聽器。該監聽器需重寫的方法包括:
--onSynthesizeStart : 合成開始。
--onSynthesizeDataArrived : 一般不使用。
--onSynthesizeFinish : 合成結束。
--onSpeechStart : 朗讀開始。
--onSpeechProgressChanged : 朗讀進度變化。
--onSpeechFinish : 朗讀結束。
--onError : 處理出錯。
setAppId : 設置appid。
setApiKey : 設置apikey和secretkey。
auth : 對appid、apikey和secretkey進行鑒權。
initTts : 初始化。TtsMode.ONLINE表示在線合成,TtsMode.MIX表示混合(即在線與離線結合)。
setAudioStreamType : 設置音頻流的類型。AudioManager.STREAM_MUSIC表示音樂。
setParam : 設置語音合成的參數。常用參數包括:
--SpeechSynthesizer.PARAM_SPEAKER : 設置朗讀者。0表示普通女聲,1表示普通男聲,2表示特別男聲,3表示情感男聲。
--SpeechSynthesizer.PARAM_VOLUME : 設置音量。取值范圍為0-9,默認5。
--SpeechSynthesizer.PARAM_SPEED : 設置語速。取值范圍為0-9,默認5。
--SpeechSynthesizer.PARAM_PITCH : 設置音調。取值范圍為0-9,默認5。
--SpeechSynthesizer.PARAM_AUDIO_ENCODE : 設置音頻的編碼類型。一般設置SpeechSynthesizer.AUDIO_ENCODE_AMR。
--SpeechSynthesizer.PARAM_AUDIO_RATE : 設置音頻的編碼速率。一般設置SpeechSynthesizer.AUDIO_BITRATE_AMR_15K85。
loadEnglishModel : 加載英語模塊。
speak : 開始合成並朗讀。
pause : 暫停朗讀。
resume : 恢復朗讀。
stop : 停止朗讀。
release : 釋放語音合成的實例。


下面是百度語音合成的代碼例子:
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() {
		// 退出時釋放連接
        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) {  // 繼續播放
            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-9,默認為5中音量
		mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, mSharedPreferences.getString("bd_speed_preference", "5")); //語速,取值0-9,默認為5中語速
		mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, mSharedPreferences.getString("bd_pitch_preference", "5")); //音調,取值0-9,默認為5中語調
		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);
	        // 聲學模型文件路徑 (離線引擎使用)
	        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME);
	        // 本地授權文件路徑,如未設置將使用默認路徑.設置臨時授權文件路徑,LICENCE_FILE_NAME請替換成臨時授權文件的實際路徑,僅在使用臨時license文件時需要進行設置,如果在[應用管理]中開通了離線授權,不需要設置該參數,建議將該行代碼刪除(離線引擎)
	        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_LICENCE_FILE, mSampleDirPath + "/" + LICENSE_FILE_NAME);
	        // 設置Mix模式的合成策略
	        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);

	        // 加載離線英文資源(提供離線英文合成功能)
	        String englishTextPath = mSampleDirPath + "/" + ENGLISH_TEXT_MODEL_NAME;
	        String englishSpeechPath = mSampleDirPath + "/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME;
	        Log.d(TAG, "englishTextPath="+englishTextPath+", englishSpeechPath="+englishSpeechPath);
	        int result = mSpeechSynthesizer.loadEnglishModel(englishTextPath, englishSpeechPath);
	        showTip("loadEnglishModel result=" + result);
	        //如果initTts使用的是在線模式TtsMode.ONLINE,則loadEnglishModel會失敗返回-11
		}
	}

    private void speak() {
        final String text = mResourceText.getText().toString();
        if (text==null || text.length()<=0) {
            showTip("請輸入要合成語音的文字");
        } else {
            int result = mSpeechSynthesizer.speak(text);
            if (result < 0) {
                showTip("result="+result+". error,please look up error code in doc or URL:http://yuyin.baidu.com/docs/tts/122 ");
            } else {
                showTip("合成結果="+result);
            }
        }
    }

    private SpeechSynthesizerListener mSpeechListener = new SpeechSynthesizerListener() {
        @Override
        public void onSynthesizeStart(String utteranceId) {
        	toPrint("onSynthesizeStart utteranceId=" + utteranceId);
        }

        @Override
        public void onSynthesizeDataArrived(String utteranceId, byte[] data, int progress) {
            // toPrint("onSynthesizeDataArrived");
        }

        @Override
        public void onSynthesizeFinish(String utteranceId) {
        	toPrint("onSynthesizeFinish utteranceId=" + utteranceId);
        }

        @Override
        public void onSpeechStart(String utteranceId) {
        	toPrint("onSpeechStart utteranceId=" + utteranceId);
        }

        @Override
        public void onSpeechProgressChanged(String utteranceId, int progress) {
            // toPrint("onSpeechProgressChanged");
        }

        @Override
        public void onSpeechFinish(String utteranceId) {
        	toPrint("onSpeechFinish utteranceId=" + utteranceId);
        }

        @Override
        public void onError(String utteranceId, SpeechError error) {
        	toPrint("onError error=" + "(" + error.code + ")" + error.description + "--utteranceId=" + utteranceId);
        }

    };
    
	private void showTip(final String str) {
		Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
	}

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            String message = (String) msg.obj;
            if (message != null) {
                Log.d(TAG, message);
                showTip(message);
            }
        }
    };

    private void toPrint(String str) {
        Message msg = Message.obtain();
        msg.obj = str;
        this.mHandler.sendMessage(msg);
    }

}


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved