FreeSWITCH添加自定义endpoint之媒体交互
一、originate流程
1、originate命令的使用
originate用于发起呼叫,命令使用的基础模板:
originate ALEG BLEG
在fs_cli控制台使用的完整语法如下:
originate <call url> <exten>|&<application_name>(<app_args>) [<dialplan>][<context>] [<cid_name>][<cid_num>] [<timeout_sec>]
originate user/1000 9196 xml default 'user1' 13012345678
2、originate功能入口函数
入口函数为originate_function,在 mod_commands_load 中绑定:
SWITCH_ADD_API(commands_api_interface, "originate", "Originate a call", originate_function, ORIGINATE_SYNTAX);
#define ORIGINATE_SYNTAX "<call url> <exten>|&<application_name>(<app_args>) [<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout_sec>]"
SWITCH_STANDARD_API(originate_function)
{
switch_channel_t *caller_channel;
switch_core_session_t *caller_session = NULL;
char *mycmd = NULL, *argv[10] = { 0 };
int i = 0, x, argc = 0;
char *aleg, *exten, *dp, *context, *cid_name, *cid_num;
uint32_t timeout = 60;
switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;
switch_status_t status = SWITCH_STATUS_SUCCESS; if (zstr(cmd)) {
stream->write_function(stream, "-USAGE: %s\n", ORIGINATE_SYNTAX);
return SWITCH_STATUS_SUCCESS;
} /* log warning if part of ongoing session, as we'll block the session */
if (session){
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Originate can take 60 seconds to complete, and blocks the existing session. Do not confuse with a lockup.\n");
} mycmd = strdup(cmd);
switch_assert(mycmd);
argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); if (argc < 2 || argc > 7) {
stream->write_function(stream, "-USAGE: %s\n", ORIGINATE_SYNTAX);
goto done;
} for (x = 0; x < argc && argv[x]; x++) {
if (!strcasecmp(argv[x], "undef")) {
argv[x] = NULL;
}
} aleg = argv[i++];
exten = argv[i++];
dp = argv[i++];
context = argv[i++];
cid_name = argv[i++];
cid_num = argv[i++]; switch_assert(exten); if (!dp) {
dp = "XML";
} if (!context) {
context = "default";
} if (argv[6]) {
timeout = atoi(argv[6]);
} if (switch_ivr_originate(NULL, &caller_session, &cause, aleg, timeout, NULL, cid_name, cid_num, NULL, NULL, SOF_NONE, NULL, NULL) != SWITCH_STATUS_SUCCESS
|| !caller_session) {
stream->write_function(stream, "-ERR %s\n", switch_channel_cause2str(cause));
goto done;
} caller_channel = switch_core_session_get_channel(caller_session); if (*exten == '&' && *(exten + 1)) {
switch_caller_extension_t *extension = NULL;
char *app_name = switch_core_session_strdup(caller_session, (exten + 1));
char *arg = NULL, *e; if ((e = strchr(app_name, ')'))) {
*e = '\0';
} if ((arg = strchr(app_name, '('))) {
*arg++ = '\0';
} if ((extension = switch_caller_extension_new(caller_session, app_name, arg)) == 0) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Memory Error!\n");
abort();
}
switch_caller_extension_add_application(caller_session, extension, app_name, arg);
switch_channel_set_caller_extension(caller_channel, extension);
switch_channel_set_state(caller_channel, CS_EXECUTE);
} else {
switch_ivr_session_transfer(caller_session, exten, dp, context);
} stream->write_function(stream, "+OK %s\n", switch_core_session_get_uuid(caller_session)); switch_core_session_rwunlock(caller_session); done:
switch_safe_free(mycmd);
return status;
}
originate_function
=> switch_ivr_originate
=> switch_core_session_outgoing_channel
=> endpoint_interface->io_routines->outgoing_channel
=> switch_core_session_thread_launch
3、switch_ivr_originate函数
该函数用于发起具体的呼叫。
switch_ivr_originate函数定义:
SWITCH_DECLARE(switch_status_t) switch_ivr_originate(
switch_core_session_t *session,
switch_core_session_t **bleg,
switch_call_cause_t *cause,
const char *bridgeto,
uint32_t timelimit_sec,
const switch_state_handler_table_t *table,
const char *cid_name_override,
const char *cid_num_override,
switch_caller_profile_t *caller_profile_override,
switch_event_t *ovars, switch_originate_flag_t flags,
switch_call_cause_t *cancel_cause,
switch_dial_handle_t *dh)
session : 发起originate的channel,即 caller_channel , aleg
bleg : originate所在的leg,会在该函数赋值
cause : 失败原因,会在该函数赋值
bridgeto : bleg的呼叫字符串,只读
timelimit_sec :originate超时时间
table : bleg的状态机回调函数
cid_name_override : origination_caller_id_name,用于设置主叫名称
cid_num_override : origination_caller_id_number,用于设置主叫号码
caller_profile_override :主叫的profile
ovars : originate导出的通道变量(从aleg)
flags : originate flag 参数,一般为 SOF_NONE
cancel_cause :originate取消原因
dh : dial handle,功能类似呼叫字符串,可以设置多条leg同时originate
if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_OUTGOING) == SWITCH_STATUS_SUCCESS) {
switch_channel_event_set_data(peer_channel, event);
switch_event_fire(&event);
}
if (!switch_core_session_running(oglobals.originate_status[i].peer_session)) {
if (oglobals.originate_status[i].per_channel_delay_start) {
switch_channel_set_flag(oglobals.originate_status[i].peer_channel, CF_BLOCK_STATE);
}
switch_core_session_thread_launch(oglobals.originate_status[i].peer_session);
}
二、bridge流程
1、流程入口
bridge app入口(mod_dptools.c):

函数调用链:
audio_bridge_function
=> switch_ivr_signal_bridge
=> switch_ivr_multi_threaded_bridge
=> audio_bridge_thread

函数调用链:
uuid_bridge_function => switch_ivr_uuid_bridge
2、bridge机制
注册回调函数:

状态机里面进行回调, 当channel进入CS_EXCHANGE_MEDIA状态后,回调 audio_bridge_on_exchange_media 函数,触发audio_bridge_thread线程。
三、媒体交互流程
1、注册编解码类型
通过 switch_core_codec_add_implementation 注册编解码。
添加PCMA编码:

添加opus编码:

2、RTP数据交互及转码
函数调用链:
audio_bridge_on_exchange_media => audio_bridge_thread
收发音频数据:
audio_bridge_thread
=> switch_core_session_read_frame
=> need_codec
=> switch_core_codec_decode (调用implement的encode进行转码操作,比如 switch_g711a_decode)
=> session->endpoint_interface->io_routines->read_frame 即: sofia_read_frame
=> switch_core_media_read_frame
=> switch_rtp_zerocopy_read_frame
=> rtp_common_read
=> read_rtp_packet
=> switch_socket_recvfrom audio_bridge_thread
=> switch_core_session_write_frame
=> switch_core_session_start_audio_write_thread (ptime不一致时启动线程,有500长度的队列)
=> switch_core_codec_encode (调用implement的encode进行转码操作,比如 switch_g711u_encode)
=> perform_write
=> session->endpoint_interface->io_routines->write_frame 比如: sofia_write_frame
=> switch_core_media_write_frame
=> switch_rtp_write_frame
=> rtp_common_write
=> switch_socket_sendto

音频数据会转成L16编码(raw格式),然后再编码成目标编码,示意图如下:

具体可参考各个编码的 encode 和 decode 代码(添加编码时的注释也可参考下):


四、自定义endpoint集成媒体交互示例
1、产生舒适噪音
产生舒适噪音,避免没有rtp导致的挂机。
1)需要设置 SFF_CNG 标志;
具体可参考 loopback 模块:

2)需要设置通道变量 bridge_generate_comfort_noise 为 true:
switch_channel_set_variable(chan_a,"bridge_generate_comfort_noise","true");
或者在orginate字符串中设置。
3)audio_bridge_thread函数里面有舒适噪音处理相关逻辑;


2、ptime保持一致
需要注意下编码的ptime值,当ptime不一致会触发freeswitch的缓存机制,进而导致运行过程中内存增加。
具体原理可从如下渠道获取:
3、示例代码
这里基于之前写的FreeSWITCH添加自定义endpoint的文章:
https://www.cnblogs.com/MikeZhang/p/fsAddEndpoint20230528.html
以 C 代码为示例,简单实现endpoint收发媒体功能,注意事项如下:
1)设置endpoint编码信息,这里使用L16编码,ptime为20ms;
2)桥接 sip 侧的leg,实现媒体互通;
3)这里用音频文件模拟 endpoint 发送媒体操作,通过 read_frame 函数发送给对端;
4)接收到sip侧的rtp数据(write_frame函数),可写入文件、通过socket发出去或直接丢弃(这里直接丢弃了);
5)不要轻易修改状态机;
6)需要注意数据的初始化和资源回收;
需要对channel进行answer,这里在ctest_on_consume_media函数实现:

完整代码可从如下渠道获取:
4、运行效果
1)编译及安装

2)呼叫效果
测试命令:
originate user/1000 &bridge(ctest/1001)
运行效果:

这里的raw文件采用之前文章里面的示例(test1.raw),如何生成请参考:
https://www.cnblogs.com/MikeZhang/p/pcm20232330.html
endpoint模块集成媒体交互功能的编译及运行效果视频:
关注微信公众号(聊聊博文,文末可扫码)后回复 2023080601 获取。
五、资源下载
本文涉及源码和文件,可从如下途径获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

FreeSWITCH添加自定义endpoint之媒体交互的更多相关文章
- freeswitch呼叫流程分析
今天翻文档时发现之前整理的关于freeswitch呼叫相关的内容,写成博文分享出来也方便我以后查阅. 整体结构图 FreeswitchCore 模块加载过程 freeswitch主程序初始化时会从mo ...
- 《FreeSWITCH: VoIP实战》:SIP 模块 - mod_sofia
SIP 模块是 FreeSWITCH 的主要模块,所以,值得拿出专门一章来讲解. 在前几章时里,你肯定见过几次 sofia 这个词,只是或许还不知道是什么意思.是这样的,Sofia-SIP 是由诺基亚 ...
- Freeswitch配置之sofia
SIP模块 - mod_sofia SIP 模块是 FreeSWITCH的主要模块. 在 FreeSWITCH中,实现一些互联协议接口的模块称为 Endpoint.FreeSWITH支持很多的 End ...
- Web富媒体应用
曾几何时,大家都在以flash开发的富媒体交互应用而感叹,一是叹它的丰富多彩的效果,一是叹它的局限.性能以及加载时长等问题. 如今,市场以及基本上没有flash什么事情了,而是H5的天下,可惜,移动应 ...
- SpringCloud学习2-Springboot监控模块(actuator)
前言 学习一项新技术最大的困难是什么? 是资料.让人高兴的是找到了一本系统学习Spring Cloud的教程,<Spring Cloud微服务实战>, 接下来的学习目标将以此书顺序演进. ...
- UPnP基本原理介绍
http://blog.csdn.net/braddoris/article/details/41576515 随着计算机产业以及计算机网络技术的迅猛发展,越来越多嵌入式设备的出现和家庭网络的发展,实 ...
- 中文翻译:pjsip教程(三)之ICE stream transport的使用
1:pjsip教程(一)之PJNATH简介 2:pjsip教程(二)之ICE穿越打洞:Interactive Connectivity Establishment简介 3:pjsip教程(三)之ICE ...
- 和菜鸟一起学linux之dlna的学习记录
关于DLNA框架 1.Networking & Connectivity 为了解决物理设备连通问题, 主要依赖于Ethernet,802.11,Ipv4协议栈,Ipv6协议栈. TCP/IP协 ...
- 使用SIP Servlet为Java EE添加语音功能
会话发起协议(Session Initiation Protocol,SIP)是一种信号传输协议,用于建立.修改和终止两个端点之间的会话.SIP 可用于建立 两方呼叫.多方呼叫,或者甚至 Intern ...
- WebRTC 学习之 WebRTC 简介
本文使用的WebRTC相关API都是基于Intel® Collaboration Suite for WebRTC的. 相关文档链接:https://software.intel.com/sites/ ...
随机推荐
- 「微服务」这10道Consul面试题值得一看
前言 Consul 是一种非常强大的分布式服务发现和配置管理工具,它可以帮助开发人员和运维人员更好地管理和维护分布式系统. 但是,使用 Consul 也需要投入一定的人力和物力,需要根据实际情况进行选 ...
- 2022-03-06:金币路径。 给定一个数组 A(下标从 1 开始)包含 N 个整数:A1,A2,……,AN 和一个整数 B。 你可以从数组 A 中的任何一个位置(下标为 i)跳到下标 i+1,i+
2022-03-06:金币路径. 给定一个数组 A(下标从 1 开始)包含 N 个整数:A1,A2,--,AN 和一个整数 B. 你可以从数组 A 中的任何一个位置(下标为 i)跳到下标 i+1,i+ ...
- 2021-09-06:给表达式添加运算符。给定一个仅包含数字 0-9 的字符串 num 和一个目标值整数 target ,在 num 的数字之间添加 二元 运算符(不是一元)+、- 或 * ,返回所有
2021-09-06:给表达式添加运算符.给定一个仅包含数字 0-9 的字符串 num 和一个目标值整数 target ,在 num 的数字之间添加 二元 运算符(不是一元)+.- 或 * ,返回所有 ...
- getattr函数
函数说明 getattr(object, name, default=None) object:类实例 name:str 属性名 default:str 如果没有查找到,用该值替代 用法实现反射,主要 ...
- 【GiraKoo】Visual Studio开启Asan提示“LINK : fatal error LNK1104: cannot open file 'LIBVCASAN.lib'”
[解决]Visual Studio开启Asan提示"LINK fatal error LNK1104 cannot open file 'LIBVCASAN.lib'" 环境 Vi ...
- 洛谷P3374 【模板】树状数组 1-(单点修改,区间查询)
题目描述 如题,已知一个数列,你需要进行下面两种操作: 将某一个数加上 x 求出某区间每一个数的和 输入格式 第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数. 第二行包含 n 个 ...
- Three.js 进阶之旅:滚动控制模型动画和相机动画 🦢
声明:本文涉及图文和模型素材仅用于个人学习.研究和欣赏,请勿二次修改.非法传播.转载.出版.商用.及进行其他获利行为. 摘要 专栏上篇文章<Three.js 进阶之旅:页面*滑滚动-王国之泪&g ...
- web自动化08-下拉选择框、弹出框、滚动条
1.下拉选择框操作 下拉框就是HTML中<select>元素: 先列需求: 需求:使用'注册A.html'页面,完成对城市的下拉框的操作 1).选择'广州' 2).暂停2秒,选择'上海 ...
- shell脚本中特殊筛选文件
问题描述:在写shell中,总会遇到一些各式各样筛选文件的需求,整理了一些特殊情况 1.查找目标文件下大于100Mb的文件 find $target_dir -type f -size +70M 2. ...
- docker-compose多服务器部署ELK
多服务器构建ELK es使用1主2从,logstash转发,kibana展现,本文ELK版本使用7.16.1 如果单机测试,请查看docker-compose单服务器部署ELK es-master:1 ...