- 📢博客主頁:https://blog.csdn.net/zhangay1998
- 📢歡迎點贊 👍 收藏 ?留言 📝 如有錯誤敬請指正!
- 📢本文由 呆呆敲代碼的小Y 原創,首發于 CSDN🙉
- 📢未來很長,值得我們全力奔赴更美好的生活?
目錄
- 📢前言
- 🍉在Android Studio該如何操作訊飛語音SDK
- 🏳??🌈第一步:打開Android Studio新建一個專案
- 🏳??🌈第二步:建立最終使用的Module檔案夾
- 🏳??🌈第三步:訊飛SDK集成
- 1.接入Unity的ckasses.jar包
- 2.接入訊飛語音的classess.jar包
- 3.關聯兩個classes.jar包
- 4.添加libmsc.so
- 5.修改AndroidManifest檔案
- 🏳??🌈第四步:新建一個class開始寫代碼
- 創建類
- 上代碼
- 代碼分析
- 🏳??🌈第五步:打包aar
- 在AS中提取aar
- 修改aar和AndroidManifest
- 💬總結

📢前言
-
系列文章總共分為三篇這是Unity接入語音識別的第二篇,第一篇是怎樣獲取到訊飛語音的相關SDK
-
沒有看的小伙伴可以去看一下這篇文章,很簡單的獲取SDK介紹!
-
Unity 實戰專案 ??| 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端該如何操作! 超級新手教程!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/289793.html
標籤:其他
