文章目录
1. 写在前面
2. 整体思路
3. 具体实现
3.1 启动语音识别
3.2 监听语音识别结果
3.3 转接到坐席
4. 遇到的问题
5. 解决的方案
6. 后续需研究的问题
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>shenghan</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
1
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>shenghan</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事件,当然如果哪位大神知道原因或解决方案,还请留言指导,非常感谢。

  希望相互学习,共同进步。