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的呼出流程是一个复杂但设计精良的系统:
-
**分层架构**:从API层到SIP协议栈,每层职责清晰
-
**灵活性**:支持多种呼叫模式(串行、并发、混合)
-
**可扩展性**:通过endpoint接口支持多种协议
-
**状态管理**:完善的状态机确保会话可控
-
**事件驱动**:丰富的事件系统便于监控和集成
理解这个流程对于:
-
调试呼叫问题
-
优化呼叫性能
-
开发自定义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事件处理