[Asterisk-code-review] res_pjsip_session: Handle re-invite collisions better (asterisk[certified/16.8])

George Joseph asteriskteam at digium.com
Fri Aug 14 09:51:54 CDT 2020


George Joseph has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/14730 )


Change subject: res_pjsip_session:  Handle re-invite collisions better
......................................................................

res_pjsip_session:  Handle re-invite collisions better

When both Asterisk and a UA send re-invites at the same time, both
send 491 "Transaction in progress" responses to each other and back
off a specified amount of time before retrying. When Asterisk
prepares to send its re-invite, it sets up the session's pending
media state with the new topology it wants, then sends the
re-invite.  Unfortunately, when it received the re-invite from the
UA, it partially processed the media in the re-invite and reset
the pending media state before sending the 491 losing the state it
set in its own re-invite.

There are 2 scenarios that need to be handled:

 1.  Asterisk is the UAS of the original INVITE transaction.
     Per RFC 3261, Asterisk will backoff between 0 and 2000ms
     before resending its re-invite and the original UAC will
     backoff between 2100 and 4000ms before resending its
     re-invite so in this case Asterisk will resend first.

   * We now do a check in session_reinvite_on_rx_request() to see if
     there's a pending invite transaction and if there is, we return
     PJ_FALSE to pjproject before we touch the media state and it
     pjproject continues on to send the 491.  This way when Asterisk
     resends the re-invite, the state is correct.

 2.  Asterisk is the UAC of the original INVITE transaction.
     The backoff timers are reversed now so Asterisk will
     backoff between 2100 and 4000ms before resending its re-invite
     and the original UAS will backoff between 0 and 2000ms before
     resending its the re-invite.  In this case, Asterisk is going
     to receive a re-invite before it resends its own.

   * In reschedule_reinvite(), we now create a temporary media state
     and clone the pending topology into it.  That then gets passed
     to delay_request() which already has the capability to use the
     state passed to it instead of the one currently on the session
     when the timer triggers to re-send the re-invite.  This way we
     can process the incoming one without losing the state of the
     one we sent.

Regardless of how the delayed re-invite was triggered, we need to
reconcile the topology that was active at the time the delayed
request was queued, the pending topology of the queued request,
and the topology currently active on the session.

 * Added a new function merge_refresh_media_states() which
   does the above.  sip_session_refresh calls that function if
   all 3 of the topologies mentioned are available on the delayed
   request structure.

 * Updated some stream topology APIs to automatically set the stream
   name to <type>-<position> if not specified to make searching for
   streams easier. There was also a code formatting fix.

Changes made for debugging purposes:

 * LOTS of debugging has been added to res_pjsip_session.

 * LOTS of debugging has been changed in res_pjsip_session to
   make debug levels consistent and to add the endpoint or
   channel name.

 * The stream name was added to the ast_sip_session_media
   structure just for debugging purposes.  It allows is to
   confirm that the session media and the stream really match
   over time.

ASTERISK-29014

Change-Id: Id3440972943c611a15f652c6c569fa0e4536bfcb
---
M bridges/bridge_softmix.c
M include/asterisk/res_pjsip_session.h
M include/asterisk/stream.h
M main/stream.c
M res/res_pjsip_session.c
5 files changed, 1,446 insertions(+), 187 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/30/14730/1

diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index 977cbd3..bc10ebf 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -630,6 +630,7 @@
 
 	joiner_video = ast_stream_topology_alloc();
 	if (!joiner_video) {
+		ast_log(LOG_ERROR, "%s: Couldn't alloc topology\n", ast_channel_name(joiner->chan));
 		return;
 	}
 
@@ -643,6 +644,7 @@
 	ast_channel_unlock(joiner->chan);
 
 	if (res || !sc->topology) {
+		ast_log(LOG_ERROR, "%s: Couldn't append source streams\n", ast_channel_name(joiner->chan));
 		goto cleanup;
 	}
 
@@ -656,11 +658,17 @@
 			ast_channel_get_stream_topology(participant->chan));
 		ast_channel_unlock(participant->chan);
 		if (res) {
+			ast_log(LOG_ERROR, "%s/%s: Couldn't append source streams\n",
+				ast_channel_name(participant->chan), ast_channel_name(joiner->chan));
 			goto cleanup;
 		}
 	}
 
-	ast_channel_request_stream_topology_change(joiner->chan, sc->topology, NULL);
+	res = ast_channel_request_stream_topology_change(joiner->chan, sc->topology, NULL);
+	if (res) {
+		ast_log(LOG_ERROR, "%s: Couldn't request topology change\n", ast_channel_name(joiner->chan));
+		goto cleanup;
+	}
 
 	AST_LIST_TRAVERSE(participants, participant, entry) {
 		if (participant == joiner) {
@@ -669,9 +677,16 @@
 
 		sc = participant->tech_pvt;
 		if (append_all_streams(sc->topology, joiner_video)) {
+			ast_log(LOG_ERROR, "%s/%s: Couldn't apopend streams\n",
+				ast_channel_name(participant->chan), ast_channel_name(joiner->chan));
 			goto cleanup;
 		}
-		ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL);
+		res = ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL);
+		if (res) {
+			ast_log(LOG_ERROR, "%s/%s: Couldn't request topology change\n",
+				ast_channel_name(participant->chan), ast_channel_name(joiner->chan));
+			goto cleanup;
+		}
 	}
 
 cleanup:
@@ -1086,7 +1101,7 @@
 				 * change the name so that routing does not attempt to route video
 				 * to this stream.
 				 */
-				removed = ast_stream_clone(stream, "removed");
+				removed = ast_stream_clone(stream, NULL);
 				if (!removed) {
 					return -1;
 				}
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index 7988303..56a7078 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -115,6 +115,10 @@
 	unsigned int changed;
 	/*! \brief Remote media stream label */
 	char *remote_mslabel;
+	/*! \brief Remote stream label */
+	char *remote_label;
+	/*! \brief Stream name */
+	char *stream_name;
 };
 
 /*!
@@ -808,6 +812,16 @@
 struct ast_sip_session_media_state *ast_sip_session_media_state_alloc(void);
 
 /*!
+ * \brief Clone a session media object
+ *
+ * \param src The source session media object
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_sip_session_media *ast_sip_session_media_clone(struct ast_sip_session_media *src);
+
+/*!
  * \brief Allocate an ast_session_media and add it to the media state's vector.
  * \since 15.0.0
  *
diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h
index a91124b..206edbb 100644
--- a/include/asterisk/stream.h
+++ b/include/asterisk/stream.h
@@ -416,6 +416,8 @@
  * \returns the position of the stream in the topology (-1 on error)
  *
  * \since 15
+ *
+ * \note If the stream's name is empty, it'll be set to <stream_type>-<position>
  */
 int ast_stream_topology_append_stream(struct ast_stream_topology *topology,
 	struct ast_stream *stream);
@@ -461,6 +463,8 @@
  * the first unused position.  You can't set positions beyond that.
  *
  * \since 15
+ *
+ * \note If the stream's name is empty, it'll be set to <stream_type>-<position>
  */
 int ast_stream_topology_set_stream(struct ast_stream_topology *topology,
 	unsigned int position, struct ast_stream *stream);
diff --git a/main/stream.c b/main/stream.c
index 091eb1c..3274e16 100644
--- a/main/stream.c
+++ b/main/stream.c
@@ -101,10 +101,12 @@
        [AST_STREAM_STATE_INACTIVE] = "inactive",
 };
 
+#define MIN_STREAM_NAME_LEN 16
+
 struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type)
 {
 	struct ast_stream *stream;
-	size_t name_len = MAX(strlen(S_OR(name, "")), 7); /* Ensure there is enough room for 'removed' */
+	size_t name_len = MAX(strlen(S_OR(name, "")), MIN_STREAM_NAME_LEN); /* Ensure there is enough room for 'removed' or a type-position */
 
 	stream = ast_calloc(1, sizeof(*stream) + name_len + 1);
 	if (!stream) {
@@ -130,7 +132,9 @@
 	}
 
 	stream_name = name ?: stream->name;
-	name_len = MAX(strlen(stream_name), 7); /* Ensure there is enough room for 'removed' */
+
+	name_len = MAX(strlen(S_OR(stream_name, "")), MIN_STREAM_NAME_LEN); /* Ensure there is enough room for 'removed' or a type-position */
+
 	new_stream = ast_calloc(1, sizeof(*stream) + name_len + 1);
 	if (!new_stream) {
 		return NULL;
@@ -242,6 +246,7 @@
 
 	stream->state = state;
 
+#if 0
 	/* When a stream is set to removed that means that any previous data for it
 	 * is no longer valid. We therefore change its name to removed and remove
 	 * any old metadata associated with it.
@@ -254,6 +259,7 @@
 			ast_format_cap_remove_by_type(stream->formats, AST_MEDIA_TYPE_UNKNOWN);
 		}
 	}
+#endif
 }
 
 const char *ast_stream_state2str(enum ast_stream_state state)
@@ -510,6 +516,10 @@
 
 	stream->position = AST_VECTOR_SIZE(&topology->streams) - 1;
 
+	if (ast_strlen_zero(stream->name)) {
+		snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position);
+	}
+
 	return AST_VECTOR_SIZE(&topology->streams) - 1;
 }
 
@@ -522,18 +532,18 @@
 
 int ast_stream_topology_get_active_count(const struct ast_stream_topology *topology)
 {
-       int i;
-       int count = 0;
-       ast_assert(topology != NULL);
+	int i;
+	int count = 0;
+	ast_assert(topology != NULL);
 
-       for (i = 0; i < AST_VECTOR_SIZE(&topology->streams); i++) {
-               struct ast_stream *stream = AST_VECTOR_GET(&topology->streams, i);
-               if (stream->state != AST_STREAM_STATE_REMOVED) {
-                       count++;
-               }
-       }
+	for (i = 0; i < AST_VECTOR_SIZE(&topology->streams); i++) {
+		struct ast_stream *stream = AST_VECTOR_GET(&topology->streams, i);
+		if (stream->state != AST_STREAM_STATE_REMOVED) {
+			count++;
+		}
+	}
 
-       return count;
+	return count;
 }
 
 struct ast_stream *ast_stream_topology_get_stream(
@@ -566,6 +576,10 @@
 		return AST_VECTOR_APPEND(&topology->streams, stream);
 	}
 
+	if (ast_strlen_zero(stream->name)) {
+		snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position);
+	}
+
 	return AST_VECTOR_REPLACE(&topology->streams, position, stream);
 }
 
@@ -624,12 +638,13 @@
 			return NULL;
 		}
 
-		stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
+		stream = ast_stream_alloc(NULL, type);
 		if (!stream) {
 			ao2_cleanup(new_cap);
 			ast_stream_topology_free(topology);
 			return NULL;
 		}
+
 		/* We're transferring the initial ref so no bump needed */
 		stream->formats = new_cap;
 		stream->state = AST_STREAM_STATE_SENDRECV;
@@ -638,6 +653,9 @@
 			ast_stream_topology_free(topology);
 			return NULL;
 		}
+
+		snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position);
+
 	}
 
 	return topology;
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index f89416e..d028336 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -74,7 +74,8 @@
 		ast_sip_session_sdp_creation_cb on_sdp_creation,
 		ast_sip_session_response_cb on_response,
 		enum ast_sip_session_refresh_method method, int generate_new_sdp,
-		struct ast_sip_session_media_state *media_state,
+		struct ast_sip_session_media_state *pending_media_state,
+		struct ast_sip_session_media_state *active_media_state,
 		int queued);
 
 /*! \brief NAT hook for modifying outgoing messages with SDP */
@@ -480,18 +481,24 @@
 
 	ast_free(session_media->mid);
 	ast_free(session_media->remote_mslabel);
+	ast_free(session_media->remote_label);
+	ast_free(session_media->stream_name);
 }
 
 struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,
 	struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position)
 {
 	struct ast_sip_session_media *session_media = NULL;
+	SCOPE_ENTER(1, "%s Adding position %d\n", ast_sip_session_get_name(session), position);
 
 	/* It is possible for this media state to already contain a session for the stream. If this
 	 * is the case we simply return it.
 	 */
 	if (position < AST_VECTOR_SIZE(&media_state->sessions)) {
-		return AST_VECTOR_GET(&media_state->sessions, position);
+		session_media = AST_VECTOR_GET(&media_state->sessions, position);
+		if (session_media) {
+			SCOPE_EXIT_RTN_VALUE(session_media, "Using existing media_session\n");
+		}
 	}
 
 	/* Determine if we can reuse the session media from the active media state if present */
@@ -500,6 +507,7 @@
 		/* A stream can never exist without an accompanying media session */
 		if (session_media->type == type) {
 			ao2_ref(session_media, +1);
+			ast_trace(1, "Reusing existing media session\n");
 			/*
 			 * If this session_media was previously removed, its bundle group was probably reset
 			 * to -1 so if bundling is enabled on the endpoint, we need to reset it to 0, set
@@ -511,10 +519,12 @@
 				ast_free(session_media->mid);
 				if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) {
 					ao2_ref(session_media, -1);
-					return NULL;
+					SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't alloc mid\n");
 				}
 			}
 		} else {
+			ast_trace(1, "Can't reuse existing media session because the types are different. %s <> %s\n",
+				ast_codec_media_type2str(type), ast_codec_media_type2str(session_media->type));
 			session_media = NULL;
 		}
 	}
@@ -525,6 +535,7 @@
 		if (!session_media) {
 			return NULL;
 		}
+		ast_trace(1, "Creating new media session\n");
 
 		session_media->encryption = session->endpoint->media.rtp.encryption;
 		session_media->remote_ice = session->endpoint->media.rtp.ice_support;
@@ -540,7 +551,7 @@
 			 */
 			if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) {
 				ao2_ref(session_media, -1);
-				return NULL;
+				SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't alloc mid\n");
 			}
 			session_media->bundle_group = 0;
 
@@ -554,18 +565,23 @@
 		}
 	}
 
+	ast_free(session_media->stream_name);
+	session_media->stream_name = ast_strdup(ast_stream_get_name(ast_stream_topology_get_stream(media_state->topology, position)));
+
 	if (AST_VECTOR_REPLACE(&media_state->sessions, position, session_media)) {
 		ao2_ref(session_media, -1);
 
-		return NULL;
+		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't replace media_session\n");
 	}
 
 	/* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */
 	if (!media_state->default_session[type] && ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) {
+		ast_trace(1, "Setting media session as default for %s\n", ast_codec_media_type2str(session_media->type));
+
 		media_state->default_session[type] = session_media;
 	}
 
-	return session_media;
+	SCOPE_EXIT_RTN_VALUE(session_media, "Done\n");
 }
 
 static int is_stream_limitation_reached(enum ast_media_type type, const struct ast_sip_endpoint *endpoint, int *type_streams)
@@ -670,6 +686,8 @@
 
 	ast_free(session_media->remote_mslabel);
 	session_media->remote_mslabel = NULL;
+	ast_free(session_media->remote_label);
+	session_media->remote_label = NULL;
 
 	for (index = 0; index < stream->attr_count; ++index) {
 		pjmedia_sdp_attr *attr = stream->attr[index];
@@ -678,8 +696,12 @@
 		char *msid, *tmp = attr_value;
 		static const pj_str_t STR_msid = { "msid", 4 };
 		static const pj_str_t STR_ssrc = { "ssrc", 4 };
+		static const pj_str_t STR_label = { "label", 5 };
 
-		if (!pj_strcmp(&attr->name, &STR_msid)) {
+		if (!pj_strcmp(&attr->name, &STR_label)) {
+			ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));
+			session_media->remote_label = ast_strdup(attr_value);
+		} else if (!pj_strcmp(&attr->name, &STR_msid)) {
 			ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));
 			msid = strsep(&tmp, " ");
 			session_media->remote_mslabel = ast_strdup(msid);
@@ -741,7 +763,8 @@
 	int type_streams[AST_MEDIA_TYPE_END] = {0};
 
 	if (session->inv_session && session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
-		ast_log(LOG_ERROR, "Failed to handle incoming SDP. Session has been already disconnected\n");
+		ast_log(LOG_ERROR, "%s: Failed to handle incoming SDP. Session has been already disconnected\n",
+			ast_sip_session_get_name(session));
 		return -1;
 	}
 
@@ -774,27 +797,37 @@
 		}
 		if (!stream) {
 			struct ast_stream *existing_stream = NULL;
+			char *stream_name = NULL;
+			const char *stream_label = NULL;
 
 			if (session->active_media_state->topology &&
 				(i < ast_stream_topology_get_count(session->active_media_state->topology))) {
 				existing_stream = ast_stream_topology_get_stream(session->active_media_state->topology, i);
+				if (ast_stream_get_state(existing_stream) != AST_STREAM_STATE_REMOVED) {
+					stream_name = (char *)ast_stream_get_name(existing_stream);
+					stream_label = ast_stream_get_metadata(existing_stream, "SDP:LABEL");
+				}
 			}
 
-			stream = ast_stream_alloc(existing_stream ? ast_stream_get_name(existing_stream) : ast_codec_media_type2str(type), type);
+			if (ast_strlen_zero(stream_name)) {
+				if (ast_asprintf(&stream_name, "%s-%d", ast_codec_media_type2str(type), i) < 0) {
+					return -1;
+				}
+			}
+
+			stream = ast_stream_alloc(stream_name, type);
 			if (!stream) {
 				return -1;
 			}
+
+			if (!ast_strlen_zero(stream_label)) {
+				ast_stream_set_metadata(stream, "SDP:LABEL", stream_label);
+			}
+
 			if (ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream)) {
 				ast_stream_free(stream);
 				return -1;
 			}
-			if (existing_stream) {
-				const char *stream_label = ast_stream_get_metadata(existing_stream, "SDP:LABEL");
-
-				if (!ast_strlen_zero(stream_label)) {
-					ast_stream_set_metadata(stream, "SDP:LABEL", stream_label);
-				}
-			}
 
 			/* For backwards compatibility with the core the default audio stream is always sendrecv */
 			if (!ast_sip_session_is_pending_stream_default(session, stream) || strcmp(media, "audio")) {
@@ -823,8 +856,8 @@
 
 		/* If this stream is already declined mark it as such, or mark it as such if we've reached the limit */
 		if (!remote_stream->desc.port || is_stream_limitation_reached(type, session->endpoint, type_streams)) {
-			ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
-				ast_codec_media_type2str(type), i);
+			ast_debug(4, "%s: Declining incoming SDP media stream '%s' at position '%d'\n",
+				ast_sip_session_get_name(session), ast_codec_media_type2str(type), i);
 			remove_stream_from_bundle(session_media, stream);
 			continue;
 		}
@@ -834,21 +867,21 @@
 
 		if (session_media->handler) {
 			handler = session_media->handler;
-			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
-				ast_codec_media_type2str(session_media->type),
+			ast_debug(4, "%s: Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
+				ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
 				session_media->handler->id);
 			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
 				return -1;
 			} else if (res == 0) {
-				ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
-					ast_codec_media_type2str(type), i);
+				ast_debug(4, "%s: Declining incoming SDP media stream '%s' at position '%d'\n",
+					ast_sip_session_get_name(session), ast_codec_media_type2str(type), i);
 				remove_stream_from_bundle(session_media, stream);
 				continue;
 			} else if (res > 0) {
-				ast_debug(1, "Media stream '%s' handled by %s\n",
-					ast_codec_media_type2str(session_media->type),
+				ast_debug(4, "%s: Media stream '%s' handled by %s\n",
+					ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
 					session_media->handler->id);
 				/* Handled by this handler. Move to the next stream */
 				handled = 1;
@@ -859,28 +892,28 @@
 
 		handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
 		if (!handler_list) {
-			ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+			ast_debug(4, "%s: No registered SDP handlers for media type '%s'\n", ast_sip_session_get_name(session), media);
 			continue;
 		}
 		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
 			if (handler == session_media->handler) {
 				continue;
 			}
-			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
-				ast_codec_media_type2str(session_media->type),
+			ast_debug(4, "%s: Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
+				ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
 				handler->id);
 			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
 				return -1;
 			} else if (res == 0) {
-				ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
-					ast_codec_media_type2str(type), i);
+				ast_debug(4, "%s: Declining incoming SDP media stream '%s' at position '%d'\n",
+					ast_sip_session_get_name(session), ast_codec_media_type2str(type), i);
 				remove_stream_from_bundle(session_media, stream);
 				continue;
 			} else if (res > 0) {
-				ast_debug(1, "Media stream '%s' handled by %s\n",
-					ast_codec_media_type2str(session_media->type),
+				ast_debug(4, "%s: Media stream '%s' handled by %s\n",
+					ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
 					handler->id);
 				/* Handled by this handler. Move to the next stream */
 				session_media_set_handler(session_media, handler);
@@ -936,13 +969,13 @@
 
 	handler = session_media->handler;
 	if (handler) {
-		ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
-			ast_codec_media_type2str(session_media->type),
+		ast_debug(4, "%s: Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+			ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
 			handler->id);
 		res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
 		if (res >= 0) {
-			ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
-				ast_codec_media_type2str(session_media->type),
+			ast_debug(4, "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+				ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
 				handler->id);
 			return 0;
 		}
@@ -951,15 +984,15 @@
 
 	handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
 	if (!handler_list) {
-		ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+		ast_debug(4, "%s: No registered SDP handlers for media type '%s'\n", ast_sip_session_get_name(session), media);
 		return -1;
 	}
 	AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
 		if (handler == session_media->handler) {
 			continue;
 		}
-		ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
-			ast_codec_media_type2str(session_media->type),
+		ast_debug(4, "%s: Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+			ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
 			handler->id);
 		res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
 		if (res < 0) {
@@ -967,8 +1000,8 @@
 			return -1;
 		}
 		if (res > 0) {
-			ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
-				ast_codec_media_type2str(session_media->type),
+			ast_debug(4, "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+				ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
 				handler->id);
 			/* Handled by this handler. Move to the next stream */
 			session_media_set_handler(session_media, handler);
@@ -977,8 +1010,8 @@
 	}
 
 	if (session_media->handler && session_media->handler->stream_stop) {
-		ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n",
-			ast_codec_media_type2str(session_media->type));
+		ast_debug(4, "%s: Stopping SDP media stream '%s' as it is not currently negotiated\n",
+			ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type));
 		session_media->handler->stream_stop(session_media);
 	}
 
@@ -1003,16 +1036,16 @@
 			active_media_state_clone =
 				ast_sip_session_media_state_clone(session->active_media_state);
 			if (!active_media_state_clone) {
-				ast_log(LOG_WARNING, "Unable to clone active media state for channel '%s'\n",
-					session->channel ? ast_channel_name(session->channel) : "unknown");
+				ast_log(LOG_WARNING, "%s: Unable to clone active media state\n",
+					ast_sip_session_get_name(session));
 				return -1;
 			}
 
 			ast_sip_session_media_state_free(session->pending_media_state);
 			session->pending_media_state = active_media_state_clone;
 		} else {
-			ast_log(LOG_WARNING, "No pending or active media state for channel '%s'\n",
-				session->channel ? ast_channel_name(session->channel) : "unknown");
+			ast_log(LOG_WARNING, "%s: No pending or active media state\n",
+				ast_sip_session_get_name(session));
 			return -1;
 		}
 	}
@@ -1024,8 +1057,8 @@
 	 */
 	if (ast_stream_topology_get_count(session->pending_media_state->topology) != local->media_count
 		|| AST_VECTOR_SIZE(&session->pending_media_state->sessions) != local->media_count) {
-		ast_log(LOG_WARNING, "Local SDP for channel '%s' contains %d media streams while we expected it to contain %u\n",
-			session->channel ? ast_channel_name(session->channel) : "unknown",
+		ast_log(LOG_WARNING, "%s: Local SDP contains %d media streams while we expected it to contain %u\n",
+			ast_sip_session_get_name(session),
 			ast_stream_topology_get_count(session->pending_media_state->topology), local->media_count);
 		return -1;
 	}
@@ -1269,7 +1302,10 @@
 	/*! Whether to generate new SDP */
 	int generate_new_sdp;
 	/*! Requested media state for the SDP */
-	struct ast_sip_session_media_state *media_state;
+	struct ast_sip_session_media_state *pending_media_state;
+	/*! Active media state at the time of the original request */
+	struct ast_sip_session_media_state *active_media_state;
+
 	AST_LIST_ENTRY(ast_sip_session_delayed_request) next;
 };
 
@@ -1279,7 +1315,8 @@
 	ast_sip_session_sdp_creation_cb on_sdp_creation,
 	ast_sip_session_response_cb on_response,
 	int generate_new_sdp,
-	struct ast_sip_session_media_state *media_state)
+	struct ast_sip_session_media_state *pending_media_state,
+	struct ast_sip_session_media_state *active_media_state)
 {
 	struct ast_sip_session_delayed_request *delay = ast_calloc(1, sizeof(*delay));
 
@@ -1291,13 +1328,15 @@
 	delay->on_sdp_creation = on_sdp_creation;
 	delay->on_response = on_response;
 	delay->generate_new_sdp = generate_new_sdp;
-	delay->media_state = media_state;
+	delay->pending_media_state = pending_media_state;
+	delay->active_media_state = active_media_state;
 	return delay;
 }
 
 static void delayed_request_free(struct ast_sip_session_delayed_request *delay)
 {
-	ast_sip_session_media_state_free(delay->media_state);
+	ast_sip_session_media_state_free(delay->pending_media_state);
+	ast_sip_session_media_state_free(delay->active_media_state);
 	ast_free(delay);
 }
 
@@ -1313,31 +1352,35 @@
 {
 	int res;
 
-	ast_debug(3, "Endpoint '%s(%s)' sending delayed %s request.\n",
-		ast_sorcery_object_get_id(session->endpoint),
-		session->channel ? ast_channel_name(session->channel) : "",
+	ast_debug(3, "%s: sending delayed %s request\n",
+		ast_sip_session_get_name(session),
 		delayed_method2str(delay->method));
 
 	switch (delay->method) {
 	case DELAYED_METHOD_INVITE:
 		res = sip_session_refresh(session, delay->on_request_creation,
 			delay->on_sdp_creation, delay->on_response,
-			AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->media_state, 1);
+			AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->pending_media_state,
+			delay->active_media_state, 1);
 		/* Ownership of media state transitions to ast_sip_session_refresh */
-		delay->media_state = NULL;
+		delay->pending_media_state = NULL;
+		delay->active_media_state = NULL;
 		return res;
 	case DELAYED_METHOD_UPDATE:
 		res = sip_session_refresh(session, delay->on_request_creation,
 			delay->on_sdp_creation, delay->on_response,
-			AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->media_state, 1);
+			AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->pending_media_state,
+			delay->active_media_state, 1);
 		/* Ownership of media state transitions to ast_sip_session_refresh */
-		delay->media_state = NULL;
+		delay->pending_media_state = NULL;
+		delay->active_media_state = NULL;
 		return res;
 	case DELAYED_METHOD_BYE:
 		ast_sip_session_terminate(session, 0);
 		return 0;
 	}
-	ast_log(LOG_WARNING, "Don't know how to send delayed %s(%d) request.\n",
+	ast_log(LOG_WARNING, "%s: Don't know how to send delayed %s(%d) request.\n",
+		ast_sip_session_get_name(session),
 		delayed_method2str(delay->method), delay->method);
 	return -1;
 }
@@ -1502,17 +1545,21 @@
 	ast_sip_session_response_cb on_response,
 	int generate_new_sdp,
 	enum delayed_method method,
-	struct ast_sip_session_media_state *media_state)
+	struct ast_sip_session_media_state *pending_media_state,
+	struct ast_sip_session_media_state *active_media_state,
+	int queue_head)
 {
 	struct ast_sip_session_delayed_request *delay = delayed_request_alloc(method,
-			on_request, on_sdp_creation, on_response, generate_new_sdp, media_state);
+			on_request, on_sdp_creation, on_response, generate_new_sdp, pending_media_state,
+			active_media_state);
 
 	if (!delay) {
-		ast_sip_session_media_state_free(media_state);
+		ast_sip_session_media_state_free(pending_media_state);
+		ast_sip_session_media_state_free(active_media_state);
 		return -1;
 	}
 
-	if (method == DELAYED_METHOD_BYE) {
+	if (method == DELAYED_METHOD_BYE || queue_head) {
 		/* Send BYE as early as possible */
 		AST_LIST_INSERT_HEAD(&session->delayed_requests, delay, next);
 	} else {
@@ -1597,7 +1644,8 @@
 	ast_channel_lock(session->channel);
 	pjsip_from_domain = pbx_builtin_getvar_helper(session->channel, "SIPFROMDOMAIN");
 	if (!ast_strlen_zero(pjsip_from_domain)) {
-		ast_debug(3, "From header domain reset by channel variable SIPFROMDOMAIN (%s)\n", pjsip_from_domain);
+		ast_debug(3, "%s: From header domain reset by channel variable SIPFROMDOMAIN (%s)\n",
+			ast_sip_session_get_name(session), pjsip_from_domain);
 		pj_strdup2(dlg_pool, &dlg_info_uri->host, pjsip_from_domain);
 	}
 	ast_channel_unlock(session->channel);
@@ -1629,56 +1677,571 @@
     }
 }
 
+/*
+ * Helper macros for merging and validating media states
+ */
+#define STREAM_REMOVED(_stream) (ast_stream_get_state(_stream) == AST_STREAM_STATE_REMOVED)
+#define STATE_REMOVED(_stream_state) (_stream_state == AST_STREAM_STATE_REMOVED)
+#define STATE_NONE(_stream_state) (_stream_state == AST_STREAM_STATE_END)
+#define GET_STREAM_SAFE(_topology, _i) (_i < ast_stream_topology_get_count(_topology) ? ast_stream_topology_get_stream(_topology, _i) : NULL)
+#define GET_STREAM_STATE_SAFE(_stream) (_stream ? ast_stream_get_state(_stream) : AST_STREAM_STATE_END)
+#define GET_STREAM_NAME_SAFE(_stream) (_stream ? ast_stream_get_name(_stream) : "")
+
+/* Thses will be moved to logger.h and refactored before final review */
+
+#ifdef AST_DEVMODE
+#define SCOPE_ENTER_DBG(__level, ...) \
+	SCOPE_ENTER(__level, " " __VA_ARGS__); \
+	ast_debug(__level, " " __VA_ARGS__);
+#else
+#define SCOPE_ENTER_DBG(__level, ...) \
+	int __scope_level = __level; \
+	ast_debug(__level, " " __VA_ARGS__);
+#endif
+
+#define SCOPE_EXIT_DBG(...) \
+({ \
+	ast_debug(__scope_level, " " __VA_ARGS__); \
+	SCOPE_EXIT(" " __VA_ARGS__); \
+})
+
+#define SCOPE_EXIT_DBG_RTN(__value, ...) \
+({ \
+	ast_debug(__scope_level, " " __VA_ARGS__); \
+	SCOPE_EXIT_RTN_VALUE(__value, " " __VA_ARGS__); \
+})
+
+#define SCOPE_EXIT_DBG_EXPR(__expr, ...) \
+({ \
+	ast_debug(__scope_level, " " __VA_ARGS__); \
+	SCOPE_EXIT_EXPR(__expr, " " __VA_ARGS__); \
+})
+
+#define SCOPE_EXIT_LOG_RTN(__value, _log_level, ...) \
+({ \
+	ast_log(_log_level, " " __VA_ARGS__); \
+	SCOPE_EXIT_RTN_VALUE(__value, " " __VA_ARGS__); \
+})
+
+#define SCOPE_EXIT_DBG_ASSERT_RTN(__value, ...) \
+({ \
+	ast_debug(__scope_level, " " __VA_ARGS__); \
+	ast_assert(0); \
+	SCOPE_EXIT_RTN_VALUE(__value, " " __VA_ARGS__); \
+})
+
+#define SCOPE_EXIT_LOG_ASSERT_RTN(__value, _log_level, ...) \
+({ \
+	ast_log(_log_level, " " __VA_ARGS__); \
+	ast_assert(0); \
+	SCOPE_EXIT_RTN_VALUE(__value, " " __VA_ARGS__); \
+})
+
+#define ast_trace_dbg(__level, ...) \
+({ \
+	ast_debug(__level < 0 ? __scope_level : __level, " " __VA_ARGS__); \
+	ast_trace(__level < 0 ? __scope_level : __level, " " __VA_ARGS__); \
+})
+
+/*!
+ * \internal
+ * \brief Validate a media state
+ *
+ * \param state Media state
+ *
+ * \retval 1 The media state is valid
+ * \retval 0 The media state is NOT valid
+ *
+ */
+static int is_media_state_valid(const char *session_name, struct ast_sip_session_media_state *state)
+{
+	int stream_count = ast_stream_topology_get_count(state->topology);
+	int session_count = AST_VECTOR_SIZE(&state->sessions);
+	int i;
+	SCOPE_ENTER_DBG(3, "%s: Topology: %s\n", session_name,
+		ast_str_tmp(256, ast_stream_topology_to_str(state->topology, &STR_TMP)));
+
+	if (session_count != stream_count) {
+		SCOPE_EXIT_DBG_ASSERT_RTN(0, "%s: %d media sessions but %d streams\n", session_name,
+			session_count, stream_count);
+	}
+
+	for (i = 0; i < stream_count; i++) {
+		struct ast_sip_session_media *media = NULL;
+		struct ast_stream *stream = NULL;
+		const char *stream_name = NULL;
+		int j;
+		SCOPE_ENTER_DBG(4, "%s: Checking stream %d\n", session_name, i);
+
+		stream = ast_stream_topology_get_stream(state->topology, i);
+		if (!stream) {
+			SCOPE_EXIT_DBG_ASSERT_RTN(0, "%s: stream %d is null\n", session_name, i);
+		}
+		stream_name = ast_stream_get_name(stream);
+
+		for (j = 0; j < stream_count; j++) {
+			struct ast_stream *possible_dup = ast_stream_topology_get_stream(state->topology, j);
+			if (j == i || !possible_dup) {
+				continue;
+			}
+			if (!STREAM_REMOVED(stream) && ast_strings_equal(stream_name, GET_STREAM_NAME_SAFE(possible_dup))) {
+				SCOPE_EXIT_DBG_ASSERT_RTN(0, "%s: stream %i %s is duplicated to %d\n", session_name,
+					i, stream_name, j);
+			}
+		}
+
+		media = AST_VECTOR_GET(&state->sessions, i);
+		if (!media) {
+			SCOPE_EXIT_DBG_EXPR(continue, "%s: media %d is null\n", session_name, i);
+		}
+
+		for (j = 0; j < session_count; j++) {
+			struct ast_sip_session_media *possible_dup = AST_VECTOR_GET(&state->sessions, j);
+			if (j == i || !possible_dup) {
+				continue;
+			}
+			if (!ast_strlen_zero(media->label) && !ast_strlen_zero(possible_dup->label)
+				&& ast_strings_equal(media->label, possible_dup->label)) {
+				SCOPE_EXIT_DBG_ASSERT_RTN(0, "%s: media %d %s is duplicated to %d\n", session_name,
+					i, media->label, j);
+			}
+		}
+
+		if (media->stream_num != i) {
+			SCOPE_EXIT_DBG_ASSERT_RTN(0, "%s: media %d has stream_num %d\n", session_name,
+				i, media->stream_num);
+		}
+
+		if (media->type != ast_stream_get_type(stream)) {
+			SCOPE_EXIT_DBG_ASSERT_RTN(0, "%s: media %d has type %s but stream has type %s\n", stream_name,
+				i, ast_codec_media_type2str(media->type), ast_codec_media_type2str(ast_stream_get_type(stream)));
+		}
+	}
+
+	SCOPE_EXIT_DBG_RTN(1, "%s: Valid\n", session_name);
+}
+
+/*!
+ * \internal
+ * \brief Merge media states for a delayed session refresh
+ *
+ * \param session_name For log messages
+ * \param delayed_pending_state The pending media state at the time the resuest was queued
+ * \param delayed_active_state The active media state  at the time the resuest was queued
+ * \param current_active_state The current active media state
+ * \param run_validation Whether to run validation on the resulting media state or not
+ *
+ * \returns New merged topology or NULL if there's an error
+ *
+ */
+static struct ast_sip_session_media_state *merge_refresh_media_states(
+	const char *session_name,
+	struct ast_sip_session_media_state *delayed_pending_state,
+	struct ast_sip_session_media_state *delayed_active_state,
+	struct ast_sip_session_media_state *current_active_state,
+	int run_post_validation)
+{
+	RAII_VAR(struct ast_sip_session_media_state *, new_pending_state, NULL, ast_sip_session_media_state_free);
+	struct ast_sip_session_media_state *returned_media_state = NULL;
+	struct ast_stream_topology *delayed_pending = delayed_pending_state->topology;
+	struct ast_stream_topology *delayed_active = delayed_active_state->topology;
+	struct ast_stream_topology *current_active = current_active_state->topology;
+	struct ast_stream_topology *new_pending = NULL;
+	int i;
+	int max_stream_count;
+	int res;
+	SCOPE_ENTER_DBG(2, "%s: DP: %s  DA: %s  CA: %s\n", session_name,
+		ast_str_tmp(256, ast_stream_topology_to_str(delayed_pending, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(delayed_active, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(current_active, &STR_TMP))
+	);
+
+	max_stream_count = MAX(ast_stream_topology_get_count(delayed_pending),
+		ast_stream_topology_get_count(delayed_active));
+	max_stream_count = MAX(max_stream_count, ast_stream_topology_get_count(current_active));
+
+	/*
+	 * The new_pending_state is always based on the currently negotiated state because
+	 * the stream ordering in its topology must be preserved.
+	 */
+	new_pending_state = ast_sip_session_media_state_clone(current_active_state);
+	if (!new_pending_state) {
+		SCOPE_EXIT_LOG_RTN(NULL, LOG_ERROR, "%s: Couldn't clone current_active_state to new_pending_state\n", session_name);
+	}
+	new_pending = new_pending_state->topology;
+
+	for (i = 0; i < max_stream_count; i++) {
+		struct ast_stream *dp_stream = GET_STREAM_SAFE(delayed_pending, i);
+		struct ast_stream *da_stream = GET_STREAM_SAFE(delayed_active, i);
+		struct ast_stream *ca_stream = GET_STREAM_SAFE(current_active, i);
+		struct ast_stream *np_stream = GET_STREAM_SAFE(new_pending, i);
+		struct ast_stream *found_da_stream = NULL;
+		struct ast_stream *found_np_stream = NULL;
+		enum ast_stream_state dp_state = GET_STREAM_STATE_SAFE(dp_stream);
+		enum ast_stream_state da_state = GET_STREAM_STATE_SAFE(da_stream);
+		enum ast_stream_state ca_state = GET_STREAM_STATE_SAFE(ca_stream);
+		enum ast_stream_state np_state = GET_STREAM_STATE_SAFE(np_stream);
+		enum ast_stream_state found_da_state = AST_STREAM_STATE_END;
+		enum ast_stream_state found_np_state = AST_STREAM_STATE_END;
+		const char *da_name = GET_STREAM_NAME_SAFE(da_stream);
+		const char *dp_name = GET_STREAM_NAME_SAFE(dp_stream);
+		const char *ca_name = GET_STREAM_NAME_SAFE(ca_stream);
+		const char *np_name = GET_STREAM_NAME_SAFE(np_stream);
+		const char *found_da_name __attribute__((unused)) = "";
+		const char *found_np_name __attribute__((unused)) = "";
+		int found_da_slot __attribute__((unused)) = -1;
+		int found_np_slot = -1;
+		int removed_np_slot = -1;
+		int j;
+		SCOPE_ENTER_DBG(3, "%s: slot: %d DP: %s  DA: %s  CA: %s\n", session_name, i,
+			ast_str_tmp(128, ast_stream_to_str(dp_stream, &STR_TMP)),
+			ast_str_tmp(128, ast_stream_to_str(da_stream, &STR_TMP)),
+			ast_str_tmp(128, ast_stream_to_str(ca_stream, &STR_TMP)));
+
+		if (STATE_NONE(da_state) && STATE_NONE(dp_state) && STATE_NONE(ca_state)) {
+			SCOPE_EXIT_DBG_EXPR(break, "%s: All gone\n", session_name);
+		}
+
+		/*
+		 * Simple cases are handled first to avoid having to search the NP and DA
+		 * topologies for streams with the same name but not in the same position.
+		 */
+
+		if (STATE_NONE(dp_state) && !STATE_NONE(da_state)) {
+		    /*
+		     * The slot in the delayed pending topology can't be empty if the delayed
+		     * active topology has a stream there.  Streams can't just go away.  They
+		     * can be reused or marked "removed" but they can't go away.
+		     */
+		    SCOPE_EXIT_LOG_RTN(NULL, LOG_WARNING, "%s: DP slot is empty but DA is not\n", session_name);
+		}
+
+		if (STATE_NONE(dp_state)) {
+		    /*
+		     * The current active topology can certainly have streams that weren't
+		     * in existence when the delayed request was queued.  In this case,
+		     * no action is needed since we already copied the current active topology
+		     * to the new pending one.
+		     */
+		    SCOPE_EXIT_DBG_EXPR(continue, "%s: No DP stream so use CA stream as is\n", session_name);
+		}
+
+		if (ast_strings_equal(dp_name, da_name) && ast_strings_equal(da_name, ca_name)) {
+			/*
+			 * The delayed pending stream in this slot matches by name, the streams
+			 * in the same slot in the other two topologies.  Easy case.
+			 */
+			ast_trace_dbg(-1, "%s: Same stream in all 3 states\n", session_name);
+			if (dp_state == da_state && da_state == ca_state) {
+				/* All the same state, no need to update. */
+				SCOPE_EXIT_DBG_EXPR(continue, "%s: All in the same state so nothing to do\n", session_name);
+			}
+			if (da_state != ca_state) {
+				/*
+				 * Something set the CA state between the time this request was queued
+				 * and now.  The CA state wins so we don't do anything.
+				 */
+				SCOPE_EXIT_DBG_EXPR(continue, "%s: Ignoring request to change state from %s to %s\n",
+					session_name, ast_stream_state2str(ca_state), ast_stream_state2str(dp_state));
+			}
+			if (dp_state != da_state) {
+				/* DP needs to update the state */
+				ast_stream_set_state(np_stream, dp_state);
+				SCOPE_EXIT_DBG_EXPR(continue, "%s: Changed NP stream state from %s to %s\n",
+					session_name, ast_stream_state2str(ca_state), ast_stream_state2str(dp_state));
+			}
+		}
+
+		/*
+		 * We're done with the simple cases.  For the rest, we need to identify if the
+		 * DP stream we're trying to take action on is already in the other topologies
+		 * possibly in a different slot.  To do that, if the stream in the DA or CA slots
+		 * doesn't match the current DP stream, we need to iterate over the topology
+		 * looking for a stream with the same name.
+		 */
+
+		/*
+		 * Since we already copied all of the CA streams to the NP topology, we'll use it
+		 * instead of CA because we'll be updating the NP as we go.
+		 */
+		if (!ast_strings_equal(dp_name, np_name)) {
+			/*
+			 * The NP stream in this slot doesn't have the same name as the DP stream
+			 * so we need to see if it's in another NP slot.  We're not going to stop
+			 * when we find a matching stream because we also want to find the first
+			 * removed removed slot, if any, so we can re-use this slot.  We'll break
+			 * early if we find both before we reach the end.
+			 */
+			ast_trace_dbg(-1, "%s: Checking if DP is already in NP somewhere\n", session_name);
+			for (j = 0; j < ast_stream_topology_get_count(new_pending); j++) {
+				struct ast_stream *possible_existing = ast_stream_topology_get_stream(new_pending, j);
+				const char *possible_existing_name = GET_STREAM_NAME_SAFE(possible_existing);
+
+				ast_trace_dbg(-1, "%s: Checking %s against %s\n", session_name, dp_name, possible_existing_name);
+				if (found_np_slot == -1 && ast_strings_equal(dp_name, possible_existing_name)) {
+					ast_trace_dbg(-1, "%s: Pending stream %s slot %d is in NP slot %d\n", session_name,
+					dp_name, i, j);
+					found_np_slot = j;
+					found_np_stream = possible_existing;
+					found_np_state = ast_stream_get_state(possible_existing);
+					found_np_name = ast_stream_get_name(possible_existing);
+				}
+				if (STREAM_REMOVED(possible_existing) && removed_np_slot == -1) {
+					removed_np_slot = j;
+				}
+				if (removed_np_slot >= 0 && found_np_slot >= 0) {
+					break;
+				}
+			}
+		} else {
+			/* Makes the subsequent code easier */
+			found_np_slot = i;
+			found_np_stream = np_stream;
+			found_np_state = np_state;
+			found_np_name = np_name;
+		}
+
+		if (!ast_strings_equal(dp_name, da_name)) {
+			/*
+			 * The DA stream in this slot doesn't have the same name as the DP stream
+			 * so we need to see if it's in another DA slot.  In real life, the DA stream
+			 * in this slot could have a different name but there shouldn't be a case
+			 * where the DP stream is another slot in the DA topology.  Just in case though.
+			 * We don't care about removed slots in the DA topology.
+			 */
+			ast_trace_dbg(-1, "%s: Checking if DP is already in DA somewhere\n", session_name);
+			for (j = 0; j < ast_stream_topology_get_count(delayed_active); j++) {
+				struct ast_stream *possible_existing = ast_stream_topology_get_stream(delayed_active, j);
+				const char *possible_existing_name = GET_STREAM_NAME_SAFE(possible_existing);
+
+				ast_trace_dbg(-1, "%s: Checking %s against %s\n", session_name, dp_name, possible_existing_name);
+				if (ast_strings_equal(dp_name, possible_existing_name)) {
+					ast_trace_dbg(-1, "%s: Pending stream %s slot %d is already in delayed active slot %d\n",
+						session_name, dp_name, i, j);
+					found_da_slot = j;
+					found_da_stream = possible_existing;
+					found_da_state = ast_stream_get_state(possible_existing);
+					found_da_name = ast_stream_get_name(possible_existing);
+					break;
+				}
+			}
+		} else {
+			/* Makes the subsequent code easier */
+			found_da_slot = i;
+			found_da_stream = da_stream;
+			found_da_state = da_state;
+			found_da_name = da_name;
+		}
+
+		ast_trace_dbg(-1, "%s: Found NP slot: %d  Found removed NP slot: %d Found DA slot: %d\n",
+			session_name, found_np_slot, removed_np_slot, found_da_slot);
+
+		/*
+		 * Now we know whether the DP stream is new or changing state and we know if the DP
+		 * stream exists in the other topologies and if so, where in those topologies it exists.
+		 */
+
+		if (!found_da_stream) {
+			/*
+			 * The DP stream isn't in the DA topology which would imply that the intention of the
+			 * request was to add the stream, not change its state.  It's possible though that
+			 * the stream was added by another request between the time this request was queued
+			 * and now so we need to check the CA topology as well.
+			 */
+			ast_trace_dbg(-1, "%s: There was no corresponding DA stream so the request was to add a stream\n", session_name);
+
+			if (found_np_stream) {
+				/*
+				 * We found it in the CA topology.  Since the intention was to add it
+				 * and it's already there, there's nothing to do.
+				 */
+				SCOPE_EXIT_DBG_EXPR(continue, "%s: New stream requested but it's already in CA\n", session_name);
+			} else {
+				/* OK, it's not in either which would again imply that the intention of the
+				 * request was to add the stream.
+				 */
+				ast_trace_dbg(-1, "%s: There was no corresponding NP stream\n", session_name);
+				if (STATE_REMOVED(dp_state)) {
+					/*
+					 * How can DP request to remove a stream that doesn't seem to exist anythere?
+					 * It's not.  It's possible that the stream was already removed and the slot
+					 * reused in the CA topology, but it would still have to exist in the DA
+					 * topology.  Bail.
+					 */
+					SCOPE_EXIT_LOG_RTN(NULL, LOG_ERROR,
+						"%s: Attempting to remove stream %d:%s but it doesn't exist anywhere.\n", session_name, i, dp_name);
+				} else {
+					/*
+					 * We're now sure we want to add the the stream.  Since we can re-use
+					 * slots in the CA topology that have streams marked as "removed", we
+					 * use the slot we saved in removed_np_slot if it exists.
+					 */
+					ast_trace_dbg(-1, "%s: Checking for open slot\n", session_name);
+					if (removed_np_slot >= 0) {
+						struct ast_sip_session_media *old_media = AST_VECTOR_GET(&new_pending_state->sessions, removed_np_slot);
+						res = ast_stream_topology_set_stream(new_pending, removed_np_slot, ast_stream_clone(dp_stream, NULL));
+						if (res != 0) {
+						    SCOPE_EXIT_LOG_RTN(NULL, LOG_WARNING, "%s: Couldn't set stream in new topology\n", session_name);
+						}
+						/*
+						 * Since we're reusing the removed_np_slot slot for something else, we need
+						 * to free and remove any session media already in it.
+						 * ast_stream_topology_set_stream() took care of freeing the old stream.
+						 */
+						res = AST_VECTOR_REPLACE(&new_pending_state->sessions, removed_np_slot, NULL);
+						if (res != 0) {
+						    SCOPE_EXIT_LOG_RTN(NULL, LOG_WARNING, "%s: Couldn't replace media session\n", session_name);
+						}
+
+						ao2_cleanup(old_media);
+						SCOPE_EXIT_DBG_EXPR(continue, "%s: Replaced removed stream in slot %d\n",
+							session_name, removed_np_slot);
+					} else {
+						int new_slot = ast_stream_topology_append_stream(new_pending, ast_stream_clone(dp_stream, NULL));
+						if (new_slot < 0) {
+						    SCOPE_EXIT_LOG_RTN(NULL, LOG_WARNING, "%s: Couldn't append stream in new topology\n", session_name);
+						}
+
+						res = AST_VECTOR_REPLACE(&new_pending_state->sessions, new_slot, NULL);
+						if (res != 0) {
+						    SCOPE_EXIT_LOG_RTN(NULL, LOG_WARNING, "%s: Couldn't replace media session\n", session_name);
+						}
+						SCOPE_EXIT_DBG_EXPR(continue, "%s: Appended new stream to slot %d\n",
+							session_name, new_slot);
+					}
+				}
+			}
+		} else {
+			/*
+			 * The DP stream exists in the DA topology so it's a change of some sort.
+			 */
+			ast_trace_dbg(-1, "%s: There was a corresponding DA stream so the request was to change/remove a stream\n", session_name);
+			if (dp_state == found_da_state) {
+				/* No change? Let's see if it's in CA */
+				if (!found_np_stream) {
+					/*
+					 * The DP and DA state are the same which would imply that the stream
+					 * already exists but it's not in the CA topology.  It's possible that
+					 * between the time this request was queued and now the stream was removed
+					 * from the CA topology and the slot used for something else.  Nothing
+					 * we can do here.
+					 */
+					SCOPE_EXIT_DBG_EXPR(continue, "%s: Stream doesn't exist in CA so nothing to do\n", session_name);
+				} else if (dp_state == found_np_state) {
+					SCOPE_EXIT_DBG_EXPR(continue, "%s: States are the same all around so nothing to do\n", session_name);
+				} else {
+					SCOPE_EXIT_DBG_EXPR(continue, "%s: Something changed the CA state so we're going to leave it as is\n", session_name);
+				}
+			} else {
+				/* We have a state change. */
+				ast_trace_dbg(-1, "%s: Requesting state change to %s\n", session_name, ast_stream_state2str(dp_state));
+				if (!found_np_stream) {
+					SCOPE_EXIT_DBG_EXPR(continue, "%s: Stream doesn't exist in CA so nothing to do\n", session_name);
+				} else if (da_state == found_np_state) {
+					ast_stream_set_state(found_np_stream, dp_state);
+					SCOPE_EXIT_DBG_EXPR(continue, "%s: Changed NP stream state from %s to %s\n",
+						session_name, ast_stream_state2str(found_np_state), ast_stream_state2str(dp_state));
+				} else {
+					SCOPE_EXIT_DBG_EXPR(continue, "%s: Something changed the CA state so we're going to leave it as is\n",
+						session_name);
+				}
+			}
+		}
+
+		SCOPE_EXIT_DBG("%s: Done with slot %d\n", session_name, i);
+	}
+
+	ast_trace_dbg(-1, "%s: Resetting default media states\n", session_name);
+	for (i = 0; i < AST_MEDIA_TYPE_END; i++) {
+		int j;
+		new_pending_state->default_session[i] = NULL;
+		for (j = 0; j < AST_VECTOR_SIZE(&new_pending_state->sessions); j++) {
+			struct ast_sip_session_media *media = AST_VECTOR_GET(&new_pending_state->sessions, j);
+			struct ast_stream *stream = ast_stream_topology_get_stream(new_pending_state->topology, j);
+
+			if (media && media->type == i && !STREAM_REMOVED(stream)) {
+				new_pending_state->default_session[i] = media;
+				break;
+			}
+		}
+	}
+
+	if (run_post_validation) {
+		ast_trace_dbg(-1, "%s: Running post-validation\n", session_name);
+		if (!is_media_state_valid(session_name, new_pending_state)) {
+			SCOPE_EXIT_LOG_RTN(NULL, LOG_ERROR, "State not consistent\n");
+		}
+	}
+
+	/*
+	 * We need to move the new pending state to another variable and set new_pending_state to NULL
+	 * so RAII_VAR doesn't free it.
+	 */
+	returned_media_state = new_pending_state;
+	new_pending_state = NULL;
+	SCOPE_EXIT_DBG_RTN(returned_media_state, "%s: NP: %s\n", session_name,
+		ast_str_tmp(256, ast_stream_topology_to_str(new_pending, &STR_TMP)));
+}
+
 static int sip_session_refresh(struct ast_sip_session *session,
 		ast_sip_session_request_creation_cb on_request_creation,
 		ast_sip_session_sdp_creation_cb on_sdp_creation,
 		ast_sip_session_response_cb on_response,
 		enum ast_sip_session_refresh_method method, int generate_new_sdp,
-		struct ast_sip_session_media_state *media_state,
+		struct ast_sip_session_media_state *pending_media_state,
+		struct ast_sip_session_media_state *active_media_state,
 		int queued)
 {
 	pjsip_inv_session *inv_session = session->inv_session;
 	pjmedia_sdp_session *new_sdp = NULL;
 	pjsip_tx_data *tdata;
 
-	if (media_state && (!media_state->topology || !generate_new_sdp)) {
-		ast_sip_session_media_state_free(media_state);
+	if (pending_media_state && (!pending_media_state->topology || !generate_new_sdp)) {
+
+		ast_debug(3, "%s: Not sending reinvite because %s%s\n", ast_sip_session_get_name(session),
+			pending_media_state->topology == NULL ? "pending topology is null " : "",
+				!generate_new_sdp ? "generate_new_sdp is false" : "");
+		ast_sip_session_media_state_free(pending_media_state);
+		ast_sip_session_media_state_free(active_media_state);
 		return -1;
 	}
 
 	if (inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
 		/* Don't try to do anything with a hung-up call */
-		ast_debug(3, "Not sending reinvite to %s because of disconnected state...\n",
-				ast_sorcery_object_get_id(session->endpoint));
-		ast_sip_session_media_state_free(media_state);
+		ast_debug(3, "%s: Not sending reinvite because of disconnected state\n",
+				ast_sip_session_get_name(session));
+		ast_sip_session_media_state_free(pending_media_state);
+		ast_sip_session_media_state_free(active_media_state);
 		return 0;
 	}
 
 	/* If the dialog has not yet been established we have to defer until it has */
 	if (inv_session->dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) {
-		ast_debug(3, "Delay sending request to %s because dialog has not been established...\n",
-			ast_sorcery_object_get_id(session->endpoint));
+		ast_debug(3, "%s: Delay sending reinvite because dialog has not been established\n",
+			ast_sip_session_get_name(session));
 		return delay_request(session, on_request_creation, on_sdp_creation, on_response,
 			generate_new_sdp,
 			method == AST_SIP_SESSION_REFRESH_METHOD_INVITE
 				? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE,
-			media_state);
+			pending_media_state, active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued);
 	}
 
 	if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
 		if (inv_session->invite_tsx) {
 			/* We can't send a reinvite yet, so delay it */
-			ast_debug(3, "Delay sending reinvite to %s because of outstanding transaction...\n",
-					ast_sorcery_object_get_id(session->endpoint));
+			ast_debug(3, "%s: Delay sending reinvite because of outstanding transaction\n",
+				ast_sip_session_get_name(session));
 			return delay_request(session, on_request_creation, on_sdp_creation,
-				on_response, generate_new_sdp, DELAYED_METHOD_INVITE, media_state);
+				on_response, generate_new_sdp, DELAYED_METHOD_INVITE, pending_media_state,
+				active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued);
 		} else if (inv_session->state != PJSIP_INV_STATE_CONFIRMED) {
 			/* Initial INVITE transaction failed to progress us to a confirmed state
 			 * which means re-invites are not possible
 			 */
-			ast_debug(3, "Not sending reinvite to %s because not in confirmed state...\n",
-					ast_sorcery_object_get_id(session->endpoint));
-			ast_sip_session_media_state_free(media_state);
+			ast_debug(3, "%s: Not sending reinvite because not in confirmed state\n",
+				ast_sip_session_get_name(session));
+			ast_sip_session_media_state_free(pending_media_state);
+			ast_sip_session_media_state_free(active_media_state);
 			return 0;
 		}
 	}
@@ -1688,32 +2251,59 @@
 		if (inv_session->neg
 			&& pjmedia_sdp_neg_get_state(inv_session->neg)
 				!= PJMEDIA_SDP_NEG_STATE_DONE) {
-			ast_debug(3, "Delay session refresh with new SDP to %s because SDP negotiation is not yet done...\n",
-				ast_sorcery_object_get_id(session->endpoint));
+			ast_debug(3, "%s: Delay session refresh with new SDP because SDP negotiation is not yet done\n",
+				ast_sip_session_get_name(session));
 			return delay_request(session, on_request_creation, on_sdp_creation,
 				on_response, generate_new_sdp,
 				method == AST_SIP_SESSION_REFRESH_METHOD_INVITE
-					? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, media_state);
+					? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, pending_media_state,
+				active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued);
 		}
 
 		/* If an explicitly requested media state has been provided use it instead of any pending one */
-		if (media_state) {
+		if (pending_media_state) {
 			int index;
 			int type_streams[AST_MEDIA_TYPE_END] = {0};
 			struct ast_stream *stream;
 
+			ast_debug(3, "%s: Pending media state exists\n", ast_sip_session_get_name(session));
+
 			/* Media state conveys a desired media state, so if there are outstanding
 			 * delayed requests we need to ensure we go into the queue and not jump
 			 * ahead. If we sent this media state now then updates could go out of
 			 * order.
 			 */
 			if (!queued && !AST_LIST_EMPTY(&session->delayed_requests)) {
-				ast_debug(3, "Delay sending reinvite to %s because of outstanding requests...\n",
-					ast_sorcery_object_get_id(session->endpoint));
+				ast_debug(3, "%s: Delay sending reinvite because of outstanding requests\n",
+					ast_sip_session_get_name(session));
 				return delay_request(session, on_request_creation, on_sdp_creation,
 					on_response, generate_new_sdp,
 					method == AST_SIP_SESSION_REFRESH_METHOD_INVITE
-						? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, media_state);
+						? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, pending_media_state,
+						active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued);
+			}
+
+			if (active_media_state) {
+				struct ast_sip_session_media_state *new_pending_state;
+
+				ast_debug(3, "%s: Active media state exists\n", ast_sip_session_get_name(session));
+				ast_debug(3, "%s: DP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(pending_media_state->topology, &STR_TMP)));
+				ast_debug(3, "%s: DA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(active_media_state->topology, &STR_TMP)));
+				ast_debug(3, "%s: CP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)));
+				ast_debug(3, "%s: CA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
+
+				new_pending_state = merge_refresh_media_states(ast_sip_session_get_name(session),
+					pending_media_state, active_media_state, session->active_media_state, 1);
+				if (new_pending_state) {
+					ast_debug(3, "%s: NP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(new_pending_state->topology, &STR_TMP)));
+					ast_sip_session_media_state_free(pending_media_state);
+					pending_media_state = new_pending_state;
+				} else {
+					ast_sip_session_media_state_reset(pending_media_state);
+					ast_sip_session_media_state_free(active_media_state);
+					ast_log(LOG_WARNING, "%s: Unable to merge media states\n", ast_sip_session_get_name(session));
+					return -1;
+				}
 			}
 
 			/* Prune the media state so the number of streams fit within the configured limits - we do it here
@@ -1721,10 +2311,10 @@
 			 * of the SDP when producing it we'd be in trouble. We also enforce formats here for media types that
 			 * are configurable on the endpoint.
 			 */
-			for (index = 0; index < ast_stream_topology_get_count(media_state->topology); ++index) {
+			for (index = 0; index < ast_stream_topology_get_count(pending_media_state->topology); ++index) {
 				struct ast_stream *existing_stream = NULL;
 
-				stream = ast_stream_topology_get_stream(media_state->topology, index);
+				stream = ast_stream_topology_get_stream(pending_media_state->topology, index);
 
 				if (session->active_media_state->topology &&
 					index < ast_stream_topology_get_count(session->active_media_state->topology)) {
@@ -1732,14 +2322,15 @@
 				}
 
 				if (is_stream_limitation_reached(ast_stream_get_type(stream), session->endpoint, type_streams)) {
-					if (index < AST_VECTOR_SIZE(&media_state->sessions)) {
-						struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index);
+					if (index < AST_VECTOR_SIZE(&pending_media_state->sessions)) {
+						struct ast_sip_session_media *session_media = AST_VECTOR_GET(&pending_media_state->sessions, index);
 
 						ao2_cleanup(session_media);
-						AST_VECTOR_REMOVE(&media_state->sessions, index, 1);
+						AST_VECTOR_REMOVE(&pending_media_state->sessions, index, 1);
 					}
 
-					ast_stream_topology_del_stream(media_state->topology, index);
+					ast_stream_topology_del_stream(pending_media_state->topology, index);
+					ast_debug(3, "%s: Droped overlimit stream %d:%s\n", ast_sip_session_get_name(session), index, ast_stream_get_name(stream));
 
 					/* A stream has potentially moved into our spot so we need to jump back so we process it */
 					index -= 1;
@@ -1750,7 +2341,9 @@
 				if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
 					/* If there is no existing stream we can just not have this stream in the topology at all. */
 					if (!existing_stream) {
-						ast_stream_topology_del_stream(media_state->topology, index);
+						ast_debug(3, "%s: Dropped removed stream %d:%s\n", ast_sip_session_get_name(session), index, ast_stream_get_name(stream));
+						ast_stream_topology_del_stream(pending_media_state->topology, index);
+						/* TODO: Do we need to remove the corresponding media state? */
 						index -= 1;
 					}
 					continue;
@@ -1762,7 +2355,8 @@
 
 					joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
 					if (!joint_cap) {
-						ast_sip_session_media_state_free(media_state);
+						ast_sip_session_media_state_free(pending_media_state);
+						ast_sip_session_media_state_free(active_media_state);
 						return 0;
 					}
 					ast_format_cap_get_compatible(ast_stream_get_formats(stream), session->endpoint->media.codecs, joint_cap);
@@ -1773,7 +2367,8 @@
 							/* If there is no existing stream we can just not have this stream in the topology
 							 * at all.
 							 */
-							ast_stream_topology_del_stream(media_state->topology, index);
+							ast_stream_topology_del_stream(pending_media_state->topology, index);
+							ast_debug(3, "%s: Dropped incompatible stream %d:%s\n", ast_sip_session_get_name(session), index, ast_stream_get_name(stream));
 							index -= 1;
 							continue;
 						} else if (ast_stream_get_state(stream) != ast_stream_get_state(existing_stream) ||
@@ -1783,6 +2378,7 @@
 							 * is preserved.
 							 */
 							ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+							ast_debug(3, "%s: Removed incompatible stream %d:%s\n", ast_sip_session_get_name(session), index, ast_stream_get_name(stream));
 							continue;
 						} else {
 							/* However if the stream is otherwise remaining the same we can keep the formats
@@ -1796,6 +2392,7 @@
 				}
 
 				++type_streams[ast_stream_get_type(stream)];
+
 			}
 
 			if (session->active_media_state->topology) {
@@ -1804,30 +2401,40 @@
 				 * streams than are currently present we fill in the topology to match the current number of streams
 				 * that are active.
 				 */
-				for (index = ast_stream_topology_get_count(media_state->topology);
+
+				for (index = ast_stream_topology_get_count(pending_media_state->topology);
 					index < ast_stream_topology_get_count(session->active_media_state->topology); ++index) {
 					struct ast_stream *cloned;
+					int position;
 
 					stream = ast_stream_topology_get_stream(session->active_media_state->topology, index);
 					ast_assert(stream != NULL);
 
 					cloned = ast_stream_clone(stream, NULL);
 					if (!cloned) {
-						ast_sip_session_media_state_free(media_state);
+						ast_sip_session_media_state_free(pending_media_state);
+						ast_sip_session_media_state_free(active_media_state);
 						return -1;
 					}
 
 					ast_stream_set_state(cloned, AST_STREAM_STATE_REMOVED);
-					if (ast_stream_topology_append_stream(media_state->topology, cloned) < 0) {
+					position = ast_stream_topology_append_stream(pending_media_state->topology, cloned);
+					if (position < 0) {
 						ast_stream_free(cloned);
-						ast_sip_session_media_state_free(media_state);
+						ast_sip_session_media_state_free(pending_media_state);
+						ast_sip_session_media_state_free(active_media_state);
 						return -1;
 					}
+					ast_debug(3, "%s: Appended empty stream in position %d to make counts match\n", ast_sip_session_get_name(session), position);
 				}
 
 				/* If the resulting media state matches the existing active state don't bother doing a session refresh */
-				if (ast_stream_topology_equal(session->active_media_state->topology, media_state->topology)) {
-					ast_sip_session_media_state_free(media_state);
+				if (ast_stream_topology_equal(session->active_media_state->topology, pending_media_state->topology)) {
+					ast_debug(3, "%s: CA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
+					ast_debug(3, "%s: NP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(pending_media_state->topology, &STR_TMP)));
+					ast_debug(3, "%s: Topologies are equal. Not sending re-invite\n", ast_sip_session_get_name(session));
+					ast_sip_session_media_state_free(pending_media_state);
+					ast_sip_session_media_state_free(active_media_state);
 					/* For external consumers we return 0 to say success, but internally for
 					 * send_delayed_request we return a separate value to indicate that this
 					 * session refresh would be redundant so we didn't send it
@@ -1837,18 +2444,22 @@
 			}
 
 			ast_sip_session_media_state_free(session->pending_media_state);
-			session->pending_media_state = media_state;
+			session->pending_media_state = pending_media_state;
 		}
 
 		new_sdp = generate_session_refresh_sdp(session);
 		if (!new_sdp) {
-			ast_log(LOG_ERROR, "Failed to generate session refresh SDP. Not sending session refresh\n");
+			ast_log(LOG_WARNING, "%s: Failed to generate session refresh SDP. Not sending session refresh\n",
+				ast_sip_session_get_name(session));
 			ast_sip_session_media_state_reset(session->pending_media_state);
+			ast_sip_session_media_state_free(active_media_state);
 			return -1;
 		}
 		if (on_sdp_creation) {
 			if (on_sdp_creation(session, new_sdp)) {
+				ast_log(LOG_WARNING, "%s: on_sdp_creation failed\n", ast_sip_session_get_name(session));
 				ast_sip_session_media_state_reset(session->pending_media_state);
+				ast_sip_session_media_state_free(active_media_state);
 				return -1;
 			}
 		}
@@ -1856,31 +2467,35 @@
 
 	if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
 		if (pjsip_inv_reinvite(inv_session, NULL, new_sdp, &tdata)) {
-			ast_log(LOG_WARNING, "Failed to create reinvite properly.\n");
+			ast_log(LOG_WARNING, "%s: Failed to create reinvite properly\n", ast_sip_session_get_name(session));
 			if (generate_new_sdp) {
 				ast_sip_session_media_state_reset(session->pending_media_state);
 			}
+			ast_sip_session_media_state_free(active_media_state);
 			return -1;
 		}
 	} else if (pjsip_inv_update(inv_session, NULL, new_sdp, &tdata)) {
-		ast_log(LOG_WARNING, "Failed to create UPDATE properly.\n");
+		ast_log(LOG_WARNING, "%s: Failed to create UPDATE properly\n", ast_sip_session_get_name(session));
 		if (generate_new_sdp) {
 			ast_sip_session_media_state_reset(session->pending_media_state);
 		}
+		ast_sip_session_media_state_free(active_media_state);
 		return -1;
 	}
 	if (on_request_creation) {
 		if (on_request_creation(session, tdata)) {
+			ast_log(LOG_WARNING, "%s: on_request_creation failed.\n", ast_sip_session_get_name(session));
 			if (generate_new_sdp) {
 				ast_sip_session_media_state_reset(session->pending_media_state);
 			}
+			ast_sip_session_media_state_free(active_media_state);
 			return -1;
 		}
 	}
-	ast_debug(3, "Sending session refresh SDP via %s to %s\n",
-		method == AST_SIP_SESSION_REFRESH_METHOD_INVITE ? "re-INVITE" : "UPDATE",
-		ast_sorcery_object_get_id(session->endpoint));
+	ast_debug(3, "%s: Sending session refresh SDP via %s\n", ast_sip_session_get_name(session),
+		method == AST_SIP_SESSION_REFRESH_METHOD_INVITE ? "re-INVITE" : "UPDATE");
 	ast_sip_session_send_request_with_cb(session, tdata, on_response);
+	ast_sip_session_media_state_free(active_media_state);
 	return 0;
 }
 
@@ -1892,7 +2507,7 @@
 		struct ast_sip_session_media_state *media_state)
 {
 	return sip_session_refresh(session, on_request_creation, on_sdp_creation,
-		on_response, method, generate_new_sdp, media_state, 0);
+		on_response, method, generate_new_sdp, media_state, NULL, 0);
 }
 
 int ast_sip_session_regenerate_answer(struct ast_sip_session *session,
@@ -2054,7 +2669,7 @@
 
 		handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
 		if (!handler_list) {
-			ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+			ast_debug(3, "%s: No registered SDP handlers for media type '%s'\n", ast_sip_session_get_name(session), media);
 			continue;
 		}
 		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
@@ -2102,6 +2717,11 @@
 		return PJ_FALSE;
 	}
 
+	if (session->inv_session->invite_tsx) {
+		/* There's a transaction in progress so bail now and let pjproject send 491 */
+		return PJ_FALSE;
+	}
+
 	if (session->deferred_reinvite) {
 		pj_str_t key, deferred_key;
 		pjsip_tx_data *tdata;
@@ -2307,7 +2927,7 @@
 	const char *endpoint_name = session->endpoint ?
 		ast_strdupa(ast_sorcery_object_get_id(session->endpoint)) : "<none>";
 
-	ast_debug(3, "Destroying SIP session with endpoint %s\n", endpoint_name);
+	ast_debug(3, "%s: Destroying SIP session\n", ast_sip_session_get_name(session));
 
 	ast_test_suite_event_notify("SESSION_DESTROYING",
 		"Endpoint: %s\r\n"
@@ -2624,18 +3244,19 @@
 	}
 
 	inv = pjsip_dlg_get_inv_session(dlg);
+	session = inv->mod_data[session_module.id];
+
 	if (PJSIP_INV_STATE_CONFIRMED <= inv->state) {
 		/*
 		 * We cannot handle reINVITE authentication at this
 		 * time because the reINVITE transaction is still in
 		 * progress.
 		 */
-		ast_debug(1, "A reINVITE is being challenged.\n");
+		ast_debug(3, "%s: A reINVITE is being challenged\n", ast_sip_session_get_name(session));
 		return PJ_FALSE;
 	}
-	ast_debug(1, "Initial INVITE is being challenged.\n");
+	ast_debug(3, "%s: Initial INVITE is being challenged.\n", ast_sip_session_get_name(session));
 
-	session = inv->mod_data[session_module.id];
 
 	if (ast_sip_create_request_with_auth(&session->endpoint->outbound_auths, rdata,
 		tsx->last_tx, &tdata)) {
@@ -2886,12 +3507,12 @@
 		break;
 	case PJSIP_INV_STATE_CONFIRMED:
 		if (session->inv_session->invite_tsx) {
-			ast_debug(3, "Delay sending BYE to %s because of outstanding transaction...\n",
-					ast_sorcery_object_get_id(session->endpoint));
+			ast_debug(3, "%s: Delay sending BYE because of outstanding transaction...\n",
+				ast_sip_session_get_name(session));
 			/* If this is delayed the only thing that will happen is a BYE request so we don't
 			 * actually need to store the response code for when it happens.
 			 */
-			delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL);
+			delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL, NULL, 1);
 			break;
 		}
 		/* Fall through */
@@ -3009,7 +3630,7 @@
 
 	if (session->ended_while_deferred) {
 		/* Complete the session end started by the remote hangup. */
-		ast_debug(3, "Ending session (%p) after being deferred\n", session);
+		ast_debug(3, "%s: Ending session after being deferred\n", ast_sip_session_get_name(session));
 		session->ended_while_deferred = 0;
 		session_end(session);
 	}
@@ -3073,7 +3694,8 @@
 
 	pickup_cfg = ast_get_chan_features_pickup_config(session->channel);
 	if (!pickup_cfg) {
-		ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+		ast_log(LOG_ERROR, "%s: Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n",
+			ast_sip_session_get_name(session));
 		pickupexten = "";
 	} else {
 		pickupexten = ast_strdupa(pickup_cfg->pickupexten);
@@ -3175,7 +3797,8 @@
 	 */
 
 	if (invite->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
-		ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n",
+		ast_log(LOG_ERROR, "%s: Session already DISCONNECTED [reason=%d (%s)]\n",
+			ast_sip_session_get_name(invite->session),
 			invite->session->inv_session->cause,
 			pjsip_get_status_text(invite->session->inv_session->cause)->ptr);
 #ifdef HAVE_PJSIP_INV_SESSION_REF
@@ -3196,8 +3819,8 @@
 		}
 		goto end;
 	case SIP_GET_DEST_EXTEN_PARTIAL:
-		ast_debug(1, "Call from '%s' (%s:%s) to extension '%s' - partial match\n",
-			ast_sorcery_object_get_id(invite->session->endpoint),
+		ast_debug(3, "%s: Call (%s:%s) to extension '%s' - partial match\n",
+			ast_sip_session_get_name(invite->session),
 			invite->rdata->tp_info.transport->type_name,
 			pj_sockaddr_print(&invite->rdata->pkt_info.src_addr, buffer, sizeof(buffer), 3),
 			invite->session->exten);
@@ -3210,8 +3833,8 @@
 		goto end;
 	case SIP_GET_DEST_EXTEN_NOT_FOUND:
 	default:
-		ast_log(LOG_NOTICE, "Call from '%s' (%s:%s) to extension '%s' rejected because extension not found in context '%s'.\n",
-			ast_sorcery_object_get_id(invite->session->endpoint),
+		ast_log(LOG_NOTICE, "%s: Call (%s:%s) to extension '%s' rejected because extension not found in context '%s'.\n",
+			ast_sip_session_get_name(invite->session),
 			invite->rdata->tp_info.transport->type_name,
 			pj_sockaddr_print(&invite->rdata->pkt_info.src_addr, buffer, sizeof(buffer), 3),
 			invite->session->exten,
@@ -3418,9 +4041,8 @@
 {
 	struct ast_sip_session *session = entry->user_data;
 
-	ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision timer expired.\n",
-		ast_sorcery_object_get_id(session->endpoint),
-		session->channel ? ast_channel_name(session->channel) : "");
+	ast_debug(3, "%s: re-INVITE collision timer expired.\n",
+		ast_sip_session_get_name(session));
 
 	if (AST_LIST_EMPTY(&session->delayed_requests)) {
 		/* No delayed request pending, so just return */
@@ -3440,18 +4062,36 @@
 {
 	pjsip_inv_session *inv = session->inv_session;
 	pj_time_val tv;
+	struct ast_sip_session_media_state *pending_media_state;
+	struct ast_sip_session_media_state *active_media_state;
+	const char *session_name = ast_sip_session_get_name(session);
 
-	ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision.\n",
-		ast_sorcery_object_get_id(session->endpoint),
-		session->channel ? ast_channel_name(session->channel) : "");
-	if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, NULL)) {
+	ast_debug(3, "%s re-INVITE collision.\n", session_name);
+
+	pending_media_state = ast_sip_session_media_state_clone(session->pending_media_state);
+	if (!pending_media_state) {
+		ast_log(LOG_ERROR, "%s: Failed to clone pending media state\n", session_name);
 		return;
 	}
+
+	active_media_state = ast_sip_session_media_state_clone(session->active_media_state);
+	if (!active_media_state) {
+		ast_sip_session_media_state_free(pending_media_state);
+		ast_log(LOG_ERROR, "%s: Failed to clone active media state\n", session_name);
+		return;
+	}
+
+	if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, pending_media_state,
+		active_media_state, 1)) {
+		ast_sip_session_media_state_free(pending_media_state);
+		ast_sip_session_media_state_free(active_media_state);
+		ast_log(LOG_ERROR, "%s: Failed to add delayed request\n", session_name);
+		return;
+	}
+
 	if (pj_timer_entry_running(&session->rescheduled_reinvite)) {
 		/* Timer already running.  Something weird is going on. */
-		ast_debug(1, "Endpoint '%s(%s)' re-INVITE collision while timer running!!!\n",
-			ast_sorcery_object_get_id(session->endpoint),
-			session->channel ? ast_channel_name(session->channel) : "");
+		ast_log(LOG_ERROR, "%s: re-INVITE collision while timer running!!!\n", session_name);
 		return;
 	}
 
@@ -3467,6 +4107,7 @@
 	if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(),
 		&session->rescheduled_reinvite, &tv) != PJ_SUCCESS) {
 		ao2_ref(session, -1);
+		ast_log(LOG_ERROR, "%s: Couldn't schedule timer\n", session_name);
 	}
 }
 
@@ -3576,8 +4217,8 @@
 	struct ast_sip_session_supplement *supplement;
 	struct pjsip_status_line status = rdata->msg_info.msg->line.status;
 
-	ast_debug(3, "Response is %d %.*s\n", status.code, (int) pj_strlen(&status.reason),
-			pj_strbuf(&status.reason));
+	ast_debug(3, "%s: Response is %d %.*s\n", ast_sip_session_get_name(session),
+		status.code, (int) pj_strlen(&status.reason), pj_strbuf(&status.reason));
 
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
 		if (!(supplement->response_priority & response_priority)) {
@@ -3592,7 +4233,7 @@
 static int handle_incoming(struct ast_sip_session *session, pjsip_rx_data *rdata,
 		enum ast_sip_session_response_priority response_priority)
 {
-	ast_debug(3, "Received %s\n", rdata->msg_info.msg->type == PJSIP_REQUEST_MSG ?
+	ast_debug(3, "%s: Received %s\n", ast_sip_session_get_name(session), rdata->msg_info.msg->type == PJSIP_REQUEST_MSG ?
 			"request" : "response");
 
 	if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) {
@@ -3609,7 +4250,8 @@
 	struct ast_sip_session_supplement *supplement;
 	struct pjsip_request_line req = tdata->msg->line.req;
 
-	ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
+	ast_debug(3, "%s: Method is %.*s\n", ast_sip_session_get_name(session),
+		(int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
 		if (supplement->outgoing_request && does_method_match(&req.method.name, supplement->method)) {
 			supplement->outgoing_request(session, tdata);
@@ -3624,11 +4266,13 @@
 	pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
 
 	if (!cseq) {
-		ast_log(LOG_ERROR, "Cannot send response due to missing sequence header");
+		ast_log(LOG_ERROR, "%s: Cannot send response due to missing sequence header",
+			ast_sip_session_get_name(session));
 		return;
 	}
 
-	ast_debug(3, "Method is %.*s, Response is %d %.*s\n", (int) pj_strlen(&cseq->method.name),
+	ast_debug(3, "%s: Method is %.*s, Response is %d %.*s\n", ast_sip_session_get_name(session),
+		(int) pj_strlen(&cseq->method.name),
 		pj_strbuf(&cseq->method.name), status.code, (int) pj_strlen(&status.reason),
 		pj_strbuf(&status.reason));
 
@@ -3716,9 +4360,8 @@
 		 * SDP answer.
 		 */
 		ast_debug(1,
-			"Endpoint '%s(%s)': Ending session due to incomplete SDP negotiation.  %s\n",
-			ast_sorcery_object_get_id(session->endpoint),
-			session->channel ? ast_channel_name(session->channel) : "",
+			"%s: Ending session due to incomplete SDP negotiation.  %s\n",
+			ast_sip_session_get_name(session),
 			pjsip_rx_data_get_info(rdata));
 		if (pjsip_inv_end_session(inv, 400, NULL, &tdata) == PJ_SUCCESS
 			&& tdata) {
@@ -3755,7 +4398,8 @@
 		handle_incoming_before_media(inv, session, e->body.rx_msg.rdata);
 		break;
 	case PJSIP_EVENT_TSX_STATE:
-		ast_debug(3, "Source of transaction state change is %s\n", pjsip_event_str(e->body.tsx_state.type));
+		ast_debug(3, "%s: Source of transaction state change is %s\n", ast_sip_session_get_name(session),
+			pjsip_event_str(e->body.tsx_state.type));
 		/* Transaction state changes are prompted by some other underlying event. */
 		switch(e->body.tsx_state.type) {
 		case PJSIP_EVENT_TX_MSG:
@@ -3791,7 +4435,7 @@
 
 	if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
 		if (session->defer_end) {
-			ast_debug(3, "Deferring session (%p) end\n", session);
+			ast_debug(3, "%s: Deferring session end\n", ast_sip_session_get_name(session));
 			session->ended_while_deferred = 1;
 			return;
 		}
@@ -3897,7 +4541,8 @@
 						return;
 					}
 					if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
-						ast_debug(1, "reINVITE received final response code %d\n",
+						ast_debug(1, "%s: reINVITE received final response code %d\n",
+							ast_sip_session_get_name(session),
 							tsx->status_code);
 						if ((tsx->status_code == 401 || tsx->status_code == 407)
 							&& !ast_sip_create_request_with_auth(
@@ -3950,15 +4595,13 @@
 							pjsip_rx_data_get_info(e->body.tsx_state.src.rdata),
 							sdp_negotiation_done ? "complete" : "incomplete");
 						if (!sdp_negotiation_done) {
-							ast_debug(1, "Endpoint '%s(%s)': Incomplete SDP negotiation cancelled session.  %s\n",
-								ast_sorcery_object_get_id(session->endpoint),
-								session->channel ? ast_channel_name(session->channel) : "",
+							ast_debug(1, "%s: Incomplete SDP negotiation cancelled session.  %s\n",
+								ast_sip_session_get_name(session),
 								pjsip_rx_data_get_info(e->body.tsx_state.src.rdata));
 						} else if (pjsip_inv_end_session(inv, 500, NULL, &tdata) == PJ_SUCCESS
 							&& tdata) {
-							ast_debug(1, "Endpoint '%s(%s)': Ending session due to RFC5407 race condition.  %s\n",
-								ast_sorcery_object_get_id(session->endpoint),
-								session->channel ? ast_channel_name(session->channel) : "",
+							ast_debug(1, "%s: Ending session due to RFC5407 race condition.  %s\n",
+								ast_sip_session_get_name(session),
 								pjsip_rx_data_get_info(e->body.tsx_state.src.rdata));
 							ast_sip_session_send_request(session, tdata);
 						}
@@ -3970,7 +4613,8 @@
 			if (tsx->role == PJSIP_ROLE_UAC) {
 				if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
 					/* This means we got a final response to our outgoing method */
-					ast_debug(1, "%.*s received final response code %d\n",
+					ast_debug(1, "%s: %.*s received final response code %d\n",
+						ast_sip_session_get_name(session),
 						(int) pj_strlen(&tsx->method.name), pj_strbuf(&tsx->method.name),
 						tsx->status_code);
 					if ((tsx->status_code == 401 || tsx->status_code == 407)
@@ -4012,9 +4656,8 @@
 
 	if (tsx->method.id == PJSIP_INVITE_METHOD) {
 		if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) {
-			ast_debug(3, "Endpoint '%s(%s)' INVITE delay check. tsx-state:%s\n",
-				ast_sorcery_object_get_id(session->endpoint),
-				session->channel ? ast_channel_name(session->channel) : "",
+			ast_debug(3, "%s: INVITE delay check. tsx-state:%s\n",
+				ast_sip_session_get_name(session),
 				pjsip_tsx_state_str(tsx->state));
 			check_delayed_requests(session, invite_proceeding);
 		} else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
@@ -4023,18 +4666,16 @@
 			 * queuing delayed requests, no matter what event caused
 			 * the transaction to terminate.
 			 */
-			ast_debug(3, "Endpoint '%s(%s)' INVITE delay check. tsx-state:%s\n",
-				ast_sorcery_object_get_id(session->endpoint),
-				session->channel ? ast_channel_name(session->channel) : "",
+			ast_debug(3, "%s: INVITE delay check. tsx-state:%s\n",
+				ast_sip_session_get_name(session),
 				pjsip_tsx_state_str(tsx->state));
 			check_delayed_requests(session, invite_terminated);
 		}
 	} else if (tsx->role == PJSIP_ROLE_UAC
 		&& tsx->state == PJSIP_TSX_STATE_COMPLETED
 		&& !pj_strcmp2(&tsx->method.name, "UPDATE")) {
-		ast_debug(3, "Endpoint '%s(%s)' UPDATE delay check. tsx-state:%s\n",
-			ast_sorcery_object_get_id(session->endpoint),
-			session->channel ? ast_channel_name(session->channel) : "",
+		ast_debug(3, "%s: UPDATE delay check. tsx-state:%s\n",
+			ast_sip_session_get_name(session),
 			pjsip_tsx_state_str(tsx->state));
 		check_delayed_requests(session, update_completed);
 	}
@@ -4172,11 +4813,13 @@
 	int stream;
 
 	if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
-		ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n");
+		ast_log(LOG_ERROR, "%s: Failed to create session SDP. Session has been already disconnected\n",
+			ast_sip_session_get_name(session));
 		return NULL;
 	}
 
 	if (!inv->pool_prov || !(local = PJ_POOL_ZALLOC_T(inv->pool_prov, pjmedia_sdp_session))) {
+		ast_log(LOG_ERROR, "%s: Pool allocation failure\n", ast_sip_session_get_name(session));
 		return NULL;
 	}
 
@@ -4202,6 +4845,7 @@
 			session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
 		}
 		if (!session->pending_media_state->topology) {
+			ast_log(LOG_ERROR, "%s: No pending media state topology\n", ast_sip_session_get_name(session));
 			return NULL;
 		}
 	}
@@ -4219,10 +4863,12 @@
 
 		session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_stream_get_type(stream), i);
 		if (!session_media) {
+			ast_log(LOG_ERROR, "%s: Couldn't alloc/add session media\n", ast_sip_session_get_name(session));
 			return NULL;
 		}
 
 		if (add_sdp_streams(session_media, session, local, offer, stream)) {
+			ast_log(LOG_ERROR, "%s: Couldn't add sdp streams\n", ast_sip_session_get_name(session));
 			return NULL;
 		}
 
@@ -4353,18 +4999,18 @@
 
 		if (inv->following_fork) {
 			if (session->endpoint->media.rtp.follow_early_media_fork) {
-				ast_debug(3, "Following early media fork with different To tags\n");
+				ast_debug(3, "%s: Following early media fork with different To tags\n", ast_sip_session_get_name(session));
 			} else {
-				ast_debug(3, "Not following early media fork with different To tags\n");
+				ast_debug(3, "%s: Not following early media fork with different To tags\n", ast_sip_session_get_name(session));
 				bail = 1;
 			}
 		}
 #ifdef HAVE_PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS
 		else if (inv->updated_sdp_answer) {
 			if (session->endpoint->media.rtp.accept_multiple_sdp_answers) {
-				ast_debug(3, "Accepting updated SDP with same To tag\n");
+				ast_debug(3, "%s: Accepting updated SDP with same To tag\n", ast_sip_session_get_name(session));
 			} else {
-				ast_debug(3, "Ignoring updated SDP answer with same To tag\n");
+				ast_debug(3, "%s: Ignoring updated SDP answer with same To tag\n", ast_sip_session_get_name(session));
 				bail = 1;
 			}
 		}
@@ -4454,6 +5100,8 @@
 	struct ast_sip_nat_hook *hook = ast_sip_mod_data_get(
 		tdata->mod_data, session_module.id, MOD_DATA_NAT_HOOK);
 	struct pjmedia_sdp_session *sdp;
+	pjsip_dialog *dlg = pjsip_tdata_get_dlg(tdata);
+	struct ast_sip_session *session = dlg ? ast_sip_dialog_get_session(dlg) : NULL;
 	int stream;
 
 	/* SDP produced by us directly will never be multipart */
@@ -4477,7 +5125,8 @@
 		 * outgoing session IP is local. If it is, we'll do
 		 * rewriting. No localnet configured? Always rewrite. */
 		if (ast_sip_transport_is_local(transport_state, &our_sdp_addr) || !transport_state->localnet) {
-			ast_debug(5, "Setting external media address to %s\n", ast_sockaddr_stringify_host(&transport_state->external_media_address));
+			ast_debug(5, "%s: Setting external media address to %s\n", ast_sip_session_get_name(session),
+				ast_sockaddr_stringify_host(&transport_state->external_media_address));
 			pj_strdup2(tdata->pool, &sdp->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address));
 			pj_strassign(&sdp->origin.addr, &sdp->conn->addr);
 		}
@@ -4494,7 +5143,8 @@
 
 		handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
 		if (!handler_list) {
-			ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+			ast_debug(4, "%s: No registered SDP handlers for media type '%s'\n",  ast_sip_session_get_name(session),
+				media);
 			continue;
 		}
 		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
@@ -4508,6 +5158,558 @@
 	ast_sip_mod_data_set(tdata->pool, tdata->mod_data, session_module.id, MOD_DATA_NAT_HOOK, nat_hook);
 }
 
+#ifdef TEST_FRAMEWORK
+
+static struct ast_stream *test_stream_alloc(const char *name, enum ast_media_type type, enum ast_stream_state state)
+{
+	struct ast_stream *stream;
+
+	stream = ast_stream_alloc(name, type);
+	if (!stream) {
+		return NULL;
+	}
+	ast_stream_set_state(stream, state);
+
+	return stream;
+}
+
+static struct ast_sip_session_media *test_media_add(
+	struct ast_sip_session_media_state *media_state, const char *name, enum ast_media_type type,
+	enum ast_stream_state state, int position)
+{
+	struct ast_sip_session_media *session_media = NULL;
+	struct ast_stream *stream = NULL;
+
+	stream = test_stream_alloc(name, type, state);
+	if (!stream) {
+		return NULL;
+	}
+
+	if (position >= 0 && position < ast_stream_topology_get_count(media_state->topology)) {
+		ast_stream_topology_set_stream(media_state->topology, position, stream);
+	} else {
+		position = ast_stream_topology_append_stream(media_state->topology, stream);
+	}
+
+	session_media = ao2_alloc_options(sizeof(*session_media), session_media_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!session_media) {
+		return NULL;
+	}
+
+	session_media->keepalive_sched_id = -1;
+	session_media->timeout_sched_id = -1;
+	session_media->type = type;
+	session_media->stream_num = position;
+	session_media->bundle_group = -1;
+	strcpy(session_media->label, name);
+
+	if (AST_VECTOR_REPLACE(&media_state->sessions, position, session_media)) {
+		ao2_ref(session_media, -1);
+
+		return NULL;
+	}
+
+	/* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */
+	if (!media_state->default_session[type] && ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) {
+		media_state->default_session[type] = session_media;
+	}
+
+	return session_media;
+}
+
+static int test_is_media_session_equal(struct ast_sip_session_media *left, struct ast_sip_session_media *right)
+{
+	if (left == right) {
+		return 1;
+	}
+
+	if (!left) {
+		return 1;
+	}
+
+	if (!right) {
+		return 0;
+	}
+	return memcmp(left, right, sizeof(*left)) == 0;
+}
+
+static int test_is_media_state_equal(struct ast_sip_session_media_state *left, struct ast_sip_session_media_state *right,
+	int assert_on_failure)
+{
+	int i;
+	SCOPE_ENTER(2);
+
+	if (left == right) {
+		SCOPE_EXIT_RTN_VALUE(1, "equal\n");
+	}
+
+	if (!(left && right)) {
+		ast_assert(!assert_on_failure);
+		SCOPE_EXIT_RTN_VALUE(0, "one is null: left: %p  right: %p\n", left, right);
+	}
+
+	if (!ast_stream_topology_equal(left->topology, right->topology)) {
+		ast_assert(!assert_on_failure);
+		SCOPE_EXIT_RTN_VALUE(0, "topologies differ\n");
+	}
+	if (AST_VECTOR_SIZE(&left->sessions) != AST_VECTOR_SIZE(&right->sessions)) {
+		ast_assert(!assert_on_failure);
+		SCOPE_EXIT_RTN_VALUE(0, "session vector sizes different: left %lu != right %lu\n", AST_VECTOR_SIZE(&left->sessions),
+			AST_VECTOR_SIZE(&right->sessions));
+	}
+	if (AST_VECTOR_SIZE(&left->read_callbacks) != AST_VECTOR_SIZE(&right->read_callbacks)) {
+		ast_assert(!assert_on_failure);
+		SCOPE_EXIT_RTN_VALUE(0, "read_callback vector sizes different: left %lu != right %lu\n", AST_VECTOR_SIZE(&left->read_callbacks),
+			AST_VECTOR_SIZE(&right->read_callbacks));
+	}
+
+	for (i = 0; i < AST_VECTOR_SIZE(&left->sessions) ; i++) {
+		if (!test_is_media_session_equal(AST_VECTOR_GET(&left->sessions, i), AST_VECTOR_GET(&right->sessions, i))) {
+			ast_assert(!assert_on_failure);
+			SCOPE_EXIT_RTN_VALUE(0, "Media session %d different\n", i);
+		}
+	}
+
+	for (i = 0; i < AST_VECTOR_SIZE(&left->read_callbacks) ; i++) {
+		if (memcmp(AST_VECTOR_GET_ADDR(&left->read_callbacks, i),
+				AST_VECTOR_GET_ADDR(&right->read_callbacks, i),
+				sizeof(struct ast_sip_session_media_read_callback_state)) != 0) {
+			ast_assert(!assert_on_failure);
+			SCOPE_EXIT_RTN_VALUE(0, "read_callback %d different\n", i);
+		}
+	}
+
+	for (i = 0; i < AST_MEDIA_TYPE_END; i++) {
+		if (!(left->default_session[i] && right->default_session[i])) {
+			continue;
+		}
+		if (!left->default_session[i] || !right->default_session[i]
+			|| left->default_session[i]->stream_num != right->default_session[i]->stream_num) {
+			ast_assert(!assert_on_failure);
+			SCOPE_EXIT_RTN_VALUE(0, "Default media session %d different.  Left: %s  Right: %s\n", i,
+				left->default_session[i] ? left->default_session[i]->label : "null",
+				right->default_session[i] ? right->default_session[i]->label : "null");
+		}
+	}
+
+	SCOPE_EXIT_RTN_VALUE(1, "equal\n");
+}
+
+AST_TEST_DEFINE(test_merge_refresh_media_states)
+{
+#define FREE_STATE() \
+({ \
+	ast_sip_session_media_state_free(new_pending_state); \
+	new_pending_state = NULL; \
+	ast_sip_session_media_state_free(delayed_pending_state); \
+	delayed_pending_state = NULL; \
+	ast_sip_session_media_state_free(delayed_active_state); \
+	delayed_active_state = NULL; \
+	ast_sip_session_media_state_free(current_active_state); \
+	current_active_state = NULL; \
+	ast_sip_session_media_state_free(expected_pending_state); \
+	expected_pending_state = NULL; \
+})
+
+#define RESET_STATE(__num) \
+({ \
+	testnum=__num; \
+	ast_trace(__num, "Test %d\n", testnum); \
+	test_failed = 0; \
+	delayed_pending_state = ast_sip_session_media_state_alloc(); \
+	delayed_pending_state->topology = ast_stream_topology_alloc(); \
+	delayed_active_state = ast_sip_session_media_state_alloc(); \
+	delayed_active_state->topology = ast_stream_topology_alloc(); \
+	current_active_state = ast_sip_session_media_state_alloc(); \
+	current_active_state->topology = ast_stream_topology_alloc(); \
+	expected_pending_state = ast_sip_session_media_state_alloc(); \
+	expected_pending_state->topology = ast_stream_topology_alloc(); \
+})
+
+#define CHECKER() \
+({ \
+	new_pending_state = merge_refresh_media_states("unittest", delayed_pending_state, delayed_active_state, current_active_state, 1); \
+	if (!test_is_media_state_equal(new_pending_state, expected_pending_state, 0)) { \
+		res = AST_TEST_FAIL; \
+		test_failed = 1; \
+		ast_test_status_update(test, "da: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(delayed_active_state->topology, &STR_TMP))); \
+		ast_test_status_update(test, "dp: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(delayed_pending_state->topology, &STR_TMP))); \
+		ast_test_status_update(test, "ca: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(current_active_state->topology, &STR_TMP))); \
+		ast_test_status_update(test, "ep: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(expected_pending_state->topology, &STR_TMP))); \
+		ast_test_status_update(test, "np: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(new_pending_state->topology, &STR_TMP))); \
+	} \
+	ast_test_status_update(test, "Test %d %s\n", testnum, test_failed ? "FAILED" : "passed"); \
+	ast_trace(1, "Test %d %s\n", testnum, test_failed ? "FAILED" : "passed"); \
+	test_failed = 0; \
+	FREE_STATE(); \
+})
+
+
+	struct ast_sip_session_media_state * delayed_pending_state = NULL;
+	struct ast_sip_session_media_state * delayed_active_state = NULL;
+	struct ast_sip_session_media_state * current_active_state = NULL;
+	struct ast_sip_session_media_state * new_pending_state = NULL;
+	struct ast_sip_session_media_state * expected_pending_state = NULL;
+
+	enum ast_test_result_state res = AST_TEST_PASS;
+	int test_failed = 0;
+	int testnum = 0;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "merge_refresh_topologies";
+		info->category = "/res/res_pjsip_session/";
+		info->summary = "Test merging of delayed request topologies";
+		info->description = "Test merging of delayed request topologies";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	RESET_STATE(1);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(2);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(3);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(4);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(5);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+	CHECKER();
+
+	RESET_STATE(6);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+	test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(7);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(8);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(9);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(10);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(11);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(12);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "294-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "290-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "290-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "294-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(13);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "298-7", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "290-6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "290-6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "298-7", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(14);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	RESET_STATE(15);
+	test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDONLY, -1);
+	test_media_add(delayed_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(delayed_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(current_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+
+	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDONLY, -1);
+	test_media_add(expected_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	CHECKER();
+
+	return res;
+}
+#endif /* TEST_FRAMEWORK */
+
+
 static int load_module(void)
 {
 	pjsip_endpoint *endpt;
@@ -4536,12 +5738,18 @@
 	ast_sip_register_service(&outbound_invite_auth_module);
 
 	ast_module_shutdown_ref(ast_module_info->self);
-
+#ifdef TEST_FRAMEWORK
+	AST_TEST_REGISTER(test_merge_refresh_media_states);
+#endif
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
 static int unload_module(void)
 {
+#ifdef TEST_FRAMEWORK
+	AST_TEST_UNREGISTER(test_merge_refresh_media_states);
+#endif
+
 	ast_sip_unregister_service(&outbound_invite_auth_module);
 	ast_sip_unregister_service(&session_reinvite_module);
 	ast_sip_unregister_service(&session_module);

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/14730
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: certified/16.8
Gerrit-Change-Id: Id3440972943c611a15f652c6c569fa0e4536bfcb
Gerrit-Change-Number: 14730
Gerrit-PatchSet: 1
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200814/94150a3f/attachment-0001.html>


More information about the asterisk-code-review mailing list