title: 在Unity中接入语音相关功能(一)
date: 2023-09-14
description: 通过调用Android平台下的语音助手实现语音唤醒、语音识别、语音合成。
在Unity中接入语音相关功能(一)
若要在Unity中实现一个类似语音助手的功能,那则需使用到语音识别相关技术。
语音相关功能
-
语音识别(Automatic Speech Recognition,ASR):识别语音内容,转化为相应的文字。
-
语音合成(Text to Speech,TTS):将文字信息转变为可以听得懂的、流利的汉语。
-
语音唤醒 (VoiceWake up): 通过识别辨认特定的词汇来返回预置好的结果(唤醒设备)。
以上,是常使用的语音技术。而现在有许多平台(讯飞、百度AI、思必驰…)带有Andorid (Java)SDK供我们选择。
此外,ASRT(Auto Speech Recognition Tool)也提供了Java版SDK。通过这个开源库也可实现ASR。
在Unity中调用后台语音服务
前提条件
安卓设备安装了作者实现的“语音助手”APK。
unity中调用流程
导入依赖
1、将aar拷贝至Unity的Assets/Plugins/Android/目录下即可。
-
eq-aidl-1.0.2.aar :封装了调用“语音助手”的接口
-
unitydev-1.0.6.aar :简化了Unity与Java的互相调用
2、导入当前仓库中的VoiceAssistantService脚本
编写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日志结果如下:
可见,唤醒词唤醒后,监测到“打开微信”的语音指令。
参考流程图如下:
实现语音指令任务
在上面的运行过程中,监听到”打开微信“这个指令。那么如何我们怎样实现这些指令任务呢?
实现思路
- 提取指令中的关键词,匹配任务类型
- 根据对应任务类型的指令,调用对应的方法
此外,为了语音交互更加友好,还得添加任务执行后的语音反馈。
关键类
抽象任务
注:以下在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){
//...
}
//...
}
调用方式
参考下列流程调用接口
- 程序初始化时,执行下面的方法注册语音任务。
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"));
}
- 在匹配到语音指令后(如:打开微信),执行任务
示例如下:
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();
}
}
}
}