FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)

这个app实用性不高,打断其实不准确,但作为一个打断方案,还是可以学习一下,以及freeswitch是如何实现的,当你看到最后之后,你就知道为何这个app不好用了
后面也有优化方法,用来提高这个app的实用性

和之前一样,我们找到这个app的注册函数FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
首先我们先看这个函数的整个逻辑吧
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
这里其实也没做啥事,就是解析参数,做了错误判断
其中这个app函数的错误响应有:
“USAGE ERROR” (使用错误)
“GRAMMAR ERROR” (语法错误)
“ASR INIT ERROR” (asr初始化失败)
“ERROR” (通用错误,通常指普通地调用app失败)
每一个错误码的提示都很清晰

进入到这个函数的定义后就可以看到
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
首先是这个核心函数最先开启语音识别,调用函数:
switch_ivr_detect_speech
这个函数其实就是之前看过的detect_speech一样,其实也是调用了这个函数去开启ASR,由于之前已经讲过这个函数,这里就不再重复
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
然后设置回调函数
play_and_detect_input_callback 这个就是打断函数,但是先不要管它
之后就是调用了放音函数
switch_ivr_play_fileFreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
重点:在回调函数play_and_detect_input_callback 和在switch_ivr_play_file
先看switch_ivr_play_file
里面的逻辑判断太多,不一一细看,只截取重要的地方说明
例如这里:解析参数,判断是文件播放还是使用asr的TTS合成
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
如果是say:
则进行asr的TTS合成播放
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
上面都不是的话,就组成文件名:
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
之后如果是文件播放:
直接进入一个死循环,打开文件,加载音频文件的信息,设置一些参数
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
重要地方,在这个循环内会开启一个线程来专门读取音频流
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
进入这个函数后,就可以发现这个条线程会一直在读取音频或视频,asr为音频
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
switch_core_session_read_frame 还记得这个函数吧,这里不再做说明这个函数
之后就是进入死循环真正播放放文件了
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
重点来了,注意在播放文件的时候,跳出这里有两种方式,
1:收码跳出 (收码打断)
2:收到mrcp start_input 事件跳出(语音打断)
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
在播放文件的同事会一直不断地检查事件
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)

接下来我们来看看是怎样打断的:
我们先回到这个函数switch_ivr_detect_speech
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
一直追寻进入:
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
进入回调
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
由于之前已经讲过这个函数,这里就不再说明
如果你看过我的上一篇源码分析,就知道这里的逻辑,我们知道,在初始化asr的时候会单独通过回调函数:speech_callback 再去创建一条线程 speech_thread
这条speech_thread线程会一直在检查asr的结果,并且生成一个事件DETECTED_SPEECH 这个时候如果mrcpserver 发回了一个mrcp的事件start_input
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
一旦有结果,或者受到start input 检查结果遍会返回成功
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
由于在播放的同时会不断地检查通过回调去检查事件
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
在进入回调后 play_and_detect_input_callback 检查由 speech_thread 产生的DETECTED_SPEECH 事件,然后就跳出
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
最后这里由于收到了事件之后,返回的状态为SWITCH_STATUS_BREAK
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)

小结:

一:首先调用这个app函数时,是先开启语音识别
开启的时候初始化及创建asr通道
通过回调函数speech_callback 来创建一条循环检查ASR的结果,并且产生事件
产生事件,是在不断地调用mod_unimrcp 里面的一个回调函数:
recog_channel_check_results
产生事件的因素:必须有到结果或者收到unimrcpserver的start input 事件,才会
产生事件,Speech-Type:begin-speaking
之后就会一直产生的事件类型为:
Speech-Type:detected-speech
这样也是通过关注asr的事件,最后会一直产生这个类型的事件的原因

二:当进入switch_ivr_play_file,在一个死循环内就会不断地去调用回调函数play_and_detect_input_callback去检查asr生成的事件,如果事件的类型为:
SWITCH_EVENT_DETECTED_SPEECH 即收到了asr的事件,这个时候不论是收到事件类型的:begin-speaking 还是 detected-speech 都会导致跳出播放文件(打断)
但一开始应该收到的事件一般多为mrcp的start input ,除非unimrcpserver不发送start input 事件,最后也就是说,freeswitch 的语音打断是通过mrcp的start input 或者是收到asr的识别结果来做打断的。但其实这个是不好的

如果有用过这个函数的同学想必也会被它糟糕的打断而抓狂,因为用start input作为打断因素,实在是准确率太差了,出现一点杂音都会被打断(稍后我们会看到为何)

但是这有不好的一点地方,就是大多数情况下收到的都是start input 而导致打断播放文件,而这个start input 事件的产生主要在于unimrcpserver,我们来看看unimrcpserver 是怎样产生这个事件的

打开unimrcpserver的源码
随便打开一个recog.c
看到 demo_recog_stream_write
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
在语音识别的过程中,一直不停地检查流的状态
注意这句话:"Detected Voice Activity " 流的状态为活动中,就直接发送start_of_input 事件了,这个不准确的,可以确定的是杂音也为活动中,就发送回去,fs就接收到了start_of_input 事件,从而把播放文件打断,这个也是为何freeswitch的语音打断不好用的原因,好奇就会掉头发!再来看看这个流活动的状态是怎么回事
看看是怎么算流的活动状态:
拿出宇宙无敌的vs 搜索关键字:MPF_DETECTOR_EVENT_ACTIVITY
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
就能看到设置的状态,但是这个if(level >= detector->level_threshold) 就是计算后,判断的的因素
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
看到这个函数mpf_activity_detector_level_calculate
就可以看到unimrcpserver是怎样计算流的状态了
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
而这个level_threshold值的初始值为2 ,当然是可以设置的
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
总结:
语音打断的方案其实有不少,fs的只是其中的一种,虽然不好用,但你也可以收动改源码使这个app好用
例如:
一:
改动最小最简单的方法:
打开mod_unimrcp.c 找到这个函数,只需要添加一句代码,这是最简单的代码方式就能使这个app play_and_detect_speech 变得好用起来,你甚至不用去改fs的核心代码,也不用去重新编译fs,只需要重新编译mod_unimrcp.c就行
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
二:
更简单更安全的方法:
不让unimrcpserver发送 start of input 事件,同样可以使这个app好用起来
这个只需要编译unimrcpserver里面的插件就行
FreeSwitch源码源码系列-play_and_detect_speech (FreeSWITCH语音打断的实现)
因为这样做都是让fs可以忽略了 mrcp 的 start of input 事件,改为收到结果再打断,准确性可以大大提高 !!!

联系我:[email protected]