最近须要对接语音识别业务,毕竟如今是AI时代,一个产品若是能经过AI能力给用户带来全新的体验,也是很值得尝试的。技术对接上选择的是科大讯飞开放平台,也算是国内最先一批作语音识别的企业了,文档方面都比较全面,对接起来也很方便。前端
开发技术栈为Cordova
+Angular
+Ionic
,这篇分享会介绍如何从头开始建立Cordova插件,并实现科大讯飞Android sdk与App端的数据交互。java
Apache Cordova
是一个开源的移动开发框架。容许你用标准的web技术——HTML5,CSS3和JavaScript作跨平台开发。 应用在每一个平台的具体执行被封装了起来,并依靠符合标准的API绑定去访问每一个设备的功能,好比说:传感器、数据、网络状态等。android
在继续阅读以前,应该确保你有经过Cordova
建立并打包一个简单Hybrid App
的经验,感兴趣的童鞋能够到Ionic官网和Cordova官网学习下。git
plugman
用于建立Cordova插件,在项目目录下执行cnpm i -g plugman
github
建立一个插件并添加android平台,并生成package.json,插件名xFeiVoice
,插件idcom.qinsilk.xFeiVoice
,版本号为0.01
web
plugman create --name xFeiVoice --plugin_id com.qinsilk.xFeiVoice --plugin_version 0.0.1
cd xFeiVoice
plugman createpackagejson ./
plugman platform add --platform_name android
复制代码
var exec = require('cordova/exec');
exports.coolMethod = function (arg0, success, error) {
exec(success, error, 'xFeiVoice', 'coolMethod', [arg0]);
};
复制代码
此时咱们作下修改,让参数名跟业务命名更加相关。npm
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安装的插件。 json
Android sdk能够去科大讯飞开放平台下载。将在官网下载的Android SDK 压缩包中libs目录下全部子文件拷贝至Android工程的libs目录下。以下图所示:后端
在工程 AndroidManifest.xml 文件中添加以下权限:bash
<!--链接网络权限,用于执行云端语音能力 -->
<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
是在开发插件时,用户的自定义方法,当页面调用插件时系统首先将会运行此方法。
@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;
}
复制代码
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);
}
}
};
复制代码
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");
}
复制代码
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
}
};
复制代码
// 处理结果
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);
}
复制代码
//语音识别
$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吧~