【语音助手】在Unity中接入语音相关功能——实现语音指令任务


title: 在Unity中接入语音相关功能(一)
date: 2023-09-14
description: 通过调用Android平台下的语音助手实现语音唤醒、语音识别、语音合成。

在Unity中接入语音相关功能(一)

若要在Unity中实现一个类似语音助手的功能,那则需使用到语音识别相关技术。

语音相关功能

  • 语音识别(Automatic Speech Recognition,ASR):识别语音内容,转化为相应的文字。

  • 语音合成(Text to Speech,TTS):将文字信息转变为可以听得懂的、流利的汉语。

  • 语音唤醒 (VoiceWake up): 通过识别辨认特定的词汇来返回预置好的结果(唤醒设备)。

以上,是常使用的语音技术。而现在有许多平台(讯飞、百度AI、思必驰…)带有Andorid (Java)SDK供我们选择。

讯飞思必驰百度AI

此外,ASRT(Auto Speech Recognition Tool)也提供了Java版SDK。通过这个开源库也可实现ASR。

在Unity中调用后台语音服务

前提条件

安卓设备安装了作者实现的“语音助手”APK。

unity中调用流程

导入依赖

1、将aar拷贝至Unity的Assets/Plugins/Android/目录下即可。

2、导入当前仓库中的VoiceAssistantService脚本

repo

编写C#脚本

下面这个示例脚本,演示了如何调用“语音助手”的服务。

通过Set…Callback的方式,即可取到不同阶段的回调结果。

注:EqLog会去执行android.util.Log的相关方法。以便于在Logcat中查看日志

public class VoiceService : MonoBehaviour
{
    private void Awake()
    {
        //绑定“语音助手”的后台服务
        VoiceAssistantService.Instance.Bind();
        EqLog.i("Ikkyu","Bind:  ");
    }

    void Start()
    {
        //设置相关事件回调
        VoiceAssistantService.Instance
            .SetAsrResultsCallback(UpdateText)
            .SetAsrReadyCallback(OnAsrReady)
            .SetAsrEndCallback(OnAsrEnd)
            .SetWakeupCallback(WhenWakeup)
            .SetTtsSpeechStartCallback(WhenTtsStart)
            .SetTtsSpeechFinishCallback(WhenTtsEnd);

		//启动服务
        VoiceAssistantService.Instance.StartService();
        EqLog.i("Ikkyu","Start:  ");
    }
    
    private void OnDestroy()
    {
        //取消绑定
        VoiceAssistantService.Instance.Unbind();
    }

    void UpdateText(string text)
    {
        //语音识别结果
        EqLog.i("Ikkyu","UpdateText:  " + text);
    }

    void OnAsrReady()
    {
        EqLog.i("Ikkyu","OnAsrReady");
    }
    void OnAsrEnd()
    {
        EqLog.i("Ikkyu","OnAsrEnd");
    }

    void WhenWakeup(double confiendce,string wakeupWord)
    {
        EqLog.i("Ikkyu","word:" + wakeupWord + "_" + confiendce);
    }

    void WhenTtsStart()
    {
        EqLog.i("Ikkyu","WhenTtsStart");
    }

    void WhenTtsEnd()
    {
        EqLog.i("Ikkyu","WhenTtsEnd");
    }
}

创建测试场景

在unity编辑器中创建“语音服务”场景,新建一个名为“语音服务”的游戏对象,并挂载“VoiceService”脚本。

挂载脚本

至此,打包运行,结束!

运行结果

查看logcat日志结果如下:

日志截图

可见,唤醒词唤醒后,监测到“打开微信”的语音指令。

参考流程图如下:

指令流程

实现语音指令任务

在上面的运行过程中,监听到”打开微信“这个指令。那么如何我们怎样实现这些指令任务呢?

实现思路

  1. 提取指令中的关键词,匹配任务类型
  2. 根据对应任务类型的指令,调用对应的方法

此外,为了语音交互更加友好,还得添加任务执行后的语音反馈。

关键类

抽象任务

注:以下在Android Studio中编写

创建一个任务的抽象类,列出所必需的公用方法

/**
 * 语音任务
 * <pre>
 *     子类必需要有一个init()方法,参数按业务需要添加。init方法内必须给taskTxt赋值
 *     此外,子类需要实现{@link #getKey()}、{@link #getTips(int)} ()}、{@link #exec(int)} ()}
 * </pre>
 * @version 1.0
 **/
public abstract class BaseTask {

    /**
     * 执行任务
     * @param taskId 任务指令
     * @return 任务返回状态
     */
    @Keep
    protected abstract void exec(int taskId);

    /**
     * 获取提示语
     * <p>如:(正在为您)打开蓝牙</p>
     * @return
     */
    protected abstract String getTips(int taskId);

    /**
     * 设置任务指令
     * @param commandTxt
     */
    public void setCommandTxt(CommandTxt[] commandTxt){
        this.taskTxt = commandTxt;
    }
    //...
}

任务示例

以实现“打开微信”为例

新增继承BaseTask的子类,名为“ProgramTask”。


/**
 * 程序打开任务
 **/
public class ProgramTask extends BaseTask{

    /**
     * 程序包名
     */
    private String packageName;

    private final int openCmd = 0;
    private String programName;

    //...
    
    /**
     * 初始化
     * @param context 上下文
     * @param programName 程序名称
     * @param packageName 程序包名
     */
    public ProgramTask init(Context context,String programName,String packageName) {
		//...
        return this;
    }

    @Override
    protected void exec(int taskId) {
        //注意:若语音指令匹配,会自动调用本方法
        if (packageName == null) {
            Log.e(getClass().getSimpleName(), "exec: packageName was null.");
        }
        switch (taskId){
            case openCmd:
                openApp();
        }
    }

    //打开程序
    private void openApp() {
        Intent launchIntent = getContext().getPackageManager().getLaunchIntentForPackage(packageName);
        if (launchIntent != null) {
            getContext().startActivity(launchIntent);
        } else {
            // 应用程序未安装或不存在
            Log.e(getClass().getSimpleName(), "exec: packageName was not exist.");
        }
    }
    
    //...
}

语音任务管理器

采用单例,实现任务管理器,实现指令任务匹配。

/**
 * 语音任务管理器
 **/
public class VoiceTaskManager {
    private static volatile VoiceTaskManager instance = null;
    private HashMap<String,BaseTask> taskHashMap;

    //...
    
    private VoiceTaskManager() {
        this.taskHashMap = new HashMap<String,BaseTask>();
        //...
    }

    /**
     * 获取实例
     */
    public static VoiceTaskManager getInstance() {
        if (instance == null){
            synchronized (VoiceTaskManager.class){
                if (instance == null){
                    instance = new VoiceTaskManager();
                }
            }
        }
        return instance;
    }

    /**
     * 任务注册
     * @param task 任务项
     */
    public void register(BaseTask task){
        //...
        taskHashMap.put(task.getKey(), task);
    }

    /**
     * 移除已注册的任务
     * @param task 任务
     */
    public void unregister(BaseTask task){
        //...
        taskHashMap.remove(task);
    }

    /**
     * 执行语音任务
     * @param voiceTest 语音文本内容
     * @return 关键词,若返回null,则表示没有匹配到任务
     */
    public String execTask(String voiceTest){
		//...
    }
    
    //...
}

调用方式

参考下列流程调用接口

  1. 程序初始化时,执行下面的方法注册语音任务。
   private void initVoiceTask() {
        //todo 后续实现通过配置文件自动载入
        VoiceTaskManager instance = VoiceTaskManager.getInstance();
        //添加蓝牙开关任务
        instance.register(new BluetoothTask().init(this));
        //添加音量调节任务
        instance.register(new VolumeTask().init(this));
        //添加亮度调节任务
        instance.register(new BrightnessTask().init(this));

        //自定义程序
        instance.register(new ProgramTask().init(this,"QQ","com.tencent.mobileqq"));
        instance.register(new ProgramTask().init(this,"微信","com.tencent.mm"));
    }
  1. 在匹配到语音指令后(如:打开微信),执行任务

示例如下:

VoiceTaskManager.getInstance().execTask("打开微信");

其它

起初,“后台语音服务”采用内置录音机采集音频,但是一些设备不具备内置麦克风。

若是设备具有蓝牙硬件,那么也可以实现语音交互。

补充说明

这里仅做参考,您可通过更多的AndoridStudio与unity联合开发方式实现自定义接口。

这里使用VoiceAssiantService脚本,需要用到我的Maven仓库下的一个包(针对AndroidJava与Unity交互提供的aar)

下载地址:unity-dev

这里的VoiceAssistantService脚本是一个单例,用于调用AndroidJava接口。

这里的做法:
1、AndroidJava中创建VoiceAssistantService.java文件,实现对三方语音库的调用(这里使用的是思必驰的,当然也可用讯飞或其它的平台SDK)
2、Unity中编写与之对应的VoiceAssistantService.cs代码。

AndroidJava部分直接使用Maven仓库下的aar包即可吗,unity端的VoiceAssistantService.cs脚本如下:


{
    /// <summary>
    /// 语音服务出错时触发
    /// </summary>
    /// <param name="error"></param>
    public delegate void OnSpeechError(string error);

    /// <summary>
    /// 语音服务识别委托
    /// </summary>
    /// <param name="content">识别结果</param>
    public delegate void OnAsrResults(string content);

    /// <summary>
    /// ASR就绪
    /// </summary>
    public delegate void OnAsrReady();

    /// <summary>
    /// ASR监测到声音时触发
    /// </summary>
    public delegate void OnAsrBegin();

    /// <summary>
    /// ASR结束时触发
    /// </summary>
    public delegate void OnAsrEnd();

    /// <summary>
    /// ASR监测到音量变化时触发
    /// </summary>
    /// <param name="rms"></param>
    public delegate void OnAsrRmsChanged(float rms);

    /// <summary>
    /// ASR监测超时触发
    /// </summary>
    public delegate void OnAsrTimeout();

    /// <summary>
    /// 语音唤醒时触发
    /// </summary>
    /// <param name="confidence">置信度</param>
    /// <param name="wakeupWord">唤醒词</param>
    public delegate void OnWakeup(double confidence, string wakeupWord);

    /// <summary>
    /// TTS开始合成语音时触发
    /// </summary>
    public delegate void OnTtsSynthesizeStart();

    /// <summary>
    /// TTS语音合成结束时触发
    /// </summary>
    public delegate void OnTtsSynthesizeFinish();

    /// <summary>
    /// TTS开始说话时触发
    /// </summary>
    public delegate void OnTtsSpeechStart();

    /// <summary>
    /// TTS说话结束时触发
    /// </summary>
    public delegate void OnTtsSpeechFinish();

    /// <summary>
    /// 语音助手服务
    /// </summary>
    public class VoiceAssistantService
    {
        private static readonly object lockObject = new object();
        private static VoiceAssistantService instance = null;
        private AndroidJavaObject service;

        //SetErrorCallback
        private OnSpeechError onSpeechError { get; set; }

        //SetAsrResultsCallback
        private OnAsrResults onAsrResults { get; set; }

        //SetAsrReadyCallback
        private OnAsrReady onAsrReady { get; set; }

        //SetAsrBeginCallback
        private OnAsrBegin onAsrBegin { get; set; }

        //SetAsrEndCallback
        private OnAsrEnd onAsrEnd { get; set; }

        //SetAsrRmsChangedCallback
        private OnAsrRmsChanged onAsrRmsChanged { get; set; }

        //SetAsrTimeoutCallback
        private OnAsrTimeout onAsrTimeout { get; set; }

        //SetWakeupCallback
        private OnWakeup onWakeup { get; set; }

        //SetTtsSynthesizeStartCallback
        private OnTtsSynthesizeStart onTtsSynthesizeStart { get; set; }

        //SetTtsSynthesizeFinishCallback
        private OnTtsSynthesizeFinish onTtsSynthesizeFinish { get; set; }

        //SetTtsSpeechStartCallback
        private OnTtsSpeechStart onTtsSpeechStart { get; set; }

        //SetTtsSpeechFinishCallback
        private OnTtsSpeechFinish onTtsSpeechFinish { get; set; }

        /// <summary>
        /// 设置出错时的回调
        /// </summary>
        /// <param name="callback">OnSpeechError</param>
        /// <returns></returns>
        public VoiceAssistantService SetErrorCallback(OnSpeechError callback)
        {
            this.onSpeechError = callback;
            return this;
        }

        /// <summary>
        /// 设置ASR识别到结果时的回调
        /// </summary>
        /// <param name="callback">OnAsrResults</param>
        /// <returns></returns>
        public VoiceAssistantService SetAsrResultsCallback(OnAsrResults callback)
        {
            this.onAsrResults = callback;
            return this;
        }

        /// <summary>
        /// 设置ASR就绪时的回调
        /// </summary>
        /// <param name="callback">OnAsrReady</param>
        /// <returns></returns>
        public VoiceAssistantService SetAsrReadyCallback(OnAsrReady callback)
        {
            this.onAsrReady = callback;
            return this;
        }

        /// <summary>
        /// 设置ASR监测开始时的回调
        /// </summary>
        /// <param name="callback">OnAsrBegin</param>
        /// <returns></returns>
        public VoiceAssistantService SetAsrBeginCallback(OnAsrBegin callback)
        {
            this.onAsrBegin = callback;
            return this;
        }

        /// <summary>
        /// 设置ASR监测结束时的回调
        /// </summary>
        /// <param name="callback">OnAsrEnd</param>
        /// <returns></returns>
        public VoiceAssistantService SetAsrEndCallback(OnAsrEnd callback)
        {
            this.onAsrEnd = callback;
            return this;
        }

        /// <summary>
        /// 设置ASR监测到音量变化时的回调
        /// </summary>
        /// <param name="callback">OnAsrRmsChanged</param>
        /// <returns></returns>
        public VoiceAssistantService SetAsrRmsChangedCallback(OnAsrRmsChanged callback)
        {
            this.onAsrRmsChanged = callback;
            return this;
        }

        /// <summary>
        /// 设置语音识别超时的回调
        /// </summary>
        /// <param name="callback">OnAsrTimeout</param>
        /// <returns></returns>
        public VoiceAssistantService SetAsrTimeoutCallback(OnAsrTimeout callback)
        {
            this.onAsrTimeout = callback;
            return this;
        }

        /// <summary>
        /// 设置语音唤醒的回调
        /// </summary>
        /// <param name="callback">OnWakeup</param>
        /// <returns></returns>
        public VoiceAssistantService SetWakeupCallback(OnWakeup callback)
        {
            this.onWakeup = callback;
            return this;
        }

        /// <summary>
        /// 设置TTS-语音合成开始时的回调
        /// </summary>
        /// <param name="callback">OnTtsSynthesizeStart</param>
        /// <returns></returns>
        public VoiceAssistantService SetTtsSynthesizeStartCallback(OnTtsSynthesizeStart callback)
        {
            this.onTtsSynthesizeStart = callback;
            return this;
        }

        /// <summary>
        /// 设置TTS-语音合成完成时的回调
        /// </summary>
        /// <param name="callback">OnTtsSynthesizeFinish</param>
        /// <returns></returns>
        public VoiceAssistantService SetTtsSynthesizeFinishCallback(OnTtsSynthesizeFinish callback)
        {
            this.onTtsSynthesizeFinish = callback;
            return this;
        }

        /// <summary>
        /// 设置TTS-说话开始时的回调
        /// </summary>
        /// <param name="callback">OnTtsSpeechStart</param>
        /// <returns></returns>
        public VoiceAssistantService SetTtsSpeechStartCallback(OnTtsSpeechStart callback)
        {
            this.onTtsSpeechStart = callback;
            return this;
        }

        /// <summary>
        /// 设置TTS-说话结束时的回调
        /// </summary>
        /// <param name="callback">OnTtsSpeechFinish</param>
        /// <returns></returns>
        public VoiceAssistantService SetTtsSpeechFinishCallback(OnTtsSpeechFinish callback)
        {
            this.onTtsSpeechFinish = callback;
            return this;
        }

        /// <summary>
        /// 获取单例
        /// </summary>
        public static VoiceAssistantService Instance
        {
            get
            {
                lock (lockObject)
                {
                    if (instance == null)
                    {
                        instance = new VoiceAssistantService();
                    }
                    return instance;
                }
            }
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        private VoiceAssistantService()
        {
            if (Application.platform != RuntimePlatform.Android)
            {
                return;
            }

            AndroidJavaClass voiceAssistantServiceCls = new AndroidJavaClass("com.eqgis.speech.VoiceAssistantService");
            service = voiceAssistantServiceCls.CallStatic<AndroidJavaObject>("getInstance");
        }

        /// <summary>
        /// 绑定语音服务
        /// </summary>
        public void Bind()
        {
            if (service == null) return;

            service.Call("bind");
        }

        /// <summary>
        /// 开始使用语音服务
        /// </summary>
        /// <param name="speechRecognize">语音识别回调</param>
        public void StartService()
        {
            if (service == null) return;

            service.Call("startService", new Callback(this));
        }

        /// <summary>
        /// 停止使用语音服务
        /// </summary>
        public void StopService() {  
            if (service == null) return;

            service.Call("stopService");
        }

        /// <summary>
        /// 解除语音服务绑定
        /// </summary>
        public void Unbind()
        {
            if (service == null) return;

            service.Call("unbind");
        }


        /// <summary>
        /// 语音识别回调
        /// </summary>
        private class Callback : UnitySpeechCallback
        {
            private VoiceAssistantService service;

            public Callback(VoiceAssistantService service)
            {
                this.service = service;
            }

            /// <summary>
            /// 识别结果回调
            /// </summary>
            /// <param name="content"></param>
            public override void OnResults(string str)
            {
                if(service.onAsrResults != null)
                {
                    service.onAsrResults(str);
                }
            }


            /// <summary>
            /// ASR就绪
            /// </summary>
            public override void OnReadyForSpeech()
            {
                if(service.onAsrReady != null)
                {
                    service.onAsrReady();
                }
            }

            /// <summary>
            /// ASR检测到开始有声音输入
            /// </summary>
            public override void OnBeginningOfSpeech()
            {
                if (service.onAsrBegin != null)
                {
                    service.onAsrBegin();
                }
            }

            /// <summary>
            /// ASR结束
            /// </summary>
            public override void OnEndOfSpeech()
            {
                if (service.onAsrEnd != null)
                {
                    service.onAsrEnd();
                }
            }

            /// <summary>
            /// ASR检测到有音量变化
            /// </summary>
            /// <param name="var1"></param>
            public override void OnRmsChanged(float var1)
            {
                if (service.onAsrRmsChanged != null)
                {
                    service.onAsrRmsChanged(var1);
                }
            }

            /// <summary>
            /// ASR\TTS\Wakeup出错时触发
            /// </summary>
            /// <param name="error"></param>
            public override void OnError(string error)
            {
                if (service.onSpeechError != null)
                {
                    service.onSpeechError(error);
                }
            }

            /// <summary>
            /// ASR超时
            /// </summary>
            public override void OnAsrTimeout()
            {
                if (service.onAsrTimeout != null)
                {
                    service.onAsrTimeout();
                }
            }

            /// <summary>
            /// 唤醒
            /// </summary>
            /// <param name="confidence">置信度</param>
            /// <param name="wakeupWord">唤醒词</param>
            public override void OnWakeup(double confidence, string wakeupWord)
            {
                if (service.onWakeup != null)
                {
                    service.onWakeup(confidence,wakeupWord);
                }
            }

            /// <summary>
            /// 语音开始合成
            /// </summary>
            /// <param name="utteranceId"></param>
            public override void OnSynthesizeStart(string utteranceId)
            {
                if (service.onTtsSynthesizeStart != null)
                {
                    service.onTtsSynthesizeStart();
                }
            }

            /// <summary>
            /// 语音合成结束
            /// </summary>
            /// <param name="utteranceId"></param>
            public override void OnSynthesizeFinish(string utteranceId)
            {
                if (service.onTtsSynthesizeFinish != null)
                {
                    service.onTtsSynthesizeFinish();
                }
            }

            /// <summary>
            /// TTS开始说话
            /// </summary>
            /// <param name="utteranceId"></param>
            public override void OnSpeechStart(string utteranceId)
            {
                if (service.onTtsSpeechStart != null)
                {
                    service.onTtsSpeechStart();
                }
            }

            /// <summary>
            /// TTS说话结束
            /// </summary>
            /// <param name="utteranceId"></param>
            public override void OnSpeechFinish(string utteranceId)
            {
                if (service.onTtsSpeechFinish != null)
                {
                    service.onTtsSpeechFinish();
                }
            }
        }
    }

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EQ-雪梨蛋花汤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值