diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 0e4468cdcd9df981c7d7a71fa696f7174374809f..3de980a520bc1d1598df6dbe4bab9481e837067a 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -792,8 +792,6 @@ static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast)
 		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 2cd27d37f33812baedfdc4bd0a19a37ba9329c40..d499d5514bfca5fa97e835adb1573724b809ea4b 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -688,6 +688,8 @@ struct ast_sip_endpoint_media_configuration {
 	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 e298e1f32da575710cbd3582baaab132b645c748..eae29de046753d9825377d48636be83e9ed93911 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -99,6 +99,12 @@ struct ast_sip_session_media {
 	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_add_read_callback(struct ast_sip_session *session, str
 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 5f439163fe1760938f153ebe9d5a61cab8b72257..20ae959e96ce2deef6b1ec42cda42aa9a2771ed2 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -603,6 +603,12 @@ struct ast_rtp_engine {
 	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 */
@@ -1506,6 +1512,20 @@ void ast_rtp_codecs_payload_formats(struct ast_rtp_codecs *codecs, struct ast_fo
  */
 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 @@ int ast_rtp_instance_sendcng(struct ast_rtp_instance *instance, int level);
  *
  * \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 @@ unsigned int ast_rtp_instance_get_ssrc(struct ast_rtp_instance *rtp);
  */
 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 9cfae09f4379c44c6d02656ef67f632eedd8e2d7..abd4b1fcfcaa9ddcfbd48a3830451a44190524a9 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -1495,21 +1495,24 @@ static int rtp_codecs_find_non_primary_dynamic_rx(struct ast_rtp_codecs *codecs)
  * \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 @@ static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int
 		 * 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 @@ static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int
 		 */
 		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,13 +1603,18 @@ int ast_rtp_codecs_payload_code(struct ast_rtp_codecs *codecs, int asterisk_form
 
 	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)
 {
 	struct ast_rtp_payload_type *type;
@@ -2424,7 +2437,7 @@ int ast_rtp_instance_add_srtp_policy(struct ast_rtp_instance *instance, struct a
 
 	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 @@ const char *ast_rtp_instance_get_cname(struct ast_rtp_instance *rtp)
 
 	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 0cf034374512a9f1219b8b6590e16febdfc0679c..1b546eff92c47a9b3accdced81c35cac5ef4ace3 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 372b01bc8dd1f8c5e8e61536b8fd6a666d8fd14f..d56ff5d23e12e499de79e7c3fa7f0c6bab4b010e 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1332,6 +1332,10 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
 		return -1;
 	}
 
+	if (endpoint->media.bundle) {
+		endpoint->media.rtcp_mux = 1;
+	}
+
 	return 0;
 }
 
@@ -1954,6 +1958,7 @@ int ast_res_pjsip_initialize_configuration(void)
 	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 a49130868203a35c2ad63a81aa7377752a0f7de8..4ec811528807ad0958a9ebe4ba84ea95a75d5cf6 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -317,6 +317,7 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp
 
 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)
 {
@@ -376,6 +377,24 @@ static int set_caps(struct ast_sip_session *session,
 
 	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);
 		ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_UNKNOWN);
@@ -496,7 +515,8 @@ static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format *
 }
 
 /*! \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 @@ static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_se
 	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;
 	}
 
@@ -521,6 +540,15 @@ static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_se
 		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);
 	for (; (candidate = ao2_iterator_next(&it_candidates)); ao2_ref(candidate, -1)) {
 		struct ast_str *attr_candidate = ast_str_create(128);
@@ -940,6 +968,63 @@ static void set_ice_components(struct ast_sip_session *session, struct ast_sip_s
 	}
 }
 
+/*! \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 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 	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 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 		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 int add_crypto_to_stream(struct ast_sip_session *session,
 	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 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
 			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 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
 			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 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 	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 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 		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));
+			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;
+			}
+		}
 	}
 
-	media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
-	if (!media->conn) {
-		return -1;
-	}
+	session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
-	/* 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 (session_media_transport == session_media || !session_media->bundled) {
+		set_ice_components(session, session_media);
+		enable_rtcp(session, session_media, NULL);
 
-	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;
-	}
+		/* 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;
+		}
 
-	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 (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));
+		}
 
-	if (!ast_strlen_zero(session->endpoint->media.address)) {
-		pj_sockaddr ip;
+		media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
+		if (!media->conn) {
+			return -1;
+		}
 
-		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 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;
 		}
-	}
 
-	/* Add ICE attributes and candidates */
-	add_ice_to_stream(session, session_media, pool, media);
+		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;
+		}
 
-	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;
+		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 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 			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 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 		}
 	}
 
+
 	/* If no formats were actually added to the media stream don't add it to the SDP */
 	if (!media->desc.fmt_count) {
 		return 1;
@@ -1365,6 +1526,8 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 		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 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
 	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 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
 		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);
 
-	if (!remote_stream->conn && !remote->conn) {
-		return 1;
-	}
+		enable_rtcp(session, session_media, remote_stream);
 
-	ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
+		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;
+		}
 
-	/* 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;
-	}
+		if (!remote_stream->conn && !remote->conn) {
+			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_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
 
-	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);
+		/* 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 ICE support is enabled find all the needed attributes */
-	process_ice_attributes(session, session_media, remote, remote_stream);
+	if (set_caps(session, session_media, session_media_transport, remote_stream, 0, asterisk_stream)) {
+		return 1;
+	}
 
 	/* Set the channel uniqueid on the RTP instance now that it is becoming active */
 	ast_channel_lock(session->channel);
@@ -1490,6 +1666,7 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
 	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 ecda4990132bff2d77eb9e4fbea1dbb44417ddaf..315db6df5db1c30ded0b08fdd61fbe022c772b66 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -324,6 +324,28 @@ int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, st
 	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 @@ static void session_media_dtor(void *obj)
 	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 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses
 		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;
 	}
 
@@ -441,6 +477,78 @@ static int is_stream_limitation_reached(enum ast_media_type type, const struct a
 	}
 }
 
+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)
 {
 	int i;
@@ -497,9 +605,13 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
 			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;
 			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
@@ -589,6 +701,8 @@ static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *ses
 	/* 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) {
 		ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
@@ -3443,6 +3557,82 @@ static int add_sdp_streams(struct ast_sip_session_media *session_media,
 	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 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru
 	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 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru
 			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 a032bb12fb43a82e3d626dd37da5acf002bf30b9..877d48fb6d41c3ce1ab21ab4b4b7a1f48db3ed9c 100644
--- a/res/res_pjsip_t38.c
+++ b/res/res_pjsip_t38.c
@@ -880,11 +880,20 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 
 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 01dfe76f29d2ef40dbe441ed5cafa20d4a3ef10a..4bfbf9b087e9a552edbd0a53d927791e2c29b98e 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 @@ struct ice_wrap {
 };
 #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_rtp {
 	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;
@@ -301,6 +311,11 @@ struct ast_rtp {
 	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_qos_set(struct ast_rtp_instance *instance, int tos, int cos,
 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 @@ static struct ast_rtp_engine asterisk_rtp_engine = {
 #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 @@ static void dtls_perform_handshake(struct ast_rtp_instance *instance, struct dtl
 }
 #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 @@ static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status)
 	}
 
 #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,59 +2282,14 @@ static int dtls_srtp_renegotiate(const void *data)
 	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;
 
-	/* 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) {
-		X509 *certificate;
-
-		if (!(certificate = SSL_get_peer_certificate(dtls->ssl))) {
-			ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance);
-			return -1;
-		}
-
-		/* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
-		if (rtp->remote_fingerprint[0]) {
-			const EVP_MD *type;
-			unsigned char fingerprint[EVP_MAX_MD_SIZE];
-			unsigned int size;
-
-			if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA1) {
-				type = EVP_sha1();
-			} else if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA256) {
-				type = EVP_sha256();
-			} else {
-				ast_log(LOG_WARNING, "Unsupported fingerprint hash type on RTP instance '%p'\n", instance);
-				return -1;
-			}
-
-			if (!X509_digest(certificate, type, fingerprint, &size) ||
-			    !size ||
-			    memcmp(fingerprint, rtp->remote_fingerprint, size)) {
-				X509_free(certificate);
-				ast_log(LOG_WARNING, "Fingerprint provided by remote party does not match that of peer certificate on RTP instance '%p'\n",
-					instance);
-				return -1;
-			}
-		}
-
-		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);
-		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",
@@ -2328,41 +2324,31 @@ static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct as
 		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, ssrc, 0);
 
-	res_srtp_policy->set_ssrc(local_policy, stats.local_ssrc, 0);
+	if (set_remote_policy) {
+		if (!(remote_policy = res_srtp_policy->alloc())) {
+			goto error;
+		}
 
-	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_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;
+		}
 
-	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);
 	}
 
-	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 (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;
-		}
-	}
-
 	res = 0;
 
 error:
@@ -2375,6 +2361,71 @@ error:
 
 	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) {
+		X509 *certificate;
+
+		if (!(certificate = SSL_get_peer_certificate(dtls->ssl))) {
+			ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance);
+			return -1;
+		}
+
+		/* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
+		if (rtp->remote_fingerprint[0]) {
+			const EVP_MD *type;
+			unsigned char fingerprint[EVP_MAX_MD_SIZE];
+			unsigned int size;
+
+			if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA1) {
+				type = EVP_sha1();
+			} else if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA256) {
+				type = EVP_sha256();
+			} else {
+				ast_log(LOG_WARNING, "Unsupported fingerprint hash type on RTP instance '%p'\n", instance);
+				return -1;
+			}
+
+			if (!X509_digest(certificate, type, fingerprint, &size) ||
+			    !size ||
+			    memcmp(fingerprint, rtp->remote_fingerprint, size)) {
+				X509_free(certificate);
+				ast_log(LOG_WARNING, "Fingerprint provided by remote party does not match that of peer certificate on RTP instance '%p'\n",
+					instance);
+				return -1;
+			}
+		}
+
+		X509_free(certificate);
+	}
+
+	if (dtls_srtp_add_local_ssrc(rtp, srtp, instance, rtcp, ast_rtp_instance_get_ssrc(instance), 1)) {
+		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);
+
+		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);
+			return -1;
+		}
+	}
+
+	return 0;
+}
 #endif
 
 static int rtcp_mux(struct ast_rtp *rtp, const unsigned char *packet)
@@ -2569,7 +2620,9 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
 	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 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
 	}
 
 #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 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
 	}
 #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 @@ static int ice_create(struct ast_rtp_instance *instance, struct ast_sockaddr *ad
 }
 #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 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
 	/* 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 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
 	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 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
 		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 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
 
 	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 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
 	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 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
 		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 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
 		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 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
 	/* 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 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
 		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 @@ static void ast_rtp_change_source(struct ast_rtp_instance *instance)
 	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;
-	}
-
-	/* 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);
+	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);
 
-	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 @@ static int ast_rtcp_write_report(struct ast_rtp_instance *instance, int sr)
 	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 @@ static int ast_rtcp_write_report(struct ast_rtp_instance *instance, int sr)
 		}
 	}
 	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)));
+
+	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;
 
-	ast_sockaddr_copy(&remote_address, &rtp->rtcp->them);
+	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 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
 
 	/* 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 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
 		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 @@ static void update_lost_stats(struct ast_rtp *rtp, unsigned int lost_packets)
 	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 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
 
 	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 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
 		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]);
@@ -4597,6 +4728,21 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
 			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 */
 		switch (pt) {
 		case RTCP_PT_SR:
@@ -4632,6 +4778,9 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
 				/* 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 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
 			 */
 
 			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 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
 
 			/* 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 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
 			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 @@ static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance,
 	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;
@@ -4950,11 +5111,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
 		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 */
 	if ((res = rtp_recvfrom(instance, read_area, read_area_size, 0,
 				&addr)) < 0) {
@@ -5070,6 +5226,33 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
 		}
 	}
 
+	/* 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
@@ -5077,11 +5260,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
 		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;
-	}
-
 	/* Pull out the various other fields we will need */
 	payloadtype = (seqno & 0x7f0000) >> 16;
 	padding = seqno & (1 << 29);
@@ -5090,7 +5268,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
 	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 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
 	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 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct
 {
 	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)) {
@@ -5553,6 +5732,13 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct
 		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;
 
 	if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN) {
@@ -5836,42 +6022,104 @@ static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance)
 /*! \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;
 }
 
-/*! \pre instance is locked */
-static int ast_rtp_activate(struct ast_rtp_instance *instance)
+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);
 
-	dtls_perform_setup(&rtp->dtls);
+	rtp->stream_num = stream_num;
+}
 
-	if (rtp->rtcp) {
-		dtls_perform_setup(&rtp->rtcp->dtls);
+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);
 
 	/* If ICE negotiation is enabled the DTLS Handshake will be performed upon completion of it */
 #ifdef HAVE_PJPROJECT
@@ -5880,9 +6128,11 @@ static int ast_rtp_activate(struct ast_rtp_instance *instance)
 	}
 #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);
 	}