AI 生成的FreeSWITCH 呼出流程深度分析freeswitch-1.10.12.-release

FreeSWITCH 呼出流程深度分析

概述

FreeSWITCH的呼出(Outbound Call)流程是一个复杂的多层次系统,涉及API命令处理、IVR层、核心会话管理、endpoint模块(如mod_sofia)以及SIP协议栈等多个组件的协同工作。


一、呼出流程总览

1.1 流程图

```

┌─────────────────────────────────────────────────────────────────┐

│ 用户/应用层 │

├─────────────────────────────────────────────────────────────────┤

│ API Command / Dialplan / ESL / Script │

│ - fs_cli -x "originate ..." │

│ - <action application="bridge" data="..."/> │

│ - ESL originate │

└──────────────────────┬──────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐

│ API/命令处理层 │

├─────────────────────────────────────────────────────────────────┤

│ mod_commands: originate_function() │

│ - 解析参数 │

│ - 调用 switch_ivr_originate() │

└──────────────────────┬──────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐

│ IVR层(核心呼出逻辑) │

├─────────────────────────────────────────────────────────────────┤

│ switch_ivr_originate.c │

│ 1. 解析呼叫字符串(dial string) │

│ 2. 处理并发/串行呼叫逻辑 │

│ 3. 为每个endpoint创建会话 │

│ 4. 监控呼叫进度 │

│ 5. 处理超时和取消 │

└──────────────────────┬──────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐

│ 核心会话层 │

├─────────────────────────────────────────────────────────────────┤

│ switch_core_session_outgoing_channel() │

│ 1. 获取endpoint接口 │

│ 2. 处理effective caller id │

│ 3. 调用endpoint的outgoing_channel回调 │

│ 4. 设置UUID和会话属性 │

│ 5. 添加event hooks │

└──────────────────────┬──────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐

│ Endpoint层(SIP: mod_sofia) │

├─────────────────────────────────────────────────────────────────┤

│ sofia_outgoing_channel() │

│ 1. 解析目标地址(profile/gateway/dest) │

│ 2. 查找/验证profile或gateway │

│ 3. 创建私有对象(tech_pvt) │

│ 4. 构造SIP URI │

│ 5. 设置SIP headers │

│ 6. 准备媒体参数(SDP) │

└──────────────────────┬──────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐

│ SIP协议栈层(Sofia-SIP) │

├─────────────────────────────────────────────────────────────────┤

│ sofia_glue_do_invite() │

│ 1. 准备本地SDP │

│ 2. 构造INVITE消息 │

│ 3. 调用 nua_invite() │

│ 4. 发送INVITE到网络 │

└──────────────────────┬──────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐

│ 状态机处理 │

├─────────────────────────────────────────────────────────────────┤

│ switch_core_session_run() │

│ CS_NEW → CS_INIT → CS_ROUTING → CS_CONSUME_MEDIA │

│ 等待应答: 100 Trying → 180 Ringing → 200 OK │

│ 协商媒体 │

│ → CS_EXECUTE (应答后执行dialplan) │

└─────────────────────────────────────────────────────────────────┘

```


二、详细流程分析

2.1 阶段一:API命令处理

入口点:`originate_function()`

**文件位置:** `src/mod/applications/mod_commands/mod_commands.c:5080`

```c

SWITCH_STANDARD_API(originate_function)

{

char *aleg, *exten, *dp, *context, *cid_name, *cid_num;

uint32_t timeout = 60;

switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;

// 1. 参数解析

argc = switch_separate_string(mycmd, ' ', argv, ...);

aleg = argv[i++]; // 呼叫目标 (如 "sofia/internal/1000@domain")

exten = argv[i++]; // 应答后执行的extension

dp = argv[i++]; // dialplan类型

context = argv[i++]; // context

cid_name = argv[i++]; // 主叫名称

cid_num = argv[i++]; // 主叫号码

timeout = argv[6]; // 超时时间

// 2. 调用IVR层的originate

if (switch_ivr_originate(NULL, &caller_session, &cause,

aleg, timeout, NULL,

cid_name, cid_num,

NULL, NULL, SOF_NONE, NULL, NULL)

!= SWITCH_STATUS_SUCCESS) {

stream->write_function(stream, "-ERR %s\n",

switch_channel_cause2str(cause));

goto done;

}

// 3. 设置dialplan extension

caller_channel = switch_core_session_get_channel(caller_session);

if (*exten == '&') {

// 执行application

switch_ivr_schedule_broadcast(...);

} else {

// 执行dialplan

switch_channel_set_variable(caller_channel,

SWITCH_CURRENT_APPLICATION_VARIABLE,

"originate");

switch_ivr_session_transfer(caller_session, exten, dp, context);

}

}

```

**关键参数说明:**

| 参数 | 示例 | 说明 |

|------|------|------|

| aleg | `sofia/internal/1000@domain.com` | 呼叫目标字符串 |

| exten | `&park()` 或 `9999` | 应答后执行的动作 |

| dp | `XML` | Dialplan类型 |

| context | `default` | Context名称 |

| timeout | `60` | 超时秒数 |

呼叫字符串格式

```

基本格式

sofia/profile_name/destination

通过gateway

sofia/gateway/gateway_name/destination

带参数

{var1=value1,var2=value2}sofia/internal/1000@domain.com

并发呼叫(同时呼多个)

sofia/internal/1000@domain.com,sofia/internal/1001@domain.com

串行呼叫(顺序呼叫)

sofia/internal/1000@domain.com|sofia/internal/1001@domain.com

组合

{ignore_early_media=true}[leg_timeout=20]sofia/gw/provider1/12345,

leg_timeout=30\]sofia/gw/provider2/12345 \`\`\` --- ### 2.2 阶段二:IVR层处理 #### 核心函数:\`switch_ivr_originate()\` \*\*文件位置:\*\* \`src/switch_ivr_originate.c\` ##### 2.2.1 参数解析和验证 \`\`\`c SWITCH_DECLARE(switch_status_t) switch_ivr_originate( switch_core_session_t \*session, // 发起会话(可为NULL) 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_list_t \*hl // 拨号句柄 ) { // 1. 初始化 oglobals_t oglobals = { 0 }; originate_global_t \*oglobals_ptr = \&oglobals; // 2. 解析呼叫字符串 // 支持: \|(串行), ,(并行), \[\](leg变量), {}(全局变量) or_argc = switch_separate_string(pipe_names, '\|', ...); // 3. 为每个目标创建呼叫 for (r = 0; r \< or_argc; r++) { // 解析并行呼叫(逗号分隔) and_argc = switch_separate_string(pipe_names\[r\], ',', ...); for (i = 0; i \< and_argc; i++) { // 提取变量 \[var1=val1,var2=val2

if (*chan_type == '[') {

switch_event_create_brackets(chan_type, ...);

}

// 分离 endpoint/data

chan_data = strchr(chan_type, '/');

*chan_data++ = '\0';

// 创建caller profile

new_profile = switch_caller_profile_new(...);

// 4. 调用核心层创建outgoing channel

reason = switch_core_session_outgoing_channel(

session, originate_var_event,

chan_type, new_profile,

&peer_session, &peer_pool,

flags, cancel_cause);

// 5. 保存会话引用

peer_channels[i] = switch_core_session_get_channel(peer_session);

oglobals.originate_status[i].peer_session = peer_session;

}

}

// 6. 监控呼叫进度

while (!(check_channel_status(&oglobals, ...))) {

// 检查所有呼叫状态

// 处理early media

// 检查超时

// 等待应答

switch_yield(10000);

}

// 7. 处理结果

if (oglobals.idx == IDX_CANCEL) {

// 取消所有呼叫

for (i = 0; i < and_argc; i++) {

if (peer_channels[i]) {

switch_channel_hangup(peer_channels[i],

SWITCH_CAUSE_ORIGINATOR_CANCEL);

}

}

} else {

// 返回应答的呼叫

*bleg = oglobals.originate_status[oglobals.idx].peer_session;

}

}

```

2.2.2 呼叫模式

**1. 串行呼叫(Failover)**

```

sofia/gw1/123|sofia/gw2/123|sofia/gw3/123

```

  • 先呼gw1,失败后呼gw2,再失败呼gw3

  • 直到有一个成功或全部失败

**2. 并发呼叫(Race)**

```

sofia/gw1/123,sofia/gw2/123,sofia/gw3/123

```

  • 同时呼叫三个目标

  • 第一个应答的获胜,其他取消

**3. 组合模式**

```

leg_timeout=10\]sofia/gw1/123,sofia/gw2/123\|\[leg_timeout=15\]sofia/gw3/123 \`\`\` - 先并发呼gw1和gw2(10秒超时) - 都失败则呼gw3(15秒超时) ##### 2.2.3 呼叫监控循环 \`\`\`c static int check_channel_status(originate_global_t \*oglobals, ...) { for (i = 0; i \< MAX_PEERS \&\& (peer_channel = peer_channels\[i\]); i++) { switch_channel_state_t state = switch_channel_get_state(peer_channel); switch (state) { case CS_NEW: case CS_INIT: case CS_ROUTING: // 呼叫建立中 break; case CS_CONSUME_MEDIA: case CS_EXCHANGE_MEDIA: // Early media if (switch_channel_test_flag(peer_channel, CF_ANSWERED)) { // 已应答 oglobals-\>idx = i; return 1; // 成功 } break; case CS_HANGUP: case CS_REPORTING: // 呼叫失败 cause = switch_channel_get_cause(peer_channel); break; } } // 检查超时 if (elapsed \> timelimit_sec) { return 1; // 超时 } return 0; // 继续等待 } \`\`\` --- ### 2.3 阶段三:核心会话层 #### 函数:\`switch_core_session_outgoing_channel()\` \*\*文件位置:\*\* \`src/switch_core_session.c:521\` \`\`\`c SWITCH_DECLARE(switch_call_cause_t) switch_core_session_outgoing_channel( switch_core_session_t \*session, switch_event_t \*var_event, const char \*endpoint_name, // "sofia" switch_caller_profile_t \*caller_profile, switch_core_session_t \*\*new_session, switch_memory_pool_t \*\*pool, switch_originate_flag_t flags, switch_call_cause_t \*cancel_cause) { switch_endpoint_interface_t \*endpoint_interface; switch_call_cause_t cause = SWITCH_CAUSE_REQUESTED_CHAN_UNAVAIL; // 1. 获取endpoint接口 if (!(endpoint_interface = switch_loadable_module_get_endpoint_interface(endpoint_name))) { switch_log_printf(..., "Could not locate channel type %s\\n", endpoint_name); return SWITCH_CAUSE_CHAN_NOT_IMPLEMENTED; } // 2. 检查是否支持outgoing if (!endpoint_interface-\>io_routines-\>outgoing_channel) { switch_log_printf(..., "Could not locate outgoing channel interface\\n"); return SWITCH_CAUSE_CHAN_NOT_IMPLEMENTED; } // 3. 处理Max-Forwards if (session) { channel = switch_core_session_get_channel(session); forwardvar = switch_channel_get_variable(channel, SWITCH_MAX_FORWARDS_VARIABLE); forwardval = atoi(forwardvar) - 1; if (forwardval \<= 0) { return SWITCH_CAUSE_EXCHANGE_ROUTING_ERROR; } } // 4. 处理Effective Caller ID if (session \&\& caller_profile) { const char \*eani = switch_channel_get_variable(channel, "effective_ani"); const char \*ecid_name = switch_channel_get_variable(channel, "effective_caller_id_name"); const char \*ecid_num = switch_channel_get_variable(channel, "effective_caller_id_number"); if (eani \|\| ecid_name \|\| ecid_num) { outgoing_profile = switch_caller_profile_clone(session, caller_profile); if (eani) outgoing_profile-\>ani = eani; if (ecid_name) outgoing_profile-\>caller_id_name = ecid_name; if (ecid_num) outgoing_profile-\>caller_id_number = ecid_num; } } // 5. 调用endpoint的outgoing_channel回调 cause = endpoint_interface-\>io_routines-\>outgoing_channel( session, var_event, outgoing_profile, new_session, pool, flags, cancel_cause); if (cause != SWITCH_CAUSE_SUCCESS) { UNPROTECT_INTERFACE(endpoint_interface); return cause; } // 6. 设置会话属性 switch_channel_t \*peer_channel = switch_core_session_get_channel(\*new_session); // 设置UUID if ((use_uuid = switch_event_get_header(var_event, "origination_uuid"))) { switch_core_session_set_uuid(\*new_session, use_uuid); } // 设置External ID if ((use_external_id = switch_event_get_header(var_event, "origination_external_id"))) { switch_channel_set_external_id(peer_channel, use_external_id); } // 7. 设置通道变量 if (var_event) { switch_event_header_t \*header; for (header = var_event-\>headers; header; header = header-\>next) { if (!strncasecmp(header-\>name, "originate_", 10)) { char \*var_name = header-\>name + 10; switch_channel_set_variable(peer_channel, var_name, header-\>value); } } } // 8. 设置Caller Profile profile = switch_caller_profile_clone(\*new_session, caller_profile); switch_channel_set_caller_profile(peer_channel, profile); switch_channel_set_originator_caller_profile(peer_channel, switch_channel_get_caller_profile(channel)); // 9. 触发outgoing_channel hooks for (ptr = session-\>event_hooks.outgoing_channel; ptr; ptr = ptr-\>next) { if (ptr-\>outgoing_channel(session, var_event, caller_profile, \*new_session, flags) != SWITCH_STATUS_SUCCESS) { break; } } // 10. 触发CHANNEL_OUTGOING事件 if (switch_event_create(\&event, SWITCH_EVENT_CHANNEL_OUTGOING) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(peer_channel, event); switch_event_fire(\&event); } return SWITCH_CAUSE_SUCCESS; } \`\`\` --- ### 2.4 阶段四:Endpoint层(Sofia SIP) #### 函数:\`sofia_outgoing_channel()\` \*\*文件位置:\*\* \`src/mod/endpoints/mod_sofia/mod_sofia.c:4719\` \`\`\`c static switch_call_cause_t sofia_outgoing_channel( switch_core_session_t \*session, switch_event_t \*var_event, switch_caller_profile_t \*outbound_profile, switch_core_session_t \*\*new_session, switch_memory_pool_t \*\*pool, switch_originate_flag_t flags, switch_call_cause_t \*cancel_cause) { switch_call_cause_t cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; switch_core_session_t \*nsession = NULL; char \*profile_name, \*dest; sofia_profile_t \*profile = NULL; private_object_t \*tech_pvt = NULL; switch_channel_t \*nchannel; char \*host = NULL, \*dest_to = NULL; sofia_gateway_t \*gateway_ptr = NULL; \*new_session = NULL; // 1. 验证目标 if (!outbound_profile \|\| zstr(outbound_profile-\>destination_number)) { switch_log_printf(..., "Invalid Empty Destination\\n"); goto error; } // 2. URL编码处理 if (!switch_true(switch_event_get_header(var_event, "sofia_suppress_url_encoding"))) { protect_dest_uri(outbound_profile); } // 3. 创建新会话 nsession = switch_core_session_request_uuid( sofia_endpoint_interface, SWITCH_CALL_DIRECTION_OUTBOUND, flags, pool, switch_event_get_header(var_event, "origination_uuid")); if (!nsession) { switch_log_printf(..., "Error Creating Session\\n"); goto error; } // 4. 创建私有对象 tech_pvt = sofia_glue_new_pvt(nsession); // 5. 解析目标字符串 // 格式: profile/dest 或 gateway/gw_name/dest data = switch_core_session_strdup(nsession, outbound_profile-\>destination_number); if ((dest_to = strchr(data, '\^'))) { \*dest_to++ = '\\0'; // \^ 分隔To URI } profile_name = data; nchannel = switch_core_session_get_channel(nsession); // 6. 处理Gateway呼叫 if (!strncasecmp(profile_name, "gateway/", 8)) { char \*gw, \*params; gw = profile_name + 8; if (!(dest = strchr(gw, '/'))) { cause = SWITCH_CAUSE_INVALID_URL; goto error; } \*dest++ = '\\0'; // 查找gateway if (!(gateway_ptr = sofia_reg_find_gateway(gw)) \|\| !gateway_ptr-\>profile) { switch_log_printf(..., "Invalid Gateway '%s'\\n", gw); cause = SWITCH_CAUSE_INVALID_GATEWAY; goto error; } profile = gateway_ptr-\>profile; // 检查gateway状态 if (gateway_ptr-\>status != SOFIA_GATEWAY_UP) { switch_log_printf(..., "Gateway '%s' is down!\\n", gw); cause = SWITCH_CAUSE_GATEWAY_DOWN; gateway_ptr-\>ob_failed_calls++; goto error; } tech_pvt-\>transport = gateway_ptr-\>register_transport; tech_pvt-\>gateway_name = switch_core_session_strdup(nsession, gateway_ptr-\>name); // 构造目标URI if (!strchr(dest, '@')) { tech_pvt-\>dest = switch_core_session_sprintf(nsession, "sip:%s%s@%s", gateway_ptr-\>destination_prefix, dest, sofia_glue_strip_proto(gateway_ptr-\>register_proxy)); } else { tech_pvt-\>dest = switch_core_session_sprintf(nsession, "sip:%s%s", gateway_ptr-\>destination_prefix, dest); } tech_pvt-\>invite_contact = switch_core_session_strdup(nsession, gateway_ptr-\>register_contact); gateway_ptr-\>ob_calls++; // From domain处理 if (!zstr(gateway_ptr-\>from_domain)) { if (!strcasecmp(gateway_ptr-\>from_domain, "auto-aleg-full")) { // 复制A-leg的完整From const char \*sip_full_from = switch_channel_get_variable( o_channel, "sip_full_from"); if (!zstr(sip_full_from)) { switch_channel_set_variable(nchannel, "sip_force_full_from", sip_full_from); } } else if (!strcasecmp(gateway_ptr-\>from_domain, "auto-aleg-domain")) { // 复制A-leg的domain const char \*sip_from_host = switch_channel_get_variable( o_channel, "sip_from_host"); if (!zstr(sip_from_host)) { switch_channel_set_variable(nchannel, "sip_invite_domain", sip_from_host); } } else { switch_channel_set_variable(nchannel, "sip_invite_domain", gateway_ptr-\>from_domain); } } } // 7. 处理Profile呼叫 else { if (!(dest = strchr(profile_name, '/'))) { cause = SWITCH_CAUSE_INVALID_URL; goto error; } \*dest++ = '\\0'; // 查找profile if (!(profile = sofia_glue_find_profile(profile_name))) { switch_log_printf(..., "Invalid Profile\\n"); cause = SWITCH_CAUSE_INVALID_PROFILE; goto error; } // 检查profile状态 if (sofia_test_pflag(profile, PFLAG_STANDBY)) { switch_log_printf(..., "System Paused\\n"); cause = SWITCH_CAUSE_SYSTEM_SHUTDOWN; goto error; } // 构造目标URI if (strchr(dest, '@')) { tech_pvt-\>dest = switch_core_session_sprintf(nsession, "sip:%s", dest); } else { tech_pvt-\>dest = switch_core_session_sprintf(nsession, "sip:%s@%s", dest, profile-\>sipip); } // 设置transport const char \*transport = switch_event_get_header(var_event, "sip_transport"); if (transport) { tech_pvt-\>transport = sofia_glue_str2transport(transport); } } // 8. 设置To URI if (dest_to) { tech_pvt-\>dest_to = switch_core_session_sprintf(nsession, "sip:%s", dest_to); } else { tech_pvt-\>dest_to = tech_pvt-\>dest; } // 9. 提取host if ((host = switch_core_session_strdup(nsession, tech_pvt-\>dest))) { char \*pp = strchr(host, '@'); if (pp) { host = pp + 1; } } // 10. 附加到profile sofia_glue_attach_private(nsession, profile, tech_pvt, dest); // 11. 设置通道名称 char name\[256\]; switch_snprintf(name, sizeof(name), "sofia/%s/%s", profile-\>name, tech_pvt-\>dest); switch_channel_set_name(nchannel, name); // 12. 设置Caller Profile caller_profile = switch_caller_profile_clone(nsession, outbound_profile); switch_channel_set_caller_profile(nchannel, caller_profile); tech_pvt-\>caller_profile = caller_profile; // 13. 处理SIP headers const char \*alert_info = switch_event_get_header(var_event, "sip_h_alert-info"); if (alert_info) { tech_pvt-\>alert_info = switch_core_session_strdup(nsession, alert_info); } // 处理所有 sip_h_ 开头的变量 switch_event_header_t \*hi; for (hi = var_event-\>headers; hi; hi = hi-\>next) { if (!strncasecmp(hi-\>name, "sip_h_", 6)) { char \*header_name = hi-\>name + 6; sofia_glue_set_extra_headers(nsession, header_name, hi-\>value); } } // 14. 准备媒体 if (switch_channel_test_flag(nchannel, CF_PROXY_MEDIA)) { // 代理模式,不需要本地媒体 sofia_glue_tech_set_local_sdp(tech_pvt, NULL, SWITCH_FALSE); } else { // 准备本地SDP sofia_glue_tech_prepare_codecs(nsession); sofia_glue_set_local_sdp(tech_pvt, NULL, 0, NULL, 0); } // 15. 设置会话状态 switch_channel_set_state(nchannel, CS_INIT); // 16. 设置flag switch_channel_set_flag(nchannel, CF_OUTBOUND); switch_set_flag(tech_pvt, TFLAG_OUTBOUND); // 17. 返回新会话 \*new_session = nsession; cause = SWITCH_CAUSE_SUCCESS; // 18. 启动会话线程(将在此线程中发送INVITE) switch_core_session_thread_launch(nsession); return cause; error: if (nsession) { switch_core_session_destroy(\&nsession); } if (profile) { sofia_glue_release_profile(profile); } if (gateway_ptr) { sofia_reg_release_gateway(gateway_ptr); } return cause; } \`\`\` --- ### 2.5 阶段五:SIP INVITE发送 #### 函数:\`sofia_glue_do_invite()\` \*\*文件位置:\*\* \`src/mod/endpoints/mod_sofia/sofia_glue.c:1614\` \`\`\`c void sofia_glue_do_invite(switch_core_session_t \*session) { private_object_t \*tech_pvt = switch_core_session_get_private(session); switch_channel_t \*channel = switch_core_session_get_channel(session); const char \*call_id = NULL; const char \*session_id_header = NULL; const char \*alert_info = NULL; const char \*extra_headers = NULL; const char \*max_forwards = NULL; const char \*route_uri = NULL; const char \*route = NULL; const char \*invite_route_uri = NULL; const char \*handle_full_from = NULL; const char \*handle_full_to = NULL; uint32_t session_timeout = 0; sip_cseq_t \*cseq = NULL; // 1. 准备Call-ID if ((call_id = switch_channel_get_variable(channel, "sip_outgoing_call_id"))) { nua_handle_set_param(tech_pvt-\>nh, NUTAG_CALLID(call_id), TAG_END()); } // 2. 准备Session-Timer if ((val = switch_channel_get_variable(channel, "sip_session_timeout"))) { session_timeout = atoi(val); } // 3. 准备Contact const char \*contact_url = tech_pvt-\>invite_contact; if (!contact_url) { contact_url = sofia_glue_get_profile_url(...); } // 4. 准备Route header if ((val = switch_channel_get_variable(channel, "sip_route_uri"))) { route_uri = switch_core_session_strdup(session, val); } // 5. 准备CSeq if ((val = switch_channel_get_variable(channel, "sip_invite_cseq"))) { uint32_t callsequence = (uint32_t)strtoul(val, NULL, 10); cseq = sip_cseq_create(nua_handle_get_home(tech_pvt-\>nh), callsequence, SIP_METHOD_INVITE); } // 6. 清除标志 switch_channel_clear_flag(channel, CF_MEDIA_ACK); // 7. 设置Session Refresher if (session_timeout) { tech_pvt-\>session_refresher = switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND ? nua_local_refresher : nua_remote_refresher; } // 8. 记录日志 switch_log_printf(..., INFO, "%s sending invite call-id: %s\\n", switch_channel_get_name(channel), call_id); switch_log_printf(..., DEBUG, "%s sending invite version: %s\\n" "Local SDP:\\n%s\\n", switch_channel_get_name(channel), switch_version_full_human(), tech_pvt-\>mparams.local_sdp_str); // 9. 发送INVITE if (has_multipart) { // 带multipart的INVITE nua_invite(tech_pvt-\>nh, SIPTAG_CONTENT_TYPE_STR(mp_type), SIPTAG_PAYLOAD_STR(mp), TAG_IF(invite_full_from, SIPTAG_FROM_STR(invite_full_from)), TAG_IF(invite_full_to, SIPTAG_TO_STR(invite_full_to)), TAG_IF(!zstr(route_uri), NUTAG_PROXY(route_uri)), TAG_IF(!zstr(route), SIPTAG_ROUTE_STR(route)), TAG_IF(cseq, SIPTAG_CSEQ(cseq)), NUTAG_AUTOANSWER(0), NUTAG_AUTOACK(0), NUTAG_SESSION_TIMER(session_timeout), TAG_END()); } else { // 普通INVITE nua_invite(tech_pvt-\>nh, NUTAG_AUTOANSWER(0), NUTAG_AUTOACK(0), NUTAG_SESSION_TIMER(session_timeout), NUTAG_SESSION_REFRESHER(tech_pvt-\>session_refresher), NUTAG_UPDATE_REFRESH(tech_pvt-\>update_refresher), TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_IF(invite_full_from, SIPTAG_FROM_STR(invite_full_from)), TAG_IF(invite_full_to, SIPTAG_TO_STR(invite_full_to)), TAG_IF(tech_pvt-\>redirected, NUTAG_URL(tech_pvt-\>redirected)), TAG_IF(!zstr(tech_pvt-\>user_via), SIPTAG_VIA_STR(tech_pvt-\>user_via)), TAG_IF(!zstr(tech_pvt-\>rpid), SIPTAG_REMOTE_PARTY_ID_STR(tech_pvt-\>rpid)), TAG_IF(!zstr(tech_pvt-\>preferred_id), SIPTAG_P_PREFERRED_IDENTITY_STR(tech_pvt-\>preferred_id)), TAG_IF(!zstr(tech_pvt-\>asserted_id), SIPTAG_P_ASSERTED_IDENTITY_STR(tech_pvt-\>asserted_id)), TAG_IF(!zstr(tech_pvt-\>privacy), SIPTAG_PRIVACY_STR(tech_pvt-\>privacy)), TAG_IF(!zstr(alert_info), SIPTAG_HEADER_STR(alert_info)), TAG_IF(!zstr(extra_headers), SIPTAG_HEADER_STR(extra_headers)), TAG_IF(!zstr(max_forwards), SIPTAG_MAX_FORWARDS_STR(max_forwards)), TAG_IF(!zstr(route_uri), NUTAG_PROXY(route_uri)), TAG_IF(!zstr(route), SIPTAG_ROUTE_STR(route)), TAG_IF(!zstr(invite_route_uri), NUTAG_INITIAL_ROUTE_STR(invite_route_uri)), TAG_IF(cseq, SIPTAG_CSEQ(cseq)), SOATAG_ADDRESS(tech_pvt-\>mparams.adv_sdp_audio_ip), SOATAG_USER_SDP_STR(tech_pvt-\>mparams.local_sdp_str), SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE), SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL), TAG_END()); } // 10. 设置标志 sofia_set_flag(tech_pvt, TFLAG_SENT_INVITE); } \`\`\` #### SIP INVITE消息示例 \`\`\` INVITE sip:1234@192.168.1.100:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.1.10:5080;rport;branch=z9hG4bKBKQ82BeDy1K2e Max-Forwards: 70 From: "Alice" \;tag=8a9yB1jjp7g0c To: \ Call-ID: 3c26700b-5f0c-1243-b0db-5ef03d8ad0e8 CSeq: 123456789 INVITE Contact: \ User-Agent: FreeSWITCH-mod_sofia/1.10.12 Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY Supported: timer, path, replaces Allow-Events: talk, hold, conference, refer Content-Type: application/sdp Content-Disposition: session Content-Length: 342 v=0 o=FreeSWITCH 1734567890 1734567891 IN IP4 192.168.1.10 s=FreeSWITCH c=IN IP4 192.168.1.10 t=0 0 m=audio 16384 RTP/AVP 0 8 101 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-16 a=ptime:20 a=sendrecv \`\`\` --- ### 2.6 阶段六:状态机处理 #### 会话状态转换 \`\`\` CS_NEW ↓ CS_INIT (发送CHANNEL_CREATE和CHANNEL_ORIGINATE事件) ↓ CS_ROUTING (呼出会话跳过routing,直接到CONSUME_MEDIA) ↓ CS_CONSUME_MEDIA (等待应答,处理early media) ↓ 收到180 Ringing → 产生CHANNEL_PROGRESS事件 收到183 Session Progress → 开始early media 收到200 OK → 应答 ↓ CS_EXECUTE (应答后,执行dialplan或application) ↓ CS_HANGUP ↓ CS_REPORTING ↓ CS_DESTROY \`\`\` #### 状态处理函数 \*\*文件位置:\*\* \`src/switch_core_state_machine.c:528\` \`\`\`c SWITCH_DECLARE(void) switch_core_session_run(switch_core_session_t \*session) { switch_channel_state_t state = CS_NEW; const switch_endpoint_interface_t \*endpoint_interface; const switch_state_handler_table_t \*driver_state_handler = NULL; const switch_state_handler_table_t \*application_state_handler = NULL; endpoint_interface = session-\>endpoint_interface; driver_state_handler = endpoint_interface-\>state_handler; while ((state = switch_channel_get_state(session-\>channel)) != CS_DESTROY) { switch (state) { case CS_NEW: switch_log_printf(..., DEBUG, "(%s) State NEW\\n", switch_channel_get_name(session-\>channel)); break; case CS_INIT: { switch_event_t \*event; // 调用driver的on_init STATE_MACRO(init, "INIT"); // 发送CHANNEL_CREATE事件 if (switch_event_create(\&event, SWITCH_EVENT_CHANNEL_CREATE) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(session-\>channel, event); switch_event_fire(\&event); } // 如果是outbound,发送CHANNEL_ORIGINATE事件 if (switch_channel_direction(session-\>channel) == SWITCH_CALL_DIRECTION_OUTBOUND) { if (switch_event_create(\&event, SWITCH_EVENT_CHANNEL_ORIGINATE) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(session-\>channel, event); switch_event_fire(\&event); } } } break; case CS_ROUTING: STATE_MACRO(routing, "ROUTING"); break; case CS_CONSUME_MEDIA: // 呼出会话在此状态等待应答 STATE_MACRO(consume_media, "CONSUME_MEDIA"); break; case CS_EXECUTE: STATE_MACRO(execute, "EXECUTE"); break; case CS_HANGUP: { switch_core_session_hangup_state(session, SWITCH_TRUE); switch_channel_set_state(session-\>channel, CS_REPORTING); } break; case CS_REPORTING: { switch_core_session_reporting_state(session); switch_channel_set_state(session-\>channel, CS_DESTROY); } goto done; case CS_DESTROY: goto done; } } done: switch_log_printf(..., DEBUG, "(%s) State Change CS_DESTROY\\n", switch_channel_get_name(session-\>channel)); } \`\`\` #### Originate State Handler \*\*文件位置:\*\* \`src/switch_ivr_originate.c:61\` \`\`\`c static switch_status_t originate_on_routing(switch_core_session_t \*session) { switch_channel_t \*channel = switch_core_session_get_channel(session); if (switch_channel_get_state(channel) == CS_ROUTING) { // 呼出会话不需要routing,直接进入CONSUME_MEDIA等待应答 switch_channel_set_state(channel, CS_CONSUME_MEDIA); } return SWITCH_STATUS_FALSE; } static switch_status_t originate_on_consume_media_transmit( switch_core_session_t \*session) { switch_channel_t \*channel = switch_core_session_get_channel(session); if (!switch_channel_test_flag(channel, CF_PROXY_MODE) \&\& switch_channel_test_flag(channel, CF_CONSUME_ON_ORIGINATE)) { // 在originating期间处理media while (switch_channel_test_flag(channel, CF_ORIGINATING) \&\& switch_channel_get_state(channel) == CS_CONSUME_MEDIA) { if (!switch_channel_media_ready(channel)) { switch_yield(10000); } else { switch_ivr_sleep(session, 10, SWITCH_FALSE, NULL); } switch_ivr_parse_all_messages(session); } } switch_channel_clear_state_handler(channel, \&originate_state_handlers); return SWITCH_STATUS_FALSE; } \`\`\` --- ### 2.7 阶段七:SIP响应处理 #### Sofia事件处理 \*\*文件位置:\*\* \`src/mod/endpoints/mod_sofia/sofia.c\` ##### 处理100 Trying \`\`\`c case nua_r_invite: if (status == 100) { // 100 Trying - 暂时不做什么 switch_log_printf(..., DEBUG, "(%s) Received 100 Trying\\n", switch_channel_get_name(channel)); } \`\`\` ##### 处理180 Ringing \`\`\`c else if (status == 180 \|\| status == 183) { // 180 Ringing or 183 Session Progress if (status == 180) { // Ringing switch_channel_mark_ring_ready(channel); // 发送CHANNEL_PROGRESS事件 if (switch_event_create(\&event, SWITCH_EVENT_CHANNEL_PROGRESS) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(channel, event); switch_event_fire(\&event); } // 设置answered变量(为"ringing") switch_channel_set_variable(channel, "endpoint_disposition", "RINGING"); } if (status == 183) { // Session Progress (early media) const sdp_session_t \*sdp; const sdp_connection_t \*connection; const sdp_media_t \*m; // 处理SDP if (sip-\>sip_payload \&\& sip-\>sip_payload-\>pl_data) { tech_pvt-\>remote_sdp_str = switch_core_session_strdup( session, sip-\>sip_payload-\>pl_data); // 协商媒体 if (sofia_glue_tech_media(tech_pvt, sip-\>sip_payload-\>pl_data) == SWITCH_STATUS_SUCCESS) { // 开始early media switch_channel_mark_pre_answered(channel); // 发送CHANNEL_PROGRESS_MEDIA事件 if (switch_event_create(\&event, SWITCH_EVENT_CHANNEL_PROGRESS_MEDIA) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(channel, event); switch_event_fire(\&event); } } } } } \`\`\` ##### 处理200 OK \`\`\`c else if (status \>= 200 \&\& status \< 300) { // 200 OK - 应答 const sdp_session_t \*sdp; // 停止振铃音 switch_channel_stop_broadcast(channel); // 处理SDP if (sip-\>sip_payload \&\& sip-\>sip_payload-\>pl_data) { tech_pvt-\>remote_sdp_str = switch_core_session_strdup( session, sip-\>sip_payload-\>pl_data); // 协商媒体 if (sofia_glue_tech_media(tech_pvt, sip-\>sip_payload-\>pl_data) == SWITCH_STATUS_SUCCESS) { // 标记为已应答 switch_channel_mark_answered(channel); // 发送CHANNEL_ANSWER事件 if (switch_event_create(\&event, SWITCH_EVENT_CHANNEL_ANSWER) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(channel, event); switch_event_fire(\&event); } // 设置answered变量 switch_channel_set_variable(channel, "endpoint_disposition", "ANSWER"); // 记录应答时间 switch_channel_set_variable_printf(channel, "sip_invite_response_time", "%d", (int)(switch_micro_time_now() - tech_pvt-\>invite_time)); } } // 发送ACK nua_ack(tech_pvt-\>nh, TAG_END()); sofia_set_flag(tech_pvt, TFLAG_GOT_ACK); // 改变状态到EXECUTE(将执行dialplan) switch_channel_set_state(channel, CS_EXECUTE); } \`\`\` ##### 处理4xx/5xx/6xx错误 \`\`\`c else if (status \>= 300) { // 失败响应 switch_call_cause_t cause = sofia_glue_sip_cause_to_freeswitch(status); switch_log_printf(..., DEBUG, "(%s) Outgoing Call Failed with status %d (%s)\\n", switch_channel_get_name(channel), status, switch_channel_cause2str(cause)); // 设置hangup cause switch_channel_hangup(channel, cause); // 记录失败原因 switch_channel_set_variable(channel, "endpoint_disposition", switch_channel_cause2str(cause)); switch_channel_set_variable_printf(channel, "sip_term_status", "%d", status); // 保存SIP原因短语 if (sip-\>sip_status \&\& sip-\>sip_status-\>st_phrase) { switch_channel_set_variable(channel, "sip_term_phrase", sip-\>sip_status-\>st_phrase); } } break; \`\`\` --- ## 三、关键数据结构 ### 3.1 originate_global_t \`\`\`c typedef struct { switch_core_session_t \*session; // 发起会话(A-leg) switch_memory_pool_t \*pool; // 内存池 switch_caller_profile_t \*caller_profile_override; originate_status_t originate_status\[MAX_PEERS\]; // 每个peer的状态 int idx; // 应答的peer索引 uint32_t hups; // 挂断计数 char \*file; // 播放文件 char \*error_file; // 错误文件 int ring_ready; // 振铃标志 int early_ok; // early media标志 int answer_state; // 应答状态 switch_mutex_t \*mutex; // 互斥锁 switch_call_cause_t cause; // 失败原因 } originate_global_t; \`\`\` ### 3.2 private_object_t (Sofia) \`\`\`c struct private_object { switch_core_session_t \*session; // 会话指针 switch_channel_t \*channel; // 通道指针 sofia_profile_t \*profile; // SIP profile char \*dest; // 目标URI char \*dest_to; // To URI char \*redirected; // 重定向URI char \*gateway_name; // Gateway名称 char \*gateway_from_str; // Gateway From字符串 nua_handle_t \*nh; // NUA handle nua_handle_t \*nh2; // 第二个NUA handle char \*invite_contact; // INVITE Contact char \*rpid; // Remote-Party-ID char \*preferred_id; // P-Preferred-Identity char \*asserted_id; // P-Asserted-Identity char \*privacy; // Privacy header sofia_transport_t transport; // 传输协议 uint32_t session_timeout; // Session Timer nua_session_refresher_t session_refresher; // Refresher int update_refresher; // Update refresher标志 switch_payload_t te; // DTMF发送payload switch_payload_t recv_te; // DTMF接收payload sofia_cid_type_t cid_type; // Caller-ID类型 switch_mutex_t \*flag_mutex; // 标志互斥锁 switch_mutex_t \*sofia_mutex; // Sofia互斥锁 uint32_t flags; // 各种标志位 switch_media_handle_t \*media_handle; // 媒体句柄 sofia_media_params_t mparams; // 媒体参数 }; \`\`\` --- ## 四、时序图 ### 4.1 成功呼出时序 \`\`\` User/App mod_commands switch_ivr_originate Core Session mod_sofia Sofia-SIP 远端 \| \| \| \| \| \| \| \|--originate--------\>\| \| \| \| \| \| \| \|--switch_ivr_ \| \| \| \| \| \| \| originate()-------\>\| \| \| \| \| \| \| \|--parse dial string \| \| \| \| \| \| \|--create profiles---\>\| \| \| \| \| \| \| \|--outgoing_ \| \| \| \| \| \| \| channel()----\>\| \| \| \| \| \| \| \|--create \| \| \| \| \| \| \| session \| \| \| \| \| \| \|--prepare SDP \| \| \| \| \| \| \| \| \| \| \| \| \| \|--launch \| \| \| \| \| \| \| thread \| \| \| \| \| \|\<----------------\| \| \| \| \| \|\<--------------------\| \| \| \| \| \| \|--monitor progress--\>\| \| \| \| \| \| \| \| \[State: CS_INIT\] \| \| \| \| \| \| \| \| \| \| \| \| \| \[State: CS_ROUTING\] \| \| \| \| \| \| \| \| \| \| \| \| \| \[State: CS_CONSUME_MEDIA\] \| \| \| \| \| \| \|--nua_invite()--\| \| \| \| \| \| \| \|--INVITE-----\>\| \| \| \| \| \| \| \| \| \| \| \| \| \|\<--100 Trying-\| \| \| \| \| \|\<---------------\| \| \| \| \| \|\<--nua_r_invite--\| \| \| \| \| \| \| \| \| \| \| \| \| \| \| \|\<--180 Ring---\| \| \| \| \| \|\<---------------\| \| \| \| \|\<--PROGRESS----------\|\<--nua_r_invite--\| \| \| \|\<--ring ready-------\|\<---------------------\| \| \| \| \| \| \| \| \| \| \| \| \| \| \| \| \| \|\<--200 OK-----\| \| \| \| \| \|\<---------------\| \| \| \| \|\<--ANSWERED----------\|\<--nua_r_invite--\| \| \| \|\<--success----------\|\<---------------------\| \| \[mark answered\] \| \| \| \| \| \| \|--nua_ack()----\>\|--ACK--------\>\| \| \| \| \| \[State: CS_EXECUTE\] \| \| \| \| \| \| \| \| \| \| \| \| \| \|\<======RTP=====\>\|\<====RTP=====\>\| \| \| \| \| \| \| \| \`\`\` ### 4.2 并发呼叫时序 \`\`\` switch_ivr_originate Peer1 Peer2 Peer3 \| \| \| \| \|--create session------\>\| \| \| \|--create session------------------------\>\| \| \|--create session------------------------------------------\>\| \| \| \| \| \| \|--INVITE--------\>\| \| \| \| \|--INVITE--------\>\| \| \| \| \|--INVITE--------\> \| \|\<--180 Ringing---\| \| \|\<--PROGRESS------------\| \| \| \| \| \|\<--180 Ringing---\| \| \| \| \|\<--486 Busy------\| \| \| \|\<--200 OK--------\| (先应答) \|\<--ANSWERED----------------------------\| (获胜) \| \| \| \| \| \|--CANCEL--------------\>\| \| \| \| \|--CANCEL--------\>\| \| \| \|\<--487 Cancelled-\| \| \| \| \| \| \|--返回Peer2-----------\>\| \| \| \`\`\` --- ## 五、常见问题和优化 ### 5.1 呼叫超时处理 #### 设置超时 \`\`\`xml \

相关推荐
秃了才能变得更强2 小时前
React Native小技巧
前端
一只爱吃糖的小羊2 小时前
React 19 vs Vue 3:深度对比与选型指南
前端·vue.js·react.js
前端老宋Running2 小时前
Vue 3 的“降维打击”:Composition API 是如何让 Mixin 成为历史文物的?
前端·javascript·vue.js
随机昵称_1234562 小时前
RSA私钥解密乱码问题
java·非对称加密
Pluto_CRown2 小时前
H5 开发的各类小知识点
前端·javascript
Pluto_CRown2 小时前
上下文存储【下】
前端·javascript
AAA阿giao2 小时前
JavaScript 中基于原型和原型链的继承方式详解
前端·javascript·面试
龙亘川2 小时前
【课程2.4】开发环境搭建:K8s集群部署、芋道框架集成、ThingsBoard对接
java·容器·kubernetes·智慧城市·智慧城市一网统管 ai 平台
用户600071819102 小时前
【翻译】如何在Vue中使用Suspense处理异步渲染?
前端