FreeSWITCH 1.10 源码阅读(7)-uuid_bridge 命令原理解析

2023-12-22 01:43:50

文章目录

  • 1. uuid_bridge 命令执行
  • 2. 源码分析
  • 3. uuid_bridge 问题处理

1. uuid_bridge 命令执行

uuid_bridge 命令用于将两条腿桥接在一起,以实现双方的通话。下图是该命令在 FreeSWITCH 中的处理流程,大致的流程可以分成以下几个步骤:

  1. uuid_bridge 命令的执行
  2. a-leg 与 b-leg 的状态机流转
  3. 桥接时的处理

在这里插入图片描述

2. 源码分析

  1. uuid_bridge 命令被下发送到 FreeSWITCH 时,会被 mod_commands.c#uuid_bridge_function() 函数处理,这个函数将解析命令参数,接着调用 switch_ivr_bridge.c#switch_ivr_uuid_bridge() 函数去触发桥接

    #define UUID_SYNTAX "<uuid> <other_uuid>"
    SWITCH_STANDARD_API(uuid_bridge_function)
    {
       char *mycmd = NULL, *argv[4] = { 0 };
       int argc = 0;
    
       if (!zstr(cmd) && (mycmd = strdup(cmd))) {
     	 argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
       }
    
       if (zstr(cmd) || argc < 2) {
     	 stream->write_function(stream, "-USAGE: %s\n", UUID_SYNTAX);
       } else {
     	switch_status_t status;
     	char *who = NULL;
    
     	if ((status = switch_ivr_uuid_bridge(argv[0], argv[1])) != SWITCH_STATUS_SUCCESS) {
     		if (argv[2]) {
     			if ((status = switch_ivr_uuid_bridge(argv[0], argv[2])) == SWITCH_STATUS_SUCCESS) {
     				who = argv[2];
     			}
     		}
     	} else {
     		who = argv[1];
     	}
    
     	if (status == SWITCH_STATUS_SUCCESS) {
     		stream->write_function(stream, "+OK %s\n", who);
     	} else {
     		stream->write_function(stream, "-ERR Invalid uuid\n");
     	}
      }
    
      switch_safe_free(mycmd);
      return SWITCH_STATUS_SUCCESS;
    }
    
  2. switch_ivr_bridge.c#switch_ivr_uuid_bridge() 函数会做许多状态校验之类的工作,但是核心其实只有两步:

    1. 调用switch_channel.c#switch_channel_add_state_handler()switch_ivr_bridge.c#uuid_bridge_state_handlers 状态机回调函数表加在将要桥接的两条腿上,当这两条腿状态流转时会回调函数表中相应的函数,不了解这部分的读者可参考 FreeSWITCH 1.10 源码阅读(4)-从呼入处理分析核心状态机
    2. 调用 switch_channel.h#switch_channel_set_state() 将两条腿切换为指定状态 CS_HIBERNATE,从而驱动状态机流转
    SWITCH_DECLARE(switch_status_t) switch_ivr_uuid_bridge(const char *originator_uuid, const char *originatee_uuid)
    {
     switch_core_session_t *originator_session, *originatee_session, *swap_session;
     switch_channel_t *originator_channel, *originatee_channel, *swap_channel;
     switch_status_t status = SWITCH_STATUS_FALSE;
     switch_caller_profile_t *originator_cp, *originatee_cp;
     switch_channel_state_t state;
    
     if ((originator_session = switch_core_session_locate(originator_uuid))) {
     	if ((originatee_session = switch_core_session_locate(originatee_uuid))) {
     		originator_channel = switch_core_session_get_channel(originator_session);
     		originatee_channel = switch_core_session_get_channel(originatee_session);
    
     		switch_ivr_check_hold(originator_session);
     		switch_ivr_check_hold(originatee_session);
     
    
     		if (switch_channel_test_flag(originator_channel, CF_LEG_HOLDING)) {
     			switch_channel_set_flag(originator_channel, CF_HOLD_ON_BRIDGE);
     		}
    
     		if (switch_channel_test_flag(originatee_channel, CF_LEG_HOLDING)) {
     			switch_channel_set_flag(originatee_channel, CF_HOLD_ON_BRIDGE);
     		}
    
    
     		if (switch_channel_direction(originator_channel) == SWITCH_CALL_DIRECTION_OUTBOUND && !switch_channel_test_flag(originator_channel, CF_DIALPLAN)) {
     			if (!switch_channel_test_flag(originator_channel, CF_RECOVERING_BRIDGE)) {
     				switch_channel_flip_cid(originator_channel);
     			}
     			switch_channel_set_flag(originator_channel, CF_DIALPLAN);
     		}
    
     		if (switch_channel_down_nosig(originator_channel)) {
     			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(originator_session), SWITCH_LOG_DEBUG, "%s is hungup refusing to bridge.\n", switch_channel_get_name(originatee_channel));
     			switch_core_session_rwunlock(originator_session);
     			switch_core_session_rwunlock(originatee_session);
     			return SWITCH_STATUS_FALSE;
     		}
    
     		if (!switch_channel_media_up(originator_channel)) {
     			if (switch_channel_media_up(originatee_channel)) {
     				swap_session = originator_session;
     				originator_session = originatee_session;
     				originatee_session = swap_session;
    
     				swap_channel = originator_channel;
     				originator_channel = originatee_channel;
     				originatee_channel = swap_channel;
     				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(originatee_session), SWITCH_LOG_WARNING, "reversing order of channels so this will work!\n");
     			} else {
     				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(originator_session), SWITCH_LOG_CRIT, "Neither channel is answered, cannot bridge them.\n");
     				switch_core_session_rwunlock(originator_session);
     				switch_core_session_rwunlock(originatee_session);
     				return SWITCH_STATUS_FALSE;
     			}
     		}
    
     		if (switch_channel_direction(originatee_channel) == SWITCH_CALL_DIRECTION_OUTBOUND && switch_channel_test_flag(originatee_channel, CF_DIALPLAN)) {
     			switch_channel_clear_flag(originatee_channel, CF_DIALPLAN);
     		}
    
     		cleanup_proxy_mode_a(originator_session);
     		cleanup_proxy_mode_a(originatee_session);
    
     		/* override transmit state for originator_channel to bridge to originatee_channel
     		 * install pointer to originatee_session into originator_channel
     		 * set CF_TRANSFER on both channels and change state to CS_SOFT_EXECUTE to
     		 * interrupt anything they are already doing.
     		 * originatee_session will fall asleep and originator_session will bridge to it
     		 */
    
     		switch_channel_set_flag(originator_channel, CF_REDIRECT);
     		switch_channel_set_flag(originatee_channel, CF_REDIRECT);
    
    
     		switch_channel_set_variable(originator_channel, SWITCH_UUID_BRIDGE, switch_core_session_get_uuid(originatee_session));
     		switch_channel_set_variable(originator_channel, SWITCH_BRIDGE_CHANNEL_VARIABLE, switch_channel_get_name(originatee_channel));
     		switch_channel_set_variable(originator_channel, SWITCH_BRIDGE_UUID_VARIABLE, switch_core_session_get_uuid(originatee_session));
     		switch_channel_set_variable(originator_channel, SWITCH_SIGNAL_BOND_VARIABLE, switch_core_session_get_uuid(originatee_session));
     		switch_channel_set_variable(originatee_channel, SWITCH_BRIDGE_CHANNEL_VARIABLE, switch_channel_get_name(originator_channel));
     		switch_channel_set_variable(originatee_channel, SWITCH_BRIDGE_UUID_VARIABLE, switch_core_session_get_uuid(originator_session));
     		switch_channel_set_variable(originatee_channel, SWITCH_SIGNAL_BOND_VARIABLE, switch_core_session_get_uuid(originator_session));
    
    
     		originator_cp = switch_channel_get_caller_profile(originator_channel);
     		originatee_cp = switch_channel_get_caller_profile(originatee_channel);
    
    
    
     		if (switch_channel_outbound_display(originator_channel)) {
     			switch_channel_invert_cid(originator_channel);
    
     			if (switch_channel_direction(originator_channel) == SWITCH_CALL_DIRECTION_INBOUND) {
     				switch_channel_clear_flag(originatee_channel, CF_BLEG);
     			}
     		}
    
     		if (switch_channel_inbound_display(originatee_channel)) {
     			switch_channel_invert_cid(originatee_channel);
    
     			if (switch_channel_direction(originatee_channel) == SWITCH_CALL_DIRECTION_INBOUND) {
     				switch_channel_set_flag(originatee_channel, CF_BLEG);
     			}
    
     		}
    
    
     		switch_channel_set_variable(originatee_channel, "original_destination_number", originatee_cp->destination_number);
     		switch_channel_set_variable(originatee_channel, "original_caller_id_name", originatee_cp->caller_id_name);
     		switch_channel_set_variable(originatee_channel, "original_caller_id_number", originatee_cp->caller_id_number);
    
     		switch_channel_set_variable(originator_channel, "original_destination_number", originator_cp->destination_number);
     		switch_channel_set_variable(originator_channel, "original_caller_id_name", originator_cp->caller_id_name);
     		switch_channel_set_variable(originator_channel, "original_caller_id_number", originator_cp->caller_id_number);
    
     		switch_channel_step_caller_profile(originatee_channel);
     		switch_channel_step_caller_profile(originator_channel);
    
     		originator_cp = switch_channel_get_caller_profile(originator_channel);
     		originatee_cp = switch_channel_get_caller_profile(originatee_channel);
    
    
    #ifdef DEEP_DEBUG_CID
     		{
     			switch_event_t *event;
    
     			if (switch_event_create_plain(&event, SWITCH_EVENT_CHANNEL_DATA) == SWITCH_STATUS_SUCCESS) {
     				//switch_channel_event_set_basic_data(originator_channel, event);
     				switch_caller_profile_event_set_data(originator_cp, "ORIGINATOR", event);
     				switch_caller_profile_event_set_data(originatee_cp, "ORIGINATEE", event);
     				DUMP_EVENT(event);
     				switch_event_destroy(&event);
     			}
     		}
    #endif
    
     		switch_channel_set_originator_caller_profile(originatee_channel, switch_caller_profile_clone(originatee_session, originator_cp));
     		switch_channel_set_originatee_caller_profile(originator_channel, switch_caller_profile_clone(originator_session, originatee_cp));
    
     		originator_cp->callee_id_name = switch_core_strdup(originator_cp->pool, originatee_cp->callee_id_name);
     		originator_cp->callee_id_number = switch_core_strdup(originator_cp->pool, originatee_cp->callee_id_number);
    
     		originatee_cp->caller_id_name = switch_core_strdup(originatee_cp->pool, originator_cp->caller_id_name);
     		originatee_cp->caller_id_number = switch_core_strdup(originatee_cp->pool, originator_cp->caller_id_number);
    
    #ifdef DEEP_DEBUG_CID
     		{
     			switch_event_t *event;
    
     			if (switch_event_create_plain(&event, SWITCH_EVENT_CHANNEL_DATA) == SWITCH_STATUS_SUCCESS) {
     				//switch_channel_event_set_basic_data(originator_channel, event);
     				switch_caller_profile_event_set_data(originator_cp, "POST-ORIGINATOR", event);
     				switch_caller_profile_event_set_data(originatee_cp, "POST-ORIGINATEE", event);
     				DUMP_EVENT(event);
     				switch_event_destroy(&event);
     			}
     		}
    #endif
    
     		switch_channel_stop_broadcast(originator_channel);
     		switch_channel_stop_broadcast(originatee_channel);
    
     		switch_channel_set_flag(originator_channel, CF_TRANSFER);
     		switch_channel_set_flag(originatee_channel, CF_TRANSFER);
    
    
     		switch_channel_clear_flag(originator_channel, CF_ORIGINATING);
     		switch_channel_clear_flag(originatee_channel, CF_ORIGINATING);
    
    
     		originator_cp->transfer_source = switch_core_sprintf(originator_cp->pool,
     															 "%ld:%s:uuid_br:%s", (long)switch_epoch_time_now(NULL), originator_cp->uuid_str,
     															 switch_core_session_get_uuid(originatee_session));
     		switch_channel_add_variable_var_check(originator_channel, SWITCH_TRANSFER_HISTORY_VARIABLE,
     											  originator_cp->transfer_source, SWITCH_FALSE, SWITCH_STACK_PUSH);
     		switch_channel_set_variable(originator_channel, SWITCH_TRANSFER_SOURCE_VARIABLE, originator_cp->transfer_source);
    
    
     		originatee_cp->transfer_source = switch_core_sprintf(originatee_cp->pool,
     															 "%ld:%s:uuid_br:%s", (long)switch_epoch_time_now(NULL), originatee_cp->uuid_str,
     															 switch_core_session_get_uuid(originator_session));
     		switch_channel_add_variable_var_check(originatee_channel, SWITCH_TRANSFER_HISTORY_VARIABLE,
     											  originatee_cp->transfer_source, SWITCH_FALSE, SWITCH_STACK_PUSH);
     		switch_channel_set_variable(originatee_channel, SWITCH_TRANSFER_SOURCE_VARIABLE, originatee_cp->transfer_source);
    
     		/* change the states and let the chips fall where they may */
    
     		//switch_channel_set_variable(originator_channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE, NULL);
     		//switch_channel_set_variable(originatee_channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE, NULL);
     		switch_channel_clear_state_handler(originator_channel, NULL);
     		switch_channel_clear_state_handler(originatee_channel, NULL);
    
    
    
     		switch_channel_clear_state_flag(originator_channel, CF_BRIDGE_ORIGINATOR);
     		switch_channel_clear_state_flag(originatee_channel, CF_BRIDGE_ORIGINATOR);
    
     		switch_channel_clear_flag(originator_channel, CF_UUID_BRIDGE_ORIGINATOR);
     		switch_channel_clear_flag(originatee_channel, CF_UUID_BRIDGE_ORIGINATOR);
     		switch_channel_set_state_flag(originator_channel, CF_UUID_BRIDGE_ORIGINATOR);
    
     		switch_channel_add_state_handler(originator_channel, &uuid_bridge_state_handlers);
     		switch_channel_add_state_handler(originatee_channel, &uuid_bridge_state_handlers);
    
     		state = switch_channel_get_state(originator_channel);
     		switch_channel_set_state(originator_channel, state == CS_HIBERNATE ? CS_CONSUME_MEDIA : CS_HIBERNATE);
     		state = switch_channel_get_state(originatee_channel);
     		switch_channel_set_state(originatee_channel, state == CS_HIBERNATE ? CS_CONSUME_MEDIA : CS_HIBERNATE);
    
     		status = SWITCH_STATUS_SUCCESS;
    
     		//switch_ivr_bridge_display(originator_session, originatee_session);
    
     		/* release the read locks we have on the channels */
     		switch_core_session_rwunlock(originator_session);
     		switch_core_session_rwunlock(originatee_session);
    
     	} else {
     		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(originator_session), SWITCH_LOG_DEBUG, "originatee uuid %s is not present\n", originatee_uuid);
     		switch_core_session_rwunlock(originator_session);
     	}
     } else {
     	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(originator_session), SWITCH_LOG_DEBUG, "originator uuid %s is not present\n", originator_uuid);
     }
    
     return status;
    }
    
  3. 当 a-leg 状态切换为 CS_HIBERNATE,在核心状态机的调度下,安装在这条腿上的回调函数 switch_ivr_bridge.c#uuid_bridge_state_handlers.uuid_bridge_on_hibernate() 将被执行,推动当前腿的状态向 CS_RESET 切换。当状态发生改变,同理switch_ivr_bridge.c#uuid_bridge_state_handlers.uuid_bridge_on_reset() 将被执行

    static switch_status_t uuid_bridge_on_hibernate(switch_core_session_t *session)
    {
     switch_channel_set_state(switch_core_session_get_channel(session), CS_RESET);
     return SWITCH_STATUS_FALSE;
    }
    
  4. switch_ivr_bridge.c#uuid_bridge_state_handlers.uuid_bridge_on_reset() 函数会判断当前腿是否为主叫腿,也就是是否为 a-leg,如是则将其状态进一步驱动为 CS_SOFT_EXECUTE,而 b-leg 在此处则停留在 CS_RESET 状态

    static switch_status_t uuid_bridge_on_reset(switch_core_session_t *session)
    {
     switch_channel_t *channel = switch_core_session_get_channel(session);
    
     switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CUSTOM RESET\n", switch_channel_get_name(channel));
    
     switch_channel_clear_flag(channel, CF_ORIGINATING);
    
     cleanup_proxy_mode_b(session);
    
     if (switch_channel_test_flag(channel, CF_UUID_BRIDGE_ORIGINATOR)) {
     	switch_channel_set_state(channel, CS_SOFT_EXECUTE);
     }
    
     return SWITCH_STATUS_SUCCESS;
    }
    
  5. switch_ivr_bridge.c#uuid_bridge_state_handlers.uuid_bridge_on_soft_execute() 函数只会在 a-leg 上执行,其核心逻辑为以下几步:

    1. 清除 a-leg 上的回调函数表 switch_ivr_bridge.c#uuid_bridge_state_handlers,检测 b-leg 状态,如果 b-leg 抵达了CS_RESET 状态,则将其向 CS_SOFT_EXECUTE 状态推动,只有 b-leg 也到达这个状态才能继续向下处理
    2. 两条腿的状态都就绪后,继续执行 switch_ivr_bridge.c#switch_ivr_multi_threaded_bridge() 完成实际的桥接处理
    static switch_status_t uuid_bridge_on_soft_execute(switch_core_session_t *session)
    {
     switch_channel_t *channel = switch_core_session_get_channel(session);
     switch_core_session_t *other_session = NULL;
     const char *other_uuid = NULL;
    
     switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CUSTOM SOFT_EXECUTE\n", switch_channel_get_name(channel));
     switch_channel_clear_state_handler(channel, &uuid_bridge_state_handlers);
    
     if (!switch_channel_test_flag(channel, CF_UUID_BRIDGE_ORIGINATOR)) {
     	return SWITCH_STATUS_SUCCESS;
     }
    
     if ((other_uuid = switch_channel_get_variable(channel, SWITCH_UUID_BRIDGE)) && (other_session = switch_core_session_locate(other_uuid))) {
     	switch_channel_t *other_channel = switch_core_session_get_channel(other_session);
     	switch_event_t *event;
     	int ready_a, ready_b;
     	switch_channel_state_t state, running_state;
     	int max = 1000, loops = max;
    
     	switch_channel_set_variable(channel, SWITCH_UUID_BRIDGE, NULL);
    
     	for (;;) {
     		state = switch_channel_get_state(other_channel);
     		running_state = switch_channel_get_running_state(other_channel);
    
     		if (switch_channel_down_nosig(other_channel) || switch_channel_down(channel)) {
     			break;
     		}
    
     		if (state < CS_HANGUP && state == running_state) {
    
     			if (--loops < 1) {
     				switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
     				switch_channel_hangup(other_channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
     			}
    
     			if (running_state == CS_RESET) {
     				switch_channel_set_state(other_channel, CS_SOFT_EXECUTE);
     			}
    
     			if (running_state == CS_SOFT_EXECUTE) {
    
     				if (switch_channel_test_flag(other_channel, CF_UUID_BRIDGE_ORIGINATOR)) {
     					goto done;
     				} else {
     					break;
     				}
     			}
    
     		} else {
     			loops = max;
     		}
    
     		switch_yield(20000);
     	}
    
     	switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE);
    
     	if (switch_ivr_wait_for_answer(session, other_session) != SWITCH_STATUS_SUCCESS) {
     		if (switch_true(switch_channel_get_variable(channel, "uuid_bridge_continue_on_cancel"))) {
     			switch_channel_set_state(channel, CS_EXECUTE);
     		} else if (switch_true(switch_channel_get_variable(channel, "uuid_bridge_park_on_cancel"))) {
     			switch_ivr_park_session(session);
     		} else if (!switch_channel_test_flag(channel, CF_TRANSFER)) {
     			switch_channel_hangup(channel, SWITCH_CAUSE_ORIGINATOR_CANCEL);
     		}
     		goto done;
     	}
    
     	ready_a = switch_channel_ready(channel);
     	ready_b = switch_channel_ready(other_channel);
    
     	if (!ready_a || !ready_b) {
     		if (!ready_a) {
     			switch_channel_hangup(other_channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
     		}
    
     		if (!ready_b) {
     			const char *cid = switch_channel_get_variable(other_channel, "rdnis");
     			if (ready_a && cid) {
     				switch_ivr_session_transfer(session, cid, NULL, NULL);
     			} else {
     				switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
     			}
     		}
     		goto done;
     	}
    
     	/* fire events that will change the data table from "show channels" */
     	if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_EXECUTE) == SWITCH_STATUS_SUCCESS) {
     		switch_channel_event_set_data(channel, event);
     		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application", "uuid_bridge");
     		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-Data", switch_core_session_get_uuid(other_session));
     		switch_event_fire(&event);
     	}
    
     	if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_EXECUTE) == SWITCH_STATUS_SUCCESS) {
     		switch_channel_event_set_data(other_channel, event);
     		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application", "uuid_bridge");
     		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Application-Data", switch_core_session_get_uuid(session));
     		switch_event_fire(&event);
     	}
    
     	switch_ivr_multi_threaded_bridge(session, other_session, NULL, NULL, NULL);
    
     	state = switch_channel_get_state(channel);
     	if (!switch_channel_test_flag(channel, CF_TRANSFER) &&
     		!switch_channel_test_flag(channel, CF_REDIRECT) && state < CS_HANGUP && state != CS_ROUTING && state != CS_PARK) {
     		switch_channel_set_state(channel, CS_EXECUTE);
     	}
     } else {
     	int hup = 1;
     	const char *var;
     	
     	if (switch_true(switch_channel_get_variable(channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE))) {
     		switch_ivr_park_session(session);
     		hup = 0;
     	} else if ((var = switch_channel_get_variable(channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE))) {
     		transfer_after_bridge(session, var);
     		hup = 0;
     	}
     	
     	if (hup) {
     		switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
     	}
     }
    
    done:
    
     if (other_session) {
     	switch_core_session_rwunlock(other_session);
     	other_session = NULL;
     }
    
     switch_channel_clear_flag(channel, CF_UUID_BRIDGE_ORIGINATOR);
    
     return SWITCH_STATUS_FALSE;
    }
    
  6. switch_ivr_bridge.c#switch_ivr_multi_threaded_bridge() 函数比较长,不过核心步骤显而易见:

    1. 首先调用 switch_channel.c#switch_channel_add_state_handler() 将回调函数表switch_ivr_bridge.c#audio_bridge_peer_state_handlers 安装在 b-leg 上,随后发送 SWITCH_MESSAGE_INDICATE_BRIDGE 消息 通知底层 Sofia 模块将要进行桥接处理
    2. 完成后首先将 b-leg 驱动到 CS_EXCHANGE_MEDIA 状态,然后 a-leg 执行 switch_ivr_bridge.c#audio_bridge_thread() 函数开始和 b-leg 的媒体交互处理
    3. 媒体交互结束后还要检查 b-leg 是否停留在 CS_EXCHANGE_MEDIA 状态,不是则表明对端的媒体交互也结束了,此时发送 SWITCH_MESSAGE_INDICATE_UNBRIDGE消息 通知底层 Sofia 模块桥接结束
    4. 桥接结束后,检查相关配置,如果 a-leg 上有 park_after_bridge 之类的参数,则调用 switch_ivr_park_session() 函数将 a-leg 设置为 CS_PARK 状态
     SWITCH_DECLARE(switch_status_t) switch_ivr_multi_threaded_bridge(switch_core_session_t *session,
     															 switch_core_session_t *peer_session,
     															 switch_input_callback_function_t input_callback, void *session_data,
     															 void *peer_session_data)
    {
     switch_ivr_bridge_data_t *a_leg = switch_core_session_alloc(session, sizeof(*a_leg));
     switch_ivr_bridge_data_t *b_leg = switch_core_session_alloc(peer_session, sizeof(*b_leg));
     switch_channel_t *caller_channel = switch_core_session_get_channel(session);
     switch_channel_t *peer_channel = switch_core_session_get_channel(peer_session);
     int stream_id = 0;
     switch_status_t status = SWITCH_STATUS_SUCCESS;
     switch_channel_state_t state;
     switch_event_t *event;
     int br = 0;
     int inner_bridge = switch_channel_test_flag(caller_channel, CF_INNER_BRIDGE);
     const char *var;
     switch_call_cause_t cause;
     switch_core_session_message_t msg = { 0 };
    
     if (switch_channel_test_flag(caller_channel, CF_PROXY_MODE)) {
     	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Call has no media... Redirecting to signal bridge.\n");
     	return switch_ivr_signal_bridge(session, peer_session);
     }
    
     check_bridge_export(caller_channel, peer_channel);
    
     switch_channel_set_flag_recursive(caller_channel, CF_MEDIA_BRIDGE_TTL);
     switch_channel_set_flag_recursive(peer_channel, CF_MEDIA_BRIDGE_TTL);
    
     switch_channel_set_flag_recursive(caller_channel, CF_BRIDGE_ORIGINATOR);
     switch_channel_clear_flag(peer_channel, CF_BRIDGE_ORIGINATOR);
    
     switch_channel_audio_sync(caller_channel);
     switch_channel_audio_sync(peer_channel);
    
     b_leg->session = peer_session;
     switch_copy_string(b_leg->b_uuid, switch_core_session_get_uuid(session), sizeof(b_leg->b_uuid));
     b_leg->stream_id = stream_id;
     b_leg->input_callback = input_callback;
     b_leg->session_data = peer_session_data;
     b_leg->clean_exit = 0;
     b_leg->other_leg_data = a_leg;
    
     a_leg->session = session;
     switch_copy_string(a_leg->b_uuid, switch_core_session_get_uuid(peer_session), sizeof(a_leg->b_uuid));
     a_leg->stream_id = stream_id;
     a_leg->input_callback = input_callback;
     a_leg->session_data = session_data;
     a_leg->clean_exit = 0;
     a_leg->other_leg_data = b_leg;
    
     switch_channel_add_state_handler(peer_channel, &audio_bridge_peer_state_handlers);
    
     if (switch_channel_test_flag(peer_channel, CF_ANSWERED) && !switch_channel_test_flag(caller_channel, CF_ANSWERED)) {
     	switch_channel_pass_callee_id(peer_channel, caller_channel);
     	switch_channel_answer(caller_channel);
     }
    
     if (switch_channel_test_flag(peer_channel, CF_ANSWERED) || switch_channel_test_flag(peer_channel, CF_EARLY_MEDIA) ||
     	switch_channel_test_flag(peer_channel, CF_RING_READY)) {
     	const char *app, *data;
    
     	if (!switch_channel_ready(caller_channel)) {
     		abort_call(caller_channel, peer_channel);
     		goto done;
     	}
    
     	if (!switch_channel_test_flag(peer_channel, CF_ARRANGED_BRIDGE)) {
     		switch_channel_set_state(peer_channel, CS_CONSUME_MEDIA);
     	}
    
     	switch_channel_set_variable(peer_channel, "call_uuid", switch_core_session_get_uuid(session));
    
     	switch_channel_set_bridge_time(caller_channel);
     	switch_channel_set_bridge_time(peer_channel);
    
     	if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_BRIDGE) == SWITCH_STATUS_SUCCESS) {
     		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Bridge-A-Unique-ID", switch_core_session_get_uuid(session));
     		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Bridge-B-Unique-ID", switch_core_session_get_uuid(peer_session));
     		switch_channel_event_set_data(caller_channel, event);
     		switch_event_add_presence_data_cols(peer_channel, event, "Bridge-B-PD-");
     		switch_event_fire(&event);
     		br = 1;
     	}
    
     	if (switch_core_session_read_lock(peer_session) == SWITCH_STATUS_SUCCESS) {
     		switch_channel_set_variable(caller_channel, SWITCH_BRIDGE_VARIABLE, switch_core_session_get_uuid(peer_session));
     		switch_channel_set_variable(peer_channel, SWITCH_BRIDGE_VARIABLE, switch_core_session_get_uuid(session));
     		switch_channel_set_variable(caller_channel, SWITCH_LAST_BRIDGE_VARIABLE, switch_core_session_get_uuid(peer_session));
     		switch_channel_set_variable(peer_channel, SWITCH_LAST_BRIDGE_VARIABLE, switch_core_session_get_uuid(session));
    
     		if (!switch_channel_ready(caller_channel)) {
     			abort_call(caller_channel, peer_channel);
     			switch_core_session_rwunlock(peer_session);
     			goto done;
     		}
    
     		if (!switch_channel_media_ready(caller_channel) ||
     			(!switch_channel_test_flag(peer_channel, CF_ANSWERED) && !switch_channel_test_flag(peer_channel, CF_EARLY_MEDIA))) {
     			if ((status = switch_ivr_wait_for_answer(session, peer_session)) != SWITCH_STATUS_SUCCESS || !switch_channel_ready(caller_channel)) {
     				switch_channel_state_t w_state = switch_channel_get_state(caller_channel);
     				switch_channel_hangup(peer_channel, SWITCH_CAUSE_ALLOTTED_TIMEOUT);
     				if (w_state < CS_HANGUP && w_state != CS_ROUTING && w_state != CS_PARK &&
     					!switch_channel_test_flag(caller_channel, CF_REDIRECT) && !switch_channel_test_flag(caller_channel, CF_TRANSFER) &&
     					w_state != CS_EXECUTE) {
     					const char *ext = switch_channel_get_variable(peer_channel, "original_destination_number");
     					if (!ext) {
     						ext = switch_channel_get_variable(peer_channel, "destination_number");
     					}
    
     					if (ext) {
     						switch_ivr_session_transfer(session, ext, NULL, NULL);
     					} else {
     						switch_channel_hangup(caller_channel, SWITCH_CAUSE_ALLOTTED_TIMEOUT);
     					}
     				}
     				abort_call(caller_channel, peer_channel);
     				switch_core_session_rwunlock(peer_session);
     				goto done;
     			}
     		}
    
     		if (switch_channel_test_flag(peer_channel, CF_ANSWERED) && !switch_channel_test_flag(caller_channel, CF_ANSWERED)) {
     			switch_channel_answer(caller_channel);
     		}
    
     		switch_channel_wait_for_flag(peer_channel, CF_BROADCAST, SWITCH_FALSE, 10000, caller_channel);
     		switch_ivr_parse_all_events(peer_session);
     		switch_ivr_parse_all_events(session);
    
     		msg.message_id = SWITCH_MESSAGE_INDICATE_BRIDGE;
     		msg.from = __FILE__;
     		msg.string_arg = switch_core_session_strdup(peer_session, switch_core_session_get_uuid(session));
    
     		if (switch_core_session_receive_message(peer_session, &msg) != SWITCH_STATUS_SUCCESS) {
     			status = SWITCH_STATUS_FALSE;
     			abort_call(caller_channel, peer_channel);
     			switch_core_session_rwunlock(peer_session);
     			goto done;
     		}
    
     		msg.string_arg = switch_core_session_strdup(session, switch_core_session_get_uuid(peer_session));
     		if (switch_core_session_receive_message(session, &msg) != SWITCH_STATUS_SUCCESS) {
     			status = SWITCH_STATUS_FALSE;
     			abort_call(caller_channel, peer_channel);
     			switch_core_session_rwunlock(peer_session);
     			goto done;
     		}
    
     		switch_channel_set_variable(caller_channel, SWITCH_BRIDGE_CHANNEL_VARIABLE, switch_channel_get_name(peer_channel));
     		switch_channel_set_variable(caller_channel, SWITCH_BRIDGE_UUID_VARIABLE, switch_core_session_get_uuid(peer_session));
     		switch_channel_set_variable(caller_channel, SWITCH_SIGNAL_BOND_VARIABLE, switch_core_session_get_uuid(peer_session));
     		switch_channel_set_variable(peer_channel, SWITCH_BRIDGE_CHANNEL_VARIABLE, switch_channel_get_name(caller_channel));
     		switch_channel_set_variable(peer_channel, SWITCH_BRIDGE_UUID_VARIABLE, switch_core_session_get_uuid(session));
     		switch_channel_set_variable(peer_channel, SWITCH_SIGNAL_BOND_VARIABLE, switch_core_session_get_uuid(session));
    
     		if ((app = switch_channel_get_variable(caller_channel, "bridge_pre_execute_aleg_app"))) {
     			switch_channel_set_variable(caller_channel, "bridge_pre_execute_app", app);
    
     			if ((data = switch_channel_get_variable(caller_channel, "bridge_pre_execute_aleg_data"))) {
     				switch_channel_set_variable(caller_channel, "bridge_pre_execute_data", data);
     			}
     		}
    
     		if ((app = switch_channel_get_variable(caller_channel, "bridge_pre_execute_bleg_app"))) {
     			switch_channel_set_variable(peer_channel, "bridge_pre_execute_app", app);
    
     			if ((data = switch_channel_get_variable(caller_channel, "bridge_pre_execute_bleg_data"))) {
     				switch_channel_set_variable(peer_channel, "bridge_pre_execute_data", data);
     			}
    
     		}
    
     		switch_channel_set_private(peer_channel, "_bridge_", b_leg);
    
     		if (switch_channel_test_flag(peer_channel, CF_ARRANGED_BRIDGE)) {
     			switch_channel_clear_flag(peer_channel, CF_ARRANGED_BRIDGE);
     		} else {
     			switch_channel_set_state(peer_channel, CS_EXCHANGE_MEDIA);
     		}
     		
     		audio_bridge_thread(NULL, (void *) a_leg);
    
     		switch_channel_clear_flag_recursive(caller_channel, CF_BRIDGE_ORIGINATOR);
    
     		switch_channel_stop_broadcast(peer_channel);
    
    
     		while (switch_channel_get_state(peer_channel) == CS_EXCHANGE_MEDIA) {
     			switch_ivr_parse_all_messages(session);
     			switch_cond_next();
     		}
    
     		if (inner_bridge) {
     			if (switch_channel_ready(caller_channel)) {
     				switch_channel_set_flag(caller_channel, CF_BRIDGED);
     			}
    
     			if (switch_channel_ready(peer_channel)) {
     				switch_channel_set_flag(peer_channel, CF_BRIDGED);
     			}
     		}
    
     		if ((cause = switch_channel_get_cause(caller_channel))) {
     			switch_channel_set_variable(peer_channel, SWITCH_BRIDGE_HANGUP_CAUSE_VARIABLE, switch_channel_cause2str(cause));
     		}
    
     		if ((cause = switch_channel_get_cause(peer_channel))) {
     			switch_channel_set_variable(caller_channel, SWITCH_BRIDGE_HANGUP_CAUSE_VARIABLE, switch_channel_cause2str(cause));
     		}
    
     		if (switch_channel_down_nosig(peer_channel)) {
     			switch_bool_t copy_xml_cdr = switch_true(switch_channel_get_variable(peer_channel, SWITCH_COPY_XML_CDR_VARIABLE));
     			switch_bool_t copy_json_cdr = switch_true(switch_channel_get_variable(peer_channel, SWITCH_COPY_JSON_CDR_VARIABLE));
    
     			if (copy_xml_cdr || copy_json_cdr) {
     				char *cdr_text = NULL;
    
     				switch_channel_wait_for_state(peer_channel, caller_channel, CS_DESTROY);
    
     				if (copy_xml_cdr) {
     					switch_xml_t cdr = NULL;
    
     					if (switch_ivr_generate_xml_cdr(peer_session, &cdr) == SWITCH_STATUS_SUCCESS) {
     						cdr_text = switch_xml_toxml(cdr, SWITCH_FALSE);
     						switch_xml_free(cdr);
     					}
     				}
     				if (copy_json_cdr) {
     					cJSON *cdr = NULL;
    
     					if (switch_ivr_generate_json_cdr(peer_session, &cdr, SWITCH_TRUE) == SWITCH_STATUS_SUCCESS) {
     						cdr_text = cJSON_PrintUnformatted(cdr);
     						cJSON_Delete(cdr);
     					}
     				}
    
     				if (cdr_text) {
     					switch_channel_set_variable(caller_channel, "b_leg_cdr", cdr_text);
     					switch_channel_set_variable_name_printf(caller_channel, cdr_text, "b_leg_cdr_%s", switch_core_session_get_uuid(peer_session));
     					switch_safe_free(cdr_text);
     				}
     			}
    
     		}
    
     		switch_core_session_rwunlock(peer_session);
    
     	} else {
     		status = SWITCH_STATUS_FALSE;
     	}
     } else {
     	status = SWITCH_STATUS_FALSE;
     }
    
     if (status != SWITCH_STATUS_SUCCESS) {
     	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Bridge Failed %s->%s\n",
     					  switch_channel_get_name(caller_channel), switch_channel_get_name(peer_channel)
     		);
     	switch_channel_hangup(peer_channel, SWITCH_CAUSE_NO_ANSWER);
     }
    
    done:
    
     switch_channel_set_variable(peer_channel, "call_uuid", switch_core_session_get_uuid(peer_session));
    
     if (br && switch_event_create(&event, SWITCH_EVENT_CHANNEL_UNBRIDGE) == SWITCH_STATUS_SUCCESS) {
     	switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Bridge-A-Unique-ID", switch_core_session_get_uuid(session));
     	switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Bridge-B-Unique-ID", switch_core_session_get_uuid(peer_session));
     	switch_channel_event_set_data(caller_channel, event);
     	switch_event_add_presence_data_cols(peer_channel, event, "Bridge-B-PD-");
     	switch_event_fire(&event);
     }
    
     msg.message_id = SWITCH_MESSAGE_INDICATE_UNBRIDGE;
     msg.from = __FILE__;
     msg.string_arg = switch_core_session_strdup(peer_session, switch_core_session_get_uuid(session));
     switch_core_session_receive_message(peer_session, &msg);
    
     msg.string_arg = switch_core_session_strdup(session, switch_core_session_get_uuid(peer_session));
     switch_core_session_receive_message(session, &msg);
    
     state = switch_channel_get_state(caller_channel);
    
     if (!switch_channel_test_flag(caller_channel, CF_TRANSFER) && !switch_channel_test_flag(caller_channel, CF_REDIRECT) &&
     	!switch_channel_test_flag(caller_channel, CF_XFER_ZOMBIE) && !a_leg->clean_exit && !inner_bridge) {
     	switch_call_cause_t cause = switch_channel_get_cause(peer_channel);
     	const char *hup = switch_channel_get_variable(caller_channel, SWITCH_HANGUP_AFTER_BRIDGE_VARIABLE);
     	int explicit = 0;
     	int answered = 0;
     	int early = 0;
    
     	if (cause == SWITCH_CAUSE_NONE) {
     		cause = SWITCH_CAUSE_NORMAL_CLEARING;
     	}
    
     	if (hup) {
     		explicit = !strcasecmp(hup, "explicit");
     	}
    
     	if (!switch_channel_test_flag(peer_channel, CF_ANSWERED)) {
     		switch_channel_handle_cause(caller_channel, cause);
     	}
    
     	if (explicit) {
     		if (switch_channel_test_flag(peer_channel, CF_INTERCEPTED)) {
     			switch_channel_set_flag(peer_channel, CF_INTERCEPT);
     		}
     		switch_channel_hangup(caller_channel, cause);
     	}
    
     	answered = switch_channel_test_flag(peer_channel, CF_ANSWERED);
     	early = switch_channel_test_flag(peer_channel, CF_EARLY_MEDIA);
    
     	if ((state != CS_EXECUTE && state != CS_SOFT_EXECUTE && state != CS_PARK && state != CS_ROUTING) || ((answered || early) && state < CS_HANGUP)) {
    
     		if (!switch_channel_test_flag(caller_channel, CF_TRANSFER)) {
    
     			if ((answered && switch_true(switch_channel_get_variable(caller_channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE))) ||
     				switch_true(switch_channel_get_variable(caller_channel, SWITCH_PARK_AFTER_EARLY_BRIDGE_VARIABLE))) {
     				switch_ivr_park_session(session);
     			} else if ((answered && (var = switch_channel_get_variable(caller_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE))) ||
     					   (var = switch_channel_get_variable(caller_channel, SWITCH_TRANSFER_AFTER_EARLY_BRIDGE_VARIABLE))) {
     				transfer_after_bridge(session, var);
     			} else if (answered) {
     				if (switch_true(hup)) {
     					if (switch_channel_test_flag(peer_channel, CF_INTERCEPTED)) {
     						switch_channel_set_flag(peer_channel, CF_INTERCEPT);
     					}
     					switch_channel_hangup(caller_channel, cause);
     				}
     			}
     		}
     	}
     }
    
     if (switch_channel_test_flag(caller_channel, CF_REDIRECT)) {
     	if (switch_channel_test_flag(caller_channel, CF_RESET)) {
     		switch_channel_clear_flag(caller_channel, CF_RESET);
     	} else {
     		state = switch_channel_get_state(caller_channel);
     		if (!(state == CS_RESET || state == CS_PARK || state == CS_ROUTING || state == CS_EXECUTE)) {
     			switch_channel_set_state(caller_channel, CS_RESET);
     		}
     	}
     }
    
     return status;
    }
    
    
  7. b-leg 状态流转到 CS_EXCHANGE_MEDIA,则将回调执行 switch_ivr_bridge.c#audio_bridge_peer_state_handlers.audio_bridge_on_exchange_media() 函数,此处核心是在 b-leg 上执行 switch_ivr_bridge.c#audio_bridge_thread() 函数进行与 a-leg 的媒体交互

     static switch_status_t audio_bridge_on_exchange_media(switch_core_session_t *session)
    {
     switch_channel_t *channel = switch_core_session_get_channel(session);
     switch_ivr_bridge_data_t *bd = switch_channel_get_private(channel, "_bridge_");
     switch_channel_state_t state;
     const char *var;
    
     if (bd) {
     	switch_channel_set_private(channel, "_bridge_", NULL);
     	if (bd->session == session && *bd->b_uuid) {
     		audio_bridge_thread(NULL, (void *) bd);
     		switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE);
     	} else {
     		switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
     	}
     } else {
     	switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
     }
     switch_channel_clear_state_handler(channel, &audio_bridge_peer_state_handlers);
    
     state = switch_channel_get_state(channel);
    
     if (!switch_channel_test_flag(channel, CF_TRANSFER) && !switch_channel_test_flag(channel, CF_REDIRECT) &&
     	!switch_channel_test_flag(channel, CF_XFER_ZOMBIE) && bd && !bd->clean_exit && state != CS_PARK && state != CS_ROUTING &&
     	state == CS_EXCHANGE_MEDIA && !switch_channel_test_flag(channel, CF_INNER_BRIDGE)) {
    
     	if (state < CS_HANGUP && switch_true(switch_channel_get_variable(channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE))) {
     		switch_ivr_park_session(session);
     		return SWITCH_STATUS_FALSE;
     	} else if (state < CS_HANGUP && (var = switch_channel_get_variable(channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE))) {
     		transfer_after_bridge(session, var);
     		return SWITCH_STATUS_FALSE;
     	}
    
     	if (switch_channel_test_flag(channel, CF_INTERCEPTED)) {
     		switch_channel_clear_flag(channel, CF_INTERCEPT);
     		switch_channel_clear_flag(channel, CF_INTERCEPTED);
     		return SWITCH_STATUS_FALSE;
     	} else {
     		if (switch_channel_test_flag(channel, CF_INTERCEPT)) {
     			switch_channel_hangup(channel, SWITCH_CAUSE_PICKED_OFF);
     		} else {
     			if (!switch_channel_test_flag(channel, CF_ANSWERED)) {
     				int x = 0;
    
     				if (switch_channel_execute_on(channel, "execute_on_orphaned_bleg") == SWITCH_STATUS_SUCCESS) {
     					x++;
     				}
    
     				if (switch_channel_api_on(channel, "api_on_orphaned_bleg") == SWITCH_STATUS_SUCCESS) {
     					x++;
     				}
    
     				if (!x) {
     					switch_channel_hangup(channel, SWITCH_CAUSE_ORIGINATOR_CANCEL);
     				}
     				
     			} else {
     				switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
     			}
     		}
     	}
     }
    
     if (switch_channel_get_state(channel) == CS_EXCHANGE_MEDIA) {
     	switch_channel_set_variable(channel, "park_timeout", "3");
     	switch_channel_set_state(channel, CS_PARK);
     }
    
     return SWITCH_STATUS_FALSE;
    }
    
    
  8. switch_ivr_bridge.c#audio_bridge_thread() 函数的核心是从当前腿调用 switch_core_io.c#switch_core_session_read_frame() 读取媒体流,再调用 switch_core_io.c#switch_core_session_write_frame() 将其写入对端,完成媒体的交换,至此桥接的主体逻辑基本结束

     static void *audio_bridge_thread(switch_thread_t *thread, void *obj)
    {
     switch_ivr_bridge_data_t *data = obj;
     int stream_id = 0, pre_b = 0, ans_a = 0, ans_b = 0, originator = 0;
     switch_input_callback_function_t input_callback;
     switch_core_session_message_t msg = { 0 };
     void *user_data;
     switch_channel_t *chan_a, *chan_b;
     switch_frame_t *read_frame;
     switch_core_session_t *session_a, *session_b;
     uint32_t read_frame_count = 0;
     const char *app_name = NULL, *app_arg = NULL;
     int inner_bridge = 0, exec_check = 0;
     switch_codec_t silence_codec = { 0 };
     switch_frame_t silence_frame = { 0 };
     int16_t silence_data[SWITCH_RECOMMENDED_BUFFER_SIZE / 2] = { 0 };
     const char *silence_var;
     int silence_val = 0, bypass_media_after_bridge = 0;
     const char *bridge_answer_timeout = NULL;
     int bridge_filter_dtmf, answer_timeout, sent_update = 0;
     time_t answer_limit = 0;
     const char *exec_app = NULL;
     const char *exec_data = NULL;
     switch_codec_implementation_t read_impl = { 0 };
     uint32_t txt_launch = 0;
     struct vid_helper th = { 0 };
     const char *banner_file = NULL;
     int played_banner = 0, banner_counter = 0;
     int pass_val = 0, last_pass_val = 0;
    
    #ifdef SWITCH_VIDEO_IN_THREADS
     struct vid_helper vh = { 0 };
     uint32_t vid_launch = 0;
    #endif
     data->clean_exit = 0;
    
     session_a = data->session;
     if (!(session_b = switch_core_session_locate(data->b_uuid))) {
     	return NULL;
     }
    
     switch_core_session_get_read_impl(session_a, &read_impl);
    
     input_callback = data->input_callback;
     user_data = data->session_data;
     stream_id = data->stream_id;
    
     chan_a = switch_core_session_get_channel(session_a);
     chan_b = switch_core_session_get_channel(session_b);
    
     if ((exec_app = switch_channel_get_variable(chan_a, "bridge_pre_execute_app"))) {
     	exec_data = switch_channel_get_variable(chan_a, "bridge_pre_execute_data");
     }
    
     bypass_media_after_bridge = switch_channel_test_flag(chan_a, CF_BYPASS_MEDIA_AFTER_BRIDGE);
     switch_channel_clear_flag(chan_a, CF_BYPASS_MEDIA_AFTER_BRIDGE);
    
     ans_a = switch_channel_test_flag(chan_a, CF_ANSWERED);
    
     if ((originator = switch_channel_test_flag(chan_a, CF_BRIDGE_ORIGINATOR))) {
     	pre_b = switch_channel_test_flag(chan_a, CF_EARLY_MEDIA);
     	ans_b = switch_channel_test_flag(chan_b, CF_ANSWERED);
     }
    
     inner_bridge = switch_channel_test_flag(chan_a, CF_INNER_BRIDGE);
    
     if (!switch_channel_test_flag(chan_a, CF_ANSWERED) && (bridge_answer_timeout = switch_channel_get_variable(chan_a, "bridge_answer_timeout"))) {
     	if ((answer_timeout = atoi(bridge_answer_timeout)) >= 0) {
     		answer_limit = switch_epoch_time_now(NULL) + answer_timeout;
     	}
     }
    
     switch_channel_clear_flag(chan_a, CF_INTERCEPT);
     switch_channel_clear_flag(chan_a, CF_INTERCEPTED);
    
     switch_channel_set_flag(chan_a, CF_BRIDGED);
    
     switch_channel_wait_for_flag(chan_b, CF_BRIDGED, SWITCH_TRUE, 10000, chan_a);
    
     if (!switch_channel_test_flag(chan_b, CF_BRIDGED)) {
     	if (!(switch_channel_test_flag(chan_b, CF_TRANSFER) || switch_channel_test_flag(chan_b, CF_REDIRECT)
     		  || switch_channel_get_state(chan_b) == CS_RESET)) {
     		switch_channel_hangup(chan_b, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
     	}
     	goto end_of_bridge_loop;
     }
    
    
     if (switch_channel_test_flag(chan_a, CF_BRIDGE_ORIGINATOR) && (banner_file = switch_channel_get_variable(chan_a, "video_pre_call_banner"))) {
     	switch_channel_set_flag(chan_a, CF_AUDIO_PAUSE_READ);
     	switch_channel_set_flag(chan_b, CF_AUDIO_PAUSE_READ);
     	switch_channel_set_flag(chan_a, CF_AUDIO_PAUSE_WRITE);
     	switch_channel_set_flag(chan_b, CF_AUDIO_PAUSE_WRITE);
     	switch_channel_set_flag(chan_a, CF_VIDEO_PAUSE_READ);
     	switch_channel_set_flag(chan_b, CF_VIDEO_PAUSE_READ);
     }
    
    
     if (bypass_media_after_bridge) {
     	const char *source_a = switch_channel_get_variable(chan_a, "source");
     	const char *source_b = switch_channel_get_variable(chan_b, "source");
    
     	if (!source_a) source_a = "";
     	if (!source_b) source_b = "";
    
     	if (switch_stristr("loopback", source_a) || switch_stristr("loopback", source_b)) {
     		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_WARNING, "Cannot bypass media while bridged to a loopback address.\n");
     		bypass_media_after_bridge = 0;
     	}
     }
    
     if ((silence_var = switch_channel_get_variable(chan_a, "bridge_generate_comfort_noise"))) {
    
     	if (!switch_channel_media_up(chan_a)) {
     		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_ERROR, "Channel has no media!\n");
     		goto end_of_bridge_loop;
     	}
    
     	if (switch_true(silence_var)) {
     		silence_val = 1400;
     	} else {
     		if ((silence_val = atoi(silence_var)) < -1) {
     			silence_val = 0;
     		}
     	}
    
     	if (silence_val) {
     		if (switch_core_codec_init(&silence_codec,
     								   "L16",
     								   NULL,
     								   NULL,
     								   read_impl.actual_samples_per_second,
     								   read_impl.microseconds_per_packet / 1000,
     								   1,
     								   SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
     								   NULL, switch_core_session_get_pool(session_a)) != SWITCH_STATUS_SUCCESS) {
    
     			silence_val = 0;
     		} else {
     			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Setup generated silence from %s to %s at %d\n", switch_channel_get_name(chan_a),
     							  switch_channel_get_name(chan_b), silence_val);
     			silence_frame.codec = &silence_codec;
     			silence_frame.data = silence_data;
     			silence_frame.buflen = sizeof(silence_data);
     			silence_frame.datalen = read_impl.decoded_bytes_per_packet;
     			silence_frame.samples = silence_frame.datalen / sizeof(int16_t);
     		}
     	}
     }
    
     bridge_filter_dtmf = switch_true(switch_channel_get_variable(chan_a, "bridge_filter_dtmf"));
    
    
     for (;;) {
     	switch_channel_state_t b_state;
     	switch_status_t status;
     	switch_event_t *event;
    
     	if (switch_core_session_transcoding(session_a, session_b, SWITCH_MEDIA_TYPE_AUDIO)) {
     		pass_val = 1;
     	} else {
     		pass_val = 2;
     	}
     	
     	if (pass_val != last_pass_val) {
     		switch_core_session_passthru(session_a, SWITCH_MEDIA_TYPE_AUDIO, pass_val == 2 ? SWITCH_TRUE : SWITCH_FALSE);
     		last_pass_val = pass_val;
     	}
     	
     	if (switch_channel_test_flag(chan_a, CF_TRANSFER)) {
     		data->clean_exit = 1;
     	}
    
     	if (data->clean_exit || switch_channel_test_flag(chan_b, CF_TRANSFER)) {
     		switch_channel_clear_flag(chan_a, CF_HOLD);
     		switch_channel_clear_flag(chan_a, CF_SUSPEND);
     		goto end_of_bridge_loop;
     	}
    
     	if (!switch_channel_test_flag(chan_b, CF_BRIDGED)) {
     		goto end_of_bridge_loop;
     	}
    
     	if (!switch_channel_ready(chan_a)) {
     		if (switch_channel_up(chan_a)) {
     			data->clean_exit = 1;
     		}
     		goto end_of_bridge_loop;
     	}
    
     	if ((b_state = switch_channel_down_nosig(chan_b))) {
     		goto end_of_bridge_loop;
     	}
    
     	if (switch_channel_test_flag(chan_a, CF_HOLD_ON_BRIDGE)) {
     		switch_core_session_message_t hmsg = { 0 };
     		switch_channel_clear_flag(chan_a, CF_HOLD_ON_BRIDGE);
     		hmsg.message_id = SWITCH_MESSAGE_INDICATE_HOLD;
     		hmsg.from = __FILE__;
     		hmsg.numeric_arg = 1;
     		switch_core_session_receive_message(session_a, &hmsg);
     	}
    
     	if (read_frame_count > DEFAULT_LEAD_FRAMES && switch_channel_media_ack(chan_a) && switch_core_session_private_event_count(session_a)) {
     		switch_channel_set_flag(chan_b, CF_SUSPEND);
     		msg.numeric_arg = 42;
     		msg.string_arg = data->b_uuid;
     		msg.message_id = SWITCH_MESSAGE_INDICATE_UNBRIDGE;
     		msg.from = __FILE__;
     		switch_core_session_receive_message(session_a, &msg);
     		switch_ivr_parse_next_event(session_a);
     		msg.message_id = SWITCH_MESSAGE_INDICATE_BRIDGE;
     		switch_core_session_receive_message(session_a, &msg);
     		switch_channel_clear_flag(chan_b, CF_SUSPEND);
     		switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK);
     	}
    
     	switch_ivr_parse_all_messages(session_a);
    
     	if (!inner_bridge && (switch_channel_test_flag(chan_a, CF_SUSPEND) || switch_channel_test_flag(chan_b, CF_SUSPEND))) {
     		status = switch_core_session_read_frame(session_a, &read_frame, SWITCH_IO_FLAG_NONE, stream_id);
    
     		if (!SWITCH_READ_ACCEPTABLE(status)) {
     			goto end_of_bridge_loop;
     		}
     		continue;
     	}
    
     	if (switch_channel_test_flag(chan_a, CF_HAS_TEXT) && switch_channel_test_flag(chan_b, CF_HAS_TEXT) && !txt_launch) {
     		txt_launch++;
    
     		th.session_a = session_a;
     		th.session_b = session_b;
     		switch_core_media_start_engine_function(th.session_a, SWITCH_MEDIA_TYPE_TEXT, text_bridge_thread, &th);
    
     	}
    
    #ifdef SWITCH_VIDEO_IN_THREADS
     	if (switch_channel_test_flag(chan_a, CF_VIDEO) && switch_channel_test_flag(chan_b, CF_VIDEO) && !vid_launch) {
     		vid_launch++;
     		vh.session_a = session_a;
     		vh.session_b = session_b;
     		switch_channel_clear_flag(chan_a, CF_VIDEO_BLANK);
     		switch_channel_clear_flag(chan_b, CF_VIDEO_BLANK);
     		launch_video(&vh);
     	} else {
     		if (switch_channel_test_flag(chan_a, CF_VIDEO)) {
     			switch_channel_set_flag(chan_a, CF_VIDEO_BLANK);
     		}
    
     		if (switch_channel_test_flag(chan_b, CF_VIDEO)) {
     			switch_channel_set_flag(chan_b, CF_VIDEO_BLANK);
     		}
     	}
    #endif
    
     	if (read_frame_count >= DEFAULT_LEAD_FRAMES && switch_channel_media_ack(chan_a)) {
     		if (!played_banner && switch_channel_test_flag(chan_a, CF_VIDEO) && switch_channel_test_flag(chan_b, CF_VIDEO) &&
     			switch_channel_test_flag(chan_a, CF_ANSWERED) && switch_channel_test_flag(chan_b, CF_ANSWERED) &&
     			++banner_counter > 100 &&
     			switch_channel_test_flag(chan_a, CF_VIDEO_READY) && switch_channel_test_flag(chan_b, CF_VIDEO_READY) &&
     			switch_channel_test_flag(chan_a, CF_BRIDGE_ORIGINATOR) && banner_file) {
     			const char *b_banner_file = switch_channel_get_variable(chan_b, "video_pre_call_banner");
    
     			if (!b_banner_file) {
     				b_banner_file = switch_channel_get_variable(chan_a, "video_pre_call_banner_bleg");
     			}
    
    
     			switch_channel_clear_flag(chan_a, CF_VIDEO_PAUSE_READ);
     			switch_channel_clear_flag(chan_b, CF_VIDEO_PAUSE_READ);
    
     			if (b_banner_file) {
     				switch_ivr_broadcast(switch_core_session_get_uuid(session_a), banner_file, SMF_ECHO_ALEG);
     				switch_ivr_broadcast(switch_core_session_get_uuid(session_b), b_banner_file, SMF_ECHO_ALEG);
     			} else {
     				switch_ivr_broadcast(switch_core_session_get_uuid(session_a), banner_file, SMF_ECHO_ALEG | SMF_ECHO_BLEG);
     			}
    
     			played_banner = 1;
    
     			switch_channel_clear_flag(chan_a, CF_AUDIO_PAUSE_READ);
     			switch_channel_clear_flag(chan_b, CF_AUDIO_PAUSE_READ);
     			switch_channel_clear_flag(chan_a, CF_AUDIO_PAUSE_WRITE);
     			switch_channel_clear_flag(chan_b, CF_AUDIO_PAUSE_WRITE);
     		}
    
    
    
     		if (!exec_check) {
     			switch_channel_execute_on(chan_a, SWITCH_CHANNEL_EXECUTE_ON_PRE_BRIDGE_VARIABLE);
    
     			if (!inner_bridge) {
     				switch_channel_api_on(chan_a, SWITCH_API_BRIDGE_START_VARIABLE);
     			}
     			exec_check = 1;
     		}
    
     		if (exec_app) {
     			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "%s Bridge execute app %s(%s)\n",
     							  switch_channel_get_name(chan_a), exec_app, exec_data);
    
     			switch_core_session_execute_application_async(session_a, exec_app, exec_data);
     			exec_app = exec_data = NULL;
     		}
    
     		if ((bypass_media_after_bridge || switch_channel_test_flag(chan_b, CF_BYPASS_MEDIA_AFTER_BRIDGE)) && switch_channel_test_flag(chan_a, CF_ANSWERED)
     			&& switch_channel_test_flag(chan_b, CF_ANSWERED)) {
    
     			if (switch_true(switch_channel_get_variable_dup(chan_a, "bypass_media_after_bridge_oldschool", SWITCH_FALSE, -1))) {
     				switch_ivr_nomedia(switch_core_session_get_uuid(session_a), SMF_REBRIDGE);
     			} else {
     				switch_ivr_3p_nomedia(switch_core_session_get_uuid(session_a), SMF_REBRIDGE);
     			}
     			switch_channel_clear_flag(chan_b, CF_BYPASS_MEDIA_AFTER_BRIDGE);
     			goto end_of_bridge_loop;
     		}
     	}
    
     	/* if 1 channel has DTMF pass it to the other */
     	while (switch_channel_has_dtmf(chan_a)) {
     		switch_dtmf_t dtmf = { 0, 0 };
     		if (switch_channel_dequeue_dtmf(chan_a, &dtmf) == SWITCH_STATUS_SUCCESS) {
     			int send_dtmf = 1;
    
     			if (input_callback) {
     				switch_status_t cb_status = input_callback(session_a, (void *) &dtmf, SWITCH_INPUT_TYPE_DTMF, user_data, 0);
    
     				if (cb_status == SWITCH_STATUS_IGNORE) {
     					send_dtmf = 0;
     				} else if (cb_status != SWITCH_STATUS_SUCCESS) {
     					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "%s ended call via DTMF\n", switch_channel_get_name(chan_a));
     					switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK);
     					goto end_of_bridge_loop;
     				}
     			}
    
     			if (bridge_filter_dtmf) {
     				send_dtmf = 0;
     				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Dropping filtered DTMF received on %s\n", switch_channel_get_name(chan_a));
     			}
    
     			if (send_dtmf) {
     				switch_core_session_send_dtmf(session_b, &dtmf);
     				switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK);
     			}
     		}
     	}
    
     	if (switch_core_session_dequeue_event(session_a, &event, SWITCH_FALSE) == SWITCH_STATUS_SUCCESS) {
     		if (input_callback) {
     			input_callback(session_a, event, SWITCH_INPUT_TYPE_EVENT, user_data, 0);
     		}
    
     		if ((event->event_id != SWITCH_EVENT_COMMAND && event->event_id != SWITCH_EVENT_MESSAGE)
     			|| switch_core_session_receive_event(session_b, &event) != SWITCH_STATUS_SUCCESS) {
     			switch_event_destroy(&event);
     		}
    
     	}
    
     	if (!switch_channel_test_flag(chan_a, CF_ANSWERED) && answer_limit && switch_epoch_time_now(NULL) > answer_limit) {
     		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Answer timeout hit on %s.\n", switch_channel_get_name(chan_a));
     		if (switch_true(switch_channel_get_variable_dup(chan_a, "continue_on_answer_timeout", SWITCH_FALSE, -1))) {
     			data->clean_exit = 1;
     			goto end_of_bridge_loop;
     		} else {
     			switch_channel_hangup(chan_a, SWITCH_CAUSE_ALLOTTED_TIMEOUT);
     		}
     	}
    
     	if (!switch_channel_test_flag(chan_a, CF_ANSWERED)) {
     		if (originator) {
     			if (!ans_b && switch_channel_test_flag(chan_b, CF_ANSWERED)) {
     				switch_channel_pass_callee_id(chan_b, chan_a);
     				if (switch_channel_answer(chan_a) != SWITCH_STATUS_SUCCESS) {
     					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "%s Media Establishment Failed.\n", switch_channel_get_name(chan_a));
     					goto end_of_bridge_loop;
     				}
     				ans_a = 1;
     			} else if (!pre_b && switch_channel_test_flag(chan_b, CF_EARLY_MEDIA)) {
     				if (switch_channel_pre_answer(chan_a) != SWITCH_STATUS_SUCCESS) {
     					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "%s Media Establishment Failed.\n", switch_channel_get_name(chan_a));
     					goto end_of_bridge_loop;
     				}
     				pre_b = 1;
     			}
     			if (!pre_b) {
     				switch_yield(10000);
     				continue;
     			}
     		} else {
     			ans_a = switch_channel_test_flag(chan_b, CF_ANSWERED);
     		}
     	}
    
     	if (ans_a != ans_b) {
     		switch_channel_t *un = ans_a ? chan_b : chan_a;
     		switch_channel_t *a = un == chan_b ? chan_a : chan_b;
    
     		if (switch_channel_direction(un) == SWITCH_CALL_DIRECTION_INBOUND) {
     			if (switch_channel_direction(a) == SWITCH_CALL_DIRECTION_OUTBOUND || (un == chan_a && !originator)) {
     				switch_channel_pass_callee_id(a, un);
     			}
    
     			if (switch_channel_answer(un) != SWITCH_STATUS_SUCCESS) {
     				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "%s Media Establishment Failed.\n", switch_channel_get_name(un));
     				goto end_of_bridge_loop;
     			}
    
     			if (ans_a) {
     				ans_b = 1;
     			} else {
     				ans_a = 1;
     			}
     		}
     	}
    
     	if (originator && !ans_b) ans_b = switch_channel_test_flag(chan_b, CF_ANSWERED);
    
     	if (originator && !sent_update && ans_a && ans_b && switch_channel_media_ack(chan_a) && switch_channel_media_ack(chan_b)) {
     		switch_ivr_bridge_display(session_a, session_b);
     		sent_update = 1;
     	}
    #ifndef SWITCH_VIDEO_IN_THREADS
     	if (switch_channel_test_flag(chan_a, CF_VIDEO) && switch_channel_test_flag(chan_b, CF_VIDEO)) {
     		/* read video from 1 channel and write it to the other */
     		status = switch_core_session_read_video_frame(session_a, &read_frame, SWITCH_IO_FLAG_NONE, 0);
    
     		if (!SWITCH_READ_ACCEPTABLE(status)) {
     			goto end_of_bridge_loop;
     		}
    
     		switch_core_session_write_video_frame(session_b, read_frame, SWITCH_IO_FLAG_NONE, 0);
     	}
    #endif
    
     	if (!switch_channel_test_flag(chan_a, CF_AUDIO)) {
     		switch_ivr_sleep(session_a, 5000, SWITCH_FALSE, NULL);
     		continue;
     	}
    
    
     	/* read audio from 1 channel and write it to the other */
     	status = switch_core_session_read_frame(session_a, &read_frame, SWITCH_IO_FLAG_NONE, stream_id);
    
     	if (SWITCH_READ_ACCEPTABLE(status)) {
     		read_frame_count++;
     		if (switch_test_flag(read_frame, SFF_CNG)) {
     			if (silence_val) {
     				switch_generate_sln_silence((int16_t *) silence_frame.data, silence_frame.samples,
     											read_impl.number_of_channels, silence_val);
     				read_frame = &silence_frame;
     			} else if (!switch_channel_test_flag(chan_b, CF_ACCEPT_CNG)) {
     				continue;
     			}
     		}
    
     		if (switch_channel_test_flag(chan_a, CF_BRIDGE_NOWRITE)) {
     			continue;
     		}
    
     		if (status != SWITCH_STATUS_BREAK && !switch_channel_test_flag(chan_a, CF_HOLD) && !switch_channel_test_flag(chan_b, CF_LEG_HOLDING)) {
     			if (switch_core_session_write_frame(session_b, read_frame, SWITCH_IO_FLAG_NONE, stream_id) != SWITCH_STATUS_SUCCESS) {
     				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG,
     								  "%s ending bridge by request from write function\n", switch_channel_get_name(chan_b));
     				goto end_of_bridge_loop;
     			}
     		}
     	} else {
     		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "%s ending bridge by request from read function\n", switch_channel_get_name(chan_a));
     		goto end_of_bridge_loop;
     	}
     }
    
     end_of_bridge_loop:
    
     switch_core_session_passthru(session_a, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_FALSE);
    
    
    #ifdef SWITCH_VIDEO_IN_THREADS
     if (vh.up > 0) {
     	vh.up = -1;
     	switch_channel_set_flag(chan_a, CF_NOT_READY);
     	//switch_channel_set_flag(chan_b, CF_NOT_READY);
     	switch_core_session_kill_channel(session_a, SWITCH_SIG_BREAK);
     	switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK);
     	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Ending video thread.\n");
     }
    #endif
    
    
     if (silence_val) {
     	switch_core_codec_destroy(&silence_codec);
     }
    
     switch_channel_execute_on(chan_a, SWITCH_CHANNEL_EXECUTE_ON_POST_BRIDGE_VARIABLE);
    
     if (!inner_bridge) {
     	switch_channel_api_on(chan_a, SWITCH_API_BRIDGE_END_VARIABLE);
     }
    
     if (!inner_bridge && switch_channel_up_nosig(chan_a) && !switch_channel_test_flag(chan_a, CF_REDIRECT)) {
     	if ((app_name = switch_channel_get_variable(chan_a, SWITCH_EXEC_AFTER_BRIDGE_APP_VARIABLE))) {
     		switch_caller_extension_t *extension = NULL;
     		if ((extension = switch_caller_extension_new(session_a, app_name, app_name)) == 0) {
     			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_CRIT, "memory error!\n");
     			goto end;
     		}
     		app_arg = switch_channel_get_variable(chan_a, SWITCH_EXEC_AFTER_BRIDGE_ARG_VARIABLE);
    
     		switch_caller_extension_add_application(session_a, extension, (char *) app_name, app_arg);
     		switch_channel_set_caller_extension(chan_a, extension);
    
     		if (switch_channel_get_state(chan_a) == CS_EXECUTE) {
     			switch_channel_set_flag(chan_a, CF_RESET);
     		} else {
     			switch_channel_set_state(chan_a, CS_EXECUTE);
     		}
     	}
     }
    
      end:
    
    
     if (switch_core_media_check_engine_function(session_a, SWITCH_MEDIA_TYPE_TEXT)) {
     	if (th.up == 1) {
     		th.up = -1;
     	}
    
     	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Ending text thread.\n");
     	switch_core_media_end_engine_function(session_a, SWITCH_MEDIA_TYPE_TEXT);
     }
    
    #ifdef SWITCH_VIDEO_IN_THREADS
     if (switch_core_media_check_engine_function(session_a, SWITCH_MEDIA_TYPE_VIDEO)) {
     	if (vh.up == 1) {
     		vh.up = -1;
     	}
    
     	switch_channel_set_flag(chan_a, CF_VIDEO_BREAK);
     	switch_channel_set_flag(chan_b, CF_VIDEO_BREAK);
     	switch_core_session_kill_channel(session_a, SWITCH_SIG_BREAK);
     	switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK);
    
     	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Ending video thread.\n");
     	switch_core_media_end_engine_function(session_a, SWITCH_MEDIA_TYPE_VIDEO);
     	switch_channel_clear_flag(chan_a, CF_NOT_READY);
     	switch_channel_clear_flag(chan_b, CF_NOT_READY);
     }
    #endif
    
    
    
     switch_core_session_reset(session_a, SWITCH_TRUE, SWITCH_TRUE);
     switch_channel_set_variable(chan_a, SWITCH_BRIDGE_VARIABLE, NULL);
     switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "BRIDGE THREAD DONE [%s]\n", switch_channel_get_name(chan_a));
     switch_channel_clear_flag(chan_a, CF_BRIDGED);
    
     if (switch_channel_test_flag(chan_a, CF_LEG_HOLDING) || switch_channel_test_flag(chan_a, CF_HANGUP_HELD)) {
     	if (switch_channel_ready(chan_b) &&
     		switch_channel_get_state(chan_b) != CS_PARK && !data->other_leg_data->clean_exit && !switch_channel_test_flag(chan_b, CF_3P_NOMEDIA_REQUESTED)) {
     		const char *ext = switch_channel_get_variable(chan_a, "hold_hangup_xfer_exten");
    
     		switch_channel_stop_broadcast(chan_b);
    
     		if (zstr(ext)) {
     			switch_call_cause_t cause = switch_channel_get_cause(chan_b);
     			if (cause == SWITCH_CAUSE_NONE) {
     				cause = SWITCH_CAUSE_NORMAL_CLEARING;
     			}
     			switch_channel_hangup(chan_b, cause);
     		} else {
     			switch_channel_set_variable(chan_b, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE, ext);
     		}
     	}
    
     	if (switch_channel_test_flag(chan_a, CF_LEG_HOLDING)) {
     		switch_channel_mark_hold(chan_a, SWITCH_FALSE);
     	}
     }
    
     if (switch_channel_test_flag(chan_a, CF_INTERCEPTED)) {
     	switch_channel_set_flag(chan_b, CF_INTERCEPT);
     }
    
     switch_channel_clear_flag(chan_a, CF_VIDEO_BLANK);
     switch_channel_clear_flag(chan_b, CF_VIDEO_BLANK);
    
     switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK);
     data->done = 1;
     switch_core_session_video_reset(session_a);
     switch_core_session_video_reset(session_b);
    
     switch_core_session_rwunlock(session_b);
     return NULL;
    }
    
    

3. uuid_bridge 问题处理

uuid_bridge 命令的使用实际上有一个时机问题需要格外注意,当监听到 CHANNEL_PROGRESS_MEDIA 事件就立即使用这个命令时,非常有可能遇到命令执行不成功的问题。这个问题发生的前提是使用尚未接通的腿作为 a-leg 去 bridge 另一条就绪的 b-leg,具体流程如下所示:

在这里插入图片描述

  1. originate 命令呼叫,拉起 a-leg。在完整的 originate 命令执行周期内状态机流转为 CS_NEW -> CS_INIT -> CS_ROUTING -> CS_EXECUTE,而在流转到 CS_ROUTING 之前 CHANNEL_PROGRESS_MEDIA 事件 就发出来了
  2. esl 监听到 CHANNEL_PROGRESS_MEDIA 事件 时下发 uuid_bridge 命令,在这个命令执行后 a-leg 完整的状态流转为 ANY -> CS_HIBERNATE/CS_CONSUME_MEDIA -> CS_RESET -> CS_SOFT_EXECUTE。a-leg 在 CS_SOFT_EXECUTE 状态时会驱动 b-leg 也到达 CS_SOFT_EXECUTE 状态,所以一旦 a-leg 在 uuid_bridge 周期内的状态流转出现异常,则 b-leg 会一直停留在 CS_RESET
  3. 从上图可以看到,如果 originate 和 uuid_bridge 执行后推动的状态机流转在时间上有重叠,则 a-leg 桥接 b-leg 时预期的状态流转很可能被打断,无法到达 CS_SOFT_EXECUTE 导致桥接事实上失败,双方无法通话。另一个影响是导致 b-leg 一直停留在 CS_RESET,而在这个状态时 FreeSWITCH 不会读这条腿上发过来的 RTP 数据,也就不会反馈 RTCP 数据给发送端,如果 b-leg 的 SIP 设备有 RTP 超时检测则可能认为媒体服务器异常,会主动发送 BYE 结束会话

这个问题的本质是 uuid_bridge 执行后 a-leg 的状态机未按照预期流转,解决方案有 2 个,读者如遇到相似问题可以参考:

  1. 修改 FreeSWITCH 源码
    uuid_bridge 命令执行后,a-leg 上安装的回调函数表 uuid_bridge_state_handlers 函数会在状态机流转相应状态时执行,从此处将 a-leg 向 CS_SOFT_EXECUTE 状态驱动。这个过程被 CS_EXECUTE 状态打断后,只需要在 uuid_bridge_state_handlerson_execute 阶段安装相应的回调函数让状态机重新流转起来即可
  2. 修改上层业务逻辑
    修改 uuid_bridge 命令的发送时机,将其延后到 a-leg CS_EXECUTE 状态后再发送,这样就避免了命令执行时序混乱导致的状态机流转异常

文章来源:https://blog.csdn.net/weixin_45505313/article/details/135139203
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。