Unity 实战项目 ☀️| 接入科大讯飞语音SDK在Android Studio该如何操作!
- 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
- 📢本文由 呆呆敲代码的小Y 原创🙉
- 📢未来很长,值得我们全力奔赴更美好的生活✨
@TOC
📢前言
-
系列文章总共分为三篇这是Unity接入语音识别的第二篇,第一篇是怎样获取到讯飞语音的相关SDK
-
没有看的小伙伴可以去看一下上一篇文章,很简单的获取SDK介绍!
🍉在Android Studio该如何操作讯飞语音SDK
- 虽然最终效果是在Unity中接入语音识别SDK,但是在Android Studio(下面统称AS)这一步才是最重要的
- 相关的语音识别的代码和逻辑都是在AS写的,最终实现在Unity中的效果也不过是调用AS端的接口来实现的
- 所以说这一块接入也是可以在AS中打包成APK直接导出的!
那这里就正式开始在AS端的操作吧!
🏳️🌈第一步:打开Android Studio新建一个项目
打开AS,新建一个项目,选择一个Empty Activity,点击Next
在这里插入图片描述然后来到下一步,自己修改工程的名字和路径,其他的忽略即可!
也可以自定义一个最低支持的安卓版本Minimnum SDK,不过无伤大雅!看你心情~
点击Finish之后,一个工程就创建好了,打开就是如下画面。
但是我们并不使用这个Activity,具体接着往下看
在这里插入图片描述
🏳️🌈第二步:建立最终使用的Module文件夹
然后我们这里新建一个module,起一个名字
File-new-new Module(下图)
然后选中Android Library,点击Next
在这里插入图片描述
然后修改一个名字,点击Finish即可!
在这里插入图片描述
然后创建完成后就来到了下面这个页面,这个iFlytek项目就是我们要进行操作的文件夹。
红框里面的内容就是我们要操作的地方!
在这里插入图片描述
🏳️🌈第三步:讯飞SDK集成
这一步较为繁琐,跟配置环境差不多,就是将各种Jar包和so文件添加到AS中去
下面一步一步来看!
1.接入Unity的ckasses.jar包
-
我们首先需要拿到Unity的class包,这个包在安装Unity的路径下
-
具体路径:Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes
-
比如我的路径就是:D:\QMDownload\SoftMgr\2020.3.8f1c1\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes
直接选中classes.jar复制到AS中的iflytek-libs文件夹下!
操作如下:
在这里插入图片描述
在这里插入图片描述
这样就把Unity的Jar包加到AS了,下面再把我们下载的讯飞SDK中的Jar包添加进去
2.接入讯飞语音的classess.jar包
找到我们下载解压的讯飞语音的SDK,进入libs文件夹下,将那三个文件前复制到libs中去!
与上一步Unity的Jar操作一样!
在这里插入图片描述
效果如下,我们总共添加了四个文件,分别是从Unity复制过去的Jar包和从讯飞的SDK复制过去的三个文件
在这里插入图片描述
3.关联两个classes.jar包
有的小伙伴复制过去是这样的,两个Jar包并不能展开
那是因为还没有关联,下面就来关联一下Jar包
第一种方法:
选中两个.jar包,右键
右键libs文件夹下两个.jar文件,Add As Libray…
在这里插入图片描述
第二种方法:
也可以右键 iflyte -> Open Module Settings
将.jar文件手动添加,添加完了记得点apply应用一下
在这里插入图片描述
在这里插入图片描述
关联之后,还是在 右键 iflyte -> Open Module Settings
这里可以看有没有关联完毕,没关联上的话,这里不会显示这俩个.jar包
4.添加libmsc.so
在iflytek的main文件夹下新建一个Jnilibs文件夹,注意字符不能打错!!
在这里插入图片描述
然后将libmsc.so添加进去,ibmsc.so在讯飞SDK文件夹里的libs\armeabi-v7a下,最好连armeabi-v7a文件夹一起复制进去。
效果如下:
在这里插入图片描述
这样的话so文件就算是添加进去了,下一步 来修改AndroidManifest文件
5.修改AndroidManifest文件
将 app -> src -> main -> res 下的AndroidManifest中的所有内容
复制到我们iflytek -> src -> main -> res 的AndroidManifest中,如下
复制到我们下面这个iflytek -> src -> main -> res 的AndroidManifes之后会报红
那我们把中间框的报红的都删掉,上面的package也改一下名字,改为:com.example.iflytek
下面的也要改为:android:name=".MainPort",这里要注意哈
我下面写错了,这里一定要改成MainPort(要改成继承UnityPlayerActivity的类,这里是MainPort)
如果这里不对,app直接打不开,也算是一个小失误了
改完名字后是下面这样的,但是还没完!
在这里插入图片描述
还要在这里加上下面一行代码,注意加入代码的位置别放错了!
加入这一行是为了在Unity中可以正常调用,不加这一行到时候就调用不到啦!
<meta-data android:name="unityplayer.UnityActivity" android:value="true"/>
最后,再加上权限:
<!--连接网络权限,用于执行云端语音能力 -->
<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" />
AndroidManifes效果如下,我这里是有一行报错,直接右键自动修复就好了!
最终的效果如下,如果出现哪里报错,那应该是哪个地方添加代码的位置不对
或者多删除了某个符号,仔细看看就好了!
在这里插入图片描述
到这一步结束,其实算刚配置完环境
真正的代码还没开始写呢!下面一起来看看怎样写代码!
🏳️🌈第四步:新建一个class开始写代码
创建类
然后我们选中com.example.iflytek ->New->Java Class
新建一个Class写代码用的
改个名字点击OK即可!第一个asrManager类就建立完成了
在这里插入图片描述
然后老样子再来一次,创建一个新的
在这里插入图片描述
这里的话,将它改为一个接口,点击中间那个框将class改为Interface即可,点击OK
在这里插入图片描述
然后再创建两个类:MainPort和JsonParser
在这里插入图片描述
整理一下,我们现在一共有三个类和一个接口,如下所示:
在这里插入图片描述
上代码
-
直接上代码,然后下面再来说一下,每个脚本是干嘛的!
-
如果复制完一下代码,AS中的import com.unity3d.player.UnityPlayerActivity;这一行会报错
-
说明Unity的Jar包没有正确的导入关联,我第一次用2020版本的导入就是报红
-
所以换了一个unity2019的Jar包导入就正常了!
-
asrManager类:这个脚本特别需要注意!!!这个地方一定要换成我们去讯飞官网自己创建的应用
-
每个应用都有自己的APPID,这里使用我这个肯定会出问题,换成自己的APPID就好了!
-
之前有的小伙伴出错,很可能就是这里出错了!
在这里插入图片描述
package com.example.iflytek;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.example.iflytek.JsonParser;
import com.example.iflytek.MainPort;
import com.example.iflytek.UnityasrEventCallback;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.SpeechUtility;
import com.unity3d.player.UnityPlayer;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.LinkedHashMap;
public class asrManager {
private SpeechRecognizer mIat;
private HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();
private static Activity activity;
private static asrManager asrManager;
public static asrManager getasrManager(Activity context) {
if (asrManager == null) {
asrManager = new asrManager(context);
}
return asrManager;
}
public asrManager(Activity activity) {
this.activity=activity;
}
//初始化
public void Initasr() {
Log.i("@@@", "语音初始化+获取权限 ");
getPermission();
SpeechUtility.createUtility(activity, SpeechConstant.APPID + "=60307482");
mIat = SpeechRecognizer.createRecognizer(activity, null);
//设置mIat的参数
//表示是什么服务
mIat.setParameter(SpeechConstant.DOMAIN, "iat");
//设置语言
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
//接受语言的类型
mIat.setParameter(SpeechConstant.ACCENT, "mandarin");
//使用什么样引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
}
RecognizerListener mRecognizerLis=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) {
printResult(recognizerResult);
}
@Override
public void onError(SpeechError speechError) {
}
@Override
public void onEvent(int i, int i1, int i2, Bundle bundle) {
}
};
//解析Json的方法
//方法来自speechDemo->java->voicedemo->IatDemo中的printResult方法
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));
}
//将语音内容回调给Unity
mCallback.Speechcontent(resultBuffer.toString());
//显示完语音的Toast
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity,"语音识别完成",Toast.LENGTH_SHORT).show();
}
});
}
//获取权限
public void getPermission() {
Log.d("@@@", "开始获取各类权限 ");
activity.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 0x01);
Log.d("@@@", "各类权限获取成功 ");
}
public void getBeginListen(){
Log.d("@@@", "开始判断权限是否获取 ");
//使用录音前首先需要获取权限
int permissionCheck= activity.checkSelfPermission(android.Manifest.permission.RECORD_AUDIO);
//如果有权限则返回PackageManager.PERMISSION_GRANTED,否则返回PackageManager.PERMISSION_DENIED。
if(permissionCheck!= PackageManager.PERMISSION_GRANTED) {//未获取权限时
//请求获取权限
Log.d("@@@", "正在请求权限 ");
activity.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 0x01);
//用new String[]的原因是可以在String[]中存储多个需要的权限,一次过请求
//将回调onRequestPermissionsResult()方法
}else{
//开始识别
mIat.startListening(mRecognizerLis);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity,"语音识别开始",Toast.LENGTH_LONG).show();
}
});
}
}
public int beginTest(int a, int b){
//交互测试
return a+b;
}
//把消息发送给Unity场景中iFlytekASRController物体上的tryConnected方法
public void connected(){
//UnityPlayer.UnitySendMessage("iFlytekASRController","tryConnected","连通成功了");
Log.d("@@@", "SetListenerCB start ");
mCallback.Test1("连通成功了");
Log.d("@@@", "SetListenerCB end ");
}
private UnityasrEventCallback mCallback;
//获取接口内容
public void setCallback(UnityasrEventCallback callback){
Log.d("@@@", "UnitasrEventCallback setCallback start ");
mCallback = callback;
Log.d("@@@", "UnityasrEventCallback setCallback end ");
}
}
UnityasrEventCallback代码:
package com.example.iflytek;
public interface UnityasrEventCallback {
public void Speechcontent(String content);
public void Test1(String msg);
}
MainPort代码:
package com.example.iflytek;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.example.iflytek.UnityasrEventCallback;
import com.example.iflytek.asrManager;
import com.iflytek.cloud.SpeechUtility;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
public class MainPort extends UnityPlayerActivity {
private asrManager masrManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//初始化
masrManager = asrManager.getasrManager(MainPort.this);
masrManager.Initasr();
UnityPlayer.UnitySendMessage("iFlytekASRController","InitCallBack","初始化成功了");
}
// =========================ASR========================= }
//得到Unity的回调
public void setUnityasrEventCallback(UnityasrEventCallback callback){
Log.d("@@@", "setUnityBatteryEventCallback callback ========= " +callback);
if(masrManager != null) {
masrManager.setCallback(callback);
}
}
public void unityStartSpeech(){
masrManager.getBeginListen();
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case 0x01: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("@@@", "进入获取权限成功的回调了 ");
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else if(grantResults[0] != PackageManager.PERMISSION_GRANTED)
{
// permission denied, boo! Disable the
// functionality that depends on this permission.
Log.d("@@@", "进入获取权限失败的回调了 ");
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainPort.this,"您拒绝了录音权限,请手动去应用->设置,修改权限",Toast.LENGTH_SHORT).show();
}
});
}
return;
}
}
}
//测试
public void unityProxyDemo(){
Log.e("@@@", "ProxyDemo start ");
masrManager.connected();
}
// =========================ASR========================= }
}
JsonParser代码:
package com.example.iflytek;
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"));
// 如果需要多候选结果,解析数组其他字段
// for(int j = 0; j < items.length(); j++)
// {
// JSONObject obj = items.getJSONObject(j);
// ret.append(obj.getString("w"));
// }
}
} catch (Exception e) {
e.printStackTrace();
}
return ret.toString();
}
public static String parseGrammarResult(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");
for(int j = 0; j < items.length(); j++)
{
JSONObject obj = items.getJSONObject(j);
if(obj.getString("w").contains("nomatch"))
{
ret.append("没有匹配结果.");
return ret.toString();
}
ret.append("【结果】" + obj.getString("w"));
ret.append("【置信度】" + obj.getInt("sc"));
ret.append("\n");
}
}
} catch (Exception e) {
e.printStackTrace();
ret.append("没有匹配结果.");
}
return ret.toString();
}
public static String parseLocalGrammarResult(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");
for(int j = 0; j < items.length(); j++)
{
JSONObject obj = items.getJSONObject(j);
if(obj.getString("w").contains("nomatch"))
{
ret.append("没有匹配结果.");
return ret.toString();
}
ret.append("【结果】" + obj.getString("w"));
ret.append("\n");
}
}
ret.append("【置信度】" + joResult.optInt("sc"));
} catch (Exception e) {
e.printStackTrace();
ret.append("没有匹配结果.");
}
return ret.toString();
}
public static String parseTransResult(String json,String key) {
StringBuffer ret = new StringBuffer();
try {
JSONTokener tokener = new JSONTokener(json);
JSONObject joResult = new JSONObject(tokener);
String errorCode = joResult.optString("ret");
if(!errorCode.equals("0")) {
return joResult.optString("errmsg");
}
JSONObject transResult = joResult.optJSONObject("trans_result");
ret.append(transResult.optString(key));
/*JSONArray words = joResult.getJSONArray("results");
for (int i = 0; i < words.length(); i++) {
JSONObject obj = words.getJSONObject(i);
ret.append(obj.getString(key));
}*/
} catch (Exception e) {
e.printStackTrace();
}
return ret.toString();
}
}
代码分析
如果只是单纯的快速的实现语音识别,那就直接看下一步怎样打包aar就可以了
-
先简单说一下思路,这一篇文章的内容不是通过UnityPlayer.UnitySendMessage来交互了
-
而是通过Proxy代理的方式来进行交互,这也是更合理的方法
-
至于更详细的思路那就在下一篇Unity端操作的时候细说,因为这里没有Unity端的代码,也说不明白
-
然后这里简单分析一下代码结构
-
asrManager类是具体的语音识别所需要的代码结构
-
开始执行语音识别后,也是该脚本通过与解析类JsonParser一起将解析完的语音文字通过接口类UnityasrEventCallback发送给Unity端
-
至于Unity端怎样接收的,请看下一篇内容!
-
UnityasrEventCallback接口类就是AS端向Unity端发送消息的关键!
-
至于MainPort类就是提供给Unity端调用的一个接口类
-
Unity调用的方法都来自这个脚本,而且这个脚本就是提供Unity与AS沟通的桥梁之一
-
通过这个脚本,就可以让Unity调用AS端,并且还可以通过该脚本调用其他脚本的方法!
🏳️🌈第五步:打包aar
在AS中提取aar
- 因为我们最终是要在Unity中使用的,所以需要将AS端写的代码打包成aar在Unity中使用!
- 如果是单纯想在AS端使用,那就直接导入我们下载的那个讯飞SDK中的Simple文件夹即可
- 那个是AS端的官方测试Demo,可以直接参考使用
那步入正题,开始打包AS
选中iflytek -> Build -> Make Module ‘iflytek'
等待编译片刻,没出问题最好!
小插曲:我这里打包编译出错了,提示NDK版本不对
那我就直接点击下载一个NDK!
在这里插入图片描述
编译完之后会发现iflytek下面多了一个文件夹:Build
然后按照下面的图示,找到iflytek-debug.aar和一个AndroidManifest.xml
修改aar和AndroidManifest
将这两个文件复制出来,这就是我们想在AS端取得的东西!!!
然后打开aar文件(打不开的话先修改后缀为.zip就可以打开了)
aar包中的文件如下,进入libs文件夹,将里面的classes.jar删掉,换成根目录下的这个classes.jar
将下图中的"假"删掉,"真"的剪切进去,只能留"真"的那一个,两个Unity打包时会报错
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后修改aar包中的AndroidManifest,将 “android:label="这行删掉,这里是设置打包出来的apk名字,这里不删会与aar包外的那个AndroidManifest冲突(跟着做就对了,我都是摸爬滚打一堆bug改过来的…)
在这里插入图片描述
修改aar包外的AndroidManifest,只需要改package即可,这里改成与包内的不一样即可,但是在Unity playerSetting中的PackageName一定要与这里设置的一样,要不然unity掉不到AS中写的方法。如下图:
在这里插入图片描述
💬总结
- 到此为止,在AS端需要做的操作就全写完了,也是最复杂的一部分已经完成了
- 主要就是为了将这个aar包打包出来!!!到时候直接放到Unity中使用就好了
- 如果觉得还不够详细,请就地活埋了博主吧!!!
请看下一篇:Unity 实战项目 ☀️| Unity接入讯飞语音SDK ,在Unity端该如何操作! 超级新手教程!
- 点赞
- 收藏
- 关注作者
评论(0)