FreeSWITCH 1.10 源码阅读(4)-从呼入处理分析核心状态机

1. 前言

FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程 中笔者分析了 sofia 模块对底层 Sofia-SIP 协议栈的封装使用,而实际上呼叫进程的推进是由上层的状态机流转完成处理的。通常一通会话的完整生命周期如下,大致可以将其划分为 3 个阶段:

  1. 会话初始化
  2. 会话的 dialplan(拨号计划) 路由及 App 执行
  3. 会话挂断及后续处理

在这里插入图片描述

2. 源码分析

FreeSWITCH 具有非常典型的分层结构,这一点也体现在 SIP 会话交互流程中,如下是一个简单示意图

  1. 底层 Sofia-SIP 负责实际处理 SIP 收发,并通过回调的方式将 SIP 会话的状态通知到上层 Sofia 模块
  2. Sofia 模块处理底层 Sofia-SIP 收到的消息,直接调用核心层函数将其通知到 FreeSWITCH 核心层
  3. FreeSWITCH 核心层进行业务逻辑处理,通过回调的方式调用下层模块处理

在这里插入图片描述

如下是 FreeSWITCH 对一通会话完整生命周期处理的源码时序,部分触发步骤有所省略,读者可结合 FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程 理解

在这里插入图片描述

2.1 会话的初始化

  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;
     
     ......
     
     }
    
    }
    
    
  2. sofia.c#sofia_handle_sip_i_state() 函数处理的分支非常多,如下是底层 SIP 会话状态的枚举nua_callstate 的定义,可知此时底层 SIP 会话实际处于 nua_callstate_received 状态,则可以看到该函数核心处理如下:

    1. 调用 sofia_media.c#sofia_media_negotiate_sdp() 函数进行 sdp 媒体协商匹配,不做深入分析
    2. 匹配不上直接调用库函数响应外部 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;
         
         ......
     }
    }
    
    
  3. switch_channel.c#switch_channel_perform_set_state() 函数的关键处理是以下几步:

    1. 首先进行 channel 状态转移的前置检查
    2. 检查通过则调用 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;
    }
    
  4. channel 状态发生改变,则在每个 session 单独的线程任务 switch_core_state_machine.c#switch_core_session_run() 内部 while 循环中将执行对应分支代码,这里的关键处理如下:

    1. 首先通过宏定义 switch_core_state_machine.c#STATE_MACRO() 执行各个层级的状态回调函数,通知 channel 状态变化
    2. 调用 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);
    }
    
    
  5. switch_core_state_machine.c#STATE_MACRO() 定义如下,可以看到其核心处理在于执行各个层级的状态变化回调函数。FreeSWITCH 从下到上将回调函数分为以下四层,回调函数将被依次调用

    1. endpoint 层级:switch_endpoint_interface_t#state_handler 函数表,由端点接口模块重写,例如 mod_sofia.c#sofia_event_handlers
    2. channel 层级:switch_channel_t#state_handlers 函数表,无固定实现,由 switch_channel.c#switch_channel_add_state_handler() 添加
    3. core 核心层级:switch_runtime#state_handlers() 函数表,无固定实现,由 switch_core.c#switch_core_add_state_handler() 添加
    4. 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)
    
    
  6. 作为 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
    };
    
  7. 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;
    }
    
  8. 最后回调的是 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);
    }
    
  9. 以上状态回调函数处理完,此时回到本节步骤4第2步switch_ivr.c#switch_ivr_parse_all_events() 函数是当前 session 相关所有事件的处理入口,其核心处理分为以下两步:

    1. switch_ivr.c#switch_ivr_parse_all_messages() 函数处理 session->message_queue 队列中的消息
    2. 在 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;
    }
    
  10. 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;
    }
    
    
  11. 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 执行

  1. 在上一节中,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;
    }
    
  2. 由上一节内容可知,接下来回调的是 switch_core_state_machine.c#switch_core_standard_on_routing() 函数,其核心处理如下:

    1. 首先通过 dialplan_interface->hunt_function() 执行指定的 dialplan 模块的回调方法,此处 dialplan 默认为 XML,则将调用 mod_dialplan_xml.c#dialplan_hunt() 函数查找拨号计划配置
    2. 如果找到了符合要求的拨号计划配置,则调用 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);
     }
    }
    
  3. mod_dialplan_xml.c#dialplan_hunt() 函数的核心逻辑由以下几步组成,一个示例的拨号计划配置如下:

    1. 首先调用 mod_dialplan_xml.c#dialplan_xml_locate() 定位获取拨号计划相关配置,这个配置可能从本地内存的 XML 树中取得,也可以通过 xml_curl 模块从外部服务器上获取,不做深入讨论
    2. 其次是遍历取得的拨号计划配置,在 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(&params, 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(&params);
     return status;
    }
    
  4. 经过以上处理,在 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(),这个函数的作用比较关键:

    1. 首先调用函数 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
    2. 拨号计划配置的 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);
     }
    }
    
    
  5. switch_core_session.c#switch_core_session_execute_application_get_flags() 函数的关键处理如下:

    1. 首先通过 switch_loadable_module.h#switch_loadable_module_get_application_interface() 获取指定名称的 app 函数结构体
    2. 调用 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;
    }
    
    
  6. 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;
    }
    
    
  7. mod_dptools.c#answer_function() 函数简单明了,就是通过宏定义 switch_channel.h#switch_channel_answer() 调用 switch_channel.c#switch_channel_perform_answer() 将上层的应答动作通知到底层 Sofia-SIP

    SWITCH_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);
    }
    
  8. 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;
    }
    
  9. 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;
    }
    
    
  10. 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
    };
    
  11. 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;
    
    }
    
    
  12. mod_sofia.c#sofia_answer_channel() 函数的关键处理如下,至此 answer app 已经执行完毕

    1. 调用 switch_core_media.c#switch_core_media_codec_chosen() 确定媒体编码,暂不做深入分析
    2. 调用 sofia_media.c#sofia_media_activate_rtp() 启动 rtp 传输端口,暂不做深入分析
    3. 调用库函数 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 会话挂断及后续处理

  1. 此时回到2.2节步骤4第2步,拨号计划配置的 app 全部执行完毕后 session 状态仍在 CS_EXECUTE 状态,则调用 switch_channel.c#switch_channel_perform_hangup() 函数进行挂断处理,其关键步骤:

    1. 在互斥锁中直接修改 channel 状态为 CS_HANGUP
    2. 调用 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;
     }
    
    
  2. switch_core_state_machine.c#switch_core_session_hangup_state() 函数逻辑比较简单,值得注意的点如下:

    1. 首先判断 session 是否已经进行过挂断处理,如是直接 return
    2. 其次通过 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);
    
    }
    
    
  3. 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;
    }
    
  4. 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;
     	}
     }
    }
    
  5. 以上流程处理完毕,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 的处理流程基本与之前的状态处理类似,本文不再赘述,有兴趣的读者可以自行分析了解,至此本文告一段落

    1. 调用 switch_core_state_machine.c#switch_core_session_hangup_state() 函数处理 session 挂断,该函数在本节步骤2已经调用过一次,故内部逻辑会提前 return
    2. 通过宏定义 switch_channel.h#switch_channel_set_state() 将 channel 的状态从 CS_HANGUP 流转到 CS_REPORTING
    3. 调用 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);
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值