Android TTS实现简单阅读器(一) 2012-03-18 21:10:30
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://vaero.blog.51cto.com/4350852/809873
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)关于讯飞(貌似广告?)
好吧,少说点了,它也提供了个开发者平台。如下:
有试了下它那语音分析,话说,弹出的框框能不能好看点啊。(做个小话筒就好了么T^T)
恩,还有,现在讯飞是要开始宣传了么?貌似3月22日什么开发者大会-_-!(又广告了?)
2)其他中文引擎
参见文章:Android中文语音合成(TTS)各家引擎对比。(原网址打不开==,另外的网址就不贴了,搜下吧)
三、阅读器工程
现在学乖了,直接贴些代码得了==。代码中注释应该满清晰详细了^^。
1)界面布局
布局由main.xml includes header.xml & footer.xml组成,并写有了定时收起等。
TtsFatherActivity.java
- /**
- * 1)本类用于main.xml的布局控制。子类再去实现各控件的TTS相关功能。
- * 2)用继承方式实现是为了利用布局中控件的onClick属性(懒得多写代码==!)。
- */
- publicabstractclass TtsFatherActivity extends Activity {
- private GestureDetector gd; // 手势检测
- private GlobalUtil globalUtil; // 全局公用类
- private ScrollView scrollView; // 滚动视图
- private LinearLayout headerLayout, footerLayout; // 顶部、底部布局
- private TextView textView; // 文本标签
- privatestaticfinallong ANIM_DURATION = 500; // 动画时间(毫秒)
- privatestaticfinalint DIALOG_TEXT_LIST = 0; // 文本列表对话框id
- privatefinal String[] textPaths = new String[] { "one.txt", "two.txt",
- "浏览..." }; // assets内文本资源路径
- protected String textTitle; // 文本标题
- protected String textContent; // 文本内容
- private Timer timer; // 计时器
- privatestaticfinallong TIMEOUT = 2000; // 超时时间
- privatestaticfinalint TIMER_LAYOUT_OUT = 1; // 布局收起
- privateboolean isLayoutOut = false; // 布局收起状态
- /** Handler处理操作 */
- public Handler mHandler = new Handler() {
- @Override
- publicvoid 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
- publicvoid 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内也需监听时的方式) */
- @Override
- publicboolean dispatchTouchEvent(MotionEvent ev) {
- gd.onTouchEvent(ev);
- scrollView.onTouchEvent(ev);
- returnsuper.dispatchTouchEvent(ev);
- }
- /** onCreateDialog */
- @Override
- protected Dialog onCreateDialog(int id) {
- switch (id) {
- case DIALOG_TEXT_LIST:
- returnnew AlertDialog.Builder(this).setItems(textPaths,
- new DialogInterface.OnClickListener() {
- @Override
- publicvoid onClick(DialogInterface dialog, int which) {
- if (2 == which) {
- // 跳转到文件浏览Activity
- startActivityForResult(new Intent(
- TtsFatherActivity.this,
- FileBrowserActivity.class),
- FileBrowserActivity.CODE_FILE_BROWSER);
- } else {
- setText(which); // 设置文本内容
- }
- }
- }).create();
- }
- returnsuper.onCreateDialog(id);
- }
- @Override
- protectedvoid 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 = "";
- }
- }
- }
- }
- /** 设置文本内容 */
- privatevoid 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 = "";
- }
- }
- /** 定时收起布局(已定时时重新开始定时) */
- protectedvoid newTimerLayoutOut() {
- if (null != timer) {
- timer.cancel();
- }
- timer = new Timer();
- // 超时TIMEOUT退出
- timer.schedule(new TimerTask() {
- @Override
- publicvoid run() {
- mHandler.sendEmptyMessage(TIMER_LAYOUT_OUT);
- }
- }, TIMEOUT);
- }
- /** 自定义手势类 */
- privateclass MySimpleGesture extends SimpleOnGestureListener {
- /** 双击第二下 */
- @Override
- publicboolean onDoubleTap(MotionEvent e) {
- if (isLayoutOut) {
- /* headerLayout进入动画 */
- headerLayout.setVisibility(View.VISIBLE);
- globalUtil.startTransAnim(headerLayout,
- GlobalUtil.AnimMode.UP_IN, ANIM_DURATION);
- /* footerLayout进入动画 */
- footerLayout.setVisibility(View.VISIBLE);
- globalUtil.startTransAnim(footerLayout,
- GlobalUtil.AnimMode.DOWN_IN, ANIM_DURATION);
- newTimerLayoutOut(); // 定时收起布局
- isLayoutOut = false; // 重置布局收起状态
- } else {
- /* 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);
- // 取消定时收起动画
- if (null != timer) {
- timer.cancel();
- }
- isLayoutOut = true; // 重置布局收起状态
- }
- returnfalse;
- }
- /** 长按屏幕时 */
- @Override
- publicvoid onLongPress(MotionEvent e) {
- // 显示文本列表对话框
- showDialog(DIALOG_TEXT_LIST);
- }
- }
- }
2)TTS控制
音量&语速控制也写了的^^。
TtsSampleActivity.java
- publicclass 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)
- privatestaticfinalint STREAM_TTS = AudioManager.STREAM_MUSIC;
- private TextToSpeech mTts; // TTS对象
- privatestaticfinalint REQ_CHECK_TTS_DATA = 110; // TTS数据校验请求值
- privateboolean isSetting = false; // 进入设置标记
- privateboolean isRateChanged = false; // 速率改变标记
- privateboolean isStopped = false; // TTS引擎停止发声标记
- privatefloat mSpeechRate = 1.0f; // 朗读速率
- private SeekBar volumeBar, speedBar; // 音量&语速
- // 合成声音资源文件的路径
- privatestaticfinal String SAVE_DIR_PATH = "/sdcard/AndroidTTS/";
- privatestaticfinal String SAVE_FILE_PATH = SAVE_DIR_PATH + "sound.wav";
- @Override
- publicvoid onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // 获得音频管理对象
- 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点击事件 */
- publicvoid 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点击事件 */
- publicvoid playFile(View v) {
- ttsPlayFile(SAVE_FILE_PATH); // 播放指定的使用文件
- newTimerLayoutOut(); // 重新定时收起布局
- }
- /** stopBtn点击事件 */
- publicvoid stop(View v) {
- ttsStop(); // 停止当前发声
- newTimerLayoutOut(); // 重新定时收起布局
- }
- /** playBtn点击事件 */
- publicvoid play(View v) {
- ttsPlay(); // tts合成语音播放
- newTimerLayoutOut(); // 重新定时收起布局
- }
- /** settingBtn点击事件 */
- publicvoid setting(View v) {
- // 跳转到“语音输入与输出”设置界面&设置标志位
- isSetting = toTtsSettings();
- newTimerLayoutOut(); // 重新定时收起布局
- }
- /** SeekBar进度改变时 */
- @Override
- publicvoid 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();
- break;
- }
- newTimerLayoutOut(); // 重新定时收起布局
- }
- /** SeekBar开始拖动时 */
- @Override
- publicvoid onStartTrackingTouch(SeekBar seekBar) {
- }
- /** SeekBar结束拖动时 */
- @Override
- publicvoid onStopTrackingTouch(SeekBar seekBar) {
- }
- /**
- * TTS引擎初始化时回调方法
- *
- * 引擎相关参数(音量、语速)等都需在这设置。
- * 1)创建完成后再去设置,会有意外的效果^^
- * 2)音量也可由AudioManager进行控制(和音乐一个媒体流类型)
- */
- @Override
- publicvoid 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
- */
- @Override
- publicvoid onUtteranceCompleted(final String utteranceId) {
- /* 测试该接口的Toast提示 */
- runOnUiThread(new Runnable() {
- @Override
- publicvoid run() {
- int resId = isStopped ? R.string.utte_stopped
- : R.string.utte_completed;
- // 提示文本发生完成
- Toast.makeText(getApplicationContext(),
- getString(resId, utteranceId), Toast.LENGTH_SHORT)
- .show();
- }
- });
- }
- /** onActivityResult */
- @Override
- protectedvoid onActivityResult(int requestCode, int resultCode, Intent data) {
- 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对象
- break;
- 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引擎数据的对话框
- break;
- case TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL: // 检查失败
- default:
- break;
- }
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
- /** 校验TTS引擎安装及资源状态 */
- privateboolean checkTtsData() {
- try {
- Intent checkIntent = new Intent();
- checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
- startActivityForResult(checkIntent, REQ_CHECK_TTS_DATA);
- returntrue;
- } catch (ActivityNotFoundException e) {
- returnfalse;
- }
- }
- /** 提示用户是否重装TTS引擎数据的对话框 */
- privatevoid notifyReinstallDialog() {
- new AlertDialog.Builder(this).setTitle("TTS引擎数据错误")
- .setMessage("是否尝试重装TTS引擎数据到设备上?")
- .setPositiveButton("是", new DialogInterface.OnClickListener() {
- @Override
- publicvoid onClick(DialogInterface dialog, int which) {
- // 触发引擎在TTS引擎在设备上安装资源文件
- Intent dataIntent = new Intent();
- dataIntent
- .setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
- startActivity(dataIntent);
- }
- }).setNegativeButton("否", null).show();
- }
- /** 跳转到“语音输入与输出”设置界面 */
- privateboolean toTtsSettings() {
- try {
- startActivity(new Intent("com.android.settings.TTS_SETTINGS"));
- returntrue;
- } catch (ActivityNotFoundException e) {
- returnfalse;
- }
- }
- @Override
- protectedvoid onStart() {
- checkTtsData(); // 校验TTS引擎安装及资源状态
- super.onStart();
- }
- @Override
- protectedvoid onResume() {
- /* 从设置返回后重新绑定TTS,避免仍用旧引擎 */
- if (isSetting) {
- checkTtsData(); // 校验TTS引擎安装及资源状态
- isSetting = false;
- }
- super.onResume();
- }
- @Override
- protectedvoid onStop() {
- /* HOME键 */
- ttsStop(); // 停止当前发声
- super.onStop();
- }
- @Override
- publicvoid onBackPressed() {
- /* BACK键 */
- ttsStop(); // 停止当前发声
- ttsShutDown(); // 释放资源
- super.onBackPressed();
- }
- /** tts合成语音播放 */
- privateint 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();
- // }
- /** 停止当前发声,同时放弃所有在等待队列的发声 */
- privateint ttsStop() {
- isStopped = true; // 设置标记
- return (null == mTts) ? TextToSpeech.ERROR : mTts.stop();
- }
- /** 释放资源(解除语音服务绑定) */
- privatevoid ttsShutDown() {
- if (null != mTts) {
- mTts.shutdown();
- }
- }
- /** 初始化文件夹路径 */
- privatevoid initDirs(final String dirpath) {
- File file = new File(dirpath);
- if (!file.exists()) {
- file.mkdirs();
- }
- }
- /** 将文本合成声音资源文件 */
- privateint ttsSaveFile(String text, final String filename) {
- return (null == mTts) ? TextToSpeech.ERROR : mTts.synthesizeToFile(
- text, null, filename);
- }
- /** 播放指定的使用文件 */
- privateint 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;
- }
- }
- return TextToSpeech.ERROR;
- }
- }