初探讯飞语音识别

准备

申请 appid 什么的就不说了。下载 SDK 一定要注意,下载的 SDK 与创建的应用一定要对的上,选择完模块和平台后,在最后的应用选择的时候需要注意选择想要的 appid ,下载后的 SDK 的 zip 文件最后也有 appid 。

下载完成后,将解压文件 lib 目录下的 Msc.jar 拷贝到 Android Studio 的 Project 视图下的 app/libs 目录下,如果目录不存在就新建一个。


将解压文件 lib 下的 armeabi 或者对应平台的文件夹拷贝到 Android Studio 的 Project 视图下的 app/src/main/jniLibs 路径下,如果不存在就自己创建。

将解压文件中的 assets 文件夹拷贝到 Android Studio 的 Project 视图下的 app/src/main 路径下,这些文件是讯飞默认的交互动画资源文件,不过这大概是好几年前的东西,风格整体没那么时尚。

配置

重写自己的 Application 类:

1
2
3
4
5
6
7
8
9
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
//科大讯飞语音听写初始化
SpeechUtility.createUtility(this, SpeechConstant.APPID + "=" + getString(R.string.iflytek_app_id));
//这里最好不要使用讯飞的标记强制在子线程中初始化,容易导致 用户校验失败 错误
}
}

修改 AndroidManifest.xml 文件:

添加权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--连接网络权限,用于执行云端语音能力 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--读取网络信息状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--获取当前wifi状态 -->
<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.READ_CONTACTS"/>

使用自己新建的 Application :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<application
android:name=".app.App"
android:allowBackup="true"
android:icon="@mipmap/done365"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

开始听写

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
private void startSpeech() {
mIat = SpeechRecognizer.createRecognizer(getActivity(), mInitListener);
mIat.setParameter(SpeechConstant.DOMAIN, "iat");
//设置语言
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
//设置地区
mIat.setParameter(SpeechConstant.ACCENT, "zh_cn");
//设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
//返回结果格式
mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");
//开始前几秒不说话超时
mIat.setParameter(SpeechConstant.VAD_BOS, "4000");
//停顿几秒后表示结束
mIat.setParameter(SpeechConstant.VAD_EOS, "1000");
//是否加标点符号,0 表示不加,1 表示加
mIat.setParameter(SpeechConstant.ASR_PTT, "0");
mIat.startListening(mRecoListener);

//听写交互动画
RecognizerDialog dialog = new RecognizerDialog(getActivity(), mInitListener);
dialog.setListener(dialogListener);
dialog.show();
}

private RecognizerListener mRecoListener = new RecognizerListener() {
@Override
public void onVolumeChanged(int i, byte[] bytes) {

}

@Override
public void onBeginOfSpeech() {

}

@Override
public void onEndOfSpeech() {

}

@Override
public void onResult(RecognizerResult recognizerResult, boolean b) {
if (b) {
Toast.makeText(getContext(), recognizerResult.getResultString(), Toast.LENGTH_LONG).show();
}
}

@Override
public void onError(SpeechError speechError) {

}

@Override
public void onEvent(int i, int i1, int i2, Bundle bundle) {

}
};

private InitListener mInitListener = new InitListener() {
@Override
public void onInit(int code) {
if (code != ErrorCode.SUCCESS) {
Toast.makeText(getActivity(), "初始化失败,错误码:" + code, Toast.LENGTH_SHORT).show();
} else if (code == ErrorCode.SUCCESS) {
Toast.makeText(getActivity(), "初始成功! :" + code, Toast.LENGTH_SHORT).show();
}
}
};

private RecognizerDialogListener dialogListener = new RecognizerDialogListener() {
@Override
public void onResult(RecognizerResult recognizerResult, boolean b) {
Toast.makeText(getActivity(), recognizerResult.getResultString(), Toast.LENGTH_SHORT).show();
}

@Override
public void onError(SpeechError speechError) {

}
};

结果演示:

当然现在由于返回的数据还是 json ,没有进行解析,所以看起来非常乱。
返回的 json 的数据:

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
{
"sn": 1, //第几句
"ls": false, //是否最后一句
"bg": 0, //开始
"ed": 0, //结束
"ws": [ //词
{
"bg": 0,
"cw": [ //中文分词
{
"sc": 0.00, //分数
"w": "这" //单字
}
]
},
{
"bg": 0,
"cw": [
{
"sc": 0.00,
"w": "是"
}
]
},
{
"bg": 0,
"cw": [
{
"sc": 0.00,
"w": "一"
}
]
},
{
"bg": 0,
"cw": [
{
"sc": 0.00,
"w": "条"
}
]
},
{
"bg": 0,
"cw": [
{
"sc": 0.00,
"w": "测试"
}
]
},
{
"bg": 0,
"cw": [
{
"sc": 0.00,
"w": "信息"
}
]
}
]
}

可以看出来讯飞基本上把所有的字跟词都分割开了。

结果解析

编写一个解析 json 数据的工具类 SpeechResultParse.java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SpeechResultParser {
public static String parseIatResult(String json) {
StringBuffer buffer = new StringBuffer();
try {
JSONTokener tokener = new JSONTokener(json);
JSONObject jResult = new JSONObject(tokener);
JSONArray words = jResult.getJSONArray("ws");//得到 ws ,也就是所有词的集合
for (int i=0;i<words.length();i++) {
JSONArray items = words.getJSONObject(i).getJSONArray("cw");//每个词的中文分词
JSONObject obj = items.getJSONObject(0);
buffer.append(obj.getString("w"));//具体的中文汉字
}
} catch (JSONException e) {
e.printStackTrace();
}
return buffer.toString();
}
}

然后修改下前台代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
private RecognizerDialogListener dialogListener = new RecognizerDialogListener() {
@Override
public void onResult(RecognizerResult recognizerResult, boolean b) {
Toast.makeText(getActivity(),
SpeechResultParser.parseIatResult(recognizerResult.getResultString()),
Toast.LENGTH_SHORT).show();
}

@Override
public void onError(SpeechError speechError) {

}
};

结果就出来了:

但是可以看出 Toast 打印了两次,第二次是空白的没有内容,所以最好将第二次的内容屏蔽掉,再修改下上面监听器的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private RecognizerDialogListener dialogListener = new RecognizerDialogListener() {
@Override
public void onResult(RecognizerResult recognizerResult, boolean b) {
if (!b) {//只需要第一次识别出的字符串,后续会输出空白
Toast.makeText(getActivity(),
SpeechResultParser.parseIatResult(recognizerResult.getResultString()),
Toast.LENGTH_SHORT).show();
}
}

@Override
public void onError(SpeechError speechError) {

}
};

再来看一下:

基本的语音听写就这些了,其实还是很容易的,得感谢科大讯飞为广大开发者提供如此便利的开放接口,让语音识别变得不再遥远和艰难。