1. 写在前面
前面的文章已经做了很多准备工作,接下来的事情,就是进行实际的对接和使用了,目标就是通过unimrcp
模块,对通话双方进行实时的语音识别,将识别的内容进行实时的智能化分析,可以做实时监控、智能质检等。
以下内容默认大家对FreeSWITCH
是有一定的了解,部分内容可能会简单描述,具体细节大家可以去深入研究。
2. 整体思路
拨入FreeSWITCH
内线后,提前在内线号码的拨号规则里面配置好,使用Outbound
模式,在呼入后连接event_socket
,将控制权交给自己开发的ESL
服务,通过DTMF
进行功能和流程控制,由于流程会进行智能语音播报,所以也配置了unmicp-tts
的参数。
<extension name="esl-mrcp">
<condition field="destination_number" expression="^4567$">
<action application="answer"/>
<action application="set" data="tts_engine=unimrcp"/>
<action application="set" data="tts_voice=aixia"/>
<action application="socket" data="192.168.160.11:16023 async full"/>
</condition>
</extension>
顺便提下,一般在ESL
中,可以通过不同的按键,进行功能的单元测试,测试通过后,再进行集成联调。
接下来继续说ESL
的实现逻辑,接入电话后,进行欢迎语的语音播报,然后提示用户按键进行功能选择,按转人工后,执行bridge
将用户和坐席桥接起来,然后桥接成功后,在2个通道上,分别执行语音识别命令,获取识别结果,一直持续到通话结束。
3. 具体实现
3.1 启动语音识别
在 智能客服搭建(3) - MRCP Server 与 FreeSWITCH 对接 验证了play_and_detect_speech
的功能,这里就不对这块进行详细介绍了。
在本次实现功能的场景中,需要连续监听双方的实时通话,所以需要使用detect_speech
命令启动unimrcp
语音识别,具体执行的命令如下:
detect_speech unimrcp {parameters1=value1,parameters2=value2}hello default
其中{}
中的内容作为MRCP header
的自定义参数,会作为传给MRCP
服务端,一般会带通话信息过去。
3.2 监听语音识别结果
通过命令event plain all
订阅所有事件,发现每次启动语音识别,说完话后,都会收到DETECTED_SPEECH
事件,具体内容如下:
Event-Name: DETECTED_SPEECH
Core-UUID: 4ad02bbf-e4a5-4d29-964b-018962db3354
Event-Calling-Line-Number: 4732
Speech-Type: detected-speech
ASR-Completion-Cause: 0
Channel-State: CS_EXECUTE
Channel-Call-State: ACTIVE
Content-Length: 192
<?xml version="1.0"?>
<result>
<interpretation confidence="99">
<engineName>Xxx</engineName>
<engineStartTime>1636615997148</engineStartTime>
<result>语音转写的结果。</result>
<beginTime>4090</beginTime>
<endTime>5370</endTime>
<volumeMax>19</volumeMax>
<volumeMin>1</volumeMin>
<volumeAvg>8</volumeAvg>
</interpretation>
</result>
当Speech-Type
为detected-speech
时,表示语音识别结束,实际测试下来发现FreeSWITCH
的unimrcp
模块实现的是短语音识别,完成后不会自动启动语音识别命令,这样的好处是在IVR场景的多并发情况下,能够节省语音识别的路数。
于是需要在收到语音识别结束事件后,重新启动语音识别,有2种方式,一种是3.1中介绍的直接使用命令启动,还有一种就是通过resume
命令实现。
detect_speech resume
3.3 转接到坐席
通过bridge
转接到需要的话机上,实现方式有如下3种:
bridge user/1002
fifo ivr in
callcenter ivr_queue
感兴趣的可以去看FreeSWITCH的官方文档,经验告诉我们,官方文档还是最有用的。
当坐席接起电话后,收到CHANNEL_BRIDGE
事件,就是这个时候,开始在2个通道上执行语音识别的命令。
按我自己的预期,事情到这里应该告一段落,然而并没有这么简单。仿佛是上帝给了一个人很多的钱财,就会带走一些东西,比如他的烦恼。而我恰恰相反,带走的钱财,带来的是烦恼,我知道这次上帝只是弄错了而已,希望下次能给我补上。
接下来就开始了探索之旅。
4. 遇到的问题
转接到坐席后,再也收不到DETECTED_SPEECH
事件了,fs_cli
能够看到FreeSWITCH
的日志中有MRCP Server
识别的结果,不过没有DETECTED_SPEECH
事件,后来发现,只要是bridge
了,就无法获取到DETECTED_SPEECH
事件了,整得挺尴尬的。
也咨询了杜老师,可能是由于bridge
阻塞了,然后我在bridge
后,多次通过按键,执行detect_speech
都成功执行了语音识别命令,看到了日志里的语音识别结果,没有收到理论上应该返回的DETECTED_SPEECH
事件。
再后来我又进行了测试,a 直接呼叫 b,然后通过Inbound模式,手动执行如下命令:
nc 192.168.160.84 8021
auth ClueCon
event DETECTED_SPEECH
sendmsg 9c603a5f-fa12-4506-a93c-c2a971bd5a5a
call-command: execute
execute-app-name: detect_speech
execute-app-arg: unimrcp {test}hello default
测试结果还是一样,fs_cli
能看到FreeSWITCH
日志中有MRCP Server
的结果识别,没有DETECTED_SPEECH
事件。
根据以上尝试,得到的结论就是,bridge
影响了DETECTED_SPEECH
事件的正常发送。
5. 解决的方案
为了解决这个问题看了部分源码,不过代码量比较庞大,也没有办法花特别多的时间去完全吃透,所以也在网上找了一些资料,其中一篇印象较为深刻,《MRCP协议栈源码修改,支持实时语音识别》,大概的思路是通过修改MRCP
协议,将短语音识别改成长语音识别,由于我在MRCP Server
中已经实现了将转写内容获取并发送到MQ中,所以这对我来说是一个可行的方案。但是转念一想,还需要对MRCP Server
进行修改,那就对标准协议产生了影响,以后也没有办法进行标准协议的对接,所以这个方案后来还是没有采用。
后来看了mod_unimrcp
的源码,考虑到时间成本,决定在mod_unimrcp
源码中新增自定义事件。
由于之前的排查,对这部分的代码比较熟悉,所以找到完成语音识别后,在收到MRCP Server
的RECOGNIZER_RECOGNITION_COMPLETE
事件后,在mod_unimrcp.c
新增了一个CUSTOM unimrcp::asrend
事件,将MRCP
的Header
和Body
的数据都添加到了事件中,代码如下:
/**
* Handle the MRCP responses/events
*/
static apt_bool_t recog_on_message_receive(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_message_t *message)
{
...
if (message->start_line.message_type == MRCP_MESSAGE_TYPE_RESPONSE) {
...
} else if (message->start_line.message_type == MRCP_MESSAGE_TYPE_EVENT) {
/* received MRCP event */
if (message->start_line.method_id == RECOGNIZER_RECOGNITION_COMPLETE) {
...
recog_channel_set_result_headers(schannel, recog_hdr);
recog_channel_set_results(schannel, result);
// add event begin
if (switch_event_create(&event, SWITCH_EVENT_CUSTOM) == SWITCH_STATUS_SUCCESS) {
event->subclass_name = strdup("unimrcp::asrend");
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Event-Subclass", event->subclass_name);
...
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "MRCP-Body", result);
switch_event_fire(&event);
}
// add event end
...
}
接下来,在FreeSWITCH
源码目录通过make mod_unimrcp-install
进行编译部署,开始进行调试,然后成功收到了自定义事件。
Callernumber: "1004"
Core-Uuid: "ceddd45d-80aa-495d-9301-297356ccf05f"
Event-Calling-File: "mod_unimrcp.c"
Event-Calling-Function: "recog_on_message_receive"
Event-Calling-Line-Number: "3694"
Event-Date-Gmt: "Tue, 15 Feb 2022 10:04:11 GMT"
Event-Date-Local: "2022-02-15 18:04:11"
Event-Date-Timestamp: "1644919451678906"
Event-Name: "CUSTOM"
Event-Sequence: "592"
Event-Subclass: "unimrcp::asrend"
Freeswitch-Hostname: "freeswitch-seat"
Freeswitch-Ipv4: "192.168.160.84"
Freeswitch-Ipv6: "::1"
Freeswitch-Switchname: "freeswitch-seat"
Mrcp-Body: "<?xml version=\"1.0\"?>\n<result>\n <interpretation confidence=\"99\">\n <engineName>Xxx</engineName>\n <engineStartTime>1644919435900</engineStartTime>\n <result>今天的天气
。</result>\n <beginTime>8490</beginTime>\n <endTime>9510</endTime>\n <volumeMax>29</volumeMax>\n <volumeMin>4</volumeMin>\n <volumeAvg>13</volumeAvg>\n </interpretation>\n</result>\n"
Source: "0"
Uuid: "ff8fd9ba-5a26-4f55-ad60-441db85c5bf1"
接下来在自己写的ESL
服务中,订阅CUSTOM unimrcp::asrend
事件,收到后执行detect_speech resume
继续进行语音识别。
6. 后续需研究的问题
还是希望后面能够有时间去研究一下,为什么bridge
影响了DETECTED_SPEECH
事件,当然如果哪位大神知道原因或解决方案,还请留言指导,非常感谢。
希望相互学习,共同进步。