操作系统 :CentOS 7.6_x64
FreeSWITCH版本 :1.10.9
 
之前写过FreeSWITCH添加自定义endpoint的文章:
今天记录下endpoint媒体交互的过程并提供示例代码及相关资源下载,本文涉及示例代码和资源可从如下渠道获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

一、originate流程

1、originate命令的使用

originate用于发起呼叫,命令使用的基础模板:

  1. originate ALEG BLEG

在fs_cli控制台使用的完整语法如下:

  1. originate <call url> <exten>|&<application_name>(<app_args>) [<dialplan>][&lt;context>] [<cid_name>][&lt;cid_num>] [<timeout_sec>]
其中,
originate 为命令关键字,为必选字段,用于定义ALEG的呼叫信息,也就是通常说的呼叫字符串,可以通过通道变量定义很多参数;
|&<application_name>(<app_args>)  为必选字段,用于指定BLEG的分机号码或者用于创建BLEG的app(比如echo、bridge等);
[][<context>]  可选参数,该参数用于指定dialplan的context,默认值:xml default ;
[<timeout_sec>] 可选参数,该参数用于指定originate超时,默认值:60 ;
 
这里以分机进行示例呼叫:
  1. originate user/1000 9196 xml default 'user1' 13012345678
更多使用方法可参考我之前写的文章:

2、originate功能入口函数

入口函数为originate_function,在 mod_commands_load 中绑定:

  1. SWITCH_ADD_API(commands_api_interface, "originate", "Originate a call", originate_function, ORIGINATE_SYNTAX);
具体实现如下:
  1. #define ORIGINATE_SYNTAX "<call url> <exten>|&<application_name>(<app_args>) [<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout_sec>]"
  2. SWITCH_STANDARD_API(originate_function)
  3. {
  4. switch_channel_t *caller_channel;
  5. switch_core_session_t *caller_session = NULL;
  6. char *mycmd = NULL, *argv[10] = { 0 };
  7. int i = 0, x, argc = 0;
  8. char *aleg, *exten, *dp, *context, *cid_name, *cid_num;
  9. uint32_t timeout = 60;
  10. switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;
  11. switch_status_t status = SWITCH_STATUS_SUCCESS;
  12.  
  13. if (zstr(cmd)) {
  14. stream->write_function(stream, "-USAGE: %s\n", ORIGINATE_SYNTAX);
  15. return SWITCH_STATUS_SUCCESS;
  16. }
  17.  
  18. /* log warning if part of ongoing session, as we'll block the session */
  19. if (session){
  20. 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");
  21. }
  22.  
  23. mycmd = strdup(cmd);
  24. switch_assert(mycmd);
  25. argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
  26.  
  27. if (argc < 2 || argc > 7) {
  28. stream->write_function(stream, "-USAGE: %s\n", ORIGINATE_SYNTAX);
  29. goto done;
  30. }
  31.  
  32. for (x = 0; x < argc && argv[x]; x++) {
  33. if (!strcasecmp(argv[x], "undef")) {
  34. argv[x] = NULL;
  35. }
  36. }
  37.  
  38. aleg = argv[i++];
  39. exten = argv[i++];
  40. dp = argv[i++];
  41. context = argv[i++];
  42. cid_name = argv[i++];
  43. cid_num = argv[i++];
  44.  
  45. switch_assert(exten);
  46.  
  47. if (!dp) {
  48. dp = "XML";
  49. }
  50.  
  51. if (!context) {
  52. context = "default";
  53. }
  54.  
  55. if (argv[6]) {
  56. timeout = atoi(argv[6]);
  57. }
  58.  
  59. if (switch_ivr_originate(NULL, &caller_session, &cause, aleg, timeout, NULL, cid_name, cid_num, NULL, NULL, SOF_NONE, NULL, NULL) != SWITCH_STATUS_SUCCESS
  60. || !caller_session) {
  61. stream->write_function(stream, "-ERR %s\n", switch_channel_cause2str(cause));
  62. goto done;
  63. }
  64.  
  65. caller_channel = switch_core_session_get_channel(caller_session);
  66.  
  67. if (*exten == '&' && *(exten + 1)) {
  68. switch_caller_extension_t *extension = NULL;
  69. char *app_name = switch_core_session_strdup(caller_session, (exten + 1));
  70. char *arg = NULL, *e;
  71.  
  72. if ((e = strchr(app_name, ')'))) {
  73. *e = '\0';
  74. }
  75.  
  76. if ((arg = strchr(app_name, '('))) {
  77. *arg++ = '\0';
  78. }
  79.  
  80. if ((extension = switch_caller_extension_new(caller_session, app_name, arg)) == 0) {
  81. switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Memory Error!\n");
  82. abort();
  83. }
  84. switch_caller_extension_add_application(caller_session, extension, app_name, arg);
  85. switch_channel_set_caller_extension(caller_channel, extension);
  86. switch_channel_set_state(caller_channel, CS_EXECUTE);
  87. } else {
  88. switch_ivr_session_transfer(caller_session, exten, dp, context);
  89. }
  90.  
  91. stream->write_function(stream, "+OK %s\n", switch_core_session_get_uuid(caller_session));
  92.  
  93. switch_core_session_rwunlock(caller_session);
  94.  
  95. done:
  96. switch_safe_free(mycmd);
  97. return status;
  98. }
调用流程如下:
  1. originate_function
  2. => switch_ivr_originate
  3. => switch_core_session_outgoing_channel
  4. => endpoint_interface->io_routines->outgoing_channel
  5. => switch_core_session_thread_launch

3、switch_ivr_originate函数

该函数用于发起具体的呼叫。

switch_ivr_originate函数定义:

  1. SWITCH_DECLARE(switch_status_t) switch_ivr_originate(
  2. switch_core_session_t *session,
  3. switch_core_session_t **bleg,
  4. switch_call_cause_t *cause,
  5. const char *bridgeto,
  6. uint32_t timelimit_sec,
  7. const switch_state_handler_table_t *table,
  8. const char *cid_name_override,
  9. const char *cid_num_override,
  10. switch_caller_profile_t *caller_profile_override,
  11. switch_event_t *ovars, switch_originate_flag_t flags,
  12. switch_call_cause_t *cancel_cause,
  13. switch_dial_handle_t *dh)
参数解释:
  1. session : 发起originatechannel,即 caller_channel , aleg
  2. bleg : originate所在的leg,会在该函数赋值
  3. cause : 失败原因,会在该函数赋值
  4. bridgeto : bleg的呼叫字符串,只读
  5. timelimit_sec originate超时时间
  6. table bleg的状态机回调函数
  7. cid_name_override : origination_caller_id_name,用于设置主叫名称
  8. cid_num_override : origination_caller_id_number,用于设置主叫号码
  9. caller_profile_override :主叫的profile
  10. ovars originate导出的通道变量(从aleg
  11. flags originate flag 参数,一般为 SOF_NONE
  12. cancel_cause originate取消原因
  13. dh dial handle,功能类似呼叫字符串,可以设置多条leg同时originate
如果outgoing_channel执行成功,会发送SWITCH_EVENT_CHANNEL_OUTGOING事件;并且该channel会设置上CF_ORIGINATING标识位。
  1. if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_OUTGOING) == SWITCH_STATUS_SUCCESS) {
  2. switch_channel_event_set_data(peer_channel, event);
  3. switch_event_fire(&event);
  4. }
使用 switch_core_session_thread_launch 启动线程创建session :
  1. if (!switch_core_session_running(oglobals.originate_status[i].peer_session)) {
  2. if (oglobals.originate_status[i].per_channel_delay_start) {
  3. switch_channel_set_flag(oglobals.originate_status[i].peer_channel, CF_BLOCK_STATE);
  4. }
  5. switch_core_session_thread_launch(oglobals.originate_status[i].peer_session);
  6. }

二、bridge流程

1、流程入口

bridge app入口(mod_dptools.c):

函数调用链:

  1. audio_bridge_function
  2. => switch_ivr_signal_bridge
  3. => switch_ivr_multi_threaded_bridge
  4. => audio_bridge_thread
uuid_bridge api入口(mod_commands.c):

函数调用链:

  1. 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数据交互及转码

函数调用链:

  1. audio_bridge_on_exchange_media => audio_bridge_thread

收发音频数据:

  1. audio_bridge_thread
  2. => switch_core_session_read_frame
  3. => need_codec
  4. => switch_core_codec_decode (调用implementencode进行转码操作,比如 switch_g711a_decode)
  5. => session->endpoint_interface->io_routines->read_frame 即: sofia_read_frame
  6. => switch_core_media_read_frame
  7. => switch_rtp_zerocopy_read_frame
  8. => rtp_common_read
  9. => read_rtp_packet
  10. => switch_socket_recvfrom
  11.  
  12. audio_bridge_thread
  13. => switch_core_session_write_frame
  14. => switch_core_session_start_audio_write_thread (ptime不一致时启动线程,有500长度的队列)
  15. => switch_core_codec_encode (调用implementencode进行转码操作,比如 switch_g711u_encode)
  16. => perform_write
  17. => session->endpoint_interface->io_routines->write_frame 比如: sofia_write_frame
  18. => switch_core_media_write_frame
  19. => switch_rtp_write_frame
  20. => rtp_common_write
  21. => switch_socket_sendto

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

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

四、自定义endpoint集成媒体交互示例

1、产生舒适噪音

产生舒适噪音,避免没有rtp导致的挂机。

1)需要设置 SFF_CNG 标志;
具体可参考 loopback 模块:

2)需要设置通道变量 bridge_generate_comfort_noise 为 true:

  1. switch_channel_set_variable(chan_a,"bridge_generate_comfort_noise","true");

或者在orginate字符串中设置。

3)audio_bridge_thread函数里面有舒适噪音处理相关逻辑;

2、ptime保持一致

需要注意下编码的ptime值,当ptime不一致会触发freeswitch的缓存机制,进而导致运行过程中内存增加。

具体原理可从如下渠道获取:

关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

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函数实现:

完整代码可从如下渠道获取:

关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

4、运行效果

1)编译及安装

2)呼叫效果

测试命令:

  1. originate user/1000 &bridge(ctest/1001)

运行效果:

这里的raw文件采用之前文章里面的示例(test1.raw),如何生成请参考:

https://www.cnblogs.com/MikeZhang/p/pcm20232330.html

endpoint模块集成媒体交互功能的编译及运行效果视频:

关注微信公众号(聊聊博文,文末可扫码)后回复 2023080601 获取。

五、资源下载

本文涉及源码和文件,可从如下途径获取:

关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

 

FreeSWITCH添加自定义endpoint之媒体交互的更多相关文章

  1. freeswitch呼叫流程分析

    今天翻文档时发现之前整理的关于freeswitch呼叫相关的内容,写成博文分享出来也方便我以后查阅. 整体结构图 FreeswitchCore 模块加载过程 freeswitch主程序初始化时会从mo ...

  2. 《FreeSWITCH: VoIP实战》:SIP 模块 - mod_sofia

    SIP 模块是 FreeSWITCH 的主要模块,所以,值得拿出专门一章来讲解. 在前几章时里,你肯定见过几次 sofia 这个词,只是或许还不知道是什么意思.是这样的,Sofia-SIP 是由诺基亚 ...

  3. Freeswitch配置之sofia

    SIP模块 - mod_sofia SIP 模块是 FreeSWITCH的主要模块. 在 FreeSWITCH中,实现一些互联协议接口的模块称为 Endpoint.FreeSWITH支持很多的 End ...

  4. Web富媒体应用

    曾几何时,大家都在以flash开发的富媒体交互应用而感叹,一是叹它的丰富多彩的效果,一是叹它的局限.性能以及加载时长等问题. 如今,市场以及基本上没有flash什么事情了,而是H5的天下,可惜,移动应 ...

  5. SpringCloud学习2-Springboot监控模块(actuator)

    前言 学习一项新技术最大的困难是什么? 是资料.让人高兴的是找到了一本系统学习Spring Cloud的教程,<Spring Cloud微服务实战>, 接下来的学习目标将以此书顺序演进. ...

  6. UPnP基本原理介绍

    http://blog.csdn.net/braddoris/article/details/41576515 随着计算机产业以及计算机网络技术的迅猛发展,越来越多嵌入式设备的出现和家庭网络的发展,实 ...

  7. 中文翻译:pjsip教程(三)之ICE stream transport的使用

    1:pjsip教程(一)之PJNATH简介 2:pjsip教程(二)之ICE穿越打洞:Interactive Connectivity Establishment简介 3:pjsip教程(三)之ICE ...

  8. 和菜鸟一起学linux之dlna的学习记录

    关于DLNA框架 1.Networking & Connectivity 为了解决物理设备连通问题, 主要依赖于Ethernet,802.11,Ipv4协议栈,Ipv6协议栈. TCP/IP协 ...

  9. 使用SIP Servlet为Java EE添加语音功能

    会话发起协议(Session Initiation Protocol,SIP)是一种信号传输协议,用于建立.修改和终止两个端点之间的会话.SIP 可用于建立 两方呼叫.多方呼叫,或者甚至 Intern ...

  10. WebRTC 学习之 WebRTC 简介

    本文使用的WebRTC相关API都是基于Intel® Collaboration Suite for WebRTC的. 相关文档链接:https://software.intel.com/sites/ ...

随机推荐

  1. Windows亚克力特效代码实现(Dev c++可以编译通过)

    #include <windows.h> #include <dwmapi.h> // 定义一个枚举类型,表示不同的窗口组合状态 enum AccentState { ACCE ...

  2. 自创简易CSS Tab 选项卡

    前段时间我注册了 w3c.run域名,打算做一个W3C相关技术在线试验工具.没错,就是在线编写html.css.js代码然后在线运行,查看效果. 在设计首页时,我打算首页提供三个代码编辑器,介于界面大 ...

  3. selenium 多窗口处理与网页frame

    多窗口处理 点击某些链接,会重新打开一个窗口,对于这种情况.想在薪页面操作,就得先切换窗口了. 获取窗口得唯一标识用句柄表示,所以只需要切换句柄,就可以在多个页面进行操作了 1. 先获取到当前得窗口句 ...

  4. 开发 Diffusers 库的道德行为指南

    我们正在努力让我们每次发布的库更加负责! 我们很荣幸宣布我们发布了 道德守则,并将作为一部分其放入  Diffusers 库的说明文档. 由于扩散模型在现实世界上的实际应用例子会对社会造成潜在的负面影 ...

  5. Syncthing 忽略模式

    忽略模式 概要 .stignore 描述 这是 Syncthing 同步文件夹的忽略模式语法指南 语法 .stignore 文件可包含一系列路径匹配模式,对指定文件的处理方式由第一个匹配到它的模式决定 ...

  6. 2020-11-23:go中,s是一个字符串,s[0]代表什么?是否等于固定字节数?

    福个答案2020-11-23:Golang 的字符串(string)是合法的 UTF-8 序列,这就涉及到了两种不同的遍历方式,一种是按照 Unicode 的 codepoint 遍历,另一种是把 s ...

  7. 2021-12-17:长城守卫军问题。 长城上有连成一排的n个烽火台,每个烽火台都有士兵驻守。 第i个烽火台驻守着ai个士兵,相邻峰火台的距离为1。另外,有m位将军, 每位将军可以驻守一个峰火台,每个

    2021-12-17:长城守卫军问题. 长城上有连成一排的n个烽火台,每个烽火台都有士兵驻守. 第i个烽火台驻守着ai个士兵,相邻峰火台的距离为1.另外,有m位将军, 每位将军可以驻守一个峰火台,每个 ...

  8. 2021-10-10:杨辉三角 II。给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。在「杨辉三角」中,每个数是它左上方和右上方的数的和。力扣119。

    2021-10-10:杨辉三角 II.给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行.在「杨辉三角」中,每个数是它左上方和右上方的数的和.力扣119. 福大大 答案20 ...

  9. ET框架6.0分析一、ECS架构

    概述 ET框架的ECS架构是从ECS原生设计思想变形而来的(关于ECS架构的分析可以参考跳转链接:<ECS架构分析>),其特点是: Entity:实体可以作为组件挂载到其他实体上,Enti ...

  10. vue全家桶进阶之路9:常用指令

    以下是一些常见的指令: v-bind - 用于绑定一个或多个属性到组件或 HTML 元素上. v-model - 用于双向绑定一个表单元素或组件的值到数据模型上. v-for - 用于循环遍历一个数组 ...