本文原创版权归 51CTO winorlose2000 所有,转载请按如下方式于文章显示位置详细标明原创作者及出处,以示尊重!!
作者:winorlose2000
Android TTS实现简单阅读器
简单的Txt文本阅读器,主要用于介绍Google Android的TTS接口。
一、 TTS
在package android.speech.tts内,主要阅读下TextToSpeech.OnInitListener、TextToSpeech. OnUtteranceCompletedListener两个接口和TextToSpeech、TextToSpeech.Engine两个类。
具体还是自己去看下SDK文档吧。(我也是完整阅读过了的^^)
二、 TTS 引擎
以前在网上的例子,或者就我《 Android基础样例》里的中文TTS例子,都是eSpeak引擎实现的。这种方式是要用其封装的TTS接口,再通过下载TTS数据使用。
而Android的SDK中还提供了TTS服务的接口,用于供应商提供服务的。也就是语音合成服务商只管提供它的服务,开发者只管使用Android的TTS接口,用户自己安装想要的服务自己进行选择。
总之呢,我用的是讯飞语音TTS v1.0。有两个文件,一个是Service程序,一个是语音数据。下载网址:http://soft.shouji.com.cn/down/22160.html
1 )关于讯飞(貌似广告?)
好吧,少说点了,它也提供了个开发者平台。如下:
讯飞语音云: http://dev.voicecloud.cn/download.php?vt=1
有试了下它那语音分析,话说,弹出的框框能不能好看点啊。(做个小话筒就好了么T^T)
恩,还有,现在讯飞是要开始宣传了么?貌似3月22日什么开发者大会-_-!(又广告了?)
2 )其他中文引擎
参见文章:Android中文语音合成(TTS)各家引擎对比。(原网址打不开==,另外的网址就不贴了,搜下吧)
三、阅读器工程
现在学乖了,直接贴些代码得了==。代码中注释应该满清晰详细了^^。
1 )界面布局
布局由main.xml includes header.xml & footer.xml组成,并写有了定时收起等。
TtsFatherActivity.java
1. /**
2. * 1)本类用于main.xml的布局控制。子类再去实现各控件的TTS相关功能。
3. * 2)用继承方式实现是为了利用布局中控件的onClick属性(懒得多写代码==!)。
4. */
5. public abstract class TtsFatherActivity extends
6.
7. private GestureDetector gd; // 手势检测
8. private GlobalUtil globalUtil; // 全局公用类
9.
10. private ScrollView scrollView; // 滚动视图
11. private LinearLayout headerLayout, footerLayout; // 顶部、底部布局
12. private TextView textView; // 文本标签
13.
14. private static final long ANIM_DURATION = 500; // 动画时间(毫秒)
15.
16. private static final int DIALOG_TEXT_LIST = 0; // 文本列表对话框id
17. private final String[] textPaths = new String[] { "one.txt", "two.txt",
18. "浏览..." }; // assets内文本资源路径
19.
20. protected String textTitle; // 文本标题
21. protected String textContent; // 文本内容
22.
23. private Timer timer; // 计时器
24. private static final long TIMEOUT = 2000; // 超时时间
25. private static final int TIMER_LAYOUT_OUT = 1; // 布局收起
26.
27. private boolean isLayoutOut = false; // 布局收起状态
28.
29. /** Handler处理操作 */
30. public Handler mHandler = new
31. @Override
32. public void
33. switch
34. case
35. /* headerLayout收起动画 */
36. globalUtil.startTransAnim(headerLayout,
37. GlobalUtil.AnimMode.UP_OUT, ANIM_DURATION);
38. headerLayout.setVisibility(View.GONE);
39. /* footerLayout收起动画 */
40. globalUtil.startTransAnim(footerLayout,
41. GlobalUtil.AnimMode.DOWN_OUT, ANIM_DURATION);
42. footerLayout.setVisibility(View.GONE);
43. true; // 重置布局收起状态
44. break;
45. }
46. }
47. };
48.
49. @Override
50. public void
51. super.onCreate(savedInstanceState);
52. setContentView(R.layout.main);
53.
54. new GestureDetector(new MySimpleGesture()); // 手势检测处理
55. // 获取全局公用类
56.
57. // 获取滚动视图
58. // 获取顶部布局
59. // 获取底部布局
60. textView = (TextView) findViewById(R.id.textView);
61.
62. 0); // 默认显示“上邪.txt”
63. // 定时收起布局
64. }
65.
66. /** 使用GestureDetector检测手势(ScrollView内也需监听时的方式) */
67. @Override
68. public boolean
69. gd.onTouchEvent(ev);
70. scrollView.onTouchEvent(ev);
71. return super.dispatchTouchEvent(ev);
72. }
73.
74. /** onCreateDialog */
75. @Override
76. protected Dialog onCreateDialog(int
77. switch
78. case
79. return new AlertDialog.Builder(this).setItems(textPaths,
80. new
81. @Override
82. public void onClick(DialogInterface dialog, int
83. if (2
84. // 跳转到文件浏览Activity
85. new
86. this,
87. class),
88. FileBrowserActivity.CODE_FILE_BROWSER);
89. else
90. // 设置文本内容
91. }
92. }
93. }).create();
94. }
95. return super.onCreateDialog(id);
96. }
97.
98. @Override
99. protected void onActivityResult(int requestCode, int
100. if
101. if
102. // 获得文件名称
103. String filename = data.getExtras().getString(
104. FileBrowserActivity.KEY_FILENAME);
105. this.textTitle = filename;
106. try
107. // FileInputStream fis = new FileInputStream(
108. // new File(filename));
109. // // FileInputStream不支持mark/reset操作,不该直接这样
110. // String encoding = globalUtil.getIsEncoding(fis);
111. // textContent = globalUtil.is2Str(fis, encoding);
112. /**
113. * TXT简单判断编码类型后转字符串
114. *
115. * ps:
116. * 1)扯淡,3.58MB的txt读出来了==
117. * 看来需要转成BufferedReader以readLine()方式读好些啊
118. *
119. * 2)TextView将大文本全部显示,这貌似...
120. *
121. * 时间主要花费在文本显示过程,不改进了,暂时将就吧==
122. * 2.1)用View自定义个控件显示文本也蛮久的,未减少多少时间。
123. * 2.2)至于AsyncTask,文本显示还是要在UI线程的==。
124. *
125. * 如果我们要仿个阅读器,用View自定义个控件还是必须的。
126. * 1)分段读取大文本,可以考虑3段(前后两段用于缓冲)
127. * 根据滑屏&显示内容等,注意文本显示衔接。
128. * 2)滚动条可以外面套个ScrollView。由各属性判断出大文本需要显示的高度,
129. * 重写onMeasure用setMeasuredDimension()设置好,才会有滚动条。
130. * 当然自己用scrollTo()、scrollBy()实现动画也是好的。
131. * 3)至于其他选中当前行啊什么的,慢慢写就成了...
132. *
133. * 不知道大家还有什么好的想法没?
134. */
135. // long time1 = System.currentTimeMillis();
136. new
137. new
138. // long time2 = System.currentTimeMillis();
139. // Log.e("TAG1", "==" + (time2 - time1) + "==");
140. textView.setText(textContent);
141. // long time3 = System.currentTimeMillis();
142. // Log.e("TAG1", "==" + (time3 - time2) + "==");
143. catch
144. textView.setText(R.string.text_error);
145. "";
146. }
147. }
148. }
149. }
150.
151. /** 设置文本内容 */
152. private void setText(int
153. this.textTitle = textPaths[textIndex];
154. try
155. textContent = globalUtil.is2Str(getAssets().open(textTitle),
156. "UTF-8");
157. textView.setText(textContent);
158. catch
159. textView.setText(R.string.text_error);
160. "";
161. }
162. }
163.
164. /** 定时收起布局(已定时时重新开始定时) */
165. protected void
166. if (null
167. timer.cancel();
168. }
169. new
170. // 超时TIMEOUT退出
171. new
172. @Override
173. public void
174. mHandler.sendEmptyMessage(TIMER_LAYOUT_OUT);
175. }
176. }, TIMEOUT);
177. }
178.
179. /** 自定义手势类 */
180. private class MySimpleGesture extends
181.
182. /** 双击第二下 */
183. @Override
184. public boolean
185. if
186. /* headerLayout进入动画 */
187. headerLayout.setVisibility(View.VISIBLE);
188. globalUtil.startTransAnim(headerLayout,
189. GlobalUtil.AnimMode.UP_IN, ANIM_DURATION);
190. /* footerLayout进入动画 */
191. footerLayout.setVisibility(View.VISIBLE);
192. globalUtil.startTransAnim(footerLayout,
193. GlobalUtil.AnimMode.DOWN_IN, ANIM_DURATION);
194. // 定时收起布局
195. false; // 重置布局收起状态
196. else
197. /* headerLayout退出动画 */
198. globalUtil.startTransAnim(headerLayout,
199. GlobalUtil.AnimMode.UP_OUT, ANIM_DURATION);
200. headerLayout.setVisibility(View.GONE);
201. /* footerLayout退出动画 */
202. globalUtil.startTransAnim(footerLayout,
203. GlobalUtil.AnimMode.DOWN_OUT, ANIM_DURATION);
204. footerLayout.setVisibility(View.GONE);
205. // 取消定时收起动画
206. if (null
207. timer.cancel();
208. }
209. true; // 重置布局收起状态
210. }
211. return false;
212. }
213.
214. /** 长按屏幕时 */
215. @Override
216. public void
217. // 显示文本列表对话框
218. showDialog(DIALOG_TEXT_LIST);
219. }
220. }
221.
222. }
2 )TTS 控制
音量&语速控制也写了的^^。
TtsSampleActivity.java
1. public class TtsSampleActivity extends TtsFatherActivity implements
2. OnSeekBarChangeListener, TextToSpeech.OnInitListener,
3. TextToSpeech.OnUtteranceCompletedListener {
4.
5. // private static final String TAG = "TtsSampleActivity"; // 日志标记
6.
7. private AudioManager audioManager; // 音频管理对象
8.
9. // TTS音量类型(AudioManager.STREAM_MUSIC = AudioManager.STREAM_TTS = 11)
10. private static final int
11.
12. private TextToSpeech mTts; // TTS对象
13. private static final int REQ_CHECK_TTS_DATA = 110; // TTS数据校验请求值
14. private boolean isSetting = false; // 进入设置标记
15. private boolean isRateChanged = false; // 速率改变标记
16. private boolean isStopped = false; // TTS引擎停止发声标记
17. private float mSpeechRate = 1.0f; // 朗读速率
18.
19. private SeekBar volumeBar, speedBar; // 音量&语速
20.
21. // 合成声音资源文件的路径
22. private static final String SAVE_DIR_PATH = "/sdcard/AndroidTTS/";
23. private static final String SAVE_FILE_PATH = SAVE_DIR_PATH + "sound.wav";
24.
25. @Override
26. public void
27. super.onCreate(savedInstanceState);
28.
29. // 获得音频管理对象
30. audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
31.
32. /* volumeBar */
33. volumeBar = (SeekBar) findViewById(R.id.volumeBar);
34. this);
35. // 由当前音量设置进度(需保证进度上限=音频上限=15,否则按比例设置)
36. volumeBar.setProgress(audioManager.getStreamVolume(STREAM_TTS));
37.
38. /* speedBar */
39. speedBar = (SeekBar) findViewById(R.id.speedBar);
40. this);
41.
42. // 初始化文件夹路径
43. }
44.
45. /** saveFileBtn点击事件 */
46. public void
47. // 将文本合成声音资源文件
48. int
49. SAVE_FILE_PATH) ? R.string.synt_success : R.string.synt_fail;
50. this, resId, Toast.LENGTH_SHORT).show(); // Toast提示
51. // 重新定时收起布局
52. }
53.
54. /** playFileBtn点击事件 */
55. public void
56. // 播放指定的使用文件
57. // 重新定时收起布局
58. }
59.
60. /** stopBtn点击事件 */
61. public void
62. // 停止当前发声
63. // 重新定时收起布局
64. }
65.
66. /** playBtn点击事件 */
67. public void
68. // tts合成语音播放
69. // 重新定时收起布局
70. }
71.
72. /** settingBtn点击事件 */
73. public void
74. // 跳转到“语音输入与输出”设置界面&设置标志位
75. isSetting = toTtsSettings();
76. // 重新定时收起布局
77. }
78.
79. /** SeekBar进度改变时 */
80. @Override
81. public void onProgressChanged(SeekBar seekBar, int
82. boolean
83. switch
84. case
85. // 由设置当前TTS音量(需保证进度上限=音频上限=15,否则按比例设置)
86. 0);
87. break;
88. case
89. /* 需要重新绑定TTS引擎,速度在onInit()里设置 */
90. true; // 速率改变标记
91. // 最大值为20时,以下方式计算为0.5~2倍速
92. 10) ? (progress / 10f)
93. 0.5f + progress / 20f);
94. // 校验TTS引擎安装及资源状态,重新绑定引擎
95. checkTtsData();
96. break;
97. }
98. // 重新定时收起布局
99. }
100.
101. /** SeekBar开始拖动时 */
102. @Override
103. public void
104. }
105.
106. /** SeekBar结束拖动时 */
107. @Override
108. public void
109. }
110.
111. /**
112. * TTS引擎初始化时回调方法
113. *
114. * 引擎相关参数(音量、语速)等都需在这设置。
115. * 1)创建完成后再去设置,会有意外的效果^^
116. * 2)音量也可由AudioManager进行控制(和音乐一个媒体流类型)
117. */
118. @Override
119. public void onInit(int
120. if
121. // 设置朗读速率
122. // 设置发声合成监听,注意也需要在onInit()中做才有效
123. this);
124. if
125. // tts合成语音播放
126. false; // 重置标记位
127. }
128. }
129. }
130.
131. /**
132. * TTS引擎完成发声完成时回调方法
133. *
134. * 1)stop()取消时也会回调
135. * 2)需在onInit()内设置接口
136. * 3)utteranceId由speak()时的请求参数设定
137. * 参数key:TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID
138. */
139. @Override
140. public void onUtteranceCompleted(final
141. /* 测试该接口的Toast提示 */
142. new
143. @Override
144. public void
145. int
146. : R.string.utte_completed;
147. // 提示文本发生完成
148. Toast.makeText(getApplicationContext(),
149. getString(resId, utteranceId), Toast.LENGTH_SHORT)
150. .show();
151. }
152. });
153. }
154.
155. /** onActivityResult */
156. @Override
157. protected void onActivityResult(int requestCode, int
158. if
159. switch
160. case TextToSpeech.Engine.CHECK_VOICE_DATA_PASS: // TTS引擎可用
161. // 针对于重新绑定引擎,需要先shutdown()
162. if (null
163. // 停止当前发声
164. // 释放资源
165. }
166. new TextToSpeech(this, this); // 创建TextToSpeech对象
167. break;
168. case TextToSpeech.Engine.CHECK_VOICE_DATA_BAD_DATA: // 数据错误
169. case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_DATA: // 缺失数据资源
170. case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_VOLUME: // 缺少数据存储量
171. // 提示用户是否重装TTS引擎数据的对话框
172. break;
173. case TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL: // 检查失败
174. default:
175. break;
176. }
177. }
178. super.onActivityResult(requestCode, resultCode, data);
179. }
180.
181. /** 校验TTS引擎安装及资源状态 */
182. private boolean
183. try
184. new
185. checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
186. startActivityForResult(checkIntent, REQ_CHECK_TTS_DATA);
187. return true;
188. catch
189. return false;
190. }
191. }
192.
193. /** 提示用户是否重装TTS引擎数据的对话框 */
194. private void
195. new AlertDialog.Builder(this).setTitle("TTS引擎数据错误")
196. "是否尝试重装TTS引擎数据到设备上?")
197. "是", new
198. @Override
199. public void onClick(DialogInterface dialog, int
200. // 触发引擎在TTS引擎在设备上安装资源文件
201. new
202. dataIntent
203. .setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
204. startActivity(dataIntent);
205. }
206. "否", null).show();
207. }
208.
209. /** 跳转到“语音输入与输出”设置界面 */
210. private boolean
211. try
212. new Intent("com.android.settings.TTS_SETTINGS"));
213. return true;
214. catch
215. return false;
216. }
217. }
218.
219. @Override
220. protected void
221. // 校验TTS引擎安装及资源状态
222. super.onStart();
223. }
224.
225. @Override
226. protected void
227. /* 从设置返回后重新绑定TTS,避免仍用旧引擎 */
228. if
229. // 校验TTS引擎安装及资源状态
230. false;
231. }
232. super.onResume();
233. }
234.
235. @Override
236. protected void
237. /* HOME键 */
238. // 停止当前发声
239. super.onStop();
240. }
241.
242. @Override
243. public void
244. /* BACK键 */
245. // 停止当前发声
246. // 释放资源
247. super.onBackPressed();
248. }
249.
250. /** tts合成语音播放 */
251. private int
252. if (null
253. false; // 设置标记
254. /**
255. * 叙述text。
256. *
257. * 1) 参数2(int queueMode)
258. * 1.1)QUEUE_ADD:增加模式。增加在队列尾,继续原来的说话。
259. * 1.2)QUEUE_FLUSH:刷新模式。中断正在进行的说话,说新的内容。
260. * 2)参数3(HashMap<String, String> params)
261. * 2.1)请求的参数,可以为null。
262. * 2.2)注意KEY_PARAM_UTTERANCE_ID。
263. */
264. new
265. params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, textTitle);
266. return
267. }
268. return
269. }
270.
271. // /** 判断TTS是否正在发声 */
272. // private boolean isSpeaking() {
273. // // 使用mTts.isSpeaking()判断时,第一次speak()返回true,多次就返回false了。
274. // return audioManager.isMusicActive();
275. // }
276.
277. /** 停止当前发声,同时放弃所有在等待队列的发声 */
278. private int
279. true; // 设置标记
280. return (null
281. }
282.
283. /** 释放资源(解除语音服务绑定) */
284. private void
285. if (null
286. mTts.shutdown();
287. }
288. }
289.
290. /** 初始化文件夹路径 */
291. private void initDirs(final
292. new
293. if
294. file.mkdirs();
295. }
296. }
297.
298. /** 将文本合成声音资源文件 */
299. private int ttsSaveFile(String text, final
300. return (null
301. null, filename);
302. }
303.
304. /** 播放指定的使用文件 */
305. private int ttsPlayFile(final
306. // 如果存在FILENAME_SAVE文件的话
307. if (new
308. try
309. /* 使用MediaPlayer进行播放(没进行控制==) */
310. new
311. player.setDataSource(filename);
312. player.prepare();
313. player.start();
314. return
315. catch
316. e.printStackTrace();
317. return
318. }
319. }
320. return
321. }
322.
323. }
3)公共工具
这个也贴出来吧==。
GlobalUtil.java
1. /**
2. * @brief 全局公用类
3. * @details 各部分公用内容都较少,就丢一起了==。
4. */
5. public final class
6.
7. /** Bob Lee懒加载:内部类GlobalUtilHolder */
8. static class
9. static GlobalUtil instance = new
10. }
11.
12. /** Bob Lee懒加载:返回GlobalUtil的单例 */
13. public static
14. return
15. }
16.
17. /** 获取窗口默认显示信息 */
18. public
19. WindowManager wm = (WindowManager) mContext
20. .getSystemService(Context.WINDOW_SERVICE);
21. return
22. }
23.
24. /** 动画方式 */
25. public enum
26. UP_IN, UP_OUT, DOWN_IN, DOWN_OUT, LEFT_IN, LEFT_OUT, RIGHT_IN, RIGHT_OUT
27. };
28.
29. /**
30. * @brief 横移或竖移动画
31. *
32. * @param v 移动视图
33. * @param animMode 动画方式
34. * @param durationMillis 持续时间
35. */
36. public void startTransAnim(View v, AnimMode animMode, long
37. int w = v.getWidth(), h = v.getHeight(); // 获取移动视图宽高
38. float fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0;
39. switch
40. case
41. fromYDelta = -h;
42. break;
43. case
44. toYDelta = -h;
45. break;
46. case
47. fromYDelta = h;
48. break;
49. case
50. toYDelta = h;
51. break;
52. case
53. fromXDelta = -w;
54. break;
55. case
56. toXDelta = -w;
57. break;
58. case
59. fromXDelta = w;
60. break;
61. case
62. toXDelta = w;
63. break;
64. }
65. new
66. // 位移动画
67. // 设置时间
68. // 开始动画
69. }
70.
71. /**
72. * @brief InputStream转为String
73. *
74. * @param is 输入流
75. * @param encoding 编码方式
76. * @return 字符串结果
77. * @throws UnsupportedEncodingException 不支持的编码
78. */
79. public
80. throws
81. /*
82. * 不直接从InputStream里读byte[],再转成String,以避免截断汉字。
83. * 如utf8一个汉字3字节,用byte[1024]会截断末尾而乱码。
84. */
85. new
86. /* 以char[]方式读取 */
87. new
88. try
89. char[] b = new char[4096]; // 1024*4*2Byte
90. for (int n; (n = isReader.read(b)) != -1;) {
91. 0, n);
92. }
93. return
94. catch
95. e.printStackTrace();
96. }
97. return "";
98. }
99.
100. /**
101. * @brief 带BOM的文本的FileInputStream转为String,自动判断编码类型
102. *
103. * @param fis 文件输入流
104. * @return 字符串结果
105. * @throws UnsupportedEncodingException 不支持的编码
106. */
107. public
108. throws
109. // 转成BufferedInputStream
110. new
111. // 简单判断文本编码
112. String encoding = getIsEncoding(bis);
113. // 转成BufferedReader
114. new BufferedReader(new
115. encoding));
116. /* 以readLine()方式读取 */
117. new
118. try
119. for (String s; (s = reader.readLine()) != null;) {
120. out.append(s);
121. "\n");
122. }
123. return
124. catch
125. e.printStackTrace();
126. }
127. return "";
128. }
129.
130. /**
131. * @brief 带BOM的文本判断,否则认为GB2312(即ANSI类型的TXT)
132. * @details 复杂文件编码检测,请google cpdetector!
133. *
134. * @param is InputStream
135. * @return 编码类型
136. *
137. * @warning markSupported为true时才进行判断,否则返回默认GB2312。
138. * \n FileInputStream不支持mark/reset操作;BufferedInputStream支持此操作。
139. */
140. public
141. "GB2312";
142. // Log.e("is.markSupported()", "==" + is.markSupported() + "==");
143. if (is.markSupported()) { // 支持mark()
144. try
145. 5); // 打个TAG(5>3)
146. byte[] head = new byte[3];
147. is.read(head);
148. if (head[0] == -1 && head[1] == -2)
149. "UTF-16";
150. if (head[0] == -2 && head[1] == -1)
151. "Unicode";
152. if (head[0] == -17 && head[1] == -69 && head[2] == -65)
153. "UTF-8";
154. // 返回TAG
155. catch
156. e.printStackTrace();
157. }
158. }
159. return
160. }
161.
162. }
四、阅读器截图
1 )进入画面.png
2 )长按屏幕.png
3 )自带two.txt.png
4 )“浏览… 操作”的截图
浏览...(1).png
浏览...(2).png
浏览...(3).png
五、后记
直接读大文本,后果自负啊。(没做控制呢T^T)
ps:应用的入口图标 ,可爱吧^^。
附件下载:
>>
Android TTS实现简单阅读器.zip
作者:winorlose2000