科大讯飞离线命令词识别


转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

效果图

  • 示例源码

地址:http://download.csdn.net/detail/q4878802/9023825

步骤:

1. 下载SDK

前面文章有,就不在复述了。这里要选择离线命令词的服务以后,重新加载,因为需要下载离线命令词识别的资源文件

地址:http://blog.csdn.net/q4878802/article/details/47762169

2. 集成方法

前面文章有,就不在复述了。

地址:http://blog.csdn.net/q4878802/article/details/47778629

3. 正题,开始集成

1. 添加权限

这里用到的唤醒功能不是所有的权限都用到的,具体用到了哪些权限,可以看上面的链接,用到哪写权限就加哪些权限,这个为了快速方便测试,把讯飞用到的权限都加上了。

1
2
3
4
5
6
7
8
9
<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下,具体可以下载源码

1
2
3
4
5
6
7
8
9
10
11
12
// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史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. 工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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. 构建语法文件的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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. 命令词识别工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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. 测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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. 界面布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<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语法文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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

最后

  1. 如果你直接用我的Demo,我用的是测试版的离线包,只有35天的试用期,而且装机量只有3个,如果大家都用,很可能是不能正常运行的
  2. 如果是参考我的demo自己写一个,千万不要忘记替换appid和资源文件。
坚持原创技术分享,您的支持将鼓励我继续创作!