FreeSWITCH 1.10 源码阅读(5)-外呼命令 originate 单腿呼出的处理

文章目录

  • 1. 前言
  • 2. 源码分析
      • 2.1 user 端点对拨号字符串的转化
      • 2.2 sofia 端点的外呼处理

1. 前言

在 FreeSWITCH 1.10 源码阅读(4)-从呼入处理分析核心状态机 中笔者大致分析了上层状态机流转推进呼叫进程的处理过程,FreeSWITCH 中外呼和呼入的处理相当类似,但也有许多细节的区别,其单腿呼出时 channel 状态的流转如下图所示:

在这里插入图片描述

2. 源码分析

originate 外呼命令的源码时序如下,图中状态机流转的部分步骤有所省略,读者可结合 FreeSWITCH 1.10 源码阅读(4)-从呼入处理分析核心状态机 中的内容补充理解。从源码来看,originate 呼叫一个内部注册用户的流程大致分为以下几个部分,笔者将据此进行分析:

  1. user 端点对注册用户拨号字符串的转化
  2. sofia 端点的外呼处理

在这里插入图片描述

2.1 user 端点对拨号字符串的转化

  1. 以外呼命令 originate user/1000 10086 XML default 为例,FreeSWITCH 接收到该命令时会去核心查找命令的实现函数并触发其执行,则 mod_commands.c#SWITCH_STANDARD_API(originate_function) 函数将被调用,可以看到其关键处理如下:

    1. 解析获取命令参数,如未指定拨号计划类型则默认使用 XML 类型
    2. 调用 switch_ivr_originate.c#switch_ivr_originate() 函数处理外呼
    3. 以上外呼处理完毕,如果 originate 命令中使用 & 符号携带了在这个会话上执行的 app,则将其封装到 switch_caller_extension_t 结构体并保存到 channel 中,然后直接将 channel 状态设置为 CS_EXECUTE,由状态机驱动执行 app;如果未使用 & 符号,则执行 switch_ivr.c#switch_ivr_session_transfer() 函数将 channel 状态设置为 CS_ROUTING,由状态机驱动转移到相应的 dialplan,本文中将执行该分支
    #define ORIGINATE_SYNTAX " |&() [] [] [] [] []"
    SWITCH_STANDARD_API(originate_function)
    {switch_channel_t *caller_channel;switch_core_session_t *caller_session = NULL;char *mycmd = NULL, *argv[10] = { 0 };int i = 0, x, argc = 0;char *aleg, *exten, *dp, *context, *cid_name, *cid_num;uint32_t timeout = 60;switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;switch_status_t status = SWITCH_STATUS_SUCCESS;if (zstr(cmd)) {stream->write_function(stream, "-USAGE: %s\n", ORIGINATE_SYNTAX);return SWITCH_STATUS_SUCCESS;}/* log warning if part of ongoing session, as we'll block the session */if (session){switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Originate can take 60 seconds to complete, and blocks the existing session. Do not confuse with a lockup.\n");}mycmd = strdup(cmd);switch_assert(mycmd);argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));if (argc < 2 || argc > 7) {stream->write_function(stream, "-USAGE: %s\n", ORIGINATE_SYNTAX);goto done;}for (x = 0; x < argc && argv[x]; x++) {if (!strcasecmp(argv[x], "undef")) {argv[x] = NULL;}}aleg = argv[i++];exten = argv[i++];dp = argv[i++];context = argv[i++];cid_name = argv[i++];cid_num = argv[i++];switch_assert(exten);if (!dp) {dp = "XML";}if (!context) {context = "default";}if (argv[6]) {timeout = atoi(argv[6]);}if (switch_ivr_originate(NULL, &caller_session, &cause, aleg, timeout, NULL, cid_name, cid_num, NULL, NULL, SOF_NONE, NULL, NULL) != SWITCH_STATUS_SUCCESS|| !caller_session) {stream->write_function(stream, "-ERR %s\n", switch_channel_cause2str(cause));goto done;}caller_channel = switch_core_session_get_channel(caller_session);if (*exten == '&' && *(exten + 1)) {switch_caller_extension_t *extension = NULL;char *app_name = switch_core_session_strdup(caller_session, (exten + 1));char *arg = NULL, *e;if ((e = strchr(app_name, ')'))) {*e = '\0';}if ((arg = strchr(app_name, '('))) {*arg++ = '\0';}if ((extension = switch_caller_extension_new(caller_session, app_name, arg)) == 0) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Memory Error!\n");abort();}switch_caller_extension_add_application(caller_session, extension, app_name, arg);switch_channel_set_caller_extension(caller_channel, extension);switch_channel_set_state(caller_channel, CS_EXECUTE);} else {switch_ivr_session_transfer(caller_session, exten, dp, context);}stream->write_function(stream, "+OK %s\n", switch_core_session_get_uuid(caller_session));switch_core_session_rwunlock(caller_session);done:switch_safe_free(mycmd);return status;
    }
    
  2. switch_ivr_originate.c#switch_ivr_originate() 函数内容庞杂,包括了呼叫字符串的解析以及 early media 之类的处理,有两千行之多,笔者只挑选出以下呼叫流程相关的进行重点分析:

    1. 通过 switch_core_session.c#switch_core_session_outgoing_channel() 函数触发端点接口的回调函数执行
    2. 调用 switch_channel.c#switch_channel_add_state_handler() 函数设置 FreeSWITCH 核心状态机在 channel 层级的回调函数表为 originate_state_handler
    3. 判断是否有启动线程处理当前新建 session,如没有则调用 switch_core_session.c#switch_core_session_thread_launch() 开启一个线程运转核心状态机,这部分可参考 FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程,本文不再赘述
    SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *session,switch_core_session_t **bleg,switch_call_cause_t *cause,const char *bridgeto,uint32_t timelimit_sec,const switch_state_handler_table_t *table,const char *cid_name_override,const char *cid_num_override,switch_caller_profile_t *caller_profile_override,switch_event_t *ovars, switch_originate_flag_t flags,switch_call_cause_t *cancel_cause,switch_dial_handle_t *dh)
    {//originate_status_t originate_status[MAX_PEERS] = { {0} };switch_originate_flag_t dftflags = SOF_NONE, myflags;char *pipe_names[MAX_PEERS] = { 0 };char *data = NULL;switch_status_t status = SWITCH_STATUS_SUCCESS;switch_channel_t *caller_channel = NULL;char *peer_names[MAX_PEERS] = { 0 };switch_event_t *peer_vars[MAX_PEERS] = { 0 };switch_core_session_t *new_session = NULL, *peer_session = NULL;switch_caller_profile_t *new_profile = NULL, *caller_caller_profile;char *chan_type = NULL, *chan_data;switch_channel_t *peer_channel = NULL;ringback_t ringback = { 0 };time_t start, global_start;switch_time_t last_retry_start = 0;switch_frame_t *read_frame = NULL;int r = 0, i, and_argc = 0, or_argc = 0;int32_t sleep_ms = 1000, try = 0, retries = 1, retry_timelimit_sec = 0;int32_t min_retry_period_ms = sleep_ms;switch_codec_t write_codec = { 0 };switch_frame_t write_frame = { 0 };char *odata, *var;switch_call_cause_t reason = SWITCH_CAUSE_NONE;switch_call_cause_t force_reason = SWITCH_CAUSE_NONE;uint8_t to = 0;char *var_val;const char *ringback_data = NULL;switch_event_t *var_event = NULL;int8_t fail_on_single_reject = 0;int8_t hangup_on_single_reject = 0;char *fail_on_single_reject_var = NULL;char *loop_data = NULL;uint32_t progress_timelimit_sec = 0;const char *cid_tmp, *lc;originate_global_t oglobals = { 0 };int cdr_total = 0;int local_clobber = 0;const char *cancel_key = NULL;const char *holding = NULL;const char *soft_holding = NULL;early_state_t early_state = { 0 };int read_packet = 0;int check_reject = 1;switch_codec_implementation_t read_impl = { 0 };const char *ani_override = NULL;const char *aniii_override = NULL;const char *ent_aleg_uuid = NULL;switch_core_session_t *a_session = session, *l_session = NULL;char *event_string;if (!bridgeto || dh) {bridgeto = "";}if (session) {caller_channel = switch_core_session_get_channel(session);if (switch_false(switch_channel_get_variable(caller_channel, "preserve_originated_vars"))) {switch_channel_set_variable(caller_channel, "originated_legs", NULL);switch_channel_set_variable(caller_channel, "originate_causes", NULL);}}if (strstr(bridgeto, SWITCH_ENT_ORIGINATE_DELIM)) {return switch_ivr_enterprise_originate(session, bleg, cause, bridgeto, timelimit_sec, table, cid_name_override, cid_num_override,caller_profile_override, ovars, flags, cancel_cause, NULL);}oglobals.check_vars = SWITCH_TRUE;oglobals.ringback_ok = 1;oglobals.bridge_early_media = -1;oglobals.file = NULL;oglobals.error_file = NULL;switch_core_new_memory_pool(&oglobals.pool);......reason = switch_core_session_outgoing_channel(oglobals.session, originate_var_event, chan_type,new_profile, &new_session, NULL, myflags, cancel_cause);......if (table) {switch_channel_add_state_handler(oglobals.originate_status[i].peer_channel, table);}if (oglobals.monitor_early_media_ring || oglobals.monitor_early_media_fail || oglobals.ignore_early_media == 4) {switch_channel_set_flag(oglobals.originate_status[i].peer_channel, CF_CONSUME_ON_ORIGINATE);}switch_channel_add_state_handler(oglobals.originate_status[i].peer_channel, &originate_state_handlers);if ((flags & SOF_NOBLOCK) && oglobals.originate_status[i].peer_session) {status = SWITCH_STATUS_SUCCESS;*bleg = oglobals.originate_status[i].peer_session;*cause = SWITCH_CAUSE_SUCCESS;goto outer_for;}if (!switch_core_session_running(oglobals.originate_status[i].peer_session)) {if (oglobals.originate_status[i].per_channel_delay_start) {switch_channel_set_flag(oglobals.originate_status[i].peer_channel, CF_BLOCK_STATE);}switch_core_session_thread_launch(oglobals.originate_status[i].peer_session);}......return status;
    }
  3. switch_core_session.c#switch_core_session_outgoing_channel() 函数源码相对简练,可以看到核心是执行 endpoint_interface->io_routines->outgoing_channel()函数新建一个外呼 session。此时端点为 user,该端点在mod_dptools模块加载时注册,则将调用到 mod_dptools.c#user_outgoing_channel() 函数

    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,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_io_event_hook_outgoing_channel_t *ptr;switch_status_t status = SWITCH_STATUS_FALSE;switch_endpoint_interface_t *endpoint_interface;switch_channel_t *channel = NULL;switch_caller_profile_t *outgoing_profile = caller_profile;switch_call_cause_t cause = SWITCH_CAUSE_REQUESTED_CHAN_UNAVAIL;const char *forwardvar;int forwardval = 70;if ((endpoint_interface = switch_loadable_module_get_endpoint_interface(endpoint_name)) == 0) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Could not locate channel type %s\n", endpoint_name);return SWITCH_CAUSE_CHAN_NOT_IMPLEMENTED;}switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Could locate channel type %s\n", endpoint_name);if (!endpoint_interface->io_routines->outgoing_channel) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Could not locate outgoing channel interface for %s\n", endpoint_name);return SWITCH_CAUSE_CHAN_NOT_IMPLEMENTED;}......if ((cause =endpoint_interface->io_routines->outgoing_channel(session, var_event, outgoing_profile, new_session, pool, flags,cancel_cause)) != SWITCH_CAUSE_SUCCESS) {UNPROTECT_INTERFACE(endpoint_interface);return cause;}if (session) {for (ptr = session->event_hooks.outgoing_channel; ptr; ptr = ptr->next) {if ((status = ptr->outgoing_channel(session, var_event, caller_profile, *new_session, flags)) != SWITCH_STATUS_SUCCESS) {break;}}}if (!*new_session) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT,"Outgoing method for endpoint: [%s] returned: [%s] but there is no new session!\n", endpoint_name,switch_channel_cause2str(cause));UNPROTECT_INTERFACE(endpoint_interface);return SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER;} else {switch_caller_profile_t *profile = NULL, *cloned_profile = NULL;switch_event_t *event;switch_channel_t *peer_channel = switch_core_session_get_channel(*new_session);const char *use_uuid;const char *use_external_id;switch_core_session_t *other_session = NULL;switch_assert(peer_channel);if (channel && switch_true(switch_channel_get_variable(channel, "session_copy_loglevel"))) {(*new_session)->loglevel = session->loglevel;}if ((use_uuid = switch_event_get_header(var_event, "origination_uuid"))) {use_uuid = switch_core_session_strdup(*new_session, use_uuid);if (switch_core_session_set_uuid(*new_session, use_uuid) == SWITCH_STATUS_SUCCESS) {switch_event_del_header(var_event, "origination_uuid");switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_DEBUG, "%s set UUID=%s\n", switch_channel_get_name(peer_channel),use_uuid);} else {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_CRIT, "%s set UUID=%s FAILED\n",switch_channel_get_name(peer_channel), use_uuid);}}if ((use_external_id = switch_event_get_header(var_event, "origination_external_id"))) {if (switch_core_session_set_external_id(*new_session, use_external_id) == SWITCH_STATUS_SUCCESS) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_DEBUG, "%s set external_id=%s\n", switch_channel_get_name(peer_channel),use_external_id);switch_event_del_header(var_event, "origination_external_id");} else {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_CRIT, "%s set external_id=%s FAILED\n",switch_channel_get_name(peer_channel), use_external_id);}}if (!channel && var_event) {const char *other_uuid;if ((other_uuid = switch_event_get_header(var_event, "origination_aleg_uuid")) && (other_session = switch_core_session_locate(other_uuid))) {channel = switch_core_session_get_channel(other_session);session = other_session;}}if (channel) {const char *val;switch_codec_t *vid_read_codec = NULL, *read_codec = switch_core_session_get_read_codec(session);const char *ep = NULL, *max_forwards = switch_core_session_sprintf(session, "%d", forwardval);switch_channel_set_variable(peer_channel, SWITCH_MAX_FORWARDS_VARIABLE, max_forwards);profile = switch_channel_get_caller_profile(channel);vid_read_codec = switch_core_session_get_video_read_codec(session);ep = switch_channel_get_variable(channel, "ep_codec_string");if (read_codec && read_codec->implementation && switch_core_codec_ready(read_codec)) {char rc[80] = "", vrc[80] = "", tmp[160] = "";switch_codec2str(read_codec, rc, sizeof(rc));if (vid_read_codec && vid_read_codec->implementation && switch_core_codec_ready(vid_read_codec)) {vrc[0] = ',';switch_codec2str(vid_read_codec, vrc + 1, sizeof(vrc) - 1);switch_channel_set_variable(peer_channel, SWITCH_ORIGINATOR_VIDEO_CODEC_VARIABLE, vrc + 1);}switch_snprintf(tmp, sizeof(tmp), "%s%s", rc, vrc);switch_channel_set_variable(peer_channel, SWITCH_ORIGINATOR_CODEC_VARIABLE, tmp);} else if (ep) {switch_channel_set_variable(peer_channel, SWITCH_ORIGINATOR_CODEC_VARIABLE, ep);}if (switch_channel_test_flag(channel, CF_MSRPS) || switch_channel_test_flag(channel, CF_WANT_MSRPS)) {switch_channel_set_flag(peer_channel, CF_WANT_MSRPS);} else if (switch_channel_test_flag(channel, CF_MSRP) || switch_channel_test_flag(channel, CF_WANT_MSRP)) {switch_channel_set_flag(peer_channel, CF_WANT_MSRP);}if (switch_channel_test_flag(channel, CF_RTT) || switch_channel_test_flag(channel, CF_WANT_RTT)) {switch_channel_set_flag(peer_channel, CF_WANT_RTT);}switch_channel_set_variable(peer_channel, SWITCH_ORIGINATOR_VARIABLE, switch_core_session_get_uuid(session));switch_channel_set_variable(peer_channel, SWITCH_SIGNAL_BOND_VARIABLE, switch_core_session_get_uuid(session));// Needed by 3PCC proxy so that aleg can find bleg to pass SDP to, when final ACK arrives.switch_channel_set_variable(channel, SWITCH_ORIGINATE_SIGNAL_BOND_VARIABLE, switch_core_session_get_uuid(*new_session));if ((val = switch_channel_get_variable(channel, SWITCH_PROCESS_CDR_VARIABLE))) {switch_channel_set_variable(peer_channel, SWITCH_PROCESS_CDR_VARIABLE, val);}if ((val = switch_channel_get_variable(channel, SWITCH_R_SDP_VARIABLE))) {switch_channel_pass_sdp(channel, peer_channel, val);}if (switch_channel_test_flag(channel, CF_PROXY_MODE)) {if (switch_channel_test_cap(peer_channel, CC_BYPASS_MEDIA)) {switch_channel_set_flag(peer_channel, CF_PROXY_MODE);} else {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING,"%s does not support the proxy feature, disabling.\n", switch_channel_get_name(peer_channel));switch_channel_clear_flag(channel, CF_PROXY_MODE);}}if (switch_channel_test_flag(channel, CF_PROXY_MEDIA)) {if (switch_channel_test_cap(peer_channel, CC_PROXY_MEDIA)) {switch_channel_set_flag(peer_channel, CF_PROXY_MEDIA);if (switch_channel_test_flag(channel, CF_VIDEO)) {switch_channel_set_flag(peer_channel, CF_VIDEO);}} else {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING,"%s does not support the proxy feature, disabling.\n", switch_channel_get_name(peer_channel));switch_channel_clear_flag(channel, CF_PROXY_MEDIA);}}if (switch_channel_test_flag(channel, CF_ZRTP_PASSTHRU_REQ)) {switch_channel_set_flag(peer_channel, CF_ZRTP_PASSTHRU_REQ);}if (profile) {if ((cloned_profile = switch_caller_profile_clone(*new_session, profile)) != 0) {switch_channel_set_originator_caller_profile(peer_channel, cloned_profile);}}if ((profile = switch_channel_get_caller_profile(peer_channel))) {if ((cloned_profile = switch_caller_profile_clone(session, profile)) != 0) {switch_channel_set_origination_caller_profile(channel, cloned_profile);}}}if (other_session) {switch_core_session_rwunlock(other_session);channel = NULL;session = NULL;}if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_OUTGOING) == SWITCH_STATUS_SUCCESS) {switch_channel_event_set_data(peer_channel, event);switch_event_fire(&event);}}UNPROTECT_INTERFACE(endpoint_interface);return cause;
    }
  4. 实际上 user 端点并没有底层协议驱动以及媒体收发处理,发起外呼依然依赖 sofia 端点,mod_dptools.c#user_outgoing_channel() 函数就是二者的转换交界,其核心处理如下:

    1. 解析获取被叫的注册用户的 id,通过 switch_xml.c#switch_xml_locate_user_merged() 函数定位获取其呼叫字符串等相关信息。用户的信息可以来自本地 xml 配置,也可以从远程服务器获取,读者如有兴趣可以参考FreeSWITCH 1.10 源码阅读(2)-xml_curl 模块原理,本文不再赘述
    2. 本文例子是单腿呼叫,则将通过宏定义调用 switch_event.c#switch_event_expand_headers_check() 函数扩展填充用户的呼叫字符串,这个步骤是将呼叫端点替换成 sofia 的关键
    3. 将替换完成的呼叫字符串入参,调用 switch_ivr_originate.c#switch_ivr_originate() 函数将外呼动作传递给 sofia 执行
    static switch_call_cause_t user_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_xml_t x_user = NULL, x_param, x_params;char *user = NULL, *domain = NULL, *dup_domain = NULL, *dialed_user = NULL;const char *dest = NULL;switch_call_cause_t cause = SWITCH_CAUSE_NONE;unsigned int timelimit = SWITCH_DEFAULT_TIMEOUT;switch_channel_t *new_channel = NULL;switch_event_t *params = NULL, *var_event_orig = var_event;char stupid[128] = "";const char *skip = NULL, *var = NULL;if (zstr(outbound_profile->destination_number)) {goto done;}user = strdup(outbound_profile->destination_number);if (!user)goto done;if ((domain = strchr(user, '@'))) {*domain++ = '\0';} else {domain = switch_core_get_domain(SWITCH_TRUE);dup_domain = domain;}if (!domain) {goto done;}switch_event_create(&params, SWITCH_EVENT_REQUEST_PARAMS);switch_assert(params);switch_event_add_header_string(params, SWITCH_STACK_BOTTOM, "as_channel", "true");switch_event_add_header_string(params, SWITCH_STACK_BOTTOM, "action", "user_call");if (var_event) {switch_event_merge(params, var_event);}if (var_event && (skip = switch_event_get_header(var_event, "user_recurse_variables")) && switch_false(skip)) {if ((var = switch_event_get_header(var_event, SWITCH_CALL_TIMEOUT_VARIABLE)) || (var = switch_event_get_header(var_event, "leg_timeout"))) {timelimit = atoi(var);}var_event = NULL;}if (switch_xml_locate_user_merged("id", user, domain, NULL, &x_user, params) != SWITCH_STATUS_SUCCESS) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Can't find user [%s@%s]\n", user, domain);cause = SWITCH_CAUSE_SUBSCRIBER_ABSENT;goto done;}if ((x_params = switch_xml_child(x_user, "params"))) {for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) {const char *pvar = switch_xml_attr_soft(x_param, "name");const char *val = switch_xml_attr(x_param, "value");if (!strcasecmp(pvar, "dial-string")) {dest = val;} else if (!strncasecmp(pvar, "dial-var-", 9)) {if (!var_event) {switch_event_create(&var_event, SWITCH_EVENT_GENERAL);} else {switch_event_del_header(var_event, pvar + 9);}switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, pvar + 9, val);}}}dialed_user = (char *)switch_xml_attr(x_user, "id");if (var_event) {switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "dialed_user", dialed_user);switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "dialed_domain", domain);if (!zstr(dest) && !strstr(dest, "presence_id=")) {switch_event_add_header(var_event, SWITCH_STACK_BOTTOM, "presence_id", "%s@%s", dialed_user, domain);}}if (!dest) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No dial-string available, please check your user directory.\n");cause = SWITCH_CAUSE_MANDATORY_IE_MISSING;} else {const char *varval;char *d_dest = NULL;switch_channel_t *channel;switch_originate_flag_t myflags = SOF_NONE;char *cid_name_override = NULL;char *cid_num_override = NULL;if (var_event) {cid_name_override = switch_event_get_header(var_event, "origination_caller_id_name");cid_num_override = switch_event_get_header(var_event, "origination_caller_id_number");}if (session) {channel = switch_core_session_get_channel(session);if ((varval = switch_channel_get_variable(channel, SWITCH_CALL_TIMEOUT_VARIABLE))|| (var_event && (varval = switch_event_get_header(var_event, "leg_timeout")))) {timelimit = atoi(varval);}switch_channel_set_variable(channel, "dialed_user", dialed_user);switch_channel_set_variable(channel, "dialed_domain", domain);d_dest = switch_channel_expand_variables(channel, dest);} else {switch_event_t *event = NULL;if (var_event) {switch_event_dup(&event, var_event);switch_event_del_header(event, "dialed_user");switch_event_del_header(event, "dialed_domain");if ((varval = switch_event_get_header(var_event, SWITCH_CALL_TIMEOUT_VARIABLE)) ||(varval = switch_event_get_header(var_event, "leg_timeout"))) {timelimit = atoi(varval);}} else {switch_event_create(&event, SWITCH_EVENT_REQUEST_PARAMS);switch_assert(event);}switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "dialed_user", dialed_user);switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "dialed_domain", domain);d_dest = switch_event_expand_headers(event, dest);switch_event_destroy(&event);}if ((flags & SOF_NO_LIMITS)) {myflags |= SOF_NO_LIMITS;}if ((flags & SOF_FORKED_DIAL)) {myflags |= SOF_NOBLOCK;}switch_snprintf(stupid, sizeof(stupid), "user/%s", dialed_user);if (switch_stristr(stupid, d_dest)) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Waddya Daft? You almost called '%s' in an infinate loop!\n",stupid);cause = SWITCH_CAUSE_INVALID_IE_CONTENTS;} else if (switch_ivr_originate(session, new_session, &cause, d_dest, timelimit, NULL,cid_name_override, cid_num_override, outbound_profile, var_event, myflags,cancel_cause, NULL) == SWITCH_STATUS_SUCCESS) {const char *context;switch_caller_profile_t *cp;if (var_event) {switch_event_del_header(var_event, "origination_uuid");}new_channel = switch_core_session_get_channel(*new_session);if ((context = switch_channel_get_variable(new_channel, "user_context"))) {if ((cp = switch_channel_get_caller_profile(new_channel))) {cp->context = switch_core_strdup(cp->pool, context);}}switch_core_session_rwunlock(*new_session);}if (d_dest != dest) {switch_safe_free(d_dest);}}if (new_channel && x_user) {if ((x_params = switch_xml_child(x_user, "variables"))) {for (x_param = switch_xml_child(x_params, "variable"); x_param; x_param = x_param->next) {const char *pvar = switch_xml_attr(x_param, "name");const char *val = switch_xml_attr(x_param, "value");switch_channel_set_variable(new_channel, pvar, val);}}}done:if (x_user) {switch_xml_free(x_user);}if (params) {switch_event_destroy(&params);}if (var_event && var_event_orig != var_event) {switch_event_destroy(&var_event);}switch_safe_free(user);switch_safe_free(dup_domain);return cause;
    }
  5. switch_event.c#switch_event_expand_headers_check() 函数比较琐碎,核心逻辑是对原始呼叫字符串中的占位符进行替换,获得可用于实际外呼的字符串,比较关键的步骤如下:

    1. 首先是对注册用户的拨号字符串中 ${} 占位符的替换,占位符的值来自于入参的 switch_event_t 结构体实例
    2. 原始拨号字符串中如果存在小括号(),则其属于 api 调用,需要通过 switch_loadable_moudle.c#switch_api_execute() 执行对应的 api 实现函数

    一个示例的原始拨号字符串如 {^^:sip_invite_domain=$ {dialed_domain}:presence_id=$ {dialed_user}@$ {dialed_domain}}$ {sofia_contact(*/$ {dialed_user}@$ {dialed_domain})},$ {verto_contact($ {dialed_user}@$ {dialed_domain})},可以看到将会调用名称为 sofia_contact 的 api,也就是触发 mod_sofia.c#SWITCH_STANDARD_API(sofia_contact_function) 函数执行

    SWITCH_DECLARE(char *) switch_event_expand_headers_check(switch_event_t *event, const char *in, switch_event_t *var_list, switch_event_t *api_list, uint32_t recur)
    {char *p, *c = NULL;char *data, *indup, *endof_indup;size_t sp = 0, len = 0, olen = 0, vtype = 0, br = 0, cpos, block = 128;const char *sub_val = NULL;char *cloned_sub_val = NULL, *expanded_sub_val = NULL;char *func_val = NULL;int nv = 0;char *gvar = NULL, *sb = NULL;if (recur > 100) {return (char *) in;}if (zstr(in)) {return (char *) in;}nv = switch_string_var_check_const(in) || switch_string_has_escaped_data(in);if (!nv) {return (char *) in;}nv = 0;olen = strlen(in) + 1;indup = strdup(in);switch_assert(indup);endof_indup = end_of_p(indup) + 1;if ((data = malloc(olen))) {memset(data, 0, olen);c = data;for (p = indup; p && p < endof_indup && *p; p++) {int global = 0;vtype = 0;if (*p == '\\') {if (*(p + 1) == '$') {nv = 1;p++;if (*(p + 1) == '$') {p++;}} else if (*(p + 1) == '\'') {p++;continue;} else if (*(p + 1) == '\\') {if (len + 1 >= olen) {resize(1);}*c++ = *p++;len++;continue;}}if (*p == '$' && !nv) {if (*(p + 1) == '$') {p++;global++;}if (*(p + 1)) {if (*(p + 1) == '{') {vtype = global ? 3 : 1;} else {nv = 1;}} else {nv = 1;}}if (nv) {if (len + 1 >= olen) {resize(1);}*c++ = *p;len++;nv = 0;continue;}if (vtype) {char *s = p, *e, *vname, *vval = NULL;size_t nlen;s++;if ((vtype == 1 || vtype == 3) && *s == '{') {br = 1;s++;}e = s;vname = s;while (*e) {if (br == 1 && *e == '}') {br = 0;*e++ = '\0';break;}if (br > 0) {if (e != s && *e == '{') {br++;} else if (br > 1 && *e == '}') {br--;}}e++;}p = e > endof_indup ? endof_indup : e;vval = NULL;for(sb = vname; sb && *sb; sb++) {if (*sb == ' ') {vval = sb;break;} else if (*sb == '(') {vval = sb;br = 1;break;}}if (vval) {e = vval - 1;*vval++ = '\0';while (*e == ' ') {*e-- = '\0';}e = vval;while (e && *e) {if (*e == '(') {br++;} else if (br > 1 && *e == ')') {br--;} else if (br == 1 && *e == ')') {*e = '\0';break;}e++;}vtype = 2;}if (vtype == 1 || vtype == 3) {char *expanded = NULL;int offset = 0;int ooffset = 0;char *ptr;int idx = -1;if ((expanded = switch_event_expand_headers_check(event, (char *) vname, var_list, api_list, recur+1)) == vname) {expanded = NULL;} else {vname = expanded;}if ((ptr = strchr(vname, ':'))) {*ptr++ = '\0';offset = atoi(ptr);if ((ptr = strchr(ptr, ':'))) {ptr++;ooffset = atoi(ptr);}}if ((ptr = strchr(vname, '[')) && strchr(ptr, ']')) {*ptr++ = '\0';idx = atoi(ptr);}if (vtype == 3 || !(sub_val = switch_event_get_header_idx(event, vname, idx))) {switch_safe_free(gvar);if ((gvar = switch_core_get_variable_dup(vname))) {sub_val = gvar;}if (var_list && !switch_event_check_permission_list(var_list, vname)) {sub_val = "";}if ((expanded_sub_val = switch_event_expand_headers_check(event, sub_val, var_list, api_list, recur+1)) == sub_val) {expanded_sub_val = NULL;} else {sub_val = expanded_sub_val;}}if (sub_val) {if (offset || ooffset) {cloned_sub_val = strdup(sub_val);switch_assert(cloned_sub_val);sub_val = cloned_sub_val;}if (offset >= 0) {sub_val += offset;} else if ((size_t) abs(offset) <= strlen(sub_val)) {sub_val = cloned_sub_val + (strlen(cloned_sub_val) + offset);}if (ooffset > 0 && (size_t) ooffset < strlen(sub_val)) {if ((ptr = (char *) sub_val + ooffset)) {*ptr = '\0';}}}switch_safe_free(expanded);} else {switch_stream_handle_t stream = { 0 };char *expanded = NULL;char *expanded_vname = NULL;if ((expanded_vname = switch_event_expand_headers_check(event, (char *) vname, var_list, api_list, recur+1)) == vname) {expanded_vname = NULL;} else {vname = expanded_vname;}if ((expanded = switch_event_expand_headers_check(event, vval, var_list, api_list, recur+1)) == vval) {expanded = NULL;} else {vval = expanded;}if (!switch_core_test_flag(SCF_API_EXPANSION) || (api_list && !switch_event_check_permission_list(api_list, vname))) {func_val = NULL;sub_val = "";} else {SWITCH_STANDARD_STREAM(stream);if (switch_api_execute(vname, vval, NULL, &stream) == SWITCH_STATUS_SUCCESS) {func_val = stream.data;sub_val = func_val;} else {free(stream.data);}}switch_safe_free(expanded);switch_safe_free(expanded_vname);}if ((nlen = sub_val ? strlen(sub_val) : 0)) {if (len + nlen >= olen) {resize(nlen);}len += nlen;strcat(c, sub_val);c += nlen;}switch_safe_free(func_val);switch_safe_free(cloned_sub_val);switch_safe_free(expanded_sub_val);sub_val = NULL;vname = NULL;br = 0;}if (sp) {if (len + 1 >= olen) {resize(1);}*c++ = ' ';sp = 0;len++;}if (*p == '$') {p--;} else {if (len + 1 >= olen) {resize(1);}*c++ = *p;len++;}}}free(indup);switch_safe_free(gvar);return data;
    }
  6. mod_sofia.c#SWITCH_STANDARD_API(sofia_contact_function) 函数并不复杂,核心逻辑是调用 mod_sofia.c#select_from_profile() 方法查询 SIP 注册表,确定注册用户具体注册在底层 Sofia-SIP 的哪个 UA 上,明确了这一点才能最终确定如何呼叫目标用户,至此 user 端点对注册用户拨号字符串的转化基本结束

    SWITCH_STANDARD_API(sofia_contact_function)
    {char *data;char *user = NULL;char *domain = NULL, *dup_domain = NULL;char *concat = NULL;char *profile_name = NULL;char *p;sofia_profile_t *profile = NULL;const char *exclude_contact = NULL;const char *match_user_agent = NULL;char *reply = "error/facility_not_subscribed";switch_stream_handle_t mystream = { 0 };if (!cmd) {stream->write_function(stream, "%s", "");return SWITCH_STATUS_SUCCESS;}if (session) {switch_channel_t *channel = switch_core_session_get_channel(session);exclude_contact = switch_channel_get_variable(channel, "sip_exclude_contact");match_user_agent = switch_channel_get_variable(channel, "sip_match_user_agent");}data = strdup(cmd);switch_assert(data);if ((p = strchr(data, '~'))) {profile_name = data;*p++ = '\0';match_user_agent = p;}if ((p = strchr(data, '/'))) {profile_name = data;*p++ = '\0';user = p;} else {user = data;}if ((domain = strchr(user, '@'))) {*domain++ = '\0';if ((concat = strchr(domain, '/'))) {*concat++ = '\0';}} else {if ((concat = strchr(user, '/'))) {*concat++ = '\0';}}if (zstr(domain)) {dup_domain = switch_core_get_domain(SWITCH_TRUE);domain = dup_domain;}if (zstr(profile_name) || strcmp(profile_name, "*") || zstr(domain)) {if (!zstr(profile_name)) {profile = sofia_glue_find_profile(profile_name);}if (!profile && !zstr(domain)) {profile = sofia_glue_find_profile(domain);}}if (profile || !zstr(domain)) {SWITCH_STANDARD_STREAM(mystream);switch_assert(mystream.data);}if (profile) {if (zstr(domain)) {domain = profile->name;}if (!zstr(profile->domain_name) && !zstr(profile_name) && !strcmp(profile_name, profile->name)) {domain = profile->domain_name;}select_from_profile(profile, user, domain, concat, exclude_contact, match_user_agent, &mystream, SWITCH_FALSE);sofia_glue_release_profile(profile);} else if (!zstr(domain)) {sofia_profile_t *profiles[1024] = {0};uint8_t i = 0, j;switch_mutex_lock(mod_sofia_globals.hash_mutex);if (mod_sofia_globals.profile_hash) {switch_hash_index_t *hi;const void *var;void *val;for (hi = switch_core_hash_first(mod_sofia_globals.profile_hash); hi; hi = switch_core_hash_next(&hi)) {switch_core_hash_this(hi, &var, NULL, &val);if ((profile = (sofia_profile_t *) val) && !strcmp((char *)var, profile->name)) {sofia_glue_profile_rdlock(profile);profiles[i++] = profile;profile = NULL;}}}switch_mutex_unlock(mod_sofia_globals.hash_mutex);if (i) {for (j = 0; j < i; j++) {select_from_profile(profiles[j], user, domain, concat, exclude_contact, match_user_agent, &mystream, SWITCH_TRUE);sofia_glue_release_profile(profiles[j]);}}}reply = (char *) mystream.data;if (zstr(reply)) {reply = "error/user_not_registered";} else if (end_of(reply) == ',') {end_of(reply) = '\0';}stream->write_function(stream, "%s", reply);reply = NULL;switch_safe_free(mystream.data);switch_safe_free(data);switch_safe_free(dup_domain);return SWITCH_STATUS_SUCCESS;
    }
    

2.2 sofia 端点的外呼处理

  1. 经过上一节处理,呼叫字符串已经被 user 端点转化为由 sofia 端点处理的格式,此时回到2.1节步骤4第3步switch_ivr_originate.c#switch_ivr_originate() 函数将被执行。这个函数在2.1节步骤2已经提及,此时其处理步骤基本不变,首先依然是通过 switch_core_session.c#switch_core_session_outgoing_channel() 函数触发端点接口的回调函数执行,只不过此时触发的是 mod_sofia.c#sofia_outgoing_channel() 函数,该函数的核心处理如下:

    1. 调用 switch_core_session.c#switch_core_session_request_uuid() 创建新的会话 session,
    2. 构建会话的私有数据结构体private_object_t实例,解析处理呼叫字符串保存各项数据到该结构体中,并通过 sofia_glue.c#sofia_glue_attach_private() 函数将二者关联起来
    3. 通过宏定义 mod_sofia.h#sofia_set_flag_locked() 将标记 TFLAG_OUTBOUND 设置到 session 私有数据结构体上,然后通过宏定义 mod_sofia.c#switch_channel_set_state() 将新建 channel 的状态从 CS_NEW 流转到 CS_INIT
    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 *data, *profile_name, *dest;	//, *dest_num = NULL;sofia_profile_t *profile = NULL;switch_caller_profile_t *caller_profile = NULL;private_object_t *tech_pvt = NULL;switch_channel_t *nchannel;char *host = NULL, *dest_to = NULL;const char *hval = NULL;char *not_const = NULL;int cid_locked = 0;switch_channel_t *o_channel = NULL;sofia_gateway_t *gateway_ptr = NULL;int mod = 0;*new_session = NULL;if (!outbound_profile || zstr(outbound_profile->destination_number)) {switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Invalid Empty Destination\n");goto error;}if (!switch_true(switch_event_get_header(var_event, "sofia_suppress_url_encoding"))) {mod = protect_dest_uri(outbound_profile);}if (!(nsession = switch_core_session_request_uuid(sofia_endpoint_interface, SWITCH_CALL_DIRECTION_OUTBOUND,flags, pool, switch_event_get_header(var_event, "origination_uuid")))) {switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Error Creating Session\n");goto error;}tech_pvt = sofia_glue_new_pvt(nsession);data = switch_core_session_strdup(nsession, outbound_profile->destination_number);if ((dest_to = strchr(data, '^'))) {*dest_to++ = '\0';}profile_name = data;nchannel = switch_core_session_get_channel(nsession);if (session) {o_channel = switch_core_session_get_channel(session);}if ((hval = switch_event_get_header(var_event, "sip_invite_to_uri"))) {dest_to = switch_core_session_strdup(nsession, hval);}if (!strncasecmp(profile_name, "gateway/", 8)) {char *gw, *params;if (!(gw = strchr(profile_name, '/'))) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid URL \'%s\'\n", profile_name);cause = SWITCH_CAUSE_INVALID_URL;goto error;}*gw++ = '\0';if (!(dest = strchr(gw, '/'))) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid URL \'%s\'\n", gw);cause = SWITCH_CAUSE_INVALID_URL;goto error;}*dest++ = '\0';if (!(gateway_ptr = sofia_reg_find_gateway(gw)) || !gateway_ptr->profile) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid Gateway \'%s\'\n", gw);cause = SWITCH_CAUSE_INVALID_GATEWAY;goto error;}profile = gateway_ptr->profile;if (gateway_ptr->status != SOFIA_GATEWAY_UP) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "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->cid_type = gateway_ptr->cid_type;cid_locked = 1;/** Handle params, strip them off the destination and add them to the* invite contact.**/if ((params = strchr(dest, ';'))) {char *tp_param;*params++ = '\0';if ((tp_param = (char *) switch_stristr("port=", params))) {tp_param += 5;tech_pvt->transport = sofia_glue_str2transport(tp_param);if (tech_pvt->transport == SOFIA_TRANSPORT_UNKNOWN) {cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER;gateway_ptr->ob_failed_calls++;goto error;}}}if (tech_pvt->transport != gateway_ptr->register_transport) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR,"You are trying to use a different transport type for this gateway (overriding the register-transport), this is unsupported!\n");cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER;goto error;}if (profile && sofia_test_pflag(profile, PFLAG_STANDBY)) {switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "System Paused\n");cause = SWITCH_CAUSE_SYSTEM_SHUTDOWN;goto error;}tech_pvt->gateway_name = switch_core_session_strdup(nsession, gateway_ptr->name);switch_channel_set_variable(nchannel, "sip_gateway_name", gateway_ptr->name);if (!sofia_test_flag(gateway_ptr, REG_FLAG_CALLERID)) {tech_pvt->gateway_from_str = switch_core_session_strdup(nsession, gateway_ptr->register_from);}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);}if ((host = switch_core_session_strdup(nsession, tech_pvt->dest))) {char *pp = strchr(host, '@');if (pp) {host = pp + 1;} else {host = NULL;dest_to = NULL;}}if (params) {tech_pvt->invite_contact = switch_core_session_sprintf(nsession, "%s;%s", gateway_ptr->register_contact, params);tech_pvt->dest = switch_core_session_sprintf(nsession, "%s;%s", tech_pvt->dest, params);} else {tech_pvt->invite_contact = switch_core_session_strdup(nsession, gateway_ptr->register_contact);}gateway_ptr->ob_calls++;if (!zstr(gateway_ptr->from_domain) && !switch_channel_get_variable(nchannel, "sip_invite_domain")) {if (!strcasecmp(gateway_ptr->from_domain, "auto-aleg-full")) {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")) {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);}}if (!zstr(gateway_ptr->outbound_sticky_proxy) && !switch_channel_get_variable(nchannel, "sip_route_uri")) {switch_channel_set_variable(nchannel, "sip_route_uri", gateway_ptr->outbound_sticky_proxy);}} else {const char *sip_destination_prefix = switch_str_nil(switch_event_get_header(var_event, "sip_destination_prefix"));if (!(dest = strchr(profile_name, '/'))) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid URL\n");cause = SWITCH_CAUSE_INVALID_URL;goto error;}*dest++ = '\0';if (!(profile = sofia_glue_find_profile(profile_name))) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid Profile\n");cause = SWITCH_CAUSE_INVALID_PROFILE;goto error;}if (sofia_test_pflag(profile, PFLAG_STANDBY)) {switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "System Paused\n");cause = SWITCH_CAUSE_SYSTEM_SHUTDOWN;goto error;}if (profile->domain_name && strcmp(profile->domain_name, profile->name)) {profile_name = profile->domain_name;}if (!strncasecmp(dest, "sip:", 4)) {tech_pvt->e_dest = switch_core_session_strdup(nsession, dest + 4);tech_pvt->dest = switch_core_session_sprintf(nsession, "sip:%s%s", sip_destination_prefix, tech_pvt->e_dest);} else if (!strncasecmp(dest, "sips:", 5)) {tech_pvt->e_dest = switch_core_session_strdup(nsession, dest + 5);tech_pvt->dest = switch_core_session_sprintf(nsession, "sips:%s%s", sip_destination_prefix, tech_pvt->e_dest);} else if (!mod && !strchr(dest, '@') && (host = strchr(dest, '%'))) {char buf[1024];*host = '@';tech_pvt->e_dest = switch_core_session_strdup(nsession, dest);*host++ = '\0';if (sofia_reg_find_reg_url(profile, dest, host, buf, sizeof(buf))) {tech_pvt->dest = switch_core_session_strdup(nsession, buf);tech_pvt->local_url = switch_core_session_sprintf(nsession, "%s@%s", dest, host);} else {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Cannot locate registered user %s@%s\n", dest, host);cause = SWITCH_CAUSE_USER_NOT_REGISTERED;goto error;}} else if (!(host = strchr(dest, '@'))) {char buf[1024];tech_pvt->e_dest = switch_core_session_strdup(nsession, dest);if (sofia_reg_find_reg_url(profile, dest, profile_name, buf, sizeof(buf))) {tech_pvt->dest = switch_core_session_strdup(nsession, buf);tech_pvt->local_url = switch_core_session_sprintf(nsession, "%s@%s", dest, profile_name);host = profile_name;} else {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Cannot locate registered user %s@%s\n", dest, profile_name);cause = SWITCH_CAUSE_USER_NOT_REGISTERED;goto error;}} else {host++;if (!strchr(host, '.') || switch_true(switch_event_get_header(var_event, "sip_gethostbyname"))) {struct sockaddr_in sa;struct hostent *he = gethostbyname(host);char buf[50] = "", *tmp;const char *ip;if (he) {memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));ip = switch_inet_ntop(AF_INET, &sa.sin_addr, buf, sizeof(buf));tmp = switch_string_replace(dest, host, ip);//host = switch_core_session_strdup(nsession, ip);//dest = switch_core_session_strdup(nsession, tmp);switch_channel_set_variable_printf(nchannel, "sip_route_uri", "sip:%s", tmp);free(tmp);}}tech_pvt->dest = switch_core_session_sprintf(nsession, "sip:%s%s", sip_destination_prefix, dest);tech_pvt->e_dest = switch_core_session_strdup(nsession, dest);}}switch_channel_set_variable_printf(nchannel, "sip_local_network_addr", "%s", profile->extsipip ? profile->extsipip : profile->sipip);switch_channel_set_variable(nchannel, "sip_profile_name", profile_name);if (switch_stristr("fs_path", tech_pvt->dest)) {char *remote_host = NULL;const char *s;if ((s = switch_stristr("fs_path=", tech_pvt->dest))) {s += 8;}if (s) {remote_host = switch_core_session_strdup(nsession, s);switch_url_decode(remote_host);}if (!zstr(remote_host)) {switch_split_user_domain(remote_host, NULL, &tech_pvt->mparams.remote_ip);}}if (zstr(tech_pvt->mparams.remote_ip)) {switch_split_user_domain(switch_core_session_strdup(nsession, tech_pvt->dest), NULL, &tech_pvt->mparams.remote_ip);}if (dest_to) {if (strchr(dest_to, '@')) {tech_pvt->dest_to = switch_core_session_sprintf(nsession, "sip:%s", dest_to);} else {tech_pvt->dest_to = switch_core_session_sprintf(nsession, "sip:%s@%s", dest_to, host ? host : profile->sipip);}}if (!tech_pvt->dest_to) {tech_pvt->dest_to = tech_pvt->dest;}if (!zstr(tech_pvt->dest) && switch_stristr("transport=ws", tech_pvt->dest)) {switch_channel_set_variable(nchannel, "media_webrtc", "true");switch_core_session_set_ice(nsession);}sofia_glue_attach_private(nsession, profile, tech_pvt, dest);if (tech_pvt->local_url) {switch_channel_set_variable(nchannel, "sip_local_url", tech_pvt->local_url);if (profile->pres_type) {const char *presence_id = switch_channel_get_variable(nchannel, "presence_id");if (zstr(presence_id)) {switch_channel_set_variable(nchannel, "presence_id", tech_pvt->local_url);}}}switch_channel_set_variable(nchannel, "sip_destination_url", tech_pvt->dest);......sofia_set_flag_locked(tech_pvt, TFLAG_OUTBOUND);sofia_clear_flag_locked(tech_pvt, TFLAG_LATE_NEGOTIATION);if (switch_channel_get_state(nchannel) == CS_NEW) {switch_channel_set_state(nchannel, CS_INIT);}tech_pvt->caller_profile = caller_profile;*new_session = nsession;cause = SWITCH_CAUSE_SUCCESS;if ((hval = switch_event_get_header(var_event, "sip_enable_soa"))) {if (switch_true(hval)) {sofia_set_flag(tech_pvt, TFLAG_ENABLE_SOA);} else {sofia_clear_flag(tech_pvt, TFLAG_ENABLE_SOA);}}if ((hval = switch_event_get_header(var_event, "sip_auto_answer")) && switch_true(hval)) {switch_channel_set_variable_printf(nchannel, "sip_h_Call-Info", ";answer-after=0", profile->sipip);switch_channel_set_variable(nchannel, "sip_invite_params", "intercom=true");}if (((hval = switch_event_get_header(var_event, "effective_callee_id_name")) ||(hval = switch_event_get_header(var_event, "sip_callee_id_name"))) && !zstr(hval)) {caller_profile->callee_id_name = switch_core_strdup(caller_profile->pool, hval);}if (((hval = switch_event_get_header(var_event, "effective_callee_id_number")) ||(hval = switch_event_get_header(var_event, "sip_callee_id_number"))) && !zstr(hval)) {caller_profile->callee_id_number = switch_core_strdup(caller_profile->pool, hval);}if (session) {const char *vval = NULL;switch_ivr_transfer_variable(session, nsession, SOFIA_REPLACES_HEADER);if (!(vval = switch_channel_get_variable(o_channel, "sip_copy_custom_headers")) || switch_true(vval)) {switch_ivr_transfer_variable(session, nsession, SOFIA_SIP_HEADER_PREFIX_T);}if (!(vval = switch_channel_get_variable(o_channel, "sip_copy_multipart")) || switch_true(vval)) {switch_ivr_transfer_variable(session, nsession, "sip_multipart");}switch_ivr_transfer_variable(session, nsession, "rtp_video_fmtp");switch_ivr_transfer_variable(session, nsession, "sip-force-contact");switch_ivr_transfer_variable(session, nsession, "sip_sticky_contact");if (!cid_locked) {switch_ivr_transfer_variable(session, nsession, "sip_cid_type");}if (switch_core_session_compare(session, nsession)) {/* It's another sofia channel! so lets cache what they use as a pt for telephone event sowe can keep it the same*/private_object_t *ctech_pvt;ctech_pvt = switch_core_session_get_private(session);switch_assert(ctech_pvt != NULL);tech_pvt->bte = ctech_pvt->te;tech_pvt->bcng_pt = ctech_pvt->cng_pt;if (!cid_locked) {tech_pvt->cid_type = ctech_pvt->cid_type;}if (sofia_test_flag(tech_pvt, TFLAG_ENABLE_SOA)) {sofia_set_flag(ctech_pvt, TFLAG_ENABLE_SOA);} else {sofia_clear_flag(ctech_pvt, TFLAG_ENABLE_SOA);}if (switch_channel_test_flag(o_channel, CF_ZRTP_PASSTHRU_REQ) && switch_channel_test_flag(o_channel, CF_ZRTP_HASH)) {const char *x = NULL;switch_core_media_pass_zrtp_hash2(session, nsession);switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "[zrtp_passthru] Setting a-leg inherit_codec=true\n");switch_channel_set_variable(o_channel, "inherit_codec", "true");if ((x = switch_channel_get_variable(o_channel, "ep_codec_string"))) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "[zrtp_passthru] Setting b-leg absolute_codec_string='%s'\n", x);switch_channel_set_variable(nchannel, "absolute_codec_string", x);}}/* SNARK: lets copy this across so we can see if we're the other leg of 3PCC + bypass_media... */if (sofia_test_flag(ctech_pvt, TFLAG_3PCC) && (switch_channel_test_flag(o_channel, CF_PROXY_MODE) || switch_channel_test_flag(o_channel, CF_PROXY_MEDIA))) {sofia_set_flag(tech_pvt, TFLAG_3PCC_INVITE);sofia_set_flag(tech_pvt, TFLAG_LATE_NEGOTIATION);} else {sofia_clear_flag(tech_pvt, TFLAG_3PCC_INVITE);}}switch_core_media_check_outgoing_proxy(nsession, session);}goto done;error:/* gateway pointer lock is really a readlock of the profile so we let the profile release below free that lock if we have a profile */if (gateway_ptr && !profile) {sofia_reg_release_gateway(gateway_ptr);}if (nsession) {switch_core_session_destroy(&nsession);}if (pool) {*pool = NULL;}
    done:if (profile) {if (cause == SWITCH_CAUSE_SUCCESS) {profile->ob_calls++;} else {profile->ob_failed_calls++;}sofia_glue_release_profile(profile);}return cause;
    }
  2. 以上步骤执行完毕,回到 switch_ivr_originate.c#switch_ivr_originate() 函数,接下来将调用 switch_channel.c#switch_channel_add_state_handler() 函数设置 FreeSWITCH 核心状态机在 channel 层级的回调函数表为 originate_state_handler,该函数表定义如下,具体函数实现暂且不表

    static const switch_state_handler_table_t originate_state_handlers = {/*.on_init */ NULL,/*.on_routing */ originate_on_routing,/*.on_execute */ NULL,/*.on_hangup */ NULL,/*.on_exchange_media */ NULL,/*.on_soft_execute */ originate_on_consume_media_transmit,/*.on_consume_media */ originate_on_consume_media_transmit
    };
    
  3. switch_ivr_originate.c#switch_ivr_originate() 函数接下来的关键步骤是判断是否有线程在处理当前新建 session,如没有则调用 switch_core_session.c#switch_core_session_thread_launch() 开启一个线程运转核心状态机。有关于 FreeSWITCH 核心状态机的状态流转处理读者可参考 FreeSWITCH 1.10 源码阅读(4)-从呼入处理分析核心状态机 了解,从本节步骤1可知 channel 已经处于 CS_INIT 状态,则在状态机线程中mod_sofia.c#sofia_on_init() 函数将被回调执行。该函数非常简练,关键点是判断标记 TFLAG_OUTBOUND存在则调用 sofia_glue.c#sofia_glue_do_invite() 函数向外发送 INVITE 请求

    static switch_status_t sofia_on_init(switch_core_session_t *session)
    {const char *hval = NULL;switch_channel_t *channel = switch_core_session_get_channel(session);private_object_t *tech_pvt = (private_object_t *) switch_core_session_get_private(session);switch_status_t status = SWITCH_STATUS_SUCCESS;switch_assert(tech_pvt != NULL);switch_mutex_lock(tech_pvt->sofia_mutex);switch_core_media_check_dtmf_type(session);switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s SOFIA INIT\n", switch_channel_get_name(channel));if (switch_channel_test_flag(channel, CF_PROXY_MODE) || switch_channel_test_flag(channel, CF_PROXY_MEDIA)) {switch_core_media_absorb_sdp(session);}if ((hval = switch_channel_get_variable(channel, "sip_watch_headers"))) {char *dupvar = NULL;char *watch_headers[10];unsigned int numhdrs = 0;unsigned int i = 0;dupvar = switch_core_session_strdup(session, hval);numhdrs = switch_separate_string(dupvar, ',', watch_headers, switch_arraylen(watch_headers));if (numhdrs) {char **wheaders = switch_core_session_alloc(session, ((numhdrs+1) * sizeof(wheaders[0])));for (i = 0; i < numhdrs; i++) {wheaders[i] = watch_headers[i];}wheaders[i] = NULL;tech_pvt->watch_headers = wheaders;}}if (switch_channel_test_flag(tech_pvt->channel, CF_RECOVERING) || switch_channel_test_flag(tech_pvt->channel, CF_RECOVERING_BRIDGE)) {sofia_set_flag(tech_pvt, TFLAG_RECOVERED);}if (sofia_test_flag(tech_pvt, TFLAG_OUTBOUND) || switch_channel_test_flag(tech_pvt->channel, CF_RECOVERING)) {if (sofia_glue_do_invite(session) != SWITCH_STATUS_SUCCESS) {switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);assert(switch_channel_get_state(channel) != CS_INIT);status = SWITCH_STATUS_FALSE;goto end;}}end:switch_mutex_unlock(tech_pvt->sofia_mutex);return status;
    }
  4. sofia_glue.c#sofia_glue_do_invite() 函数比较长,不过核心的处理只有两步:

    1. 准备 INVITE 请求报文,包括调用 switch_core_media.c#switch_core_media_choose_port() 函数选择 rtp 端口,调用 switch_core_media.c#switch_core_media_gen_local_sdp() 根据本地配置生成媒体协商的 sdp 报文
    2. 调用底层库函数 nua_invite() 向目标用户发送 INVITE 请求,在此实际发起外呼
    switch_status_t sofia_glue_do_invite(switch_core_session_t *session)
    {char *alert_info = NULL;const char *max_forwards = NULL;const char *alertbuf;const char *identity = NULL;private_object_t *tech_pvt = switch_core_session_get_private(session);switch_channel_t *channel = switch_core_session_get_channel(session);switch_caller_profile_t *caller_profile;const char *cid_name, *cid_num;char *e_dest = NULL;const char *holdstr = "";char *extra_headers = NULL;switch_status_t status = SWITCH_STATUS_FALSE;uint32_t session_timeout = tech_pvt->profile->session_timeout;const char *val;const char *rep;const char *call_id = NULL;char *route = NULL;char *route_uri = NULL;sofia_destination_t *dst = NULL;sofia_cid_type_t cid_type = tech_pvt->profile->cid_type;sip_cseq_t *cseq = NULL;const char *invite_record_route = switch_channel_get_variable(tech_pvt->channel, "sip_invite_record_route");const char *invite_route_uri = switch_channel_get_variable(tech_pvt->channel, "sip_invite_route_uri");const char *invite_full_from = switch_channel_get_variable(tech_pvt->channel, "sip_invite_full_from");const char *invite_full_to = switch_channel_get_variable(tech_pvt->channel, "sip_invite_full_to");const char *handle_full_from = switch_channel_get_variable(tech_pvt->channel, "sip_handle_full_from");const char *handle_full_to = switch_channel_get_variable(tech_pvt->channel, "sip_handle_full_to");const char *force_full_from = switch_channel_get_variable(tech_pvt->channel, "sip_force_full_from");const char *force_full_to = switch_channel_get_variable(tech_pvt->channel, "sip_force_full_to");const char *content_encoding = switch_channel_get_variable(tech_pvt->channel, "sip_content_encoding");char *mp = NULL, *mp_type = NULL;char *record_route = NULL;const char *recover_via = NULL;int require_timer = 1;uint8_t is_t38 = 0;const char *hold_char = "*";const char *session_id_header = sofia_glue_session_id_header(session, tech_pvt->profile);
    #ifdef NUTAG_CALL_TLS_ORQ_CONNECT_TIMEOUTconst char *sip_call_tls_orq_connect_timeout_str = switch_channel_get_variable(tech_pvt->channel, "sip_call_tls_orq_connect_timeout");uint32_t sip_call_tls_orq_connect_timeout = (sip_call_tls_orq_connect_timeout_str) ? atoi(sip_call_tls_orq_connect_timeout_str) : 0;
    #endifconst char *stir_shaken_attest = NULL;char *identity_to_free = NULL;const char *date = NULL;if (sofia_test_flag(tech_pvt, TFLAG_SIP_HOLD_INACTIVE) ||switch_true(switch_channel_get_variable_dup(tech_pvt->channel, "sofia_hold_inactive", SWITCH_FALSE, -1))) {hold_char = "#";}if (switch_channel_test_flag(tech_pvt->channel, CF_RECOVERING)) {const char *recover_contact = switch_channel_get_variable(tech_pvt->channel, "sip_recover_contact");recover_via = switch_channel_get_variable(tech_pvt->channel, "sip_recover_via");if (!zstr(invite_record_route)) {record_route = switch_core_session_sprintf(session, "Record-Route: %s", invite_record_route);}if (recover_contact) {char *tmp = switch_core_session_strdup(session, recover_contact);tech_pvt->redirected = sofia_glue_get_url_from_contact(tmp, 0);}}if ((rep = switch_channel_get_variable(channel, SOFIA_REPLACES_HEADER))) {switch_channel_set_variable(channel, SOFIA_REPLACES_HEADER, NULL);}switch_assert(tech_pvt != NULL);sofia_clear_flag_locked(tech_pvt, TFLAG_SDP);caller_profile = switch_channel_get_caller_profile(channel);if (!caller_profile) {switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);switch_goto_status(SWITCH_STATUS_FALSE, end);}if ((val = switch_channel_get_variable_dup(channel, "sip_require_timer", SWITCH_FALSE, -1)) && switch_false(val)) {require_timer = 0;}cid_name = caller_profile->caller_id_name;cid_num = caller_profile->caller_id_number;if (!tech_pvt->sent_invites && !switch_channel_test_flag(channel, CF_ANSWERED)) {switch_core_media_prepare_codecs(tech_pvt->session, SWITCH_FALSE);switch_core_media_check_video_codecs(tech_pvt->session);}check_decode(cid_name, session);check_decode(cid_num, session);if ((alertbuf = switch_channel_get_variable(channel, "alert_info"))) {alert_info = switch_core_session_sprintf(tech_pvt->session, "Alert-Info: %s", alertbuf);}if ((stir_shaken_attest = switch_channel_get_variable(tech_pvt->channel, "sip_stir_shaken_attest"))) {char date_buf[80] = "";char *dest = caller_profile->destination_number;check_decode(dest, session);switch_rfc822_date(date_buf, switch_micro_time_now());date = switch_core_session_strdup(tech_pvt->session, date_buf);identity = identity_to_free = sofia_stir_shaken_as_create_identity_header(tech_pvt->session, stir_shaken_attest, cid_num, dest);}if (!identity) {identity = switch_channel_get_variable(channel, "sip_h_identity");}if (!date) {date = switch_channel_get_variable(channel, "sip_h_date");}max_forwards = switch_channel_get_variable(channel, SWITCH_MAX_FORWARDS_VARIABLE);if ((status = switch_core_media_choose_port(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO, 0)) != SWITCH_STATUS_SUCCESS) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "Port Error!\n");goto end;}if (!switch_channel_get_private(tech_pvt->channel, "t38_options") || zstr(tech_pvt->mparams.local_sdp_str)) {switch_core_media_gen_local_sdp(session, SDP_TYPE_REQUEST, NULL, 0, NULL, 0);}......if (sofia_use_soa(tech_pvt)) {nua_invite(tech_pvt->nh,NUTAG_AUTOANSWER(0),//TAG_IF(zstr(tech_pvt->mparams.local_sdp_str), NUTAG_AUTOACK(0)),//TAG_IF(!zstr(tech_pvt->mparams.local_sdp_str), NUTAG_AUTOACK(1)),// The code above is breaking things...... grrr WE need this because we handle our own acks and there are 3pcc cases in there tooNUTAG_AUTOACK(0),NUTAG_SESSION_TIMER(tech_pvt->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(sofia_test_flag(tech_pvt, TFLAG_RECOVERED), NUTAG_INVITE_TIMER(UINT_MAX)),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(recover_via), SIPTAG_VIA_STR(recover_via)),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(identity), SIPTAG_IDENTITY_STR(identity)),TAG_IF(!zstr(date), SIPTAG_DATE_STR(date)),TAG_IF(!zstr(alert_info), SIPTAG_HEADER_STR(alert_info)),TAG_IF(!zstr(extra_headers), SIPTAG_HEADER_STR(extra_headers)),TAG_IF(sofia_test_pflag(tech_pvt->profile, PFLAG_PASS_CALLEE_ID), SIPTAG_HEADER_STR("X-FS-Support: " FREESWITCH_SUPPORT)),TAG_IF(!zstr(max_forwards), SIPTAG_MAX_FORWARDS_STR(max_forwards)),TAG_IF(!zstr(route_uri), NUTAG_PROXY(route_uri)),TAG_IF(!zstr(invite_route_uri), NUTAG_INITIAL_ROUTE_STR(invite_route_uri)),TAG_IF(!zstr(route), SIPTAG_ROUTE_STR(route)),TAG_IF(tech_pvt->profile->minimum_session_expires, NUTAG_MIN_SE(tech_pvt->profile->minimum_session_expires)),TAG_IF(cseq, SIPTAG_CSEQ(cseq)),TAG_IF(content_encoding, SIPTAG_CONTENT_ENCODING_STR(content_encoding)),TAG_IF(zstr(tech_pvt->mparams.local_sdp_str), SIPTAG_PAYLOAD_STR("")),TAG_IF(!zstr(tech_pvt->mparams.local_sdp_str), SOATAG_ADDRESS(tech_pvt->mparams.adv_sdp_audio_ip)),TAG_IF(!zstr(tech_pvt->mparams.local_sdp_str), SOATAG_USER_SDP_STR(tech_pvt->mparams.local_sdp_str)),TAG_IF(!zstr(tech_pvt->mparams.local_sdp_str), SOATAG_REUSE_REJECTED(1)),TAG_IF(is_t38, SOATAG_ORDERED_USER(1)),TAG_IF(!zstr(tech_pvt->mparams.local_sdp_str), SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE)),TAG_IF(!zstr(tech_pvt->mparams.local_sdp_str), SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL)),TAG_IF(rep, SIPTAG_REPLACES_STR(rep)),TAG_IF(!require_timer, NUTAG_TIMER_AUTOREQUIRE(0)),TAG_IF(!zstr(tech_pvt->mparams.local_sdp_str), SOATAG_HOLD(holdstr)), TAG_END());} else {nua_invite(tech_pvt->nh,NUTAG_AUTOANSWER(0),NUTAG_AUTOACK(0),NUTAG_SESSION_TIMER(tech_pvt->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(sofia_test_flag(tech_pvt, TFLAG_RECOVERED), NUTAG_INVITE_TIMER(UINT_MAX)),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(recover_via), SIPTAG_VIA_STR(recover_via)),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(sofia_test_pflag(tech_pvt->profile, PFLAG_PASS_CALLEE_ID), SIPTAG_HEADER_STR("X-FS-Support: " FREESWITCH_SUPPORT)),TAG_IF(!zstr(max_forwards), SIPTAG_MAX_FORWARDS_STR(max_forwards)),TAG_IF(!zstr(identity), SIPTAG_IDENTITY_STR(identity)),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(tech_pvt->profile->minimum_session_expires, NUTAG_MIN_SE(tech_pvt->profile->minimum_session_expires)),TAG_IF(!require_timer, NUTAG_TIMER_AUTOREQUIRE(0)),TAG_IF(cseq, SIPTAG_CSEQ(cseq)),TAG_IF(content_encoding, SIPTAG_CONTENT_ENCODING_STR(content_encoding)),NUTAG_MEDIA_ENABLE(0),SIPTAG_CONTENT_TYPE_STR(mp_type ? mp_type : "application/sdp"),SIPTAG_PAYLOAD_STR(mp ? mp : tech_pvt->mparams.local_sdp_str), TAG_IF(rep, SIPTAG_REPLACES_STR(rep)), SOATAG_HOLD(holdstr), TAG_END());}switch_safe_free(extra_headers);switch_safe_free(mp);tech_pvt->redirected = NULL;status = SWITCH_STATUS_SUCCESS;end:if (dst) {sofia_glue_free_destination(dst);}switch_safe_free(identity_to_free);return status;
    }
  5. channel 状态将在 switch_core_state_machine.c#switch_core_standard_on_init()被流转为 CS_ROUTING,则核心状态机触发状态变迁,此时会优先触发本节步骤2 channel 层级的回调函数 switch_ivr_originate.c#originate_on_routing()。可以看到该函数非常简单,核心逻辑就是将 channel 状态从 CS_ROUTING 流转到 CS_CONSUME_MEDIA,由于回调函数过程中 channel 状态改变,则在宏定义 switch_core_state_machine.c#STATE_MACRO 中不会回调状态机层级的标准回调函数,channel 状态停留在 CS_CONSUME_MEDIA

    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) {/* put the channel in a passive state until it is answered */switch_channel_set_state(channel, CS_CONSUME_MEDIA);}return SWITCH_STATUS_FALSE;
    }
    
  6. channel 状态变迁为 CS_CONSUME_MEDIA,核心状态机再次触发各层级回调,此时依然是优先触发本节步骤2 channel 层级的回调函数 switch_ivr_originate.c#originate_on_consume_media_transmit()。该函数比较简单,核心如下:

    1. 首先是将当前线程等待休眠一段时间,随后调用 switch_ivr.c#switch_ivr_parse_all_messages() 函数处理当前 session 私有消息队列中的消息
    2. 清除 channel 层级的状态机回调函数表
    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)) {while (switch_channel_test_flag(channel, CF_ORIGINATING) &&switch_channel_get_state(channel) == CS_CONSUME_MEDIA && !switch_channel_test_flag(channel, CF_TAGGED)) {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;
    }
    
  7. 以上步骤中核心状态机线程将休眠一段时间,则回到2.1节步骤1 originate 命令处理线程switch_ivr_originate.c#switch_ivr_originate() 函数执行完毕后switch_ivr.c#switch_ivr_session_transfer() 函数会把 channel 状态重新设置为 CS_ROUTING,当核心状态机线程唤醒后会继续驱动 channel 状态流转执行相应回调动作,不再赘述,至此 originate 命令单腿呼叫的流程基本结束

    SWITCH_DECLARE(switch_status_t) switch_ivr_session_transfer(switch_core_session_t *session, const char *extension, const char *dialplan,const char *context)
    {switch_channel_t *channel = switch_core_session_get_channel(session);switch_caller_profile_t *profile, *new_profile;switch_core_session_message_t msg = { 0 };switch_core_session_t *other_session;switch_channel_t *other_channel = NULL;const char *uuid = NULL;const char *max_forwards;const char *forwardvar_name = SWITCH_MAX_SESSION_TRANSFERS_VARIABLE; /* max_session_transfers has first priority for setting maximum */const char *forwardvar = switch_channel_get_variable(channel, forwardvar_name);int forwardval = 70;const char *use_dialplan = dialplan, *use_context = context;if (zstr(forwardvar)) {forwardvar_name = SWITCH_MAX_FORWARDS_VARIABLE; /* fall back to max_forwards variable for setting maximum */forwardvar = switch_channel_get_variable(channel, forwardvar_name);}if (!zstr(forwardvar)) {forwardval = atoi(forwardvar) - 1;}if (forwardval <= 0) {switch_channel_hangup(channel, SWITCH_CAUSE_EXCHANGE_ROUTING_ERROR);return SWITCH_STATUS_FALSE;}switch_ivr_check_hold(session);max_forwards = switch_core_session_sprintf(session, "%d", forwardval);switch_channel_set_variable(channel, forwardvar_name, max_forwards);switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE);switch_channel_clear_flag(channel, CF_ORIGINATING);/* clear all state handlers */switch_channel_clear_state_handler(channel, NULL);/* reset temp hold music */switch_channel_set_variable(channel, SWITCH_TEMP_HOLD_MUSIC_VARIABLE, NULL);switch_channel_execute_on(channel, "execute_on_blind_transfer");if ((profile = switch_channel_get_caller_profile(channel))) {const char *var;if (zstr(dialplan) && (var = switch_channel_get_variable(channel, "force_transfer_dialplan"))) {use_dialplan = var;}if (zstr(context) && (var = switch_channel_get_variable(channel, "force_transfer_context"))) {use_context = var;}if (zstr(use_dialplan)) {use_dialplan = profile->dialplan;if (!zstr(use_dialplan) && !strcasecmp(use_dialplan, "inline")) {use_dialplan = NULL;}}if (zstr(use_context)) {use_context = profile->context;}if (zstr(use_dialplan)) {use_dialplan = "XML";}if (zstr(use_context)) {use_context = "default";}if (zstr(extension)) {extension = "service";}if (switch_channel_test_flag(channel, CF_REUSE_CALLER_PROFILE)){new_profile = switch_channel_get_caller_profile(channel);} else {new_profile = switch_caller_profile_clone(session, profile);}new_profile->dialplan = switch_core_strdup(new_profile->pool, use_dialplan);new_profile->context = switch_core_strdup(new_profile->pool, use_context);new_profile->destination_number = switch_core_strdup(new_profile->pool, extension);new_profile->rdnis = switch_core_strdup(new_profile->pool, profile->destination_number);switch_channel_set_variable(channel, SWITCH_SIGNAL_BOND_VARIABLE, NULL);/* Set CF_TRANSFER flag before hanging up bleg to avoid race condition */switch_channel_set_flag(channel, CF_TRANSFER);/* If HANGUP_AFTER_BRIDGE is set to 'true', SWITCH_SIGNAL_BRIDGE_VARIABLE* will not have a value, so we need to check SWITCH_BRIDGE_VARIABLE */uuid = switch_channel_get_variable(channel, SWITCH_SIGNAL_BRIDGE_VARIABLE);if (!uuid) {uuid = switch_channel_get_variable(channel, SWITCH_BRIDGE_VARIABLE);}if (uuid && (other_session = switch_core_session_locate(uuid))) {other_channel = switch_core_session_get_channel(other_session);switch_channel_set_variable(other_channel, SWITCH_SIGNAL_BOND_VARIABLE, NULL);switch_core_session_rwunlock(other_session);}if ((uuid = switch_channel_get_variable(channel, SWITCH_SIGNAL_BRIDGE_VARIABLE))&& (other_session = switch_core_session_locate(uuid))) {other_channel = switch_core_session_get_channel(other_session);switch_channel_set_variable(channel, SWITCH_SIGNAL_BRIDGE_VARIABLE, NULL);switch_channel_set_variable(other_channel, SWITCH_SIGNAL_BRIDGE_VARIABLE, NULL);switch_channel_set_variable(channel, SWITCH_BRIDGE_VARIABLE, NULL);switch_channel_set_variable(other_channel, SWITCH_BRIDGE_VARIABLE, NULL);/* If we are transferring the CALLER out of the bridge, we do not want to hang up on them */switch_channel_set_variable(channel, SWITCH_HANGUP_AFTER_BRIDGE_VARIABLE, "false");switch_channel_hangup(other_channel, SWITCH_CAUSE_BLIND_TRANSFER);switch_ivr_media(uuid, SMF_NONE);switch_core_session_rwunlock(other_session);}if (!switch_channel_test_flag(channel, CF_REUSE_CALLER_PROFILE)){switch_channel_set_caller_profile(channel, new_profile); 	}switch_channel_set_state(channel, CS_ROUTING);switch_channel_audio_sync(channel);msg.message_id = SWITCH_MESSAGE_INDICATE_TRANSFER;msg.from = __FILE__;switch_core_session_receive_message(session, &msg);switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Transfer %s to %s[%s@%s]\n", switch_channel_get_name(channel), use_dialplan,extension, use_context);new_profile->transfer_source = switch_core_sprintf(new_profile->pool, "%ld:%s:bl_xfer:%s/%s/%s",(long) switch_epoch_time_now(NULL), new_profile->uuid_str,extension, use_context, use_dialplan);switch_channel_add_variable_var_check(channel, SWITCH_TRANSFER_HISTORY_VARIABLE, new_profile->transfer_source, SWITCH_FALSE, SWITCH_STACK_PUSH);switch_channel_set_variable_var_check(channel, SWITCH_TRANSFER_SOURCE_VARIABLE, new_profile->transfer_source, SWITCH_FALSE);return SWITCH_STATUS_SUCCESS;}return SWITCH_STATUS_FALSE;
    }


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部