[asterisk-commits] res rtp asterisk / res pjsip: Add support for BUNDLE. (asterisk[master])

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Jul 13 14:40:12 CDT 2017


Jenkins2 has submitted this change and it was merged. ( https://gerrit.asterisk.org/5981 )

Change subject: res_rtp_asterisk / res_pjsip: Add support for BUNDLE.
......................................................................

res_rtp_asterisk / res_pjsip: Add support for BUNDLE.

BUNDLE is a specification used in WebRTC to allow multiple
streams to use the same underlying transport. This reduces
the number of ICE and DTLS negotiations that has to occur
to 1 normally.

This change implements this by adding support for it to
the RTP SDP module in PJSIP. BUNDLE can be turned on using
the "bundle" option and on an offer we will offer to
bundle streams together. On an answer we will accept any
bundle groups provided. Once accepted each stream is bundled
to another RTP instance for transport.

For the res_rtp_asterisk changes the ability to bundle
an RTP instance to another based on the SSRC received
from the remote side has been added. For outgoing traffic
if an RTP instance is bundled to another we will use the
other RTP instance for any transport related things. For
incoming traffic received from the transport instance we
look up the correct instance based on the SSRC and use it
for any non-transport related data.

ASTERISK-27118

Change-Id: I96c0920b9f9aca7382256484765a239017973c11
---
M channels/chan_pjsip.c
M include/asterisk/res_pjsip.h
M include/asterisk/res_pjsip_session.h
M include/asterisk/rtp_engine.h
M main/rtp_engine.c
M res/res_pjsip.c
M res/res_pjsip/pjsip_configuration.c
M res/res_pjsip_sdp_rtp.c
M res/res_pjsip_session.c
M res/res_pjsip_t38.c
M res/res_rtp_asterisk.c
11 files changed, 1,170 insertions(+), 393 deletions(-)

Approvals:
  Kevin Harwell: Looks good to me, but someone else must approve
  Matthew Fredrickson: Looks good to me, approved
  Jenkins2: Approved for Submit



diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 0e4468c..3de980a 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -792,8 +792,6 @@
 		return f;
 	}
 
-	f->stream_num = callback_state->session->stream_num;
-
 	if (f->frametype != AST_FRAME_VOICE ||
 		callback_state->session != session->active_media_state->default_session[callback_state->session->type]) {
 		return f;
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 2cd27d3..d499d55 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -688,6 +688,8 @@
 	unsigned int max_audio_streams;
 	/*! Maximum number of video streams to offer/accept */
 	unsigned int max_video_streams;
+	/*! Use BUNDLE */
+	unsigned int bundle;
 };
 
 /*!
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index e298e1f..eae29de 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -99,6 +99,12 @@
 	ast_sip_session_media_write_cb write_callback;
 	/*! \brief The stream number to place into any resulting frames */
 	int stream_num;
+	/*! \brief Media identifier for this stream (may be shared across multiple streams) */
+	char *mid;
+	/*! \brief The bundle group the stream belongs to */
+	int bundle_group;
+	/*! \brief Whether this stream is currently bundled or not */
+	unsigned int bundled;
 };
 
 /*!
@@ -833,6 +839,19 @@
 int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
 	ast_sip_session_media_write_cb callback);
 
+/*!
+ * \brief Retrieve the underlying media session that is acting as transport for a media session
+ * \since 15.0.0
+ *
+ * \param session The session
+ * \param session_media The media session to retrieve the transport for
+ *
+ * \note This operates on the pending media state
+ *
+ * \note This function is guaranteed to return non-NULL
+ */
+struct ast_sip_session_media *ast_sip_session_media_get_transport(struct ast_sip_session *session, struct ast_sip_session_media *session_media);
+
 /*! \brief Determines whether the res_pjsip_session module is loaded */
 #define CHECK_PJSIP_SESSION_MODULE_LOADED()				\
 	do {								\
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index 5f43916..20ae959 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -603,6 +603,12 @@
 	unsigned int (*ssrc_get)(struct ast_rtp_instance *instance);
 	/*! Callback to retrieve RTCP SDES CNAME */
 	const char *(*cname_get)(struct ast_rtp_instance *instance);
+	/*! Callback to bundle an RTP instance to another */
+	int (*bundle)(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
+	/*! Callback to set remote SSRC information */
+	void (*set_remote_ssrc)(struct ast_rtp_instance *instance, unsigned int ssrc);
+	/*! Callback to set the stream identifier */
+	void (*set_stream_num)(struct ast_rtp_instance *instance, int stream_num);
 	/*! Callback to pointer for optional ICE support */
 	struct ast_rtp_engine_ice *ice;
 	/*! Callback to pointer for optional DTLS SRTP support */
@@ -1507,6 +1513,20 @@
 int ast_rtp_codecs_payload_code(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code);
 
 /*!
+ * \brief Set a payload code for use with a specific Asterisk format
+ *
+ * \param codecs Codecs structure to manipulate
+ * \param code The payload code
+ * \param format Asterisk format
+ *
+ * \retval 0 Payload was set to the given format
+ * \retval -1 Payload was in use or could not be set
+ *
+ * \since 15.0.0
+ */
+int ast_rtp_codecs_payload_set_rx(struct ast_rtp_codecs *codecs, int code, struct ast_format *format);
+
+/*!
  * \brief Retrieve a tx mapped payload type based on whether it is an Asterisk format and the code
  * \since 14.0.0
  *
@@ -2266,6 +2286,8 @@
  *
  * \retval 0 Success
  * \retval non-zero Failure
+ *
+ * \note If no remote policy is provided any existing SRTP policies are left and the new local policy is added
  */
 int ast_rtp_instance_add_srtp_policy(struct ast_rtp_instance *instance, struct ast_srtp_policy* remote_policy, struct ast_srtp_policy *local_policy, int rtcp);
 
@@ -2411,6 +2433,36 @@
  */
 const char *ast_rtp_instance_get_cname(struct ast_rtp_instance *rtp);
 
+/*!
+ * \brief Request that an RTP instance be bundled with another
+ * \since 15.0.0
+ *
+ * \param child The child RTP instance
+ * \param parent The parent RTP instance the child should be bundled with
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_rtp_instance_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
+
+/*!
+ * \brief Set the remote SSRC for an RTP instance
+ * \since 15.0.0
+ *
+ * \param rtp The RTP instance
+ * \param ssrc The remote SSRC
+ */
+void ast_rtp_instance_set_remote_ssrc(struct ast_rtp_instance *rtp, unsigned int ssrc);
+
+/*!
+ * \brief Set the stream number for an RTP instance
+ * \since 15.0.0
+ *
+ * \param rtp The RTP instance
+ * \param stream_num The stream identifier number
+ */
+void ast_rtp_instance_set_stream_num(struct ast_rtp_instance *instance, int stream_num);
+
 /*! \addtogroup StasisTopicsAndMessages
  * @{
  */
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index 9cfae09..abd4b1f 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -1495,21 +1495,24 @@
  * \param asterisk_format Non-zero if the given Asterisk format is present
  * \param format Asterisk format to look for
  * \param code The format to look for
+ * \param explicit Require the provided code to be explicitly used
  *
  * \note It is assumed that static_RTP_PT_lock is at least read locked before calling.
  *
  * \retval Numerical payload type
  * \retval -1 if could not assign.
  */
-static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code)
+static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code, int explicit)
 {
-	int payload;
+	int payload = code;
 	struct ast_rtp_payload_type *new_type;
 
-	payload = find_static_payload_type(asterisk_format, format, code);
+	if (!explicit) {
+		payload = find_static_payload_type(asterisk_format, format, code);
 
-	if (payload < 0 && (!asterisk_format || ast_option_rtpusedynamic)) {
-		return payload;
+		if (payload < 0 && (!asterisk_format || ast_option_rtpusedynamic)) {
+			return payload;
+		}
 	}
 
 	new_type = rtp_payload_type_alloc(format, payload, code, 1);
@@ -1525,9 +1528,9 @@
 		 * The payload type is a static assignment
 		 * or our default dynamic position is available.
 		 */
-               rtp_codecs_payload_replace_rx(codecs, payload, new_type);
-	} else if (-1 < (payload = find_unused_payload(codecs))
-		|| -1 < (payload = rtp_codecs_find_non_primary_dynamic_rx(codecs))) {
+		rtp_codecs_payload_replace_rx(codecs, payload, new_type);
+	} else if (!explicit && (-1 < (payload = find_unused_payload(codecs))
+		|| -1 < (payload = rtp_codecs_find_non_primary_dynamic_rx(codecs)))) {
 		/*
 		 * We found the first available empty dynamic position
 		 * or we found a mapping that should no longer be
@@ -1535,6 +1538,11 @@
 		 */
 		new_type->payload = payload;
 		rtp_codecs_payload_replace_rx(codecs, payload, new_type);
+	} else if (explicit) {
+		/*
+		* They explicitly requested this payload number be used but it couldn't be
+		*/
+		payload = -1;
 	} else {
 		/*
 		 * There are no empty or non-primary dynamic positions
@@ -1595,11 +1603,16 @@
 
 	if (payload < 0) {
 		payload = rtp_codecs_assign_payload_code_rx(codecs, asterisk_format, format,
-			code);
+			code, 0);
 	}
 	ast_rwlock_unlock(&static_RTP_PT_lock);
 
 	return payload;
+}
+
+int ast_rtp_codecs_payload_set_rx(struct ast_rtp_codecs *codecs, int code, struct ast_format *format)
+{
+	return rtp_codecs_assign_payload_code_rx(codecs, 1, format, code, 1);
 }
 
 int ast_rtp_codecs_payload_code_tx(struct ast_rtp_codecs *codecs, int asterisk_format, const struct ast_format *format, int code)
@@ -2424,7 +2437,7 @@
 
 	if (!*srtp) {
 		res = res_srtp->create(srtp, instance, remote_policy);
-	} else {
+	} else if (remote_policy) {
 		res = res_srtp->replace(srtp, instance, remote_policy);
 	}
 	if (!res) {
@@ -3366,3 +3379,38 @@
 
 	return cname;
 }
+
+int ast_rtp_instance_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent)
+{
+	int res = -1;
+
+	if (child->engine != parent->engine) {
+		return -1;
+	}
+
+	ao2_lock(child);
+	if (child->engine->bundle) {
+		res = child->engine->bundle(child, parent);
+	}
+	ao2_unlock(child);
+
+	return res;
+}
+
+void ast_rtp_instance_set_remote_ssrc(struct ast_rtp_instance *rtp, unsigned int ssrc)
+{
+	ao2_lock(rtp);
+	if (rtp->engine->set_remote_ssrc) {
+		rtp->engine->set_remote_ssrc(rtp, ssrc);
+	}
+	ao2_unlock(rtp);
+}
+
+void ast_rtp_instance_set_stream_num(struct ast_rtp_instance *rtp, int stream_num)
+{
+	ao2_lock(rtp);
+	if (rtp->engine->set_stream_num) {
+		rtp->engine->set_stream_num(rtp, stream_num);
+	}
+	ao2_unlock(rtp);
+}
\ No newline at end of file
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 0cf0343..1b546ef 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -995,6 +995,14 @@
 						streams allowed for the endpoint.
 					</para></description>
 				</configOption>
+				<configOption name="bundle" default="no">
+					<synopsis>Enable RTP bundling</synopsis>
+					<description><para>
+						With this option enabled, Asterisk will attempt to negotiate the use of bundle.
+						If negotiated this will result in multiple RTP streams being carried over the same
+						underlying transport. Note that enabling bundle will also enable the rtcp_mux option.
+					</para></description>
+				</configOption>
 			</configObject>
 			<configObject name="auth">
 				<synopsis>Authentication type</synopsis>
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 372b01b..d56ff5d 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1332,6 +1332,10 @@
 		return -1;
 	}
 
+	if (endpoint->media.bundle) {
+		endpoint->media.rtcp_mux = 1;
+	}
+
 	return 0;
 }
 
@@ -1954,6 +1958,7 @@
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_audio_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_audio_streams));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_video_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_video_streams));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "bundle", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.bundle));
 
 	if (ast_sip_initialize_sorcery_transport()) {
 		ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index a491308..4ec8115 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -317,6 +317,7 @@
 
 static int set_caps(struct ast_sip_session *session,
 	struct ast_sip_session_media *session_media,
+	struct ast_sip_session_media *session_media_transport,
 	const struct pjmedia_sdp_media *stream,
 	int is_offer, struct ast_stream *asterisk_stream)
 {
@@ -375,6 +376,24 @@
 		session_media->rtp);
 
 	ast_stream_set_formats(asterisk_stream, joint);
+
+	/* If this is a bundled stream then apply the payloads to RTP instance acting as transport to prevent conflicts */
+	if (session_media_transport != session_media && session_media->bundled) {
+		int index;
+
+		for (index = 0; index < ast_format_cap_count(joint); ++index) {
+			struct ast_format *format = ast_format_cap_get_format(joint, index);
+			int rtp_code;
+
+			/* Ensure this payload is in the bundle group transport codecs, this purposely doesn't check the return value for
+			 * things as the format is guaranteed to have a payload already.
+			 */
+			rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0);
+			ast_rtp_codecs_payload_set_rx(ast_rtp_instance_get_codecs(session_media_transport->rtp), rtp_code, format);
+
+			ao2_ref(format, -1);
+		}
+	}
 
 	if (session->channel && ast_sip_session_is_pending_stream_default(session, asterisk_stream)) {
 		ast_channel_lock(session->channel);
@@ -496,7 +515,8 @@
 }
 
 /*! \brief Function which adds ICE attributes to a media stream */
-static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
+static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media,
+	unsigned int include_candidates)
 {
 	struct ast_rtp_engine_ice *ice;
 	struct ao2_container *candidates;
@@ -506,8 +526,7 @@
 	struct ao2_iterator it_candidates;
 	struct ast_rtp_engine_ice_candidate *candidate;
 
-	if (!session->endpoint->media.rtp.ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp)) ||
-		!(candidates = ice->get_local_candidates(session_media->rtp))) {
+	if (!session->endpoint->media.rtp.ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp))) {
 		return;
 	}
 
@@ -519,6 +538,15 @@
 	if ((password = ice->get_password(session_media->rtp))) {
 		attr = pjmedia_sdp_attr_create(pool, "ice-pwd", pj_cstr(&stmp, password));
 		media->attr[media->attr_count++] = attr;
+	}
+
+	if (!include_candidates) {
+		return;
+	}
+
+	candidates = ice->get_local_candidates(session_media->rtp);
+	if (!candidates) {
+		return;
 	}
 
 	it_candidates = ao2_iterator_init(candidates, 0);
@@ -940,6 +968,63 @@
 	}
 }
 
+/*! \brief Function which adds ssrc attributes to a media stream */
+static void add_ssrc_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
+{
+	pj_str_t stmp;
+	pjmedia_sdp_attr *attr;
+	char tmp[128];
+
+	if (!session->endpoint->media.bundle || session_media->bundle_group == -1) {
+		return;
+	}
+
+	snprintf(tmp, sizeof(tmp), "%u cname:%s", ast_rtp_instance_get_ssrc(session_media->rtp), ast_rtp_instance_get_cname(session_media->rtp));
+	attr = pjmedia_sdp_attr_create(pool, "ssrc", pj_cstr(&stmp, tmp));
+	media->attr[media->attr_count++] = attr;
+}
+
+/*! \brief Function which processes ssrc attributes in a stream */
+static void process_ssrc_attributes(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+				   const struct pjmedia_sdp_media *remote_stream)
+{
+	int index;
+
+	if (!session->endpoint->media.bundle) {
+		return;
+	}
+
+	for (index = 0; index < remote_stream->attr_count; ++index) {
+		pjmedia_sdp_attr *attr = remote_stream->attr[index];
+		char attr_value[pj_strlen(&attr->value) + 1];
+		char *ssrc_attribute_name, *ssrc_attribute_value = NULL;
+		unsigned int ssrc;
+
+		/* We only care about ssrc attributes */
+		if (pj_strcmp2(&attr->name, "ssrc")) {
+			continue;
+		}
+
+		ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));
+
+		if ((ssrc_attribute_name = strchr(attr_value, ' '))) {
+			/* This has an actual attribute */
+			*ssrc_attribute_name++ = '\0';
+			ssrc_attribute_value = strchr(ssrc_attribute_name, ':');
+			if (ssrc_attribute_value) {
+				/* Values are actually optional according to the spec */
+				*ssrc_attribute_value++ = '\0';
+			}
+		}
+
+		if (sscanf(attr_value, "%30u", &ssrc) < 1) {
+			continue;
+		}
+
+		ast_rtp_instance_set_remote_ssrc(session_media->rtp, ssrc);
+	}
+}
+
 /*! \brief Function which negotiates an incoming media stream */
 static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 	struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,
@@ -948,6 +1033,7 @@
 	char host[NI_MAXHOST];
 	RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
 	pjmedia_sdp_media *stream = sdp->media[index];
+	struct ast_sip_session_media *session_media_transport;
 	enum ast_media_type media_type = session_media->type;
 	enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
 	int res;
@@ -981,38 +1067,51 @@
 		return -1;
 	}
 
-	session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(stream, "rtcp-mux", NULL) != NULL);
-	set_ice_components(session, session_media);
+	process_ssrc_attributes(session, session_media, stream);
 
-	enable_rtcp(session, session_media, stream);
+	session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
-	res = setup_media_encryption(session, session_media, sdp, stream);
-	if (res) {
-		if (!session->endpoint->media.rtp.encryption_optimistic ||
-			!pj_strncmp2(&stream->desc.transport, "RTP/SAVP", 8)) {
-			/* If optimistic encryption is disabled and crypto should have been enabled
-			 * but was not this session must fail. This must also fail if crypto was
-			 * required in the offer but could not be set up.
-			 */
-			return -1;
+	if (session_media_transport == session_media || !session_media->bundled) {
+		/* If this media session is carrying actual traffic then set up those aspects */
+		session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(stream, "rtcp-mux", NULL) != NULL);
+		set_ice_components(session, session_media);
+
+		enable_rtcp(session, session_media, stream);
+
+		res = setup_media_encryption(session, session_media, sdp, stream);
+		if (res) {
+			if (!session->endpoint->media.rtp.encryption_optimistic ||
+				!pj_strncmp2(&stream->desc.transport, "RTP/SAVP", 8)) {
+				/* If optimistic encryption is disabled and crypto should have been enabled
+				 * but was not this session must fail. This must also fail if crypto was
+				 * required in the offer but could not be set up.
+				 */
+				return -1;
+			}
+			/* There is no encryption, sad. */
+			session_media->encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
 		}
-		/* There is no encryption, sad. */
-		session_media->encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
- 	}
 
-	/* If we've been explicitly configured to use the received transport OR if
-	 * encryption is on and crypto is present use the received transport.
-	 * This is done in case of optimistic because it may come in as RTP/AVP or RTP/SAVP depending
-	 * on the configuration of the remote endpoint (optimistic themselves or mandatory).
-	 */
-	if ((session->endpoint->media.rtp.use_received_transport) ||
-		((encryption == AST_SIP_MEDIA_ENCRYPT_SDES) && !res)) {
-		pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);
- 	}
+		/* If we've been explicitly configured to use the received transport OR if
+		 * encryption is on and crypto is present use the received transport.
+		 * This is done in case of optimistic because it may come in as RTP/AVP or RTP/SAVP depending
+		 * on the configuration of the remote endpoint (optimistic themselves or mandatory).
+		 */
+		if ((session->endpoint->media.rtp.use_received_transport) ||
+			((encryption == AST_SIP_MEDIA_ENCRYPT_SDES) && !res)) {
+			pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);
+		}
+	} else {
+		/* This is bundled with another session, so mark it as such */
+		ast_rtp_instance_bundle(session_media->rtp, session_media_transport->rtp);
 
-	if (set_caps(session, session_media, stream, 1, asterisk_stream)) {
+		enable_rtcp(session, session_media, stream);
+	}
+
+	if (set_caps(session, session_media, session_media_transport, stream, 1, asterisk_stream)) {
 		return 0;
 	}
+
 	return 1;
 }
 
@@ -1032,6 +1131,7 @@
 	static const pj_str_t STR_PASSIVE = { "passive", 7 };
 	static const pj_str_t STR_ACTPASS = { "actpass", 7 };
 	static const pj_str_t STR_HOLDCONN = { "holdconn", 8 };
+	enum ast_rtp_dtls_setup setup;
 
 	switch (session_media->encryption) {
 	case AST_SIP_MEDIA_ENCRYPT_NONE:
@@ -1085,7 +1185,16 @@
 			break;
 		}
 
-		switch (dtls->get_setup(session_media->rtp)) {
+		/* If this is an answer we need to use our current state, if it's an offer we need to use
+		 * the configured value.
+		 */
+		if (pjmedia_sdp_neg_get_state(session->inv_session->neg) != PJMEDIA_SDP_NEG_STATE_DONE) {
+			setup = dtls->get_setup(session_media->rtp);
+		} else {
+			setup = session->endpoint->media.rtp.dtls_cfg.default_setup;
+		}
+
+		switch (setup) {
 		case AST_RTP_DTLS_SETUP_ACTIVE:
 			attr = pjmedia_sdp_attr_create(pool, "setup", &STR_ACTIVE);
 			media->attr[media->attr_count++] = attr;
@@ -1100,7 +1209,6 @@
 			break;
 		case AST_RTP_DTLS_SETUP_HOLDCONN:
 			attr = pjmedia_sdp_attr_create(pool, "setup", &STR_HOLDCONN);
-			media->attr[media->attr_count++] = attr;
 			break;
 		default:
 			break;
@@ -1152,6 +1260,7 @@
 	int rtp_code;
 	RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
 	enum ast_media_type media_type = session_media->type;
+	struct ast_sip_session_media *session_media_transport;
 
 	int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
 		ast_format_cap_count(session->direct_media_cap);
@@ -1195,68 +1304,106 @@
 		return -1;
 	}
 
-	set_ice_components(session, session_media);
-	enable_rtcp(session, session_media, NULL);
+	/* If this stream has not been bundled already it is new and we need to ensure there is no SSRC conflict */
+	if (session_media->bundle_group != -1 && !session_media->bundled) {
+		for (index = 0; index < sdp->media_count; ++index) {
+			struct ast_sip_session_media *other_session_media;
 
-	/* Crypto has to be added before setting the media transport so that SRTP is properly
-	 * set up according to the configuration. This ends up changing the media transport.
-	 */
-	if (add_crypto_to_stream(session, session_media, pool, media)) {
-		return -1;
-	}
+			other_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);
+			if (!other_session_media->rtp || other_session_media->bundle_group != session_media->bundle_group) {
+				continue;
+			}
 
-	if (pj_strlen(&session_media->transport)) {
-		/* If a transport has already been specified use it */
-		media->desc.transport = session_media->transport;
-	} else {
-		media->desc.transport = pj_str(ast_sdp_get_rtp_profile(
-			/* Optimistic encryption places crypto in the normal RTP/AVP profile */
-			!session->endpoint->media.rtp.encryption_optimistic &&
-				(session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES),
-			session_media->rtp, session->endpoint->media.rtp.use_avpf,
-			session->endpoint->media.rtp.force_avp));
-	}
-
-	media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
-	if (!media->conn) {
-		return -1;
-	}
-
-	/* Add connection level details */
-	if (direct_media_enabled) {
-		hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR);
-	} else if (ast_strlen_zero(session->endpoint->media.address)) {
-		hostip = ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET());
-	} else {
-		hostip = session->endpoint->media.address;
-	}
-
-	if (ast_strlen_zero(hostip)) {
-		ast_log(LOG_ERROR, "No local host IP available for stream %s\n",
-			ast_codec_media_type2str(session_media->type));
-		return -1;
-	}
-
-	media->conn->net_type = STR_IN;
-	/* Assume that the connection will use IPv4 until proven otherwise */
-	media->conn->addr_type = STR_IP4;
-	pj_strdup2(pool, &media->conn->addr, hostip);
-
-	if (!ast_strlen_zero(session->endpoint->media.address)) {
-		pj_sockaddr ip;
-
-		if ((pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &media->conn->addr, &ip) == PJ_SUCCESS) &&
-			(ip.addr.sa_family == pj_AF_INET6())) {
-			media->conn->addr_type = STR_IP6;
+			if (ast_rtp_instance_get_ssrc(session_media->rtp) == ast_rtp_instance_get_ssrc(other_session_media->rtp)) {
+				ast_rtp_instance_change_source(session_media->rtp);
+				/* Start the conflict check over again */
+				index = -1;
+				continue;
+			}
 		}
 	}
 
-	/* Add ICE attributes and candidates */
-	add_ice_to_stream(session, session_media, pool, media);
+	session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
-	ast_rtp_instance_get_local_address(session_media->rtp, &addr);
-	media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr);
-	media->desc.port_count = 1;
+	if (session_media_transport == session_media || !session_media->bundled) {
+		set_ice_components(session, session_media);
+		enable_rtcp(session, session_media, NULL);
+
+		/* Crypto has to be added before setting the media transport so that SRTP is properly
+		 * set up according to the configuration. This ends up changing the media transport.
+		 */
+		if (add_crypto_to_stream(session, session_media, pool, media)) {
+			return -1;
+		}
+
+		if (pj_strlen(&session_media->transport)) {
+			/* If a transport has already been specified use it */
+			media->desc.transport = session_media->transport;
+		} else {
+			media->desc.transport = pj_str(ast_sdp_get_rtp_profile(
+				/* Optimistic encryption places crypto in the normal RTP/AVP profile */
+				!session->endpoint->media.rtp.encryption_optimistic &&
+					(session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES),
+				session_media->rtp, session->endpoint->media.rtp.use_avpf,
+				session->endpoint->media.rtp.force_avp));
+		}
+
+		media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
+		if (!media->conn) {
+			return -1;
+		}
+
+		/* Add connection level details */
+		if (direct_media_enabled) {
+			hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR);
+		} else if (ast_strlen_zero(session->endpoint->media.address)) {
+			hostip = ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET());
+		} else {
+			hostip = session->endpoint->media.address;
+		}
+
+		if (ast_strlen_zero(hostip)) {
+			ast_log(LOG_ERROR, "No local host IP available for stream %s\n",
+				ast_codec_media_type2str(session_media->type));
+			return -1;
+		}
+
+		media->conn->net_type = STR_IN;
+		/* Assume that the connection will use IPv4 until proven otherwise */
+		media->conn->addr_type = STR_IP4;
+		pj_strdup2(pool, &media->conn->addr, hostip);
+
+		if (!ast_strlen_zero(session->endpoint->media.address)) {
+			pj_sockaddr ip;
+
+			if ((pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &media->conn->addr, &ip) == PJ_SUCCESS) &&
+				(ip.addr.sa_family == pj_AF_INET6())) {
+				media->conn->addr_type = STR_IP6;
+			}
+		}
+
+		/* Add ICE attributes and candidates */
+		add_ice_to_stream(session, session_media, pool, media, 1);
+
+		ast_rtp_instance_get_local_address(session_media->rtp, &addr);
+		media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr);
+		media->desc.port_count = 1;
+	} else {
+		pjmedia_sdp_media *bundle_group_stream = sdp->media[session_media_transport->stream_num];
+
+		/* As this is in a bundle group it shares the same details as the group instance */
+		media->desc.transport = bundle_group_stream->desc.transport;
+		media->conn = bundle_group_stream->conn;
+		media->desc.port = bundle_group_stream->desc.port;
+
+		if (add_crypto_to_stream(session, session_media_transport, pool, media)) {
+			return -1;
+		}
+
+		add_ice_to_stream(session, session_media_transport, pool, media, 0);
+
+		enable_rtcp(session, session_media, NULL);
+	}
 
 	if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
 		ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
@@ -1278,10 +1425,23 @@
 			continue;
 		}
 
-		if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0)) == -1) {
-			ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
-			ao2_ref(format, -1);
-			continue;
+		/* If this stream is not a transport we need to use the transport codecs structure for payload management to prevent
+		 * conflicts.
+		 */
+		if (session_media_transport != session_media) {
+			if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media_transport->rtp), 1, format, 0)) == -1) {
+				ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
+				ao2_ref(format, -1);
+				continue;
+			}
+			/* Our instance has to match the payload number though */
+			ast_rtp_codecs_payload_set_rx(ast_rtp_instance_get_codecs(session_media->rtp), rtp_code, format);
+		} else {
+			if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0)) == -1) {
+				ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
+				ao2_ref(format, -1);
+				continue;
+			}
 		}
 
 		if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 1, format, 0))) {
@@ -1332,6 +1492,7 @@
 		}
 	}
 
+
 	/* If no formats were actually added to the media stream don't add it to the SDP */
 	if (!media->desc.fmt_count) {
 		return 1;
@@ -1364,6 +1525,8 @@
 		attr = pjmedia_sdp_attr_create(pool, "rtcp-mux", NULL);
 		pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
 	}
+
+	add_ssrc_to_stream(session, session_media, pool, media);
 
 	/* Add the media stream to the SDP */
 	sdp->media[sdp->media_count++] = media;
@@ -1425,6 +1588,7 @@
 	enum ast_media_type media_type = session_media->type;
 	char host[NI_MAXHOST];
 	int res;
+	struct ast_sip_session_media *session_media_transport;
 
 	if (!session->channel) {
 		return 1;
@@ -1441,48 +1605,60 @@
 		return -1;
 	}
 
-	session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(remote_stream, "rtcp-mux", NULL) != NULL);
-	set_ice_components(session, session_media);
+	process_ssrc_attributes(session, session_media, remote_stream);
 
-	enable_rtcp(session, session_media, remote_stream);
+	session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
-	res = setup_media_encryption(session, session_media, remote, remote_stream);
-	if (!session->endpoint->media.rtp.encryption_optimistic && res) {
-		/* If optimistic encryption is disabled and crypto should have been enabled but was not
-		 * this session must fail.
-		 */
-		return -1;
+	if (session_media_transport == session_media || !session_media->bundled) {
+		session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(remote_stream, "rtcp-mux", NULL) != NULL);
+		set_ice_components(session, session_media);
+
+		enable_rtcp(session, session_media, remote_stream);
+
+		res = setup_media_encryption(session, session_media, remote, remote_stream);
+		if (!session->endpoint->media.rtp.encryption_optimistic && res) {
+			/* If optimistic encryption is disabled and crypto should have been enabled but was not
+			 * this session must fail.
+			 */
+			return -1;
+		}
+
+		if (!remote_stream->conn && !remote->conn) {
+			return 1;
+		}
+
+		ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
+
+		/* Ensure that the address provided is valid */
+		if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) {
+			/* The provided host was actually invalid so we error out this negotiation */
+			return -1;
+		}
+
+		/* Apply connection information to the RTP instance */
+		ast_sockaddr_set_port(addrs, remote_stream->desc.port);
+		ast_rtp_instance_set_remote_address(session_media->rtp, addrs);
+
+		ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback);
+		ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0),
+			media_session_rtp_read_callback);
+		if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) {
+			ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1),
+				media_session_rtcp_read_callback);
+		}
+
+		/* If ICE support is enabled find all the needed attributes */
+		process_ice_attributes(session, session_media, remote, remote_stream);
+	} else {
+		/* This is bundled with another session, so mark it as such */
+		ast_rtp_instance_bundle(session_media->rtp, session_media_transport->rtp);
+		ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback);
+		enable_rtcp(session, session_media, remote_stream);
 	}
 
-	if (!remote_stream->conn && !remote->conn) {
+	if (set_caps(session, session_media, session_media_transport, remote_stream, 0, asterisk_stream)) {
 		return 1;
 	}
-
-	ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
-
-	/* Ensure that the address provided is valid */
-	if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) {
-		/* The provided host was actually invalid so we error out this negotiation */
-		return -1;
-	}
-
-	/* Apply connection information to the RTP instance */
-	ast_sockaddr_set_port(addrs, remote_stream->desc.port);
-	ast_rtp_instance_set_remote_address(session_media->rtp, addrs);
-	if (set_caps(session, session_media, remote_stream, 0, asterisk_stream)) {
-		return 1;
-	}
-
-	ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback);
-	ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0),
-		media_session_rtp_read_callback);
-	if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) {
-		ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1),
-			media_session_rtcp_read_callback);
-	}
-
-	/* If ICE support is enabled find all the needed attributes */
-	process_ice_attributes(session, session_media, remote, remote_stream);
 
 	/* Set the channel uniqueid on the RTP instance now that it is becoming active */
 	ast_channel_lock(session->channel);
@@ -1490,6 +1666,7 @@
 	ast_channel_unlock(session->channel);
 
 	/* Ensure the RTP instance is active */
+	ast_rtp_instance_set_stream_num(session_media->rtp, ast_stream_get_position(asterisk_stream));
 	ast_rtp_instance_activate(session_media->rtp);
 
 	/* audio stream handles music on hold */
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index ecda499..315db6d 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -324,6 +324,28 @@
 	return 0;
 }
 
+struct ast_sip_session_media *ast_sip_session_media_get_transport(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+	int index;
+
+	if (!session->endpoint->media.bundle || ast_strlen_zero(session_media->mid)) {
+		return session_media;
+	}
+
+	for (index = 0; index < AST_VECTOR_SIZE(&session->pending_media_state->sessions); ++index) {
+		struct ast_sip_session_media *bundle_group_session_media;
+
+		bundle_group_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);
+
+		/* The first session which is in the bundle group is considered the authoritative session for transport */
+		if (bundle_group_session_media->bundle_group == session_media->bundle_group) {
+			return bundle_group_session_media;
+		}
+	}
+
+	return session_media;
+}
+
 /*!
  * \brief Set an SDP stream handler for a corresponding session media.
  *
@@ -371,6 +393,8 @@
 	if (session_media->srtp) {
 		ast_sdp_srtp_destroy(session_media->srtp);
 	}
+
+	ast_free(session_media->mid);
 }
 
 struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,
@@ -408,13 +432,25 @@
 		session_media->timeout_sched_id = -1;
 		session_media->type = type;
 		session_media->stream_num = position;
+
+		if (session->endpoint->media.bundle) {
+			/* This is a new stream so create a new mid based on media type and position, which makes it unique.
+			 * If this is the result of an offer the mid will just end up getting replaced.
+			 */
+			if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) {
+				ao2_ref(session_media, -1);
+				return NULL;
+			}
+			session_media->bundle_group = 0;
+		} else {
+			session_media->bundle_group = -1;
+		}
 	}
 
 	AST_VECTOR_REPLACE(&media_state->sessions, position, session_media);
 
 	/* 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) {
+	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;
 	}
 
@@ -439,6 +475,78 @@
 		 */
 		return 1;
 	}
+}
+
+static int get_mid_bundle_group(const pjmedia_sdp_session *sdp, const char *mid)
+{
+	int bundle_group = 0;
+	int index;
+
+	for (index = 0; index < sdp->attr_count; ++index) {
+		pjmedia_sdp_attr *attr = sdp->attr[index];
+		char value[pj_strlen(&attr->value) + 1], *mids = value, *attr_mid;
+
+		if (pj_strcmp2(&attr->name, "group") || pj_strncmp2(&attr->value, "BUNDLE", 6)) {
+			continue;
+		}
+
+		ast_copy_pj_str(value, &attr->value, sizeof(value));
+
+		/* Skip the BUNDLE at the front */
+		mids += 7;
+
+		while ((attr_mid = strsep(&mids, " "))) {
+			if (!strcmp(attr_mid, mid)) {
+				/* The ordering of attributes determines our internal identification of the bundle group based on number,
+				 * with -1 being not in a bundle group. Since this is only exposed internally for response purposes it's
+				 * actually even fine if things move around.
+				 */
+				return bundle_group;
+			}
+		}
+
+		bundle_group++;
+	}
+
+	return -1;
+}
+
+static int set_mid_and_bundle_group(struct ast_sip_session *session,
+	struct ast_sip_session_media *session_media,
+	const pjmedia_sdp_session *sdp,
+	const struct pjmedia_sdp_media *stream)
+{
+	pjmedia_sdp_attr *attr;
+
+	if (!session->endpoint->media.bundle) {
+		return 0;
+	}
+
+	/* By default on an incoming negotiation we assume no mid and bundle group is present */
+	ast_free(session_media->mid);
+	session_media->mid = NULL;
+	session_media->bundle_group = -1;
+	session_media->bundled = 0;
+
+	/* Grab the media identifier for the stream */
+	attr = pjmedia_sdp_media_find_attr2(stream, "mid", NULL);
+	if (!attr) {
+		return 0;
+	}
+
+	session_media->mid = ast_calloc(1, attr->value.slen + 1);
+	if (!session_media->mid) {
+		return 0;
+	}
+	ast_copy_pj_str(session_media->mid, &attr->value, attr->value.slen + 1);
+
+	/* Determine what bundle group this is part of */
+	session_media->bundle_group = get_mid_bundle_group(sdp, session_media->mid);
+
+	/* If this is actually part of a bundle group then the other side requested or accepted the bundle request */
+	session_media->bundled = session_media->bundle_group != -1;
+
+	return 0;
 }
 
 static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp)
@@ -497,8 +605,12 @@
 			ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
 				ast_codec_media_type2str(type), i);
 			ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+			session_media->bundle_group = -1;
+			session_media->bundled = 0;
 			continue;
 		}
+
+		set_mid_and_bundle_group(session, session_media, sdp, remote_stream);
 
 		if (session_media->handler) {
 			handler = session_media->handler;
@@ -588,6 +700,8 @@
 
 	/* We need a null-terminated version of the media string */
 	ast_copy_pj_str(media, &local->media[index]->desc.media, sizeof(media));
+
+	set_mid_and_bundle_group(session, session_media, remote, remote->media[index]);
 
 	handler = session_media->handler;
 	if (handler) {
@@ -3443,6 +3557,82 @@
 	return 0;
 }
 
+/*! \brief Bundle group building structure */
+struct sip_session_media_bundle_group {
+	/*! \brief The media identifiers in this bundle group */
+	char *mids[PJMEDIA_MAX_SDP_MEDIA];
+	/*! \brief SDP attribute string */
+	struct ast_str *attr_string;
+};
+
+static int add_bundle_groups(struct ast_sip_session *session, pj_pool_t *pool, pjmedia_sdp_session *answer)
+{
+	pj_str_t stmp;
+	pjmedia_sdp_attr *attr;
+	struct sip_session_media_bundle_group bundle_groups[PJMEDIA_MAX_SDP_MEDIA];
+	int index, mid_id;
+	struct sip_session_media_bundle_group *bundle_group;
+
+	if (!session->endpoint->media.bundle) {
+		return 0;
+	}
+
+	memset(bundle_groups, 0, sizeof(bundle_groups));
+
+	attr = pjmedia_sdp_attr_create(pool, "msid-semantic", pj_cstr(&stmp, "WMS *"));
+	pjmedia_sdp_attr_add(&answer->attr_count, answer->attr, attr);
+
+	/* Build the bundle group layout so we can then add it to the SDP */
+	for (index = 0; index < AST_VECTOR_SIZE(&session->pending_media_state->sessions); ++index) {
+		struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);
+
+		/* If this stream is not part of a bundle group we can't add it */
+		if (session_media->bundle_group == -1) {
+			continue;
+		}
+
+		bundle_group = &bundle_groups[session_media->bundle_group];
+
+		/* If this is the first mid then we need to allocate the attribute string and place BUNDLE in front */
+		if (!bundle_group->mids[0]) {
+			bundle_group->mids[0] = session_media->mid;
+			bundle_group->attr_string = ast_str_create(64);
+			if (!bundle_group->attr_string) {
+				continue;
+			}
+
+			ast_str_set(&bundle_group->attr_string, -1, "BUNDLE %s", session_media->mid);
+			continue;
+		}
+
+		for (mid_id = 1; mid_id < PJMEDIA_MAX_SDP_MEDIA; ++mid_id) {
+			if (!bundle_group->mids[mid_id]) {
+				bundle_group->mids[mid_id] = session_media->mid;
+				ast_str_append(&bundle_group->attr_string, -1, " %s", session_media->mid);
+				break;
+			} else if (!strcmp(bundle_group->mids[mid_id], session_media->mid)) {
+				break;
+			}
+		}
+	}
+
+	/* Add all bundle groups that have mids to the SDP */
+	for (index = 0; index < PJMEDIA_MAX_SDP_MEDIA; ++index) {
+		bundle_group = &bundle_groups[index];
+
+		if (!bundle_group->attr_string) {
+			continue;
+		}
+
+		attr = pjmedia_sdp_attr_create(pool, "group", pj_cstr(&stmp, ast_str_buffer(bundle_group->attr_string)));
+		pjmedia_sdp_attr_add(&answer->attr_count, answer->attr, attr);
+
+		ast_free(bundle_group->attr_string);
+	}
+
+	return 0;
+}
+
 static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer)
 {
 	static const pj_str_t STR_IN = { "IN", 2 };
@@ -3485,6 +3675,7 @@
 	for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) {
 		struct ast_sip_session_media *session_media;
 		struct ast_stream *stream;
+		unsigned int streams = local->media_count;
 
 		/* This code does not enforce any maximum stream count limitations as that is done on either
 		 * the handling of an incoming SDP offer or on the handling of a session refresh.
@@ -3501,12 +3692,30 @@
 			return NULL;
 		}
 
+		/* If a stream was actually added then add any additional details */
+		if (streams != local->media_count) {
+			pjmedia_sdp_media *media = local->media[streams];
+			pj_str_t stmp;
+			pjmedia_sdp_attr *attr;
+
+			/* Add the media identifier if present */
+			if (!ast_strlen_zero(session_media->mid)) {
+				attr = pjmedia_sdp_attr_create(inv->pool_prov, "mid", pj_cstr(&stmp, session_media->mid));
+				pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
+			}
+		}
+
 		/* Ensure that we never exceed the maximum number of streams PJMEDIA will allow. */
 		if (local->media_count == PJMEDIA_MAX_SDP_MEDIA) {
 			break;
 		}
 	}
 
+	/* Add any bundle groups that are present on the media state */
+	if (add_bundle_groups(session, inv->pool_prov, local)) {
+		return NULL;
+	}
+
 	/* Use the connection details of an available media if possible for SDP level */
 	for (stream = 0; stream < local->media_count; stream++) {
 		if (!local->media[stream]->conn) {
diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c
index a032bb1..877d48f 100644
--- a/res/res_pjsip_t38.c
+++ b/res/res_pjsip_t38.c
@@ -880,11 +880,20 @@
 
 static struct ast_frame *media_session_udptl_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
 {
+	struct ast_frame *frame;
+
 	if (!session_media->udptl) {
 		return &ast_null_frame;
 	}
 
-	return ast_udptl_read(session_media->udptl);
+	frame = ast_udptl_read(session_media->udptl);
+	if (!frame) {
+		return NULL;
+	}
+
+	frame->stream_num = session_media->stream_num;
+
+	return frame;
 }
 
 static int media_session_udptl_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame)
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 01dfe76..4bfbf9b 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -68,6 +68,7 @@
 #include "asterisk/module.h"
 #include "asterisk/rtp_engine.h"
 #include "asterisk/smoother.h"
+#include "asterisk/uuid.h"
 #include "asterisk/test.h"
 
 #define MAX_TIMESTAMP_SKEW	640
@@ -238,6 +239,14 @@
 };
 #endif
 
+/*! \brief Structure used for mapping an incoming SSRC to an RTP instance */
+struct rtp_ssrc_mapping {
+	/*! \brief The received SSRC */
+	unsigned int ssrc;
+	/*! \brief The RTP instance this SSRC belongs to*/
+	struct ast_rtp_instance *instance;
+};
+
 /*! \brief RTP session description */
 struct ast_rtp {
 	int s;
@@ -245,6 +254,7 @@
 	struct ast_frame f;
 	unsigned char rawdata[8192 + AST_FRIENDLY_OFFSET];
 	unsigned int ssrc;		/*!< Synchronization source, RFC 3550, page 10. */
+	char cname[AST_UUID_STR_LEN]; /*!< Our local CNAME */
 	unsigned int themssrc;		/*!< Their SSRC */
 	unsigned int rxssrc;
 	unsigned int lastts;
@@ -300,6 +310,11 @@
 	void *data;
 	struct ast_rtcp *rtcp;
 	struct ast_rtp *bridged;        /*!< Who we are Packet bridged to */
+
+	struct ast_rtp_instance *bundled; /*!< The RTP instance we are bundled to */
+	int stream_num; /*!< Stream num for this RTP instance */
+	AST_VECTOR(, struct rtp_ssrc_mapping) ssrc_mapping; /*!< Mappings of SSRC to RTP instances */
+	struct ast_sockaddr bind_address; /*!< Requested bind address for the sockets */
 
 	enum strict_rtp_state strict_rtp_state; /*!< Current state that strict RTP protection is in */
 	struct ast_sockaddr strict_rtp_address;  /*!< Remote address information for strict RTP purposes */
@@ -477,6 +492,9 @@
 static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level);
 static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance);
 static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance);
+static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc);
+static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num);
+static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
 
 #ifdef HAVE_OPENSSL_SRTP
 static int ast_rtp_activate(struct ast_rtp_instance *instance);
@@ -1907,6 +1925,9 @@
 #endif
 	.ssrc_get = ast_rtp_get_ssrc,
 	.cname_get = ast_rtp_get_cname,
+	.set_remote_ssrc = ast_rtp_set_remote_ssrc,
+	.set_stream_num = ast_rtp_set_stream_num,
+	.bundle = ast_rtp_bundle,
 };
 
 #ifdef HAVE_OPENSSL_SRTP
@@ -1943,6 +1964,23 @@
 }
 #endif
 
+#ifdef HAVE_OPENSSL_SRTP
+static void dtls_perform_setup(struct dtls_details *dtls)
+{
+	if (!dtls->ssl || !SSL_is_init_finished(dtls->ssl)) {
+		return;
+	}
+
+	SSL_clear(dtls->ssl);
+	if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
+		SSL_set_accept_state(dtls->ssl);
+	} else {
+		SSL_set_connect_state(dtls->ssl);
+	}
+	dtls->connection = AST_RTP_DTLS_CONNECTION_NEW;
+}
+#endif
+
 #ifdef HAVE_PJPROJECT
 static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq);
 
@@ -1971,9 +2009,12 @@
 	}
 
 #ifdef HAVE_OPENSSL_SRTP
+
+	dtls_perform_setup(&rtp->dtls);
 	dtls_perform_handshake(instance, &rtp->dtls, 0);
 
 	if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
+		dtls_perform_setup(&rtp->rtcp->dtls);
 		dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1);
 	}
 #endif
@@ -2241,14 +2282,90 @@
 	return 0;
 }
 
-static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp)
+static int dtls_srtp_add_local_ssrc(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp, unsigned int ssrc, int set_remote_policy)
 {
 	unsigned char material[SRTP_MASTER_LEN * 2];
 	unsigned char *local_key, *local_salt, *remote_key, *remote_salt;
 	struct ast_srtp_policy *local_policy, *remote_policy = NULL;
-	struct ast_rtp_instance_stats stats = { 0, };
 	int res = -1;
 	struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls;
+
+	/* Produce key information and set up SRTP */
+	if (!SSL_export_keying_material(dtls->ssl, material, SRTP_MASTER_LEN * 2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) {
+		ast_log(LOG_WARNING, "Unable to extract SRTP keying material from DTLS-SRTP negotiation on RTP instance '%p'\n",
+			instance);
+		return -1;
+	}
+
+	/* Whether we are acting as a server or client determines where the keys/salts are */
+	if (rtp->dtls.dtls_setup == AST_RTP_DTLS_SETUP_ACTIVE) {
+		local_key = material;
+		remote_key = local_key + SRTP_MASTER_KEY_LEN;
+		local_salt = remote_key + SRTP_MASTER_KEY_LEN;
+		remote_salt = local_salt + SRTP_MASTER_SALT_LEN;
+	} else {
+		remote_key = material;
+		local_key = remote_key + SRTP_MASTER_KEY_LEN;
+		remote_salt = local_key + SRTP_MASTER_KEY_LEN;
+		local_salt = remote_salt + SRTP_MASTER_SALT_LEN;
+	}
+
+	if (!(local_policy = res_srtp_policy->alloc())) {
+		return -1;
+	}
+
+	if (res_srtp_policy->set_master_key(local_policy, local_key, SRTP_MASTER_KEY_LEN, local_salt, SRTP_MASTER_SALT_LEN) < 0) {
+		ast_log(LOG_WARNING, "Could not set key/salt information on local policy of '%p' when setting up DTLS-SRTP\n", rtp);
+		goto error;
+	}
+
+	if (res_srtp_policy->set_suite(local_policy, rtp->suite)) {
+		ast_log(LOG_WARNING, "Could not set suite to '%u' on local policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp);
+		goto error;
+	}
+
+	res_srtp_policy->set_ssrc(local_policy, ssrc, 0);
+
+	if (set_remote_policy) {
+		if (!(remote_policy = res_srtp_policy->alloc())) {
+			goto error;
+		}
+
+		if (res_srtp_policy->set_master_key(remote_policy, remote_key, SRTP_MASTER_KEY_LEN, remote_salt, SRTP_MASTER_SALT_LEN) < 0) {
+			ast_log(LOG_WARNING, "Could not set key/salt information on remote policy of '%p' when setting up DTLS-SRTP\n", rtp);
+			goto error;
+		}
+
+		if (res_srtp_policy->set_suite(remote_policy, rtp->suite)) {
+			ast_log(LOG_WARNING, "Could not set suite to '%u' on remote policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp);
+			goto error;
+		}
+
+		res_srtp_policy->set_ssrc(remote_policy, 0, 1);
+	}
+
+	if (ast_rtp_instance_add_srtp_policy(instance, remote_policy, local_policy, rtcp)) {
+		ast_log(LOG_WARNING, "Could not set policies when setting up DTLS-SRTP on '%p'\n", rtp);
+		goto error;
+	}
+
+	res = 0;
+
+error:
+	/* policy->destroy() called even on success to release local reference to these resources */
+	res_srtp_policy->destroy(local_policy);
+
+	if (remote_policy) {
+		res_srtp_policy->destroy(remote_policy);
+	}
+
+	return res;
+}
+
+static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp)
+{
+	struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls;
+	int index;
 
 	/* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
 	if (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_FINGERPRINT) {
@@ -2287,93 +2404,27 @@
 		X509_free(certificate);
 	}
 
-	/* Ensure that certificate verification was successful */
-	if ((rtp->dtls_verify & AST_RTP_DTLS_VERIFY_CERTIFICATE) && SSL_get_verify_result(dtls->ssl) != X509_V_OK) {
-		ast_log(LOG_WARNING, "Peer certificate on RTP instance '%p' failed verification test\n",
-			instance);
+	if (dtls_srtp_add_local_ssrc(rtp, srtp, instance, rtcp, ast_rtp_instance_get_ssrc(instance), 1)) {
 		return -1;
 	}
 
-	/* Produce key information and set up SRTP */
-	if (!SSL_export_keying_material(dtls->ssl, material, SRTP_MASTER_LEN * 2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) {
-		ast_log(LOG_WARNING, "Unable to extract SRTP keying material from DTLS-SRTP negotiation on RTP instance '%p'\n",
-			instance);
-		return -1;
-	}
+	for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) {
+		struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index);
 
-	/* Whether we are acting as a server or client determines where the keys/salts are */
-	if (rtp->dtls.dtls_setup == AST_RTP_DTLS_SETUP_ACTIVE) {
-		local_key = material;
-		remote_key = local_key + SRTP_MASTER_KEY_LEN;
-		local_salt = remote_key + SRTP_MASTER_KEY_LEN;
-		remote_salt = local_salt + SRTP_MASTER_SALT_LEN;
-	} else {
-		remote_key = material;
-		local_key = remote_key + SRTP_MASTER_KEY_LEN;
-		remote_salt = local_key + SRTP_MASTER_KEY_LEN;
-		local_salt = remote_salt + SRTP_MASTER_SALT_LEN;
-	}
-
-	if (!(local_policy = res_srtp_policy->alloc())) {
-		return -1;
-	}
-
-	if (res_srtp_policy->set_master_key(local_policy, local_key, SRTP_MASTER_KEY_LEN, local_salt, SRTP_MASTER_SALT_LEN) < 0) {
-		ast_log(LOG_WARNING, "Could not set key/salt information on local policy of '%p' when setting up DTLS-SRTP\n", rtp);
-		goto error;
-	}
-
-	if (res_srtp_policy->set_suite(local_policy, rtp->suite)) {
-		ast_log(LOG_WARNING, "Could not set suite to '%u' on local policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp);
-		goto error;
-	}
-
-	if (ast_rtp_instance_get_stats(instance, &stats, AST_RTP_INSTANCE_STAT_LOCAL_SSRC)) {
-		goto error;
-	}
-
-	res_srtp_policy->set_ssrc(local_policy, stats.local_ssrc, 0);
-
-	if (!(remote_policy = res_srtp_policy->alloc())) {
-		goto error;
-	}
-
-	if (res_srtp_policy->set_master_key(remote_policy, remote_key, SRTP_MASTER_KEY_LEN, remote_salt, SRTP_MASTER_SALT_LEN) < 0) {
-		ast_log(LOG_WARNING, "Could not set key/salt information on remote policy of '%p' when setting up DTLS-SRTP\n", rtp);
-		goto error;
-	}
-
-	if (res_srtp_policy->set_suite(remote_policy, rtp->suite)) {
-		ast_log(LOG_WARNING, "Could not set suite to '%u' on remote policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp);
-		goto error;
-	}
-
-	res_srtp_policy->set_ssrc(remote_policy, 0, 1);
-
-	if (ast_rtp_instance_add_srtp_policy(instance, remote_policy, local_policy, rtcp)) {
-		ast_log(LOG_WARNING, "Could not set policies when setting up DTLS-SRTP on '%p'\n", rtp);
-		goto error;
+		if (dtls_srtp_add_local_ssrc(rtp, srtp, instance, rtcp, ast_rtp_instance_get_ssrc(mapping->instance), 0)) {
+			return -1;
+		}
 	}
 
 	if (rtp->rekey) {
 		ao2_ref(instance, +1);
 		if ((rtp->rekeyid = ast_sched_add(rtp->sched, rtp->rekey * 1000, dtls_srtp_renegotiate, instance)) < 0) {
 			ao2_ref(instance, -1);
-			goto error;
+			return -1;
 		}
 	}
 
-	res = 0;
-
-error:
-	/* policy->destroy() called even on success to release local reference to these resources */
-	res_srtp_policy->destroy(local_policy);
-
-	if (remote_policy) {
-		res_srtp_policy->destroy(remote_policy);
-	}
-
-	return res;
+	return 0;
 }
 #endif
 
@@ -2569,7 +2620,9 @@
 	int len = size;
 	void *temp = buf;
 	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
-	struct ast_srtp *srtp = ast_rtp_instance_get_srtp(instance, rtcp);
+	struct ast_rtp_instance *transport = rtp->bundled ? rtp->bundled : instance;
+	struct ast_rtp *transport_rtp = ast_rtp_instance_get_data(transport);
+	struct ast_srtp *srtp = ast_rtp_instance_get_srtp(transport, rtcp);
 	int res;
 
 	*via_ice = 0;
@@ -2579,20 +2632,24 @@
 	}
 
 #ifdef HAVE_PJPROJECT
-	if (rtp->ice) {
+	if (transport_rtp->ice) {
 		pj_status_t status;
 		struct ice_wrap *ice;
 
 		pj_thread_register_check();
 
 		/* Release the instance lock to avoid deadlock with PJPROJECT group lock */
-		ice = rtp->ice;
+		ice = transport_rtp->ice;
 		ao2_ref(ice, +1);
-		ao2_unlock(instance);
+		if (instance == transport) {
+			ao2_unlock(instance);
+		}
 		status = pj_ice_sess_send_data(ice->real_ice,
 			rtcp ? AST_RTP_ICE_COMPONENT_RTCP : AST_RTP_ICE_COMPONENT_RTP, temp, len);
 		ao2_ref(ice, -1);
-		ao2_lock(instance);
+		if (instance == transport) {
+			ao2_lock(instance);
+		}
 		if (status == PJ_SUCCESS) {
 			*via_ice = 1;
 			return len;
@@ -2600,7 +2657,7 @@
 	}
 #endif
 
-	res = ast_sendto(rtcp ? rtp->rtcp->s : rtp->s, temp, len, flags, sa);
+	res = ast_sendto(rtcp ? transport_rtp->rtcp->s : transport_rtp->s, temp, len, flags, sa);
 	if (res > 0) {
 		ast_rtp_instance_set_last_tx(instance, time(NULL));
 	}
@@ -2990,22 +3047,10 @@
 }
 #endif
 
-/*! \pre instance is locked */
-static int ast_rtp_new(struct ast_rtp_instance *instance,
-		       struct ast_sched_context *sched, struct ast_sockaddr *addr,
-		       void *data)
+static int rtp_allocate_transport(struct ast_rtp_instance *instance, struct ast_rtp *rtp)
 {
-	struct ast_rtp *rtp = NULL;
 	int x, startplace;
 
-	/* Create a new RTP structure to hold all of our data */
-	if (!(rtp = ast_calloc(1, sizeof(*rtp)))) {
-		return -1;
-	}
-
-	/* Set default parameters on the newly created RTP structure */
-	rtp->ssrc = ast_random();
-	rtp->seqno = ast_random() & 0x7fff;
 	rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_LEARN : STRICT_RTP_OPEN);
 	if (strictrtp) {
 		rtp_learning_seq_init(&rtp->rtp_source_learn, (uint16_t)rtp->seqno);
@@ -3015,10 +3060,9 @@
 	/* Create a new socket for us to listen on and use */
 	if ((rtp->s =
 	     create_new_socket("RTP",
-			       ast_sockaddr_is_ipv4(addr) ? AF_INET  :
-			       ast_sockaddr_is_ipv6(addr) ? AF_INET6 : -1)) < 0) {
+			       ast_sockaddr_is_ipv4(&rtp->bind_address) ? AF_INET  :
+			       ast_sockaddr_is_ipv6(&rtp->bind_address) ? AF_INET6 : -1)) < 0) {
 		ast_log(LOG_WARNING, "Failed to create a new socket for RTP instance '%p'\n", instance);
-		ast_free(rtp);
 		return -1;
 	}
 
@@ -3028,11 +3072,11 @@
 	startplace = x;
 
 	for (;;) {
-		ast_sockaddr_set_port(addr, x);
+		ast_sockaddr_set_port(&rtp->bind_address, x);
 		/* Try to bind, this will tell us whether the port is available or not */
-		if (!ast_bind(rtp->s, addr)) {
+		if (!ast_bind(rtp->s, &rtp->bind_address)) {
 			ast_debug(1, "Allocated port %d for RTP instance '%p'\n", x, instance);
-			ast_rtp_instance_set_local_address(instance, addr);
+			ast_rtp_instance_set_local_address(instance, &rtp->bind_address);
 			break;
 		}
 
@@ -3045,7 +3089,6 @@
 		if (x == startplace || (errno != EADDRINUSE && errno != EACCES)) {
 			ast_log(LOG_ERROR, "Oh dear... we couldn't allocate a port for RTP instance '%p'\n", instance);
 			close(rtp->s);
-			ast_free(rtp);
 			return -1;
 		}
 	}
@@ -3056,40 +3099,30 @@
 
 	generate_random_string(rtp->local_ufrag, sizeof(rtp->local_ufrag));
 	generate_random_string(rtp->local_passwd, sizeof(rtp->local_passwd));
-#endif
-	ast_rtp_instance_set_data(instance, rtp);
-#ifdef HAVE_PJPROJECT
+
 	/* Create an ICE session for ICE negotiation */
 	if (icesupport) {
 		rtp->ice_num_components = 2;
-		ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(addr), x, instance);
-		if (ice_create(instance, addr, x, 0)) {
+		ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->bind_address), x, instance);
+		if (ice_create(instance, &rtp->bind_address, x, 0)) {
 			ast_log(LOG_NOTICE, "Failed to create ICE session\n");
 		} else {
 			rtp->ice_port = x;
-			ast_sockaddr_copy(&rtp->ice_original_rtp_addr, addr);
+			ast_sockaddr_copy(&rtp->ice_original_rtp_addr, &rtp->bind_address);
 		}
 	}
 #endif
-	/* Record any information we may need */
-	rtp->sched = sched;
 
 #ifdef HAVE_OPENSSL_SRTP
 	rtp->rekeyid = -1;
 	rtp->dtls.timeout_timer = -1;
 #endif
 
-	rtp->f.subclass.format = ao2_bump(ast_format_none);
-	rtp->lastrxformat = ao2_bump(ast_format_none);
-	rtp->lasttxformat = ao2_bump(ast_format_none);
-
 	return 0;
 }
 
-/*! \pre instance is locked */
-static int ast_rtp_destroy(struct ast_rtp_instance *instance)
+static void rtp_deallocate_transport(struct ast_rtp_instance *instance, struct ast_rtp *rtp)
 {
-	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
 #ifdef HAVE_PJPROJECT
 	struct timeval wait = ast_tvadd(ast_tvnow(), ast_samp2tv(TURN_STATE_WAIT_TIME, 1000));
 	struct timespec ts = { .tv_sec = wait.tv_sec, .tv_nsec = wait.tv_usec * 1000, };
@@ -3099,35 +3132,16 @@
 	ast_rtp_dtls_stop(instance);
 #endif
 
-	/* Destroy the smoother that was smoothing out audio if present */
-	if (rtp->smoother) {
-		ast_smoother_free(rtp->smoother);
-	}
-
 	/* Close our own socket so we no longer get packets */
 	if (rtp->s > -1) {
 		close(rtp->s);
+		rtp->s = -1;
 	}
 
 	/* Destroy RTCP if it was being used */
-	if (rtp->rtcp) {
-		/*
-		 * It is not possible for there to be an active RTCP scheduler
-		 * entry at this point since it holds a reference to the
-		 * RTP instance while it's active.
-		 */
+	if (rtp->rtcp && rtp->rtcp->s > -1) {
 		close(rtp->rtcp->s);
-		ast_free(rtp->rtcp->local_addr_str);
-		ast_free(rtp->rtcp);
-	}
-
-	/* Destroy RED if it was being used */
-	if (rtp->red) {
-		ao2_unlock(instance);
-		AST_SCHED_DEL(rtp->sched, rtp->red->schedid);
-		ao2_lock(instance);
-		ast_free(rtp->red);
-		rtp->red = NULL;
+		rtp->rtcp->s = -1;
 	}
 
 #ifdef HAVE_PJPROJECT
@@ -3148,6 +3162,7 @@
 		while (rtp->turn_state != PJ_TURN_STATE_DESTROYING) {
 			ast_cond_timedwait(&rtp->cond, ao2_object_get_lockaddr(instance), &ts);
 		}
+		rtp->turn_rtp = NULL;
 	}
 
 	/* Destroy the RTCP TURN relay if being used */
@@ -3161,6 +3176,7 @@
 		while (rtp->turn_state != PJ_TURN_STATE_DESTROYING) {
 			ast_cond_timedwait(&rtp->cond, ao2_object_get_lockaddr(instance), &ts);
 		}
+		rtp->turn_rtcp = NULL;
 	}
 
 	/* Destroy any ICE session */
@@ -3169,10 +3185,12 @@
 	/* Destroy any candidates */
 	if (rtp->ice_local_candidates) {
 		ao2_ref(rtp->ice_local_candidates, -1);
+		rtp->ice_local_candidates = NULL;
 	}
 
 	if (rtp->ice_active_remote_candidates) {
 		ao2_ref(rtp->ice_active_remote_candidates, -1);
+		rtp->ice_active_remote_candidates = NULL;
 	}
 
 	if (rtp->ioqueue) {
@@ -3184,17 +3202,109 @@
 		ao2_unlock(instance);
 		rtp_ioqueue_thread_remove(rtp->ioqueue);
 		ao2_lock(instance);
+		rtp->ioqueue = NULL;
 	}
 #endif
+}
+
+/*! \pre instance is locked */
+static int ast_rtp_new(struct ast_rtp_instance *instance,
+		       struct ast_sched_context *sched, struct ast_sockaddr *addr,
+		       void *data)
+{
+	struct ast_rtp *rtp = NULL;
+
+	/* Create a new RTP structure to hold all of our data */
+	if (!(rtp = ast_calloc(1, sizeof(*rtp)))) {
+		return -1;
+	}
+
+	/* Set default parameters on the newly created RTP structure */
+	rtp->ssrc = ast_random();
+	ast_uuid_generate_str(rtp->cname, sizeof(rtp->cname));
+	rtp->seqno = ast_random() & 0x7fff;
+	rtp->sched = sched;
+	ast_sockaddr_copy(&rtp->bind_address, addr);
+
+	/* Transport creation operations can grab the RTP data from the instance, so set it */
+	ast_rtp_instance_set_data(instance, rtp);
+
+	if (rtp_allocate_transport(instance, rtp)) {
+		ast_free(rtp);
+		return -1;
+	}
+
+	rtp->f.subclass.format = ao2_bump(ast_format_none);
+	rtp->lastrxformat = ao2_bump(ast_format_none);
+	rtp->lasttxformat = ao2_bump(ast_format_none);
+	rtp->stream_num = -1;
+	AST_VECTOR_INIT(&rtp->ssrc_mapping, 1);
+
+	return 0;
+}
+
+/*!
+ * \brief SSRC mapping comparator for AST_VECTOR_REMOVE_CMP_UNORDERED()
+ *
+ * \param elem Element to compare against
+ * \param value Value to compare with the vector element.
+ *
+ * \return 0 if element does not match.
+ * \return Non-zero if element matches.
+ */
+#define SSRC_MAPPING_ELEM_CMP(elem, value) ((elem).ssrc == (value))
+
+/*! \pre instance is locked */
+static int ast_rtp_destroy(struct ast_rtp_instance *instance)
+{
+	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
+	if (rtp->bundled) {
+		struct ast_rtp *bundled_rtp;
+
+		/* We can't hold our instance lock while removing ourselves from the parent */
+		ao2_unlock(instance);
+
+		ao2_lock(rtp->bundled);
+		bundled_rtp = ast_rtp_instance_get_data(rtp->bundled);
+		AST_VECTOR_REMOVE_CMP_UNORDERED(&bundled_rtp->ssrc_mapping, rtp->themssrc, SSRC_MAPPING_ELEM_CMP, AST_VECTOR_ELEM_CLEANUP_NOOP);
+		ao2_unlock(rtp->bundled);
+
+		ao2_lock(instance);
+		ao2_ref(rtp->bundled, -1);
+	}
+
+	rtp_deallocate_transport(instance, rtp);
+
+	/* Destroy the smoother that was smoothing out audio if present */
+	if (rtp->smoother) {
+		ast_smoother_free(rtp->smoother);
+	}
+
+	/* Destroy RTCP if it was being used */
+	if (rtp->rtcp) {
+		/*
+		 * It is not possible for there to be an active RTCP scheduler
+		 * entry at this point since it holds a reference to the
+		 * RTP instance while it's active.
+		 */
+		ast_free(rtp->rtcp->local_addr_str);
+		ast_free(rtp->rtcp);
+	}
+
+	/* Destroy RED if it was being used */
+	if (rtp->red) {
+		ao2_unlock(instance);
+		AST_SCHED_DEL(rtp->sched, rtp->red->schedid);
+		ao2_lock(instance);
+		ast_free(rtp->red);
+		rtp->red = NULL;
+	}
 
 	ao2_cleanup(rtp->lasttxformat);
 	ao2_cleanup(rtp->lastrxformat);
 	ao2_cleanup(rtp->f.subclass.format);
-
-#ifdef HAVE_PJPROJECT
-	/* Destroy synchronization items */
-	ast_cond_destroy(&rtp->cond);
-#endif
+	AST_VECTOR_FREE(&rtp->ssrc_mapping);
 
 	/* Finally destroy ourselves */
 	ast_free(rtp);
@@ -3444,21 +3554,18 @@
 	struct ast_srtp *rtcp_srtp = ast_rtp_instance_get_srtp(instance, 1);
 	unsigned int ssrc = ast_random();
 
-	if (!rtp->lastts) {
-		ast_debug(3, "Not changing SSRC since we haven't sent any RTP yet\n");
-		return;
-	}
+	if (rtp->lastts) {
+		/* We simply set this bit so that the next packet sent will have the marker bit turned on */
+		ast_set_flag(rtp, FLAG_NEED_MARKER_BIT);
 
-	/* We simply set this bit so that the next packet sent will have the marker bit turned on */
-	ast_set_flag(rtp, FLAG_NEED_MARKER_BIT);
+		ast_debug(3, "Changing ssrc from %u to %u due to a source change\n", rtp->ssrc, ssrc);
 
-	ast_debug(3, "Changing ssrc from %u to %u due to a source change\n", rtp->ssrc, ssrc);
-
-	if (srtp) {
-		ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc);
-		res_srtp->change_source(srtp, rtp->ssrc, ssrc);
-		if (rtcp_srtp != srtp) {
-			res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc);
+		if (srtp) {
+			ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc);
+			res_srtp->change_source(srtp, rtp->ssrc, ssrc);
+			if (rtcp_srtp != srtp) {
+				res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc);
+			}
 		}
 	}
 
@@ -3573,14 +3680,13 @@
 	struct timeval now;
 	unsigned int now_lsw;
 	unsigned int now_msw;
-	unsigned int *rtcpheader;
+	unsigned char *rtcpheader;
 	unsigned int lost_packets;
 	int fraction_lost;
 	struct timeval dlsr = { 0, };
-	char bdata[512];
+	unsigned char bdata[512] = "";
 	int rate = rtp_get_rate(rtp->f.subclass.format);
 	int ice;
-	int header_offset = 0;
 	struct ast_sockaddr remote_address = { { 0, } };
 	struct ast_rtp_rtcp_report_block *report_block = NULL;
 	RAII_VAR(struct ast_rtp_rtcp_report *, rtcp_report,
@@ -3634,38 +3740,42 @@
 		}
 	}
 	timeval2ntp(rtcp_report->sender_information.ntp_timestamp, &now_msw, &now_lsw);
-	rtcpheader = (unsigned int *)bdata;
-	rtcpheader[1] = htonl(rtcp_report->ssrc);            /* Our SSRC */
+	rtcpheader = bdata;
+	put_unaligned_uint32(rtcpheader + 4, htonl(rtcp_report->ssrc)); /* Our SSRC */
 	len += 8;
 	if (sr) {
-		header_offset = 5;
-		rtcpheader[2] = htonl(now_msw);                 /* now, MSW. gettimeofday() + SEC_BETWEEN_1900_AND_1970*/
-		rtcpheader[3] = htonl(now_lsw);                 /* now, LSW */
-		rtcpheader[4] = htonl(rtcp_report->sender_information.rtp_timestamp);
-		rtcpheader[5] = htonl(rtcp_report->sender_information.packet_count);
-		rtcpheader[6] = htonl(rtcp_report->sender_information.octet_count);
+		put_unaligned_uint32(rtcpheader + len, htonl(now_msw)); /* now, MSW. gettimeofday() + SEC_BETWEEN_1900_AND_1970*/
+		put_unaligned_uint32(rtcpheader + len + 4, htonl(now_lsw)); /* now, LSW */
+		put_unaligned_uint32(rtcpheader + len + 8, htonl(rtcp_report->sender_information.rtp_timestamp));
+		put_unaligned_uint32(rtcpheader + len + 12, htonl(rtcp_report->sender_information.packet_count));
+		put_unaligned_uint32(rtcpheader + len + 16, htonl(rtcp_report->sender_information.octet_count));
 		len += 20;
 	}
 	if (report_block) {
-		rtcpheader[2 + header_offset] = htonl(report_block->source_ssrc);     /* Their SSRC */
-		rtcpheader[3 + header_offset] = htonl((report_block->lost_count.fraction << 24) | report_block->lost_count.packets);
-		rtcpheader[4 + header_offset] = htonl(report_block->highest_seq_no);
-		rtcpheader[5 + header_offset] = htonl(report_block->ia_jitter);
-		rtcpheader[6 + header_offset] = htonl(report_block->lsr);
-		rtcpheader[7 + header_offset] = htonl(report_block->dlsr);
+		put_unaligned_uint32(rtcpheader + len, htonl(report_block->source_ssrc)); /* Their SSRC */
+		put_unaligned_uint32(rtcpheader + len + 4, htonl((report_block->lost_count.fraction << 24) | report_block->lost_count.packets));
+		put_unaligned_uint32(rtcpheader + len + 8, htonl(report_block->highest_seq_no));
+		put_unaligned_uint32(rtcpheader + len + 12, htonl(report_block->ia_jitter));
+		put_unaligned_uint32(rtcpheader + len + 16, htonl(report_block->lsr));
+		put_unaligned_uint32(rtcpheader + len + 20, htonl(report_block->dlsr));
 		len += 24;
 	}
-	rtcpheader[0] = htonl((2 << 30) | (rtcp_report->reception_report_count << 24)
-					| ((sr ? RTCP_PT_SR : RTCP_PT_RR) << 16) | ((len/4)-1));
 
-	/* Insert SDES here. Probably should make SDES text equal to mimetypes[code].type (not subtype 'cos */
-	/* it can change mid call, and SDES can't) */
-	rtcpheader[len/4]     = htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | 2);
-	rtcpheader[(len/4)+1] = htonl(rtcp_report->ssrc);
-	rtcpheader[(len/4)+2] = htonl(0x01 << 24);
-	len += 12;
+	put_unaligned_uint32(rtcpheader, htonl((2 << 30) | (rtcp_report->reception_report_count << 24)
+					| ((sr ? RTCP_PT_SR : RTCP_PT_RR) << 16) | ((len/4)-1)));
 
-	ast_sockaddr_copy(&remote_address, &rtp->rtcp->them);
+	put_unaligned_uint32(rtcpheader + len, htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | (2 + (AST_UUID_STR_LEN / 4))));
+	put_unaligned_uint32(rtcpheader + len + 4, htonl(rtcp_report->ssrc));
+	put_unaligned_uint16(rtcpheader + len + 8, htonl(0x01 << 24));
+	put_unaligned_uint16(rtcpheader + len + 9, htonl(AST_UUID_STR_LEN << 24));
+	memcpy(rtcpheader + len + 10, rtp->cname, AST_UUID_STR_LEN);
+	len += 12 + AST_UUID_STR_LEN;
+
+	if (rtp->bundled) {
+		ast_rtp_instance_get_remote_address(instance, &remote_address);
+	} else {
+		ast_sockaddr_copy(&remote_address, &rtp->rtcp->them);
+	}
 	res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, &remote_address, &ice);
 	if (res < 0) {
 		ast_log(LOG_ERROR, "RTCP %s transmission error to %s, rtcp halted %s\n",
@@ -3942,7 +4052,6 @@
 
 	/* VP8: is this a request to send a RTCP FIR? */
 	if (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_VIDUPDATE) {
-		struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
 		unsigned int *rtcpheader;
 		char bdata[1024];
 		int len = 20;
@@ -3972,7 +4081,7 @@
 		rtcpheader[2] = htonl(rtp->themssrc);
 		rtcpheader[3] = htonl(rtp->themssrc);	/* FCI: SSRC */
 		rtcpheader[4] = htonl(rtp->rtcp->firseq << 24);			/* FCI: Sequence number */
-		res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, &rtp->rtcp->them, &ice);
+		res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, rtp->bundled ? &remote_address : &rtp->rtcp->them, &ice);
 		if (res < 0) {
 			ast_log(LOG_ERROR, "RTCP FIR transmission error: %s\n", strerror(errno));
 		}
@@ -4537,9 +4646,29 @@
 	rtp->rtcp->reported_normdev_lost = reported_normdev_lost_current;
 }
 
+/*! \pre instance is locked */
+static struct ast_rtp_instance *rtp_find_instance_by_ssrc(struct ast_rtp_instance *instance,
+	struct ast_rtp *rtp, unsigned int ssrc)
+{
+	int index;
+	struct ast_rtp_instance *found = instance;
+
+	for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) {
+		struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index);
+
+		if (mapping->ssrc == ssrc) {
+			found = mapping->instance;
+			break;
+		}
+	}
+
+	return found;
+}
+
 static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, const unsigned char *rtcpdata, size_t size, struct ast_sockaddr *addr)
 {
-	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+	struct ast_rtp_instance *transport = instance;
+	struct ast_rtp *transport_rtp = ast_rtp_instance_get_data(instance);
 	unsigned int *rtcpheader = (unsigned int *)(rtcpdata);
 	int packetwords, position = 0;
 	int report_counter = 0;
@@ -4548,13 +4677,13 @@
 
 	packetwords = size / 4;
 
-	if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) {
+	if (ast_rtp_instance_get_prop(transport, AST_RTP_PROPERTY_NAT)) {
 		/* Send to whoever sent to us */
-		if (ast_sockaddr_cmp(&rtp->rtcp->them, addr)) {
-			ast_sockaddr_copy(&rtp->rtcp->them, addr);
+		if (ast_sockaddr_cmp(&transport_rtp->rtcp->them, addr)) {
+			ast_sockaddr_copy(&transport_rtp->rtcp->them, addr);
 			if (rtpdebug) {
 				ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n",
-					  ast_sockaddr_stringify(&rtp->rtcp->them));
+					  ast_sockaddr_stringify(&transport_rtp->rtcp->them));
 			}
 		}
 	}
@@ -4566,6 +4695,8 @@
 		unsigned int length;
 		struct ast_json *message_blob;
 		RAII_VAR(struct ast_rtp_rtcp_report *, rtcp_report, NULL, ao2_cleanup);
+		struct ast_rtp_instance *child;
+		struct ast_rtp *rtp;
 
 		i = position;
 		length = ntohl(rtcpheader[i]);
@@ -4595,6 +4726,21 @@
 							(pt == RTCP_PT_FUR) ? "H.261 FUR" : "Unknown");
 			ast_verbose("Reception reports: %d\n", rc);
 			ast_verbose("SSRC of sender: %u\n", rtcp_report->ssrc);
+		}
+
+		/* Determine the appropriate instance for this */
+		child = rtp_find_instance_by_ssrc(transport, transport_rtp, rtcp_report->ssrc);
+		if (child != transport) {
+			/* It is safe to hold the child lock while holding the parent lock, we guarantee that the locking order
+			 * is always parent->child or that the child lock is not held when acquiring the parent lock.
+			 */
+			ao2_lock(child);
+			instance = child;
+			rtp = ast_rtp_instance_get_data(instance);
+		} else {
+			/* The child is the parent! We don't need to unlock it. */
+			child = NULL;
+			rtp = transport_rtp;
 		}
 
 		i += 2; /* Advance past header and ssrc */
@@ -4632,6 +4778,9 @@
 				/* Don't handle multiple reception reports (rc > 1) yet */
 				report_block = ast_calloc(1, sizeof(*report_block));
 				if (!report_block) {
+					if (child) {
+						ao2_unlock(child);
+					}
 					return &ast_null_frame;
 				}
 				rtcp_report->report_block[report_counter] = report_block;
@@ -4678,8 +4827,8 @@
 			 */
 
 			message_blob = ast_json_pack("{s: s, s: s, s: f}",
-					"from", ast_sockaddr_stringify(&rtp->rtcp->them),
-					"to", rtp->rtcp->local_addr_str,
+					"from", ast_sockaddr_stringify(&transport_rtp->rtcp->them),
+					"to", transport_rtp->rtcp->local_addr_str,
 					"rtt", rtp->rtcp->rtt);
 			ast_rtp_publish_rtcp_message(instance, ast_rtp_rtcp_received_type(),
 					rtcp_report,
@@ -4688,26 +4837,26 @@
 
 			/* Return an AST_FRAME_RTCP frame with the ast_rtp_rtcp_report
 			 * object as a its data */
-			rtp->f.frametype = AST_FRAME_RTCP;
-			rtp->f.data.ptr = rtp->rtcp->frame_buf + AST_FRIENDLY_OFFSET;
-			memcpy(rtp->f.data.ptr, rtcp_report, sizeof(struct ast_rtp_rtcp_report));
-			rtp->f.datalen = sizeof(struct ast_rtp_rtcp_report);
+			transport_rtp->f.frametype = AST_FRAME_RTCP;
+			transport_rtp->f.data.ptr = rtp->rtcp->frame_buf + AST_FRIENDLY_OFFSET;
+			memcpy(transport_rtp->f.data.ptr, rtcp_report, sizeof(struct ast_rtp_rtcp_report));
+			transport_rtp->f.datalen = sizeof(struct ast_rtp_rtcp_report);
 			if (rc > 0) {
 				/* There's always a single report block stored, here */
 				struct ast_rtp_rtcp_report *rtcp_report2;
-				report_block = rtp->f.data.ptr + rtp->f.datalen + sizeof(struct ast_rtp_rtcp_report_block *);
+				report_block = transport_rtp->f.data.ptr + transport_rtp->f.datalen + sizeof(struct ast_rtp_rtcp_report_block *);
 				memcpy(report_block, rtcp_report->report_block[report_counter-1], sizeof(struct ast_rtp_rtcp_report_block));
-				rtcp_report2 = (struct ast_rtp_rtcp_report *)rtp->f.data.ptr;
+				rtcp_report2 = (struct ast_rtp_rtcp_report *)transport_rtp->f.data.ptr;
 				rtcp_report2->report_block[report_counter-1] = report_block;
-				rtp->f.datalen += sizeof(struct ast_rtp_rtcp_report_block);
+				transport_rtp->f.datalen += sizeof(struct ast_rtp_rtcp_report_block);
 			}
-			rtp->f.offset = AST_FRIENDLY_OFFSET;
-			rtp->f.samples = 0;
-			rtp->f.mallocd = 0;
-			rtp->f.delivery.tv_sec = 0;
-			rtp->f.delivery.tv_usec = 0;
-			rtp->f.src = "RTP";
-			f = &rtp->f;
+			transport_rtp->f.offset = AST_FRIENDLY_OFFSET;
+			transport_rtp->f.samples = 0;
+			transport_rtp->f.mallocd = 0;
+			transport_rtp->f.delivery.tv_sec = 0;
+			transport_rtp->f.delivery.tv_usec = 0;
+			transport_rtp->f.src = "RTP";
+			f = &transport_rtp->f;
 			break;
 		case RTCP_PT_FUR:
 		/* Handle RTCP FIR as FUR */
@@ -4715,34 +4864,38 @@
 			if (rtcp_debug_test_addr(addr)) {
 				ast_verbose("Received an RTCP Fast Update Request\n");
 			}
-			rtp->f.frametype = AST_FRAME_CONTROL;
-			rtp->f.subclass.integer = AST_CONTROL_VIDUPDATE;
-			rtp->f.datalen = 0;
-			rtp->f.samples = 0;
-			rtp->f.mallocd = 0;
-			rtp->f.src = "RTP";
-			f = &rtp->f;
+			transport_rtp->f.frametype = AST_FRAME_CONTROL;
+			transport_rtp->f.subclass.integer = AST_CONTROL_VIDUPDATE;
+			transport_rtp->f.datalen = 0;
+			transport_rtp->f.samples = 0;
+			transport_rtp->f.mallocd = 0;
+			transport_rtp->f.src = "RTP";
+			f = &transport_rtp->f;
 			break;
 		case RTCP_PT_SDES:
 			if (rtcp_debug_test_addr(addr)) {
 				ast_verbose("Received an SDES from %s\n",
-					    ast_sockaddr_stringify(&rtp->rtcp->them));
+					    ast_sockaddr_stringify(&transport_rtp->rtcp->them));
 			}
 			break;
 		case RTCP_PT_BYE:
 			if (rtcp_debug_test_addr(addr)) {
 				ast_verbose("Received a BYE from %s\n",
-					    ast_sockaddr_stringify(&rtp->rtcp->them));
+					    ast_sockaddr_stringify(&transport_rtp->rtcp->them));
 			}
 			break;
 		default:
 			ast_debug(1, "Unknown RTCP packet (pt=%d) received from %s\n",
-				  pt, ast_sockaddr_stringify(&rtp->rtcp->them));
+				  pt, ast_sockaddr_stringify(&transport_rtp->rtcp->them));
 			break;
 		}
 		position += (length + 1);
+		rtp->rtcp->rtcp_info = 1;
+
+		if (child) {
+			ao2_unlock(child);
+		}
 	}
-	rtp->rtcp->rtcp_info = 1;
 
 	return f;
 
@@ -4928,11 +5081,19 @@
 	return 0;
 }
 
+static void rtp_instance_unlock(struct ast_rtp_instance *instance)
+{
+	if (instance) {
+		ao2_unlock(instance);
+	}
+}
+
 /*! \pre instance is locked */
 static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtcp)
 {
 	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
 	struct ast_rtp_instance *instance1;
+	RAII_VAR(struct ast_rtp_instance *, child, NULL, rtp_instance_unlock);
 	struct ast_sockaddr addr;
 	int res, hdrlen = 12, version, payloadtype, padding, mark, ext, cc, prev_seqno;
 	unsigned char *read_area = rtp->rawdata + AST_FRIENDLY_OFFSET;
@@ -4948,11 +5109,6 @@
 			return ast_rtcp_read(instance);
 		}
 		return &ast_null_frame;
-	}
-
-	/* If we are currently sending DTMF to the remote party send a continuation packet */
-	if (rtp->sending_digit) {
-		ast_rtp_dtmf_continuation(instance);
 	}
 
 	/* Actually read in the data from the socket */
@@ -5070,15 +5226,37 @@
 		}
 	}
 
+	/* If the version is not what we expected by this point then just drop the packet */
+	if (version != 2) {
+		return &ast_null_frame;
+	}
+
+	/* We use the SSRC to determine what RTP instance this packet is actually for */
+	ssrc = ntohl(rtpheader[2]);
+
+	/* Determine the appropriate instance for this */
+	child = rtp_find_instance_by_ssrc(instance, rtp, ssrc);
+	if (child != instance) {
+		/* It is safe to hold the child lock while holding the parent lock, we guarantee that the locking order
+		 * is always parent->child or that the child lock is not held when acquiring the parent lock.
+		 */
+		ao2_lock(child);
+		instance = child;
+		rtp = ast_rtp_instance_get_data(instance);
+	} else {
+		/* The child is the parent! We don't need to unlock it. */
+		child = NULL;
+	}
+
+	/* If we are currently sending DTMF to the remote party send a continuation packet */
+	if (rtp->sending_digit) {
+		ast_rtp_dtmf_continuation(instance);
+	}
+
 	/* If we are directly bridged to another instance send the audio directly out */
 	instance1 = ast_rtp_instance_get_bridged(instance);
 	if (instance1
 		&& !bridge_p2p_rtp_write(instance, instance1, rtpheader, res, hdrlen)) {
-		return &ast_null_frame;
-	}
-
-	/* If the version is not what we expected by this point then just drop the packet */
-	if (version != 2) {
 		return &ast_null_frame;
 	}
 
@@ -5090,7 +5268,6 @@
 	cc = (seqno & 0xF000000) >> 24;
 	seqno &= 0xffff;
 	timestamp = ntohl(rtpheader[1]);
-	ssrc = ntohl(rtpheader[2]);
 
 	AST_LIST_HEAD_INIT_NOLOCK(&frames);
 	/* Force a marker bit and change SSRC if the SSRC changes */
@@ -5264,6 +5441,7 @@
 	rtp->f.data.ptr = read_area + hdrlen;
 	rtp->f.offset = hdrlen + AST_FRIENDLY_OFFSET;
 	rtp->f.seqno = seqno;
+	rtp->f.stream_num = rtp->stream_num;
 
 	if ((ast_format_cmp(rtp->f.subclass.format, ast_format_t140) == AST_FORMAT_CMP_EQUAL)
 		&& ((int)seqno - (prev_seqno + 1) > 0)
@@ -5525,6 +5703,7 @@
 {
 	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
 	struct ast_sockaddr local;
+	int index;
 
 	ast_rtp_instance_get_local_address(instance, &local);
 	if (!ast_sockaddr_isnull(addr)) {
@@ -5551,6 +5730,13 @@
 
 		ast_free(rtp->rtcp->local_addr_str);
 		rtp->rtcp->local_addr_str = ast_strdup(ast_sockaddr_stringify(&local));
+	}
+
+	/* Update any bundled RTP instances */
+	for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) {
+		struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index);
+
+		ast_rtp_instance_set_remote_address(mapping->instance, addr);
 	}
 
 	rtp->rxseqno = 0;
@@ -5836,42 +6022,104 @@
 /*! \pre instance is locked */
 static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance)
 {
-	/* XXX
-	 *
-	 * Asterisk currently puts a zero-length CNAME value in RTCP SDES items,
-	 * meaning our CNAME will always be an empty string. In future, should
-	 * Asterisk actually start using meaningful CNAMEs, this function will
-	 * need to return that instead of an empty string
-	 */
-	return "";
+	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
+	return rtp->cname;
 }
 
-#ifdef HAVE_OPENSSL_SRTP
-static void dtls_perform_setup(struct dtls_details *dtls)
+static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc)
 {
-	if (!dtls->ssl || !SSL_is_init_finished(dtls->ssl)) {
+	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
+	if (rtp->themssrc) {
 		return;
 	}
 
-	SSL_clear(dtls->ssl);
-	if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
-		SSL_set_accept_state(dtls->ssl);
-	} else {
-		SSL_set_connect_state(dtls->ssl);
-	}
-	dtls->connection = AST_RTP_DTLS_CONNECTION_NEW;
+	rtp->themssrc = ssrc;
 }
 
+static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num)
+{
+	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
+	rtp->stream_num = stream_num;
+}
+
+static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent)
+{
+	struct ast_rtp *child_rtp = ast_rtp_instance_get_data(child);
+	struct ast_rtp *parent_rtp = ast_rtp_instance_get_data(parent);
+	struct rtp_ssrc_mapping mapping;
+	struct ast_sockaddr them = { { 0, } };
+
+	if (child_rtp->bundled == parent) {
+		return 0;
+	}
+
+	/* If this instance was already bundled then remove the SSRC mapping */
+	if (child_rtp->bundled) {
+		struct ast_rtp *bundled_rtp;
+
+		ao2_unlock(child);
+
+		/* The child lock can't be held while accessing the parent */
+		ao2_lock(child_rtp->bundled);
+		bundled_rtp = ast_rtp_instance_get_data(child_rtp->bundled);
+		AST_VECTOR_REMOVE_CMP_UNORDERED(&bundled_rtp->ssrc_mapping, child_rtp->themssrc, SSRC_MAPPING_ELEM_CMP, AST_VECTOR_ELEM_CLEANUP_NOOP);
+		ao2_unlock(child_rtp->bundled);
+
+		ao2_lock(child);
+		ao2_ref(child_rtp->bundled, -1);
+		child_rtp->bundled = NULL;
+	}
+
+	if (!parent) {
+		/* We transitioned away from bundle so we need our own transport resources once again */
+		rtp_allocate_transport(child, child_rtp);
+		return 0;
+	}
+
+	/* We no longer need any transport related resources as we will use our parent RTP instance instead */
+	rtp_deallocate_transport(child, child_rtp);
+
+	/* Children maintain a reference to the parent to guarantee that the transport doesn't go away on them */
+	child_rtp->bundled = ao2_bump(parent);
+
+	mapping.ssrc = child_rtp->themssrc;
+	mapping.instance = child;
+
+	ao2_unlock(child);
+
+	ao2_lock(parent);
+
+	AST_VECTOR_APPEND(&parent_rtp->ssrc_mapping, mapping);
+
+#ifdef HAVE_OPENSSL_SRTP
+	/* If DTLS-SRTP is already in use then add the local SSRC to it, otherwise it will get added once DTLS
+	 * negotiation has been completed.
+	 */
+	if (parent_rtp->dtls.connection == AST_RTP_DTLS_CONNECTION_EXISTING) {
+		dtls_srtp_add_local_ssrc(parent_rtp, ast_rtp_instance_get_srtp(parent, 0), parent, 0, child_rtp->ssrc, 0);
+	}
+#endif
+
+	/* Bundle requires that RTCP-MUX be in use so only the main remote address needs to match */
+	ast_rtp_instance_get_remote_address(parent, &them);
+
+	ao2_unlock(parent);
+
+	ao2_lock(child);
+
+	ast_rtp_instance_set_remote_address(child, &them);
+
+	return 0;
+}
+
+#ifdef HAVE_OPENSSL_SRTP
 /*! \pre instance is locked */
 static int ast_rtp_activate(struct ast_rtp_instance *instance)
 {
 	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
-
-	dtls_perform_setup(&rtp->dtls);
-
-	if (rtp->rtcp) {
-		dtls_perform_setup(&rtp->rtcp->dtls);
-	}
 
 	/* If ICE negotiation is enabled the DTLS Handshake will be performed upon completion of it */
 #ifdef HAVE_PJPROJECT
@@ -5880,9 +6128,11 @@
 	}
 #endif
 
+	dtls_perform_setup(&rtp->dtls);
 	dtls_perform_handshake(instance, &rtp->dtls, 0);
 
 	if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
+		dtls_perform_setup(&rtp->rtcp->dtls);
 		dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1);
 	}
 

-- 
To view, visit https://gerrit.asterisk.org/5981
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I96c0920b9f9aca7382256484765a239017973c11
Gerrit-Change-Number: 5981
Gerrit-PatchSet: 5
Gerrit-Owner: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Benjamin Keith Ford <bford at digium.com>
Gerrit-Reviewer: Jenkins2
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: Matthew Fredrickson <creslin at digium.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-commits/attachments/20170713/ade560dc/attachment-0001.html>


More information about the asterisk-commits mailing list