使用的引擎工具:

Unity2021.3.19

android-studio-2021.1.21

第一步:

新建一个Android项目(工程名字随便啦)




unity导入音频不是audio clip unity导入声音_Powered by 金山文档


unity导入音频不是audio clip unity导入声音_unity_02


然后新建一个library


unity导入音频不是audio clip unity导入声音_语音识别_03


unity导入音频不是audio clip unity导入声音_unity_04


(同上,库名自己命名吧)

Android环境目前就算是初步建立好了。

第二步:

导包

libs文件夹里面放入这4个文件,arm64-v8a,armeabi-v7a,Msc.jar这三个文件是讯飞官网下载下来的demo项目里面的,直接复制到libs里面就好,classes.jar包是在下面这个路径下的


unity导入音频不是audio clip unity导入声音_Powered by 金山文档_05


(注:Classes.jar用mono还是IL2CPP得和Unity-PlayerSetting-ScriptBackend一致)


unity导入音频不是audio clip unity导入声音_android_06


第三步:

倒入UnityPlayerActivity

最新版本Unity里面的classes.jar包文件里面以及不包含UnityPlayerActivity了,所以我们需要自己导入UnityPlayerActivity(或者自己编写一个也可以,回头可以再出一篇)

文件位置:


unity导入音频不是audio clip unity导入声音_语音识别_07


unity导入音频不是audio clip unity导入声音_json_08


第四步:

在AndroidStudio实现供unity调用的接口方法,直接上代码了(讯飞APPID自己填写)

package com.example.mylibrary;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
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.unity.upa.UnityPlayerActivity;
import com.unity3d.player.UnityPlayer;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.LinkedHashMap;

public class XunFeiSdk extends UnityPlayerActivity {

private HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();
    SpeechRecognizer mIAT;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

        SpeechUtility.createUtility(this, SpeechConstant.APPID + "=xxxxxx");//这里是你在讯飞官网上的APPID
        mIAT=SpeechRecognizer.createRecognizer(this,null);
        mIAT.setParameter(SpeechConstant.DOMAIN,"iat");
        // 语言
        mIAT.setParameter(SpeechConstant.LANGUAGE,"zh_cn");
        // 接收语言的类型
        mIAT.setParameter(SpeechConstant.ACCENT,"mandarin");
        // 使用什么引擎
        mIAT.setParameter(SpeechConstant.ENGINE_TYPE,SpeechConstant.TYPE_CLOUD);
    }

public void StartListening() {

mIAT.startListening(recognizerListener);
    }

    RecognizerListener recognizerListener = new RecognizerListener() {
@Override
        public void onVolumeChanged(int i, byte[] bytes) {
            UnityPlayer.UnitySendMessage("XfManager","OnVolumeChanged","");
        }

@Override
        public void onBeginOfSpeech() {
            UnityPlayer.UnitySendMessage("XfManager","OnBeginOfSpeech","");
        }

@Override
        public void onEndOfSpeech() {
            UnityPlayer.UnitySendMessage("XfManager","OnEndOfSpeech","");
        }

@Override
        public void onResult(RecognizerResult recognizerResult, boolean b) {
            printResult(recognizerResult);
        }

@Override
        public void onError(SpeechError speechError) {
            UnityPlayer.UnitySendMessage("XfManager","OnError",speechError.getErrorDescription());
        }

@Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {

        }
    };

    private void printResult(com.iflytek.cloud.RecognizerResult results) {
// JsonParser是一个工具类
        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);
        // resultBuffer 为最终返回的结果

        StringBuffer resultBuffer = new StringBuffer();
        for (String key : mIatResults.keySet()) {
            resultBuffer.append(mIatResults.get(key));
        }
// 把得到的结果返回给Unity  第一个参数为unity种的游戏物体 第二个参数为 这个游戏物体身上脚本的方法   第三个参数为讯飞返回的最终结果
        UnityPlayer.UnitySendMessage("XfManager","OnResult",resultBuffer.toString());
    }

public  void VoidTest()
    {
        UnityPlayer.UnitySendMessage("XfManager","FromAndroid","Android:消息发送至Unity");
    }
}

Json类(这个是讯飞demo里面的)

package com.example.mylibrary;

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, String engType) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);

            JSONArray words = joResult.getJSONArray("ws");
            // 云端和本地结果分情况解析
            if ("cloud".equals(engType)) {
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");
                    }
                }
            } else if ("local".equals(engType)) {
                ret.append("【结果】");
                for (int i = 0; i < words.length(); i++) {
                    JSONObject wsItem = words.getJSONObject(i);
                    JSONArray items = wsItem.getJSONArray("cw");
                    if ("<contact>".equals(wsItem.getString("slot"))) {
// 可能会有多个联系人供选择,用中括号括起来,这些候选项具有相同的置信度
                        ret.append("【");
                        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")).append("|");
                        }
                        ret.setCharAt(ret.length() - 1, '】');
                    } else {
//本地多候选按照置信度高低排序,一般选取第一个结果即可
                        JSONObject obj = items.getJSONObject(0);
                        if (obj.getString("w").contains("nomatch")) {
                            ret.append("没有匹配结果.");
                            return ret.toString();
                        }
                        ret.append(obj.getString("w"));
                    }
                }
                ret.append("【置信度】" + joResult.getInt("sc"));
                ret.append("\n");
            }

        } catch (Exception e) {
            e.printStackTrace();
            ret.append("没有匹配结果.");
        }
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();
    }
}

代码就是以上这些了,接下来就是修改AndroidManifest

第五步:

AndroidManifest.xml文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mylibrary">

    <application
        android:allowBackup="true"
        android:supportsRtl="true">
        <activity android:name=".XunFeiSdk"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <meta-data android:name="unityplayer.UnityActivity" android:value="true"/>
    </application>


    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <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.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>

第六步:

完成以上这些步骤就可以打包成arr文件了

选中你的库文件然后点击build-Make Module,如图所示


unity导入音频不是audio clip unity导入声音_Powered by 金山文档_09


arr生成路径是


unity导入音频不是audio clip unity导入声音_json_10


结果如下


unity导入音频不是audio clip unity导入声音_Powered by 金山文档_11


把mylibrary.arr文件根目录里的classes.jar和AndroidManifest复制出来(拖出来)

下面这个是从arr包里取出来的AndroidManifest文件,把<uses-sdk android:minSdkVersion="26" />删掉


unity导入音频不是audio clip unity导入声音_语音识别_12


第七步:

下面就是Unity这边了,Unity端比较简单,直接看图吧

这个AndroidManifest文件就是第六步的AndroidManifest文件,

classes.jar文件放在Plugins-Android-bin目录下

libs目录下放的是同第二步一样的3个文件


unity导入音频不是audio clip unity导入声音_android_13


unity导入音频不是audio clip unity导入声音_语音识别_14


unity导入音频不是audio clip unity导入声音_Powered by 金山文档_15


然后Unity测试界面


unity导入音频不是audio clip unity导入声音_json_16


using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class XfManager : MonoBehaviour
{
    private AndroidJavaClass ajc;
    private AndroidJavaObject ajo;
    //private AndroidJavaObject XunFeiSdk;
    public Button StartButton,testBtn;
    public TextMeshProUGUI ResultText;

    private void Start()
    {
         ajc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
         ajo = ajc.GetStatic<AndroidJavaObject>("currentActivity");
        //XunFeiSdk = new AndroidJavaObject("com.example.mylibrary.XunFeiSdk");
        testBtn.onClick.AddListener(TestConnect);
        if (StartButton)
        {
            StartButton.onClick.AddListener(() => { StartListening(); });
        }
    }

    private void TestConnect()
    {
        ajo.Call("VoidTest");
    }


    public void FromAndroid(string s) 
    {
        ResultText.text = s;
    }

    public void StartListening()
    {
        ajo.Call("StartListening");
    }

    public void OnStartListening(string ret)
    {
        int result = int.Parse(ret);
        StartButton.interactable = result == 0;
    }

    public void OnResult(string result)
    {
        ResultText.text = result;
    }

    public void OnError(string errorMessage)
    {
        ResultText.text = errorMessage;
    }

    public void OnEndOfSpeech()
    {
        StartButton.GetComponentInChildren<TextMeshProUGUI>().text = "已结束,点击聆听";
        StartButton.interactable = true;
    }

    public void OnBeginOfSpeech()
    {
        StartButton.GetComponentInChildren<TextMeshProUGUI>().text = "聆听ing";
        StartButton.interactable = false;
    }
}

最后就是打包了


unity导入音频不是audio clip unity导入声音_Powered by 金山文档_17


注:这里的包名必须跟你配置文件里面的一致才行,还有就是再重复一遍,Classes.jar用mono还是IL2CPP得和Unity-PlayerSetting-ScriptBackend一致。


unity导入音频不是audio clip unity导入声音_Powered by 金山文档_18


打包成功,测试正常(注意开启权限哦)