<b>Android TTS</b><b>實作簡單閱讀器</b>
簡單的Txt文本閱讀器,主要用于介紹Google Android的TTS接口。
<b>一、</b><b>TTS</b>
在package android.speech.tts内,主要閱讀下TextToSpeech.OnInitListener、TextToSpeech. OnUtteranceCompletedListener兩個接口和TextToSpeech、TextToSpeech.Engine兩個類。
具體還是自己去看下SDK文檔吧。(我也是完整閱讀過了的^^)
<b>二、</b><b>TTS</b><b>引擎</b>
而Android的SDK中還提供了TTS服務的接口,用于供應商提供服務的。也就是語音合成服務商隻管提供它的服務,開發者隻管使用Android的TTS接口,使用者自己安裝想要的服務自己進行選擇。
<b>1</b><b>)關于訊飛(貌似廣告?)</b>
好吧,少說點了,它也提供了個開發者平台。如下:
有試了下它那語音分析,話說,彈出的框框能不能好看點啊。(做個小話筒就好了麼T^T)
恩,還有,現在訊飛是要開始宣傳了麼?貌似3月22日什麼開發者大會-_-!(又廣告了?)
<b>2</b><b>)其他中文引擎</b>
參見文章:Android中文語音合成(TTS)各家引擎對比。(原網址打不開==,另外的網址就不貼了,搜下吧)
<b>三、閱讀器工程</b>
現在學乖了,直接貼些代碼得了==。代碼中注釋應該滿清晰詳細了^^。
<b>1</b><b>)界面布局</b>
布局由main.xml includes header.xml & footer.xml組成,并寫有了定時收起等。
TtsFatherActivity.java
/**
* 1)本類用于main.xml的布局控制。子類再去實作各控件的TTS相關功能。
* 2)用繼承方式實作是為了利用布局中控件的onClick屬性(懶得多寫代碼==!)。
*/
public abstract class TtsFatherActivity extends Activity {
private GestureDetector gd; // 手勢檢測
private GlobalUtil globalUtil; // 全局公用類
private ScrollView scrollView; // 滾動視圖
private LinearLayout headerLayout, footerLayout; // 頂部、底部布局
private TextView textView; // 文本标簽
private static final long ANIM_DURATION = 500; // 動畫時間(毫秒)
private static final int DIALOG_TEXT_LIST = 0; // 文本清單對話框id
private final String[] textPaths = new String[] { "one.txt", "two.txt",
"浏覽..." }; // assets内文本資源路徑
protected String textTitle; // 文本标題
protected String textContent; // 文本内容
private Timer timer; // 計時器
private static final long TIMEOUT = 2000; // 逾時時間
private static final int TIMER_LAYOUT_OUT = 1; // 布局收起
private boolean isLayoutOut = false; // 布局收起狀态
/** Handler處理操作 */
public Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TIMER_LAYOUT_OUT:
/* headerLayout收起動畫 */
globalUtil.startTransAnim(headerLayout,
GlobalUtil.AnimMode.UP_OUT, ANIM_DURATION);
headerLayout.setVisibility(View.GONE);
/* footerLayout收起動畫 */
globalUtil.startTransAnim(footerLayout,
GlobalUtil.AnimMode.DOWN_OUT, ANIM_DURATION);
footerLayout.setVisibility(View.GONE);
isLayoutOut = true; // 重置布局收起狀态
break;
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gd = new GestureDetector(new MySimpleGesture()); // 手勢檢測處理
globalUtil = GlobalUtil.getInstance(); // 擷取全局公用類
scrollView = (ScrollView) findViewById(R.id.scrollView); // 擷取滾動視圖
headerLayout = (LinearLayout) findViewById(R.id.headerLayout); // 擷取頂部布局
footerLayout = (LinearLayout) findViewById(R.id.footerLayout); // 擷取底部布局
textView = (TextView) findViewById(R.id.textView);
setText(0); // 預設顯示“上邪.txt”
newTimerLayoutOut(); // 定時收起布局
}
/** 使用GestureDetector檢測手勢(ScrollView内也需監聽時的方式) */
public boolean dispatchTouchEvent(MotionEvent ev) {
gd.onTouchEvent(ev);
scrollView.onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
/** onCreateDialog */
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_TEXT_LIST:
return new AlertDialog.Builder(this).setItems(textPaths,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (2 == which) {
// 跳轉到檔案浏覽Activity
startActivityForResult(new Intent(
TtsFatherActivity.this,
FileBrowserActivity.class),
FileBrowserActivity.CODE_FILE_BROWSER);
} else {
setText(which); // 設定文本内容
}
}
}).create();
return super.onCreateDialog(id);
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FileBrowserActivity.CODE_FILE_BROWSER) {
if (resultCode == RESULT_OK) {
// 獲得檔案名稱
String filename = data.getExtras().getString(
FileBrowserActivity.KEY_FILENAME);
this.textTitle = filename;
try {
// FileInputStream fis = new FileInputStream(
// new File(filename));
// // FileInputStream不支援mark/reset操作,不該直接這樣
// String encoding = globalUtil.getIsEncoding(fis);
// textContent = globalUtil.is2Str(fis, encoding);
/**
* TXT簡單判斷編碼類型後轉字元串
*
* ps:
* 1)扯淡,3.58MB的txt讀出來了==
* 看來需要轉成BufferedReader以readLine()方式讀好些啊
*
* 2)TextView将大文本全部顯示,這貌似...
* 時間主要花費在文本顯示過程,不改進了,暫時将就吧==
* 2.1)用View自定義個控件顯示文本也蠻久的,未減少多少時間。
* 2.2)至于AsyncTask,文本顯示還是要在UI線程的==。
* 如果我們要仿個閱讀器,用View自定義個控件還是必須的。
* 1)分段讀取大文本,可以考慮3段(前後兩段用于緩沖)
* 根據滑屏&顯示内容等,注意文本顯示銜接。
* 2)滾動條可以外面套個ScrollView。由各屬性判斷出大文本需要顯示的高度,
* 重寫onMeasure用setMeasuredDimension()設定好,才會有滾動條。
* 當然自己用scrollTo()、scrollBy()實作動畫也是好的。
* 3)至于其他選中目前行啊什麼的,慢慢寫就成了...
* 不知道大家還有什麼好的想法沒?
*/
// long time1 = System.currentTimeMillis();
textContent = globalUtil.is2Str(new FileInputStream(
new File(filename)));
// long time2 = System.currentTimeMillis();
// Log.e("TAG1", "==" + (time2 - time1) + "==");
textView.setText(textContent);
// long time3 = System.currentTimeMillis();
// Log.e("TAG1", "==" + (time3 - time2) + "==");
} catch (Exception e) {
textView.setText(R.string.text_error);
textContent = "";
}
/** 設定文本内容 */
private void setText(int textIndex) {
this.textTitle = textPaths[textIndex];
try {
textContent = globalUtil.is2Str(getAssets().open(textTitle),
"UTF-8");
textView.setText(textContent);
} catch (IOException e) {
textView.setText(R.string.text_error);
textContent = "";
/** 定時收起布局(已定時時重新開始定時) */
protected void newTimerLayoutOut() {
if (null != timer) {
timer.cancel();
timer = new Timer();
// 逾時TIMEOUT退出
timer.schedule(new TimerTask() {
@Override
public void run() {
mHandler.sendEmptyMessage(TIMER_LAYOUT_OUT);
}, TIMEOUT);
/** 自定義手勢類 */
private class MySimpleGesture extends SimpleOnGestureListener {
/** 輕按兩下第二下 */
public boolean onDoubleTap(MotionEvent e) {
if (isLayoutOut) {
/* headerLayout進入動畫 */
headerLayout.setVisibility(View.VISIBLE);
GlobalUtil.AnimMode.UP_IN, ANIM_DURATION);
/* footerLayout進入動畫 */
footerLayout.setVisibility(View.VISIBLE);
GlobalUtil.AnimMode.DOWN_IN, ANIM_DURATION);
newTimerLayoutOut(); // 定時收起布局
isLayoutOut = false; // 重置布局收起狀态
} else {
/* headerLayout退出動畫 */
/* footerLayout退出動畫 */
// 取消定時收起動畫
if (null != timer) {
timer.cancel();
return false;
/** 長按螢幕時 */
public void onLongPress(MotionEvent e) {
// 顯示文本清單對話框
showDialog(DIALOG_TEXT_LIST);
}
<b>2</b><b>)TTS</b><b>控制</b>
音量&語速控制也寫了的^^。
TtsSampleActivity.java<b></b>
public class TtsSampleActivity extends TtsFatherActivity implements
OnSeekBarChangeListener, TextToSpeech.OnInitListener,
TextToSpeech.OnUtteranceCompletedListener {
// private static final String TAG = "TtsSampleActivity"; // 日志标記
private AudioManager audioManager; // 音頻管理對象
// TTS音量類型(AudioManager.STREAM_MUSIC = AudioManager.STREAM_TTS = 11)
private static final int STREAM_TTS = AudioManager.STREAM_MUSIC;
private TextToSpeech mTts; // TTS對象
private static final int REQ_CHECK_TTS_DATA = 110; // TTS資料校驗請求值
private boolean isSetting = false; // 進入設定标記
private boolean isRateChanged = false; // 速率改變标記
private boolean isStopped = false; // TTS引擎停止發聲标記
private float mSpeechRate = 1.0f; // 朗讀速率
private SeekBar volumeBar, speedBar; // 音量&語速
// 合成聲音資源檔案的路徑
private static final String SAVE_DIR_PATH = "/sdcard/AndroidTTS/";
private static final String SAVE_FILE_PATH = SAVE_DIR_PATH + "sound.wav";
// 獲得音頻管理對象
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
/* volumeBar */
volumeBar = (SeekBar) findViewById(R.id.volumeBar);
volumeBar.setOnSeekBarChangeListener(this);
// 由目前音量設定進度(需保證進度上限=音頻上限=15,否則按比例設定)
volumeBar.setProgress(audioManager.getStreamVolume(STREAM_TTS));
/* speedBar */
speedBar = (SeekBar) findViewById(R.id.speedBar);
speedBar.setOnSeekBarChangeListener(this);
initDirs(SAVE_DIR_PATH); // 初始化檔案夾路徑
/** saveFileBtn點選事件 */
public void saveFile(View v) {
// 将文本合成聲音資源檔案
int resId = TextToSpeech.SUCCESS == ttsSaveFile(textContent,
SAVE_FILE_PATH) ? R.string.synt_success : R.string.synt_fail;
Toast.makeText(this, resId, Toast.LENGTH_SHORT).show(); // Toast提示
newTimerLayoutOut(); // 重新定時收起布局
/** playFileBtn點選事件 */
public void playFile(View v) {
ttsPlayFile(SAVE_FILE_PATH); // 播放指定的使用檔案
/** stopBtn點選事件 */
public void stop(View v) {
ttsStop(); // 停止目前發聲
/** playBtn點選事件 */
public void play(View v) {
ttsPlay(); // tts合成語音播放
/** settingBtn點選事件 */
public void setting(View v) {
// 跳轉到“語音輸入與輸出”設定界面&設定标志位
isSetting = toTtsSettings();
/** SeekBar進度改變時 */
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
switch (seekBar.getId()) {
case R.id.volumeBar:
// 由設定目前TTS音量(需保證進度上限=音頻上限=15,否則按比例設定)
audioManager.setStreamVolume(STREAM_TTS, progress, 0);
break;
case R.id.speedBar:
/* 需要重新綁定TTS引擎,速度在onInit()裡設定 */
isRateChanged = true; // 速率改變标記
// 最大值為20時,以下方式計算為0.5~2倍速
mSpeechRate = (progress >= 10) ? (progress / 10f)
: (0.5f + progress / 20f);
// 校驗TTS引擎安裝及資源狀态,重新綁定引擎
checkTtsData();
/** SeekBar開始拖動時 */
public void onStartTrackingTouch(SeekBar seekBar) {
/** SeekBar結束拖動時 */
public void onStopTrackingTouch(SeekBar seekBar) {
/**
* TTS引擎初始化時回調方法
*
* 引擎相關參數(音量、語速)等都需在這設定。
* 1)建立完成後再去設定,會有意外的效果^^
* 2)音量也可由AudioManager進行控制(和音樂一個媒體流類型)
*/
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
mTts.setSpeechRate(mSpeechRate); // 設定朗讀速率
// 設定發聲合成監聽,注意也需要在onInit()中做才有效
mTts.setOnUtteranceCompletedListener(this);
if (isRateChanged) {
ttsPlay(); // tts合成語音播放
isRateChanged = false; // 重置标記位
/**
* TTS引擎完成發聲完成時回調方法
* 1)stop()取消時也會回調
* 2)需在onInit()内設定接口
* 3)utteranceId由speak()時的請求參數設定
* 參數key:TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID
public void onUtteranceCompleted(final String utteranceId) {
/* 測試該接口的Toast提示 */
runOnUiThread(new Runnable() {
int resId = isStopped ? R.string.utte_stopped
: R.string.utte_completed;
// 提示文本發生完成
Toast.makeText(getApplicationContext(),
getString(resId, utteranceId), Toast.LENGTH_SHORT)
.show();
});
/** onActivityResult */
if (requestCode == REQ_CHECK_TTS_DATA) {
switch (resultCode) {
case TextToSpeech.Engine.CHECK_VOICE_DATA_PASS: // TTS引擎可用
// 針對于重新綁定引擎,需要先shutdown()
if (null != mTts) {
ttsStop(); // 停止目前發聲
ttsShutDown(); // 釋放資源
mTts = new TextToSpeech(this, this); // 建立TextToSpeech對象
case TextToSpeech.Engine.CHECK_VOICE_DATA_BAD_DATA: // 資料錯誤
case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_DATA: // 缺失資料資源
case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_VOLUME: // 缺少資料存儲量
notifyReinstallDialog(); // 提示使用者是否重裝TTS引擎資料的對話框
case TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL: // 檢查失敗
default:
super.onActivityResult(requestCode, resultCode, data);
/** 校驗TTS引擎安裝及資源狀态 */
private boolean checkTtsData() {
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, REQ_CHECK_TTS_DATA);
return true;
} catch (ActivityNotFoundException e) {
/** 提示使用者是否重裝TTS引擎資料的對話框 */
private void notifyReinstallDialog() {
new AlertDialog.Builder(this).setTitle("TTS引擎資料錯誤")
.setMessage("是否嘗試重裝TTS引擎資料到裝置上?")
.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 觸發引擎在TTS引擎在裝置上安裝資源檔案
Intent dataIntent = new Intent();
dataIntent
.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(dataIntent);
}
}).setNegativeButton("否", null).show();
/** 跳轉到“語音輸入與輸出”設定界面 */
private boolean toTtsSettings() {
startActivity(new Intent("com.android.settings.TTS_SETTINGS"));
protected void onStart() {
checkTtsData(); // 校驗TTS引擎安裝及資源狀态
super.onStart();
protected void onResume() {
/* 從設定傳回後重新綁定TTS,避免仍用舊引擎 */
if (isSetting) {
checkTtsData(); // 校驗TTS引擎安裝及資源狀态
isSetting = false;
super.onResume();
protected void onStop() {
/* HOME鍵 */
super.onStop();
public void onBackPressed() {
/* BACK鍵 */
ttsShutDown(); // 釋放資源
super.onBackPressed();
/** tts合成語音播放 */
private int ttsPlay() {
if (null != mTts) {
isStopped = false; // 設定标記
/**
* 叙述text。
*
* 1) 參數2(int queueMode)
* 1.1)QUEUE_ADD:增加模式。增加在隊列尾,繼續原來的說話。
* 1.2)QUEUE_FLUSH:重新整理模式。中斷正在進行的說話,說新的内容。
* 2)參數3(HashMap<String, String> params)
* 2.1)請求的參數,可以為null。
* 2.2)注意KEY_PARAM_UTTERANCE_ID。
*/
HashMap<String, String> params = new HashMap<String, String>();
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, textTitle);
return mTts.speak(textContent, TextToSpeech.QUEUE_FLUSH, params);
return TextToSpeech.ERROR;
// /** 判斷TTS是否正在發聲 */
// private boolean isSpeaking() {
// // 使用mTts.isSpeaking()判斷時,第一次speak()傳回true,多次就傳回false了。
// return audioManager.isMusicActive();
// }
/** 停止目前發聲,同時放棄所有在等待隊列的發聲 */
private int ttsStop() {
isStopped = true; // 設定标記
return (null == mTts) ? TextToSpeech.ERROR : mTts.stop();
/** 釋放資源(解除語音服務綁定) */
private void ttsShutDown() {
mTts.shutdown();
/** 初始化檔案夾路徑 */
private void initDirs(final String dirpath) {
File file = new File(dirpath);
if (!file.exists()) {
file.mkdirs();
/** 将文本合成聲音資源檔案 */
private int ttsSaveFile(String text, final String filename) {
return (null == mTts) ? TextToSpeech.ERROR : mTts.synthesizeToFile(
text, null, filename);
/** 播放指定的使用檔案 */
private int ttsPlayFile(final String filename) {
// 如果存在FILENAME_SAVE檔案的話
if (new File(filename).exists()) {
try {
/* 使用MediaPlayer進行播放(沒進行控制==) */
MediaPlayer player = new MediaPlayer();
player.setDataSource(filename);
player.prepare();
player.start();
return TextToSpeech.SUCCESS;
} catch (Exception e) {
e.printStackTrace();
return TextToSpeech.ERROR;
本文轉自winorlose2000 51CTO部落格,原文連結:http://blog.51cto.com/vaero/809873,如需轉載請自行聯系原作者