1. 前言
在 FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程 中笔者分析了 sofia 模块对底层 Sofia-SIP 协议栈的封装使用,而实际上呼叫进程的推进是由上层的状态机流转完成处理的。通常一通会话的完整生命周期如下,大致可以将其划分为 3 个阶段:
- 会话初始化
- 会话的 dialplan(拨号计划) 路由及 App 执行
- 会话挂断及后续处理
2. 源码分析
FreeSWITCH 具有非常典型的分层结构,这一点也体现在 SIP 会话交互流程中,如下是一个简单示意图
- 底层 Sofia-SIP 负责实际处理 SIP 收发,并通过回调的方式将 SIP 会话的状态通知到上层 Sofia 模块
- Sofia 模块处理底层 Sofia-SIP 收到的消息,直接调用核心层函数将其通知到 FreeSWITCH 核心层
- FreeSWITCH 核心层进行业务逻辑处理,通过回调的方式调用下层模块处理
如下是 FreeSWITCH 对一通会话完整生命周期处理的源码时序,部分触发步骤有所省略,读者可结合 FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程 理解
2.1 会话的初始化
-
在 FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程 中笔者分析到 FreeSWITCH 处理外部 UA 的 INVITE 请求,其在系统中造成的影响是新建了一个 session 及其 channel,此时 channel 处于 CS_NEW 状态。对于底层的 Sofia-SIP 来说,处理 INVITE 请求后会进入到发送 100 Trying 响应的状态,该变化会通过
nua_i_state
事件上报到上层,最终触发sofia.c#our_sofia_event_callback()
函数内部执行sofia.c#sofia_handle_sip_i_state()
函数//sofia_dispatch_event_t *de static void our_sofia_event_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, sofia_profile_t *profile, nua_handle_t *nh, sofia_private_t *sofia_private, sip_t const *sip, sofia_dispatch_event_t *de, tagi_t tags[]) { ...... switch (event) { case nua_r_get_params: case nua_i_fork: case nua_r_info: break; case nua_i_bye: sofia_handle_sip_i_bye(session, status, phrase, nua, profile, nh, sofia_private, sip, de, tags); break; case nua_r_bye: sofia_handle_sip_r_bye(session, status, phrase, nua, profile, nh, sofia_private, sip, de, tags); break; case nua_r_notify: if (session) { sofia_handle_sip_r_notify(session, status, phrase, nua, profile, nh, sofia_private, sip, de, tags); } break; case nua_i_notify: sofia_handle_sip_i_notify(session, status, phrase, nua, profile, nh, sofia_private, sip, de, tags); break; case nua_r_register: sofia_reg_handle_sip_r_register(status, phrase, nua, profile, nh, sofia_private, sip, de, tags); break; case nua_i_options: sofia_handle_sip_i_options(status, phrase, nua, profile, nh, sofia_private, sip, de, tags); break; case nua_i_invite: if (session && sofia_private) { if (sofia_private->is_call > 1) { sofia_handle_sip_i_reinvite(session, nua, profile, nh, sofia_private, sip, de, tags); } else { sofia_private->is_call++; sofia_handle_sip_i_invite(session, nua, profile, nh, sofia_private, sip, de, tags); } } break; case nua_i_publish: sofia_presence_handle_sip_i_publish(nua, profile, nh, sofia_private, sip, de, tags); break; case nua_i_register: //nua_respond(nh, SIP_200_OK, SIPTAG_CONTACT(sip->sip_contact), NUTAG_WITH_THIS_MSG(de->data->e_msg), TAG_END()); //nua_handle_destroy(nh); sofia_reg_handle_sip_i_register(nua, profile, nh, &sofia_private, sip, de, tags); break; case nua_i_state: sofia_handle_sip_i_state(session, status, phrase, nua, profile, nh, sofia_private, sip, de, tags); break; ...... } }
-
sofia.c#sofia_handle_sip_i_state()
函数处理的分支非常多,如下是底层 SIP 会话状态的枚举nua_callstate
的定义,可知此时底层 SIP 会话实际处于nua_callstate_received
状态,则可以看到该函数核心处理如下:- 调用
sofia_media.c#sofia_media_negotiate_sdp()
函数进行 sdp 媒体协商匹配,不做深入分析 - 匹配不上直接调用库函数响应外部 488 状态码,匹配上则调用宏定义
switch_channel.h#switch_channel_set_state()
将 session 中 channel 的状态从 CS_NEW 流转到 CS_INIT 状态,实际调用到switch_channel.c#switch_channel_perform_set_state()
函数
enum nua_callstate { nua_callstate_init, /**<Initial state */ nua_callstate_authenticating, /**< 401/407 received */ nua_callstate_calling, /**< INVITE sent*/ nua_callstate_proceeding, /**< 18X received */ nua_callstate_completing, /**< 2XX received */ nua_callstate_received, /**< INVITE received */ nua_callstate_early, /**< 18X sent (w/SDP) */ nua_callstate_completed, /**< 2XX sent*/ nua_callstate_ready, /**< 2XX received, ACK sent*/ nua_callstate_terminating, /**< BYE sent */ nua_callstate_terminated /**< BYE complete */ };
static void sofia_handle_sip_i_state(switch_core_session_t *session, int status, char const *phrase, nua_t *nua, sofia_profile_t *profile, nua_handle_t *nh, sofia_private_t *sofia_private, sip_t const *sip, sofia_dispatch_event_t *de, tagi_t tags[]) { ...... switch ((enum nua_callstate) ss_state) { case nua_callstate_init: break; case nua_callstate_authenticating: break; case nua_callstate_calling: tech_pvt->sent_last_invite = 1; tech_pvt->sent_invites++; break; ..... case nua_callstate_received: tech_pvt->recv_invites++; tech_pvt->sent_last_invite = 0; if (!sofia_test_flag(tech_pvt, TFLAG_SDP)) { if (switch_core_session_get_partner(session, &other_session) == SWITCH_STATUS_SUCCESS) { private_object_t *other_tech_pvt = switch_core_session_get_private(other_session); int r = sofia_test_flag(other_tech_pvt, TFLAG_REINVITED); switch_core_session_rwunlock(other_session); if (r) { /* Due to a race between simultaneous reinvites to both legs of a bridge, an earlier call to nua_invite silently failed. So we reject the incoming invite with a 491 and redo the failed outgoing invite. */ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Other leg already handling a reinvite, so responding with 491\n"); nua_respond(tech_pvt->nh, SIP_491_REQUEST_PENDING, TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_END()); sofia_glue_do_invite(session); goto done; } } if (r_sdp && !sofia_test_flag(tech_pvt, TFLAG_SDP)) { if (switch_channel_test_flag(channel, CF_PROXY_MODE)) { switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "RECEIVED_NOMEDIA"); sofia_set_flag_locked(tech_pvt, TFLAG_READY); if (switch_channel_get_state(channel) == CS_NEW) { switch_channel_set_state(channel, CS_INIT); } sofia_set_flag(tech_pvt, TFLAG_SDP); goto done; } else if (switch_channel_test_flag(tech_pvt->channel, CF_PROXY_MEDIA)) { switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "PROXY MEDIA"); sofia_set_flag_locked(tech_pvt, TFLAG_READY); if (switch_channel_get_state(channel) == CS_NEW) { switch_channel_set_state(channel, CS_INIT); } } else if (sofia_test_flag(tech_pvt, TFLAG_LATE_NEGOTIATION)) { switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "DELAYED NEGOTIATION"); sofia_set_flag_locked(tech_pvt, TFLAG_READY); if (switch_channel_get_state(channel) == CS_NEW) { switch_channel_set_state(channel, CS_INIT); } } else { uint8_t match = 0; if (tech_pvt->mparams.num_codecs) { match = sofia_media_negotiate_sdp(session, r_sdp, SDP_TYPE_REQUEST); } if (!match) { if (switch_channel_get_state(channel) != CS_NEW) { nua_respond(tech_pvt->nh, SIP_488_NOT_ACCEPTABLE, TAG_END()); } } else { nua_handle_t *bnh; sip_replaces_t *replaces; su_home_t *home = NULL; switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "RECEIVED"); sofia_set_flag_locked(tech_pvt, TFLAG_READY); if (switch_channel_get_state(channel) == CS_NEW) { switch_channel_set_state(channel, CS_INIT); } else { nua_respond(tech_pvt->nh, SIP_200_OK, TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_END()); } sofia_set_flag(tech_pvt, TFLAG_SDP); if (replaces_str) { home = su_home_new(sizeof(*home)); switch_assert(home != NULL); if ((replaces = sip_replaces_make(home, replaces_str)) && (bnh = nua_handle_by_replaces(nua, replaces))) { sofia_private_t *b_private; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Processing Replaces Attended Transfer\n"); while (switch_channel_get_state(channel) < CS_EXECUTE) { switch_yield(10000); } if ((b_private = nua_handle_magic(bnh))) { const char *br_b = switch_channel_get_partner_uuid(channel); char *br_a = b_private->uuid; if (br_b) { switch_core_session_t *tmp; if (switch_true(switch_channel_get_variable(channel, "recording_follow_transfer")) && (tmp = switch_core_session_locate(br_a))) { switch_ivr_transfer_recordings(session, tmp); switch_core_session_rwunlock(tmp); } switch_channel_set_variable_printf(channel, "transfer_to", "att:%s", br_b); mark_transfer_record(session, br_a, br_b); switch_ivr_uuid_bridge(br_a, br_b); switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "ATTENDED_TRANSFER"); sofia_clear_flag_locked(tech_pvt, TFLAG_SIP_HOLD); switch_channel_clear_flag(channel, CF_LEG_HOLDING); sofia_clear_flag_locked(tech_pvt, TFLAG_HOLD_LOCK); switch_channel_hangup(channel, SWITCH_CAUSE_ATTENDED_TRANSFER); } else { switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "ATTENDED_TRANSFER_ERROR"); switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } } else { switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "ATTENDED_TRANSFER_ERROR"); switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } nua_handle_unref_user(bnh); } su_home_unref(home); home = NULL; } goto done; } switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "NO CODECS"); switch_channel_hangup(channel, SWITCH_CAUSE_INCOMPATIBLE_DESTINATION); } } else { if (sofia_test_pflag(profile, PFLAG_3PCC)) { if (switch_channel_test_flag(channel, CF_PROXY_MODE) || switch_channel_test_flag(channel, CF_PROXY_MEDIA)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "No SDP in INVITE and 3pcc=yes cannot work with bypass or proxy media, hanging up.\n"); switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "3PCC DISABLED"); switch_channel_hangup(channel, SWITCH_CAUSE_MANDATORY_IE_MISSING); } else { switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "RECEIVED_NOSDP"); switch_core_media_choose_port(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO, 0); switch_core_media_prepare_codecs(session, 1); switch_channel_set_state(channel, CS_HIBERNATE); switch_core_media_gen_local_sdp(session, SDP_TYPE_REQUEST, NULL, 0, NULL, 0); sofia_set_flag_locked(tech_pvt, TFLAG_3PCC); if (sofia_use_soa(tech_pvt)) { nua_respond(tech_pvt->nh, SIP_200_OK, SIPTAG_CONTACT_STR(tech_pvt->reply_contact), SOATAG_USER_SDP_STR(tech_pvt->mparams.local_sdp_str), SOATAG_REUSE_REJECTED(1), SOATAG_AUDIO_AUX("cn telephone-event"), TAG_IF(sofia_test_pflag(profile, PFLAG_DISABLE_100REL), NUTAG_INCLUDE_EXTRA_SDP(1)), TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_END()); } else { nua_respond(tech_pvt->nh, SIP_200_OK, NUTAG_MEDIA_ENABLE(0), SIPTAG_CONTACT_STR(tech_pvt->reply_contact), SIPTAG_CONTENT_TYPE_STR("application/sdp"), SIPTAG_PAYLOAD_STR(tech_pvt->mparams.local_sdp_str), TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_END()); } } } else if (sofia_test_pflag(profile, PFLAG_3PCC_PROXY)) { //3PCC proxy mode delays the 200 OK until the call is answered // so can be made to work with bypass media as we have time to find out what the other end thinks codec offer should be... switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "RECEIVED_NOSDP"); sofia_set_flag_locked(tech_pvt, TFLAG_3PCC); //switch_core_media_choose_port(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO, 0); //switch_core_media_gen_local_sdp(session, NULL, 0, NULL, 0); sofia_set_flag(tech_pvt, TFLAG_LATE_NEGOTIATION); //Moves into CS_INIT so call moves forward into the dialplan if (switch_channel_get_state(channel) == CS_NEW) { switch_channel_set_state(channel, CS_INIT); } } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "No SDP in INVITE and 3pcc not enabled, hanging up.\n"); switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "3PCC DISABLED"); switch_channel_hangup(channel, SWITCH_CAUSE_MANDATORY_IE_MISSING); } goto done; } } else if (tech_pvt && sofia_test_flag(tech_pvt, TFLAG_SDP) && !r_sdp) { sofia_set_flag_locked(tech_pvt, TFLAG_NOSDP_REINVITE); if ((switch_channel_test_flag(channel, CF_PROXY_MODE) || switch_channel_test_flag(channel, CF_PROXY_MEDIA)) && sofia_test_pflag(profile, PFLAG_3PCC_PROXY)) { sofia_set_flag_locked(tech_pvt, TFLAG_3PCC); sofia_clear_flag(tech_pvt, TFLAG_ENABLE_SOA); if (switch_core_session_get_partner(session, &other_session) == SWITCH_STATUS_SUCCESS) { switch_core_session_message_t *msg; if (switch_core_session_compare(session, other_session)) { private_object_t *other_tech_pvt = switch_core_session_get_private(other_session); sofia_clear_flag(other_tech_pvt, TFLAG_ENABLE_SOA); } msg = switch_core_session_alloc(other_session, sizeof(*msg)); msg->message_id = SWITCH_MESSAGE_INDICATE_MEDIA_REDIRECT; msg->from = __FILE__; msg->string_arg = NULL; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Passing NOSDP to other leg.\n"); switch_core_session_queue_message(other_session, msg); switch_core_session_rwunlock(other_session); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "NOSDP Re-INVITE to a proxy mode channel that is not in a bridge.\n"); switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } goto done; } switch_channel_set_flag(channel, CF_NOSDP_REINVITE); if (switch_channel_var_true(channel, "sip_unhold_nosdp")) { switch_core_media_gen_local_sdp(session, SDP_TYPE_RESPONSE, NULL, 0, "sendrecv", zstr(tech_pvt->mparams.local_sdp_str) || !switch_channel_test_flag(channel, CF_PROXY_MODE)); } else { switch_core_media_gen_local_sdp(session, SDP_TYPE_RESPONSE, NULL, 0, NULL, zstr(tech_pvt->mparams.local_sdp_str) || !switch_channel_test_flag(channel, CF_PROXY_MODE)); } switch_channel_clear_flag(channel, CF_NOSDP_REINVITE); if (zstr(tech_pvt->mparams.local_sdp_str)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Cannot find a SDP\n"); switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } else { if (sofia_use_soa(tech_pvt)) { nua_respond(tech_pvt->nh, SIP_200_OK, SIPTAG_CONTACT_STR(tech_pvt->reply_contact), SOATAG_USER_SDP_STR(tech_pvt->mparams.local_sdp_str), SOATAG_REUSE_REJECTED(1), SOATAG_AUDIO_AUX("cn telephone-event"), TAG_IF(sofia_test_pflag(profile, PFLAG_DISABLE_100REL), NUTAG_INCLUDE_EXTRA_SDP(1)), TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_END()); } else { nua_respond(tech_pvt->nh, SIP_200_OK, NUTAG_MEDIA_ENABLE(0), SIPTAG_CONTACT_STR(tech_pvt->reply_contact), SIPTAG_CONTENT_TYPE_STR("application/sdp"), SIPTAG_PAYLOAD_STR(tech_pvt->mparams.local_sdp_str), TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_END()); } } goto done; } else { ss_state = nua_callstate_completed; goto state_process; } break; ...... } }
- 调用
-
switch_channel.c#switch_channel_perform_set_state()
函数的关键处理是以下几步:- 首先进行 channel 状态转移的前置检查
- 检查通过则调用
switch_channel.c#careful_set()
函数加锁更新 channel 状态,接着调用switch_core_session.c#switch_core_session_signal_state_change()
函数回调端点接口
的状态变化回调函数,不做深入分析
SWITCH_DECLARE(switch_channel_state_t) switch_channel_perform_set_state(switch_channel_t *channel, const char *file, const char *func, int line, switch_channel_state_t state) { switch_channel_state_t last_state; int ok = 0; switch_assert(channel != NULL); switch_assert(state <= CS_DESTROY); switch_mutex_lock(channel->state_mutex); last_state = channel->state; switch_assert(last_state <= CS_DESTROY); if (last_state == state) { goto done; } if (last_state >= CS_HANGUP && state < last_state) { goto done; } /* STUB for more dev case CS_INIT: switch(state) { case CS_NEW: case CS_INIT: case CS_EXCHANGE_MEDIA: case CS_SOFT_EXECUTE: case CS_ROUTING: case CS_EXECUTE: case CS_HANGUP: case CS_DESTROY: default: break; } break; */ switch (last_state) { case CS_NEW: case CS_RESET: switch (state) { default: ok++; break; } break; case CS_INIT: switch (state) { case CS_EXCHANGE_MEDIA: case CS_SOFT_EXECUTE: case CS_ROUTING: case CS_EXECUTE: case CS_PARK: case CS_CONSUME_MEDIA: case CS_HIBERNATE: case CS_RESET: ok++; default: break; } break; case CS_EXCHANGE_MEDIA: switch (state) { case CS_SOFT_EXECUTE: case CS_ROUTING: case CS_EXECUTE: case CS_PARK: case CS_CONSUME_MEDIA: case CS_HIBERNATE: case CS_RESET: ok++; default: break; } break; case CS_SOFT_EXECUTE: switch (state) { case CS_EXCHANGE_MEDIA: case CS_ROUTING: case CS_EXECUTE: case CS_PARK: case CS_CONSUME_MEDIA: case CS_HIBERNATE: case CS_RESET: ok++; default: break; } break; case CS_PARK: switch (state) { case CS_EXCHANGE_MEDIA: case CS_ROUTING: case CS_EXECUTE: case CS_SOFT_EXECUTE: case CS_HIBERNATE: case CS_RESET: case CS_CONSUME_MEDIA: ok++; default: break; } break; case CS_CONSUME_MEDIA: switch (state) { case CS_EXCHANGE_MEDIA: case CS_ROUTING: case CS_EXECUTE: case CS_SOFT_EXECUTE: case CS_HIBERNATE: case CS_RESET: case CS_PARK: ok++; default: break; } break; case CS_HIBERNATE: switch (state) { case CS_EXCHANGE_MEDIA: case CS_INIT: case CS_ROUTING: case CS_EXECUTE: case CS_SOFT_EXECUTE: case CS_PARK: case CS_CONSUME_MEDIA: case CS_RESET: ok++; default: break; } break; case CS_ROUTING: switch (state) { case CS_EXCHANGE_MEDIA: case CS_EXECUTE: case CS_SOFT_EXECUTE: case CS_PARK: case CS_CONSUME_MEDIA: case CS_HIBERNATE: case CS_RESET: ok++; default: break; } break; case CS_EXECUTE: switch (state) { case CS_EXCHANGE_MEDIA: case CS_SOFT_EXECUTE: case CS_ROUTING: case CS_PARK: case CS_CONSUME_MEDIA: case CS_HIBERNATE: case CS_RESET: ok++; default: break; } break; case CS_HANGUP: switch (state) { case CS_REPORTING: case CS_DESTROY: ok++; default: break; } break; case CS_REPORTING: switch (state) { case CS_DESTROY: ok++; default: break; } break; default: break; } if (ok) { switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, switch_channel_get_uuid(channel), SWITCH_LOG_DEBUG, "(%s) State Change %s -> %s\n", channel->name, state_names[last_state], state_names[state]); careful_set(channel, &channel->state, state); if (state == CS_HANGUP && !channel->hangup_cause) { channel->hangup_cause = SWITCH_CAUSE_NORMAL_CLEARING; } if (state <= CS_DESTROY) { switch_core_session_signal_state_change(channel->session); } } else { switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, switch_channel_get_uuid(channel), SWITCH_LOG_WARNING, "(%s) Invalid State Change %s -> %s\n", channel->name, state_names[last_state], state_names[state]); /* we won't tolerate an invalid state change so we can make sure we are as robust as a nice cup of dark coffee! */ /* not cool lets crash this bad boy and figure out wtf is going on */ switch_assert(channel->state >= CS_HANGUP); } done: switch_mutex_unlock(channel->state_mutex); return channel->state; }
-
channel 状态发生改变,则在每个 session 单独的线程任务
switch_core_state_machine.c#switch_core_session_run()
内部 while 循环中将执行对应分支代码,这里的关键处理如下:- 首先通过宏定义
switch_core_state_machine.c#STATE_MACRO()
执行各个层级的状态回调函数,通知 channel 状态变化 - 调用
switch_ivr.c#switch_ivr_parse_all_events()
函数处理当前 session 内部各个消息队列中的消息
SWITCH_DECLARE(void) switch_core_session_run(switch_core_session_t *session) { ...... while ((state = switch_channel_get_state(session->channel)) != CS_DESTROY) { if (switch_channel_test_flag(session->channel, CF_BLOCK_STATE)) { switch_channel_wait_for_flag(session->channel, CF_BLOCK_STATE, SWITCH_FALSE, 0, NULL); if ((state = switch_channel_get_state(session->channel)) == CS_DESTROY) { break; } } midstate = state; if (state != switch_channel_get_running_state(session->channel) || state >= CS_HANGUP) { int index = 0; int proceed = 1; int global_proceed = 1; int do_extra_handlers = 1; switch_io_event_hook_state_run_t *ptr; switch_status_t rstatus = SWITCH_STATUS_SUCCESS; switch_channel_set_running_state(session->channel, state); switch_channel_clear_flag(session->channel, CF_TRANSFER); switch_channel_clear_flag(session->channel, CF_REDIRECT); switch_ivr_parse_all_messages(session); if (session->endpoint_interface->io_routines->state_run) { rstatus = session->endpoint_interface->io_routines->state_run(session); } if (rstatus == SWITCH_STATUS_SUCCESS) { for (ptr = session->event_hooks.state_run; ptr; ptr = ptr->next) { if ((rstatus = ptr->state_run(session)) != SWITCH_STATUS_SUCCESS) { break; } } } switch (state) { case CS_NEW: /* Just created, Waiting for first instructions */ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%s) State NEW\n", switch_channel_get_name(session->channel)); break; case CS_DESTROY: goto done; case CS_REPORTING: /* Call Detail */ { switch_core_session_reporting_state(session); switch_channel_set_state(session->channel, CS_DESTROY); } goto done; case CS_HANGUP: /* Deactivate and end the thread */ { switch_core_session_hangup_state(session, SWITCH_TRUE); if (switch_channel_test_flag(session->channel, CF_VIDEO)) { switch_core_session_wake_video_thread(session); } switch_channel_set_state(session->channel, CS_REPORTING); } break; case CS_INIT: /* Basic setup tasks */ { switch_event_t *event; STATE_MACRO(init, "INIT"); if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_CREATE) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(session->channel, event); switch_event_fire(&event); } 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: /* Look for a dialplan and find something to do */ STATE_MACRO(routing, "ROUTING"); break; case CS_RESET: /* Reset */ STATE_MACRO(reset, "RESET"); break; /* These other states are intended for prolonged durations so we do not signal lock for them */ case CS_EXECUTE: /* Execute an Operation */ STATE_MACRO(execute, "EXECUTE"); break; case CS_EXCHANGE_MEDIA: /* loop all data back to source */ STATE_MACRO(exchange_media, "EXCHANGE_MEDIA"); break; case CS_SOFT_EXECUTE: /* send/recieve data to/from another channel */ STATE_MACRO(soft_execute, "SOFT_EXECUTE"); break; case CS_PARK: /* wait in limbo */ STATE_MACRO(park, "PARK"); break; case CS_CONSUME_MEDIA: /* wait in limbo */ STATE_MACRO(consume_media, "CONSUME_MEDIA"); break; case CS_HIBERNATE: /* sleep */ STATE_MACRO(hibernate, "HIBERNATE"); break; case CS_NONE: abort(); break; } check_presence(session); if (midstate == CS_DESTROY) { break; } } endstate = switch_channel_get_state(session->channel); if (endstate == switch_channel_get_running_state(session->channel)) { if (endstate == CS_NEW) { switch_yield(20000); switch_ivr_parse_all_events(session); if (!--new_loops) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s %s Abandoned\n", session->uuid_str, switch_core_session_get_name(session)); switch_channel_set_flag(session->channel, CF_NO_CDR); switch_channel_hangup(session->channel, SWITCH_CAUSE_WRONG_CALL_STATE); } } else { switch_channel_state_thread_lock(session->channel); switch_ivr_parse_all_events(session); if (switch_channel_test_flag(session->channel, CF_STATE_REPEAT)) { switch_channel_clear_flag(session->channel, CF_STATE_REPEAT); } else if (switch_channel_get_state(session->channel) == switch_channel_get_running_state(session->channel)) { switch_channel_set_flag(session->channel, CF_THREAD_SLEEPING); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG1, "%s session thread sleep state: %s!\n", switch_channel_get_name(session->channel), switch_channel_state_name(switch_channel_get_running_state(session->channel))); switch_thread_cond_wait(session->cond, session->mutex); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG1, "%s session thread wake state: %s!\n", switch_channel_get_name(session->channel), switch_channel_state_name(switch_channel_get_running_state(session->channel))); switch_channel_clear_flag(session->channel, CF_THREAD_SLEEPING); } switch_channel_state_thread_unlock(session->channel); switch_ivr_parse_all_events(session); } } } done: switch_mutex_unlock(session->mutex); switch_clear_flag(session, SSF_THREAD_RUNNING); }
- 首先通过宏定义
-
switch_core_state_machine.c#STATE_MACRO()
定义如下,可以看到其核心处理在于执行各个层级的状态变化回调函数。FreeSWITCH 从下到上将回调函数分为以下四层,回调函数将被依次调用- endpoint 层级:
switch_endpoint_interface_t#state_handler
函数表,由端点接口模块重写,例如 mod_sofia.c#sofia_event_handlers - channel 层级:
switch_channel_t#state_handlers
函数表,无固定实现,由switch_channel.c#switch_channel_add_state_handler()
添加 - core 核心层级:
switch_runtime#state_handlers()
函数表,无固定实现,由switch_core.c#switch_core_add_state_handler()
添加 - state_machine 状态机层级:
switch_core_standard_on_xxx 函数
,固定实现,例如switch_core_state_machine.c#switch_core_standard_on_init()
函数
#define STATE_MACRO(__STATE, __STATE_STR) do { \ midstate = state; \ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%s) State %s\n", switch_channel_get_name(session->channel), __STATE_STR); \ if (state < CS_HANGUP && switch_channel_get_callstate(session->channel) == CCS_UNHELD) { \ switch_channel_set_callstate(session->channel, CCS_ACTIVE); \ } \ switch_core_session_request_video_refresh(session); \ switch_core_media_gen_key_frame(session); \ proceed = 1; \ while (do_extra_handlers && (application_state_handler = switch_channel_get_state_handler(session->channel, index++)) != 0) { \ if (!switch_test_flag(application_state_handler, SSH_FLAG_PRE_EXEC)) {\ continue; \ } \ if (!application_state_handler->on_##__STATE \ || (application_state_handler->on_##__STATE \ && application_state_handler->on_##__STATE(session) == SWITCH_STATUS_SUCCESS \ )) { \ proceed++; \ continue; \ } else { \ proceed = 0; \ break; \ } \ } \ index = 0; \ if (!proceed) global_proceed = 0; \ proceed = 1; \ while (do_extra_handlers && proceed && (application_state_handler = switch_core_get_state_handler(index++)) != 0) { \ if (!switch_test_flag(application_state_handler, SSH_FLAG_PRE_EXEC)) { \ continue; \ } \ if (!application_state_handler->on_##__STATE || \ (application_state_handler->on_##__STATE && \ application_state_handler->on_##__STATE(session) == SWITCH_STATUS_SUCCESS \ )) { \ proceed++; \ continue; \ } else { \ proceed = 0; \ break; \ } \ } \ index = 0; \ if (!proceed) global_proceed = 0; \ if (!driver_state_handler->on_##__STATE || (driver_state_handler->on_##__STATE(session) == SWITCH_STATUS_SUCCESS )) { \ while (do_extra_handlers && (application_state_handler = switch_channel_get_state_handler(session->channel, index++)) != 0) { \ if (switch_test_flag(application_state_handler, SSH_FLAG_PRE_EXEC)) { \ continue; \ } \ if (!application_state_handler->on_##__STATE \ || (application_state_handler->on_##__STATE \ && application_state_handler->on_##__STATE(session) == SWITCH_STATUS_SUCCESS \ )) { \ proceed++; \ continue; \ } else { \ proceed = 0; \ break; \ } \ } \ index = 0; \ if (!proceed) global_proceed = 0; \ proceed = 1; \ while (do_extra_handlers && proceed && (application_state_handler = switch_core_get_state_handler(index++)) != 0) { \ if (switch_test_flag(application_state_handler, SSH_FLAG_PRE_EXEC)) { \ continue; \ } \ if (!application_state_handler->on_##__STATE || \ (application_state_handler->on_##__STATE && \ application_state_handler->on_##__STATE(session) == SWITCH_STATUS_SUCCESS \ )) { \ proceed++; \ continue; \ } else { \ proceed = 0; \ break; \ } \ } \ if (!proceed || midstate != switch_channel_get_state(session->channel)) global_proceed = 0; \ if (global_proceed) { \ switch_core_standard_on_##__STATE(session); \ } \ } \ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%s) State %s going to sleep\n", switch_channel_get_name(session->channel), __STATE_STR); \ } while (silly)
- endpoint 层级:
-
作为 endpoint 接口的 Sofia 模块在加载时将函数表
mod_sofia.c#sofia_event_handlers
安装在端点接口结构体的 state_handler 属性,其定义如下,则当 channel 状态流转到 CS_INIT,可知mod_sofia.c#sofia_on_init()
函数将被调用switch_state_handler_table_t sofia_event_handlers = { /*.on_init */ sofia_on_init, /*.on_routing */ sofia_on_routing, /*.on_execute */ sofia_on_execute, /*.on_hangup */ sofia_on_hangup, /*.on_exchange_media */ sofia_on_exchange_media, /*.on_soft_execute */ sofia_on_soft_execute, /*.on_consume_media */ NULL, /*.on_hibernate */ sofia_on_hibernate, /*.on_reset */ sofia_on_reset, /*.on_park */ NULL, /*.on_reporting */ NULL, /*.on_destroy */ sofia_on_destroy };
-
mod_sofia.c#sofia_on_init()
函数比较简单,大多是一些 channel 属性的设置和判断,值得注意的是如果当前会话是外呼会话,则将调用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; }
-
最后回调的是
switch_core_state_machine.c#switch_core_standard_on_init()
函数,可以看到此处的核心逻辑是将 channel 状态从 CS_INIT 流转到 CS_ROUTING,则当switch_core_state_machine.c#switch_core_session_run()
内部 while 循环进入下一轮周期时会回调相关函数static void switch_core_standard_on_init(switch_core_session_t *session) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Standard INIT\n", switch_channel_get_name(session->channel)); if (switch_channel_test_flag(session->channel, CF_RECOVERING_BRIDGE)) { switch_channel_set_state(session->channel, CS_RESET); } else { if (switch_channel_test_flag(session->channel, CF_RECOVERING)) { switch_channel_set_state(session->channel, CS_EXECUTE); } else { switch_channel_set_state(session->channel, CS_ROUTING); } } switch_channel_clear_flag(session->channel, CF_RECOVERING); }
-
以上状态回调函数处理完,此时回到本节步骤4第2步,
switch_ivr.c#switch_ivr_parse_all_events()
函数是当前 session 相关所有事件的处理入口,其核心处理分为以下两步:switch_ivr.c#switch_ivr_parse_all_messages()
函数处理 session->message_queue 队列中的消息- 在 while 循环中不断调用
switch_ivr.c#switch_ivr_parse_next_event()
函数处理 seesion 私有的 session->private_event_queue_pri / session->private_event_queue 队列中的事件
SWITCH_DECLARE(switch_status_t) switch_ivr_parse_all_events(switch_core_session_t *session) { switch_channel_t *channel; uint32_t stack_count = 0; if ((stack_count = switch_core_session_stack_count(session, 0)) > SWITCH_MAX_STACKS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error %s too many stacked extensions [depth=%d]\n", switch_core_session_get_name(session), stack_count); return SWITCH_STATUS_FALSE; } switch_core_session_stack_count(session, 1); switch_ivr_parse_all_messages(session); channel = switch_core_session_get_channel(session); if (!switch_channel_test_flag(channel, CF_PROXY_MODE) && switch_channel_test_flag(channel, CF_BLOCK_BROADCAST_UNTIL_MEDIA)) { if (switch_channel_media_up(channel)) { switch_channel_clear_flag(channel, CF_BLOCK_BROADCAST_UNTIL_MEDIA); } else { goto done; } } while (switch_ivr_parse_next_event(session) == SWITCH_STATUS_SUCCESS) {} done: switch_core_session_stack_count(session, -1); return SWITCH_STATUS_SUCCESS; }
-
switch_ivr.c#switch_ivr_parse_all_messages()
函数的逻辑比较简单,其实就是消费 session->message_queue 队列的消息,消息的处理函数为switch_ivr.c#switch_ivr_process_indications()
。这个队列的数据大都由底层 SIP 会话状态变化触发的上层回调生产,整体上看它的作用是监听底层 SIP 状态变化驱动上层 channel 状态变化,会通过回调调用到mod_sofia.c#sofia_receive_message()
,不做深入讨论SWITCH_DECLARE(switch_status_t) switch_ivr_parse_all_messages(switch_core_session_t *session) { switch_core_session_message_t *message; int i = 0; switch_ivr_parse_all_signal_data(session); while (switch_core_session_dequeue_message(session, &message) == SWITCH_STATUS_SUCCESS) { i++; if (switch_ivr_process_indications(session, message) == SWITCH_STATUS_SUCCESS) { switch_core_session_free_message(&message); } else { switch_core_session_receive_message(session, message); message = NULL; } } return i ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; } SWITCH_DECLARE(switch_status_t) switch_ivr_process_indications(switch_core_session_t *session, switch_core_session_message_t *message) { switch_status_t status = SWITCH_STATUS_SUCCESS; switch_channel_t *channel = switch_core_session_get_channel(session); switch(message->message_id) { case SWITCH_MESSAGE_INDICATE_ANSWER: if (switch_channel_answer(channel) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } break; case SWITCH_MESSAGE_INDICATE_PROGRESS: if (switch_channel_pre_answer(channel) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } break; case SWITCH_MESSAGE_INDICATE_RINGING: if (switch_channel_ring_ready(channel) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } break; case SWITCH_MESSAGE_INDICATE_RESPOND: switch_core_session_receive_message(session, message); status = SWITCH_STATUS_SUCCESS; break; default: status = SWITCH_STATUS_FALSE; break; } return status; }
-
switch_ivr.c#switch_ivr_parse_next_event()
函数的关键处理是从 seesion 私有的 session->private_event_queue_pri / session->private_event_queue 队列中取出数据,调用switch_ivr.c#switch_ivr_parse_event()
消费。这两个队列中的数据很大一部分源自 esl 外部连接,主要用途是基于当前 session 进行指令处理,例如 execute 各种 app。至此,session 会话的初始化基本结束SWITCH_DECLARE(switch_status_t) switch_ivr_parse_next_event(switch_core_session_t *session) { switch_event_t *event; switch_status_t status = SWITCH_STATUS_FALSE; if (switch_core_session_dequeue_private_event(session, &event) == SWITCH_STATUS_SUCCESS) { status = switch_ivr_parse_event(session, event); event->event_id = SWITCH_EVENT_PRIVATE_COMMAND; switch_event_prep_for_delivery(event); switch_channel_event_set_data(switch_core_session_get_channel(session), event); switch_event_fire(&event); } return status; }
2.2 会话的路由及 App 执行
-
在上一节中,channel 的状态已经由 CS_INIT 流转到 CS_ROUTING,则在
switch_core_state_machine.c#switch_core_session_run()
内部 while 循环中会触发switch_core_state_machine.c#STATE_MACRO()
宏调用各个层级的状态回调函数,首先调到的是mod_sofia.c#sofia_on_routing()
。这个函数比较简单,值得注意的是,如果当前会话符合一定条件,例如是呼入会话并且配置了自动发送 100 Trying 等,则调用mod_sofia.c#sofia_acknowledge_call()
函数直接给外部呼入 UA 响应 100 Trying 报文static switch_status_t sofia_on_routing(switch_core_session_t *session) { private_object_t *tech_pvt = (private_object_t *) switch_core_session_get_private(session); switch_channel_t *channel = switch_core_session_get_channel(session); switch_assert(tech_pvt != NULL); if (sofia_test_pflag(tech_pvt->profile, PFLAG_AUTO_INVITE_100) && !switch_channel_test_flag(channel, CF_ANSWERED) && switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_INBOUND) { if (sofia_acknowledge_call(session) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Call appears to be already acknowledged\n"); } } if (!sofia_test_flag(tech_pvt, TFLAG_HOLD_LOCK)) { sofia_clear_flag_locked(tech_pvt, TFLAG_SIP_HOLD); switch_channel_clear_flag(channel, CF_LEG_HOLDING); } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s SOFIA ROUTING\n", switch_channel_get_name(switch_core_session_get_channel(session))); return SWITCH_STATUS_SUCCESS; } static switch_status_t sofia_acknowledge_call(switch_core_session_t *session) { struct private_object *tech_pvt = switch_core_session_get_private(session); const char *session_id_header = sofia_glue_session_id_header(session, tech_pvt->profile); if (!tech_pvt->sent_100) { nua_respond(tech_pvt->nh, SIP_100_TRYING, TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_END()); tech_pvt->sent_100 = 1; return SWITCH_STATUS_SUCCESS; } return SWITCH_STATUS_FALSE; }
-
由上一节内容可知,接下来回调的是
switch_core_state_machine.c#switch_core_standard_on_routing()
函数,其核心处理如下:- 首先通过
dialplan_interface->hunt_function()
执行指定的 dialplan 模块的回调方法,此处 dialplan 默认为 XML,则将调用mod_dialplan_xml.c#dialplan_hunt()
函数查找拨号计划配置 - 如果找到了符合要求的拨号计划配置,则调用
switch_channel.c#switch_channel_set_caller_extension()
将其暂存起来,并通过宏定义switch_channel.h#switch_channel_set_state()
将 channel 状态从 CS_ROUTING 流转到 CS_EXECUTE
static void switch_core_standard_on_routing(switch_core_session_t *session) { switch_dialplan_interface_t *dialplan_interface = NULL; switch_caller_profile_t *caller_profile; switch_caller_extension_t *extension = NULL; char *expanded = NULL; char *dpstr = NULL; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Standard ROUTING\n", switch_channel_get_name(session->channel)); switch_channel_set_variable(session->channel, "call_uuid", switch_core_session_get_uuid(session)); if ((switch_channel_test_flag(session->channel, CF_ANSWERED) || switch_channel_test_flag(session->channel, CF_EARLY_MEDIA) || switch_channel_test_flag(session->channel, CF_SIGNAL_BRIDGE_TTL)) && switch_channel_test_flag(session->channel, CF_PROXY_MODE)) { switch_ivr_media(session->uuid_str, SMF_NONE); } if ((caller_profile = switch_channel_get_caller_profile(session->channel)) == 0) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Can't get profile!\n"); switch_channel_hangup(session->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return; } else { char *dp[25]; int argc, x, count = 0; if ((extension = switch_channel_get_queued_extension(session->channel))) { switch_channel_set_caller_extension(session->channel, extension); switch_channel_set_state(session->channel, CS_EXECUTE); goto end; } if (!zstr(caller_profile->dialplan)) { if ((dpstr = switch_core_session_strdup(session, caller_profile->dialplan))) { expanded = switch_channel_expand_variables(session->channel, dpstr); argc = switch_separate_string(expanded, ',', dp, (sizeof(dp) / sizeof(dp[0]))); for (x = 0; x < argc; x++) { char *dpname = dp[x]; char *dparg = NULL; if (dpname) { if ((dparg = strchr(dpname, ':'))) { *dparg++ = '\0'; } } else { continue; } if (!(dialplan_interface = switch_loadable_module_get_dialplan_interface(dpname))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Dialplan [%s] not found, skipping\n", dpname); continue; } count++; extension = dialplan_interface->hunt_function(session, dparg, NULL); UNPROTECT_INTERFACE(dialplan_interface); if (extension) { switch_channel_set_caller_extension(session->channel, extension); switch_channel_set_state(session->channel, CS_EXECUTE); goto end; } } } } if (!count) { if (switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_OUTBOUND) { if (switch_channel_test_flag(session->channel, CF_ANSWERED)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "No Dialplan on answered channel, changing state to HANGUP\n"); switch_channel_hangup(session->channel, SWITCH_CAUSE_NO_ROUTE_DESTINATION); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "No Dialplan, changing state to CONSUME_MEDIA\n"); switch_channel_set_state(session->channel, CS_CONSUME_MEDIA); } goto end; } } } if (!extension) { if (switch_ivr_blind_transfer_ack(session, SWITCH_FALSE) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "No Route, Aborting\n"); switch_channel_hangup(session->channel, SWITCH_CAUSE_NO_ROUTE_DESTINATION); } } end: if (expanded && dpstr && expanded != dpstr) { free(expanded); } }
- 首先通过
-
mod_dialplan_xml.c#dialplan_hunt()
函数的核心逻辑由以下几步组成,一个示例的拨号计划配置如下:- 首先调用
mod_dialplan_xml.c#dialplan_xml_locate()
定位获取拨号计划相关配置,这个配置可能从本地内存的 XML 树中取得,也可以通过 xml_curl 模块从外部服务器上获取,不做深入讨论 - 其次是遍历取得的拨号计划配置,在 while 循环中调用
mod_dialplan_xml.c#parse_exten()
结合当前会话的信息进行拨号计划的解析匹配,匹配条件来自 condition 节点,一旦有一个拨号计划匹配上则跳出循环,具体做法本文不做深入分析,感兴趣的读者可自行研读
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <document type="freeswitch/xml"> <section name="dialplan" description="RE Dial Plan For FreeSwitch"> <context name="default"> <extension name="nathan_local"> <condition field="destination_number" expression="^(99[0-1][0-9]*)$"> <action application="answer"/> <action application="set" data="variable=1"/> <action application="echo"/> </condition> </extension> </context> </section> </document>
SWITCH_STANDARD_DIALPLAN(dialplan_hunt) { switch_caller_extension_t *extension = NULL; switch_channel_t *channel = switch_core_session_get_channel(session); switch_xml_t alt_root = NULL, cfg, xml = NULL, xcontext, xexten = NULL; char *alt_path = (char *) arg; const char *hunt = NULL; if (!caller_profile) { if (!(caller_profile = switch_channel_get_caller_profile(channel))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error Obtaining Profile!\n"); goto done; } } if (!caller_profile->context) { caller_profile->context = "default"; } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Processing %s <%s>->%s in context %s\n", caller_profile->caller_id_name, caller_profile->caller_id_number, caller_profile->destination_number, caller_profile->context); /* get our handle to the "dialplan" section of the config */ if (!zstr(alt_path)) { switch_xml_t conf = NULL, tag = NULL; if (!(alt_root = switch_xml_parse_file_simple(alt_path))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Open of [%s] failed\n", alt_path); goto done; } if ((conf = switch_xml_find_child(alt_root, "section", "name", "dialplan")) && (tag = switch_xml_find_child(conf, "dialplan", NULL, NULL))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Getting dialplan from alternate path: %s\n", alt_path); xml = alt_root; cfg = tag; } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Open of dialplan failed\n"); goto done; } } else { if (dialplan_xml_locate(session, caller_profile, &xml, &cfg) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Open of dialplan failed\n"); goto done; } } /* get a handle to the context tag */ if (!(xcontext = switch_xml_find_child(cfg, "context", "name", caller_profile->context))) { if (!(xcontext = switch_xml_find_child(cfg, "context", "name", "global"))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Context %s not found\n", caller_profile->context); goto done; } } if ((hunt = switch_channel_get_variable(channel, "auto_hunt")) && switch_true(hunt)) { xexten = switch_xml_find_child(xcontext, "extension", "name", caller_profile->destination_number); } if (!xexten) { xexten = switch_xml_child(xcontext, "extension"); } while (xexten) { int proceed = 0; const char *cont = switch_xml_attr(xexten, "continue"); const char *exten_name = switch_xml_attr(xexten, "name"); if (!exten_name) { exten_name = "UNKNOWN"; } if ( switch_core_test_flag(SCF_DIALPLAN_TIMESTAMPS) ) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Dialplan: %s parsing [%s->%s] continue=%s\n", switch_channel_get_name(channel), caller_profile->context, exten_name, cont ? cont : "false"); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG_CLEAN(session), SWITCH_LOG_DEBUG, "Dialplan: %s parsing [%s->%s] continue=%s\n", switch_channel_get_name(channel), caller_profile->context, exten_name, cont ? cont : "false"); } proceed = parse_exten(session, caller_profile, xexten, &extension, exten_name, 0); if (proceed && !switch_true(cont)) { break; } xexten = xexten->next; } switch_xml_free(xml); xml = NULL; done: switch_xml_free(xml); return extension; } static switch_status_t dialplan_xml_locate(switch_core_session_t *session, switch_caller_profile_t *caller_profile, switch_xml_t *root, switch_xml_t *node) { switch_channel_t *channel = switch_core_session_get_channel(session); switch_status_t status = SWITCH_STATUS_GENERR; switch_event_t *params = NULL; switch_event_create(¶ms, SWITCH_EVENT_REQUEST_PARAMS); switch_assert(params); switch_channel_event_set_data(channel, params); switch_caller_profile_event_set_data(caller_profile, "Hunt", params); status = switch_xml_locate("dialplan", NULL, NULL, NULL, root, node, params, SWITCH_FALSE); switch_event_destroy(¶ms); return status; }
- 首先调用
-
经过以上处理,在
switch_core_state_machine.c#switch_core_session_run()
内部 while 循环中执行完switch_ivr.c#switch_ivr_parse_all_events()
函数则本轮 CS_ROUTING 处理结束,下一轮将通过switch_core_state_machine.c#STATE_MACRO()
宏调用各个层级的 CSC_EXECTUE 状态回调函数。与上文类似,此时首先将调用mod_sofia.c#sofia_on_execute()
,该函数几乎没有业务逻辑,略过。接着将触发switch_core_state_machine.c#switch_core_standard_on_execute()
,这个函数的作用比较关键:- 首先调用函数
switch_channel.c#switch_channel_get_caller_extension()
取出本节步骤2第2步暂存的拨号计划配置,在 while 循环中不断通过宏定义switch_core.h#switch_core_session_execute_application()
调用switch_core_session.c#switch_core_session_execute_application_get_flags()
函数执行拨号计划 action 节点配置的 app - 拨号计划配置的 app 全部执行完毕,如果 channel 仍旧处于 CS_EXECUTE 状态,则通过宏定义
switch_channel.h#switch_channel_hangup()
调用switch_channel.c#switch_channel_perform_hangup()
函数执行挂断
static void switch_core_standard_on_execute(switch_core_session_t *session) { switch_caller_extension_t *extension; const char *uuid; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Standard EXECUTE\n", switch_channel_get_name(session->channel)); switch_channel_clear_flag(session->channel, CF_RECOVERING); switch_channel_set_variable(session->channel, "call_uuid", switch_core_session_get_uuid(session)); if (switch_channel_get_variable(session->channel, "recovered") && !switch_channel_test_flag(session->channel, CF_RECOVERED)) { switch_channel_set_flag(session->channel, CF_RECOVERED); } top: switch_channel_clear_flag(session->channel, CF_RESET); switch_core_session_video_reset(session); switch_channel_audio_sync(session->channel); switch_channel_video_sync(session->channel); if ((extension = switch_channel_get_caller_extension(session->channel)) == 0) { switch_channel_hangup(session->channel, SWITCH_CAUSE_NORMAL_CLEARING); return; } while (switch_channel_get_state(session->channel) == CS_EXECUTE && extension->current_application) { switch_caller_application_t *current_application = extension->current_application; extension->current_application = extension->current_application->next; if (switch_core_session_execute_application(session, current_application->application_name, current_application->application_data) != SWITCH_STATUS_SUCCESS) { return; } if (switch_channel_test_flag(session->channel, CF_RESET)) { goto top; } } if (switch_channel_ready(session->channel) && switch_channel_get_state(session->channel) == CS_EXECUTE && switch_channel_test_flag(session->channel, CF_CONFIRM_BLIND_TRANSFER) && (uuid = switch_channel_get_variable(session->channel, "blind_transfer_uuid"))) { switch_core_session_t *other_session; if ((other_session = switch_core_session_locate(uuid))) { switch_core_session_message_t msg = { 0 }; msg.message_id = SWITCH_MESSAGE_INDICATE_BLIND_TRANSFER_RESPONSE; msg.from = __FILE__; msg.numeric_arg = 0; switch_core_session_receive_message(other_session, &msg); switch_core_session_rwunlock(other_session); switch_channel_set_variable(session->channel, "park_timeout", "10:blind_transfer"); switch_channel_set_state(session->channel, CS_PARK); switch_channel_clear_flag(session->channel, CF_CONFIRM_BLIND_TRANSFER); } } if (switch_channel_ready(session->channel) && switch_channel_get_state(session->channel) == CS_EXECUTE) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "%s has executed the last dialplan instruction, hanging up.\n", switch_channel_get_name(session->channel)); switch_channel_hangup(session->channel, SWITCH_CAUSE_NORMAL_CLEARING); } }
- 首先调用函数
-
switch_core_session.c#switch_core_session_execute_application_get_flags()
函数的关键处理如下:- 首先通过
switch_loadable_module.h#switch_loadable_module_get_application_interface()
获取指定名称的 app 函数结构体 - 调用
switch_core_session.c#switch_core_session_exec()
函数执行 app
SWITCH_DECLARE(switch_status_t) switch_core_session_execute_application_get_flags(switch_core_session_t *session, const char *app, const char *arg, int32_t *flags) { switch_application_interface_t *application_interface; switch_status_t status = SWITCH_STATUS_SUCCESS; switch_core_session_request_video_refresh(session); switch_core_media_gen_key_frame(session); if (switch_channel_down_nosig(session->channel)) { char *p; if (!arg && (p = strstr(app, "::"))) { *p++ = '0'; *p++ = '0'; arg = p; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s ASYNC CALL CONVERTED TO INLINE %s(%s)\n", switch_channel_get_name(session->channel), app, switch_str_nil(arg)); } if ((application_interface = switch_loadable_module_get_application_interface(app)) == 0) { return SWITCH_STATUS_FALSE; } if (switch_test_flag(application_interface, SAF_ZOMBIE_EXEC)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s ZOMBIE EXEC %s(%s)\n", switch_channel_get_name(session->channel), app, switch_str_nil(arg)); goto exec; } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Channel is hungup and application '%s' does not have the zombie_exec flag.\n", switch_channel_get_name(session->channel), app); switch_goto_status(SWITCH_STATUS_IGNORE, done); } if (!arg && strstr(app, "::")) { return switch_core_session_execute_application_async(session, app, arg); } if ((application_interface = switch_loadable_module_get_application_interface(app)) == 0) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid Application %s\n", app); switch_channel_hangup(session->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); switch_goto_status(SWITCH_STATUS_FALSE, done); } if (!application_interface->application_function) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No Function for %s\n", app); switch_channel_hangup(session->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); switch_goto_status(SWITCH_STATUS_FALSE, done); } if (flags && application_interface->flags) { *flags = application_interface->flags; } if (!switch_test_flag(application_interface, SAF_SUPPORT_NOMEDIA) && (switch_channel_test_flag(session->channel, CF_VIDEO))) { switch_core_session_request_video_refresh(session); } if (switch_channel_test_flag(session->channel, CF_PROXY_MODE) && !switch_test_flag(application_interface, SAF_SUPPORT_NOMEDIA)) { switch_ivr_media(session->uuid_str, SMF_NONE); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Application %s Requires media on channel %s!\n", app, switch_channel_get_name(session->channel)); } else if (!switch_test_flag(application_interface, SAF_SUPPORT_NOMEDIA) && !switch_channel_media_ready(session->channel)) { if (switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_INBOUND) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Application %s Requires media! pre_answering channel %s\n", app, switch_channel_get_name(session->channel)); if (switch_channel_pre_answer(session->channel) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Well, that didn't work very well did it? ...\n"); switch_goto_status(SWITCH_STATUS_FALSE, done); } } else { uint32_t ready = 0, sanity = 2000; do { sanity--; ready = switch_channel_media_up(session->channel); switch_cond_next(); } while(!ready && sanity); if (!ready) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Cannot execute app '%s' media required on an outbound channel that does not have media established\n", app); switch_goto_status(SWITCH_STATUS_FALSE, done); } } } if (switch_channel_text_only(session->channel) && !switch_test_flag(application_interface, SAF_SUPPORT_NOMEDIA) && !switch_test_flag(application_interface, SAF_SUPPORT_TEXT_ONLY)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Application %s does not support text-only mode on channel %s!\n", app, switch_channel_get_name(session->channel)); switch_channel_hangup(session->channel, SWITCH_CAUSE_SERVICE_NOT_IMPLEMENTED); switch_goto_status(SWITCH_STATUS_FALSE, done); } exec: switch_core_session_exec(session, application_interface, arg); done: UNPROTECT_INTERFACE(application_interface); return status; }
- 首先通过
-
switch_core_session.c#switch_core_session_exec()
函数的核心逻辑是通过函数指针application_interface->application_function()
调用目标 app 的函数,本文以 answer 为例,此处将调用到mod_dptools.c#answer_function()
函数SWITCH_DECLARE(switch_status_t) switch_core_session_exec(switch_core_session_t *session, const switch_application_interface_t *application_interface, const char *arg) { switch_app_log_t *log, *lp; switch_event_t *event; const char *var; switch_channel_t *channel = switch_core_session_get_channel(session); char *expanded = NULL; const char *app, *app_uuid_var, *app_uuid_name; switch_core_session_message_t msg = { 0 }; char delim = ','; int scope = 0; char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; char *app_uuid = uuid_str; switch_bool_t expand_variables = !switch_true(switch_channel_get_variable(session->channel, "app_disable_expand_variables")); if ((app_uuid_var = switch_channel_get_variable(channel, "app_uuid"))) { app_uuid = (char *)app_uuid_var; switch_channel_set_variable(channel, "app_uuid", NULL); } else { switch_uuid_str(uuid_str, sizeof(uuid_str)); } if((app_uuid_name = switch_channel_get_variable(channel, "app_uuid_name"))) { switch_channel_set_variable(channel, "app_uuid_name", NULL); } switch_assert(application_interface); app = application_interface->interface_name; if (arg) { if (expand_variables) { expanded = switch_channel_expand_variables(session->channel, arg); } else { expanded = (char *)arg; } } if (expand_variables && expanded && *expanded == '%' && (*(expanded+1) == '[' || *(expanded+2) == '[')) { char *p, *dup; switch_event_t *ovars = NULL; p = expanded + 1; if (*p != '[') { delim = *p; p++; } dup = strdup(p); if (expanded != arg) { switch_safe_free(expanded); } switch_event_create_brackets(dup, '[', ']', delim, &ovars, &expanded, SWITCH_TRUE); free(dup); switch_channel_set_scope_variables(session->channel, &ovars); scope = 1; } if ( switch_core_test_flag(SCF_DIALPLAN_TIMESTAMPS) ) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "EXECUTE [depth=%d] %s %s(%s)\n", switch_core_session_stack_count(session, 0), switch_channel_get_name(session->channel), app, switch_str_nil(expanded)); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG_CLEAN(session), SWITCH_LOG_INFO, "EXECUTE [depth=%d] %s %s(%s)\n", switch_core_session_stack_count(session, 0), switch_channel_get_name(session->channel), app, switch_str_nil(expanded)); } if ((var = switch_channel_get_variable(session->channel, "verbose_presence")) && switch_true(var)) { char *myarg = NULL; if (expanded) { myarg = switch_mprintf("%s(%s)", app, expanded); } else if (!zstr(arg)) { myarg = switch_mprintf("%s(%s)", app, arg); } else { myarg = switch_mprintf("%s", app); } if (myarg) { switch_channel_presence(session->channel, "unknown", myarg, NULL); switch_safe_free(myarg); } } if (!(var = switch_channel_get_variable(session->channel, SWITCH_DISABLE_APP_LOG_VARIABLE)) || (!(switch_true(var)))) { log = switch_core_session_alloc(session, sizeof(*log)); log->app = switch_core_session_strdup(session, application_interface->interface_name); if (expanded) { log->arg = switch_core_session_strdup(session, expanded); } log->stamp = switch_time_now(); for (lp = session->app_log; lp && lp->next; lp = lp->next); if (lp) { lp->next = log; } else { session->app_log = log; } } switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_VARIABLE, application_interface->interface_name); switch_channel_set_variable_var_check(channel, SWITCH_CURRENT_APPLICATION_DATA_VARIABLE, expanded, SWITCH_FALSE); switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, NULL); if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_EXECUTE) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(session->channel, event); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application", application_interface->interface_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-Data", expanded); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-UUID", app_uuid); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-UUID-Name", app_uuid_name); switch_event_fire(&event); } switch_channel_clear_flag(session->channel, CF_BREAK); switch_assert(application_interface->application_function); switch_channel_set_variable(session->channel, SWITCH_CURRENT_APPLICATION_VARIABLE, application_interface->interface_name); msg.from = __FILE__; msg.message_id = SWITCH_MESSAGE_INDICATE_APPLICATION_EXEC; msg.string_array_arg[0] = application_interface->interface_name; msg.string_array_arg[1] = expanded; switch_core_session_receive_message(session, &msg); application_interface->application_function(session, expanded); if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_EXECUTE_COMPLETE) == SWITCH_STATUS_SUCCESS) { const char *resp = switch_channel_get_variable(session->channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE); switch_channel_event_set_data(session->channel, event); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application", application_interface->interface_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-Data", expanded); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-Response", resp ? resp : "_none_"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-UUID", app_uuid); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-UUID-Name", app_uuid_name); switch_event_fire(&event); } msg.message_id = SWITCH_MESSAGE_INDICATE_APPLICATION_EXEC_COMPLETE; switch_core_session_receive_message(session, &msg); if (expanded != arg) { switch_safe_free(expanded); } if (scope) { switch_channel_set_scope_variables(session->channel, NULL); } return SWITCH_STATUS_SUCCESS; }
-
mod_dptools.c#answer_function()
函数简单明了,就是通过宏定义switch_channel.h#switch_channel_answer()
调用switch_channel.c#switch_channel_perform_answer()
将上层的应答动作通知到底层 Sofia-SIPSWITCH_STANDARD_APP(answer_function) { switch_channel_t *channel = switch_core_session_get_channel(session); const char *arg = (char *) data; if (zstr(arg)) { arg = switch_channel_get_variable(channel, "answer_flags"); } if (!zstr(arg)) { if (switch_stristr("is_conference", arg)) { switch_channel_set_flag(channel, CF_CONFERENCE); } if (switch_stristr("decode_video", arg)) { switch_channel_set_flag_recursive(channel, CF_VIDEO_DECODED_READ); } if (switch_stristr("debug_video", arg)) { switch_channel_set_flag_recursive(channel, CF_VIDEO_DEBUG_READ); } } switch_channel_answer(channel); }
-
switch_channel.c#switch_channel_perform_answer()
的核心逻辑是触发switch_core_session.c#switch_core_session_perform_receive_message()
执行,将SWITCH_MESSAGE_INDICATE_ANSWER
消息往底层传递SWITCH_DECLARE(switch_status_t) switch_channel_perform_answer(switch_channel_t *channel, const char *file, const char *func, int line) { switch_core_session_message_t msg = { 0 }; switch_status_t status = SWITCH_STATUS_SUCCESS; switch_assert(channel != NULL); if (switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND) { return SWITCH_STATUS_SUCCESS; } if (channel->hangup_cause || channel->state >= CS_HANGUP) { return SWITCH_STATUS_FALSE; } if (switch_channel_test_flag(channel, CF_ANSWERED)) { return SWITCH_STATUS_SUCCESS; } msg.message_id = SWITCH_MESSAGE_INDICATE_ANSWER; msg.from = channel->name; status = switch_core_session_perform_receive_message(channel->session, &msg, file, func, line); if (status == SWITCH_STATUS_SUCCESS) { switch_channel_perform_mark_answered(channel, file, func, line); if (!switch_channel_test_flag(channel, CF_EARLY_MEDIA)) { switch_channel_audio_sync(channel); } } else { switch_channel_hangup(channel, SWITCH_CAUSE_INCOMPATIBLE_DESTINATION); } if (switch_core_session_in_thread(channel->session) && !switch_channel_test_flag(channel, CF_PROXY_MODE) && !switch_channel_test_flag(channel, CF_HAS_TEXT)) { const char *delay; if ((delay = switch_channel_get_variable(channel, "answer_delay"))) { uint32_t msec = atoi(delay); if (msec) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(channel->session), SWITCH_LOG_DEBUG, "Answer delay for %u msec\n", msec); switch_ivr_sleep(channel->session, msec, SWITCH_TRUE, NULL); } } } return status; }
-
switch_core_session.c#switch_core_session_perform_receive_message()
函数比较简单,关键逻辑是通过端点接口
的回调session->endpoint_interface->io_routines->receive_message()
将上层动作通知到底层SWITCH_DECLARE(switch_status_t) switch_core_session_perform_receive_message(switch_core_session_t *session, switch_core_session_message_t *message, const char *file, const char *func, int line) { switch_io_event_hook_receive_message_t *ptr; switch_status_t status = SWITCH_STATUS_SUCCESS; switch_assert(session != NULL); if (message->message_id == SWITCH_MESSAGE_INDICATE_SIGNAL_DATA) { if (session->endpoint_interface->io_routines->receive_message) { status = session->endpoint_interface->io_routines->receive_message(session, message); } switch_core_session_free_message(&message); return status; } if ((status = switch_core_session_read_lock_hangup(session)) != SWITCH_STATUS_SUCCESS) { return status; } if (!message->_file) { message->_file = file; } if (!message->_func) { message->_func = func; } if (!message->_line) { message->_line = line; } if (message->message_id > SWITCH_MESSAGE_INVALID-1) { message->message_id = SWITCH_MESSAGE_INVALID-1; } switch_log_printf(SWITCH_CHANNEL_ID_LOG, message->_file, message->_func, message->_line, switch_core_session_get_uuid(session), SWITCH_LOG_DEBUG1, "%s receive message [%s]\n", switch_channel_get_name(session->channel), message_names[message->message_id]); if (message->message_id == SWITCH_MESSAGE_INDICATE_CLEAR_PROGRESS) { switch_channel_clear_flag(session->channel, CF_EARLY_MEDIA); } if (message->message_id == SWITCH_MESSAGE_INDICATE_MEDIA) { switch_channel_set_flag(session->channel, CF_PROXY_OFF); } if (message->message_id == SWITCH_MESSAGE_INDICATE_DISPLAY) { char *arg = NULL; if (zstr(message->string_array_arg[0]) && !zstr(message->string_arg)) { arg = switch_core_session_strdup(session, message->string_arg); switch_separate_string(arg, '|', (char **)message->string_array_arg, 2); } if (!zstr(message->string_array_arg[0])) { switch_channel_set_variable(session->channel, "last_sent_callee_id_name", message->string_array_arg[0]); } if (!zstr(message->string_array_arg[1])) { switch_channel_set_variable(session->channel, "last_sent_callee_id_number", message->string_array_arg[1]); } if (switch_true(switch_channel_get_variable(session->channel, SWITCH_IGNORE_DISPLAY_UPDATES_VARIABLE))) { switch_log_printf(SWITCH_CHANNEL_ID_LOG, message->_file, message->_func, message->_line, switch_core_session_get_uuid(session), SWITCH_LOG_DEBUG1, "Ignoring display update.\n"); status = SWITCH_STATUS_SUCCESS; goto end; } } if (switch_channel_down_nosig(session->channel)) { switch_log_printf(SWITCH_CHANNEL_ID_LOG, message->_file, message->_func, message->_line, switch_core_session_get_uuid(session), SWITCH_LOG_DEBUG, "%s skip receive message [%s] (channel is hungup already)\n", switch_channel_get_name(session->channel), message_names[message->message_id]); } else { if (session->media_handle) { status = switch_core_media_receive_message(session, message); } if (status == SWITCH_STATUS_SUCCESS) { if (session->endpoint_interface->io_routines->receive_message) { status = session->endpoint_interface->io_routines->receive_message(session, message); } } } if (status == SWITCH_STATUS_SUCCESS) { for (ptr = session->event_hooks.receive_message; ptr; ptr = ptr->next) { if ((status = ptr->receive_message(session, message)) != SWITCH_STATUS_SUCCESS) { break; } } if (message->message_id == SWITCH_MESSAGE_INDICATE_BRIDGE && switch_channel_test_flag(session->channel, CF_CONFIRM_BLIND_TRANSFER)) { switch_core_session_t *other_session; const char *uuid = switch_channel_get_variable(session->channel, "blind_transfer_uuid"); switch_channel_clear_flag(session->channel, CF_CONFIRM_BLIND_TRANSFER); if (!zstr(uuid) && (other_session = switch_core_session_locate(uuid))) { switch_core_session_message_t msg = { 0 }; msg.message_id = SWITCH_MESSAGE_INDICATE_BLIND_TRANSFER_RESPONSE; msg.from = __FILE__; msg.numeric_arg = 1; switch_core_session_receive_message(other_session, &msg); switch_core_session_rwunlock(other_session); } } } message->_file = NULL; message->_func = NULL; message->_line = 0; if (switch_channel_up_nosig(session->channel)) { if (message->message_id == SWITCH_MESSAGE_INDICATE_BRIDGE || message->message_id == SWITCH_MESSAGE_INDICATE_UNBRIDGE) { switch_core_media_bug_flush_all(session); switch_core_recovery_track(session); } switch (message->message_id) { case SWITCH_MESSAGE_REDIRECT_AUDIO: case SWITCH_MESSAGE_INDICATE_ANSWER: case SWITCH_MESSAGE_INDICATE_PROGRESS: case SWITCH_MESSAGE_INDICATE_BRIDGE: case SWITCH_MESSAGE_INDICATE_UNBRIDGE: case SWITCH_MESSAGE_INDICATE_TRANSFER: case SWITCH_MESSAGE_INDICATE_RINGING: case SWITCH_MESSAGE_INDICATE_MEDIA: case SWITCH_MESSAGE_INDICATE_NOMEDIA: case SWITCH_MESSAGE_INDICATE_HOLD: case SWITCH_MESSAGE_INDICATE_UNHOLD: case SWITCH_MESSAGE_INDICATE_REDIRECT: case SWITCH_MESSAGE_INDICATE_RESPOND: case SWITCH_MESSAGE_INDICATE_BROADCAST: case SWITCH_MESSAGE_INDICATE_MEDIA_REDIRECT: case SWITCH_MESSAGE_INDICATE_DEFLECT: switch_channel_set_flag(session->channel, CF_VIDEO_BREAK); switch_core_session_kill_channel(session, SWITCH_SIG_BREAK); break; default: break; } } end: if (message->message_id == SWITCH_MESSAGE_INDICATE_MEDIA) { switch_channel_clear_flag(session->channel, CF_PROXY_OFF); } switch_core_session_free_message(&message); switch_core_session_rwunlock(session); return status; }
-
Sofia 模块在加载时将函数表
mod_sofia.c#sofia_io_routines
安装在端点接口结构体的 io_routines 属性,该函数表定义如下,则可知此处将触发mod_sofia.c#sofia_receive_message()
函数执行switch_io_routines_t sofia_io_routines = { /*.outgoing_channel */ sofia_outgoing_channel, /*.read_frame */ sofia_read_frame, /*.write_frame */ sofia_write_frame, /*.kill_channel */ sofia_kill_channel, /*.send_dtmf */ sofia_send_dtmf, /*.receive_message */ sofia_receive_message, /*.receive_event */ sofia_receive_event, /*.state_change */ NULL, /*.read_video_frame */ sofia_read_video_frame, /*.write_video_frame */ sofia_write_video_frame, /*.read_text_frame */ sofia_read_text_frame, /*.write_text_frame */ sofia_write_text_frame, /*.state_run*/ NULL, /*.get_jb*/ sofia_get_jb };
-
mod_sofia.c#sofia_receive_message()
函数对于SWITCH_MESSAGE_INDICATE_ANSWER
消息的核心处理是调用mod_sofia.c#sofia_answer_channel()
函数static switch_status_t sofia_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg) { ...... switch (msg->message_id) { ...... case SWITCH_MESSAGE_INDICATE_ANSWER: status = sofia_answer_channel(session); break; ...... end_lock: //if (msg->message_id == SWITCH_MESSAGE_INDICATE_ANSWER || msg->message_id == SWITCH_MESSAGE_INDICATE_PROGRESS) { //sofia_send_callee_id(session, NULL, NULL); //} switch_mutex_unlock(tech_pvt->sofia_mutex); end: if (switch_channel_down(channel) || sofia_test_flag(tech_pvt, TFLAG_BYE)) { status = SWITCH_STATUS_FALSE; } return status; }
-
mod_sofia.c#sofia_answer_channel()
函数的关键处理如下,至此 answer app 已经执行完毕- 调用
switch_core_media.c#switch_core_media_codec_chosen()
确定媒体编码,暂不做深入分析 - 调用
sofia_media.c#sofia_media_activate_rtp()
启动 rtp 传输端口,暂不做深入分析 - 调用库函数
nua_respond()
响应 200 OK 消息给呼入 UA
static switch_status_t sofia_answer_channel(switch_core_session_t *session) { private_object_t *tech_pvt = (private_object_t *) switch_core_session_get_private(session); switch_channel_t *channel = switch_core_session_get_channel(session); switch_status_t status; uint32_t session_timeout = tech_pvt->profile->session_timeout; const char *val; const char *b_sdp = NULL; int is_proxy = 0; int is_3pcc_proxy = 0; int is_3pcc = 0; char *sticky = NULL; const char *call_info = switch_channel_get_variable(channel, "presence_call_info_full"); const char *session_id_header = sofia_glue_session_id_header(session, tech_pvt->profile); ...... if ((is_proxy && !b_sdp) || sofia_test_flag(tech_pvt, TFLAG_LATE_NEGOTIATION) || switch_core_media_codec_chosen(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO) != SWITCH_STATUS_SUCCESS) { sofia_clear_flag_locked(tech_pvt, TFLAG_LATE_NEGOTIATION); if (is_proxy) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Disabling proxy mode due to call answer with no bridge\n"); switch_channel_clear_flag(channel, CF_PROXY_MEDIA); switch_channel_clear_flag(channel, CF_PROXY_MODE); } if (switch_channel_direction(tech_pvt->channel) == SWITCH_CALL_DIRECTION_INBOUND) { const char *r_sdp = switch_channel_get_variable(channel, SWITCH_R_SDP_VARIABLE); switch_core_media_prepare_codecs(tech_pvt->session, SWITCH_TRUE); if (zstr(r_sdp) || sofia_media_tech_media(tech_pvt, r_sdp, SDP_TYPE_REQUEST) != SWITCH_STATUS_SUCCESS) { switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "CODEC NEGOTIATION ERROR"); //switch_mutex_lock(tech_pvt->sofia_mutex); //nua_respond(tech_pvt->nh, SIP_488_NOT_ACCEPTABLE, TAG_END()); //switch_mutex_unlock(tech_pvt->sofia_mutex); return SWITCH_STATUS_FALSE; } } } if ((status = switch_core_media_choose_port(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO, 0)) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return status; } switch_core_media_gen_local_sdp(session, SDP_TYPE_RESPONSE, NULL, 0, NULL, 0); if (sofia_media_activate_rtp(tech_pvt) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } ...... if (sofia_use_soa(tech_pvt)) { nua_respond(tech_pvt->nh, SIP_200_OK, NUTAG_AUTOANSWER(0), TAG_IF(call_info, SIPTAG_CALL_INFO_STR(call_info)), TAG_IF(sticky, NUTAG_PROXY(tech_pvt->record_route)), TAG_IF(cid, SIPTAG_HEADER_STR(cid)), TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), NUTAG_SESSION_TIMER(tech_pvt->session_timeout), NUTAG_SESSION_REFRESHER(tech_pvt->session_refresher), NUTAG_UPDATE_REFRESH(tech_pvt->update_refresher), SIPTAG_CONTACT_STR(tech_pvt->reply_contact), SIPTAG_CALL_INFO_STR(switch_channel_get_variable(tech_pvt->channel, SOFIA_SIP_HEADER_PREFIX "call_info")), SOATAG_USER_SDP_STR(tech_pvt->mparams.local_sdp_str), SOATAG_REUSE_REJECTED(1), SOATAG_AUDIO_AUX("cn telephone-event"), TAG_IF(sofia_test_pflag(tech_pvt->profile, PFLAG_DISABLE_100REL), NUTAG_INCLUDE_EXTRA_SDP(1)), SOATAG_RTP_SELECT(1), TAG_IF(!zstr(extra_headers), SIPTAG_HEADER_STR(extra_headers)), TAG_IF(switch_stristr("update_display", tech_pvt->x_freeswitch_support_remote), SIPTAG_HEADER_STR("X-FS-Support: " FREESWITCH_SUPPORT)), TAG_END()); } else { nua_respond(tech_pvt->nh, SIP_200_OK, NUTAG_AUTOANSWER(0), NUTAG_MEDIA_ENABLE(0), TAG_IF(call_info, SIPTAG_CALL_INFO_STR(call_info)), TAG_IF(sticky, NUTAG_PROXY(tech_pvt->record_route)), TAG_IF(cid, SIPTAG_HEADER_STR(cid)), TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), NUTAG_SESSION_TIMER(tech_pvt->session_timeout), NUTAG_SESSION_REFRESHER(tech_pvt->session_refresher), NUTAG_UPDATE_REFRESH(tech_pvt->update_refresher), SIPTAG_CONTACT_STR(tech_pvt->reply_contact), SIPTAG_CALL_INFO_STR(switch_channel_get_variable(tech_pvt->channel, SOFIA_SIP_HEADER_PREFIX "call_info")), SIPTAG_CONTENT_TYPE_STR("application/sdp"), SIPTAG_PAYLOAD_STR(tech_pvt->mparams.local_sdp_str), TAG_IF(!zstr(extra_headers), SIPTAG_HEADER_STR(extra_headers)), TAG_IF(switch_stristr("update_display", tech_pvt->x_freeswitch_support_remote), SIPTAG_HEADER_STR("X-FS-Support: " FREESWITCH_SUPPORT)), TAG_END()); } switch_safe_free(extra_headers); sofia_set_flag_locked(tech_pvt, TFLAG_ANS); } return SWITCH_STATUS_SUCCESS; }
- 调用
2.3 会话挂断及后续处理
-
此时回到2.2节步骤4第2步,拨号计划配置的 app 全部执行完毕后 session 状态仍在 CS_EXECUTE 状态,则调用
switch_channel.c#switch_channel_perform_hangup()
函数进行挂断处理,其关键步骤:- 在互斥锁中直接修改 channel 状态为 CS_HANGUP
- 调用
switch_core_state_machine.c#switch_core_session_hangup_state()
函数处理 session 挂断
SWITCH_DECLARE(switch_channel_state_t) switch_channel_perform_hangup(switch_channel_t *channel, const char *file, const char *func, int line, switch_call_cause_t hangup_cause) { int ok = 0; switch_assert(channel != NULL); /* one per customer */ switch_mutex_lock(channel->state_mutex); if (!(channel->opaque_flags & OCF_HANGUP)) { channel->opaque_flags |= OCF_HANGUP; ok = 1; } switch_mutex_unlock(channel->state_mutex); if (switch_channel_test_flag(channel, CF_LEG_HOLDING)) { switch_channel_mark_hold(channel, SWITCH_FALSE); switch_channel_set_flag(channel, CF_HANGUP_HELD); } if (!ok) { return channel->state; } switch_channel_clear_flag(channel, CF_BLOCK_STATE); if (channel->state < CS_HANGUP) { switch_channel_state_t last_state; switch_event_t *event; const char *var; switch_mutex_lock(channel->profile_mutex); if (channel->hold_record && !channel->hold_record->off) { channel->hold_record->off = switch_time_now(); } switch_mutex_unlock(channel->profile_mutex); switch_mutex_lock(channel->state_mutex); last_state = channel->state; channel->state = CS_HANGUP; switch_mutex_unlock(channel->state_mutex); channel->hangup_cause = hangup_cause; switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, switch_channel_get_uuid(channel), SWITCH_LOG_NOTICE, "Hangup %s [%s] [%s]\n", channel->name, state_names[last_state], switch_channel_cause2str(channel->hangup_cause)); switch_channel_set_variable_partner(channel, "last_bridge_hangup_cause", switch_channel_cause2str(hangup_cause)); if ((var = switch_channel_get_variable(channel, SWITCH_PROTO_SPECIFIC_HANGUP_CAUSE_VARIABLE))) { switch_channel_set_variable_partner(channel, "last_bridge_" SWITCH_PROTO_SPECIFIC_HANGUP_CAUSE_VARIABLE, var); } if (switch_channel_test_flag(channel, CF_BRIDGE_ORIGINATOR)) { switch_channel_set_variable(channel, "last_bridge_role", "originator"); } else if (switch_channel_test_flag(channel, CF_BRIDGED)) { switch_channel_set_variable(channel, "last_bridge_role", "originatee"); } if (!switch_core_session_running(channel->session) && !switch_core_session_started(channel->session)) { switch_core_session_thread_launch(channel->session); } if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_HANGUP) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(channel, event); switch_event_fire(&event); } switch_core_session_kill_channel(channel->session, SWITCH_SIG_KILL); switch_core_session_signal_state_change(channel->session); switch_core_session_hangup_state(channel->session, SWITCH_FALSE); } return channel->state; }
-
switch_core_state_machine.c#switch_core_session_hangup_state()
函数逻辑比较简单,值得注意的点如下:- 首先判断 session 是否已经进行过挂断处理,如是直接 return
- 其次通过
switch_core_state_machine.c#STATE_MACRO()
宏触发 CS_HANGUP 状态下的各级回调,此时mod_sofia.c#sofia_on_hangup()
函数和switch_core_state_machine.c#switch_core_standard_on_hangup()
函数将依次被触发
SWITCH_DECLARE(void) switch_core_session_hangup_state(switch_core_session_t *session, switch_bool_t force) { switch_call_cause_t cause = switch_channel_get_cause(session->channel); switch_call_cause_t cause_q850 = switch_channel_get_cause_q850(session->channel); int proceed = 1; int global_proceed = 1; int do_extra_handlers = 1; int silly = 0; int index = 0; switch_channel_state_t state = switch_channel_get_state(session->channel), midstate; 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; const char *hook_var; int use_session = 0; if (!force) { if (!switch_channel_test_flag(session->channel, CF_EARLY_HANGUP) && !switch_test_flag((&runtime), SCF_EARLY_HANGUP)) { return; } if (switch_thread_self() != session->thread_id) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG10, "%s thread mismatch skipping state handler.\n", switch_channel_get_name(session->channel)); return; } } if (switch_test_flag(session, SSF_HANGUP)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG10, "%s handler already called, skipping state handler.\n", switch_channel_get_name(session->channel)); return; } endpoint_interface = session->endpoint_interface; switch_assert(endpoint_interface != NULL); driver_state_handler = endpoint_interface->state_handler; switch_assert(driver_state_handler != NULL); switch_channel_set_hangup_time(session->channel); switch_core_media_bug_remove_all(session); switch_channel_stop_broadcast(session->channel); switch_channel_set_variable(session->channel, "hangup_cause", switch_channel_cause2str(cause)); switch_channel_set_variable_printf(session->channel, "hangup_cause_q850", "%d", cause_q850); //switch_channel_presence(session->channel, "unknown", switch_channel_cause2str(cause), NULL); switch_channel_set_timestamps(session->channel); switch_channel_set_callstate(session->channel, CCS_HANGUP); STATE_MACRO(hangup, "HANGUP"); switch_core_media_set_stats(session); if ((hook_var = switch_channel_get_variable(session->channel, SWITCH_API_HANGUP_HOOK_VARIABLE))) { if (switch_true(switch_channel_get_variable(session->channel, SWITCH_SESSION_IN_HANGUP_HOOK_VARIABLE))) { use_session = 1; } api_hook(session, hook_var, use_session); } switch_channel_process_device_hangup(session->channel); switch_set_flag(session, SSF_HANGUP); }
-
mod_sofia.c#sofia_on_hangup()
函数主要做一些资源的清理释放工作,对于呼入会话,最关键的处理是调用库函数nua_bye()
发送 BYE 请求到呼入 UA,请求 SIP 会话结束switch_status_t sofia_on_hangup(switch_core_session_t *session) { switch_core_session_t *a_session; private_object_t *tech_pvt = (private_object_t *) switch_core_session_get_private(session); switch_channel_t *channel = switch_core_session_get_channel(session); switch_call_cause_t cause = switch_channel_get_cause(channel); int sip_cause = hangup_cause_to_sip(cause); const char *ps_cause = NULL, *use_my_cause; const char *gateway_name = NULL; sofia_gateway_t *gateway_ptr = NULL; if ((gateway_name = switch_channel_get_variable(channel, "sip_gateway_name"))) { gateway_ptr = sofia_reg_find_gateway(gateway_name); } if (!tech_pvt) { return SWITCH_STATUS_SUCCESS; } switch_mutex_lock(tech_pvt->sofia_mutex); if (!switch_channel_test_flag(channel, CF_ANSWERED)) { if (switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND) { tech_pvt->profile->ob_failed_calls++; } else { tech_pvt->profile->ib_failed_calls++; } if (gateway_ptr) { if (switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND) { gateway_ptr->ob_failed_calls++; } else { gateway_ptr->ib_failed_calls++; } } } if (gateway_ptr) { sofia_reg_release_gateway(gateway_ptr); } if (!((use_my_cause = switch_channel_get_variable(channel, "sip_ignore_remote_cause")) && switch_true(use_my_cause))) { ps_cause = switch_channel_get_variable(channel, "last_bridge_" SWITCH_PROTO_SPECIFIC_HANGUP_CAUSE_VARIABLE); } if (!zstr(ps_cause) && (!strncasecmp(ps_cause, "sip:", 4) || !strncasecmp(ps_cause, "sips:", 5))) { int new_cause = atoi(sofia_glue_strip_proto(ps_cause)); if (new_cause) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Overriding SIP cause %d with %d from the other leg\n", switch_channel_get_name(channel), sip_cause, new_cause); sip_cause = new_cause; } } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Channel %s hanging up, cause: %s\n", switch_channel_get_name(channel), switch_channel_cause2str(cause)); if (tech_pvt->hash_key && !sofia_test_pflag(tech_pvt->profile, PFLAG_DESTROY)) { switch_core_hash_delete_locked(tech_pvt->profile->chat_hash, tech_pvt->hash_key, tech_pvt->profile->flag_mutex); } if (session && tech_pvt->profile->pres_type) { char *sql = switch_mprintf("delete from sip_dialogs where uuid='%q'", switch_core_session_get_uuid(session)); switch_assert(sql); sofia_glue_execute_sql_now(tech_pvt->profile, &sql, SWITCH_TRUE); } if (tech_pvt->kick && (a_session = switch_core_session_locate(tech_pvt->kick))) { switch_channel_t *a_channel = switch_core_session_get_channel(a_session); switch_channel_hangup(a_channel, switch_channel_get_cause(channel)); switch_core_session_rwunlock(a_session); } if (sofia_test_pflag(tech_pvt->profile, PFLAG_DESTROY)) { sofia_set_flag(tech_pvt, TFLAG_BYE); } else if (tech_pvt->nh && !sofia_test_flag(tech_pvt, TFLAG_BYE)) { char reason[128] = ""; char *bye_headers = sofia_glue_get_extra_headers(channel, SOFIA_SIP_BYE_HEADER_PREFIX); const char *val = NULL; const char *max_forwards = switch_channel_get_variable(channel, SWITCH_MAX_FORWARDS_VARIABLE); const char *call_info = switch_channel_get_variable(channel, "presence_call_info_full"); const char *session_id_header = sofia_glue_session_id_header(session, tech_pvt->profile); val = switch_channel_get_variable(tech_pvt->channel, "disable_q850_reason"); if (!val || switch_false(val)) { if ((val = switch_channel_get_variable(tech_pvt->channel, "sip_reason"))) { switch_snprintf(reason, sizeof(reason), "%s", val); } else { if ((switch_channel_test_flag(channel, CF_INTERCEPT) || cause == SWITCH_CAUSE_PICKED_OFF || cause == SWITCH_CAUSE_LOSE_RACE) && !switch_true(switch_channel_get_variable(channel, "ignore_completed_elsewhere"))) { switch_snprintf(reason, sizeof(reason), "SIP;cause=200;text=\"Call completed elsewhere\""); } else if (cause > 0 && cause < 128) { switch_snprintf(reason, sizeof(reason), "Q.850;cause=%d;text=\"%s\"", cause, switch_channel_cause2str(cause)); } else { switch_snprintf(reason, sizeof(reason), "SIP;cause=%d;text=\"%s\"", cause, switch_channel_cause2str(cause)); } } } if (switch_channel_test_flag(channel, CF_INTERCEPT) || cause == SWITCH_CAUSE_PICKED_OFF || cause == SWITCH_CAUSE_LOSE_RACE) { switch_channel_set_variable(channel, "call_completed_elsewhere", "true"); } if (switch_channel_test_flag(channel, CF_ANSWERED) || sofia_test_flag(tech_pvt, TFLAG_ANS)) { if (!tech_pvt->got_bye) { switch_channel_set_variable(channel, "sip_hangup_disposition", "send_bye"); } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Sending BYE to %s\n", switch_channel_get_name(channel)); if (!sofia_test_flag(tech_pvt, TFLAG_BYE)) { nua_bye(tech_pvt->nh, TAG_IF(!zstr(tech_pvt->route_uri), NUTAG_PROXY(tech_pvt->route_uri)), SIPTAG_CONTACT(SIP_NONE), TAG_IF(!zstr(reason), SIPTAG_REASON_STR(reason)), TAG_IF(call_info, SIPTAG_CALL_INFO_STR(call_info)), TAG_IF(!zstr(tech_pvt->user_via), SIPTAG_VIA_STR(tech_pvt->user_via)), TAG_IF(!zstr(bye_headers), SIPTAG_HEADER_STR(bye_headers)), TAG_IF(!zstr(session_id_header), SIPTAG_HEADER_STR(session_id_header)), TAG_END()); } } ...... sofia_set_flag_locked(tech_pvt, TFLAG_BYE); switch_safe_free(bye_headers); } if (cause == SWITCH_CAUSE_WRONG_CALL_STATE) { switch_event_t *s_event; if (switch_event_create_subclass(&s_event, SWITCH_EVENT_CUSTOM, MY_EVENT_WRONG_CALL_STATE) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "from_user", tech_pvt->from_user); switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "network_ip", tech_pvt->mparams.remote_ip); switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "network_port", "%d", tech_pvt->mparams.remote_port); switch_event_fire(&s_event); } } sofia_clear_flag(tech_pvt, TFLAG_IO); if (tech_pvt->sofia_private) { /* set to NULL so that switch_core_session_locate no longer succeeds, but don't lose the UUID in uuid_str so we * can fire events with session UUID */ tech_pvt->sofia_private->uuid = NULL; } switch_mutex_unlock(tech_pvt->sofia_mutex); return SWITCH_STATUS_SUCCESS; }
-
switch_core_state_machine.c#switch_core_standard_on_hangup()
函数没有复杂逻辑,可以看到比较关键的处理是调用switch_channel.c#switch_channel_get_caller_extension()
获取暂存的拨号计划,如果拨号计划未执行完,则接着通过宏定义switch_core.h#switch_core_session_execute_application()
将其执行完毕。通常情况下这部分逻辑不会运行,这里主要是考虑异常情况static void switch_core_standard_on_hangup(switch_core_session_t *session) { switch_caller_extension_t *extension; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Standard HANGUP, cause: %s\n", switch_channel_get_name(session->channel), switch_channel_cause2str(switch_channel_get_cause(session->channel))); if (switch_true(switch_channel_get_variable(session->channel, "log_audio_stats_on_hangup"))) { switch_rtp_stats_t *audio_stats = NULL; switch_core_media_set_stats(session); audio_stats = switch_core_media_get_stats(session, SWITCH_MEDIA_TYPE_AUDIO, switch_core_session_get_pool(session)); if (audio_stats) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Call statistics:\n" "in_raw_bytes: %d\n" "in_media_bytes: %d\n" "in_packet_count: %d\n" "in_media_packet_count: %d\n" "in_skip_packet_count: %d\n" "in_jitter_packet_count: %d\n" "in_dtmf_packet_count: %d\n" "in_cng_packet_count: %d\n" "in_flush_packet_count: %d\n" "in_largest_jb_size: %d\n\n" "in_jitter_min_variance: %lf\n" "in_jitter_max_variance: %lf\n" "in_jitter_loss_rate: %lf\n" "in_jitter_burst_rate: %lf\n" "in_mean_interval: %lf\n\n" "in_flaw_total: %d\n" "in_quality_percentage: %lf\n" "in_mos: %lf\n\n" "out_raw_bytes: %d\n" "out_media_bytes: %d\n" "out_packet_count: %d\n" "out_media_packet_count: %d\n" "out_skip_packet_count: %d\n" "out_dtmf_packet_count: %d\n" "out_cng_packet_count: %d\n\n" "rtcp_packet_count: %d\n" "rtcp_octet_count: %d\n", switch_channel_get_name(session->channel), (int)audio_stats->inbound.raw_bytes, (int)audio_stats->inbound.media_bytes, (int)audio_stats->inbound.packet_count, (int)audio_stats->inbound.media_packet_count, (int)audio_stats->inbound.skip_packet_count, (int)audio_stats->inbound.jb_packet_count, (int)audio_stats->inbound.dtmf_packet_count, (int)audio_stats->inbound.cng_packet_count, (int)audio_stats->inbound.flush_packet_count, (int)audio_stats->inbound.largest_jb_size, audio_stats->inbound.min_variance, audio_stats->inbound.max_variance, audio_stats->inbound.lossrate, audio_stats->inbound.burstrate, audio_stats->inbound.mean_interval, (int)audio_stats->inbound.flaws, audio_stats->inbound.R, audio_stats->inbound.mos, (int)audio_stats->outbound.raw_bytes, (int)audio_stats->outbound.media_bytes, (int)audio_stats->outbound.packet_count, (int)audio_stats->outbound.media_packet_count, (int)audio_stats->outbound.skip_packet_count, (int)audio_stats->outbound.dtmf_packet_count, (int)audio_stats->outbound.cng_packet_count, (int)audio_stats->rtcp.packet_count, (int)audio_stats->rtcp.octet_count ); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s Missing call statistics!\n", switch_channel_get_name(session->channel)); } } switch_channel_clear_flag(session->channel, CF_RECOVERING); switch_core_recovery_untrack(session, SWITCH_TRUE); if (!switch_channel_test_flag(session->channel, CF_ZOMBIE_EXEC)) { return; } if ((extension = switch_channel_get_caller_extension(session->channel)) == 0) { return; } while(extension->current_application) { switch_caller_application_t *current_application = extension->current_application; switch_status_t status; extension->current_application = extension->current_application->next; status = switch_core_session_execute_application(session, current_application->application_name, current_application->application_data); if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_IGNORE) { return; } } }
-
以上流程处理完毕,
switch_core_state_machine.c#switch_core_session_run()
内部 while 循环进入下一周期,此时 channel 处在 CS_HANGUP 状态,可以看到核心处理如下。这部分处理完毕后, channel 状态将流转到 CS_REPORTING(这个状态主要用于对通话进行一些统计分析动作),并很快在下一个 while 周期进入到 CS_DESTORY 状态。CS_DESTORY 意味着 channel 的生命周期结束,系统将回收资源,由于 CS_REPORTING/CS_DESTORY 的处理流程基本与之前的状态处理类似,本文不再赘述,有兴趣的读者可以自行分析了解,至此本文告一段落- 调用
switch_core_state_machine.c#switch_core_session_hangup_state()
函数处理 session 挂断,该函数在本节步骤2已经调用过一次,故内部逻辑会提前 return - 通过宏定义
switch_channel.h#switch_channel_set_state()
将 channel 的状态从 CS_HANGUP 流转到 CS_REPORTING - 调用
switch_ivr.c#switch_ivr_parse_all_events()
函数消费处理 session 内部队列中的消息
SWITCH_DECLARE(void) switch_core_session_run(switch_core_session_t *session) { ...... while ((state = switch_channel_get_state(session->channel)) != CS_DESTROY) { ...... midstate = state; if (state != switch_channel_get_running_state(session->channel) || state >= CS_HANGUP) { ...... switch (state) { case CS_NEW: /* Just created, Waiting for first instructions */ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%s) State NEW\n", switch_channel_get_name(session->channel)); break; case CS_DESTROY: goto done; case CS_REPORTING: /* Call Detail */ { switch_core_session_reporting_state(session); switch_channel_set_state(session->channel, CS_DESTROY); } goto done; case CS_HANGUP: /* Deactivate and end the thread */ { switch_core_session_hangup_state(session, SWITCH_TRUE); if (switch_channel_test_flag(session->channel, CF_VIDEO)) { switch_core_session_wake_video_thread(session); } switch_channel_set_state(session->channel, CS_REPORTING); } break; ...... } endstate = switch_channel_get_state(session->channel); if (endstate == switch_channel_get_running_state(session->channel)) { if (endstate == CS_NEW) { switch_yield(20000); switch_ivr_parse_all_events(session); if (!--new_loops) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s %s Abandoned\n", session->uuid_str, switch_core_session_get_name(session)); switch_channel_set_flag(session->channel, CF_NO_CDR); switch_channel_hangup(session->channel, SWITCH_CAUSE_WRONG_CALL_STATE); } } else { switch_channel_state_thread_lock(session->channel); switch_ivr_parse_all_events(session); if (switch_channel_test_flag(session->channel, CF_STATE_REPEAT)) { switch_channel_clear_flag(session->channel, CF_STATE_REPEAT); } else if (switch_channel_get_state(session->channel) == switch_channel_get_running_state(session->channel)) { switch_channel_set_flag(session->channel, CF_THREAD_SLEEPING); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG1, "%s session thread sleep state: %s!\n", switch_channel_get_name(session->channel), switch_channel_state_name(switch_channel_get_running_state(session->channel))); switch_thread_cond_wait(session->cond, session->mutex); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG1, "%s session thread wake state: %s!\n", switch_channel_get_name(session->channel), switch_channel_state_name(switch_channel_get_running_state(session->channel))); switch_channel_clear_flag(session->channel, CF_THREAD_SLEEPING); } switch_channel_state_thread_unlock(session->channel); switch_ivr_parse_all_events(session); } } } } done: switch_mutex_unlock(session->mutex); switch_clear_flag(session, SSF_THREAD_RUNNING); }
- 调用