智能语音技术

如今越来越多的app用到了语音播报功能,例如地图导航、天气预报、文字阅读、口语训练等等。语音技术主要分两块,一块是语音转文字,即语音识别;另一块是文字转语音,即语音合成。

对中文来说,和语音播报相关的一个技术是汉字转拼音,想想看,拼音本身就是音节拼读的标记,每个音节对应一段音频,那么一句的拼音便能用一连串的音频流合成而来。汉字转拼音的说明参见《 Android开发笔记(八十三)多语言支持》。

语音合成通常也简称为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;@Overrideprotected 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<String> starAdapter = new ArrayAdapter<String>(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<mEngineList.size(); i++) {mEngineArray[i] = mEngineList.get(i).label;}ArrayAdapter<String> starAdapter = new ArrayAdapter<String>(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);}@Overrideprotected 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) {}}@Overridepublic 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<EngineInfo> 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;// 语音听写UIprivate RecognizerDialog mRecognizeDialog;// 用HashMap存储听写结果private HashMap<String, String> mRecognizeResults = new LinkedHashMap<String, String>();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);}@Overrideprotected void onDestroy() {super.onDestroy();// 退出时释放连接mRecognize.cancel();mRecognize.destroy();}@Overridepublic 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 或者 onErrormResultText.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() {@Overridepublic void onInit(int code) {Log.d(TAG, "SpeechRecognizer init() code = " + code);if (code != ErrorCode.SUCCESS) {showTip("初始化失败,错误码:" + code);}}};//听写监听器private RecognizerListener mRecognizeListener = new RecognizerListener() {@Overridepublic void onBeginOfSpeech() {// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入showTip("开始说话");}@Overridepublic void onError(SpeechError error) {// 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。// 如果使用本地功能(语记)需要提示用户开启语记的录音权限。showTip(error.getPlainDescription(true));}@Overridepublic void onEndOfSpeech() {// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入showTip("结束说话");}@Overridepublic void onResult(RecognizerResult results, boolean isLast) {Log.d(TAG, results.getResultString());printResult(results);if (isLast) {// TODO 最后的结果}}@Overridepublic void onVolumeChanged(int volume, byte[] data) {showTip("当前正在说话,音量大小:" + volume);Log.d(TAG, "返回音频数据:"+data.length);}@Overridepublic 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);}@Overrideprotected void onDestroy() {super.onDestroy();// 退出时释放连接mCompose.stopSpeaking();mCompose.destroy();}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onSpeakBegin() {showTip("开始播放");}@Overridepublic void onSpeakPaused() {showTip("暂停播放");}@Overridepublic void onSpeakResumed() {showTip("继续播放");}@Overridepublic void onBufferProgress(int percent, int beginPos, int endPos, String info) {// 合成进度mPercentForBuffering = percent;showTip(String.format(getString(R.string.xf_compose_toast_format),mPercentForBuffering, mPercentForPlaying));}@Overridepublic void onSpeakProgress(int percent, int beginPos, int endPos) {// 播放进度mPercentForPlaying = percent;showTip(String.format(getString(R.string.xf_compose_toast_format),mPercentForBuffering, mPercentForPlaying));}@Overridepublic void onCompleted(SpeechError error) {if (error == null) {showTip("播放完成");} else if (error != null) {showTip(error.getPlainDescription(true));}}@Overridepublic 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"));// 设置播放合成音频打断音乐播放,默认为truemCompose.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;@Overridepublic 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));}@Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {return true;}
}

下面是PreferenceFragment的布局示例:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <ListPreferenceandroid:key="recognize_language_preference"android:title="语言设置"android:entries="@array/language_entries"android:entryValues="@array/language_values"android:summary="支持:普通话,粤语,河南话,英语 "android:defaultValue="mandarin"  /><EditTextPreferenceandroid:key="recognize_vadbos_preference"   android:title="前端点超时" android:dialogTitle="请输入时间(0-10000)ms"android:summary="默认值:短信转写5000,其他4000"android:defaultValue="5000" /><EditTextPreferenceandroid:key="recognize_vadeos_preference"   android:title="后端点超时" android:dialogTitle="请输入时间(0-10000)ms"android:summary="默认值:短信转写1800,其他700 "android:defaultValue="1800" /><ListPreferenceandroid:key="recognize_punc_preference"android:title="标点符号"android:entries="@array/punc_entries"android:entryValues="@array/punc_values"android:summary="默认值:有标点 "android:defaultValue="1"  /><CheckBoxPreferenceandroid:key="show_dialog"android:title="显示听写界面"android:defaultValue="true" /></PreferenceScreen>

百度语音

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部分例子如下:

        <meta-data android:name="com.baidu.speech.APP_ID" android:value="8282403"/><meta-data android:name="com.baidu.speech.API_KEY" android:value="M2OT6nhn1beu4IxI5GqQk4ev"/><meta-data android:name="com.baidu.speech.SECRET_KEY" android:value="6e448840e00a12881c6d63346771caa5"/><service android:name="com.baidu.speech.VoiceRecognitionService" android:exported="false" /><activityandroid:name="com.baidu.voicerecognition.android.ui.BaiduASRDigitalDialog"android:configChanges="orientation|keyboardHidden|screenLayout"android:theme="@android:style/Theme.Dialog"android:exported="false"android:screenOrientation="portrait"><intent-filter><action android:name="com.baidu.action.RECOGNIZE_SPEECH" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></activity>

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;@Overrideprotected 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);}@Overrideprotected void onDestroy() {speechRecognizer.destroy();super.onDestroy();}@Overrideprotected 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() {@Overridepublic void onReadyForSpeech(Bundle params) {status = STATUS_Ready;print("准备就绪,可以开始说话");}@Overridepublic void onBeginningOfSpeech() {status = STATUS_Speaking;btnStart.setText("说完了");print("检测到用户已经开始说话");}@Overridepublic void onRmsChanged(float rmsdB) {}@Overridepublic void onBufferReceived(byte[] buffer) {}@Overridepublic void onEndOfSpeech() {speechEndTime = System.currentTimeMillis();status = STATUS_Recognition;print("检测到用户已经停止说话");btnStart.setText("识别中");}@Overridepublic 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("开始");}@Overridepublic void onResults(Bundle results) {long end2finish = System.currentTimeMillis() - speechEndTime;ArrayList<String> 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("开始");}@Overridepublic void onPartialResults(Bundle partialResults) {ArrayList<String> nbest = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);if (nbest.size() > 0) {print("~临时识别结果:" + Arrays.toString(nbest.toArray(new String[0])));txtResult.setText(nbest.get(0));}}@Overridepublic 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);}@Overridepublic 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);}@Overrideprotected void onDestroy() {// 退出时释放连接mSpeechSynthesizer.release();super.onDestroy();}@Overridepublic 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();}}@Overridepublic 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() {@Overridepublic void onSynthesizeStart(String utteranceId) {toPrint("onSynthesizeStart utteranceId=" + utteranceId);}@Overridepublic void onSynthesizeDataArrived(String utteranceId, byte[] data, int progress) {// toPrint("onSynthesizeDataArrived");}@Overridepublic void onSynthesizeFinish(String utteranceId) {toPrint("onSynthesizeFinish utteranceId=" + utteranceId);}@Overridepublic void onSpeechStart(String utteranceId) {toPrint("onSpeechStart utteranceId=" + utteranceId);}@Overridepublic void onSpeechProgressChanged(String utteranceId, int progress) {// toPrint("onSpeechProgressChanged");}@Overridepublic void onSpeechFinish(String utteranceId) {toPrint("onSpeechFinish utteranceId=" + utteranceId);}@Overridepublic 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() {@Overridepublic 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);}}

点此查看Android开发笔记的完整目录

Android开发笔记(一百零八)智能语音相关推荐

  1. Android开发笔记(序)写在前面的目录

    知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经验教训,与网友互相切磋,从而去芜存菁进一步提升自己的水平.因此博主就想,入门的东西咱就不写了,人不能老停留在入 ...

  2. Android开发笔记(序)

    本开发笔记,借鉴与其他开发者整理的文章范例与心得体会.在这里作为开发过程中的一个总结与笔记式记录. 如有侵犯作者权益,请及时联系告知删除.俗话说:集百家成一言,去粕成金. ************** ...

  3. Android开发笔记(序)写在前面的目录大全

    转自  湖前琴亭 的博客https://blog.csdn.net/aqi00/article/details/50012511 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面 ...

  4. Andriod开发之二十:Android开发笔记(序)写在前面的目录

    https://blog.csdn.net/aqi00/article/details/50038385 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经验教 ...

  5. Android开发笔记(一百二十六)自定义音乐播放器

    MediaRecorder/MediaPlayer 在Android手机上面,音频的处理比视频还要复杂,这真是出人意料.在前面的博文< Android开发笔记(五十七)录像录音与播放>中, ...

  6. Android开发笔记(一百五十)自动识别验证码图片

    若问目前IT领域最炙手可热的技术方向,必属人工智能(简称AI)无疑.前有谷歌的阿法狗完胜围棋世界冠军柯洁,后有微软小冰出版了诗集<阳光失了玻璃窗>,一时间沸沸扬扬,似乎人工智能无所不能,从 ...

  7. Android开发笔记(一百四十六)仿支付宝的支付密码输入框

    编辑框EditText算是Android的一个基础控件了,表面上看,EditText只负责接收用户手工输入的文本:可实际上,要把这看似简单的文本输入做得方便易用,并不是一个简单的事情.因为用户可能希望 ...

  8. Android开发笔记(一百零三)地图与定位SDK

    集成地图SDK 国内常用的地图SDK就是百度和高德了,二者的用法大同小异,可按照官网上的开发指南一步步来.下面是我在集成地图SDK时遇到的问题说明: 1.点击基本地图功能选项,不能打开地图,弹出&qu ...

  9. Android开发笔记(六十一)文件下载管理DownloadManager

    下载管理DownloadManager 文件下载其实是网络数据访问的一种特殊形式,使用普通的http请求也能完成,就是实现起来会繁琐一些.因为下载功能比较常用,而且业务功能相对统一,所以从Androi ...

最新文章

  1. Linux 打包及压缩命令使用方法总结
  2. 2021-1-17 随笔
  3. 在Linux下安装Apache
  4. CVPR 2020 《12-in-1: Multi-Task Vision and Language Representation Learning》论文笔记
  5. Spark Streaming(二)Flume
  6. angular4更改表单中显示的值_angular4 Form表单相关
  7. Linux网络编程——I/O复用函数之epoll
  8. 基于Docker搭建分布式消息队列Kafka
  9. Python官方文档学习心得(第五篇)
  10. python 库总结_最近用的python 库总结
  11. 使用 CSS 模拟鼠标点击交互
  12. Oracle如何查看表空间的大小及使用情况
  13. 优酷的视频地址 java_查找各大视频网站真实视频地址方法(乐视优酷会员腾讯56酷6pptv激动网)...
  14. linux系统设置自动拨号上网
  15. selenium 模拟登陆 古诗文网 含验证码
  16. 小酌重构系列[10]——分离职责
  17. 软件设计师2018下半年上午真题解析
  18. 清华最新发布的毕业生去向,够卷!
  19. 野火stm32开发板给定一个脉冲程序_STM32开发板哪个好,推荐一款高性价比stm32MP157开发板...
  20. 机器学习中精确率(precision)、召回率(recall)和准确率(accuracy)的理解

热门文章

  1. Linux 编译Aria2c最新版本
  2. php 查询功能,php实现查询功能的方法
  3. 计算机专业综合素质试题答案,2015中学教师资格证考试试题及答案《综合素质》(4)...
  4. 北京航空航天大学计算机系考研复试上机真题及答案---2014
  5. 洛谷P2455 [SDOI2006]线性方程组
  6. Java 6-1 人口统计
  7. 华为云点学堂为你揭开DevOps转型的秘密
  8. selenium之浏览器弹出新窗口并在新窗口操作
  9. VUE之组件(Props特性深析)
  10. UE4 通过按键升降电梯