ember's blog

让你的Hybrid App听懂你的话(Android篇)

2018/04/28 Share

前言

最近需要对接语音识别业务,毕竟现在是AI时代,一个产品如果能通过AI能力给用户带来全新的体验,也是很值得尝试的。技术对接上选择的是科大讯飞开放平台,也算是国内最早一批做语音识别的企业了,文档方面都比较全面,对接起来也很方便。

技术基础

开发技术栈为Cordova+Angular+Ionic,这篇分享会介绍如何从头开始创建Cordova插件,并实现科大讯飞Android sdk与App端的数据交互。

Apache Cordova是一个开源的移动开发框架。允许你用标准的web技术——HTML5,CSS3和JavaScript做跨平台开发。 应用在每个平台的具体执行被封装了起来,并依靠符合标准的API绑定去访问每个设备的功能,比如说:传感器、数据、网络状态等。

在继续阅读之前,应该确保你有通过Cordova创建并打包一个简单Hybrid App的经验,感兴趣的童鞋可以到Ionic官网Cordova官网学习下。

创建Cordova插件

全局安装plugman

plugman用于创建Cordova插件,在项目目录下执行cnpm i -g plugman

创建插件

创建一个插件并添加android平台,并生成package.json,插件名xFeiVoice,插件idcom.qinsilk.xFeiVoice,版本号为0.01

1
2
3
4
plugman create --name xFeiVoice --plugin_id com.qinsilk.xFeiVoice --plugin_version 0.0.1
cd xFeiVoice
plugman createpackagejson ./
plugman platform add --platform_name android


创建成功可以看到对应目录如下

  • src目录存放原生代码,此处为java文件
  • www目录存放js暴露给设备的接口,如下
    1
    2
    3
    4
    5
    var exec = require('cordova/exec');

    exports.coolMethod = function (arg0, success, error) {
    exec(success, error, 'xFeiVoice', 'coolMethod', [arg0]);
    };

此时我们做下修改,让参数名跟业务命名更加相关。

1
2
3
4
5
var exec = require('cordova/exec');

exports.record = function (arg0, success, error) {
exec(success, error, 'xFeiVoice', 'record', [arg0]);
};

安装插件

执行cordova plugin add xFeiVoice,再执行cordova plugin ls可以查看当前App安装的插件。

此时用Android Studio打开项目,可以看到这个插件已经添加成功。

到这里我们的准备工作就完成了。

对接语音识别

导入sdk

Android sdk可以去科大讯飞开放平台下载。将在官网下载的Android SDK 压缩包中libs目录下所有子文件拷贝至Android工程的libs目录下。如下图所示:

添加权限

在工程 AndroidManifest.xml 文件中添加如下权限:

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
<!--连接网络权限,用于执行云端语音能力 -->
<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"/>
<!--外存储写权限,构建语法需要用到此权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--外存储读权限,构建语法需要用到此权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--配置权限,用来记录应用配置信息 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务-->
<!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--如需使用人脸识别,还要添加:摄相头权限,拍照需要用到 -->
<uses-permission android:name="android.permission.CAMERA" />

初始化语音识别对象

此处调用的是60秒语音听写功能。excute是在开发插件时,用户的自定义方法,当页面调用插件时系统首先将会运行此方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
if (action.equals("record")) {
// 初始化语音识别对象
SpeechUtility.createUtility(cordova.getActivity(), "appid=yourAppid,force_login=true");
// 使用SpeechRecognizer对象,可根据回调消息自定义界面;
mIat = SpeechRecognizer.createRecognizer(cordova.getActivity(), mInitListener);
// 设置参数
setParam();
// 监听事件
mIat.startListening(mRecognizerListener);
return true;
}
return false;
}

初始化监听器

1
2
3
4
5
6
7
8
9
private InitListener mInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.d(LOG_TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
Log.d(LOG_TAG, "初始化失败,错误码:" + code);
}
}
};

设置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void setParam() {
// 清空参数
mIat.setParameter(SpeechConstant.PARAMS, null);

// 设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
// 设置返回结果格式
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");

// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
// 注:AUDIO_FORMAT参数语记需要更新版本才能生效
mIat.setParameter(SpeechConstant.AUDIO_FORMAT,"wav");
mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/iat.wav");
}

识别监听器

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
private RecognizerListener mRecognizerListener = new RecognizerListener() {
@Override
public void onVolumeChanged(int volume, byte[] data) {
// showTip("当前正在说话,音量大小:" + volume);
Log.d(LOG_TAG, "返回音频数据:"+data.length);
}

@Override
public void onResult(final RecognizerResult result, boolean isLast) {
//此处有坑,isLast为true时会返回标点符号
if (null != result && !isLast) {
String text = parseIatResult(result.getResultString());
JSONObject obj = new JSONObject();
try {
obj.put("searchText", text);
} catch (JSONException e) {
Log.d(LOG_TAG, "This should never happen");
}
if( null != mIat ){
// 退出时释放连接
mIat.cancel();
mIat.destroy();
}
getSearchText(obj);
} else {
Log.d(LOG_TAG, "recognizer result : null");
}
}

@Override
public void onEndOfSpeech() {
// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
Log.d(LOG_TAG, "结束说话");
}

@Override
public void onBeginOfSpeech() {
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
Log.d(LOG_TAG, "开始说话");
}

@Override
public void onError(SpeechError error) {
Log.d(LOG_TAG, "onError Code:" + error.getErrorCode());
}

@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
// 若使用本地能力,会话id为null
}

};

处理结果并返回App端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 处理结果
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"));
}
} catch (Exception e) {
e.printStackTrace();
}
return ret.toString();
}
//将结果通过callbackContext返回App端
public void getSearchText(JSONObject obj) {
this.callbackContext.success(obj);
}

App端调用sdk并监听数据返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//语音识别
$scope.record = function () {
if (window.cordova && window.cordova.plugins) {
if(!$scope.recording){
//调用sdk
cordova.plugins.xFeiVoice.record({}, function (result) {
if(result){
//返回识别结果
$scope.search.goodKey = result.searchText;
$scope.openModal();
$scope.recording = false;
}
console.log('success');
}, function (result) {
console.log('fail');
});
}else{
//$scope.recordMedia.stopRecord();
}
$scope.recording = !$scope.recording;
}
};

结语

插件代码我已经上传到github了,有需要的可以clone。走过路过给个star吧~

CATALOG
  1. 1. 前言
  2. 2. 技术基础
  3. 3. 创建Cordova插件
    1. 3.1. 全局安装plugman
    2. 3.2. 创建插件
    3. 3.3. 安装插件
  4. 4. 对接语音识别
    1. 4.1. 导入sdk
    2. 4.2. 添加权限
    3. 4.3. 初始化语音识别对象
    4. 4.4. 初始化监听器
    5. 4.5. 设置参数
    6. 4.6. 识别监听器
    7. 4.7. 处理结果并返回App端
    8. 4.8. App端调用sdk并监听数据返回
  5. 5. 结语