离线命令词识别
- 效果图
- 示例源码
地址:http://download.csdn.net/detail/q4878802/9023825
步骤:
1. 下载SDK
前面文章有,就不在复述了。这里要选择离线命令词的服务以后,重新加载,因为需要下载离线命令词识别的资源文件
2. 集成方法
前面文章有,就不在复述了。
3. 正题,开始集成
1. 添加权限
这里用到的唤醒功能不是所有的权限都用到的,具体用到了哪些权限,可以看上面的链接,用到哪写权限就加哪些权限,这个为了快速方便测试,把讯飞用到的权限都加上了。
<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" />
2. 初始化appid
我是将appid的初始化放在的Applicaiton下,具体可以下载源码
// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
// 如在Application中调用初始化,需要在Mainifest中注册该Applicaiton
// 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
// 参数间使用“,”分隔。
// 设置你申请的应用appid
StringBuffer param = new StringBuffer();
param.append("appid=55d33f09");
param.append(",");
param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
// param.append(",");
// param.append(SpeechConstant.FORCE_LOGIN + "=true");
SpeechUtility.createUtility(InitKqwSpeech.this, param.toString());
3. 工具类
package com.example.kqwspeechdemo2.utils;
import java.io.InputStream;
import android.content.Context;
import android.util.Log;
/**
* 功能性函数扩展类
*
* @author kongqw
*
*/
public class FucUtil {
// Log标签
private static final String TAG = "FucUtil";
/**
* 读取asset目录下文件内容
*
* @return content
*/
public static String readFile(Context mContext, String file, String code) {
int len = 0;
byte[] buf = null;
String result = "";
try {
InputStream in = mContext.getAssets().open(file);
len = in.available();
buf = new byte[len];
in.read(buf, 0, len);
result = new String(buf, code);
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return result;
}
}
4. 构建语法文件的工具类
package com.example.kqwspeechdemo2.engine;
import com.example.kqwspeechdemo2.utils.FucUtil;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
/**
* 构建离线命令词语法
*
* @author kongqw
*
*/
public abstract class BuildLocalGrammar {
/**
* 构建语法的回调
*
* @param errMsg
* null 构造成功
*/
public abstract void result(String errMsg, String grammarId);
// Log标签
private static final String TAG = "BuildLocalGrammar";
public static final String GRAMMAR_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/test";
// 上下文
private Context mContext;
// 语音识别对象
private SpeechRecognizer mAsr;
public BuildLocalGrammar(Context context) {
mContext = context;
// 初始化识别对象
mAsr = SpeechRecognizer.createRecognizer(context, new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
result(code + "", null);
Log.d(TAG, "初始化失败,错误码:" + code);
Toast.makeText(mContext, "初始化失败,错误码:" + code, Toast.LENGTH_SHORT).show();
}
}
});
};
/**
* 构建语法
*
* @return
*/
public void buildLocalGrammar() {
try {
/*
* TODO 如果你要在程序里维护bnf文件,可以在这里加上你维护的一些逻辑
* 如果不嫌麻烦,要一直改bnf文件,这里的代码可以不用动,不过我个人不建议一直手动修改bnf文件
* ,内容多了以后很容易出错,不好找Bug,建议每次改之前先备份。 建议用程序维护bnf文件。
*/
/*
* 构建语法
*/
String mContent;// 语法、词典临时变量
String mLocalGrammar = FucUtil.readFile(mContext, "kqw.bnf", "utf-8");
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, GRAMMAR_PATH);
// 使用8k音频的时候请解开注释
// mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
// 设置资源路径
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
// 构建语法
int ret = mAsr.buildGrammar("bnf", mContent, new GrammarListener() {
@Override
public void onBuildFinish(String grammarId, SpeechError error) {
if (error == null) {
Log.d(TAG, "语法构建成功");
result(null, grammarId);
} else {
Log.d(TAG, "语法构建失败,错误码:" + error.getErrorCode());
result(error.getErrorCode() + "", grammarId);
}
}
});
if (ret != ErrorCode.SUCCESS) {
Log.d(TAG, "语法构建失败,错误码:" + ret);
result(ret + "", null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取识别资源路径
private String getResourcePath() {
StringBuffer tempBuffer = new StringBuffer();
// 识别通用资源
tempBuffer.append(ResourceUtil.generateResourcePath(mContext, 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();
}
}
5. 命令词识别工具类
package com.example.kqwspeechdemo2.engine;
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.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
/**
* 命令词识别
*
* @author kongqw
*
*/
public abstract class KqwSpeechRecognizer {
/**
* 初始化的回调
*
* @param flag
* true 初始化成功 false 初始化失败
*/
public abstract void initListener(boolean flag);
public abstract void resultData(String data);
public abstract void speechLog(String log);
// Log标签
private static final String TAG = "KqwLocalCommandRecognizer";
public static final String GRAMMAR_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/test";
// 上下文
private Context mContext;
// 语音识别对象
private SpeechRecognizer mAsr;
public KqwSpeechRecognizer(Context context) {
mContext = context;
// 初始化识别对象
mAsr = SpeechRecognizer.createRecognizer(context, new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
initListener(false);
Toast.makeText(mContext, "初始化失败,错误码:" + code, Toast.LENGTH_SHORT).show();
} else {
initListener(true);
}
}
});
}
/**
* 参数设置
*/
public void setParam() {
// 清空参数
mAsr.setParameter(SpeechConstant.PARAMS, null);
// 设置识别引擎 本地引擎
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
// mAsr.setParameter(SpeechConstant.ENGINE_TYPE,
// SpeechConstant.TYPE_MIX);
// mAsr.setParameter(SpeechConstant.ENGINE_TYPE, "mixed");
// // 设置本地识别资源
mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
// 设置语法构建路径
mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, GRAMMAR_PATH);
// 设置返回结果格式
mAsr.setParameter(SpeechConstant.RESULT_TYPE, "json");
// 设置本地识别使用语法id
mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "kqw");
// 设置识别的门限值
mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "60");
// 使用8k音频的时候请解开注释
// mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
mAsr.setParameter(SpeechConstant.DOMAIN, "iat");
mAsr.setParameter(SpeechConstant.NLP_VERSION, "2.0");
mAsr.setParameter("asr_sch", "1");
// mAsr.setParameter(SpeechConstant.RESULT_TYPE, "json");
}
// 获取识别资源路径
private String getResourcePath() {
StringBuffer tempBuffer = new StringBuffer();
// 识别通用资源
tempBuffer.append(ResourceUtil.generateResourcePath(mContext, 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();
}
int ret = 0;// 函数调用返回值
/**
* 开始识别
*/
public void startListening() {
// 设置参数
setParam();
ret = mAsr.startListening(mRecognizerListener);
if (ret != ErrorCode.SUCCESS) {
Log.i(TAG, "识别失败,错误码: " + ret);
}
}
/**
* 识别监听器。
*/
private RecognizerListener mRecognizerListener = new RecognizerListener() {
StringBuffer stringBuffer = new StringBuffer();
public void onVolumeChanged(int volume) {
Log.i(TAG, "当前正在说话,音量大小:" + volume);
speechLog("当前正在说话,音量大小:" + volume);
}
@Override
public void onResult(final RecognizerResult result, boolean isLast) {
/*
* TODO 拼接返回的数据
*
* 这里返回的是Json数据,具体返回的是离线名命令词返回的Json还是语义返回的Json,需要做判断以后在对数据数据进行拼接
*/
stringBuffer.append(result.getResultString()).append("\n\n");
// isLast为true的时候,表示一句话说完,将拼接后的完整的一句话返回
if (isLast) {
// 数据回调
resultData(stringBuffer.toString());
}
}
@Override
public void onEndOfSpeech() {
Log.i(TAG, "结束说话");
speechLog("结束说话");
}
@Override
public void onBeginOfSpeech() {
stringBuffer.delete(0, stringBuffer.length());
Log.i(TAG, "开始说话");
speechLog("开始说话");
}
@Override
public void onError(SpeechError error) {
Log.i(TAG, "error = " + error.getErrorCode());
if (error.getErrorCode() == 20005) {
// 本地命令词没有识别,也没有请求到网络
resultData("没有构建的语法");
speechLog("没有构建的语法");
/*
* TODO
* 当网络正常的情况下是不会回调20005的错误,只有当本地命令词识别不匹配,网络请求也失败的情况下,会返回20005
* 这里可以自己再做处理,例如回复“没有听清”等回复
*/
} else {
/*
* TODO
* 其他错误有很多,需要具体问题具体分析,正常在程序没有错误的情况下,只会回调一个没有检测到说话的错误,没记错的话错误码是10118
*/
}
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
Log.i(TAG, "eventType = " + eventType);
}
};
}
这里的识别引擎设置的是SpeechConstant.TYPE_LOCAL,这种是本地识别引擎,只走本地识别,不走网络,如果换成SpeechConstant.TYPE_MIX,就是混合引擎,这种引擎方式,当本地没有识别到语法,返回20005错误码的时候,会直接请求语义接口,如果你语义开通了对应的场景,会走网络把你的语音转为语义,如果没有开通对应的场景,会把语音转为文字。
6. 测试类
package com.example.kqwspeechdemo2;
import com.example.kqwspeechdemo2.engine.BuildLocalGrammar;
import com.example.kqwspeechdemo2.engine.KqwSpeechRecognizer;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView mTvResult;
private TextView mTvLog;
private BuildLocalGrammar buildLocalGrammar;
private KqwSpeechRecognizer kqwSpeechRecognizer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvResult = (TextView) findViewById(R.id.tv_result);
mTvLog = (TextView) findViewById(R.id.tv_log);
/**
* 初始化本地语法构造器
*/
buildLocalGrammar = new BuildLocalGrammar(this) {
@Override
public void result(String errMsg, String grammarId) {
// errMsg为null 构造成功
if (TextUtils.isEmpty(errMsg)) {
Toast.makeText(MainActivity.this, "构造成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "构造失败", Toast.LENGTH_SHORT).show();
}
}
};
/**
* 初始化离线命令词识别器
*/
kqwSpeechRecognizer = new KqwSpeechRecognizer(this) {
@Override
public void speechLog(String log) {
// 录音Log信息的回调
mTvLog.setText(log);
}
@Override
public void resultData(String data) {
// 是识别结果的回调
mTvResult.setText(data);
}
@Override
public void initListener(boolean flag) {
// 初始化的回调
if (flag) {
Toast.makeText(MainActivity.this, "初始化成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "初始化失败", Toast.LENGTH_SHORT).show();
}
}
};
/**
* 构造本地语法文件,只有语法文件有变化的时候构造成功一次即可,不用每次都构造
*/
buildLocalGrammar.buildLocalGrammar();
}
/**
* 开始识别按钮
*
* @param view
*/
public void start(View view) {
mTvResult.setText(null);
// 开始识别
kqwSpeechRecognizer.startListening();
}
}
7. 界面布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.kqwspeechdemo2.MainActivity" >
<Button
android:id="@+id/bt_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:onClick="start"
android:text="开始录音" />
<TextView
android:id="@+id/tv_log"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/bt_start"
android:gravity="center"
android:text="录音信息" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/tv_log" >
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="返回结果" />
</ScrollView>
</RelativeLayout>
8. BNF语法文件
#BNF+IAT 1.0 UTF-8;
!grammar kqw;
!slot <contact>;
!slot <callPre>;
!slot <callPhone>;
!slot <callTo>;
!slot <go>;
!slot <position>;
!slot <move>;
!start <callStart>;
<callStart>:
[<callPre>][<callTo>]<contact><callPhone>
|[<callPre>]<callPhone>[<callTo>]<contact>
|[<callPre>]<callPhone>
|<move>
|<go><position>;
<contact>:庆威|小孔;
<callPre>:我要|我想|我想要;
<callPhone>:打电话;
<callTo>:给;
<move>:前进|后退|左转|右转;
<go>:去|到;
<position>:厨房|卧室|阳台|客厅;
在构建语法的时候,我们不是必要在assets目录下创建一个xxx.bnf文件,构建的时候我们只要能够拿到满足BNF语法文件的字符串就行,至于这个文件内容,你存在哪都无所谓,在程序里写死、存sp、数据库、自己程序维护都OK,只要满足BNF的语法就行。
BNF语法开发指南
下载地址:http://download.csdn.net/detail/q4878802/9023791
最后
- 如果你直接用我的Demo,我用的是测试版的离线包,只有35天的试用期,而且装机量只有3个,如果大家都用,很可能是不能正常运行的
- 如果是参考我的demo自己写一个,千万不要忘记替换appid和资源文件。