前言
语音识别在现在的APP中是常见的,但是通常小的项目中我们不会去费心思自己去做这一块的业务功能开发,常规的是接入第三方的SDK快速实现功能,比如百度、讯飞之类的,百度语音识别之前我已经写过了,本着雨露均沾的原则的,写这篇讯飞的SDK对接步骤,开始吧~
效果图
点击
识别中,识别到之后
源码在文章最后面
正文
首先呢,你先去注册讯飞的开发者账号,点击讯飞开放平台,完成注册登录。
注册好之后你可以选择实名认证或者不认证都可以,然后登录进入控制台或者我的应用。
创建一个应用
填写信息然后提交
点击这个应用名称查看详细信息
右边的是对接过程需要用到的值,APPID用SDK中,APIKey或APISecret适用于WebAPI调用方式,这两者有什么区别呢?SDK的话很好理解,复制一些文件到你的项目中,然后添加依赖,你就可以使用讯飞的SDK中的语音识别功能了,而WebAPI调用顾名思义是通过网络接口携带参数的方式去实现的,需要自己去做网络请求,本文中目前使用SDK的方式进行对接实现功能,如果有想看WebAPI调用方式的请在评论区留言,
OK,现在滑动到下面下载Android SDK
可以看到,最新的版本是1143,当然这取决于你看文章的时间,点击下载。
下载到本地,然后解压,打开文件目录如下:
然后就是创建你自己项目了,打开AS
一、配置资源文件
我取名是XFASRDemo,然后Finish,等待项目创建好之后,复制文件中libs里面的三个文件,到项目的libs中。
右键Mac.jar添加Add As Library
点击OK添加,添加好之后你的jar包就会有一个三角箭头,可以展开,这个时候你就可以使用里面的方法了。
然后复制资源文件到main下面
二、配置项目
创建SpeechApplication.java
import android.app.Application;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechUtility;
public class SpeechApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 5ef048e1 为在开放平台注册的APPID 注意没有空格,直接替换即可,这个=号保留
SpeechUtility.createUtility(SpeechApplication.this, SpeechConstant.APPID + "=5ef048e1");
}
}
打开AndroidManifest.xml,增加权限配置
<!--连接网络权限,用于执行云端语音能力 -->
<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"/>
配置SpeechApplication和注册权限
然后修改app模块下面的build.gradle
然后在app模块下build.gradle中配置如下代码:
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
添加位置如下图所示:
改完记得Sync Now
一下,然后修改布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="识别到的内容"
android:textColor="#000" />
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="开始识别" />
</LinearLayout>
接下来就是MainActivity了
三、编码
首先我们开启一下ViewBinding,也在app模块下的build.gradle中,添加如下代码:
buildFeatures {
viewBinding true
}
点击Sync Now
。
① 声明变量和初始化
private static final String TAG = "MainActivity";
private ActivityMainBinding binding;
private SpeechRecognizer mIat;// 语音听写对象
private RecognizerDialog mIatDialog;// 语音听写UI
// 用HashMap存储听写结果
private HashMap<String, String> mIatResults = new LinkedHashMap<>();
private SharedPreferences mSharedPreferences;//缓存
private String mEngineType = SpeechConstant.TYPE_CLOUD;// 引擎类型
private String language = "zh_cn";//识别语言
private String resultType = "json";//结果内容数据格式
写onCreate中的方法,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.btnStart.setOnClickListener(v -> {
});
这里我们使用了viewBinding,同时给按钮添加一个点击事件。
② 动态权限请求
/**
* android 6.0 以上需要动态申请权限
*/
private void initPermission() {
String[] permissions = {Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.INTERNET,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
ArrayList<String> toApplyList = new ArrayList<>();
for (String perm : permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
toApplyList.add(perm);
}
}
String[] tmpList = new String[toApplyList.size()];
if (!toApplyList.isEmpty()) {
ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
}
}
/**
* 权限申请回调,可以作进一步处理
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// 此处为android 6.0以上动态授权的回调,用户自行实现。
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
如果Manifest报错就在添加加一个android.
,例如Manifest.permission.RECORD_AUDIO
就改成android.Manifest.permission.RECORD_AUDIO
,在onCreate()
方法中调用,
initPermission();//权限请求
如下图所示:
③ 语音监听
/**
* 初始化监听器。
*/
private final InitListener mInitListener = code -> {
Log.d(TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
showMsg("初始化失败,错误码:" + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
}
};
/**
* 听写UI监听器
*/
private final RecognizerDialogListener mRecognizerDialogListener = new RecognizerDialogListener() {
public void onResult(RecognizerResult results, boolean isLast) {
printResult(results);//结果数据解析
}
/**
* 识别回调错误.
*/
public void onError(SpeechError error) {
showMsg(error.getPlainDescription(true));
}
};
/**
* 提示消息
* @param msg
*/
private void showMsg(String msg) {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
里面用到一个方法,用于解析监听到的结果
④ 数据解析
/**
* 数据解析
*
* @param results
*/
private void printResult(RecognizerResult results) {
String text = JsonParser.parseIatResult(results.getResultString());
String sn = null;
// 读取json结果中的sn字段
try {
JSONObject resultJson = new JSONObject(results.getResultString());
sn = resultJson.optString("sn");
} catch (JSONException e) {
e.printStackTrace();
}
mIatResults.put(sn, text);
StringBuffer resultBuffer = new StringBuffer();
for (String key : mIatResults.keySet()) {
resultBuffer.append(mIatResults.get(key));
}
binding.tvResult.setText(resultBuffer.toString());//听写结果显示
}
里面用到一个JsonParser的工具类,需要手动去创建一个
代码如下:
package com.llw.xfasrdemo;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
/**
* Json结果解析类
*/
public class JsonParser {
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();
}
}
⑤ 参数配置
/**
* 参数设置
*
* @return
*/
public void setParam() {
// 清空参数
mIat.setParameter(SpeechConstant.PARAMS, null);
// 设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
// 设置返回结果格式
mIat.setParameter(SpeechConstant.RESULT_TYPE, resultType);
if (language.equals("zh_cn")) {
String lag = mSharedPreferences.getString("iat_language_preference",
"mandarin");
Log.e(TAG, "language:" + language);// 设置语言
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
// 设置语言区域
mIat.setParameter(SpeechConstant.ACCENT, lag);
} else {
mIat.setParameter(SpeechConstant.LANGUAGE, language);
}
Log.e(TAG, "last language:" + mIat.getParameter(SpeechConstant.LANGUAGE));
//此处用于设置dialog中不显示错误码信息
//mIat.setParameter("view_tips_plain","false");
// 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
mIat.setParameter(SpeechConstant.VAD_BOS, mSharedPreferences.getString("iat_vadbos_preference", "4000"));
// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
mIat.setParameter(SpeechConstant.VAD_EOS, mSharedPreferences.getString("iat_vadeos_preference", "1000"));
// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
mIat.setParameter(SpeechConstant.ASR_PTT, mSharedPreferences.getString("iat_punc_preference", "1"));
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/iat.wav");
}
⑥ 代码组装
在onCreate中增加如下代码,进行初始化
// 使用SpeechRecognizer对象,可根据回调消息自定义界面;
mIat = SpeechRecognizer.createRecognizer(MainActivity.this, mInitListener);
// 使用UI听写功能,请根据sdk文件目录下的notice.txt,放置布局文件和图片资源
mIatDialog = new RecognizerDialog(MainActivity.this, mInitListener);
mSharedPreferences = getSharedPreferences("ASR",
Activity.MODE_PRIVATE);
然后再按钮点击事件中添加代码:
binding.btnStart.setOnClickListener(v -> {
if( null == mIat ){
// 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
showMsg( "创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化" );
return;
}
mIatResults.clear();//清除数据
setParam(); // 设置参数
mIatDialog.setListener(mRecognizerDialogListener);//设置监听
mIatDialog.show();// 显示对话框
});
添加位置代码如下:
最后在onDestory中
@Override
protected void onDestroy() {
super.onDestroy();
if (null != mIat) {
// 退出时释放连接
mIat.cancel();
mIat.destroy();
}
}
这样逻辑就很清楚了
⑦ 运行效果图
点击
识别中,识别到之后
四、梳理
从平台创建应用之后的到一个appid,然后新建一个项目,先配置AndroidManifest.xml,配置了静态权限和Application,在Application中对讯飞语音插件进行初始化,这样就可以影响到全局,而不是在某一个Activity的中onCreate进行初始化,当然你也可以这样做。然后初始化控件和讯飞的一些工具类,并实现点击监听,之后进行动态权限的获取,成功之后,进行语音监听(包括UI对话框)的接口实现,在返回值中做数据解析,得到需要的数据显示在页面的TextView上,然后再配置一些需要在调用语音识别之前需要的参数,比如语言类型和结果类型之类的,之后就是点击出现语音识别的UI对话框,当你说完之后会消失,并识别到你说的内容解析显示在页面上。至此,语音识别完成。
五、源码
这里无非就是一些感想而已,其实实现功能的方法有很多,文章中只是其中之一而已,欢迎留言讨论,我是初学者-Study,山高水长,后会有期~
项目源码:XFASRDemo
温馨提示:请在自己的手机(真机)上运行,别用Android的虚拟器或者任何其他的模拟器,你会出现如评论区的朋友所遇到相同的问题:“创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化”。原因就是虚拟机cpu是x86的,在讯飞的SDK中没有x86对应的libmsc.so。所以你会报错。但是如果你用真机还是报这个错的话,那你可以理直气壮的去讯飞的官网上提问,让他们开发人员给你解决。