Freeswitch使用media_bug能力实现回铃音检测

利用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);

           }

通过以上的程序处理,即可完成回铃音的检测功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/2490.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我的年度总结

这一年的人生起伏&#xff1a;从曙光到低谷再到新的曙光 其实本来没打算做年度总结的&#xff0c;无聊打开了帅帅的视频&#xff0c;结合自己最近经历的&#xff0c;打算简单聊下。因为原本打算做的内容会是一篇比较丧、低能量者的呻吟。 实习生与创业公司的零到一 第一段工…

Hive SQL必刷练习题:留存率问题

首次登录算作当天新增&#xff0c;第二天也登录了算作一日留存。可以理解为&#xff0c;在10月1号登陆了。在10月2号也登陆了&#xff0c;那这个人就可以算是在1号留存 今日留存率 &#xff08;今日登录且明天也登录的用户数&#xff09; / 今日登录的总用户数 * 100% 解决思…

ASP.NET Core - 缓存之分布式缓存

ASP.NET Core - 缓存之分布式缓存 1. 分布式缓存的使用2. 分布式缓存的接入2.1 基于内存的分布式缓存2.2 基于 Redis 的分布式缓存 分布式缓存是由多个应用服务器共享的缓存&#xff0c;通常作为访问它的应用服务器的外部服务进行维护。 分布式缓存可以提高 ASP.NET Core 应用的…

机器学习中的凸函数和梯度下降法

一、凸函数 在机器学习中&#xff0c;凸函数 和 凸优化 是优化问题中的重要概念&#xff0c;许多机器学习算法的目标是优化一个凸函数。这些概念的核心思想围绕着优化问题的简化和求解效率。下面从简单直观的角度来解释。 1. 什么是凸函数&#xff1f; 数学定义 一个函数 f…

flutter R库对图片资源进行自动管理

项目中对资源的使用是开发过程中再常见不过的一环。 一般我们在将资源导入到项目中后,会通过资源名称来访问。 但在很多情况下由于我们疏忽输入错了资源名称,从而导致资源无法访问。 所以,急需解决两个问题: 资源编译期可检查可方便预览资源安装相关插件 在vscode中安装两…

闲谭SpringBoot--ShardingSphere分布式事务探究

文章目录 0. 背景1. 未分库分表时2. 仅分表时3. 分库分表时3.1 不涉及分库表3.2 涉及分库表&#xff0c;且分库表处于一个库3.3 涉及分库表&#xff0c;且分库表处于多个库3.4 涉及分库表&#xff0c;且运行中某库停机 4. 小结 0. 背景 接上篇文章《闲谭SpringBoot–ShardingS…

【Linux】--- 进程的等待与替换

进程的等待与替换 一、进程等待1、进程等待的必要性2、获取子进程status3、进程等待的方法&#xff08;1&#xff09;wait&#xff08;&#xff09;函数&#xff08;2&#xff09;waitpid函数 4、多进程创建以及等待的代码模型5、非阻塞接口 轮询 二、进程替换1、替换原理2、替…

AI 编程工具—Cursor进阶使用 阅读开源项目

AI 编程工具—Cursor进阶使用 阅读开源项目 首先我们打开一个最近很火的项目browser-use ,直接从github 上克隆即可 索引整个代码库 这里我们使用@Codebase 这个选项会索引这个代码库,然后我们再选上这个项目的README.md 文件开始提问 @Codebase @README.md 这个项目是用…

keepalived双机热备(LVS+keepalived)实验笔记

目录 前提准备&#xff1a; keepalived1&#xff1a; keepalived2&#xff1a; web1&#xff1a; web2&#xff1a; keepalived介绍 功能特点 工作原理 应用场景 前提准备&#xff1a; 准备4台centos&#xff0c;其中两台为keepalived&#xff0c;两台为webkeepalive…

【dockerros2】ROS2节点通信:docker容器之间/docker容器与宿主机之间

&#x1f300; 一个中大型ROS项目常需要各个人员分别完成特定的功能&#xff0c;而后再组合部署&#xff0c;而各人员完成的功能常常依赖于一定的环境&#xff0c;而我们很难确保这些环境之间不会相互冲突&#xff0c;特别是涉及深度学习环境时。这就给团队项目的部署落地带来了…

ASP.NET Core - IStartupFilter 与 IHostingStartup

ASP.NET Core - IStartupFilter 与 IHostingStartup 1. IStartupFilter2 IHostingStartup2.5.1 创建外部程序集2.5.2 激活外部程序集 1. IStartupFilter 上面讲到的方式虽然能够根据不同环境将Startup中的启动逻辑进行分离&#xff0c;但是有些时候我们还会可以根据应用中的功能…

HarmonyOS NEXT应用开发边学边玩系列:从零实现一影视APP (五、电影详情页的设计实现)

在上一篇文章中&#xff0c;完成了电影列表页的开发。接下来&#xff0c;将进入电影详情页的设计实现阶段。这个页面将展示电影的详细信息&#xff0c;包括电影海报、评分、简介以及相关影人等。将使用 HarmonyOS 提供的常用组件&#xff0c;并结合第三方库 nutpi/axios 来实现…

【excel】VBA股票数据获取(搜狐股票)

文章目录 一、序二、excel 自动刷新股票数据三、付费获取 一、序 我其实不会 excel 的函数和 visual basic。因为都可以用matlab和python完成。 今天用了下VBA&#xff0c;还挺不错的。分享下。 上传写了个matlab获取股票数据的&#xff0c;是雅虎财经的。这次是搜狐股票的数…

Android 高德地图API(新版)

新版高德地图 前言正文一、创建应用① 获取PackageName② 获取调试版安全码SHA1③ 获取发布版安全码SHA1 二、配置项目① 导入SDK② 配置AndroidManifest.xml 三、获取当前定位信息① ViewBinding使用和导包② 隐私合规设置③ 权限请求④ 初始化定位⑤ 获取定位信息 四、显示地…

springCloudGateway+nacos自定义负载均衡-通过IP隔离开发环境

先说一下想法&#xff0c;小公司开发项目&#xff0c;参考若依框架使用的spring-cloud-starter-gateway和spring-cloud-starter-alibaba-nacos, 用到了nacos的配置中心和注册中心&#xff0c;有多个模块&#xff08;每个模块都是一个服务&#xff09;。 想本地开发&#xff0c;…

【NLP】语言模型的发展历程 (1)

语言模型的发展历程系列博客主要包含以下文章&#xff1a; 【NLP】语言模型的发展历程 (1)【NLP】大语言模型的发展历程 (2) 本篇博客是该系列的第一篇&#xff0c;主要讲讲 语言模型&#xff08;LM&#xff0c;Language Model&#xff09; 的发展历程。 文章目录 一、统计语…

【Compose multiplatform教程】05 IOS环境编译

了解如何使现有的 Android 应用程序跨平台&#xff0c;以便它在 Android 和 iOS 上都能运行。您将能够在一个位置编写代码并针对 Android 和 iOS 进行测试一次。 本教程使用一个示例 Android 应用程序&#xff0c;其中包含用于输入用户名和密码的单个屏幕。凭证经过验证并保存…

Redis哨兵(Sentinel)

Redis哨兵 ‌[Redis哨兵]&#xff08;Sentinel&#xff09;是Redis的一个高可用性解决方案&#xff0c;主要用于监控和管理多个Redis服务器&#xff0c;确保Redis系统的高可用性‌。哨兵通过实时监测主节点和从节点的状态&#xff0c;及时发现并自动处理故障&#xff0c;保证系…

WEB 攻防-通用漏-XSS 跨站脚本攻击-反射型/存储型/DOMBEEF-XSS

XSS跨站脚本攻击技术&#xff08;一&#xff09; XSS的定义 XSS攻击&#xff0c;全称为跨站脚本攻击&#xff0c;是指攻击者通过在网页中插入恶意脚本代码&#xff0c;当用户浏览该网页时&#xff0c;恶意脚本会被执行&#xff0c;从而达到攻击目的的一种安全漏洞。这些恶意脚…

【C++】B2112 石头剪子布

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述游戏规则&#xff1a;输入格式&#xff1a;输出格式&#xff1a;输入输出样例&#xff1a;解题分析与实现 &#x1f4af;我的做法实现逻辑优点与不足 &#x1f4af…