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

           }

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot的企业级工位管理系统【源码+文档+部署讲解】

系统介绍 基于SpringBootVue实现的企业级工位管理系统采用前后端分离架构方式&#xff0c;系统设计了管理员、员工两种角色&#xff0c;系统实现了用户登录与注册、个人中心、员工管理、部门信息管理、工位信息管理、使用情况管理、工位分配管理等功能。 技术选型 开发工具&…

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

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

【Linux】12.Linux进程概念(1)

文章目录 1. 冯诺依曼体系结构2. 操作系统(Operator System)概念设计OS的目的胆小的操作系统定位如何理解 "管理"总结 3. 进程基本概念task_struct-PCB的一种task_ struct内容分类组织进程查看进程通过系统调用获取进程标示符通过系统调用创建进程-fork初识 1. 冯诺依…

LabVIEW 程序中的 R6025 错误

R6025错误 通常是 运行时库 错误&#xff0c;特别是与 C 运行时库 相关。这种错误通常会在程序运行时出现&#xff0c;尤其是在使用 C 编译的程序或依赖 C 运行时库的程序时。 ​ 可能的原因&#xff1a; 内存访问冲突&#xff1a; R6025 错误通常是由于程序在运行时访问无效内…

03JavaWeb——Ajax-Vue-Element(项目实战)

1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2个程序&#xf…

2024 京东零售技术年度总结

每一次回望&#xff0c;都为了更好地前行。 2024 年&#xff0c;京东零售技术在全面助力业务发展的同时&#xff0c;在大模型应用、智能供应链、端技术、XR 体验等多个方向深入探索。京东 APP 完成阶段性重要改版&#xff0c;打造“又好又便宜”的优质体验&#xff1b;国补专区…

Apache搭建https服务器

Apache搭建https服务器 REF: 使用OpenSSL自建一个HTTPS服务

XML在线格式化 - 加菲工具

XML在线格式化 打开网站 加菲工具 选择“XML 在线格式化” 输入XML&#xff0c;点击左上角的“格式化”按钮 得到格式化后的结果

BO-SVM贝叶斯算法优化支持向量机的数据多变量时间序列预测

BO-SVM贝叶斯算法优化支持向量机的数据多变量时间序列预测 目录 BO-SVM贝叶斯算法优化支持向量机的数据多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于BO-SVR贝叶斯算法优化支持向量机的数据多变量时间序列预测&#xff0c;加入5折交叉验…

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

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

【鱼皮大佬API开放平台项目】Spring Cloud Gateway HTTPS 配置问题解决方案总结

问题背景 项目架构为前后端分离的微服务架构&#xff1a; 前端部署在 8000 端口API 网关部署在 9000 端口后端服务包括&#xff1a; api-backend (9001端口)api-interface (9002端口) 初始状态&#xff1a; 前端已配置 HTTPS&#xff08;端口 8000&#xff09;后端服务未配…

Windows远程桌面网关出现重大漏洞

微软披露了其Windows远程桌面网关&#xff08;RD Gateway&#xff09;中的一个重大漏洞&#xff0c;该漏洞可能允许攻击者利用竞争条件&#xff0c;导致拒绝服务&#xff08;DoS&#xff09;攻击。该漏洞被标识为CVE-2025-21225&#xff0c;已在2025年1月的补丁星期二更新中得到…

‌如何有效学习PyTorch:从基础到实践的全面指南‌

随着人工智能和深度学习技术的飞速发展&#xff0c;PyTorch作为当前最流行的深度学习框架之一&#xff0c;凭借其动态计算图、灵活的编程模型以及强大的社区支持&#xff0c;在学术界和工业界均得到了广泛应用。本文旨在为初学者和有一定基础的读者提供一套系统、全面的PyTorch…

2Spark Core

2Spark Core 1.RDD 详解1) 为什么要有 RDD?2) RDD 是什么?3) RDD 主要属性 2.RDD-API1) RDD 的创建方式2) RDD 的算子分类3) Transformation 转换算子4) Action 动作算子 3. RDD 的持久化/缓存4. RDD 容错机制 Checkpoint5. RDD 依赖关系1) 宽窄依赖2) 为什么要设计宽窄依赖 …

视频超分(VSR)论文阅读记录/idea积累(一)

STAR: Spatial-Temporal Augmentation with Text-to-Video Models for Real-World Video Super-Resolution 关键词: text-to-video (T2V) Local Information Enhancement Module (LIEM) Dynamic Frequency (DF) 引言: VSR: 传统VSR分两大类recurrent-based和sliding-wind…

MySQL8数据库全攻略:版本特性、下载、安装、卸载与管理工具详解

大家好&#xff0c;我是袁庭新。 MySQL作为企业项目中的主流数据库&#xff0c;其5.x和8.x版本尤为常用。本文将详细介绍MySQL 8.x的特性、下载、安装、服务管理、卸载及管理工具&#xff0c;旨在帮助用户更好地掌握和使用MySQL数据库。 1.MySQL版本及下载 企业项目中使用的…

Docker安装PostGreSQL docker安装PostGreSQL 完整详细教程

Docker安装PostGreSQL docker安装PostGreSQL 完整详细教程 Docker常用命令大全Docker 运行命令生成Docker 上安装 PostGreSQL 14.15 的步骤&#xff1a;1、拉取 PostGreSQL 14.15 镜像2、创建并运行容器3、测试连接4、设置所有IP都可以运行连接进入容器内 修改配置文件关闭容器…

Elasticsearch:Jira 连接器教程第一部分

作者&#xff1a;来自 Elastic Gustavo Llermaly 将我们的 Jira 内容索引到 Elaasticsearch 中以创建统一的数据源并使用文档级别安全性进行搜索。 在本文中&#xff0c;我们将回顾 Elastic Jira 原生连接器的一个用例。我们将使用一个模拟项目&#xff0c;其中一家银行正在开发…

Spring 6 第1章——概述

一.Spring是什么 Spring是一款主流的Java EE轻量级&#xff08;体积小、不需要依赖其它组件&#xff09;开源框架Spring的目的是用于简化Java企业级应用的开发难度和开发周期Spring的用途不仅限于服务端的开发&#xff0c;从简单性、可测试性和松耦合的角度而言&#xff0c;任…

git管理源码之git安装和使用

git是什么&#xff1f; git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理&#xff0c;也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。git与常用的版本控制工具SVN等不同&#xff0c;它采用…