利用freeswitch的media bug能力来在智能外呼时通过websocket对接智能中心的声音检测接口,来实现回铃音检测,来判断用户当前是否已响应,拒接,关机等。
1.回铃音处理流程
2.模块源码目录结构
首先新建一个freeswitch的源码的src/application目录下新建一个子目录mod_ringback_check,目录结构如下:
mod_ringback_check:
conf/autoload_configs/ringback_check.conf.xml
mod_ringback_check.h
mod_ringback_check.cpp --主要的media_bug代码
light_websocket_client.cpp --用于websocket链接
light_websocket_client.hpp
Makefile --c++编译文件
test_ringback_press.sh -->压测脚本
3.源码解析
3.1配置文件(ringback_check.conf.xml)
首先将配置文件放置在模块的conf/autoload_configs目录下,这样安装时,会将该文件复制到freesswitch的conf/autoload_configs目录下。
<configuration name="ringback_check.conf" description="mod_ringback_check configuration">
<settings>
<param name="ai_center_url" value="wss://xxx.xxx.xxx:8080/xxxx"/>
</settings>
</configuration>
通过xml配置文件的方式,配置智能中心对应的websocket地址,加载ringback_check模块时将ai_center_url读取到对应的全局静态变量中。
3.2 头文件(ringback_check.h)
typedef struct {
CWebsocket* cli; //websocket对象
switch_core_session_t *session; //freeswitch session对象
vector<uint8_t> audio_data; //发送给智能中心的语音流数据
int data_len; //数据流长度
} ringback_check_info_t;
static struct {
char *ai_center_url; //智能中心websocket地址
} global;
定义全局struct对象
3.3回铃音media_bug处理程序(mod_ringback_check.cpp)
3.3.1 引入的头文件
#include <switch.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
#include "ringback_check.h"
#include "light_websocket_client.hpp"
using namespace ringbackcheckws;
#define RINGBACK_PRIVATE "mod_ringback_check_bug"
3.3.2读取xml文件的结构
static switch_xml_config_item_t instructions[] = {
/* parameter name type reloadable pointer default value options structure */
SWITCH_CONFIG_ITEM_STRING_STRDUP("ai_center_url", CONFIG_RELOAD, &global.ai_center_url, NULL, "", "ai_center_url address"),
SWITCH_CONFIG_ITEM_END()
};
3.3.3定义media_bug相关处理函数
//回铃音模块的加载函数
SWITCH_MODULE_LOAD_FUNCTION(mod_ringback_check_load);
//回铃音模块的模块卸载函数
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_ringback_check_shutdown);
//回铃音模块的定义函数
SWITCH_MODULE_DEFINITION(mod_ringback_check, mod_ringback_check_load, mod_ringback_check_shutdown, NULL);
//回铃音模块的启动处理APP函数
SWITCH_STANDARD_APP(ringback_check_start_app);
//回铃音模块的停止处理APP函数
SWITCH_STANDARD_APP(ringback_check_stop_app);
//接收和处理模块启动时传递的参数的函数
switch_status_t ringback_check_callback_start(switch_core_session_t *session, const char *parameter);
//语音流处理函数
static switch_bool_t callprogress_ringback_check_process_buffer(switch_media_bug_t *bug, void *user_data,switch_abc_type_t type);
3.3.4 回铃音模块Load函数
SWITCH_MODULE_LOAD_FUNCTION(mod_ringback_check_load)
{
switch_application_interface_t *interface;
if (switch_xml_config_parse_module_settings("ringback_check.conf", SWITCH_FALSE, instructions) != SWITCH_STATUS_SUCCESS) {
return SWITCH_STATUS_FALSE;
}
//注册开始回铃音APP程序名称(ringback_check_start_detect)及处理函数(ringback_check_start_app)
SWITCH_ADD_APP(app_interface, "ringback_check_start_detect", "start", "ringback_check",ringback_check_start_app, "<name>", SAF_NONE);
//注册停止回铃音APP程序名称(ringback_check_stop_detect)及处理函数(ringback_check_stop_app)
SWITCH_ADD_APP(app_interface, "ringback_check_stop_detect" , "stop", "ringback_check", ringback_check_stop_app, "", SAF_NONE);
return SWITCH_STATUS_SUCCESS;
}
3.3.5回铃音模块shutdown函数
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_ringback_check_shutdown)
{
switch_xml_config_cleanup(instructions); //释放xml文件对象
return SWITCH_STATUS_SUCCESS;
}
3.3.6 回铃音模块start函数
SWITCH_STANDARD_APP(ringback_start_app)
{
switch_channel_t *channel;
if (!session) {
return;
}
channel = switch_core_session_get_channel(session);
if (zstr(data)) {
switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "-ERR missing ringback_check parameter!");
} else if (ringback_check_callback_start(session, data) != SWITCH_STATUS_SUCCESS) { //开始回铃音检测处理
switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "-ERR failed to start ringback_check detector");
} else {
switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "+OK started");
}
}
3.3.7回铃音检测处理函数(ringback_check_callback_start)
switch_status_t ringback_callback_start(switch_core_session_t *session, const char *name)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = NULL;
ringback_check_info_t *info = (ringback_info_t *)switch_core_session_alloc(session, sizeof(ringback_check_info_t));
bug = (switch_media_bug_t *)switch_channel_get_private(channel, RINGBACK_PRIVATE);
if (bug) { return SWITCH_STATUS_FALSE; }
ringback_info->session = session;
//注册media bug的处理函数
switch_core_media_bug_add(session, "ringback_check_detect", NULL, check_process_buffer, info, 0, SMBF_READ_STREAM, &bug);
if (!bug) { return SWITCH_STATUS_FALSE; }
switch_channel_set_private(channel, RINGBACK_PRIVATE, bug);
return SWITCH_STATUS_SUCCESS;
}
3.3.8 media_bug处理函数
static switch_bool_t check_process_buffer(switch_media_bug_t *bug, void *user_data,switch_abc_type_t type)
{
ringback_check_info_t *info = (ringback_check_info_t *)user_data;
switch_core_session_t *session = ringback_info->session;
switch (type) {
case SWITCH_ABC_TYPE_INIT:
{
char msg[100];
int ret = 0;
info->cli = new CWebsocket(global.ai_center_url, true);
if (info->cli == NULL){return SWITCH_FALSE;}
ret = info->ws_cli->connect_hostname();
if (ret != 0) {
return SWITCH_FALSE;
}
//给light_websocket_client传递接收数据的回调处理函数
info->cli->callback_fun(callback_recv_data,ringback_info);
ret = ringback_info->ws_cli->send(msg);
ringback_info->ws_cli->poll();
ringback_info->ws_cli->dispatch();
break;
}
case SWITCH_ABC_TYPE_READ: {
int res = 0;
switch_frame_t tmpframe = {0};
switch_channel_t *channel = switch_core_session_get_channel(session);
uint8_t data[SWITCH_RECOMMENDED_BUFFER_SIZE];
tmpframe.data = data;
tmpframe.buflen = sizeof(data);
if(ringback_info && ringback_info->ws_cli && (ringback_info->ws_cli->get_websocket_state() != EWebsocketState::CLOSED) ) {
res = switch_core_media_bug_read(bug, &tmpframe, SWITCH_FALSE);
//流内容小于24480时,先合并,不发给智能中心,流内容够24480后,统一发给智能中心
if ((ringback_info->len <= 24000) && (res != SWITCH_STATUS_FALSE)) {
const uint8_t* ptr = static_cast<const uint8_t*>(tmpframe.data);
info->audio_data.insert(info->audio_data.end(),ptr,ptr + tmpframe.datalen); ringback_info->len = ringback_info->len + tmpframe.datalen;
return SWITCH_TRUE;
}
res = info->cli->send_binary(info->audio_data);
if(res != 0) {
return SWITCH_FALSE;
}
info->ws_cli->poll(); //发送流数据
info->ws_cli->dispatch(); //处理接收数据
info->audio_data.clear();
info->audio_data.shrink_to_fit();
info->data_len = 0;
} else {
return SWITCH_FALSE;
}
//用户接听或拒接后,停掉media_bug处理程序
if (switch_channel_get_callstate(channel) >= CCS_ACTIVE) {
return SWITCH_FALSE;
}
break;
}
case SWITCH_ABC_TYPE_CLOSE: {
if(info && info->ws_cli && (info->cli->get_websocket_state() != EWebsocketState::CLOSED) ) {
if(info->len > 0) {
info->cli->send_binary(info->audio_data);
}
info->cli->send("end");
while (info->cli->get_websocket_state() != EWebsocketState::CLOSED) {
info->cli->poll();
info->cli->dispatch();
}
}
if(info) {
info->audio_data.clear();
info->audio_data.shrink_to_fit();
info->data_len = 0;
}
clear_detector(info);
break;
}
default:
break;
}
return SWITCH_TRUE;
}
3.3.9清理函数
/**
* 释放info处理对象
*/
void clear_detector(ringback_check_info_t *info){
delete info->cli;
info->ws_cli = NULL;
memset(info->sid, 0, sizeof(char));
memset(info->province, 0, sizeof(char));
memset(info,0,sizeof(ringback_check_info_t));
info = NULL;
}
3.3.10 分发结果函数
static void callback_recv_data(std::string result,void *obj) {
ringback_check_info_t *rb_info = (ringback_check_info_t *)obj;
switch_channel_t *channel = switch_core_session_get_channel(rb_info->session);
switch_event_t *event = NULL;
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, "result::data") == SWITCH_STATUS_SUCCESS) {
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "result", result.c_str());
switch_channel_event_set_data(channel, event);
switch_event_fire(&event);
}
}
3.4 第三方websocket处理框架(light_websocket_client)
light_websocket_client处理框架很小巧,但他对于接收到的数据,只做了一个printf打印处理,而我们的业务不仅仅只对接收的数据进行打印,还有其他处理逻辑,因此对该框架的接收数据部分进行了改造,通过传递给其一个“回调函数”的方式,将接收到的数据返回给业务处理的回调函数。
具体改造点如下:
(1)定义一个回调的函数原型
typedef void (*Func)(std::string,void *);
(2)给client类增加一个set方法,该方法用于传递回调函数
void callback_fun(Func func,void *obj);
void CWebsocket::set_callback_fun(Func function,void *vobj) {
func = function;
obj = vobj;
}
(3)在接收数据处,执行回调函数:
if(func) {
func(stringMessage,obj);
}
通过以上的程序处理,即可完成回铃音的检测功能。