最近因为项目的需求,需要在无网络的情况下实现语音识别的功能,因为之前在线识别一直用的科大的,所以经理就和我说,你花半天时间简单熟悉一下,然后出一个Demo,下午有人过来看;因为之前科大在线SR也是别人做的,准确的说我只是了解过一点,也写过相关的blog——百度语音识别结合云知声离线TTSDemo(AS),Android原生TTS的基本使用以及配合中文语音包实现中文TTS等,但是就半天不到的时间写一个Demo还是很赶的,比较不熟悉。下面就来简单的总结一下这半天的经历。
源码下载地址
第一步:找到科大讯飞开发平台官网,注册账户
平台地址
第二步:点击右上角“控制台”进入个人控制台
第三步:创建应用,根据选择的服务生成SDK并下载
这里我们添加离线命令词识别服务,获取了对应SDK之后,也就完成的最基本的准备工作了,生成的APPID很重要哟,这个不用说你也应该知道。我们的第一阶段就算完成了
第四步:打开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代码中已经添加了权限:
为什么还有问题,这个时候你再进入到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);
// 设置资源路径
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
//使用8k音频的时候请解开注释
// mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
// 设置语法构建路径
mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
// 设置语法名称
mAsr.setParameter(SpeechConstant.GRAMMAR_LIST, "call");
// 设置文本编码格式
mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
ret = mAsr.updateLexicon("contact", mContent, lexiconListener);
if(ret != ErrorCode.SUCCESS){
showTip("更新词典失败,错误码:" + ret);
}
break;
// 开始识别
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;
// 停止识别
case R.id.isr_stop:
mAsr.stopListening();
showTip("停止识别");
break;
// 取消识别
case R.id.isr_cancel:
mAsr.cancel();
showTip("取消识别");
break;
}
}
/**
* 初始化监听器。
*/
private InitListener mInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
showTip("初始化失败,错误码:"+code);
}
}
};
/**
* 更新词典监听器。
*/
private LexiconListener lexiconListener = new LexiconListener() {
@Override
public void onLexiconUpdated(String lexiconId, SpeechError error) {
if(error == null){
showTip("词典更新成功");
}else{
showTip("词典更新失败,错误码:"+error.getErrorCode());
}
}
};
/**
* 构建语法监听器。
*/
private GrammarListener grammarListener = new GrammarListener() {
@Override
public void onBuildFinish(String grammarId, SpeechError error) {
if(error == null){
if (mEngineType.equals(SpeechConstant.TYPE_CLOUD)) {
Editor editor = mSharedPreferences.edit();
if(!TextUtils.isEmpty(grammarId))
editor.putString(KEY_GRAMMAR_ABNF_ID, grammarId);
editor.commit();
}
showTip("语法构建成功:" + grammarId);
}else{
showTip("语法构建失败,错误码:" + error.getErrorCode());
}
}
};
/**
* 获取联系人监听器。
*/
private ContactListener mContactListener = new ContactListener() {
@Override
public void onContactQueryFinish(String contactInfos, boolean changeFlag) {
//获取联系人
mLocalLexicon = contactInfos;
}
};
/**
* 识别监听器。
*/
private RecognizerListener mRecognizerListener = new RecognizerListener() {
@Override
public void onVolumeChanged(int volume, byte[] data) {
showTip("当前正在说话,音量大小:" + volume);
Log.d(TAG, "返回音频数据:"+data.length);
}
@Override
public void onResult(final RecognizerResult result, boolean isLast) {
if (null != result && !TextUtils.isEmpty(result.getResultString())) {
Log.d(TAG, "recognizer result:" + result.getResultString());
String text = "";
if (mResultType.equals("json")) {
text = JsonParser.parseGrammarResult(result.getResultString(), mEngineType);
} else if (mResultType.equals("xml")) {
text = XmlParser.parseNluResult(result.getResultString());
}
// 显示
((EditText) findViewById(R.id.isr_text)).setText(text);
} else {
Log.d(TAG, "recognizer result : null");
}
}
@Override
public void onEndOfSpeech() {
// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
showTip("结束说话");
}
@Override
public void onBeginOfSpeech() {
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
showTip("开始说话");
}
@Override
public void onError(SpeechError error) {
showTip("onError Code:" + error.getErrorCode());
}
@Override
public 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) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mToast.setText(str);
mToast.show();
}
});
}
/**
* 参数设置
* @param
* @return
*/
public boolean setParam(){
boolean result = false;
// 清空参数
mAsr.setParameter(SpeechConstant.PARAMS, null);
// 设置识别引擎
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
if("cloud".equalsIgnoreCase(mEngineType))
{
String grammarId = mSharedPreferences.getString(KEY_GRAMMAR_ABNF_ID, null);
if(TextUtils.isEmpty(grammarId))
{
result = false;
}else {
// 设置返回结果格式
mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
// 设置云端识别使用的语法id
mAsr.setParameter(SpeechConstant.CLOUD_GRAMMAR, grammarId);
result = true;
}
}
else
{
// 设置本地识别资源
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
// 设置语法构建路径
mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
// 设置返回结果格式
mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
// 设置本地识别使用语法id
mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
// 设置识别的门限值
mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
// 使用8k音频的时候请解开注释
// mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
result = true;
}
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
// 注:AUDIO_FORMAT参数语记需要更新版本才能生效
mAsr.setParameter(SpeechConstant.AUDIO_FORMAT,"wav");
mAsr.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/asr.wav");
return result;
}
//获取识别资源路径
private String getResourcePath(){
StringBuffer tempBuffer = new StringBuffer();
//识别通用资源
tempBuffer.append(ResourceUtil.generateResourcePath(this, RESOURCE_TYPE.assets, "asr/common.jet"));
//识别8k资源-使用8k的时候请解开注释
// tempBuffer.append(";");
// tempBuffer.append(ResourceUtil.generateResourcePath(this, RESOURCE_TYPE.assets, "asr/common_8k.jet"));
return tempBuffer.toString();
}
@Override
protected void onDestroy() {
super.onDestroy();
if( null != mAsr ){
// 退出时释放连接
mAsr.cancel();
mAsr.destroy();
}
}
}
看着还是比较多的,我之所以说多而没有说难就是因为它并不难;下面的介绍中我们会对其进行再次瘦身。
第八步:为自己的Demo做准备工作
(1)把assets目录copy到我们的module中
(2)把jniLibs目录copy到我们的module中
这里是在Project视图下完成的,这里在Android视图下展示效果更好一下
(3)打开Project视图,把libs目录中的内容复制到我们的module中
(4)在build.gradle(Module:app)中的depandencies下添加依赖:
compile files('libs/Msc.jar')
(5)把Demo中的工具类copy到我们的module中
截止到现在,我们还在准备阶段,下面就进入正题,来对我们的需要的功能的实现做一个简要的梳理
第九步:提取离线命令词识别功能到我们的项目
定义一个activity,CallStepActivity,把AsrDemo中的逻辑代码copy到CallStepActivty中,把对应的布局文件也对应copy进来
第十步:梳理逻辑,继续瘦身
上面也说了,AsrDemo中的Demo还是有点冗余,因为好多我们用不上或者暂时用不上,比如在线的命令词识别等肯定用不上,比如词典更新我们暂时用不上,下面就来分析一下单纯使用离线命令词识别的实现(下面是重点)
(1)根据应用ID初始化SpeechUtility,通常在程序入口Application中完成
package com.hfut.offlinerecongnizer.activity.util;
import android.app.Application;
import com.hfut.offlinerecongnizer.R;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechUtility;
/**
* author:why
* created on: 2018/8/27 11:10
* description:
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
// 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
// 参数间使用“,”分隔。
// 设置你申请的应用appid
// 注意: appid 必须和下载的SDK保持一致,否则会出现10407错误
StringBuffer param = new StringBuffer();
param.append("appid=" + getString(R.string.app_id));
param.append(",");
// 设置使用v5+
param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
SpeechUtility.createUtility(MyApplication.this, param.toString());
super.onCreate();
}
}
(2)在Activity中初始化初始化监听器,用于初始化语音识别引擎
/**
* 初始化监听器。
*/
private InitListener mInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
showTip("初始化失败,错误码:" + code);
}
}
};
(3)初始化语音识别监听器
/**
* 识别监听器。
*/
private RecognizerListener mRecognizerListener = new RecognizerListener() {
@Override
public void onVolumeChanged(int volume, byte[] data) {
showTip("当前正在说话,音量大小:" + volume);
Log.d(TAG, "返回音频数据:" + data.length);
}
@Override
public void onResult(final RecognizerResult result, boolean isLast) {
if (null != result && !TextUtils.isEmpty(result.getResultString())) {
Log.d(TAG, "recognizer result:" + result.getResultString());
String text = "";
if (mResultType.equals("json")) {
text = JsonParser.parseGrammarResult(result.getResultString(), SpeechConstant.TYPE_LOCAL);
} else if (mResultType.equals("xml")) {
text = XmlParser.parseNluResult(result.getResultString());
}
// 显示
((EditText) findViewById(R.id.isr_text)).setText(text);
} else {
Log.d(TAG, "recognizer result : null");
}
}
@Override
public void onEndOfSpeech() {
// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
showTip("结束说话");
}
@Override
public void onBeginOfSpeech() {
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
showTip("开始说话");
}
@Override
public void onError(SpeechError error) {
showTip("onError Code:" + error.getErrorCode());
}
@Override
public void onEvent(int i, int i1, int i2, Bundle bundle) {
}
};
(4)初始化语法文件构建监听器
/**
* 构建语法监听器。
*/
private GrammarListener grammarListener = new GrammarListener() {
@Override
public void onBuildFinish(String grammarId, SpeechError error) {
if (error == null) {
showTip("语法构建成功:" + grammarId);
} else {
showTip("语法构建失败,错误码:" + error.getErrorCode());
}
}
};
(5)初始化语音识别引擎并完成参数设置
// 初始化识别引擎
mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);
//设置识别引擎参数
setParam();
其中setPatam():
public void setParam() {
boolean result = true;
// 清空参数
mAsr.setParameter(SpeechConstant.PARAMS, null);
// 设置识别引擎
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
// 设置本地识别资源
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
// 设置语法构建路径
mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
// 设置返回结果格式
mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
// 设置本地识别使用语法id
mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
// 设置识别的门限值
mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
}
(6)完成语法构建
private void buildGrammer() {
mLocalGrammar = FucUtil.readFile(this, "call.bnf", "utf-8");
// 本地-构建语法文件,生成语法id
((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, SpeechConstant.TYPE_LOCAL);
// 设置语法构建路径
mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
// 设置资源路径
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
if (ret != ErrorCode.SUCCESS) {
showTip("语法构建失败,错误码:" + ret);
} else {
showTip("语法构建成功");
}
}
(7)开启识别,停止识别,取消识别分别是:
mAsr.startListening(mRecognizerListener);
mAsr.stopListening();
mAsr.cancel();
第十一步:最简单的功能实现代码
所以最后组合起来,我们实现剥离了所有其他功能的只是实现离线命令词识别的代码,CallStepActivity代码如下:
package com.hfut.offlinerecongnizer.activity.activity;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.SharedPreferences;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.hfut.offlinerecongnizer.R;
import com.hfut.offlinerecongnizer.activity.util.FucUtil;
import com.hfut.offlinerecongnizer.activity.util.JsonParser;
import com.hfut.offlinerecongnizer.activity.util.XmlParser;
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.ResourceUtil;
/**
* @author why
* @date 2018-8-27 15:09:38
*/
public class CallStepActivity extends AppCompatActivity implements View.OnClickListener {
private static String TAG = OffLineTestActivity.class.getSimpleName();
// 语音识别对象
private SpeechRecognizer mAsr;
private Toast mToast;
// 本地语法文件
private String mLocalGrammar = null;
// 本地语法构建路径
private String grmPath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/msc/call";
// 返回结果格式,支持:xml,json
private String mResultType = "json";
private final String GRAMMAR_TYPE_BNF = "bnf";
@SuppressLint("ShowToast")
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_call_step);
initLayout();
mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
// 初始化识别引擎
mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);
//构建本地语法
buildGrammer();
}
/**
* 初始化Layout。
*/
private void initLayout() {
findViewById(R.id.isr_recognize).setOnClickListener(this);
findViewById(R.id.isr_stop).setOnClickListener(this);
findViewById(R.id.isr_cancel).setOnClickListener(this);
}
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;
}
switch (view.getId()) {
// 开始识别
case R.id.isr_recognize:
((EditText) findViewById(R.id.isr_text)).setText(null);// 清空显示内容
//设置识别引擎参数
setParam();
ret = mAsr.startListening(mRecognizerListener);
if (ret != ErrorCode.SUCCESS) {
showTip("识别失败,错误码: " + ret);
}
break;
// 停止识别
case R.id.isr_stop:
mAsr.stopListening();
showTip("停止识别");
break;
// 取消识别
case R.id.isr_cancel:
mAsr.cancel();
showTip("取消识别");
break;
}
}
/**
* 初始化监听器。
*/
private InitListener mInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
showTip("初始化失败,错误码:" + code);
}
}
};
/**
* 构建语法监听器。
*/
private GrammarListener grammarListener = new GrammarListener() {
@Override
public void onBuildFinish(String grammarId, SpeechError error) {
if (error == null) {
showTip("语法构建成功:" + grammarId);
} else {
showTip("语法构建失败,错误码:" + error.getErrorCode());
}
}
};
/**
* 识别监听器。
*/
private RecognizerListener mRecognizerListener = new RecognizerListener() {
@Override
public void onVolumeChanged(int volume, byte[] data) {
showTip("当前正在说话,音量大小:" + volume);
Log.d(TAG, "返回音频数据:" + data.length);
}
@Override
public void onResult(final RecognizerResult result, boolean isLast) {
if (null != result && !TextUtils.isEmpty(result.getResultString())) {
Log.d(TAG, "recognizer result:" + result.getResultString());
String text = "";
if (mResultType.equals("json")) {
text = JsonParser.parseGrammarResult(result.getResultString(), SpeechConstant.TYPE_LOCAL);
} else if (mResultType.equals("xml")) {
text = XmlParser.parseNluResult(result.getResultString());
}
// 显示
((EditText) findViewById(R.id.isr_text)).setText(text);
} else {
Log.d(TAG, "recognizer result : null");
}
}
@Override
public void onEndOfSpeech() {
// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
showTip("结束说话");
}
@Override
public void onBeginOfSpeech() {
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
showTip("开始说话");
}
@Override
public void onError(SpeechError error) {
showTip("onError Code:" + error.getErrorCode());
}
@Override
public void onEvent(int i, int i1, int i2, Bundle bundle) {
}
};
private void showTip(final String str) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mToast.setText(str);
mToast.show();
}
});
}
/**
* 参数设置
*
* @param
* @return
*/
public void setParam() {
boolean result = true;
// 清空参数
mAsr.setParameter(SpeechConstant.PARAMS, null);
// 设置识别引擎
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
// 设置本地识别资源
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
// 设置语法构建路径
mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
// 设置返回结果格式
mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
// 设置本地识别使用语法id
mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
// 设置识别的门限值
mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
// 注:AUDIO_FORMAT参数语记需要更新版本才能生效
mAsr.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mAsr.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/asr.wav");
}
//获取识别资源路径
private String getResourcePath() {
StringBuffer tempBuffer = new StringBuffer();
//识别通用资源
tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, "asr/common.jet"));
return tempBuffer.toString();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != mAsr) {
// 退出时释放连接
mAsr.cancel();
mAsr.destroy();
}
}
private void buildGrammer() {
mLocalGrammar = FucUtil.readFile(this, "call.bnf", "utf-8");
// 本地-构建语法文件,生成语法id
((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, SpeechConstant.TYPE_LOCAL);
// 设置语法构建路径
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);
} else {
showTip("语法构建成功");
}
}
}
activity_call_step.xml文件:
如果不出意外的话,运行应该没有任何问题的。至此,最难的最复杂的第三阶段已经结束了,下面就来看看第四阶段的工作任务:
第十二步:丰富我们的功能
因为API里面提供了更新词典的功能(从这里我们也可以推出来后面介绍的bnf文件中词槽的定义也可以通过代码来实现):
mAsr.updateLexicon(groupName, mLocalLexicon, lexiconListener);
所以我们就该利用起来,毕竟如果我想修改某一个词槽的定义时,不能每次都是通过编辑bnf文件,然后在运行程序来实现,太麻烦了。这里我通过一个自定义的AlertDialog来实现对词槽的重新赋值,并列的同义词用“,”隔开即可,类似于bnf文件中的 | 符号;下面直接给出OffLineTestActivity代码:
package com.hfut.offlinerecongnizer.activity.activity;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Toast;
import com.hfut.offlinerecongnizer.R;
import com.hfut.offlinerecongnizer.activity.util.FucUtil;
import com.hfut.offlinerecongnizer.activity.util.JsonParser;
import com.hfut.offlinerecongnizer.activity.util.XmlParser;
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;
/**
* @author why
* @date 2018-8-27 13:20:58
*/
public class OffLineTestActivity extends AppCompatActivity implements View.OnClickListener {
private static String TAG = OffLineTestActivity.class.getSimpleName();
// 语音识别对象
private SpeechRecognizer mAsr;
private Toast mToast;
// 缓存
//private SharedPreferences mSharedPreferences;
// 本地语法文件
private String mLocalGrammar = null;
// 本地词典
private String mLocalLexicon = null;
// 本地语法构建路径
private String grmPath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/msc/call";
// 返回结果格式,支持:xml,json
private String mResultType = "json";
private final String GRAMMAR_TYPE_BNF = "bnf";
private String groupName;
private String groupInfo;
@SuppressLint("ShowToast")
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_off_line_test);
initLayout();
// 初始化识别引擎对象
mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);
mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
//构建本地语法
buildGrammer();
}
/**
* 初始化Layout
*/
private void initLayout() {
findViewById(R.id.isr_recognize).setOnClickListener(this);
findViewById(R.id.isr_lexcion).setOnClickListener(this);
findViewById(R.id.isr_stop).setOnClickListener(this);
findViewById(R.id.isr_cancel).setOnClickListener(this);
}
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;
}
switch (view.getId()) {
// 本地-更新词典
case R.id.isr_lexcion:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = LayoutInflater.from(this);
final View v = inflater.inflate(R.layout.user_info_editor, null);
final EditText wordGroupName = v.findViewById(R.id.enter_word_group_name);
final EditText wordGroupInfo = v.findViewById(R.id.enter_word_group_info);
Button cancleButton = v.findViewById(R.id.register_cancle);
Button confirmButton = v.findViewById(R.id.register_confirm);
final Dialog dialog = builder.create();
//点击EditText弹出软键盘
cancleButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(OffLineTestActivity.this, "取消", Toast.LENGTH_SHORT).show();
dialog.cancel();
}
});
confirmButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!wordGroupName.getText().toString().equals("")) {
groupName= wordGroupName.getText().toString();
}
if (!wordGroupInfo.getText().toString().equals("")) {
groupInfo = wordGroupInfo.getText().toString();
}
mLocalLexicon=getUpdateInfo(groupInfo);
((EditText) findViewById(R.id.isr_text)).setText(mLocalLexicon);
mAsr.setParameter(SpeechConstant.PARAMS, null);
// 设置引擎类型
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
// 设置资源路径
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
// 设置语法构建路径
mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
// 设置语法名称
mAsr.setParameter(SpeechConstant.GRAMMAR_LIST, "call");
// 设置文本编码格式
mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
//执行更新操作
ret = mAsr.updateLexicon(groupName, mLocalLexicon, lexiconListener);
if (ret != ErrorCode.SUCCESS) {
showTip("更新词典失败,错误码:" + ret);
}
else{
showTip("更新词典成功" );
}
dialog.cancel();
}
});
dialog.show();
dialog.getWindow().setContentView(v);//自定义布局应该在这里添加,要在dialog.show()的后面
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
break;
// 开始识别
case R.id.isr_recognize:
//设置识别引擎参数
setParam();
((EditText) findViewById(R.id.isr_text)).setText(null);// 清空显示内容
ret = mAsr.startListening(mRecognizerListener);
if (ret != ErrorCode.SUCCESS) {
showTip("识别失败,错误码: " + ret);
}
break;
// 停止识别
case R.id.isr_stop:
mAsr.stopListening();
showTip("停止识别");
break;
// 取消识别
case R.id.isr_cancel:
mAsr.cancel();
showTip("取消识别");
break;
}
}
private String getUpdateInfo(String groupInfo) {
String[] wordList=groupInfo.split(",");
StringBuilder builder=new StringBuilder();
for(int i=0;i
activity_off_line_test.xml代码:
word_info_editor.xml代码:
其中还有一个所有布局都用到的title.xml代码:
上面介绍的离线命令词识别都是基于我们自己编辑的bnf文件中的规则来识别,下面给出一个文件示例:
#BNF+IAT 1.0 UTF-8;
!grammar call;
//通用词槽
!slot ;
!slot ;
!slot ;
//联系相关词槽声明
!slot ;//联系人
!slot ;//联系方式
!slot ;//联系动作
//巡游相关词槽声明
!slot ;//巡游点
!slot ;//去
!slot ;//准备去
/*
专业语料相关
*/
//办卡业务
!slot ;
//公积金业务
!slot ;
!slot ;
!slot ;
!start ;
:|||;
//通用语料
:我想|我要|我准备;
:如何|怎么|怎样;
:办理|解决|处理;
//测试语料
:黄老板|王华洋|齐带华|火警!id(119);
:打电话|发微信|发短信;
:给;
:|;//联系语料相关规则
//巡游语料
:卫生间|饮水机|现金柜台|取款机|充电器|大堂经理;
:去|到|找;
:带我|请带我|我想;
:[];//巡游语料相关规则
//办卡语料
:卡|信用卡|儿童卡|储蓄卡;//卡片类型
:[];
//公积金业务
:比例;//公积金比例
:转移|提取;//处理公积金
:公积金;
:[];
具体的编辑规则请参考bnf文档编辑指南,后续我还会对这个编辑规则进行介绍,具体就介绍到这里。
注:欢迎扫码关注