[Asterisk-code-review] chan pjsip: Multistream: Modify use of ast sip session media (asterisk[master])

Mark Michelson asteriskteam at digium.com
Tue Jun 13 17:19:55 CDT 2017


Mark Michelson has uploaded this change for review. ( https://gerrit.asterisk.org/5824


Change subject: chan_pjsip: Multistream: Modify use of ast_sip_session_media
......................................................................

chan_pjsip: Multistream: Modify use of ast_sip_session_media

This commit reworks the use of ast_sip_session media structures in order
to facilitate multistream behavior in chan_pjsip.

Prior to this commit the ast_sip_session structure contained an
ao2_container of ast_sip_session_media structures. This container was a
hash table keyed on the media type. At session creation, a session media
structure was pre-allocated for each supported media type and added to
the container. This effectively limited the allocation of session media
to being one per media type.

With this commit, this limitation is lifted. The following has been
changed:

* Rather than using an ao2_container, session medias are stored in a
  vector on the session. This allows for the index of the session media
  to be important, rather than the media type. The index of the session
  media vector corresponds to the index of our local SDP as well as the
  index of the stream topology on which the local SDP is based.

* Session media structures are not pre-allocated at session creation.
  Rather, they are created as needed due to the circumstances
  surrounding the session. Typically, session media structures are
  allocated based on a stream topology that will be represented by our
  local SDP. So typically the session media is allocated at the time of
  SDP creation. The big exception to this is during outbound session
  creation. We create the session media at that time due to assumptions
  made in chan_pjsip about session media already being allocated.

In addition to the primary changes, there are some changes that rippled
out of this change

* Because the index is important to have in the SDP handlers, some SDP
  handler functions have had their parameters changed. Rather than
  explicitly passing in SDP streams and a session media, we pass in a
  session, an SDP (or two), and an index. The index can be used to find
  the appropriate SDP stream, session media, and Asterisk stream this
  way, since the all correspond to the same index.

* Because a session media corresponds to an index in the SDP, this can
  result in a session media changing types. The most common cause of
  this would be for a session media changing from audio to T.38. In such
  a case, we need to be able to change the session media's type as
  necessary. Because of the type being variable, it means that we can no
  longer store the RTP instance and UDPTL as a union. They need to be
  distinct structures. This way, a session media could switch back and
  forth between types without causing memory problems.

NOTE: This change breaks some tests in the testsuite. Specifically, it
breaks the tests that make sure that Asterisk only accepts one stream of
each media type. The next change I will be working on will add
configuration values that govern the number of streams allowed on a
session. With that change in place, the default values will result in
those tests passing again. Therefore, it's not worth it for me to go to
special effort with this change to ensure those tests are passing.

Change-Id: I144b04f43633387b8e42a43ef3b25d7c5682b451
---
M channels/chan_pjsip.c
M include/asterisk/res_pjsip_session.h
M res/res_pjsip_sdp_rtp.c
M res/res_pjsip_session.c
M res/res_pjsip_t38.c
5 files changed, 377 insertions(+), 289 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/24/5824/1

diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index f092383..bd76110 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -198,7 +198,7 @@
 	struct chan_pjsip_pvt *pvt = channel->pvt;
 	struct ast_sip_endpoint *endpoint;
 
-	if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO]->rtp) {
+	if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO] || !pvt->media[SIP_MEDIA_VIDEO]->rtp) {
 		return AST_RTP_GLUE_RESULT_FORBID;
 	}
 
@@ -461,6 +461,35 @@
 	return ast_format_cap_iscompatible(cap1, cap2);
 }
 
+/*!
+ * \brief Get the first ast_sip_session_media of a certain media type.
+ *
+ * XXX For multistream, we'll likely want to change this to be a function
+ * to retrieve the nth session media instead of the first.
+ *
+ * \param session The session on which the session media lives
+ * \param type The type of the session media
+ */
+static struct ast_sip_session_media *get_first_session_media_by_type(
+	const struct ast_sip_session *session, enum ast_media_type type)
+{
+	int i;
+
+	for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {
+		struct ast_sip_session_media *session_media;
+
+		session_media = AST_VECTOR_GET(&session->media, i);
+
+		ast_assert(session_media != NULL);
+
+		if (session_media->type == type) {
+			return ao2_bump(session_media);
+		}
+	}
+
+	return NULL;
+}
+
 /*! \brief Function called to create a new PJSIP Asterisk channel */
 static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int state, const char *exten, const char *title, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *cid_name)
 {
@@ -569,11 +598,12 @@
 	ast_channel_stage_snapshot_done(chan);
 	ast_channel_unlock(chan);
 
-	/* If res_pjsip_session is ever updated to create/destroy ast_sip_session_media
-	 * during a call such as if multiple same-type stream support is introduced,
-	 * these will need to be recaptured as well */
-	pvt->media[SIP_MEDIA_AUDIO] = ao2_find(session->media, "audio", OBJ_KEY);
-	pvt->media[SIP_MEDIA_VIDEO] = ao2_find(session->media, "video", OBJ_KEY);
+	/* XXX For multistream, the chan_pjsip_pvt structure will be completely
+	 * redone so it's not a fixed size array. It may have multiple audio / video
+	 * streams
+	 */
+	pvt->media[SIP_MEDIA_AUDIO] = get_first_session_media_by_type(session, AST_MEDIA_TYPE_AUDIO);
+	pvt->media[SIP_MEDIA_VIDEO] = get_first_session_media_by_type(session, AST_MEDIA_TYPE_VIDEO);
 	set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(chan));
 
 	return chan;
@@ -1340,20 +1370,15 @@
 }
 
 /*! \brief Callback which changes the value of locally held on the media stream */
-static int local_hold_set_state(void *obj, void *arg, int flags)
+static void local_hold_set_state(struct ast_sip_session_media *session_media, unsigned int held)
 {
-	struct ast_sip_session_media *session_media = obj;
-	unsigned int *held = arg;
-
-	session_media->locally_held = *held;
-
-	return 0;
+	session_media->locally_held = held;
 }
 
 /*! \brief Update local hold state and send a re-INVITE with the new SDP */
 static int remote_send_hold_refresh(struct ast_sip_session *session, unsigned int held)
 {
-	ao2_callback(session->media, OBJ_NODATA, local_hold_set_state, &held);
+	AST_VECTOR_CALLBACK_VOID(&session->media, local_hold_set_state, held);
 	ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
 	ao2_ref(session, -1);
 
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index 9bffa16..8829828 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -28,6 +28,8 @@
 #include "asterisk/netsock2.h"
 /* Needed for ast_sdp_srtp struct */
 #include "asterisk/sdp_srtp.h"
+/* Needed for ast_media_type */
+#include "asterisk/codec.h"
 
 /* Forward declarations */
 struct ast_sip_endpoint;
@@ -61,12 +63,10 @@
  * \brief A structure containing SIP session media information
  */
 struct ast_sip_session_media {
-	union {
-		/*! \brief RTP instance itself */
-		struct ast_rtp_instance *rtp;
-		/*! \brief UDPTL instance itself */
-		struct ast_udptl *udptl;
-	};
+	/*! \brief RTP instance itself */
+	struct ast_rtp_instance *rtp;
+	/*! \brief UDPTL instance itself */
+	struct ast_udptl *udptl;
 	/*! \brief Direct media address */
 	struct ast_sockaddr direct_media_addr;
 	/*! \brief SDP handler that setup the RTP */
@@ -87,8 +87,8 @@
 	unsigned int locally_held:1;
 	/*! \brief Does remote support rtcp_mux */
 	unsigned int remote_rtcp_mux:1;
-	/*! \brief Stream type this session media handles */
-	char stream_type[1];
+	/*! \brief Media type of this session media */
+	enum ast_media_type type;
 };
 
 /*!
@@ -124,7 +124,7 @@
 	/*! Datastores added to the session by supplements to the session */
 	struct ao2_container *datastores;
 	/*! Media streams */
-	struct ao2_container *media;
+	AST_VECTOR(, struct ast_sip_session_media *) media;
 	/*! Serializer for tasks relating to this SIP session */
 	struct ast_taskprocessor *serializer;
 	/*! Non-null if the session serializer is suspended or being suspended. */
@@ -311,14 +311,13 @@
 	/*!
 	 * \brief Set session details based on a stream in an incoming SDP offer or answer
 	 * \param session The session for which the media is being negotiated
-	 * \param session_media The media to be setup for this session
 	 * \param sdp The entire SDP. Useful for getting "global" information, such as connections or attributes
-	 * \param stream The stream on which to operate
+	 * \param index The index for the session media, Asterisk stream, and PJMEDIA stream being negotiated
 	 * \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called.
 	 * \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned.
 	 * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.
 	 */
-	int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream);
+	int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, const struct pjmedia_sdp_session *sdp, int index);
 	/*!
 	 * \brief Create an SDP media stream and add it to the outgoing SDP offer or answer
 	 * \param session The session for which media is being added
@@ -334,11 +333,12 @@
 	 * \param session The session for which media is being added
 	 * \param session_media The media to be setup for this session
 	 * \param sdp The entire SDP as currently built
+	 * \param stream The stream that is to be added to the outgoing SDP
 	 * \retval 0 This handler has no stream to add. If there are other registered handlers for this stream type, they will be called.
 	 * \retval <0 There was an error encountered. No further operation will take place and the current SDP negotiation will be abandoned.
 	 * \retval >0 The handler has a stream to be added to the SDP. No further handler of this stream type will be called.
 	 */
-	int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp);
+	int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp, struct ast_stream *stream);
 	/*!
 	 * \brief Update media stream with external address if applicable
 	 * \param tdata The outgoing message itself
@@ -349,17 +349,15 @@
 	/*!
 	 * \brief Apply a negotiated SDP media stream
 	 * \param session The session for which media is being applied
-	 * \param session_media The media to be setup for this session
 	 * \param local The entire local negotiated SDP
-	 * \param local_stream The local stream which to apply
 	 * \param remote The entire remote negotiated SDP
-	 * \param remote_stream The remote stream which to apply
+	 * \param index The index of the session media, SDP streams, and Asterisk streams
 	 * \retval 0 The stream was not applied by this handler. If there are other registered handlers for this stream type, they will be called.
 	 * \retval <0 There was an error encountered. No further operation will take place and the current application will be abandoned.
 	 * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.
 	 */
-	int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
-		const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream);
+	int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, const struct pjmedia_sdp_session *local,
+		const struct pjmedia_sdp_session *remote, int index);
 	/*!
 	 * \brief Stop a session_media created by this handler but do not destroy resources
 	 * \param session The session for which media is being stopped
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index 7b5f297..52245c8 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -68,18 +68,6 @@
 static const char STR_VIDEO[] = "video";
 static const int FD_VIDEO = 2;
 
-/*! \brief Retrieves an ast_format_type based on the given stream_type */
-static enum ast_media_type stream_to_media_type(const char *stream_type)
-{
-	if (!strcasecmp(stream_type, STR_AUDIO)) {
-		return AST_MEDIA_TYPE_AUDIO;
-	} else if (!strcasecmp(stream_type, STR_VIDEO)) {
-		return AST_MEDIA_TYPE_VIDEO;
-	}
-
-	return 0;
-}
-
 /*! \brief Get the starting descriptor for a media type */
 static int media_type_to_fdno(enum ast_media_type media_type)
 {
@@ -242,11 +230,11 @@
 		ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);
 	}
 
-	if (!strcmp(session_media->stream_type, STR_AUDIO) &&
+	if (session_media->type == AST_MEDIA_TYPE_AUDIO &&
 			(session->endpoint->media.tos_audio || session->endpoint->media.cos_audio)) {
 		ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_audio,
 				session->endpoint->media.cos_audio, "SIP RTP Audio");
-	} else if (!strcmp(session_media->stream_type, STR_VIDEO) &&
+	} else if (session_media->type == AST_MEDIA_TYPE_VIDEO &&
 			(session->endpoint->media.tos_video || session->endpoint->media.cos_video)) {
 		ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video,
 				session->endpoint->media.cos_video, "SIP RTP Video");
@@ -336,34 +324,33 @@
 static int set_caps(struct ast_sip_session *session,
 	struct ast_sip_session_media *session_media,
 	const struct pjmedia_sdp_media *stream,
-	int is_offer)
+	int is_offer, int index)
 {
 	RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_format_cap *, peer, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_format_cap *, joint, NULL, ao2_cleanup);
-	enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+	RAII_VAR(struct ast_format_cap *, endpoint_caps, NULL, ao2_cleanup);
+	enum ast_media_type media_type = session_media->type;
 	struct ast_rtp_codecs codecs = AST_RTP_CODECS_NULL_INIT;
 	int fmts = 0;
 	int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
 		ast_format_cap_count(session->direct_media_cap);
 	int dsp_features = 0;
-	struct ast_stream *req_stream;
+	struct ast_stream *req_stream = NULL;
 	struct ast_format_cap *req_caps;
-	struct ast_stream *endpoint_stream;
-	struct ast_format_cap *endpoint_caps;
 
 	if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
 	    !(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
 	    !(joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);
+		ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
+			ast_codec_media_type2str(session_media->type));
 		return -1;
 	}
 
-	endpoint_stream = ast_stream_topology_get_first_stream_by_type(session->endpoint->media.codecs, media_type);
-	if (!endpoint_stream) {
+	endpoint_caps = ast_format_cap_from_stream_topology(session->endpoint->media.codecs);
+	if (!endpoint_caps) {
 		return -1;
 	}
-	endpoint_caps = ast_stream_get_formats(endpoint_stream);
 
 	/* get the endpoint capabilities */
 	if (direct_media_enabled) {
@@ -384,7 +371,7 @@
 
 		ast_rtp_codecs_payloads_destroy(&codecs);
 		ast_log(LOG_NOTICE, "No joint capabilities for '%s' media stream between our configuration(%s) and incoming SDP(%s)\n",
-			session_media->stream_type,
+			ast_codec_media_type2str(session_media->type),
 			ast_format_cap_get_names(caps, &usbuf),
 			ast_format_cap_get_names(peer, &thembuf));
 		return -1;
@@ -400,14 +387,16 @@
 	ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),
 		session_media->rtp);
 
-	req_stream = ast_stream_topology_get_first_stream_by_type(session->req_topology, media_type);
+	if (index < ast_stream_topology_get_count(session->req_topology)) {
+		req_stream = ast_stream_topology_get_stream(session->req_topology, index);
+	}
 	if (!req_stream) {
 		req_stream = ast_stream_alloc(ast_codec_media_type2str(media_type), media_type);
 		if (!req_stream) {
 			return -1;
 		}
 		ast_stream_set_formats(req_stream, joint);
-		ast_stream_topology_append_stream(session->req_topology, req_stream);
+		ast_stream_topology_set_stream(session->req_topology, index, req_stream);
 	} else {
 		req_caps = ast_stream_get_formats(req_stream);
 		ast_format_cap_replace_from_cap(req_caps, joint, AST_MEDIA_TYPE_UNKNOWN);
@@ -977,24 +966,28 @@
 }
 
 /*! \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 struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
+static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
+	const pjmedia_sdp_session *sdp, int index)
 {
 	char host[NI_MAXHOST];
 	RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
-	enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+	struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);
+	pjmedia_sdp_media *stream = sdp->media[index];
+	enum ast_media_type media_type = session_media->type;
 	enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
 	int res;
 
 	/* If port is 0, ignore this media stream */
 	if (!stream->desc.port) {
-		ast_debug(3, "Media stream '%s' is already declined\n", session_media->stream_type);
+		ast_debug(3, "Media stream '%s' is already declined\n",
+			ast_codec_media_type2str(session_media->type));
 		return 0;
 	}
 
 	/* If no type formats have been configured reject this stream */
 	if (!ast_stream_topology_get_first_stream_by_type(session->endpoint->media.codecs, media_type)) {
-		ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n", session_media->stream_type);
+		ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n",
+			ast_codec_media_type2str(session_media->type));
 		return 0;
 	}
 
@@ -1049,7 +1042,7 @@
 		pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);
  	}
 
-	if (set_caps(session, session_media, stream, 1)) {
+	if (set_caps(session, session_media, stream, 1, index)) {
 		return 0;
 	}
 	return 1;
@@ -1170,7 +1163,7 @@
 
 /*! \brief Function which creates an outgoing stream */
 static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
-				      struct pjmedia_sdp_session *sdp)
+				      struct pjmedia_sdp_session *sdp, struct ast_stream *stream)
 {
 	pj_pool_t *pool = session->inv_session->pool_prov;
 	static const pj_str_t STR_IN = { "IN", 2 };
@@ -1189,27 +1182,14 @@
 	int min_packet_size = 0, max_packet_size = 0;
 	int rtp_code;
 	RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
-	enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
-	int use_override_prefs = ast_stream_topology_get_count(session->req_topology);
-	/* XXX Getting first stream by media type is not going to cut it when we're creating multiple streams of the same type */
-	struct ast_stream *req_stream = ast_stream_topology_get_first_stream_by_type(session->req_topology, media_type);
-	struct ast_stream *endpoint_config_stream = ast_stream_topology_get_first_stream_by_type(session->endpoint->media.codecs, media_type);
-	struct ast_format_cap *req_caps;
-	struct ast_format_cap *endpoint_config_caps;
+	enum ast_media_type media_type = session_media->type;
 
 	int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
 		ast_format_cap_count(session->direct_media_cap);
 
-	if ((use_override_prefs && !req_stream) ||
-	    (!use_override_prefs && !endpoint_config_stream)) {
-		/* If no type formats are configured don't add a stream */
-		return 0;
-	} else if (!session_media->rtp && create_rtp(session, session_media)) {
+	if (!session_media->rtp && create_rtp(session, session_media)) {
 		return -1;
 	}
-
-	req_caps = req_stream ? ast_stream_get_formats(req_stream) : NULL;
-	endpoint_config_caps = ast_stream_get_formats(endpoint_config_stream);
 
 	set_ice_components(session, session_media);
 	enable_rtcp(session, session_media, NULL);
@@ -1223,7 +1203,7 @@
 		return -1;
 	}
 
-	media->desc.media = pj_str(session_media->stream_type);
+	pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));
 	if (pj_strlen(&session_media->transport)) {
 		/* If a transport has already been specified use it */
 		media->desc.transport = session_media->transport;
@@ -1246,7 +1226,8 @@
 	}
 
 	if (ast_strlen_zero(hostip)) {
-		ast_log(LOG_ERROR, "No local host IP available for stream %s\n", session_media->stream_type);
+		ast_log(LOG_ERROR, "No local host IP available for stream %s\n",
+			ast_codec_media_type2str(session_media->type));
 		return -1;
 	}
 
@@ -1272,17 +1253,22 @@
 	add_ice_to_stream(session, session_media, pool, media);
 
 	if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);
+		ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
+			ast_codec_media_type2str(session_media->type));
 		return -1;
 	}
 
 	if (direct_media_enabled) {
+		struct ast_format_cap *endpoint_config_caps;
+
+		endpoint_config_caps = ast_format_cap_from_stream_topology(session->endpoint->media.codecs);
+		if (!endpoint_config_caps) {
+			return -1;
+		}
 		ast_format_cap_get_compatible(endpoint_config_caps, session->direct_media_cap, caps);
-	} else if (!req_caps || !ast_format_cap_count(req_caps) ||
-		!ast_format_cap_iscompatible(req_caps, endpoint_config_caps)) {
-		ast_format_cap_append_from_cap(caps, endpoint_config_caps, media_type);
+		ao2_ref(endpoint_config_caps, -1);
 	} else {
-		ast_format_cap_append_from_cap(caps, req_caps, media_type);
+		ast_format_cap_append_from_cap(caps, ast_stream_get_formats(stream), media_type);
 	}
 
 	for (index = 0; index < ast_format_cap_count(caps); ++index) {
@@ -1385,12 +1371,15 @@
 	return 1;
 }
 
-static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
-				       const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
-				       const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)
+static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
+	const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote,
+	int index)
 {
 	RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
-	enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+	struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);
+	struct pjmedia_sdp_media *local_stream = local->media[index];
+	struct pjmedia_sdp_media *remote_stream = remote->media[index];
+	enum ast_media_type media_type = session_media->type;
 	char host[NI_MAXHOST];
 	int fdno, res;
 
@@ -1441,7 +1430,7 @@
 	/* 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)) {
+	if (set_caps(session, session_media, remote_stream, 0, index)) {
 		return 1;
 	}
 
@@ -1493,7 +1482,7 @@
 	session_media->encryption = session->endpoint->media.rtp.encryption;
 
 	if (session->endpoint->media.rtp.keepalive > 0 &&
-			stream_to_media_type(session_media->stream_type) == AST_MEDIA_TYPE_AUDIO) {
+			session_media->type == AST_MEDIA_TYPE_AUDIO) {
 		ast_rtp_instance_set_keepalive(session_media->rtp, session->endpoint->media.rtp.keepalive);
 		/* Schedule the initial keepalive early in case this is being used to punch holes through
 		 * a NAT. This way there won't be an awkward delay before media starts flowing in some
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 41ef0ce..56acbd3 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -54,6 +54,9 @@
 #define MOD_DATA_ON_RESPONSE "on_response"
 #define MOD_DATA_NAT_HOOK "nat_hook"
 
+/* Most common case is one audio and one video stream */
+#define DEFAULT_NUM_SESSION_MEDIA 2
+
 /* Some forward declarations */
 static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata);
 static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata,
@@ -102,23 +105,6 @@
 	const char *stream_type2 = flags & OBJ_KEY ? arg : handler_list2->stream_type;
 
 	return strcmp(handler_list1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;
-}
-
-static int session_media_hash(const void *obj, int flags)
-{
-	const struct ast_sip_session_media *session_media = obj;
-	const char *stream_type = flags & OBJ_KEY ? obj : session_media->stream_type;
-
-	return ast_str_hash(stream_type);
-}
-
-static int session_media_cmp(void *obj, void *arg, int flags)
-{
-	struct ast_sip_session_media *session_media1 = obj;
-	struct ast_sip_session_media *session_media2 = arg;
-	const char *stream_type2 = flags & OBJ_KEY ? arg : session_media2->stream_type;
-
-	return strcmp(session_media1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;
 }
 
 int ast_sip_session_register_sdp_handler(struct ast_sip_session_sdp_handler *handler, const char *stream_type)
@@ -208,6 +194,71 @@
 	session_media->handler = handler;
 }
 
+static int stream_destroy(void *obj, void *arg, int flags)
+{
+	struct sdp_handler_list *handler_list = obj;
+	struct ast_sip_session_media *session_media = arg;
+	struct ast_sip_session_sdp_handler *handler;
+
+	AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+		handler->stream_destroy(session_media);
+	}
+
+	return 0;
+}
+
+static void session_media_dtor(void *obj)
+{
+	struct ast_sip_session_media *session_media = obj;
+
+	/* It is possible for multiple handlers to have allocated memory on the
+	 * session media (usually through a stream changing types). Therefore, we
+	 * traverse all the SDP handlers and let them all call stream_destroy on
+	 * the session_media
+	 */
+	ao2_callback(sdp_handlers, 0, stream_destroy, session_media);
+
+	if (session_media->srtp) {
+		ast_sdp_srtp_destroy(session_media->srtp);
+	}
+}
+
+/*!
+ * \brief Allocate an ast_session_media and add it to the session's vector.
+ *
+ * This allocates a session media of the specified type. The position argument
+ * determines where in the vector that the new session media will be inserted.
+ * If the position is less than 0, then the session media is appended to the end
+ * of the vector. Otherwise, it is inserted at the given index.
+ *
+ * \note The returned ast_session_media is the reference held by the vector. Callers
+ * of this function must NOT decrement the refcount of the session media.
+ *
+ * \param session Session on which to add the session media
+ * \param type The type of the session media
+ * \param position Position at which to insert the new session media.
+ */
+static struct ast_sip_session_media *add_session_media(struct ast_sip_session *session,
+	enum ast_media_type type, int position)
+{
+	struct ast_sip_session_media *session_media;
+
+	session_media = ao2_alloc(sizeof(*session_media), session_media_dtor);
+	if (!session_media) {
+		return NULL;
+	}
+	session_media->encryption = session->endpoint->media.rtp.encryption;
+	session_media->keepalive_sched_id = -1;
+	session_media->timeout_sched_id = -1;
+	session_media->type = type;
+	if (position >= 0) {
+		AST_VECTOR_REPLACE(&session->media, position, session_media);
+	} else {
+		AST_VECTOR_APPEND(&session->media, session_media);
+	}
+	return session_media;
+}
+
 static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp)
 {
 	int i;
@@ -223,32 +274,43 @@
 		char media[20];
 		struct ast_sip_session_sdp_handler *handler;
 		RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
-		RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+		struct ast_sip_session_media *session_media = NULL;
 		int res;
 
 		/* We need a null-terminated version of the media string */
 		ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
 
-		session_media = ao2_find(session->media, media, OBJ_KEY);
+		if (i < AST_VECTOR_SIZE(&session->media)) {
+			session_media = AST_VECTOR_GET(&session->media, i);
+			if (ast_media_type_from_str(media) != session_media->type) {
+				/* The stream may have changed types (e.g. from audio to T.38). In this
+				 * case, we can just correct the type and clear the cached SDP handler
+				 * on this session media. The session media destructor will ensure that
+				 * all SDP handlers get a chance to free the memory they allocated
+				 */
+				session_media->type = ast_media_type_from_str(media);
+				session_media->handler = NULL;
+			}
+		}
 		if (!session_media) {
-			/* if the session_media doesn't exist, there weren't
-			 * any handlers at the time of its creation */
-			continue;
+			session_media = add_session_media(session, ast_media_type_from_str(media), i);
+			if (!session_media) {
+				return -1;
+			}
 		}
 
 		if (session_media->handler) {
 			handler = session_media->handler;
 			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
-				session_media->stream_type,
+				ast_codec_media_type2str(session_media->type),
 				session_media->handler->id);
-			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp,
-				sdp->media[i]);
+			res = handler->negotiate_incoming_sdp_stream(session, sdp, i);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
 				return -1;
 			} else if (res > 0) {
 				ast_debug(1, "Media stream '%s' handled by %s\n",
-					session_media->stream_type,
+					ast_codec_media_type2str(session_media->type),
 					session_media->handler->id);
 				/* Handled by this handler. Move to the next stream */
 				handled = 1;
@@ -266,17 +328,16 @@
 				continue;
 			}
 			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
-				session_media->stream_type,
+				ast_codec_media_type2str(session_media->type),
 				handler->id);
-			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp,
-				sdp->media[i]);
+			res = handler->negotiate_incoming_sdp_stream(session, sdp, i);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
 				return -1;
 			}
 			if (res > 0) {
 				ast_debug(1, "Media stream '%s' handled by %s\n",
-					session_media->stream_type,
+					ast_codec_media_type2str(session_media->type),
 					handler->id);
 				/* Handled by this handler. Move to the next stream */
 				session_media_set_handler(session_media, handler);
@@ -291,110 +352,93 @@
 	return 0;
 }
 
-struct handle_negotiated_sdp_cb {
-	struct ast_sip_session *session;
-	const pjmedia_sdp_session *local;
-	const pjmedia_sdp_session *remote;
-};
-
-static int handle_negotiated_sdp_session_media(void *obj, void *arg, int flags)
+static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *session_media,
+		struct ast_sip_session *session, const pjmedia_sdp_session *local,
+		const pjmedia_sdp_session *remote, int index)
 {
-	struct ast_sip_session_media *session_media = obj;
-	struct handle_negotiated_sdp_cb *callback_data = arg;
-	struct ast_sip_session *session = callback_data->session;
-	const pjmedia_sdp_session *local = callback_data->local;
-	const pjmedia_sdp_session *remote = callback_data->remote;
-	int i;
+	/* See if there are registered handlers for this media stream type */
+	char media[20];
+	struct ast_sip_session_sdp_handler *handler;
+	RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
+	int res;
 
-	for (i = 0; i < local->media_count; ++i) {
-		/* See if there are registered handlers for this media stream type */
-		char media[20];
-		struct ast_sip_session_sdp_handler *handler;
-		RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
-		int res;
+	/* We need a null-terminated version of the media string */
+	ast_copy_pj_str(media, &local->media[index]->desc.media, sizeof(media));
 
-		if (!remote->media[i]) {
-			continue;
-		}
-
-		/* We need a null-terminated version of the media string */
-		ast_copy_pj_str(media, &local->media[i]->desc.media, sizeof(media));
-
-		/* stream type doesn't match the one we're looking to fill */
-		if (strcasecmp(session_media->stream_type, media)) {
-			continue;
-		}
-
-		handler = session_media->handler;
-		if (handler) {
-			ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
-				session_media->stream_type,
+	handler = session_media->handler;
+	if (handler) {
+		ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+			ast_codec_media_type2str(session_media->type),
+			handler->id);
+		res = handler->apply_negotiated_sdp_stream(session, local, remote, index);
+		if (res >= 0) {
+			ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+				ast_codec_media_type2str(session_media->type),
 				handler->id);
-			res = handler->apply_negotiated_sdp_stream(session, session_media, local,
-				local->media[i], remote, remote->media[i]);
-			if (res >= 0) {
-				ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
-					session_media->stream_type,
-					handler->id);
-				return CMP_MATCH;
-			}
 			return 0;
 		}
+		return -1;
+	}
 
-		handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
-		if (!handler_list) {
-			ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+	handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
+	if (!handler_list) {
+		ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+		return -1;
+	}
+	AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+		if (handler == session_media->handler) {
 			continue;
 		}
-		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
-			if (handler == session_media->handler) {
-				continue;
-			}
-			ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
-				session_media->stream_type,
+		ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+			ast_codec_media_type2str(session_media->type),
+			handler->id);
+		res = handler->apply_negotiated_sdp_stream(session, local, remote, index);
+		if (res < 0) {
+			/* Catastrophic failure. Abort! */
+			return -1;
+		}
+		if (res > 0) {
+			ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+				ast_codec_media_type2str(session_media->type),
 				handler->id);
-			res = handler->apply_negotiated_sdp_stream(session, session_media, local,
-				local->media[i], remote, remote->media[i]);
-			if (res < 0) {
-				/* Catastrophic failure. Abort! */
-				return 0;
-			}
-			if (res > 0) {
-				ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
-					session_media->stream_type,
-					handler->id);
-				/* Handled by this handler. Move to the next stream */
-				session_media_set_handler(session_media, handler);
-				return CMP_MATCH;
-			}
+			/* Handled by this handler. Move to the next stream */
+			session_media_set_handler(session_media, handler);
+			return 0;
 		}
 	}
 
 	if (session_media->handler && session_media->handler->stream_stop) {
 		ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n",
-			session_media->stream_type);
+			ast_codec_media_type2str(session_media->type));
 		session_media->handler->stream_stop(session_media);
 	}
 
-	return CMP_MATCH;
+	return 0;
 }
 
 static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote)
 {
-	RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);
-	struct handle_negotiated_sdp_cb callback_data = {
-		.session = session,
-		.local = local,
-		.remote = remote,
-	};
+	int i;
 
-	successful = ao2_callback(session->media, OBJ_MULTIPLE, handle_negotiated_sdp_session_media, &callback_data);
-	if (successful && ao2_iterator_count(successful) == ao2_container_count(session->media)) {
-		/* Nothing experienced a catastrophic failure */
-		ast_queue_frame(session->channel, &ast_null_frame);
-		return 0;
+	for (i = 0; i < local->media_count; ++i) {
+		struct ast_sip_session_media *session_media;
+		if (!remote->media[i]) {
+			continue;
+		}
+
+		/* If we're handling negotiated streams, then we should already have set
+		 * up session media instances that correspond to the local SDP, and there should
+		 * be the same number of session medias as there are local streams
+		 */
+		ast_assert(i < AST_VECTOR_SIZE(&session->media));
+
+		session_media = AST_VECTOR_GET(&session->media, i);
+		if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i)) {
+			return -1;
+		}
 	}
-	return -1;
+
+	return 0;
 }
 
 AST_RWLIST_HEAD_STATIC(session_supplements, ast_sip_session_supplement);
@@ -998,17 +1042,29 @@
 		char media[20];
 		struct ast_sip_session_sdp_handler *handler;
 		RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
-		RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+		struct ast_sip_session_media *session_media = NULL;
 		enum ast_sip_session_sdp_stream_defer res;
 
 		/* We need a null-terminated version of the media string */
 		ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
 
-		session_media = ao2_find(session->media, media, OBJ_KEY);
+		if (i < AST_VECTOR_SIZE(&session->media)) {
+			session_media = AST_VECTOR_GET(&session->media, i);
+			if (ast_media_type_from_str(media) != session_media->type) {
+				/* The stream may have changed types (e.g. from audio to T.38). In this
+				 * case, we can just correct the type and clear the cached SDP handler
+				 * on this session media. The session media destructor will ensure that
+				 * all SDP handlers get a chance to free the memory they allocated
+				 */
+				session_media->type = ast_media_type_from_str(media);
+				session_media->handler = NULL;
+			}
+		}
 		if (!session_media) {
-			/* if the session_media doesn't exist, there weren't
-			 * any handlers at the time of its creation */
-			continue;
+			session_media = add_session_media(session, ast_media_type_from_str(media), i);
+			if (!session_media) {
+				return -1;
+			}
 		}
 
 		if (session_media->handler) {
@@ -1270,29 +1326,6 @@
 	return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
 }
 
-static void session_media_dtor(void *obj)
-{
-	struct ast_sip_session_media *session_media = obj;
-	struct sdp_handler_list *handler_list;
-	/* It is possible for SDP handlers to allocate memory on a session_media but
-	 * not end up getting set as the handler for this session_media. This traversal
-	 * ensures that all memory allocated by SDP handlers on the session_media is
-	 * cleared (as well as file descriptors, etc.).
-	 */
-	handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);
-	if (handler_list) {
-		struct ast_sip_session_sdp_handler *handler;
-
-		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
-			handler->stream_destroy(session_media);
-		}
-	}
-	ao2_cleanup(handler_list);
-	if (session_media->srtp) {
-		ast_sdp_srtp_destroy(session_media->srtp);
-	}
-}
-
 static void session_destructor(void *obj)
 {
 	struct ast_sip_session *session = obj;
@@ -1300,6 +1333,7 @@
 	struct ast_sip_session_delayed_request *delay;
 	const char *endpoint_name = session->endpoint ?
 		ast_sorcery_object_get_id(session->endpoint) : "<none>";
+	int i;
 
 	ast_debug(3, "Destroying SIP session with endpoint %s\n", endpoint_name);
 
@@ -1321,7 +1355,10 @@
 
 	ast_taskprocessor_unreference(session->serializer);
 	ao2_cleanup(session->datastores);
-	ao2_cleanup(session->media);
+	for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {
+		ao2_cleanup(AST_VECTOR_GET(&session->media, i));
+	}
+	AST_VECTOR_FREE(&session->media);
 
 	AST_LIST_HEAD_DESTROY(&session->supplements);
 	while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {
@@ -1355,25 +1392,6 @@
 		}
 		AST_LIST_INSERT_TAIL(&session->supplements, copy, next);
 	}
-	return 0;
-}
-
-static int add_session_media(void *obj, void *arg, int flags)
-{
-	struct sdp_handler_list *handler_list = obj;
-	struct ast_sip_session *session = arg;
-	RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
-
-	session_media = ao2_alloc(sizeof(*session_media) + strlen(handler_list->stream_type), session_media_dtor);
-	if (!session_media) {
-		return CMP_STOP;
-	}
-	session_media->encryption = session->endpoint->media.rtp.encryption;
-	session_media->keepalive_sched_id = -1;
-	session_media->timeout_sched_id = -1;
-	/* Safe use of strcpy */
-	strcpy(session_media->stream_type, handler_list->stream_type);
-	ao2_link(session->media, session_media);
 	return 0;
 }
 
@@ -1449,12 +1467,9 @@
 
 	session->endpoint = ao2_bump(endpoint);
 
-	session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp);
-	if (!session->media) {
+	if (AST_VECTOR_INIT(&session->media, DEFAULT_NUM_SESSION_MEDIA) < 0) {
 		return NULL;
 	}
-	/* fill session->media with available types */
-	ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session);
 
 	if (rdata) {
 		/*
@@ -1811,6 +1826,9 @@
 				/* replace instances of joint caps equivalents in req_caps */
 				ast_format_cap_replace_from_cap(req_cap, joint_cap,
 						AST_MEDIA_TYPE_UNKNOWN);
+				if (add_session_media(session, type, -1) == NULL) {
+					continue;
+				}
 				clone_stream = ast_stream_clone(req_stream, NULL);
 				if (!clone_stream) {
 					continue;
@@ -1819,6 +1837,21 @@
 					continue;
 				}
 			}
+		}
+	}
+
+	if (ast_stream_topology_get_count(session->req_topology) == 0) {
+		/* We haven't added anything yet to the session->req_topology, either because
+		 * 1) The passed in req_topology had no streams.
+		 * 2) The passed in req_topology had no overlapping capabilities with the endpoint.
+		 */
+		int i;
+
+		for (i = 0; i < ast_stream_topology_get_count(endpoint->media.codecs); ++i) {
+			struct ast_stream *stream;
+
+			stream = ast_stream_topology_get_stream(endpoint->media.codecs, i);
+			add_session_media(session, ast_stream_get_type(stream), -1);
 		}
 	}
 
@@ -2954,27 +2987,26 @@
 	}
 }
 
-static int add_sdp_streams(void *obj, void *arg, void *data, int flags)
+static int add_sdp_streams(struct ast_sip_session_media *session_media,
+	struct ast_sip_session *session, pjmedia_sdp_session *answer,
+	struct ast_stream *stream)
 {
-	struct ast_sip_session_media *session_media = obj;
-	pjmedia_sdp_session *answer = arg;
-	struct ast_sip_session *session = data;
 	struct ast_sip_session_sdp_handler *handler = session_media->handler;
 	RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
 	int res;
 
 	if (handler) {
 		/* if an already assigned handler reports a catastrophic error, fail */
-		res = handler->create_outgoing_sdp_stream(session, session_media, answer);
+		res = handler->create_outgoing_sdp_stream(session, session_media, answer, stream);
 		if (res < 0) {
-			return 0;
+			return -1;
 		}
-		return CMP_MATCH;
+		return 0;
 	}
 
-	handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);
+	handler_list = ao2_find(sdp_handlers, ast_codec_media_type2str(session_media->type), OBJ_KEY);
 	if (!handler_list) {
-		return CMP_MATCH;
+		return 0;
 	}
 
 	/* no handler for this stream type and we have a list to search */
@@ -2982,29 +3014,30 @@
 		if (handler == session_media->handler) {
 			continue;
 		}
-		res = handler->create_outgoing_sdp_stream(session, session_media, answer);
+		res = handler->create_outgoing_sdp_stream(session, session_media, answer, stream);
 		if (res < 0) {
 			/* catastrophic error */
-			return 0;
+			return -1;
 		}
 		if (res > 0) {
 			/* Handled by this handler. Move to the next stream */
 			session_media_set_handler(session_media, handler);
-			return CMP_MATCH;
+			return 0;
 		}
 	}
 
 	/* streams that weren't handled won't be included in generated outbound SDP */
-	return CMP_MATCH;
+	return 0;
 }
 
 static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer)
 {
-	RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);
 	static const pj_str_t STR_IN = { "IN", 2 };
 	static const pj_str_t STR_IP4 = { "IP4", 3 };
 	static const pj_str_t STR_IP6 = { "IP6", 3 };
 	pjmedia_sdp_session *local;
+	struct ast_stream_topology *topology;
+	int i;
 
 	if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
 		ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n");
@@ -3025,11 +3058,35 @@
 	pj_strdup2(inv->pool_prov, &local->origin.user, session->endpoint->media.sdpowner);
 	pj_strdup2(inv->pool_prov, &local->name, session->endpoint->media.sdpsession);
 
-	/* Now let the handlers add streams of various types, pjmedia will automatically reorder the media streams for us */
-	successful = ao2_callback_data(session->media, OBJ_MULTIPLE, add_sdp_streams, local, session);
-	if (!successful || ao2_iterator_count(successful) != ao2_container_count(session->media)) {
-		/* Something experienced a catastrophic failure */
-		return NULL;
+	if (session->req_topology && ast_stream_topology_get_count(session->req_topology)) {
+		/* The most common case. An incoming call, an incoming SDP offer, or an application
+		 * has dictated what streams are requested on this call.
+		 */
+		topology = session->req_topology;
+	} else {
+		/* Weird case. We're originating a call where the codecs were not specified, or we're
+		 * answering an incoming call that had no SDP offer
+		 */
+		topology = session->endpoint->media.codecs;
+	}
+
+	for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
+		struct ast_sip_session_media *session_media;
+		struct ast_stream *stream;
+
+		stream = ast_stream_topology_get_stream(topology, i);
+		if (i < AST_VECTOR_SIZE(&session->media)) {
+			session_media = AST_VECTOR_GET(&session->media, i);
+		} else {
+			session_media = add_session_media(session, ast_stream_get_type(stream), i);
+		}
+
+		if (!session_media) {
+			return NULL;
+		}
+		if (add_sdp_streams(session_media, session, local, stream)) {
+			return NULL;
+		}
 	}
 
 	/* Use the connection details of the first media stream if possible for SDP level */
diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c
index bb1641a..96043d8 100644
--- a/res/res_pjsip_t38.c
+++ b/res/res_pjsip_t38.c
@@ -190,12 +190,29 @@
 	}
 }
 
+/*! \brief Find the first instance of a session media of type "image" */
+static struct ast_sip_session_media *find_image_session_media(struct ast_sip_session *session)
+{
+	int i;
+
+	for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {
+		struct ast_sip_session_media *session_media;
+
+		session_media = AST_VECTOR_GET(&session->media, i);
+		if (session_media->type == AST_MEDIA_TYPE_IMAGE) {
+			return session_media;
+		}
+	}
+
+	return NULL;
+}
+
 /*! \brief Task function which rejects a T.38 re-invite and resumes handling it */
 static int t38_automatic_reject(void *obj)
 {
 	RAII_VAR(struct ast_sip_session *, session, obj, ao2_cleanup);
 	RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "t38"), ao2_cleanup);
-	RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(session->media, "image", OBJ_KEY), ao2_cleanup);
+	struct ast_sip_session_media *session_media = find_image_session_media(session);
 
 	if (!datastore) {
 		return 0;
@@ -292,14 +309,14 @@
 {
 	struct pjsip_status_line status = rdata->msg_info.msg->line.status;
 	struct t38_state *state;
-	RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+	struct ast_sip_session_media *session_media = NULL;
 
 	if (status.code == 100) {
 		return 0;
 	}
 
 	if (!(state = t38_state_get_or_alloc(session)) ||
-		!(session_media = ao2_find(session->media, "image", OBJ_KEY))) {
+		!(session_media = find_image_session_media(session))) {
 		ast_log(LOG_WARNING, "Received response to T.38 re-invite on '%s' but state unavailable\n",
 			ast_channel_name(session->channel));
 		return 0;
@@ -316,7 +333,7 @@
 	RAII_VAR(struct t38_parameters_task_data *, data, obj, ao2_cleanup);
 	const struct ast_control_t38_parameters *parameters = data->frame->data.ptr;
 	struct t38_state *state = t38_state_get_or_alloc(data->session);
-	RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(data->session->media, "image", OBJ_KEY), ao2_cleanup);
+	struct ast_sip_session_media *session_media = find_image_session_media(data->session);
 
 	/* Without session media or state we can't interpret parameters */
 	if (!session_media || !state) {
@@ -417,12 +434,11 @@
 
 		/* Avoid deadlock between chan and the session->media container lock */
 		ast_channel_unlock(chan);
-		session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY);
+		session_media = find_image_session_media(session);
 		ast_channel_lock(chan);
 		if (session_media && session_media->udptl) {
 			ast_udptl_write(session_media->udptl, f);
 		}
-		ao2_cleanup(session_media);
 	}
 
 	return f;
@@ -437,12 +453,11 @@
 
 		/* Avoid deadlock between chan and the session->media container lock */
 		ast_channel_unlock(chan);
-		session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY);
+		session_media = find_image_session_media(session);
 		ast_channel_lock(chan);
 		if (session_media && session_media->udptl) {
 			f = ast_udptl_read(session_media->udptl);
 		}
-		ao2_cleanup(session_media);
 	}
 
 	return f;
@@ -676,11 +691,13 @@
 }
 
 /*! \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 struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
+static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
+	const struct pjmedia_sdp_session *sdp, int index)
 {
 	struct t38_state *state;
 	char host[NI_MAXHOST];
+	struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);
+	pjmedia_sdp_media *stream = sdp->media[index];
 	RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
 
 	if (!session->endpoint->media.t38.enabled) {
@@ -720,7 +737,7 @@
 
 /*! \brief Function which creates an outgoing stream */
 static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
-				      struct pjmedia_sdp_session *sdp)
+				      struct pjmedia_sdp_session *sdp, struct ast_stream *stream)
 {
 	pj_pool_t *pool = session->inv_session->pool_prov;
 	static const pj_str_t STR_IN = { "IN", 2 };
@@ -758,7 +775,7 @@
 		return -1;
 	}
 
-	media->desc.media = pj_str(session_media->stream_type);
+	pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));
 	media->desc.transport = STR_UDPTL;
 
 	if (ast_strlen_zero(session->endpoint->media.address)) {
@@ -827,11 +844,13 @@
 }
 
 /*! \brief Function which applies a negotiated stream */
-static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
-				       const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
-				       const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)
+static int apply_negotiated_sdp_stream(struct ast_sip_session *session, 
+	const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote,
+	int index)
 {
 	RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
+	struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);
+	pjmedia_sdp_media *remote_stream = remote->media[index];
 	char host[NI_MAXHOST];
 	struct t38_state *state;
 

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

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: I144b04f43633387b8e42a43ef3b25d7c5682b451
Gerrit-Change-Number: 5824
Gerrit-PatchSet: 1
Gerrit-Owner: Mark Michelson <mmichelson at digium.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20170613/85fee896/attachment-0001.html>


More information about the asterisk-code-review mailing list