智能语音技术
如今越来越多的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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tts);
et_tts_resource = (EditText) findViewById(R.id.et_tts_resource);
Button btn_tts_start = (Button) findViewById(R.id.btn_tts_start);
btn_tts_start.setOnClickListener(this);
initLanguageSpinner();
mSpeech = new TextToSpeech(TTSActivity.this, new TTSListener());
}
private void initLanguageSpinner() {
ArrayAdapter<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);
}
@Override
protected void onDestroy() {
recycleSpeech();
super.onDestroy();
}
private void recycleSpeech() {
if (mSpeech != null) {
mSpeech.stop();
mSpeech.shutdown();
mSpeech = null;
}
}
private String[] mLangArray = {"英语", "法语", "德语", "意大利语", "汉语普通话" };
private Locale[] mLocaleArray = {
Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.CHINA };
private int mLanguage;
private String mTextEN = "hello world. This is a TTS demo.";
private String mTextCN = "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。";
private class LanguageSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
mLanguage = arg2;
if (mLocaleArray[mLanguage]==Locale.SIMPLIFIED_CHINESE
|| mLocaleArray[mLanguage]==Locale.TRADITIONAL_CHINESE) {
et_tts_resource.setText(mTextCN);
} else {
et_tts_resource.setText(mTextEN);
}
if (mEngineList != null) {
resetLanguage();
}
}
public void onNothingSelected(AdapterView<?> arg0) {
}
}
private class EngineSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
mEngine = arg2;
recycleSpeech();
mSpeech = new TextToSpeech(TTSActivity.this, new TTSListener(),
mEngineList.get(mEngine).name);
}
public void onNothingSelected(AdapterView<?> arg0) {
}
}
private void resetLanguage() {
int result = mSpeech.setLanguage(mLocaleArray[mLanguage]);
//如果打印为-2,说明不支持这种语言;-1说明缺失数据
Toast.makeText(TTSActivity.this, "您选择的是"+mLangArray[mLanguage]
+",result="+result, Toast.LENGTH_SHORT).show();
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_tts_start) {
String content = et_tts_resource.getText().toString();
int result = mSpeech.speak(content, TextToSpeech.QUEUE_FLUSH, null);
Toast.makeText(TTSActivity.this, "speak result="+result, Toast.LENGTH_SHORT).show();
}
}
private List<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;
// 语音听写UI
private 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);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 退出时释放连接
mRecognize.cancel();
mRecognize.destroy();
}
@Override
public void onClick(View v) {
int ret = 0; // 函数调用返回值
int resid = v.getId();
if (resid == R.id.xf_recognize_setting) { // 进入参数设置页面
Intent intent = new Intent(this, SettingsAc