FreeSWITCH添加自定义endpoint之媒体交互 - Mike_Zhang
source link: https://www.cnblogs.com/MikeZhang/p/fsAddEndpoint20230806.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
一、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 获取。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK