Index: channels/chan_gulp.c
===================================================================
--- channels/chan_gulp.c	(revision 382643)
+++ channels/chan_gulp.c	(working copy)
@@ -61,6 +61,35 @@
 static const char desc[] = "Gulp SIP Channel";
 static const char channel_type[] = "Gulp";
 
+/*!
+ * \brief Positions of various media
+ */
+enum ast_sip_session_media_position {
+	/*! \brief First is audio */
+	AST_SIP_MEDIA_AUDIO = 0,
+	/*! \brief Second is video */
+	AST_SIP_MEDIA_VIDEO,
+	/*! \brief Last is the size for media details */
+	AST_SIP_MEDIA_SIZE,
+};
+
+struct gulp_pvt {
+	struct ast_sip_session *session;
+	struct ast_sip_session_media *media[AST_SIP_MEDIA_SIZE];
+};
+
+static void gulp_pvt_dtor(void *obj)
+{
+	struct gulp_pvt *pvt = obj;
+	int i;
+	ao2_cleanup(pvt->session);
+	pvt->session = NULL;
+	for (i = 0; i < AST_SIP_MEDIA_SIZE; ++i) {
+		ao2_cleanup(pvt->media[i]);
+		pvt->media[i] = NULL;
+	}
+}
+
 /* \brief Asterisk core interaction functions */
 static struct ast_channel *gulp_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
 static int gulp_sendtext(struct ast_channel *ast, const char *text);
@@ -111,14 +140,14 @@
 /*! \brief Function called by RTP engine to get local audio RTP peer */
 static enum ast_rtp_glue_result gulp_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(chan);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(chan);
 
-	if (!session || !session->media[AST_SIP_MEDIA_AUDIO].rtp) {
+	if (!pvt || !pvt->session || !pvt->media[AST_SIP_MEDIA_AUDIO]->rtp) {
 		return AST_RTP_GLUE_RESULT_FORBID;
 	}
 
-	ao2_ref(session->media[AST_SIP_MEDIA_AUDIO].rtp, +1);
-	*instance = session->media[AST_SIP_MEDIA_AUDIO].rtp;
+	ao2_ref(pvt->media[AST_SIP_MEDIA_AUDIO]->rtp, +1);
+	*instance = pvt->media[AST_SIP_MEDIA_AUDIO]->rtp;
 
 	return AST_RTP_GLUE_RESULT_LOCAL;
 }
@@ -147,16 +176,25 @@
 {
 	struct ast_channel *chan;
 	struct ast_format fmt;
+	struct gulp_pvt *pvt;
 
+	if (!(pvt = ao2_alloc(sizeof(*pvt), gulp_pvt_dtor))) {
+		return NULL;
+	}
+
 	if (!(chan = ast_channel_alloc(1, state, "", S_OR(cid_name, ""), "", "", "", linkedid, 0, "Gulp/%s-%.*s", ast_sorcery_object_get_id(session->endpoint),
 		(int)session->inv_session->dlg->call_id->id.slen, session->inv_session->dlg->call_id->id.ptr))) {
+		ao2_cleanup(pvt);
 		return NULL;
 	}
 
 	ast_channel_tech_set(chan, &gulp_tech);
 
 	ao2_ref(session, +1);
-	ast_channel_tech_pvt_set(chan, session);
+	pvt->session = session;
+	pvt->media[AST_SIP_MEDIA_AUDIO] = ao2_find(session->media, "audio", OBJ_KEY);
+	pvt->media[AST_SIP_MEDIA_VIDEO] = ao2_find(session->media, "video", OBJ_KEY);
+	ast_channel_tech_pvt_set(chan, pvt);
 
 	ast_format_cap_copy(ast_channel_nativeformats(chan), session->endpoint->codecs);
 	ast_codec_choose(&session->endpoint->prefs, session->endpoint->codecs, 1, &fmt);
@@ -196,7 +234,8 @@
 /*! \brief Function called by core when we should answer a Gulp session */
 static int gulp_answer(struct ast_channel *ast)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(ast);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+	struct ast_sip_session *session = pvt->session;
 
 	if (ast_channel_state(ast) == AST_STATE_UP) {
 		return 0;
@@ -216,15 +255,15 @@
 /*! \brief Function called by core to read any waiting frames */
 static struct ast_frame *gulp_read(struct ast_channel *ast)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(ast);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
 	struct ast_frame *f;
 
 	switch (ast_channel_fdno(ast)) {
 	case 0:
-		f = ast_rtp_instance_read(session->media[AST_SIP_MEDIA_AUDIO].rtp, 0);
+		f = ast_rtp_instance_read(pvt->media[AST_SIP_MEDIA_AUDIO]->rtp, 0);
 		break;
 	case 1:
-		f = ast_rtp_instance_read(session->media[AST_SIP_MEDIA_AUDIO].rtp, 1);
+		f = ast_rtp_instance_read(pvt->media[AST_SIP_MEDIA_AUDIO]->rtp, 1);
 		break;
 	default:
 		f = &ast_null_frame;
@@ -245,7 +284,7 @@
 /*! \brief Function called by core to write frames */
 static int gulp_write(struct ast_channel *ast, struct ast_frame *frame)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(ast);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
 	int res = 0;
 
 	switch (frame->frametype) {
@@ -261,8 +300,8 @@
 				ast_getformatname(ast_channel_writeformat(ast)));
 			return 0;
 		}
-		if (session->media[AST_SIP_MEDIA_AUDIO].rtp) {
-			res = ast_rtp_instance_write(session->media[AST_SIP_MEDIA_AUDIO].rtp, frame);
+		if (pvt->media[AST_SIP_MEDIA_AUDIO]->rtp) {
+			res = ast_rtp_instance_write(pvt->media[AST_SIP_MEDIA_AUDIO]->rtp, frame);
 		}
 		break;
 	default:
@@ -288,7 +327,8 @@
 /*! \brief Function called by core to change the underlying owner channel */
 static int gulp_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(newchan);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(newchan);
+	struct ast_sip_session *session = pvt->session;
 	struct fixup_data fix_data;
 	fix_data.session = session;
 	fix_data.chan = newchan;
@@ -360,7 +400,8 @@
 static int gulp_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
 {
 	int res = 0;
-	struct ast_sip_session *session = ast_channel_tech_pvt(ast);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+	struct ast_sip_session *session = pvt->session;
 	int response_code = 0;
 
 	switch (condition) {
@@ -449,13 +490,14 @@
 /*! \brief Function called by core to start a DTMF digit */
 static int gulp_digit_begin(struct ast_channel *chan, char digit)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(chan);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(chan);
+	struct ast_sip_session *session = pvt->session;
 	int res = 0;
 
 	switch (session->endpoint->dtmf) {
 	case AST_SIP_DTMF_RFC_4733:
-		if (session->media[AST_SIP_MEDIA_AUDIO].rtp) {
-			ast_rtp_instance_dtmf_begin(session->media[AST_SIP_MEDIA_AUDIO].rtp, digit);
+		if (pvt->media[AST_SIP_MEDIA_AUDIO]->rtp) {
+			ast_rtp_instance_dtmf_begin(pvt->media[AST_SIP_MEDIA_AUDIO]->rtp, digit);
 		}
 	case AST_SIP_DTMF_NONE:
 		break;
@@ -472,7 +514,8 @@
 /*! \brief Function called by core to stop a DTMF digit */
 static int gulp_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(ast);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+	struct ast_sip_session *session = pvt->session;
 	int res = 0;
 
 	switch (session->endpoint->dtmf) {
@@ -480,8 +523,8 @@
 		/* TODO: Send INFO dtmf here */
 		break;
 	case AST_SIP_DTMF_RFC_4733:
-		if (session->media[AST_SIP_MEDIA_AUDIO].rtp) {
-			ast_rtp_instance_dtmf_end_with_duration(session->media[AST_SIP_MEDIA_AUDIO].rtp, digit, duration);
+		if (pvt->media[AST_SIP_MEDIA_AUDIO]->rtp) {
+			ast_rtp_instance_dtmf_end_with_duration(pvt->media[AST_SIP_MEDIA_AUDIO]->rtp, digit, duration);
 		}
 	case AST_SIP_DTMF_NONE:
 		break;
@@ -511,7 +554,8 @@
 /*! \brief Function called by core to actually start calling a remote party */
 static int gulp_call(struct ast_channel *ast, const char *dest, int timeout)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(ast);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+	struct ast_sip_session *session = pvt->session;
 
 	ao2_ref(session, +1);
 	if (ast_sip_push_task_synchronous(session->serializer, call, session)) {
@@ -598,7 +642,8 @@
 	pjsip_tx_data *packet = NULL;
 	struct hangup_data *h_data = data;
 	struct ast_channel *ast = h_data->chan;
-	struct ast_sip_session *session = ast_channel_tech_pvt(ast);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+	struct ast_sip_session *session = pvt->session;
 	int cause = h_data->cause;
 
 	if (((status = pjsip_inv_end_session(session->inv_session, cause ? cause : 603, NULL, &packet)) == PJ_SUCCESS) && packet) {
@@ -612,7 +657,7 @@
 	session->channel = NULL;
 	ast_channel_tech_pvt_set(ast, NULL);
 
-	ao2_cleanup(session);
+	ao2_cleanup(pvt);
 	ao2_cleanup(h_data);
 	return 0;
 }
@@ -620,7 +665,8 @@
 /*! \brief Function called by core to hang up a Gulp session */
 static int gulp_hangup(struct ast_channel *ast)
 {
-	struct ast_sip_session *session = ast_channel_tech_pvt(ast);
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+	struct ast_sip_session *session = pvt->session;
 	int cause = hangup_cause2sip(ast_channel_hangupcause(session->channel));
 	struct hangup_data *h_data = hangup_data_alloc(cause, ast);
 	if (!h_data) {
@@ -641,7 +687,7 @@
 	session->channel = NULL;
 	ast_channel_tech_pvt_set(ast, NULL);
 
-	ao2_cleanup(session);
+	ao2_cleanup(pvt);
 	return -1;
 }
 
Index: include/asterisk/res_sip_session.h
===================================================================
--- include/asterisk/res_sip_session.h	(revision 382643)
+++ include/asterisk/res_sip_session.h	(working copy)
@@ -37,17 +37,7 @@
 struct pjmedia_sdp_session;
 struct ast_rtp_instance;
 
-/*!
- * \brief Positions of various media
- */
-enum ast_sip_session_media_position {
-	/*! \brief First is audio */
-	AST_SIP_MEDIA_AUDIO = 0,
-	/*! \brief Second is video */
-	AST_SIP_MEDIA_VIDEO,
-	/*! \brief Last is the size for media details */
-	AST_SIP_MEDIA_SIZE,
-};
+struct ast_sip_session_sdp_handler;
 
 /*!
  * \brief A structure containing SIP session media information
@@ -55,8 +45,12 @@
 struct ast_sip_session_media {
 	/*! \brief RTP instance itself */
 	struct ast_rtp_instance *rtp;
+	/*! \brief SDP handler that setup the RTP */
+	struct ast_sip_session_sdp_handler *handler;
 	/*! \brief Stream is on hold */
 	unsigned int held:1;
+	/*! \brief Stream type this session media handles */
+	char stream_type[1];
 };
 
 /*!
@@ -81,7 +75,7 @@
 	/* Datastores added to the session by supplements to the session */
 	struct ao2_container *datastores;
 	/* Media streams */
-	struct ast_sip_session_media media[AST_SIP_MEDIA_SIZE];
+	struct ao2_container *media;
 	/* Serializer for tasks relating to this SIP session */
 	struct ast_taskprocessor *serializer;
 };
@@ -166,34 +160,38 @@
 	/*!
 	 * \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
 	 * \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, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream);
+	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);
 	/*!
 	 * \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
+	 * \param session_media The media to be setup for this session
 	 * \param stream The stream on which to operate
 	 * \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 (*handle_incoming_sdp_stream)(struct ast_sip_session *session, const struct pjmedia_sdp_session *sdp, struct pjmedia_sdp_media *stream);
+	int (*handle_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, struct pjmedia_sdp_media *stream);
 	/*!
 	 * \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
+	 * \param session_media The media to be setup for this session
 	 * \param sdp The entire SDP as currently built
 	 * \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 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);
 	/*!
 	 * \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
@@ -202,9 +200,15 @@
 	 * \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, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
+	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);
-	/*! Next item int he list. */
+	/*!
+	 * \brief Destroy a session_media created by this handler
+	 * \param session The session for which media is being destroyed
+	 * \param session_media The media to destroy
+	 */
+	void (*stream_destroy)(struct ast_sip_session_media *session_media);
+	/*! Next item in the list. */
 	AST_LIST_ENTRY(ast_sip_session_sdp_handler) next;
 };
 
Index: res/res_sip_session.c
===================================================================
--- res/res_sip_session.c	(revision 382643)
+++ res/res_sip_session.c	(working copy)
@@ -89,6 +89,23 @@
 	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)
 {
 	RAII_VAR(struct sdp_handler_list *, handler_list,
@@ -156,9 +173,41 @@
 	ao2_callback_data(sdp_handlers, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, remove_handler, (void *)stream_type, handler);
 }
 
+static int sdp_validate_cb(void *obj, void *arg, int flags)
+{
+	int *port = obj;
+	int *newport = arg;
+	if (*port == *newport) {
+		return CMP_MATCH;
+	}
+	return 0;
+}
+
+static int validate_incoming_sdp(const pjmedia_sdp_session *sdp)
+{
+	int i;
+	RAII_VAR(struct ao2_container *, portlist, ao2_container_alloc(1, NULL, NULL), ao2_cleanup);
+	for (i = 0; i < sdp->media_count; ++i) {
+		RAII_VAR(int *, port, ao2_alloc(sizeof(int), NULL), ao2_cleanup);
+		RAII_VAR(int *, match, NULL, ao2_cleanup);
+		*port = sdp->media[i]->desc.port;
+		match = ao2_callback(portlist, 0, sdp_validate_cb, port);
+		if (match) {
+			/* This port already exists elsewhere in the SDP, fail catastrophically */
+			return -1;
+		}
+		ao2_link(portlist, port);
+	}
+	return 0;
+}
+
 static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp)
 {
 	int i;
+	if (validate_incoming_sdp(sdp)) {
+		return -1;
+	}
+
 	for (i = 0; i < sdp->media_count; ++i) {
 		/* See if there are registered handlers for this media stream type */
 		char media[20];
@@ -174,15 +223,22 @@
 			continue;
 		}
 		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
-			int res = handler->negotiate_incoming_sdp_stream(session, sdp, sdp->media[i]);
+			int res;
+			RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+			session_media = ao2_find(session->media, handler_list->stream_type, OBJ_KEY);
+			if (!session_media || session_media->handler) {
+				/* There is only one slot for this stream type and it has already been claimed
+				 * so it will go unhandled */
+				break;
+			}
+			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, sdp->media[i]);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
 				return -1;
-			} else if (res == 0) {
-				/* Not handled yet. Move to the next handler */
-				continue;
-			} else {
+			}
+			if (res > 0) {
 				/* Handled by this handler. Move to the next stream */
+				session_media->handler = handler;
 				break;
 			}
 		}
@@ -190,9 +246,21 @@
 	return 0;
 }
 
-static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote)
+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)
 {
+	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;
+
 	for (i = 0; i < local->media_count; ++i) {
 		/* See if there are registered handlers for this media stream type */
 		char media[20];
@@ -202,28 +270,58 @@
 		/* 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) {
+			int res = handler->apply_negotiated_sdp_stream(session, session_media, local, local->media[i], remote, remote->media[i]);
+			if (res >= 0) {
+				return CMP_MATCH;
+			}
+			return 0;
+		}
+
 		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);
 			continue;
 		}
 		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
-			int res = handler->apply_negotiated_sdp_stream(session, local, local->media[i], remote, remote->media[i]);
+			int res = handler->apply_negotiated_sdp_stream(session, session_media, local, local->media[i], remote, remote->media[i]);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
-				return -1;
-			} else if (res == 0) {
-				/* Not handled yet. Move to the next handler */
-				continue;
-			} else {
+				return 0;
+			}
+			if (res > 0) {
 				/* Handled by this handler. Move to the next stream */
-				break;
+				session_media->handler = handler;
+				return CMP_MATCH;
 			}
 		}
 	}
-	return 0;
+	return CMP_MATCH;
 }
 
+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,
+	};
+
+	successful = ao2_callback(session->media, OBJ_MULTIPLE, handle_negotiated_sdp_session_media, &callback_data);
+	if (successful && ao2_container_count(successful->c) == ao2_container_count(session->media)) {
+		/* Nothing experienced a catastrophic failure */
+		return 0;
+	}
+	return -1;
+}
+
 AST_RWLIST_HEAD_STATIC(session_supplements, ast_sip_session_supplement);
 
 int ast_sip_session_register_supplement(struct ast_sip_session_supplement *supplement)
@@ -260,6 +358,7 @@
 }
 
 #define DATASTORE_BUCKETS 53
+#define MEDIA_BUCKETS 7
 
 static void session_datastore_destroy(void *obj)
 {
@@ -453,6 +552,14 @@
 	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;
+	if (session_media->handler) {
+		session_media->handler->stream_destroy(session_media);
+	}
+}
+
 static void session_destructor(void *obj)
 {
 	struct ast_sip_session *session = obj;
@@ -465,8 +572,10 @@
 		}
 		ast_free(supplement);
 	}
+
 	ast_taskprocessor_unreference(session->serializer);
 	ao2_cleanup(session->datastores);
+	ao2_cleanup(session->media);
 	AST_LIST_HEAD_DESTROY(&session->supplements);
 	ao2_cleanup(session->endpoint);
 }
@@ -486,6 +595,21 @@
 	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;
+	}
+	/* Safe use of strcpy */
+	strcpy(session_media->stream_type, handler_list->stream_type);
+	ao2_link(session->media, session_media);
+	return 0;
+}
+
 struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, pjsip_inv_session *inv_session)
 {
 	RAII_VAR(struct ast_sip_session *, session, ao2_alloc(sizeof(*session), session_destructor), ao2_cleanup);
@@ -498,6 +622,14 @@
 	if (!session->datastores) {
 		return NULL;
 	}
+
+	session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp);
+	if (!session->media) {
+		return NULL;
+	}
+	/* fill session->media with available types */
+	ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session);
+
 	session->serializer = ast_sip_create_serializer();
 	if (!session->serializer) {
 		return NULL;
@@ -1035,26 +1167,45 @@
 
 static int add_sdp_streams(void *obj, void *arg, void *data, int flags)
 {
+	struct ast_sip_session_media *session_media = obj;
 	pjmedia_sdp_session *answer = arg;
-	struct sdp_handler_list *handler_list = obj;
-	struct ast_sip_session_sdp_handler *handler;
 	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);
 
+	if (handler) {
+		/* if an already assigned handler does not handle the session_media or reports a catastrophic error, fail */
+		if (handler->create_outgoing_sdp_stream(session, session_media, answer) <= 0) {
+			return 0;
+		}
+		return CMP_MATCH;
+	}
+
+	handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);
+	if (!handler_list) {
+		return CMP_MATCH;
+	}
+
+	/* no handler for this stream type and we have a list to search */
 	AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
-		int res = handler->create_outgoing_sdp_stream(session, answer);
+		int res = handler->create_outgoing_sdp_stream(session, session_media, answer);
 		if (res < 0) {
-			/* Catastrophic failure. Completely stop processing */
-			return CMP_STOP;
-		} else if (res > 0) {
-			/* This stream type has been handled. Move on to the next type */
+			/* catastrophic error */
 			return 0;
 		}
+		if (res > 0) {
+			/* handled */
+			return CMP_MATCH;
+		}
 	}
-	return 0;
+
+	/* streams that weren't handled won't be included in generated outbound SDP */
+	return CMP_MATCH;
 }
 
 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_ASTERISK = { "Asterisk", 8 };
 	static const pj_str_t STR_IN = { "IN", 2 };
 	static const pj_str_t STR_IP4 = { "IP4", 3 };
@@ -1078,8 +1229,16 @@
 	local->origin.addr = *pj_gethostname();
 	local->name = local->origin.user;
 
+	if (validate_incoming_sdp(offer)) {
+		return NULL;
+	}
+
 	/* Now let the handlers add streams of various types, pjmedia will automatically reorder the media streams for us */
-	ao2_callback_data(sdp_handlers, 0, add_sdp_streams, local, session);
+	successful = ao2_callback_data(session->media, OBJ_MULTIPLE, add_sdp_streams, local, session);
+	if (!successful || ao2_container_count(successful->c) != ao2_container_count(session->media)) {
+		/* Something experienced a catastrophic failure */
+		return NULL;
+	}
 
 	/* Use the connection details of the first media stream if possible for SDP level */
 	if (local->media_count) {
Index: res/res_sip_sdp_audio.c
===================================================================
--- res/res_sip_sdp_audio.c	(revision 382643)
+++ res/res_sip_sdp_audio.c	(working copy)
@@ -54,13 +54,13 @@
 static struct ast_sched_context *sched;
 
 /*! \brief Forward declarations for SDP handler functions */
-static int audio_negotiate_incoming_sdp_stream(struct ast_sip_session *session, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream);
-static int audio_create_outgoing_sdp_stream(struct ast_sip_session *session, struct pjmedia_sdp_session *sdp);
-static int audio_apply_negotiated_sdp_stream(struct ast_sip_session *session, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
+static int audio_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 audio_create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp);
+static int audio_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);
 
 /*! \brief Forward declaration for session supplement functions */
-static void audio_session_destroy(struct ast_sip_session *session);
+static void audio_stream_destroy(struct ast_sip_session_media *session_media);
 
 /*! \brief SDP handler for 'audio' media stream */
 static struct ast_sip_session_sdp_handler audio_sdp_handler = {
@@ -68,15 +68,11 @@
 	.negotiate_incoming_sdp_stream = audio_negotiate_incoming_sdp_stream,
 	.create_outgoing_sdp_stream = audio_create_outgoing_sdp_stream,
 	.apply_negotiated_sdp_stream = audio_apply_negotiated_sdp_stream,
+	.stream_destroy = audio_stream_destroy,
 };
 
-/*! \brief Session supplement for 'audio' media stream */
-static struct ast_sip_session_supplement audio_session_supplement = {
-	.session_destroy = audio_session_destroy,
-};
-
 /*! \brief Internal function which creates an RTP instance */
-static int audio_create_rtp(struct ast_sip_session *session, unsigned int ipv6)
+static int audio_create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media, unsigned int ipv6)
 {
 	pj_sockaddr addr;
 	char hostip[PJ_INET6_ADDRSTRLEN+2];
@@ -92,21 +88,21 @@
 		return -1;
 	}
 
-	if (!(session->media[AST_SIP_MEDIA_AUDIO].rtp = ast_rtp_instance_new("asterisk", sched, &tmp, NULL))) {
+	if (!(session_media->rtp = ast_rtp_instance_new("asterisk", sched, &tmp, NULL))) {
 		return -1;
 	}
 
-	ast_rtp_instance_set_prop(session->media[AST_SIP_MEDIA_AUDIO].rtp, AST_RTP_PROPERTY_RTCP, 1);
-	ast_rtp_instance_set_prop(session->media[AST_SIP_MEDIA_AUDIO].rtp, AST_RTP_PROPERTY_NAT, session->endpoint->rtp_symmetric);
+	ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RTCP, 1);
+	ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_NAT, session->endpoint->rtp_symmetric);
 
-	ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(session->media[AST_SIP_MEDIA_AUDIO].rtp),
-					 session->media[AST_SIP_MEDIA_AUDIO].rtp, &session->endpoint->prefs);
+	ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(session_media->rtp),
+					 session_media->rtp, &session->endpoint->prefs);
 
 	return 0;
 }
 
 /*! \brief Function which negotiates an incoming 'audio' stream */
-static int audio_negotiate_incoming_sdp_stream(struct ast_sip_session *session, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
+static int audio_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)
 {
 	char host[NI_MAXHOST];
 	RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free_ptr);
@@ -125,7 +121,7 @@
 	}
 
 	/* Using the connection information create an appropriate RTP instance */
-	if (!session->media[AST_SIP_MEDIA_AUDIO].rtp && audio_create_rtp(session, ast_sockaddr_is_ipv6(addrs))) {
+	if (!session_media->rtp && audio_create_rtp(session, session_media, ast_sockaddr_is_ipv6(addrs))) {
 		return -1;
 	}
 
@@ -134,7 +130,7 @@
 }
 
 /*! \brief Function which creates an outgoing 'audio' stream */
-static int audio_create_outgoing_sdp_stream(struct ast_sip_session *session, struct pjmedia_sdp_session *sdp)
+static int audio_create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp)
 {
 	static const pj_str_t STR_AUDIO = { "audio", 5};
 	static const pj_str_t STR_IN = { "IN", 2 };
@@ -153,7 +149,7 @@
 	if (!ast_format_cap_has_type(session->endpoint->codecs, AST_FORMAT_TYPE_AUDIO)) {
 		/* If no audio formats are configured don't add a stream */
 		return 0;
-	} else if (!session->media[AST_SIP_MEDIA_AUDIO].rtp && audio_create_rtp(session, session->endpoint->rtp_ipv6)) {
+	} else if (!session_media->rtp && audio_create_rtp(session, session_media, session->endpoint->rtp_ipv6)) {
 		return -1;
 	}
 
@@ -167,7 +163,7 @@
 	media->desc.transport = STR_RTP_AVP;
 
 	/* Add connection level details */
-	ast_rtp_instance_get_local_address(session->media[AST_SIP_MEDIA_AUDIO].rtp, &addr);
+	ast_rtp_instance_get_local_address(session_media->rtp, &addr);
 	media->conn->net_type = STR_IN;
 	media->conn->addr_type = (ast_sockaddr_is_ipv6(&addr) && !ast_sockaddr_is_ipv4_mapped(&addr)) ? STR_IP6 : STR_IP4;
 	pj_strdup2(pool, &media->conn->addr, ast_sockaddr_stringify_addr_remote(&addr));
@@ -179,13 +175,13 @@
 		struct ast_format format;
 		int rtp_code;
 		pjmedia_sdp_rtpmap rtpmap;
-		struct ast_codec_pref *pref = &ast_rtp_instance_get_codecs(session->media[AST_SIP_MEDIA_AUDIO].rtp)->pref;
+		struct ast_codec_pref *pref = &ast_rtp_instance_get_codecs(session_media->rtp)->pref;
 
 		if (!ast_codec_pref_index(&session->endpoint->prefs, index, &format)) {
 			break;
 		} else if (AST_FORMAT_GET_TYPE(format.id) != AST_FORMAT_TYPE_AUDIO) {
 			continue;
-		} else if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session->media[AST_SIP_MEDIA_AUDIO].rtp),
+		} else if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp),
 								   1, &format, 0)) == -1) {
 			return -1;
 		}
@@ -213,7 +209,7 @@
 		int rtp_code;
 		pjmedia_sdp_rtpmap rtpmap;
 
-		if (!(noncodec & index) || (rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session->media[AST_SIP_MEDIA_AUDIO].rtp),
+		if (!(noncodec & index) || (rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp),
 										   0, NULL, index)) == -1) {
 			continue;
 		}
@@ -254,7 +250,7 @@
 }
 
 /*! \brief Function which applies a negotiated SDP stream */
-static int audio_apply_negotiated_sdp_stream(struct ast_sip_session *session, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
+static int audio_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 format, othercapability = 0;
@@ -268,7 +264,7 @@
 	struct ast_format fmt;
 
 	/* Create an RTP instance if need be */
-	if (!session->media[AST_SIP_MEDIA_AUDIO].rtp && audio_create_rtp(session, session->endpoint->rtp_ipv6)) {
+	if (!session_media->rtp && audio_create_rtp(session, session_media, session->endpoint->rtp_ipv6)) {
 		return -1;
 	}
 
@@ -289,7 +285,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[AST_SIP_MEDIA_AUDIO].rtp, addrs);
+	ast_rtp_instance_set_remote_address(session_media->rtp, addrs);
 
 	ast_rtp_codecs_payloads_initialize(&codecs);
 
@@ -320,11 +316,11 @@
 		pj_str_t value = attr->value;
 		unsigned long framing = pj_strtoul(pj_strltrim(&value));
 		int codec;
-		struct ast_codec_pref *pref = &ast_rtp_instance_get_codecs(session->media[AST_SIP_MEDIA_AUDIO].rtp)->pref;
+		struct ast_codec_pref *pref = &ast_rtp_instance_get_codecs(session_media->rtp)->pref;
 
 		for (codec = 0; codec < AST_RTP_MAX_PT; codec++) {
 			struct ast_rtp_payload_type format = ast_rtp_codecs_payload_lookup(ast_rtp_instance_get_codecs(
-				session->media[AST_SIP_MEDIA_AUDIO].rtp), codec);
+				session_media->rtp), codec);
 
 			if (!format.asterisk_format) {
 				continue;
@@ -333,8 +329,8 @@
 			ast_codec_pref_setsize(pref, &format.format, framing);
 		}
 
-		ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(session->media[AST_SIP_MEDIA_AUDIO].rtp),
-			session->media[AST_SIP_MEDIA_AUDIO].rtp, pref);
+		ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(session_media->rtp),
+			session_media->rtp, pref);
 	}
 
 	/* Using the configured codecs and the codecs in this SDP we determine the joint formats for *audio only* */
@@ -353,8 +349,8 @@
 		return -1;
 	}
 
-	ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session->media[AST_SIP_MEDIA_AUDIO].rtp),
-				     session->media[AST_SIP_MEDIA_AUDIO].rtp);
+	ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),
+				     session_media->rtp);
 
 	/* Now that we have joint formats for audio remove the existing ones from the channel and add the new ones */
 	ast_format_cap_copy(cap, ast_channel_nativeformats(session->channel));
@@ -369,25 +365,25 @@
 	ast_set_read_format(session->channel, ast_channel_readformat(session->channel));
 	ast_set_write_format(session->channel, ast_channel_writeformat(session->channel));
 
-	ast_channel_set_fd(session->channel, 0, ast_rtp_instance_fd(session->media[AST_SIP_MEDIA_AUDIO].rtp, 0));
-	ast_channel_set_fd(session->channel, 1, ast_rtp_instance_fd(session->media[AST_SIP_MEDIA_AUDIO].rtp, 1));
+	ast_channel_set_fd(session->channel, 0, ast_rtp_instance_fd(session_media->rtp, 0));
+	ast_channel_set_fd(session->channel, 1, ast_rtp_instance_fd(session_media->rtp, 1));
 
-	if (session->media[AST_SIP_MEDIA_AUDIO].held && (!ast_sockaddr_isnull(addrs) ||
+	if (session_media->held && (!ast_sockaddr_isnull(addrs) ||
 							  !pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL))) {
 		/* The remote side has taken us off hold */
 		ast_queue_control(session->channel, AST_CONTROL_UNHOLD);
 		ast_queue_frame(session->channel, &ast_null_frame);
-		session->media[AST_SIP_MEDIA_AUDIO].held = 0;
+		session_media->held = 0;
 	} else if (ast_sockaddr_isnull(addrs) || ast_sockaddr_is_any(addrs) || pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL)) {
 		/* The remote side has put us on hold */
 		ast_queue_control_data(session->channel, AST_CONTROL_HOLD, S_OR(session->endpoint->mohsuggest, NULL),
 				       !ast_strlen_zero(session->endpoint->mohsuggest) ? strlen(session->endpoint->mohsuggest) + 1 : 0);
-		ast_rtp_instance_stop(session->media[AST_SIP_MEDIA_AUDIO].rtp);
+		ast_rtp_instance_stop(session_media->rtp);
 		ast_queue_frame(session->channel, &ast_null_frame);
-		session->media[AST_SIP_MEDIA_AUDIO].held = 1;
+		session_media->held = 1;
 	} else {
 		/* The remote side has not changed state, but make sure the instance is active */
-		ast_rtp_instance_activate(session->media[AST_SIP_MEDIA_AUDIO].rtp);
+		ast_rtp_instance_activate(session_media->rtp);
 	}
 
 	ast_rtp_codecs_payloads_destroy(&codecs);
@@ -395,14 +391,14 @@
 }
 
 /*! \brief Function which destroys the audio RTP instance when session ends */
-static void audio_session_destroy(struct ast_sip_session *session)
+static void audio_stream_destroy(struct ast_sip_session_media *session_media)
 {
-	if (!session->media[AST_SIP_MEDIA_AUDIO].rtp) {
+	if (!session_media->rtp) {
 		return;
 	}
 
-	ast_rtp_instance_stop(session->media[AST_SIP_MEDIA_AUDIO].rtp);
-	ast_rtp_instance_destroy(session->media[AST_SIP_MEDIA_AUDIO].rtp);
+	ast_rtp_instance_stop(session_media->rtp);
+	ast_rtp_instance_destroy(session_media->rtp);
 }
 
 /*!
@@ -427,11 +423,6 @@
 		goto end;
 	}
 
-	if (ast_sip_session_register_supplement(&audio_session_supplement)) {
-		ast_log(LOG_ERROR, "Unable to register session supplement for audio media stream\n");
-		goto end;
-	}
-
 	if (ast_sip_session_register_sdp_handler(&audio_sdp_handler, "audio")) {
 		ast_log(LOG_ERROR, "Unable to register SDP handler for 'audio' stream type\n");
 		goto end;
@@ -443,8 +434,6 @@
 		ast_sched_context_destroy(sched);
 	}
 
-	ast_sip_session_unregister_supplement(&audio_session_supplement);
-
 	return AST_MODULE_LOAD_FAILURE;
 }
 
@@ -452,7 +441,6 @@
 static int unload_module(void)
 {
 	ast_sip_session_unregister_sdp_handler(&audio_sdp_handler, "audio");
-	ast_sip_session_unregister_supplement(&audio_session_supplement);
 	ast_sched_context_destroy(sched);
 	return 0;
 }
