【Android】灵云在线语音识别使用说明
来源:互联网 发布:香水时代淘宝店真假 编辑:程序博客网 时间:2024/04/29 03:22
注册
使用灵云的语音识别功能需要先在官网上进行注册应用。官网地址
注册比较简单,就不做过多介绍了,注册完应用以后,在后台创建自己的应用,创建完应用以后需要给应用开通对应的语音能力。
capKey说明:
- asr.cloud.freetalk 云端自由说识别功能
- asr.cloud.dialog 云端自由说+语义理解功能
- asr.cloud.grammar 云端语法识别功能
- asr.local.grammar.v4 本地的语法识别功能
使用asr.cloud.dialog功能时,需要勾选对应的领域,以下示例按照百科领域进行测试。
集成
下载灵云的Android版本语音识别功能,下载地址
源码
Github
灵云在线语音识别功能
需要加入的so和jar包有:
- libhci_curl.so
- libhci_sys.so
- libhci_sys_jni.so
- libhci_asr.so
- libhci_asr_jni.so
- libhci_asr_cloud_recog.so
- libspeex.so
- libstlport_shared.so
- hcicloud-5.0.jar
- hcicloud_recorder-5.0.jar
需要使用的权限
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
加载配置类
package com.miao.test.util;/** * 灵云配置信息 * Created by 10048 on 2016/12/3. */public class ConfigUtil { /** * 灵云APP_KEY */ public static final String APP_KEY = "c85d54f1"; /** * 开发者密钥 */ public static final String DEVELOPER_KEY = "712ddd892cf9163e6383aa169e0454e3"; /** * 灵云云服务的接口地址 */ public static final String CLOUD_URL = "test.api.hcicloud.com:8888"; /** * 需要运行的灵云能力 */ // 云端自由说 public static final String CAP_KEY_ASR_CLOUD_FREETALK = "asr.cloud.freetalk"; // 云端语音识别+语义 public static final String CAP_KEY_ASR_CLOUD_DIALOG = "asr.cloud.dialog"; // 离线命令词 public static final String CAP_KEY_ASR_LOCAL_GRAMMAR = "asr.local.grammar.v4"; // 在线命令词 public static final String CAP_KEY_ASR_CLOUD_GRAMMAR = "asr.cloud.grammar";}
封装灵云系统的初始化功能
package com.example.sinovoice.util;import android.content.Context;import android.os.Environment;import android.util.Log;import com.sinovoice.hcicloudsdk.api.HciCloudSys;import com.sinovoice.hcicloudsdk.common.AuthExpireTime;import com.sinovoice.hcicloudsdk.common.HciErrorCode;import com.sinovoice.hcicloudsdk.common.InitParam;import java.io.File;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Locale;/** * Created by miaochangchun on 2016/11/28. */public class HciCloudSysHelper { private static final String TAG = HciCloudSysHelper.class.getSimpleName(); private static HciCloudSysHelper mHciCloudSysHelper = null; private HciCloudSysHelper(){ } public static HciCloudSysHelper getInstance() { if (mHciCloudSysHelper == null) { return new HciCloudSysHelper(); } return mHciCloudSysHelper; } /** * 初始化函数 * @param context * @return */ public int init(Context context){ //配置串参数 String strConfig = getInitParam(context); int errCode = HciCloudSys.hciInit(strConfig, context); if (errCode != HciErrorCode.HCI_ERR_NONE){ Log.e(TAG, "hciInit Failed and return errcode = " + errCode); return errCode; } errCode = checkAuthAndUpdateAuth(); if (errCode != HciErrorCode.HCI_ERR_NONE) { Log.e(TAG, "checkAuthAndUpdateAuth Failed and return errcode = " + errCode); return errCode; } return HciErrorCode.HCI_ERR_NONE; } /** * 获取授权 * @return */ private int checkAuthAndUpdateAuth() { // 获取系统授权到期时间 AuthExpireTime objExpireTime = new AuthExpireTime(); int initResult = HciCloudSys.hciGetAuthExpireTime(objExpireTime); if (initResult == HciErrorCode.HCI_ERR_NONE) { // 显示授权日期,如用户不需要关注该值,此处代码可忽略 Date date = new Date(objExpireTime.getExpireTime() * 1000); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA); Log.i(TAG, "expire time: " + sdf.format(date)); if (objExpireTime.getExpireTime() * 1000 > System.currentTimeMillis()) { // 已经成功获取了授权,并且距离授权到期有充足的时间(>7天) Log.i(TAG, "checkAuth success"); return initResult; } } // 获取过期时间失败或者已经过期 initResult = HciCloudSys.hciCheckAuth(); if (initResult == HciErrorCode.HCI_ERR_NONE) { Log.i(TAG, "checkAuth success"); return initResult; } else { Log.e(TAG, "checkAuth failed: " + initResult); return initResult; } } /** * 获取配置传参数 * @param context * @return */ private String getInitParam(Context context) { InitParam initParam = new InitParam(); //灵云云服务的接口地址,此项必填 initParam.addParam(InitParam.AuthParam.PARAM_KEY_APP_KEY, ConfigUtil.APP_KEY); //灵云云服务的接口地址,此项必填 initParam.addParam(InitParam.AuthParam.PARAM_KEY_DEVELOPER_KEY, ConfigUtil.DEVELOPER_KEY); //灵云云服务的接口地址,此项必填 initParam.addParam(InitParam.AuthParam.PARAM_KEY_CLOUD_URL, ConfigUtil.CLOUD_URL); String authPath = context.getFilesDir().getAbsolutePath(); //授权文件所在路径,此项必填 initParam.addParam(InitParam.AuthParam.PARAM_KEY_AUTH_PATH, authPath); //日志数目,默认保留多少个日志文件,超过则覆盖最旧的日志 initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_COUNT, "5"); String logPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "sinovoice" + File.separator + context.getPackageName() + File.separator + "log" + File.separator; File file = new File(logPath); if (!file.exists()) { file.mkdirs(); } //日志的路径,可选,如果不传或者为空则不生成日志 initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_PATH, logPath); Log.d(TAG, "logPath = " + logPath); //日志大小,默认一个日志文件写多大,单位为K initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_SIZE, "1024"); //日志等级,0=无,1=错误,2=警告,3=信息,4=细节,5=调试,SDK将输出小于等于logLevel的日志信息 initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5"); return initParam.getStringConfig(); } /** * 反初始化 * @return */ public int release(){ return HciCloudSys.hciRelease(); }}
封装灵云语音识别功能
package com.miao.test.util;import android.content.Context;import android.os.Bundle;import android.os.Handler;import android.os.Message;import com.sinovoice.hcicloudsdk.android.asr.recorder.ASRRecorder;import com.sinovoice.hcicloudsdk.common.asr.AsrConfig;import com.sinovoice.hcicloudsdk.common.asr.AsrInitParam;import com.sinovoice.hcicloudsdk.common.asr.AsrRecogResult;import com.sinovoice.hcicloudsdk.recorder.ASRRecorderListener;import com.sinovoice.hcicloudsdk.recorder.RecorderEvent;/** * Created by miaochangchun on 2016/11/28. */public class HciCloudAsrHelper { private static final String TAG = HciCloudAsrHelper.class.getSimpleName(); public static final int RECORDER_STATE = 3; public static final int RECORDER_ERROR = 2; public static final int RECORDER_RESULT = 1; private static HciCloudAsrHelper mHciCloudAsrHelper = null; private ASRRecorder mASRRecorder; private Handler myHander;// private String voicePath; //音频文件保存路径// public void setVoicePath(String voicePath) {// this.voicePath = voicePath;// }// public String getVoicePath() {// return voicePath;// } private HciCloudAsrHelper() { } public static HciCloudAsrHelper getInstance() { if (mHciCloudAsrHelper == null) { return new HciCloudAsrHelper(); } return mHciCloudAsrHelper; } public Handler getMyHander() { return myHander; } public void setMyHander(Handler myHander) { this.myHander = myHander; } /** * 录音机初始化 * * @param context 上下文 * @param initCapkeys 初始化录音机时设置的capkey,可以设置为多个。 * @return true录音机初始化成功,false录音机初始化失敗 */ public boolean initAsrRecorder(Context context, String initCapkeys) { mASRRecorder = new ASRRecorder(); String strConfig = getAsrInitParam(context, initCapkeys); // 语法相关的配置,若使用自由说能力可以不必配置该项// String grammarData = "";// String grammarConfigString = "capkey=" + initCapkeys + ",isFile=no,grammarType=jsgf"; mASRRecorder.init(strConfig, "", "", new ASRRecorderCallback()); if (mASRRecorder.getRecorderState() == ASRRecorder.RECORDER_STATE_IDLE) { return true; } else { return false; } } /** * 获取初始化时的参数配置 * * @param context 上下文 * @param initCapkeys 需要初始化的capkey,可以设置为多个 * @return */ private String getAsrInitParam(Context context, String initCapkeys) { AsrInitParam asrInitParam = new AsrInitParam(); asrInitParam.addParam(AsrInitParam.PARAM_KEY_INIT_CAP_KEYS, initCapkeys); asrInitParam.addParam(AsrInitParam.PARAM_KEY_FILE_FLAG, "android_so"); String dataPath = context.getFilesDir().getAbsolutePath().replace("files", "lib"); asrInitParam.addParam(AsrInitParam.PARAM_KEY_DATA_PATH, dataPath); return asrInitParam.getStringConfig(); } /** * 开启录音机识别 * * @param capkey 开启录音机时使用的capkey * @param domain 设置识别的领域,没有特殊设置,domain=common * @param isContinue 设置是否为连续识别,yes为是连续识别,no是非连续识别 */ public void startAsrRecorder(String capkey, String domain, String isContinue) { if (mASRRecorder.getRecorderState() == mASRRecorder.RECORDER_STATE_RECOGING) { mASRRecorder.cancel(); } if (mASRRecorder.getRecorderState() == ASRRecorder.RECORDER_STATE_IDLE) { String strConfig = getAsrConfigParam(capkey, domain, isContinue); mASRRecorder.start(strConfig); } } /** * 取消录音识别功能 */ public void CancelAsrRecorder() { if (mASRRecorder.getRecorderState() == mASRRecorder.RECORDER_STATE_RECOGING) { mASRRecorder.cancel(); } } /** * 获取asr识别时的配置参数 * * @param capkey 录音机识别是的配置参数capkey * @param domain 设置的领域值 * @param isContinue 设置是否为连续识别 * @return */ private String getAsrConfigParam(String capkey, String domain, String isContinue) { AsrConfig asrConfig = new AsrConfig(); asrConfig.addParam(AsrConfig.AudioConfig.PARAM_KEY_AUDIO_FORMAT, "pcm16k16bit"); asrConfig.addParam(AsrConfig.AudioConfig.PARAM_KEY_ENCODE, "speex"); asrConfig.addParam(AsrConfig.SessionConfig.PARAM_KEY_CAP_KEY, capkey); asrConfig.addParam(AsrConfig.SessionConfig.PARAM_KEY_REALTIME, "yes"); asrConfig.addParam(AsrConfig.ResultConfig.PARAM_KEY_ADD_PUNC, "yes"); asrConfig.addParam(AsrConfig.ResultConfig.PARAM_KEY_DOMAIN, domain); asrConfig.addParam("continue", isContinue); if ("asr.cloud.dialog".equals(capkey)) { //intention需要在开发者社区勾选,必须要设置对应的asr.cloud.dialog才可以使用,默认使用baike的领域 asrConfig.addParam("intention", "baike"); } return asrConfig.getStringConfig(); } /** * 录音机release接口 */ public void releaseAsrRecorder() { if (mASRRecorder != null) { mASRRecorder.release(); } } /** * ASR录音机回调类 */ private class ASRRecorderCallback implements ASRRecorderListener { String result = ""; @Override public void onRecorderEventStateChange(RecorderEvent recorderEvent) { String state = "初始状态"; if (recorderEvent == RecorderEvent.RECORDER_EVENT_BEGIN_RECORD) { state = "开始录音"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_BEGIN_RECOGNIZE) { state = "开始识别"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_NO_VOICE_INPUT) { state = "无音频输入"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_HAVING_VOICE) { state = "录音中"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_END_RECORD) { state = "录音结束"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_RECOGNIZE_COMPLETE) { state = "识别结束"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_VOICE_BUFFER_FULL) { state = "缓冲满"; } //把录音机状态传递到Activity上 Message message = new Message(); message.arg1 = RECORDER_STATE; Bundle bundle = new Bundle(); bundle.putString("state", "录音机状态:" + state); message.setData(bundle); myHander.sendMessage(message); } @Override public void onRecorderEventRecogFinsh(RecorderEvent recorderEvent, AsrRecogResult asrRecogResult) { if (asrRecogResult != null) { if (asrRecogResult.getRecogItemList().size() > 0) { //识别结果 result = asrRecogResult.getRecogItemList().get(0).getRecogResult(); //置信度 int score = asrRecogResult.getRecogItemList().get(0).getScore(); //把识别结果传递到Activity上 Message message = new Message(); message.arg1 = RECORDER_RESULT; Bundle bundle = new Bundle(); bundle.putString("result", "识别结果是:" + result); message.setData(bundle); myHander.sendMessage(message); } } } @Override public void onRecorderEventRecogProcess(RecorderEvent recorderEvent, AsrRecogResult asrRecogResult) { } @Override public void onRecorderEventError(RecorderEvent recorderEvent, int i) { String error = "" + i; //把错误信息传递到Activity上 Message message = new Message(); message.arg1 = RECORDER_ERROR; Bundle bundle = new Bundle(); bundle.putString("error", "错误码:" + error); message.setData(bundle); myHander.sendMessage(message); } @Override public void onRecorderRecording(byte[] bytes, int i) {// File file = new File(voicePath);// if (!file.exists()) {// file.getParentFile().mkdirs();// try {// file.createNewFile();// } catch (IOException e) {// e.printStackTrace();// }// }// try {// FileOutputStream outputStream = new FileOutputStream(file);// outputStream.write(bytes);// outputStream.close();// } catch (FileNotFoundException e) {// e.printStackTrace();// } catch (IOException e) {// e.printStackTrace();// } } }}
在MainActivity中使用语音识别的功能
package com.miao.test.util;import android.content.Context;import android.os.Bundle;import android.os.Handler;import android.os.Message;import com.sinovoice.hcicloudsdk.android.asr.recorder.ASRRecorder;import com.sinovoice.hcicloudsdk.common.asr.AsrConfig;import com.sinovoice.hcicloudsdk.common.asr.AsrInitParam;import com.sinovoice.hcicloudsdk.common.asr.AsrRecogResult;import com.sinovoice.hcicloudsdk.recorder.ASRRecorderListener;import com.sinovoice.hcicloudsdk.recorder.RecorderEvent;/** * Created by miaochangchun on 2016/11/28. */public class HciCloudAsrHelper { private static final String TAG = HciCloudAsrHelper.class.getSimpleName(); public static final int RECORDER_STATE = 3; public static final int RECORDER_ERROR = 2; public static final int RECORDER_RESULT = 1; private static HciCloudAsrHelper mHciCloudAsrHelper = null; private ASRRecorder mASRRecorder; private Handler myHander;// private String voicePath; //音频文件保存路径// public void setVoicePath(String voicePath) {// this.voicePath = voicePath;// }// public String getVoicePath() {// return voicePath;// } private HciCloudAsrHelper() { } public static HciCloudAsrHelper getInstance() { if (mHciCloudAsrHelper == null) { return new HciCloudAsrHelper(); } return mHciCloudAsrHelper; } public Handler getMyHander() { return myHander; } public void setMyHander(Handler myHander) { this.myHander = myHander; } /** * 录音机初始化 * * @param context 上下文 * @param initCapkeys 初始化录音机时设置的capkey,可以设置为多个。 * @return true录音机初始化成功,false录音机初始化失敗 */ public boolean initAsrRecorder(Context context, String initCapkeys) { mASRRecorder = new ASRRecorder(); String strConfig = getAsrInitParam(context, initCapkeys); // 语法相关的配置,若使用自由说能力可以不必配置该项// String grammarData = "";// String grammarConfigString = "capkey=" + initCapkeys + ",isFile=no,grammarType=jsgf"; mASRRecorder.init(strConfig, "", "", new ASRRecorderCallback()); if (mASRRecorder.getRecorderState() == ASRRecorder.RECORDER_STATE_IDLE) { return true; } else { return false; } } /** * 获取初始化时的参数配置 * * @param context 上下文 * @param initCapkeys 需要初始化的capkey,可以设置为多个 * @return */ private String getAsrInitParam(Context context, String initCapkeys) { AsrInitParam asrInitParam = new AsrInitParam(); asrInitParam.addParam(AsrInitParam.PARAM_KEY_INIT_CAP_KEYS, initCapkeys); asrInitParam.addParam(AsrInitParam.PARAM_KEY_FILE_FLAG, "android_so"); String dataPath = context.getFilesDir().getAbsolutePath().replace("files", "lib"); asrInitParam.addParam(AsrInitParam.PARAM_KEY_DATA_PATH, dataPath); return asrInitParam.getStringConfig(); } /** * 开启录音机识别 * * @param capkey 开启录音机时使用的capkey * @param domain 设置识别的领域,没有特殊设置,domain=common * @param isContinue 设置是否为连续识别,yes为是连续识别,no是非连续识别 */ public void startAsrRecorder(String capkey, String domain, String isContinue) { if (mASRRecorder.getRecorderState() == mASRRecorder.RECORDER_STATE_RECOGING) { mASRRecorder.cancel(); } if (mASRRecorder.getRecorderState() == ASRRecorder.RECORDER_STATE_IDLE) { String strConfig = getAsrConfigParam(capkey, domain, isContinue); mASRRecorder.start(strConfig); } } /** * 取消录音识别功能 */ public void CancelAsrRecorder() { if (mASRRecorder.getRecorderState() == mASRRecorder.RECORDER_STATE_RECOGING) { mASRRecorder.cancel(); } } /** * 获取asr识别时的配置参数 * * @param capkey 录音机识别是的配置参数capkey * @param domain 设置的领域值 * @param isContinue 设置是否为连续识别 * @return */ private String getAsrConfigParam(String capkey, String domain, String isContinue) { AsrConfig asrConfig = new AsrConfig(); asrConfig.addParam(AsrConfig.AudioConfig.PARAM_KEY_AUDIO_FORMAT, "pcm16k16bit"); asrConfig.addParam(AsrConfig.AudioConfig.PARAM_KEY_ENCODE, "speex"); asrConfig.addParam(AsrConfig.SessionConfig.PARAM_KEY_CAP_KEY, capkey); asrConfig.addParam(AsrConfig.SessionConfig.PARAM_KEY_REALTIME, "yes"); asrConfig.addParam(AsrConfig.ResultConfig.PARAM_KEY_ADD_PUNC, "yes"); asrConfig.addParam(AsrConfig.ResultConfig.PARAM_KEY_DOMAIN, domain); asrConfig.addParam("continue", isContinue); if ("asr.cloud.dialog".equals(capkey)) { //intention需要在开发者社区勾选,必须要设置对应的asr.cloud.dialog才可以使用,默认使用baike的领域 asrConfig.addParam("intention", "baike"); } return asrConfig.getStringConfig(); } /** * 录音机release接口 */ public void releaseAsrRecorder() { if (mASRRecorder != null) { mASRRecorder.release(); } } /** * ASR录音机回调类 */ private class ASRRecorderCallback implements ASRRecorderListener { String result = ""; @Override public void onRecorderEventStateChange(RecorderEvent recorderEvent) { String state = "初始状态"; if (recorderEvent == RecorderEvent.RECORDER_EVENT_BEGIN_RECORD) { state = "开始录音"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_BEGIN_RECOGNIZE) { state = "开始识别"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_NO_VOICE_INPUT) { state = "无音频输入"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_HAVING_VOICE) { state = "录音中"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_END_RECORD) { state = "录音结束"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_RECOGNIZE_COMPLETE) { state = "识别结束"; } else if (recorderEvent == RecorderEvent.RECORDER_EVENT_VOICE_BUFFER_FULL) { state = "缓冲满"; } //把录音机状态传递到Activity上 Message message = new Message(); message.arg1 = RECORDER_STATE; Bundle bundle = new Bundle(); bundle.putString("state", "录音机状态:" + state); message.setData(bundle); myHander.sendMessage(message); } @Override public void onRecorderEventRecogFinsh(RecorderEvent recorderEvent, AsrRecogResult asrRecogResult) { if (asrRecogResult != null) { if (asrRecogResult.getRecogItemList().size() > 0) { //识别结果 result = asrRecogResult.getRecogItemList().get(0).getRecogResult(); //置信度 int score = asrRecogResult.getRecogItemList().get(0).getScore(); //把识别结果传递到Activity上 Message message = new Message(); message.arg1 = RECORDER_RESULT; Bundle bundle = new Bundle(); bundle.putString("result", "识别结果是:" + result); message.setData(bundle); myHander.sendMessage(message); } } } @Override public void onRecorderEventRecogProcess(RecorderEvent recorderEvent, AsrRecogResult asrRecogResult) { } @Override public void onRecorderEventError(RecorderEvent recorderEvent, int i) { String error = "" + i; //把错误信息传递到Activity上 Message message = new Message(); message.arg1 = RECORDER_ERROR; Bundle bundle = new Bundle(); bundle.putString("error", "错误码:" + error); message.setData(bundle); myHander.sendMessage(message); } @Override public void onRecorderRecording(byte[] bytes, int i) {// File file = new File(voicePath);// if (!file.exists()) {// file.getParentFile().mkdirs();// try {// file.createNewFile();// } catch (IOException e) {// e.printStackTrace();// }// }// try {// FileOutputStream outputStream = new FileOutputStream(file);// outputStream.write(bytes);// outputStream.close();// } catch (FileNotFoundException e) {// e.printStackTrace();// } catch (IOException e) {// e.printStackTrace();// } } }}
注:灵云的在线识别功能需要联网才能使用。
0 0
- 【Android】灵云在线语音识别使用说明
- 【Android】灵云离线语音识别使用说明
- Android 百度在线语音识别
- 百度Android在线语音识别SDK使用方法
- 科大讯飞在线语音识别
- 【Android】灵云手写离线识别使用说明
- 灵云语音识别
- android 百度语音识别(离在线)以及唤醒功能
- 【Android】灵云在线语义理解功能使用说明
- 【Android】灵云银行卡离线识别功能使用说明
- android 语音识别接口
- android语音识别简介
- Android语音识别
- android语音识别代码
- android 本地语音识别
- android 语音识别
- android 语音识别
- Android-语音识别
- React Native Android 应用层实战沦陷记
- <转>基于Spark的大数据精准营销中搜狗搜索引擎的用户画像挖掘
- Android写第三方库时候用Java代码设置圆角效果
- Ubuntu下安装Docker
- QT QTableView用法小结
- 【Android】灵云在线语音识别使用说明
- Java基础面试题
- 【Android】灵云手写离线识别使用说明
- 进程和线程
- 关于JFinal的框架-逻辑架构
- android开发之wifi无线调试
- 蓝鸥React Native零基础入门到项目实战 组件
- 【Android】灵云银行卡离线识别功能使用说明
- oracle db link的查看创建与删除