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" \