2023-09-14-在Unity中接入语音相关功能(一)

在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("打开微信");

其它

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

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

posted @ 2024-05-30 17:31  EQ-雪梨蛋花汤  阅读(155)  评论(0)    收藏  举报