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 = argvi++; // 呼叫目标 (如 "sofia/internal/1000@domain")

exten = argvi++; // 应答后执行的extension

dp = argvi++; // dialplan类型

context = argvi++; // context

cid_name = argvi++; // 主叫名称

cid_num = argvi++; // 主叫号码

timeout = argv6; // 超时时间

// 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=20sofia/gw/provider1/12345,

leg_timeout=30sofia/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_namesr, ',', ...);

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_channelsi = switch_core_session_get_channel(peer_session);

oglobals.originate_statusi.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_channelsi) {

switch_channel_hangup(peer_channelsi,

SWITCH_CAUSE_ORIGINATOR_CANCEL);

}

}

} else {

// 返回应答的呼叫

*bleg = oglobals.originate_statusoglobals.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=10sofia/gw1/123,sofia/gw2/123|leg_timeout=15sofia/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_channelsi); 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 name256;

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" <sip:1000@domain.com>;tag=8a9yB1jjp7g0c

To: <sip:1234@192.168.1.100>

Call-ID: 3c26700b-5f0c-1243-b0db-5ef03d8ad0e8

CSeq: 123456789 INVITE

Contact: <sip:1000@192.168.1.10:5080;transport=udp>

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_statusMAX_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

<!-- Dialplan -->

<action application="set" data="call_timeout=30"/>

<action application="set" data="originate_timeout=60"/>

<action application="bridge" data="sofia/external/1234@provider.com"/>

```

```bash

API

originate {originate_timeout=30}sofia/external/1234@provider.com &park()

```

超时类型

| 变量 | 说明 | 默认值 |

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

| `call_timeout` | 整个呼叫的超时(秒) | 60 |

| `originate_timeout` | originate操作超时 | 60 |

| `leg_timeout` | 单个leg的超时 | - |

| `originate_continue_on_timeout` | 超时后继续 | false |

5.2 Early Media处理

忽略Early Media

```xml

<action application="set" data="ignore_early_media=true"/>

<action application="bridge" data="sofia/external/1234@provider.com"/>

```

消费Early Media

```xml

<action application="set" data="ringback=${us-ring}"/>

<action application="bridge" data="{ignore_early_media=true}sofia/external/1234@provider.com"/>

```

5.3 并发呼叫优化

Enterprise Originate

```xml

<!-- 同时呼叫多个用户,第一个应答的获胜 -->

<action application="bridge" data="user/1001,user/1002,user/1003"/>

```

Leg Timeout

```xml

<!-- 每个leg设置不同的超时 -->

<action application="bridge" data="leg_timeout=10sofia/gw1/123,leg_timeout=20sofia/gw2/123"/>

```

Failover

```xml

<!-- 按顺序呼叫 -->

<action application="bridge" data="sofia/gw1/123|sofia/gw2/123|sofia/gw3/123"/>

```

5.4 性能优化

1. 使用线程池

```xml

<!-- switch.conf.xml -->

<param name="sessions-per-second" value="100"/>

<param name="max-sessions" value="1000"/>

```

2. 异步Originate

```bash

使用bgapi进行异步呼叫

bgapi originate sofia/external/1234@provider.com &park()

```

3. 批量Originate

```python

Python ESL example

for number in numbers:

con.bgapi("originate", f"sofia/external/{number}@provider.com &park()")

```


六、调试技巧

6.1 日志分析

启用详细日志

```bash

Sofia SIP日志

fs_cli -x "sofia global siptrace on"

fs_cli -x "sofia loglevel all 9"

核心日志

fs_cli -x "console loglevel debug"

```

关键日志点

```

呼出开始

DEBUG switch_ivr_originate.c: Parsing session specific variables

DEBUG mod_sofia.c: sofia_outgoing_channel()

DEBUG sofia_glue.c: sofia_glue_do_invite() sending invite

收到响应

DEBUG sofia.c: Received 100 Trying

DEBUG sofia.c: Received 180 Ringing

DEBUG sofia.c: Received 200 OK

应答

NOTICE switch_core_state_machine.c: Channel sofia/... has been answered

```

6.2 SIP抓包

```bash

使用tcpdump

tcpdump -i any -s 0 -w /tmp/sip.pcap port 5060

使用ngrep

ngrep -q -W byline port 5060

使用Sofia内置抓包

fs_cli -x "sofia global siptrace on"

```

6.3 通道变量检查

```bash

查看所有通道

fs_cli -x "show channels"

查看特定通道详情

fs_cli -x "uuid_dump <uuid>"

查看通道变量

fs_cli -x "uuid_getvar <uuid> variable_name"

```


七、总结

FreeSWITCH的呼出流程是一个复杂但设计精良的系统:

  1. **分层架构**:从API层到SIP协议栈,每层职责清晰

  2. **灵活性**:支持多种呼叫模式(串行、并发、混合)

  3. **可扩展性**:通过endpoint接口支持多种协议

  4. **状态管理**:完善的状态机确保会话可控

  5. **事件驱动**:丰富的事件系统便于监控和集成

理解这个流程对于:

  • 调试呼叫问题

  • 优化呼叫性能

  • 开发自定义endpoint

  • 集成第三方系统

都至关重要。


**相关源代码文件:**

  • `src/mod/applications/mod_commands/mod_commands.c` - API命令

  • `src/switch_ivr_originate.c` - IVR层呼出逻辑

  • `src/switch_core_session.c` - 核心会话管理

  • `src/switch_core_state_machine.c` - 状态机

  • `src/mod/endpoints/mod_sofia/mod_sofia.c` - Sofia endpoint

  • `src/mod/endpoints/mod_sofia/sofia_glue.c` - Sofia辅助函数

  • `src/mod/endpoints/mod_sofia/sofia.c` - SIP事件处理

相关推荐
xiaofeichaichai1 小时前
Webpack
前端·webpack·node.js
问心无愧05131 小时前
ctf show web入门111
android·前端·笔记
唐某人丶1 小时前
模型越来越强,我们还需要 Agent 工程吗?—— 从价值重估到 Harness 实践
前端·agent·ai编程
智码看视界2 小时前
现代Web开发基础:全栈工程师的起航点
前端·后端·c5全栈
JS菌2 小时前
手写一个 AI Agent 全栈项目:从沙箱执行到子智能体的完整实现
前端·人工智能·后端
wang09072 小时前
自己动手写一个spring之IOC_2
java·后端·spring
来杯@Java3 小时前
学生选课管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·maven·mybatis
excel3 小时前
HLS TS 文件损坏的元凶:Git 提交与拉取
前端