基于freeswitch的智能外呼2-自定义freeswitch模块

小小幸运

共 3880字,需浏览 8分钟

 ·

2022-02-09 17:34

我们接触到的自动外呼市面上大多都是用户接听后,播放一段录音。

这种完全跟不上现在时代, 目前有实力的技术公司均实现了基于asr和tts的智能外呼,同时很多公司并将此作为一种能力对外开放和进行商业合作。

那么我们如何实现基于freeswitch的智能外呼模块? 首先我们来看看如何自动freeswitch模块。

我们需要实时记录用户的音频信息,当用户声音低于某个阈值时,将当前的音频数据发给asr进行识别,并将识别结果推送给智能问答系统,再将智能问答返回的文字,使用tts播放给用户听。

// mini 版本 myrobot,可进行学习, 自定义freeswitch application
// 通过media bug 来对channel进行监听,实时获取音频流 20ms, 160 samples

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include   
#include 
#include 
#include 
#include 
#include  
#include 

/*! Syntax of the API call. */
#define ROBOT_SYNTAX " //xxxx>"

/*! Number of expected parameters in api call. */
#define ROBOT_PARAMS 2

#define ROBOT_EVENT_ASR "myrobot::asr"

static switch_bool_t robot_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type);

#define MAX_VOICE_LEN 240000
#define MAX_VOICE_LEN_BASE64 645000  
int use_asr = 8,use_tts=2,use_cache=0,use_url=0;
#define MAXFILES 8
#define TTS_MAX_SIZE 900  
#define MAX_HZ_SIZE  240
typedef struct robot_session_info {	
	int index;
	int filetime;
	int fileplaytime;
	int nostoptime;
	int asrtimeout;
	int asr;
	int play, pos;
	int sos, eos, ec, count;
	int eos_silence_threshold;	
	int final_timeout_ms;
	int silence_threshold;
	int harmonic;
	int monitor;
	int lanid;
	switch_core_session_t *session; 		
	char taskid[32];
	char groupid[32];
	char telno[32];
	char userid[64];
	char callid[64];
	char orgi[64];
	char extid[64];
	char uuid[64];
	char uuidbak[64];
	char recordfilename[128];	
	char para1[256];
	char para2[256];
	char para3[256];
	char filename[TTS_MAX_SIZE];
	short buffer[MAX_VOICE_LEN];
} robot_session_info_t;

int GetPrivateProfileString(const char*set,const char*cmd,const char*def,char*res,int para_len,const char*filename)
{
    FILE 	*fp=NULL;
    char	tmp[500];
    char 	line_str[500];
    int 	i,len;

    strcpy(res,def);
    fp=fopen(filename,"r");
    if(fp==NULL){printf("open %s fail",filename);	return 0;}
    while (fgets(line_str, 256, fp))
    {
        len=strlen(line_str);
        for(i=0;i<len;i++)
        {
            if(line_str[i]=='\r'){line_str[i]=0; break;}
            if(line_str[i]=='\n'){line_str[i]=0; break;}
        }
        len=strlen(line_str);
        if(line_str[0]=='#' || len<3) continue;
        strcpy(tmp,line_str);
        for(i=0; i<len;i++)
        {
            if(tmp[i]=='=') break;
        }
        tmp[i]='\0';
        if(strcmp(cmd,tmp)==0)
        {
            i++;
            strcpy(res,line_str+i);
            break;
        }
    }
    fclose(fp);
    return 0;

}

//定义shutdown module方法, free 掉malloc 堆内存
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_vmd_shutdown);

// 定义加载模块时,读取配置文件,初始化变量等
SWITCH_MODULE_LOAD_FUNCTION(mod_vmd_load);
SWITCH_MODULE_DEFINITION(mod_vmd, mod_vmd_load, mod_vmd_shutdown, NULL);

// 定义自定义application, 这样在dialplan中 可以 
SWITCH_STANDARD_APP(robot_start_function);


SWITCH_MODULE_LOAD_FUNCTION(mod_vmd_load)
{
	char tmp[256];
	// 有配置文件,可以进行读取
	FILE* fp;	
	char line_str[1024],*p;
	int i,len;
	// 使用app_interface 进行 add app
	switch_application_interface_t *app_interface;

	if (switch_event_reserve_subclass(ROBOT_EVENT_ASR) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Robot Couldn't register subclass %s!\n", ROBOT_EVENT_ASR);
		return SWITCH_STATUS_TERM;
	}
	/* connect my internal structure to the blank pointer passed to me */
	*module_interface = switch_loadable_module_create_module_interface(pool, modname);

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Robot enabled,monitor=%d,use_asr=%d,use_tts=%d\n",1,use_asr,2);

	// 为此模块增加app,调用名称即为 myrobot
	SWITCH_ADD_APP(app_interface, "myrobot", "myrobot", "ai robot", robot_start_function, "[stop|restart|start|wavefilename.wav]", SAF_NONE);

	/* indicate that the module should continue to be loaded */
	return SWITCH_STATUS_SUCCESS;
}


SWITCH_STANDARD_APP(robot_start_function)
{
	switch_media_bug_t *bug;
	switch_status_t status;
	switch_channel_t *channel;
	robot_session_info_t *robot_info;

	if (session == NULL)
		return;
	// 通过sesion 获取channel
	channel = switch_core_session_get_channel(session);

	// 根据channel 可以获取channel 上绑定的变量 variable

	/* Is this channel already set? */
	bug = (switch_media_bug_t *) switch_channel_get_private(channel, "_robot_");
	/* If yes */
	if (bug != NULL) 
	{
		/* We have already started */
		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Robot Cannot run 2 at once on the same channel!\n");
		return;
	}
	// 初始化变量, 一定记得要 free掉
	robot_info = (robot_session_info_t *)malloc(sizeof(robot_session_info_t)); 
	if(robot_info==NULL) return;
	robot_info->session = session; 
	strcpy(robot_info->uuid, switch_core_session_get_uuid(robot_info->session));

	// 重中之重, 为session 增加media bug, 绑定回调函数 robot_callback
	status = switch_core_media_bug_add(session, "vmd", NULL, robot_callback, robot_info, 0, SMBF_READ_REPLACE, &bug);

	if (status != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Robot Failure hooking to stream\n");
		return;
	}
	switch_channel_set_private(channel, "_robot_", bug);
}


SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_vmd_shutdown)
{
	int i;
	// free 掉
	switch_event_free_subclass(ROBOT_EVENT_ASR);
	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "myapplication disabled\n");
	return SWITCH_STATUS_SUCCESS;
}


static switch_bool_t process_close(robot_session_info_t *rh)
{
	switch_channel_t *channel;			
	char info[2048], result[2048];
	int send=1;			
	rh->uuid[0] = 0;	
	rh->index = -1;	
	channel = switch_core_session_get_channel(rh->session);
	switch_channel_set_private(channel, "_robot_", NULL);
	free(rh);
	return SWITCH_TRUE;
}

static switch_bool_t robot_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
	robot_session_info_t *robot_info;
//	switch_codec_t *read_codec;
	switch_frame_t *frame;

	robot_info = (robot_session_info_t *) user_data;
	if (robot_info == NULL) {
		return SWITCH_FALSE;
	}

	switch (type) {

	case SWITCH_ABC_TYPE_INIT:
		break;

	case SWITCH_ABC_TYPE_READ_REPLACE:
		if(robot_info->uuid[0]==0) break;
		frame = switch_core_media_bug_get_read_replace_frame(bug);
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "received data\n");
		break;
	
	case SWITCH_ABC_TYPE_CLOSE:
		process_close(robot_info);
		break;
	default:
		break;
	}

	return SWITCH_TRUE;
}

浏览 8
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报