最近因为项目的需求,需要在无网络的情况下实现语音识别的功能,因为之前在线识别一直用的科大的,所以经理就和我说,你花半天时间简单熟悉一下,然后出一个Demo,下午有人过来看;因为之前科大在线SR也是别人做的,准确的说我只是了解过一点,也写过相关的blog——百度语音识别结合云知声离线TTSDemo(AS),Android原生TTS的基本使用以及配合中文语音包实现中文TTS等,但是就半天不到的时间写一个Demo还是很赶的,比较不熟悉。下面就来简单的总结一下这半天的经历。
第一阶段 基础准备
第一步:找到科大讯飞开发平台官网,注册账户
第二步:点击右上角“控制台”进入个人控制台
第三步:创建应用,根据选择的服务生成SDK并下载
这里我们添加离线命令词识别服务,获取了对应SDK之后,也就完成的最基本的准备工作了,生成的APPID很重要哟,这个不用说你也应该知道。我们的第一阶段就算完成了
第二阶段 Demo导入
第四步:打开AS,创建一个和上图同名的应用
第五步:导入SDK解压文件夹下的sample目录里面的的mscV5PlusDemomodule
这里面需要实现在AS项目中导入module操作,如下图所示:
选择上面sample下面对应的mscV5PlusDemo即可,如果有需要调整sdk版本的就按照错误提示调整就好了,比较简单;至此,我们就把SDK中的Demo(mscV5PlusDemo)导入到了我们的项目中:
第六步:这个时候选择导入的module,在arm机上运行,发现并不能正常运行,那么你需要考虑以下几个问题
(1)Demo中的离线命令词识别的commen.jet文件位置错误
在解压文件夹的res目录下找到asr文件夹,将其copy到Demo里面的assets目录下:
(2)一定要在arm机上测试,因为这个Demo里面只有armeabi的so文件。
(3)如果可以运行,进入如下界面,发现里面不仅仅只有我们需要的离线命令词识别,还有在线识别等等:
我们点击“立刻体验语法识别”,关闭设备网络,选择下图中的“本地”,然后点击“构建语法”,再点击“开始识别”;
这个时候很有可能再报错误,查看错误码发现原来是没有录音权限等权限问题,这个时候你就纳闷了,明明Demo代码中已经添加了权限:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
为什么还有问题,这个时候你再进入到Demo代码里面查看,里面并没有做6.0以及以上版本的动态权限申请处理,所以怎么办了,要么我们自己加上,要么换一个低一点的机子测试一下。
// 开始识别,没有权限判断
case R.id.isr_recognize:
((EditText)findViewById(R.id.isr_text)).setText(null);// 清空显示内容
// 设置参数
if (!setParam()) {
showTip("请先构建语法。");
return;
};
ret = mAsr.startListening(mRecognizerListener);
if (ret != ErrorCode.SUCCESS) {
showTip("识别失败,错误码: " + ret);
}
break;
这里我们就不深究了,因为后面还有好多内容了,假设这个时候你能够正常运行了,也能在Demo中完成离线命令词识别了。那么下一阶段就是瘦身处理了。
第三阶段 功能瘦身
第七步:提取离线命令词识别功能
不得不说,这个Demo对于我们只使用离线命令词识别来说有一点冗余,太多了;下面我们就来把离线命令词功能抽取出来,如下图:
实现离线命令词识别的功能实现主要是上图中红色框中AsrDemo中的逻辑,其源码如下:
package com.iflytek.mscv5plusdemo;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.Toast;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.LexiconListener;
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.util.ContactManager;
import com.iflytek.cloud.util.ContactManager.ContactListener;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;
import com.iflytek.speech.util.FucUtil;
import com.iflytek.speech.util.JsonParser;
import com.iflytek.speech.util.XmlParser;
public class AsrDemo extends Activity implements OnClickListener{
private static String TAG = AsrDemo.class.getSimpleName();
// 语音识别对象
private SpeechRecognizer mAsr;
private Toast mToast;
// 缓存
private SharedPreferences mSharedPreferences;
// 本地语法文件
private String mLocalGrammar = null;
// 本地词典
private String mLocalLexicon = null;
// 云端语法文件
private String mCloudGrammar = null;
// 本地语法构建路径
private String grmPath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/msc/test";
// 返回结果格式,支持:xml,json
private String mResultType = "json";
private final String KEY_GRAMMAR_ABNF_ID = "grammar_abnf_id";
private final String GRAMMAR_TYPE_ABNF = "abnf";
private final String GRAMMAR_TYPE_BNF = "bnf";
private String mEngineType = "cloud";
@SuppressLint("ShowToast")
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.isrdemo);
initLayout();
// 初始化识别对象
mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);
// 初始化语法、命令词
mLocalLexicon = "张海羊\n刘婧\n王锋\n";
mLocalGrammar = FucUtil.readFile(this,"call.bnf", "utf-8");
mCloudGrammar = FucUtil.readFile(this,"grammar_sample.abnf","utf-8");
// 获取联系人,本地更新词典时使用
ContactManager mgr = ContactManager.createManager(AsrDemo.this, mContactListener);
mgr.asyncQueryAllContactsName();
mSharedPreferences = getSharedPreferences(getPackageName(), MODE_PRIVATE);
mToast = Toast.makeText(this,"",Toast.LENGTH_SHORT);
}
/**
* 初始化Layout。
*/
private void initLayout(){
findViewById(R.id.isr_recognize).setOnClickListener(this);
findViewById(R.id.isr_grammar).setOnClickListener(this);
findViewById(R.id.isr_lexcion).setOnClickListener(this);
findViewById(R.id.isr_stop).setOnClickListener(this);
findViewById(R.id.isr_cancel).setOnClickListener(this);
//选择云端or本地
RadioGroup group = (RadioGroup)this.findViewById(R.id.radioGroup);
group.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if(checkedId == R.id.radioCloud)
{
((EditText)findViewById(R.id.isr_text)).setText(mCloudGrammar);
findViewById(R.id.isr_lexcion).setEnabled(false);
mEngineType = SpeechConstant.TYPE_CLOUD;
}else if(checkedId == R.id.radioLocal)
{
((EditText)findViewById(R.id.isr_text)).setText(mLocalGrammar);
findViewById(R.id.isr_lexcion).setEnabled(true);
mEngineType = SpeechConstant.TYPE_LOCAL;
}
}
});
}
String mContent;// 语法、词典临时变量
int ret = 0;// 函数调用返回值
@Override
public void onClick(View view) {
if( null == mAsr ){
// 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
this.showTip( "创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化" );
return;
}
if(null == mEngineType) {
showTip("请先选择识别引擎类型");
return;
}
switch(view.getId())
{
case R.id.isr_grammar:
showTip("上传预设关键词/语法文件");
// 本地-构建语法文件,生成语法id
if (mEngineType.equals(SpeechConstant.TYPE_LOCAL)) {
((EditText)findViewById(R.id.isr_text)).setText(mLocalGrammar);
mContent = new String(mLocalGrammar);
mAsr.setParameter(SpeechConstant.PARAMS, null);
// 设置文本编码格式
mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
// 设置引擎类型
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
// 设置语法构建路径
mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
//使用8k音频的时候请解开注释
// mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
// 设置资源路径
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
if(ret != ErrorCode.SUCCESS){
showTip("语法构建失败,错误码:" + ret);
}
}
// 在线-构建语法文件,生成语法id
else {
((EditText)findViewById(R.id.isr_text)).setText(mCloudGrammar);
mContent = new String(mCloudGrammar);
// 指定引擎类型
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
// 设置文本编码格式
mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
ret = mAsr.buildGrammar(GRAMMAR_TYPE_ABNF, mContent, grammarListener);
if(ret != ErrorCode.SUCCESS)
showTip("语法构建失败,错误码:" + ret);
}
break;
// 本地-更新词典
case R.id.isr_lexcion:
((EditText)findViewById(R.id.isr_text)).setText(mLocalLexicon);
mContent = new String(mLocalLexicon);
mAsr.setParameter(SpeechConstant.PARAMS, null);
// 设置引擎类型
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
// 设置资源路径