孔庆威的博客

精于心,简于形


  • 首页

  • 分类

  • 归档

  • 标签

科大讯飞语音识别

发表于 2015-08-21 | 分类于 语音 |

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


离线语音识别(语记)

效果图

效果图

源码

下载地址:http://download.csdn.net/detail/q4878802/9032149

下载语记并安装离线资源

下载语记并安装离线资源

集成

初始化

在清单文件中application标签下添加

1
android:name="InitKqwSpeech"

初始化

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
package com.example.kqwlocalspeechdemo;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechUtility;
import android.app.Application;
public class InitKqwSpeech extends Application {
@Override
public void onCreate() {
// 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史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());
super.onCreate();
}
}

语音听写工具类

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
package com.example.kqwlocalspeechdemo.engine;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.example.kqwlocalspeechdemo.utils.JsonUtils;
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;
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);
// 语音听写对象
private SpeechRecognizer mIat;
// TAG标签
private static String TAG = "KqwSpeechRecognizer";
// 上下文
private static Context mContext;
// SharedPreferences
// private SharedPreferences mSharedPreferences;
public KqwSpeechRecognizer(Context context) {
// 获取上下文
mContext = context;
// 初始化识别对象
mIat = 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 startListening() {
// 设置参数
setParam();
// 不显示听写对话框
int ret = mIat.startListening(recognizerListener);
if (ret != ErrorCode.SUCCESS) {
Toast.makeText(mContext, "听写失败,错误码:" + ret, Toast.LENGTH_SHORT).show();
}
}
/**
* 停止录音并获取录入的文字
*/
public void cancel() {
if (null != mIat) {
// mIat.cancel();
mIat.stopListening();
}
}
/**
* 听写监听器。
*/
private RecognizerListener recognizerListener = new RecognizerListener() {
StringBuffer resultText;
@Override
public void onBeginOfSpeech() {
speechLog("开始说话");
Log.i(TAG, "开始说话");
resultText = new StringBuffer();
}
@Override
public void onError(SpeechError error) {
Log.i(TAG, error.getPlainDescription(true));
}
@Override
public void onEndOfSpeech() {
speechLog("结束说话");
Log.i(TAG, "结束说话");
}
@Override
public void onResult(RecognizerResult results, boolean isLast) {
Log.d(TAG, results.getResultString());
String text = JsonUtils.parseIatResult(results.getResultString());
resultText.append(text);
if (isLast) {
// 最后的结果
resultData(resultText.toString().trim());
}
}
@Override
public void onVolumeChanged(int volume, byte[] data) {
speechLog("当前正在说话,音量大小:" + volume);
Log.i(TAG, "当前正在说话,音量大小:" + volume);
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
}
};
/**
* 参数设置
*
* @param param
* @return
*/
public void setParam() {
// 清空参数
mIat.setParameter(SpeechConstant.PARAMS, null);
// 设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
// 设置返回结果格式
mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");
// 设置语言
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
// 设置语言区域
mIat.setParameter(SpeechConstant.ACCENT, "zh_cn");
// 设置语音前端点
mIat.setParameter(SpeechConstant.VAD_BOS, "4000");
// 设置语音后端点
mIat.setParameter(SpeechConstant.VAD_EOS, "1000");
// 设置标点符号
mIat.setParameter(SpeechConstant.ASR_PTT, "1");
// 设置音频保存路径
// mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH,
// Environment.getExternalStorageDirectory() + "/iflytek/wavaudio.pcm");
}
}

工具类

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
package com.example.kqwlocalspeechdemo.utils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
public class JsonUtils {
public static String parseIatResult(String json) {
StringBuffer ret = new StringBuffer();
try {
JSONTokener tokener = new JSONTokener(json);
JSONObject joResult = new JSONObject(tokener);
JSONArray words = joResult.getJSONArray("ws");
for (int i = 0; i < words.length(); i++) {
// 转写结果词,默认使用第一个结果
JSONArray items = words.getJSONObject(i).getJSONArray("cw");
JSONObject obj = items.getJSONObject(0);
ret.append(obj.getString("w"));
// 如果需要多候选结果,解析数组其他字段
// for(int j = 0; j < items.length(); j++)
// {
// JSONObject obj = items.getJSONObject(j);
// ret.append(obj.getString("w"));
// }
}
} catch (Exception e) {
e.printStackTrace();
}
return ret.toString();
}
}

测试类

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
package com.example.kqwlocalspeechdemo;
import com.example.kqwlocalspeechdemo.engine.KqwSpeechRecognizer;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView mTvResult;
private TextView mTvLog;
private KqwSpeechRecognizer mKqwSpeechRecognizer;
@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);
// 初始化语音听写识别器
mKqwSpeechRecognizer = new KqwSpeechRecognizer(this) {
@Override
public void speechLog(String 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();
}
}
};
}
/**
* 开始识别按钮
*
* @param view
*/
public void start(View view) {
mTvResult.setText(null);
// 开始识别
mKqwSpeechRecognizer.startListening();
}
}

页面布局

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.kqwlocalspeechdemo.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>

在线语音识别

在线语音听写和离线语音听写基本一样,只要修改一下识别引擎即可。(就可以不用语记了)

1
2
// 设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);

科大讯飞离线命令词+语义识别

发表于 2015-08-20 | 分类于 语音 |

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


离线命令词+语义识别

意思就是先走离线命令词识别,如果离线命令词识别没有识别到结果,那么就再走语义接口,如果已开通对应场景,则转为语义,如果没有开通对应场景,则将语音转为文字

效果图

  • 说的话满足本地构建的语法—-离线命令词识别

效果图

  • 说的话离线命令词无法识别,走语义识别并且满足语义场景—-将语音转为语义

效果图

  • 说话既不满足离线命令词识别也不满足语义开通的场景—-将语音转为文字

效果图

程序和离线命令词识别基本一样

离线命令词识别地址:http://blog.csdn.net/q4878802/article/details/47780485

只要改一个参数,将识别引擎改为混合模式即可

1
mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_MIX);

因为使用的混合模式,当本地命令词没有识别到结果的时候会走在线语义(语义只有在线),所以,我们必须要先开通语义服务

程序流程

流程图

最后

  1. 如果你直接用我的Demo,我用的是测试版的离线包,只有35天的试用期,而且装机量只有3个,如果大家都用,很可能是不能正常运行的
  2. 如果是参考我的demo自己写一个,千万不要忘记替换appid和资源文件。

科大讯飞语音唤醒

发表于 2015-08-19 | 分类于 语音 |

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


语音唤醒

唤醒功能,顾名思义,通过语音,唤醒服务,做我们想做的事情。

效果图

开启应用后说讯飞语音或者讯飞语点唤醒

效果图

源码下载

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

步骤

1. 创建应用,开通服务

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

2. 下载SDK

我们要使用的是讯飞的付费功能,选择唤醒服务,点击下载以后,会提示没有购买。点击“购买服务”

购买服务

点击购买一会看到付费情况,有项目需要,就必须购买,我们写Demo,讯飞给提供了体验版的SDK,和正式版的没有功能上的区别,但是只能试用35天,装机量只有3个,唤醒词不能改,只有“讯飞语音”和“讯飞语点”两个唤醒词。

下载体验版SDK

3. 解压SDK

assets目录下是一些图片资源文件
doc目录下是一些开发文档
libs目录下是一些jar包和so库
res目录下是语音的资源文件,非常重要
sample目录下是Demo

解压SDK

开发步骤

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
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
package com.example.kqwspeechdemo.engine;
import org.json.JSONException;
import org.json.JSONObject;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechUtility;
import com.iflytek.cloud.VoiceWakeuper;
import com.iflytek.cloud.WakeuperListener;
import com.iflytek.cloud.WakeuperResult;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
/**
* 语音唤醒
*
* @author kongqw
*
*/
public abstract class KqwWake {
/**
* 唤醒的回调
*/
public abstract void kqwWake();
// Log标签
private static final String TAG = "KqwWake";
// 上下文
private Context mContext;
// 语音唤醒对象
private VoiceWakeuper mIvw;
/*
* TODO 设置门限值 : 门限值越低越容易被唤醒,需要自己反复测试,根据不同的使用场景,设置一个比较合适的唤醒门限
*/
// private final static int MAX = 60;
// private final static int MIN = -20;
private int curThresh = 40;
public KqwWake(Context context) {
mContext = context;
// 加载识唤醒地资源,resPath为本地识别资源路径
StringBuffer param = new StringBuffer();
String resPath = ResourceUtil.generateResourcePath(context, RESOURCE_TYPE.assets, "ivw/55d33f09.jet");
param.append(ResourceUtil.IVW_RES_PATH + "=" + resPath);
param.append("," + ResourceUtil.ENGINE_START + "=" + SpeechConstant.ENG_IVW);
boolean ret = SpeechUtility.getUtility().setParameter(ResourceUtil.ENGINE_START, param.toString());
if (!ret) {
Log.d(TAG, "启动本地引擎失败!");
}
// 初始化唤醒对象
mIvw = VoiceWakeuper.createWakeuper(context, null);
};
/**
* 唤醒
*/
public void wake() {
// 非空判断,防止因空指针使程序崩溃
mIvw = VoiceWakeuper.getWakeuper();
if (mIvw != null) {
// textView.setText(resultString);
// 清空参数
mIvw.setParameter(SpeechConstant.PARAMS, null);
// 唤醒门限值,根据资源携带的唤醒词个数按照“id:门限;id:门限”的格式传入
mIvw.setParameter(SpeechConstant.IVW_THRESHOLD, "0:" + curThresh);
// 设置唤醒模式
mIvw.setParameter(SpeechConstant.IVW_SST, "wakeup");
// 设置持续进行唤醒
mIvw.setParameter(SpeechConstant.KEEP_ALIVE, "1");
mIvw.startListening(mWakeuperListener);
} else {
Toast.makeText(mContext, "唤醒未初始化", Toast.LENGTH_SHORT).show();
}
}
public void stopWake() {
mIvw = VoiceWakeuper.getWakeuper();
if (mIvw != null) {
mIvw.stopListening();
} else {
Toast.makeText(mContext, "唤醒未初始化", Toast.LENGTH_SHORT).show();
}
}
String resultString = "";
private WakeuperListener mWakeuperListener = new WakeuperListener() {
@Override
public void onResult(WakeuperResult result) {
try {
String text = result.getResultString();
JSONObject object;
object = new JSONObject(text);
StringBuffer buffer = new StringBuffer();
buffer.append("【RAW】 " + text);
buffer.append("\n");
buffer.append("【操作类型】" + object.optString("sst"));
buffer.append("\n");
buffer.append("【唤醒词id】" + object.optString("id"));
buffer.append("\n");
buffer.append("【得分】" + object.optString("score"));
buffer.append("\n");
buffer.append("【前端点】" + object.optString("bos"));
buffer.append("\n");
buffer.append("【尾端点】" + object.optString("eos"));
resultString = buffer.toString();
stopWake();
kqwWake();
} catch (JSONException e) {
resultString = "结果解析出错";
e.printStackTrace();
}
}
@Override
public void onError(SpeechError error) {
Log.i(TAG, error.getPlainDescription(true));
}
@Override
public void onBeginOfSpeech() {
Log.i(TAG, "开始说话");
}
@Override
public void onEvent(int eventType, int isLast, int arg2, Bundle obj) {
}
};
}

测试类

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
package com.example.kqwspeechdemo;
import com.example.kqwspeechdemo.engine.KqwWake;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView mTvLog;
private TextView mTvResult;
private KqwWake kqwWake;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvLog = (TextView) findViewById(R.id.tv_log);
mTvResult = (TextView) findViewById(R.id.tv_result);
kqwWake = new KqwWake(this) {
@Override
public void kqwWake() {
Toast.makeText(MainActivity.this, "Debug:\n唤醒成功", Toast.LENGTH_SHORT).show();
// 开启唤醒
kqwWake.wake();
}
};
// 开启唤醒
kqwWake.wake();
}
}

页面布局

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
<LinearLayout 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:orientation="vertical"
tools:context="com.example.kqwspeechdemo.MainActivity" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF000000"
android:gravity="center"
android:padding="10dp"
android:text="唤醒词:讯飞语音、讯飞语点"
android:textColor="#FFFFFFFF"
android:textSize="20dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FFFFFFFF" />
<TextView
android:id="@+id/tv_log"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF000000"
android:gravity="center"
android:padding="10dp"
android:text="录音信息"
android:textColor="#FFFFFFFF"
android:textSize="10dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FFFFFFFF" />
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF000000"
android:padding="10dp"
android:text="返回结果"
android:textColor="#FFFFFFFF"
android:textSize="10dp" />
</LinearLayout>

注意

  1. 如果你直接用我的Demo,我用的是测试版的离线包,只有35天的试用期,而且装机量只有3个,如果大家都用,很可能是不能正常运行的
  2. 如果是参考我的demo自己写一个,千万不要忘记替换appid和资源文件。

科大讯飞离线命令词识别

发表于 2015-08-19 | 分类于 语音 |

转载请说明出处!
作者: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和资源文件。
1…56
kongqw

kongqw

Android Developer

54 日志
4 分类
25 标签
GitHub CSDN
Links
  • 干货集中营
  • 讯飞开放平台
  • 环信
  • 百度统计
  • DISQUS评论
  • BTC Wallet
  • OKCoin中国站
  • OKCoin国际站
  • BTC.com
© 2015 - 2018 kongqw
由 Hexo 强力驱动
主题 - NexT.Pisces