Unity 实战项目 ☀️| 接入科大讯飞语音SDK在Android Studio该如何操作!

呆呆敲代码的小Y 发表于 2021/08/17 09:48:02 2021/08/17
【摘要】 Unity接入科大讯飞安卓端操作。
  • 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
  • 📢本文由 呆呆敲代码的小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

iflytekmain文件夹下新建一个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端该如何操作! 超级新手教程!

请添加图片描述
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。