概述
悟空机器人在语音识别这块官方只支持中文、英语和韩文,开发者们可能希望让悟空支持更多的语言,为此他们需要接入新的语音服务商(如Alexa, Google Assistant等),如果想让语音交互的体验达到悟空官方版本的效果,开发者需要做大量的开发工作。为此我们开发了一套开发框架SpeechFramework,方便快速接入。 该框架把麦克风阵列模块、唤醒模块、识别模块、TTS模块、交互模型整合在一起,并实现各阶段的自动切换。它实现了麦克风阵列的开关,录音数据的读取,声源定位,并实现与机器人中其他模块的通信,实现开机自动启动, 休眠时自动关闭麦克风阵列,激活时重新开启的功能。开发者只需要按照我们定义的接口,来实现唤醒、识别、TTS模块(可选),就可以实现完整的与悟空机器人内置语音模块一样的交互表现。
基本框架如下:
核心模块说明
MicArrayModule:从4-Mic中读取原始的音频数据,经过麦克风阵列前端算法处理后,输出16K HZ、 16bit、 单通道的音频数据。
WakeupModule : 实现唤醒功能,通过不断从MicArrayModule读取麦克风数据输入到唤醒引擎中监测是否发生了语音唤醒,当发生了语音唤醒时通过发布唤醒事件来触发RecognizeModule进入到识别阶段。
RecognizeModule:包含ASR、NLP、TTS过程,开发者即可以集成Amazon AVS和腾讯叮当这类端到端的完整语音服务,也可以通过分别接入ASR Api,NLP Api,TTS Api来实现语音交互功能。
Interactive Model: 当机器人被语音唤醒时,当机器人处于识别状态时,当机器人处于等待识别结果阶段时,机器人都有不同的表现,为保证交互的一致性,我们已经实现了标准的交互模型,开发者不需额外开发。
唤醒时:播报唤醒应答音频,眼睛播放微笑动画,转头声源定位;
语音识别阶段:眼睛播放眨眼动画;
NLP识别阶段:眼睛播放左右转动动画;
Note:
开发者可以通过传入音频资源的id来自定义唤醒应答,并允许传入多个;
转头声源定位的时机可以自定义,支持在唤醒时或者语音识别结束时。
编码实现
如何获取访问麦克风阵列访问权限
1.增加标签
在AndroidManifest.xml
文件application标签里,加入如下代码,申请访问麦克风阵列
<meta-data android:name="ubt-master-app" android:value="third_part_speechservice"/>
2. 首次安装该apk后,需要重启机器人
机器人在每次开机时会检查安装的apk里有没有third_part_speechservice
标记,如果有的话,
则disable系统内置的访问麦克风阵列的应用SpeechService,把麦克风阵列的访问权让给该apk。
系统内置的SpeechService被disable后,用户将无法访问腾讯叮当语音服务。
让出麦克风阵列访问权
1.卸载app或去掉meta-data的标签
2.重启机器人
重启机器人后,系统内置的SpeechService将重新获取麦克风阵列访问权限, 用户可以继续使用叮当语音服务。
SDK引入
-
下载demo工程speechmoduledemo
包含的sdk如下:
micrecord-sdk-release-versionCode.aar
mini-outer-sdk-versionCode.jar
protobuf-java-versionCode.jar
speechFramework-release-versionCode.jar
-
sdk引入:
在app module的build.gradle文件里配置如下:
android {
repositories {
flatDir {
dirs 'libs'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(name: 'speechFramework-release', ext: 'aar')
implementation(name: 'micrecord-sdk-release', ext: 'aar')
implementation 'org.greenrobot:eventbus:3.0.0'
}
-
初始化:
在自定义的Application
子类的onCreate()
的方法中加入如下代码:
//setting turn head timing and wakeup reply media file
Config config = new Config.Builder().timing(TurnHeadApi.Timing.AfterRecord).
wakeupResList(new int[] {R.raw.wakeup_001,R.raw.wakeup_002}).build();
IModuleManager.Builder builder = new IModuleManager.Builder().recognizeModuleFactory(new DemoRecognizeModuleFactory())
.ttsModuleFactory(new DemoTtsModuleFactory())
.wakeupModuleFactory(new DemoWakeupModuleFactory())
.config(config);
//inject
SpeechManager.get().setImpl(new DefaultSpeechServiceImpl(builder));
Config
可以设置声源定位转头的时机以及自定义唤醒响应音频,当设置多个音频时则会随机播放一个。音频资源建议放在app工程的res/raw/目录下,支持mp3,wav等格式。
IModuleManager
用于设置RecognizeModule的工厂类,WakeupModule的工厂类,TTSModule的工厂类以及设置Config.
SpeechManager.get().setImpl(new DefaultSpeechServiceImpl(builder));
用于把`IModuleManager注入到SpeechFramework,由SpeechFramework来实现各个Module的调用。
-
WakeupModule的实现
1.实现IWakeupModule
接口。
public interface IWakeupModule {
void init(IWakeuperListener listener);
void destroy();
void enable();
void disable();
boolean isEnable();
/**
* @param pcmData the record data after wakeup
* @param dataLen the record data length
*/
void writeAudio(byte[] pcmData, int dataLen);
}
所有的接口方法都由SpeechFramework调用,开发者必须要严格按照要求进行重写。
init():
在此方法中实现唤醒模块的初始化,当初始化成功时请调用IWakeuperListener.onInitSuccess()
当初始化失败时请调用IWakeuperListener.onInitError(errMsg,errCode)
. 只有WakeupModule初始化成功,机器人开机时才会播报已经准备好了。
enable():
使唤醒模块可用
disable():
禁用唤醒模块
isEnable():
判断唤醒模块是否可用,只有isEnable() ==true
时麦克风数据才会输入到WakeupModule。
writeAudio():
用于接收麦克风数据,麦克风数据为16K HZ, 16bit, 单通道。 开发者需要把麦克风数据持续写入到唤醒引擎。当唤醒引擎检测到唤醒时,需要调用IWakeuperListener.onWakeup(wakeword)
,当SpeechFramework收到onWakeup()
回调时Interactive Model会播报唤醒响应音频,并播放唤醒眼睛表情,同时会启动RecognizeModule,进入到语音识别阶段。
destory():
模块退出时调用,可以在此释放资源。
- 实现
WakeupModuleFactory
接口来实例化WakeupModule对象。
public interface WakeupModuleFactory {
IWakeupModule factory();
}
-
RecognizeModule的实现
1.实现IRecognizeModule接口
public interface IRecognizeModule {
void init(IRecognizerListener listener);
void destroy();
void startListening();
/**
* stop recognize
*/
void stopListening();
/**
* @param data byte[]
* @param start start >= 0
* @param end end < byte.length
*/
int writeAudio(byte[] data, int start, int end);
/**
* @return boolean Is Listening
*/
boolean isListening();
}
在该Module里可以去接入Amazon AVS、腾讯叮当这样的完整的语音服务,也可以去分别接入ASR、NLP、TTS API.
init():
进行初始化。
startListening():
启动识别过程,当采用云端识别时,应开始于服务器建立连接,并开始把麦克风数据传输到云端识别服务。
stopListening():
中断识别过程,当在识别过程中,用户再次唤醒会调用该方法来中断当前的识别,然后再调用startListening()来启动新一轮的识别。
writeAudio():
用于接收麦克风数据,开发者需要把收到的数据持续地传输到识别服务。当isListening()== false
时将无法收到麦克风数据。
isListening():
判断当前是否在识别阶段。
destory():
模块退出时调用,可以在此释放资源。
2.错误处理:
在RecognizeModule可能存在多种错误,在Speech.ErrorCode
里我们有定义常见的错误,开发者在回调IRecognizerListener.onError()
需要传入正确的错误码。SpeechFramework会把Error Event发布出去,机器人其他模块会进行相应的错误播报。
- 实现
RecognizeModuleFactory
接口来实例化RecognizeModule对象。
public interface RecognizeModuleFactory {
IRecognizeModule factory();
}
-
TTSModule的实现:
当开发者在RecognizeModule里接入的是Amazon AVS或腾讯叮当这样的完整的语音服务平台时,NLP识别的结果会直接在云端进行tts合成,然后把合成的结果返回给机器人端。 这种情况可以不需要实现TTSModule,但机器人其他模块如果需要实现tts播报时就需要实现,比如在一个app里实现了人脸识别的功能,需要把识别的人名播报出来,在另外一个app里实现了开机播报天气预报的功能,都需要使用到tts功能,这时就需要实现TTSModule,在其他app里开发者可以通过我们提供的SpeechApi.playTTS()
方法来把tts请求传递给TTSModule。
- 实现ITtsModule接口
public interface ITtsModule {
void init();
boolean destroy();
/**
* 启动tts播报
*
* @param text 播报文本
* @param listener 播报状态回调监听
*/
void startSpeaking(String text, ITTsListener listener);
/**
* 停止tts播报
*/
void stopSpeaking();
}
init():
模块的初始化
destory():
退出该模块,可以释放资源
startSpeaking()
:开发者需要在此实现tts合成功能,并把结果通过回调ITtsListener
的方法传递给调用者。
``
stopSpeaking():
停止播报。
- 实现TtsModuleFactory接口,实例化TtsModule.
public interface TtsModuleFactory {
ITtsModule factory();
}