1. 编码前准备
1.1 新建plugin
因为unimrcp
使用automake
进行源码编译管理,所以除了添加源代码,我们还需要进行相应配置添加。
1.2 修改configure.ac
首先编辑configure.ac
文件,添加如下,其实是一个宏定义会在后面的Makefile
中使用到,以及添加后面我们新增的Makefile
:
dnl Xxx recognizer plugin.
UNI_PLUGIN_ENABLED(Xxxrecog)
AM_CONDITIONAL([XxxRECOG_PLUGIN],[test "${enable_Xxxrecog_plugin}" = "yes"])
...
AC_CONFIG_FILES([
plugins/Xxx-recog/Makefile
])
...
AC_OUTPUT
echo Xxx recognizer plugin.... : $enable_Xxxrecog_plugin
1.3 新增源代码及目录
在 plugin
目录下,新建 Xxx-recog
目录,并在该目录下新建 src
目录,可以将 demo_recog_engine.c
拷贝到该目录下改名为 Xxx_recog_engine.c
,并将源代码中的所有的demo
关键字替换为Xxx
。
新建 Makefile.am
文件,内容如下:
AM_CPPFLAGS = $(UNIMRCP_PLUGIN_INCLUDES)
plugin_LTLIBRARIES = Xxxrecog.la
Xxxrecog_la_SOURCES = src/Xxx_recog_engine.c
Xxxrecog_la_LDFLAGS = $(UNIMRCP_PLUGIN_OPTS) -std=c++11 -pthread
include $(top_srcdir)/build/rules/uniplugin.am
修改 plugin
目录下的 Makefile.am
文件,新增如下内容:
if XxxRECOG_PLUGIN
SUBDIRS += Xxx-recog
endif
2. 代码编写
2.1 语音引擎类封装
对自己对接的语音引擎模块进行类封装,建议类中包含以下方法。
/** 初始化语音Engine,返回创建后的类指针 */
static AsrXxx* create_Xxx_engine();
/** ws连接成功后,发送语音流数据到ws服务端 */
void ws_send_buffer(const void * wave, const size_t wave_size);
/** 获取到ws服务端推送的语音识别结果的事件 */
bool have_asr_result();
/** 获取ws服务端推送的语音识别结果 */
std::string get_asr_result();
/** 用于语音引擎的销毁,包括销毁线程、释放websocket内存、释放Engine的内存 */
void release_Xxx_engine();
2.2 MRCP Server 框架代码编写
2.2.1 引用头文件
基于语音引擎的类封装,在Xxx_recog_engine.c
中引用其头文件。
#include "AsrXxx.h"
2.2.2 新增类的变量
在结构体Xxx_recog_channel_t
中新增语音引擎类变量。
/** Declaration of Xxx recognizer channel */
struct Xxx_recog_channel_t {
/** Back pointer to engine */
Xxx_recog_engine_t *Xxx_engine;
/** Engine channel base */
mrcp_engine_channel_t *channel;
/** Active (in-progress) recognition request */
mrcp_message_t *recog_request;
/** Pending stop response */
mrcp_message_t *stop_response;
/** Indicates whether input timers are started */
apt_bool_t timers_started;
/** Voice activity detector */
mpf_activity_detector_t *detector;
/** File to write utterance to */
FILE *audio_out;
/** Xxx asr engine parameter */
AsrXxx *Xxx_asr; // Xxx引擎 ASR 类
};
2.2.3 框架核心函数介绍
对MRCP Server
框架的核心函数进行介绍,同时对Xxx语音引擎代码集成进行讲解。
mrcp_plugin_create
用于语音引擎涉及到登录/注册/鉴权事宜,例如讯飞语音引擎可在其中添加如xxx_login()
函数,Xxx暂不涉及到注册,在这部分不进行操作。
/** Create Xxx recognizer engine */
MRCP_PLUGIN_DECLARE(mrcp_engine_t*) mrcp_plugin_create(apr_pool_t *pool);
Xxx_recog_engine_destroy
用于语音引擎全局的销毁,如果有语音引擎类/SDK涉及到全局销毁,则添加代码在这里。
/** Destroy recognizer engine */
static apt_bool_t Xxx_recog_engine_destroy(mrcp_engine_t *engine);
Xxx_recog_engine_channel_create
当有MRCP Client
连接MRCP Server
时会创建channel
,框架会调用该方法,可在其中添加语音引擎实例化的函数,用于处理1路asr结果,create_Xxx_engine
就是在这个函数中调用的。
/* create Xxx recognizer channel */
static mrcp_engine_channel_t* Xxx_recog_engine_channel_create(mrcp_engine_t *engine, apr_pool_t *pool);
Xxx_recog_channel_destroy
当有MRCP Client
断开MRCP Server
时会销毁channel
,框架会调用该方法,可在其中添加语音引擎销毁的函数,用于对类中创建的对象进行释放,release_Xxx_engine
就是在这个函数中调用的。
/** Destroy engine channel */
static apt_bool_t Xxx_recog_channel_destroy(mrcp_engine_channel_t *channel)
Xxx_recog_stream_write
通过函数名称能看出,这个函数用于接收MRCP Client
发送过来的语音流,在这个函数中接收语音流后,做了2件事情:
- 将语音流传给
Xxx_recog_stream_recog
函数进行异步语音识别处理 - 将语音流传给
mpf_activity_detector_process
函数进行能量计算,端点检测
下面我们对这两个函数进行介绍。
Xxx_recog_stream_recog
在创建的channel
中,通过Xxx_recog_stream_recog
函数,获取语音流,进行语音识别处理,在该函数中,调用ws_send_buffer
进行Xxx语音识别。
static apt_bool_t Xxx_recog_stream_recog(Xxx_recog_channel_t *recog_channel, const void *voice_data, unsigned int voice_len)
mpf_activity_detector_process
这个函数,是MRCP中封装的涉及到能量计算,端点检测的函数,查看Xxx_recog_engine_channel_create
函数,会发现调用mpf_activity_detector_process
的入参为mpf_activity_detector_create
创建的语音活动检测器。
根据mpf_activity_detector_create
函数的实现,发现level_threshold
设置了能量阈值。
MPF_DECLARE(mpf_activity_detector_t*) mpf_activity_detector_create(apr_pool_t *pool)
{
mpf_activity_detector_t *detector = apr_palloc(pool,sizeof(mpf_activity_detector_t));
detector->level_threshold = 12; /* 0 .. 255 */
detector->speech_timeout = 300; /* 0.3 s */
detector->silence_timeout = 300; /* 0.3 s */
detector->noinput_timeout = 5000; /* 5 s */
detector->duration = 0;
detector->state = DETECTOR_STATE_INACTIVITY;
return detector;
}
我使用主播麦进行测试,在默认的12的能量阈值下,无法检测到说话结束,调整到25有时候可以端点检测,有时候也不能,整体感受下来VAD的效果不好,顺便提一下能量阈值的设置方法,可直接通过mpf_activity_detector_level_set
函数进行设置。
recog_channel->detector = mpf_activity_detector_create(pool);
mpf_activity_detector_level_set(recog_channel->detector, 30);
后来我去看了网上对MRCP的端点检测效果,评价基本是不太能用的状态。
其中一篇帖子如下,感兴趣的可以去了解下:
对于希望自己实现VAD替换自带的VAD,可参考这两篇文章:
由于我使用的语音引擎支持VAD,于是没有再深入研究,对于XxxVAD集成方法2.2.4中介绍。
Xxx_recog_result_load
该函数在检测到语音识别结束的事件后调用,功能就是在这里获取识别结果,组装成xml
发送给MRCP Client
端,这样就完成了从语音传输和识别的过程。
/* Load Xxx recognition result */
static apt_bool_t Xxx_recog_result_load(Xxx_recog_channel_t *recog_channel, mrcp_message_t *message);
2.2.4 XxxVAD集成
这部分重点介绍下,通过对语音引擎vad事件的封装,替换MRCP的VAD,函数核心实现如下。
/** Callback is called from MPF engine context to write/send new frame */
static apt_bool_t Xxx_recog_stream_write(mpf_audio_stream_t *stream, const mpf_frame_t *frame)
{
...
if(recog_channel->recog_request) {
// 屏蔽以下代码,原因是mrcp原生的vad效果差,使用Xxx的vad
// mpf_detector_event_e det_event = mpf_activity_detector_process(recog_channel->detector,frame);
......
// 使用Xxxvad,根据asr结果情况,进行mrcp协议的发送
bool Xxx_vad_completion = recog_channel->Xxx_asr->have_asr_result();
if (Xxx_vad_completion == true)
{
apt_log(RECOG_LOG_MARK,APT_PRIO_INFO,"[Xxx] Detected Voice Inactivity " APT_SIDRES_FMT,
MRCP_MESSAGE_SIDRES(recog_channel->recog_request));
Xxx_recog_recognition_complete(recog_channel,RECOGNIZER_COMPLETION_CAUSE_SUCCESS);
}
if(recog_channel->audio_out) {
fwrite(frame->codec_frame.buffer,1,frame->codec_frame.size,recog_channel->audio_out);
}
}
return TRUE;
}
3. 编译
3.1 标准编译方法
完成编码后,重新编译安装后,就可以进行配置和使用了。
编译安装方法可参考:智能客服搭建(1) - MRCP Server 搭建。
3.2 独立编译方法
编写Makefile
,将mrcp
及引擎类的lib/include
添加进去,即可进行编译。
本次开发使用g++
进行编译,具体详情可参考makefile
待完代码开发完成且测试稳定后,根据情况考虑将代码进行上传,由于目前集成的SDK暂未公开,所以后面我会写一篇集成阿里/讯飞语音引擎的详细文章。
将完成编译的so放到 /usr/local/unimrcp/plugin
中。
下篇文章介绍MRCP
如何与FreeSWITCH
对接相关事宜。