本文主要是介绍FreeSWITCH 1.10 源码阅读(4)-从呼入处理分析核心状态机,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 1. 前言
- 2. 源码分析
- 2.1 会话的初始化
- 2.2 会话的路由及 App 执行
- 2.3 会话挂断及后续处理
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 dialplanif (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 devcase 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); }
- 调用
这篇关于FreeSWITCH 1.10 源码阅读(4)-从呼入处理分析核心状态机的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!