[Asterisk-code-review] chan pjsip: Multistream: Actually do it. (asterisk[master])

Joshua Colp asteriskteam at digium.com
Sun Jun 18 17:05:48 CDT 2017


Joshua Colp has uploaded this change for review. ( https://gerrit.asterisk.org/5876


Change subject: chan_pjsip: Multistream: Actually do it.
......................................................................

chan_pjsip: Multistream: Actually do it.

This change allows chan_pjsip to negotiate and use
multiple streams on both incoming and outgoing SDP
negotiation.

For both offering and answering we will restrict what
streams are allowed by declining when the maximum
number of configured streams is reached. This allows
us to maintain the proper ordering for streams.

When sending an offer we will use the topology that
has been provided by a calling party or what has
been provided by an application.

When sending an answer we will accept how ever many
streams has been configured as the maximum and decline
any further streams. In the future it will be up to
the application currently handling the channel to
provide a response topology back.

When both of these negotiations are complete the
pending media updates are applied to PJSIP and the
Asterisk channel. This results in an updated stream
topology which is communicated to the application
currently handling the channel.

Some other bonus things as a result of this is that
some of the logic for handling media reading and writing
is now done via an API which allows it to more easily
live outside of chan_pjsip itself.

The res_pjsip_sdp_rtp has now also gained basic support
for declining media streams.

For the case of direct media, however, only the first
audio and video streams will be directly reinvited. In
the future when bridge_native_rtp is updated to understand
streams and the RTP glue API extended this could be improved.

Change-Id: I8afd8dd2eb538806a39b887af0abd046266e14c7
---
M channels/chan_pjsip.c
M channels/pjsip/cli_commands.c
M channels/pjsip/dialplan_functions.c
M channels/pjsip/include/chan_pjsip.h
M include/asterisk/res_pjsip_session.h
M res/res_pjsip_sdp_rtp.c
M res/res_pjsip_session.c
M res/res_pjsip_session.exports.in
M res/res_pjsip_t38.c
9 files changed, 893 insertions(+), 464 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/76/5876/1

diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index ec6e3a3..d014348 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -77,27 +77,19 @@
 
 static unsigned int chan_idx;
 
-static void chan_pjsip_pvt_dtor(void *obj)
-{
-	struct chan_pjsip_pvt *pvt = obj;
-	int i;
-
-	for (i = 0; i < SIP_MEDIA_SIZE; ++i) {
-		ao2_cleanup(pvt->media[i]);
-		pvt->media[i] = NULL;
-	}
-}
-
 /* \brief Asterisk core interaction functions */
 static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
+static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type,
+	struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids,
+	const struct ast_channel *requestor, const char *data, int *cause);
 static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text);
 static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit);
 static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
 static int chan_pjsip_call(struct ast_channel *ast, const char *dest, int timeout);
 static int chan_pjsip_hangup(struct ast_channel *ast);
 static int chan_pjsip_answer(struct ast_channel *ast);
-static struct ast_frame *chan_pjsip_read(struct ast_channel *ast);
-static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f);
+static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast);
+static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *f);
 static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
 static int chan_pjsip_transfer(struct ast_channel *ast, const char *target);
 static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
@@ -110,16 +102,16 @@
 	.type = channel_type,
 	.description = "PJSIP Channel Driver",
 	.requester = chan_pjsip_request,
+	.requester_with_stream_topology = chan_pjsip_request_with_stream_topology,
 	.send_text = chan_pjsip_sendtext,
 	.send_digit_begin = chan_pjsip_digit_begin,
 	.send_digit_end = chan_pjsip_digit_end,
 	.call = chan_pjsip_call,
 	.hangup = chan_pjsip_hangup,
 	.answer = chan_pjsip_answer,
-	.read = chan_pjsip_read,
-	.write = chan_pjsip_write,
-	.write_video = chan_pjsip_write,
-	.exception = chan_pjsip_read,
+	.read_stream = chan_pjsip_read_stream,
+	.write_stream = chan_pjsip_write_stream,
+	.exception = chan_pjsip_read_stream,
 	.indicate = chan_pjsip_indicate,
 	.transfer = chan_pjsip_transfer,
 	.fixup = chan_pjsip_fixup,
@@ -160,11 +152,20 @@
 static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
-	struct chan_pjsip_pvt *pvt;
 	struct ast_sip_endpoint *endpoint;
 	struct ast_datastore *datastore;
+	struct ast_sip_session_media *media;
 
-	if (!channel || !channel->session || !(pvt = channel->pvt) || !pvt->media[SIP_MEDIA_AUDIO]->rtp) {
+	if (!channel || !channel->session) {
+		return AST_RTP_GLUE_RESULT_FORBID;
+	}
+
+	/* XXX Getting the first RTP instance for direct media related stuff seems just
+	 * absolutely wrong. But the native RTP bridge knows no other method than single-stream
+	 * for direct media. So this is the best we can do.
+	 */
+	media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+	if (!media || !media->rtp) {
 		return AST_RTP_GLUE_RESULT_FORBID;
 	}
 
@@ -176,7 +177,7 @@
 
 	endpoint = channel->session->endpoint;
 
-	*instance = pvt->media[SIP_MEDIA_AUDIO]->rtp;
+	*instance = media->rtp;
 	ao2_ref(*instance, +1);
 
 	ast_assert(endpoint != NULL);
@@ -195,16 +196,21 @@
 static enum ast_rtp_glue_result chan_pjsip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
 	struct ast_sip_endpoint *endpoint;
+	struct ast_sip_session_media *media;
 
-	if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO] || !pvt->media[SIP_MEDIA_VIDEO]->rtp) {
+	if (!channel || !channel->session) {
+		return AST_RTP_GLUE_RESULT_FORBID;
+	}
+
+	media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
+	if (!media || !media->rtp) {
 		return AST_RTP_GLUE_RESULT_FORBID;
 	}
 
 	endpoint = channel->session->endpoint;
 
-	*instance = pvt->media[SIP_MEDIA_VIDEO]->rtp;
+	*instance = media->rtp;
 	ao2_ref(*instance, +1);
 
 	ast_assert(endpoint != NULL);
@@ -266,18 +272,43 @@
 	return 0;
 }
 
+/*! \brief Helper function to find the position for RTCP */
+static int rtp_find_rtcp_fd_position(struct ast_sip_session *session, struct ast_rtp_instance *rtp)
+{
+	int index;
+
+	for (index = 0; index < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++index) {
+		struct ast_sip_session_media_read_callback_state *callback_state =
+			AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, index);
+
+		if (callback_state->fd != ast_rtp_instance_fd(rtp, 1)) {
+			continue;
+		}
+
+		return index;
+	}
+
+	return -1;
+}
+
 /*!
  * \pre chan is locked
  */
 static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instance *rtp,
-		struct ast_sip_session_media *media, int rtcp_fd)
+		struct ast_sip_session_media *media, struct ast_sip_session *session)
 {
-	int changed = 0;
+	int changed = 0, position = -1;
+
+	if (media->rtp) {
+		position = rtp_find_rtcp_fd_position(session, media->rtp);
+	}
 
 	if (rtp) {
 		changed = ast_rtp_instance_get_and_cmp_remote_address(rtp, &media->direct_media_addr);
 		if (media->rtp) {
-			ast_channel_set_fd(chan, rtcp_fd, -1);
+			if (position != -1) {
+				ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, -1);
+			}
 			ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 0);
 		}
 	} else if (!ast_sockaddr_isnull(&media->direct_media_addr)){
@@ -285,7 +316,9 @@
 		changed = 1;
 		if (media->rtp) {
 			ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 1);
-			ast_channel_set_fd(chan, rtcp_fd, ast_rtp_instance_fd(media->rtp, 1));
+			if (position != -1) {
+				ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, ast_rtp_instance_fd(media->rtp, 1));
+			}
 		}
 	}
 
@@ -334,22 +367,27 @@
 {
 	struct rtp_direct_media_data *cdata = data;
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(cdata->chan);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
+	struct ast_sip_session *session;
 	int changed = 0;
 	int res = 0;
+
+	/* XXX In an ideal world each media stream would be direct, but for now preserve behavior
+	 * and connect only the default media sessions for audio and video.
+	 */
 
 	/* The channel needs to be locked when checking for RTP changes.
 	 * Otherwise, we could end up destroying an underlying RTCP structure
 	 * at the same time that the channel thread is attempting to read RTCP
 	 */
 	ast_channel_lock(cdata->chan);
-	if (pvt->media[SIP_MEDIA_AUDIO]) {
+	session = channel->session;
+	if (session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {
 		changed |= check_for_rtp_changes(
-			cdata->chan, cdata->rtp, pvt->media[SIP_MEDIA_AUDIO], 1);
+			cdata->chan, cdata->rtp, session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO], session);
 	}
-	if (pvt->media[SIP_MEDIA_VIDEO]) {
+	if (session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]) {
 		changed |= check_for_rtp_changes(
-			cdata->chan, cdata->vrtp, pvt->media[SIP_MEDIA_VIDEO], 3);
+			cdata->chan, cdata->vrtp, session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO], session);
 	}
 	ast_channel_unlock(cdata->chan);
 
@@ -421,13 +459,20 @@
 	.update_peer = chan_pjsip_set_rtp_peer,
 };
 
-static void set_channel_on_rtp_instance(struct chan_pjsip_pvt *pvt, const char *channel_id)
+static void set_channel_on_rtp_instance(const struct ast_sip_session *session,
+	const char *channel_id)
 {
-	if (pvt->media[SIP_MEDIA_AUDIO] && pvt->media[SIP_MEDIA_AUDIO]->rtp) {
-		ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_AUDIO]->rtp, channel_id);
-	}
-	if (pvt->media[SIP_MEDIA_VIDEO] && pvt->media[SIP_MEDIA_VIDEO]->rtp) {
-		ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_VIDEO]->rtp, channel_id);
+	int i;
+
+	for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->sessions); ++i) {
+		struct ast_sip_session_media *session_media;
+
+		session_media = AST_VECTOR_GET(&session->active_media_state->sessions, i);
+		if (!session_media || !session_media->rtp) {
+			continue;
+		}
+
+		ast_rtp_instance_set_channel_id(session_media->rtp, channel_id);
 	}
 }
 
@@ -463,36 +508,6 @@
 	return res;
 }
 
-/*!
- * \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);
-		if (!session_media) {
-			continue;
-		}
-
-		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)
 {
@@ -501,8 +516,9 @@
 	RAII_VAR(struct chan_pjsip_pvt *, pvt, NULL, ao2_cleanup);
 	struct ast_sip_channel_pvt *channel;
 	struct ast_variable *var;
+	struct ast_stream_topology *topology;
 
-	if (!(pvt = ao2_alloc(sizeof(*pvt), chan_pjsip_pvt_dtor))) {
+	if (!(pvt = ao2_alloc(sizeof(*pvt), NULL))) {
 		return NULL;
 	}
 
@@ -529,8 +545,8 @@
 
 	ast_channel_tech_pvt_set(chan, channel);
 
-	if (!ast_stream_topology_get_count(session->req_topology) ||
-		!compatible_formats_exist(session->req_topology, session->endpoint->media.codecs)) {
+	if (!ast_stream_topology_get_count(session->pending_media_state->topology) ||
+		!compatible_formats_exist(session->pending_media_state->topology, session->endpoint->media.codecs)) {
 		caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
 		if (!caps) {
 			ast_channel_unlock(chan);
@@ -538,9 +554,20 @@
 			return NULL;
 		}
 		ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);
+		topology = ast_stream_topology_clone(session->endpoint->media.topology);
 	} else {
-		caps = ast_format_cap_from_stream_topology(session->req_topology);
+		caps = ast_format_cap_from_stream_topology(session->pending_media_state->topology);
+		topology = ast_stream_topology_clone(session->pending_media_state->topology);
 	}
+
+	if (!topology) {
+		ao2_ref(caps, -1);
+		ast_channel_unlock(chan);
+		ast_hangup(chan);
+		return NULL;
+	}
+
+	ast_channel_set_stream_topology(chan, topology);
 
 	if (!caps) {
 		ast_channel_unlock(chan);
@@ -607,13 +634,7 @@
 	ast_channel_stage_snapshot_done(chan);
 	ast_channel_unlock(chan);
 
-	/* 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));
+	set_channel_on_rtp_instance(session, ast_channel_uniqueid(chan));
 
 	return chan;
 }
@@ -752,48 +773,31 @@
  *
  * \note The channel is already locked.
  */
-static struct ast_frame *chan_pjsip_read(struct ast_channel *ast)
+static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
-	struct ast_sip_session *session;
-	struct chan_pjsip_pvt *pvt = channel->pvt;
+	struct ast_sip_session *session = channel->session;
+	struct ast_sip_session_media_read_callback_state *callback_state;
 	struct ast_frame *f;
-	struct ast_sip_session_media *media = NULL;
-	int rtcp = 0;
-	int fdno = ast_channel_fdno(ast);
+	int fdno = ast_channel_fdno(ast) - AST_EXTENDED_FDS;
 
-	switch (fdno) {
-	case 0:
-		media = pvt->media[SIP_MEDIA_AUDIO];
-		break;
-	case 1:
-		media = pvt->media[SIP_MEDIA_AUDIO];
-		rtcp = 1;
-		break;
-	case 2:
-		media = pvt->media[SIP_MEDIA_VIDEO];
-		break;
-	case 3:
-		media = pvt->media[SIP_MEDIA_VIDEO];
-		rtcp = 1;
-		break;
-	}
-
-	if (!media || !media->rtp) {
+	if (fdno >= AST_VECTOR_SIZE(&session->active_media_state->read_callbacks)) {
 		return &ast_null_frame;
 	}
 
-	if (!(f = ast_rtp_instance_read(media->rtp, rtcp))) {
+	callback_state = AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, fdno);
+	f = callback_state->read_callback(session, callback_state->session);
+
+	if (!f) {
 		return f;
 	}
 
-	ast_rtp_instance_set_last_rx(media->rtp, time(NULL));
+	f->stream_num = callback_state->session->stream_num;
 
-	if (f->frametype != AST_FRAME_VOICE) {
+	if (f->frametype != AST_FRAME_VOICE ||
+		callback_state->session != session->active_media_state->default_session[callback_state->session->type]) {
 		return f;
 	}
-
-	session = channel->session;
 
 	if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
 		ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when it has not been negotiated\n",
@@ -864,22 +868,27 @@
 	return f;
 }
 
-/*! \brief Function called by core to write frames */
-static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
+static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *frame)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
-	struct ast_sip_session_media *media;
+	struct ast_sip_session *session = channel->session;
+	struct ast_sip_session_media *media = NULL;
 	int res = 0;
+
+	/* The core provides a guarantee that the stream will exist when we are called if stream_num is provided */
+	if (stream_num >= 0) {
+		media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, stream_num);
+	}
 
 	switch (frame->frametype) {
 	case AST_FRAME_VOICE:
-		media = pvt->media[SIP_MEDIA_AUDIO];
-
 		if (!media) {
 			return 0;
-		}
-		if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
+		} else if (media->type != AST_MEDIA_TYPE_AUDIO) {
+			ast_log(LOG_WARNING, "Channel %s stream %d is of type '%s', not audio!\n",
+				ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
+			return 0;
+		} else if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
 			struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
 			struct ast_str *write_transpath = ast_str_alloca(256);
 			struct ast_str *read_transpath = ast_str_alloca(256);
@@ -896,14 +905,20 @@
 				ast_format_get_name(ast_channel_rawwriteformat(ast)),
 				ast_translate_path_to_str(ast_channel_writetrans(ast), &write_transpath));
 			return 0;
-		}
-		if (media->rtp) {
-			res = ast_rtp_instance_write(media->rtp, frame);
+		} else if (media->write_callback) {
+			res = media->write_callback(session, media, frame);
+
 		}
 		break;
 	case AST_FRAME_VIDEO:
-		if ((media = pvt->media[SIP_MEDIA_VIDEO]) && media->rtp) {
-			res = ast_rtp_instance_write(media->rtp, frame);
+		if (!media) {
+			return 0;
+		} else if (media->type != AST_MEDIA_TYPE_VIDEO) {
+			ast_log(LOG_WARNING, "Channel %s stream %d is of type '%s', not video!\n",
+				ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
+			return 0;
+		} else if (media->write_callback) {
+			res = media->write_callback(session, media, frame);
 		}
 		break;
 	case AST_FRAME_MODEM:
@@ -920,7 +935,6 @@
 static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(newchan);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
 
 	if (channel->session->channel != oldchan) {
 		return -1;
@@ -933,7 +947,7 @@
 	 */
 	channel->session->channel = newchan;
 
-	set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(newchan));
+	set_channel_on_rtp_instance(channel->session, ast_channel_uniqueid(newchan));
 
 	return 0;
 }
@@ -1387,7 +1401,7 @@
 /*! \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)
 {
-	AST_VECTOR_CALLBACK_VOID(&session->media, local_hold_set_state, held);
+	AST_VECTOR_CALLBACK_VOID(&session->active_media_state->sessions, local_hold_set_state, held);
 	ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
 	ao2_ref(session, -1);
 
@@ -1406,16 +1420,104 @@
 	return remote_send_hold_refresh(data, 0);
 }
 
+struct topology_change_refresh_data {
+	struct ast_sip_session *session;
+	struct ast_stream_topology *topology;
+};
+
+static void topology_change_refresh_data_free(struct topology_change_refresh_data *refresh_data)
+{
+	ao2_cleanup(refresh_data->session);
+	ast_stream_topology_free(refresh_data->topology);
+	ast_free(refresh_data);
+}
+
+static struct topology_change_refresh_data *topology_change_refresh_data_alloc(
+	struct ast_sip_session *session, const struct ast_stream_topology *topology)
+{
+	struct topology_change_refresh_data *refresh_data;
+
+	refresh_data = ast_calloc(1, sizeof(*refresh_data));
+	if (!refresh_data) {
+		return NULL;
+	}
+
+	refresh_data->session = ao2_bump(session);
+	refresh_data->topology = ast_stream_topology_clone(topology);
+	if (!refresh_data->topology) {
+		topology_change_refresh_data_free(refresh_data);
+		return NULL;
+	}
+
+	return refresh_data;
+}
+
+static int on_topology_change_response(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+	if (rdata->msg_info.msg->line.status.code == 200) {
+		/* The topology was changed to something new so give notice to what requested
+		 * it so it queries the channel and updates accordingly.
+		 */
+		ast_queue_control(session->channel, AST_CONTROL_STREAM_TOPOLOGY_CHANGED);
+	} else {
+		/* The topology change failed, so drop the current pending media state */
+		ast_stream_topology_free(session->pending_media_state->topology);
+		session->pending_media_state->topology = NULL;
+	}
+
+	return 0;
+}
+
+static int send_topology_change_refresh(void *data)
+{
+	struct topology_change_refresh_data *refresh_data = data;
+
+	refresh_data->session->pending_media_state->topology = ast_stream_topology_clone(refresh_data->topology);
+	if (!refresh_data->session->pending_media_state->topology) {
+		topology_change_refresh_data_free(refresh_data);
+		return -1;
+	}
+
+	if (ast_sip_session_refresh(refresh_data->session, NULL, NULL, on_topology_change_response,
+		AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1)) {
+		refresh_data->session->pending_media_state->topology = NULL;
+		topology_change_refresh_data_free(refresh_data);
+		return -1;
+	}
+
+	topology_change_refresh_data_free(refresh_data);
+	return 0;
+}
+
+static int handle_topology_request_change(struct ast_sip_session *session,
+	const struct ast_stream_topology *proposed)
+{
+	struct topology_change_refresh_data *refresh_data;
+	int res;
+
+	refresh_data = topology_change_refresh_data_alloc(session, proposed);
+	if (!refresh_data) {
+		return -1;
+	}
+
+	res = ast_sip_push_task(session->serializer, send_topology_change_refresh, refresh_data);
+	if (res) {
+		topology_change_refresh_data_free(refresh_data);
+	}
+	return res;
+}
+
 /*! \brief Function called by core to ask the channel to indicate some sort of condition */
 static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
 	struct ast_sip_session_media *media;
 	int response_code = 0;
 	int res = 0;
 	char *device_buf;
 	size_t device_buf_size;
+	int i;
+	const struct ast_stream_topology *topology;
 
 	switch (condition) {
 	case AST_CONTROL_RINGING:
@@ -1468,39 +1570,47 @@
 		ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_sorcery_object_get_id(channel->session->endpoint));
 		break;
 	case AST_CONTROL_VIDUPDATE:
-		media = pvt->media[SIP_MEDIA_VIDEO];
-		if (media && media->rtp) {
-			/* FIXME: Only use this for VP8. Additional work would have to be done to
-			 * fully support other video codecs */
-
-			if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) {
-				/* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the
-				 * RTP engine would provide a way to externally write/schedule RTCP
-				 * packets */
-				struct ast_frame fr;
-				fr.frametype = AST_FRAME_CONTROL;
-				fr.subclass.integer = AST_CONTROL_VIDUPDATE;
-				res = ast_rtp_instance_write(media->rtp, &fr);
-			} else {
-				ao2_ref(channel->session, +1);
-#ifdef HAVE_PJSIP_INV_SESSION_REF
-				if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) {
-					ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
-					ao2_cleanup(channel->session);
-				} else {
-#endif
-					if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) {
-						ao2_cleanup(channel->session);
-					}
-#ifdef HAVE_PJSIP_INV_SESSION_REF
-				}
-#endif
+		for (i = 0; i < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions); ++i) {
+			media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, i);
+			if (!media || media->type != AST_MEDIA_TYPE_VIDEO) {
+				continue;
 			}
-			ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success");
-		} else {
-			ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure");
-			res = -1;
+			if (media->rtp) {
+				/* FIXME: Only use this for VP8. Additional work would have to be done to
+				 * fully support other video codecs */
+
+				if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) {
+					/* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the
+					 * RTP engine would provide a way to externally write/schedule RTCP
+					 * packets */
+					struct ast_frame fr;
+					fr.frametype = AST_FRAME_CONTROL;
+					fr.subclass.integer = AST_CONTROL_VIDUPDATE;
+					res = ast_rtp_instance_write(media->rtp, &fr);
+				} else {
+					ao2_ref(channel->session, +1);
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+					if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) {
+						ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
+						ao2_cleanup(channel->session);
+					} else {
+#endif
+						if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) {
+							ao2_cleanup(channel->session);
+						}
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+					}
+#endif
+				}
+				ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success");
+			} else {
+				ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure");
+				res = -1;
+			}
 		}
+		/* XXX If there were no video streams, then this should set
+		 * res to -1
+		 */
 		break;
 	case AST_CONTROL_CONNECTED_LINE:
 		ao2_ref(channel->session, +1);
@@ -1595,6 +1705,10 @@
 			}
 		}
 
+		break;
+	case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE:
+		topology = data;
+		res = handle_topology_request_change(channel->session, topology);
 		break;
 	case -1:
 		res = -1;
@@ -1809,9 +1923,10 @@
 static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
-	struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];
+	struct ast_sip_session_media *media;
 	int res = 0;
+
+	media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
 
 	switch (channel->session->endpoint->dtmf) {
 	case AST_SIP_DTMF_RFC_4733:
@@ -1820,14 +1935,14 @@
 		}
 
 		ast_rtp_instance_dtmf_begin(media->rtp, digit);
-                break;
+		break;
 	case AST_SIP_DTMF_AUTO:
-                       if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
-                        return -1;
-                }
+		if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
+			return -1;
+		}
 
-                ast_rtp_instance_dtmf_begin(media->rtp, digit);
-                break;
+		ast_rtp_instance_dtmf_begin(media->rtp, digit);
+		break;
 	case AST_SIP_DTMF_NONE:
 		break;
 	case AST_SIP_DTMF_INBAND:
@@ -1923,9 +2038,10 @@
 static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
-	struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];
+	struct ast_sip_session_media *media;
 	int res = 0;
+
+	media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
 
 	switch (channel->session->endpoint->dtmf) {
 	case AST_SIP_DTMF_INFO:
@@ -2008,7 +2124,6 @@
 {
 	struct ast_sip_channel_pvt *channel = data;
 	struct ast_sip_session *session = channel->session;
-	struct chan_pjsip_pvt *pvt = channel->pvt;
 	pjsip_tx_data *tdata;
 
 	int res = ast_sip_session_create_invite(session, &tdata);
@@ -2017,7 +2132,7 @@
 		ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0);
 		ast_queue_hangup(session->channel);
 	} else {
-		set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(session->channel));
+		set_channel_on_rtp_instance(session, ast_channel_uniqueid(session->channel));
 		update_initial_connected_line(session);
 		ast_sip_session_send_request(session, tdata);
 	}
@@ -2115,10 +2230,10 @@
 }
 
 /*! \brief Clear a channel from a session along with its PVT */
-static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast, struct chan_pjsip_pvt *pvt)
+static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast)
 {
 	session->channel = NULL;
-	set_channel_on_rtp_instance(pvt, "");
+	set_channel_on_rtp_instance(session, "");
 	ast_channel_tech_pvt_set(ast, NULL);
 }
 
@@ -2127,7 +2242,6 @@
 	struct hangup_data *h_data = data;
 	struct ast_channel *ast = h_data->chan;
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
 	struct ast_sip_session *session = channel->session;
 	int cause = h_data->cause;
 
@@ -2137,7 +2251,7 @@
 	 * afterwards.
 	 */
 	ast_sip_session_terminate(ao2_bump(session), cause);
-	clear_session_and_channel(session, ast, pvt);
+	clear_session_and_channel(session, ast);
 	ao2_cleanup(session);
 	ao2_cleanup(channel);
 	ao2_cleanup(h_data);
@@ -2148,7 +2262,6 @@
 static int chan_pjsip_hangup(struct ast_channel *ast)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
-	struct chan_pjsip_pvt *pvt;
 	int cause;
 	struct hangup_data *h_data;
 
@@ -2156,7 +2269,6 @@
 		return -1;
 	}
 
-	pvt = channel->pvt;
 	cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel));
 	h_data = hangup_data_alloc(cause, ast);
 
@@ -2175,7 +2287,7 @@
 	/* Go ahead and do our cleanup of the session and channel even if we're not going
 	 * to be able to send our SIP request/response
 	 */
-	clear_session_and_channel(channel->session, ast, pvt);
+	clear_session_and_channel(channel->session, ast);
 	ao2_cleanup(channel);
 	ao2_cleanup(h_data);
 
@@ -2270,26 +2382,20 @@
 }
 
 /*! \brief Function called by core to create a new outgoing PJSIP session */
-static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
+static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type, struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
 {
 	struct request_data req_data;
 	RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);
 
-	req_data.topology = ast_stream_topology_create_from_format_cap(cap);
+	req_data.topology = topology;
 	req_data.dest = data;
-
-	if (!req_data.topology) {
-		return NULL;
-	}
 
 	if (ast_sip_push_task_synchronous(NULL, request, &req_data)) {
 		*cause = req_data.cause;
-		ast_stream_topology_free(req_data.topology);
 		return NULL;
 	}
 
 	session = req_data.session;
-	ast_stream_topology_free(req_data.topology);
 
 	if (!(session->channel = chan_pjsip_new(session, AST_STATE_DOWN, NULL, NULL, assignedids, requestor, NULL))) {
 		/* Session needs to be terminated prematurely */
@@ -2299,6 +2405,23 @@
 	return session->channel;
 }
 
+static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
+{
+	struct ast_stream_topology *topology;
+	struct ast_channel *chan;
+
+	topology = ast_stream_topology_create_from_format_cap(cap);
+	if (!topology) {
+		return NULL;
+	}
+
+	chan = chan_pjsip_request_with_stream_topology(type, topology, assignedids, requestor, data, cause);
+
+	ast_stream_topology_free(topology);
+
+	return chan;
+}
+
 struct sendtext_data {
 	struct ast_sip_session *session;
 	char text[0];
diff --git a/channels/pjsip/cli_commands.c b/channels/pjsip/cli_commands.c
index fc14b25..7705bbd 100644
--- a/channels/pjsip/cli_commands.c
+++ b/channels/pjsip/cli_commands.c
@@ -336,14 +336,24 @@
 	return 0;
 }
 
+static int find_audio(struct ast_sip_session_media *session_media)
+{
+	if (session_media->type == AST_MEDIA_TYPE_AUDIO) {
+		return CMP_MATCH;
+	}
+
+	return 0;
+}
+
 static int cli_channelstats_print_body(void *obj, void *arg, int flags)
 {
 	struct ast_sip_cli_context *context = arg;
 	const struct ast_channel_snapshot *snapshot = obj;
 	struct ast_channel *channel = ast_channel_get_by_name(snapshot->name);
 	struct ast_sip_channel_pvt *cpvt = channel ? ast_channel_tech_pvt(channel) : NULL;
-	struct chan_pjsip_pvt *pvt = cpvt ? cpvt->pvt : NULL;
-	struct ast_sip_session_media *media = pvt ? pvt->media[SIP_MEDIA_AUDIO] : NULL;
+	struct ast_sip_session *session;
+	struct ast_sip_session_media *media;
+	struct ast_rtp_instance *rtp;
 	struct ast_rtp_instance_stats stats;
 	char *print_name = NULL;
 	char *print_time = alloca(32);
@@ -351,21 +361,38 @@
 
 	ast_assert(context->output_buffer != NULL);
 
-	if (!media || !media->rtp) {
+	if (!channel) {
 		ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+		return -1;
+	}
+
+	ast_channel_lock(channel);
+
+	session = cpvt->session;
+	if (!session) {
+		ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+		ast_channel_unlock(channel);
 		ao2_cleanup(channel);
 		return -1;
 	}
 
+	media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_audio, NULL);
+	if (!media || !media->rtp) {
+		ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+		ast_channel_unlock(channel);
+		ao2_cleanup(channel);
+		return -1;
+	}
+
+	rtp = ao2_bump(media->rtp);
+
 	codec_in_use[0] = '\0';
 
-	if (channel) {
-		ast_channel_lock(channel);
-		if (ast_channel_rawreadformat(channel)) {
-			ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));
-		}
-		ast_channel_unlock(channel);
+	if (ast_channel_rawreadformat(channel)) {
+		ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));
 	}
+
+	ast_channel_unlock(channel);
 
 	print_name = ast_strdupa(snapshot->name);
 	/* Skip the PJSIP/.  We know what channel type it is and we need the space. */
@@ -373,7 +400,7 @@
 
 	ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);
 
-	if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
+	if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
 		ast_str_append(&context->output_buffer, 0, "%s direct media\n", snapshot->name);
 	} else {
 		ast_str_append(&context->output_buffer, 0,
@@ -398,6 +425,7 @@
 		);
 	}
 
+	ao2_cleanup(rtp);
 	ao2_cleanup(channel);
 
 	return 0;
diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c
index 9de7ceb..ca2d340 100644
--- a/channels/pjsip/dialplan_functions.c
+++ b/channels/pjsip/dialplan_functions.c
@@ -456,14 +456,23 @@
 	[T38_REJECTED] = "REJECTED",
 };
 
+static int find_media(struct ast_sip_session_media *session_media, enum ast_media_type type)
+{
+	if (session_media->type == type) {
+		return CMP_MATCH;
+	}
+
+	return 0;
+}
+
 /*!
  * \internal \brief Handle reading RTP information
  */
 static int channel_read_rtp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
-	struct chan_pjsip_pvt *pvt;
-	struct ast_sip_session_media *media = NULL;
+	struct ast_sip_session *session;
+	struct ast_sip_session_media *media;
 	struct ast_sockaddr addr;
 
 	if (!channel) {
@@ -471,9 +480,9 @@
 		return -1;
 	}
 
-	pvt = channel->pvt;
-	if (!pvt) {
-		ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));
+	session = channel->session;
+	if (!session) {
+		ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan));
 		return -1;
 	}
 
@@ -483,9 +492,9 @@
 	}
 
 	if (ast_strlen_zero(field) || !strcmp(field, "audio")) {
-		media = pvt->media[SIP_MEDIA_AUDIO];
+		media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_media, NULL, AST_MEDIA_TYPE_AUDIO);
 	} else if (!strcmp(field, "video")) {
-		media = pvt->media[SIP_MEDIA_VIDEO];
+		media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_media, NULL, AST_MEDIA_TYPE_VIDEO);
 	} else {
 		ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field);
 		return -1;
@@ -523,17 +532,17 @@
 static int channel_read_rtcp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
-	struct chan_pjsip_pvt *pvt;
-	struct ast_sip_session_media *media = NULL;
+	struct ast_sip_session *session;
+	struct ast_sip_session_media *media;
 
 	if (!channel) {
 		ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan));
 		return -1;
 	}
 
-	pvt = channel->pvt;
-	if (!pvt) {
-		ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));
+	session = channel->session;
+	if (!session) {
+		ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan));
 		return -1;
 	}
 
@@ -543,9 +552,9 @@
 	}
 
 	if (ast_strlen_zero(field) || !strcmp(field, "audio")) {
-		media = pvt->media[SIP_MEDIA_AUDIO];
+		media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_media, NULL, AST_MEDIA_TYPE_AUDIO);
 	} else if (!strcmp(field, "video")) {
-		media = pvt->media[SIP_MEDIA_VIDEO];
+		media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_media, NULL, AST_MEDIA_TYPE_AUDIO);
 	} else {
 		ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field);
 		return -1;
@@ -934,8 +943,8 @@
 	size_t accum = 0;
 
 	/* Find the first suitable stream */
-	for (idx = 0; idx < ast_stream_topology_get_count(session->req_topology); ++idx) {
-		stream = ast_stream_topology_get_stream(session->req_topology, idx);
+	for (idx = 0; idx < ast_stream_topology_get_count(session->pending_media_state->topology); ++idx) {
+		stream = ast_stream_topology_get_stream(session->pending_media_state->topology, idx);
 
 		if (ast_stream_get_type(stream) != media_type ||
 			ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
@@ -1002,7 +1011,7 @@
 	 * b) Change the dialplan function to be able to specify which stream to alter and alter only that
 	 * one stream
 	 */
-	stream = ast_stream_topology_get_first_stream_by_type(data->session->req_topology, data->media_type);
+	stream = ast_stream_topology_get_first_stream_by_type(data->session->pending_media_state->topology, data->media_type);
 	if (!stream) {
 		return 0;
 	}
diff --git a/channels/pjsip/include/chan_pjsip.h b/channels/pjsip/include/chan_pjsip.h
index b229a04..1fee864 100644
--- a/channels/pjsip/include/chan_pjsip.h
+++ b/channels/pjsip/include/chan_pjsip.h
@@ -34,25 +34,12 @@
 	pj_sockaddr local_addr;
 };
 
-/*!
- * \brief Positions of various media
- */
-enum sip_session_media_position {
-	/*! \brief First is audio */
-	SIP_MEDIA_AUDIO = 0,
-	/*! \brief Second is video */
-	SIP_MEDIA_VIDEO,
-	/*! \brief Last is the size for media details */
-	SIP_MEDIA_SIZE,
-};
 
 /*!
  * \brief The PJSIP channel driver pvt, stored in the \ref ast_sip_channel_pvt
  * data structure
  */
 struct chan_pjsip_pvt {
-	/*! \brief The available media sessions */
-	struct ast_sip_session_media *media[SIP_MEDIA_SIZE];
 };
 
 #endif /* _CHAN_PJSIP_HEADER */
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index c6d1574..d6f8229 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -58,6 +58,12 @@
 };
 
 struct ast_sip_session_sdp_handler;
+struct ast_sip_session;
+struct ast_sip_session_media;
+
+typedef struct ast_frame *(*ast_sip_session_media_read_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media);
+typedef int (*ast_sip_session_media_write_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	struct ast_frame *frame);
 
 /*!
  * \brief A structure containing SIP session media information
@@ -89,6 +95,36 @@
 	unsigned int remote_rtcp_mux:1;
 	/*! \brief Media type of this session media */
 	enum ast_media_type type;
+	/*! \brief The write callback when writing frames */
+	ast_sip_session_media_write_cb write_callback;
+	/*! \brief The stream number to place into any resulting frames */
+	int stream_num;
+};
+
+/*!
+ * \brief Structure which contains read callback information
+ */
+struct ast_sip_session_media_read_callback_state {
+	/*! \brief The file descriptor itself */
+	int fd;
+	/*! \brief The callback to invoke */
+	ast_sip_session_media_read_cb read_callback;
+	/*! \brief The media session */
+	struct ast_sip_session_media *session;
+};
+
+/*!
+ * \brief Structure which contains media state information (streams, sessions)
+ */
+struct ast_sip_session_media_state {
+	/*! \brief Mapping of stream to media sessions */
+	AST_VECTOR(, struct ast_sip_session_media *) sessions;
+	/*! \brief Added read callbacks */
+	AST_VECTOR(, struct ast_sip_session_media_read_callback_state) read_callbacks;
+	/*! \brief Default media sessions for each type */
+	struct ast_sip_session_media *default_session[AST_MEDIA_TYPE_END];
+	/*! \brief The media stream topology */
+	struct ast_stream_topology *topology;
 };
 
 /*!
@@ -123,10 +159,6 @@
 	AST_LIST_HEAD(, ast_sip_session_supplement) supplements;
 	/*! Datastores added to the session by supplements to the session */
 	struct ao2_container *datastores;
-	/*! Lock for vector of session media */
-	ast_mutex_t media_streams_lock;
-	/*! Media streams */
-	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. */
@@ -141,8 +173,10 @@
 	pj_timer_entry scheduled_termination;
 	/*! Identity of endpoint this session deals with */
 	struct ast_party_id id;
-	/*! Requested capabilities */
-	struct ast_stream_topology *req_topology;
+	/*! Active media state (sessions + streams) */
+	struct ast_sip_session_media_state *active_media_state;
+	/*! Pending media state (sessions + streams) */
+	struct ast_sip_session_media_state *pending_media_state;
 	/*! Optional DSP, used only for inband DTMF/Fax-CNG detection if configured */
 	struct ast_dsp *dsp;
 	/*! Whether the termination of the session should be deferred */
@@ -317,23 +351,16 @@
 	/*!
 	 * \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 session
 	 * \param sdp The entire SDP. Useful for getting "global" information, such as connections or attributes
 	 * \param index The index for the session media, Asterisk stream, and PJMEDIA stream being negotiated
+	 * \param asterisk_stream The Asterisk stream representation
 	 * \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, 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
-	 * \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, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, 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, int index, struct ast_stream *asterisk_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
@@ -355,15 +382,18 @@
 	/*!
 	 * \brief Apply a negotiated SDP media stream
 	 * \param session The session for which media is being applied
+         * \param session_media The media session
 	 * \param local The entire local negotiated SDP
 	 * \param remote The entire remote negotiated SDP
 	 * \param index The index of the session media, SDP streams, and Asterisk streams
+	 * \param asterisk_stream The Asterisk stream representation
 	 * \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, const struct pjmedia_sdp_session *local,
-		const struct pjmedia_sdp_session *remote, int index);
+	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_session *remote, int index,
+		struct ast_stream *asterisk_stream);
 	/*!
 	 * \brief Stop a session_media created by this handler but do not destroy resources
 	 * \param session The session for which media is being stopped
@@ -393,7 +423,7 @@
 /*!
  * \brief Allocate a new SIP channel pvt structure
  *
- * \param pvt Pointer to channel specific implementation
+ * \param pvt Pointer to channel specific information
  * \param session Pointer to SIP session
  *
  * \retval non-NULL success
@@ -692,6 +722,51 @@
  */
 void ast_sip_session_resume_reinvite(struct ast_sip_session *session);
 
+/*!
+ * \brief Determines if a provided pending stream will be the default stream or not
+ * \since 15.0.0
+ *
+ * \param session The session to check against
+ * \param stream The pending stream
+ *
+ * \retval 1 if stream will be default
+ * \retval 0 if stream will NOT be the default
+ */
+int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream);
+
+/*!
+ * \brief Set a read callback for a media session with a specific file descriptor
+ * \since 15.0.0
+ *
+ * \param session The session
+ * \param session_media The media session
+ * \param fd The file descriptor
+ * \param callback The read callback
+ *
+ * \retval 0 the read callback was successfully added
+ * \retval -1 the read callback could not be added
+ *
+ * \note This operations on the pending media state
+ */
+int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	int fd, ast_sip_session_media_read_cb callback);
+
+/*!
+ * \brief Set a write callback for a media session
+ * \since 15.0.0
+ *
+ * \param session The session
+ * \param session_media The media session
+ * \param callback The write callback
+ *
+ * \retval 0 the write callback was successfully add
+ * \retval -1 the write callback is already set to something different
+ *
+ * \note This operates on the pending media state
+ */
+int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	ast_sip_session_media_write_cb callback);
+
 /*! \brief Determines whether the res_pjsip_session module is loaded */
 #define CHECK_PJSIP_SESSION_MODULE_LOADED()				\
 	do {								\
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index b6f9aa7..6c04a9a 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -52,6 +52,7 @@
 #include "asterisk/dsp.h"
 #include "asterisk/linkedlists.h"       /* for AST_LIST_NEXT */
 #include "asterisk/stream.h"
+#include "asterisk/format_cache.h"
 
 #include "asterisk/res_pjsip.h"
 #include "asterisk/res_pjsip_session.h"
@@ -63,24 +64,7 @@
 static struct ast_sockaddr address_rtp;
 
 static const char STR_AUDIO[] = "audio";
-static const int FD_AUDIO = 0;
-
 static const char STR_VIDEO[] = "video";
-static const int FD_VIDEO = 2;
-
-/*! \brief Get the starting descriptor for a media type */
-static int media_type_to_fdno(enum ast_media_type media_type)
-{
-	switch (media_type) {
-	case AST_MEDIA_TYPE_AUDIO: return FD_AUDIO;
-	case AST_MEDIA_TYPE_VIDEO: return FD_VIDEO;
-	case AST_MEDIA_TYPE_TEXT:
-	case AST_MEDIA_TYPE_UNKNOWN:
-	case AST_MEDIA_TYPE_IMAGE:
-	case AST_MEDIA_TYPE_END: break;
-	}
-	return -1;
-}
 
 static int send_keepalive(const void *data)
 {
@@ -324,7 +308,7 @@
 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 index)
+	int is_offer, struct ast_stream *asterisk_stream)
 {
 	RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_format_cap *, peer, NULL, ao2_cleanup);
@@ -336,7 +320,6 @@
 	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 = NULL;
 
 	if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
 	    !(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
@@ -381,20 +364,9 @@
 	ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),
 		session_media->rtp);
 
-	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_topology_set_stream(session->req_topology, index, req_stream);
-	}
+	ast_stream_set_formats(asterisk_stream, joint);
 
-	ast_stream_set_formats(req_stream, joint);
-
-	if (session->channel) {
+	if (session->channel && ast_sip_session_is_pending_stream_default(session, asterisk_stream)) {
 		ast_channel_lock(session->channel);
 		ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_UNKNOWN);
 		ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel),
@@ -959,11 +931,11 @@
 
 /*! \brief Function which negotiates an incoming media stream */
 static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
-	const pjmedia_sdp_session *sdp, int index)
+	struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,
+	int index, struct ast_stream *asterisk_stream)
 {
 	char host[NI_MAXHOST];
 	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 *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;
@@ -1034,7 +1006,7 @@
 		pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);
  	}
 
-	if (set_caps(session, session_media, stream, 1, index)) {
+	if (set_caps(session, session_media, stream, 1, asterisk_stream)) {
 		return 0;
 	}
 	return 1;
@@ -1158,6 +1130,7 @@
 				      struct pjmedia_sdp_session *sdp, struct ast_stream *stream)
 {
 	pj_pool_t *pool = session->inv_session->pool_prov;
+	static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
 	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};
@@ -1179,23 +1152,27 @@
 	int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
 		ast_format_cap_count(session->direct_media_cap);
 
+	media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media));
+	if (!media) {
+		return -1;
+	}
+	pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));
+
+	/* If this is a removed (or declined) stream then construct a minimal stream in SDP */
+	if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+		media->desc.transport = STR_RTP_AVP;
+		media->desc.port = 0;
+	        media->desc.port_count = 1;
+		pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], "32");
+		sdp->media[sdp->media_count++] = media;
+
+		return 1;
+	}
+
 	if (!session_media->rtp && create_rtp(session, session_media)) {
 		return -1;
 	}
 
-	set_ice_components(session, session_media);
-	enable_rtcp(session, session_media, NULL);
-
-	if (!(media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media))) ||
-		!(media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)))) {
-		return -1;
-	}
-
-	if (add_crypto_to_stream(session, session_media, pool, media)) {
-		return -1;
-	}
-
-	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;
@@ -1206,6 +1183,18 @@
 				(session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES),
 			session_media->rtp, session->endpoint->media.rtp.use_avpf,
 			session->endpoint->media.rtp.force_avp));
+	}
+
+	set_ice_components(session, session_media);
+	enable_rtcp(session, session_media, NULL);
+
+	media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
+	if (!media->conn) {
+		return -1;
+	}
+
+	if (add_crypto_to_stream(session, session_media, pool, media)) {
+		return -1;
 	}
 
 	/* Add connection level details */
@@ -1237,12 +1226,12 @@
 		}
 	}
 
+	/* Add ICE attributes and candidates */
+	add_ice_to_stream(session, session_media, pool, media);
+
 	ast_rtp_instance_get_local_address(session_media->rtp, &addr);
 	media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr);
 	media->desc.port_count = 1;
-
-	/* Add ICE attributes and candidates */
-	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",
@@ -1290,7 +1279,8 @@
 	}
 
 	/* Add non-codec formats */
-	if (media_type != AST_MEDIA_TYPE_VIDEO && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) {
+	if (ast_sip_session_is_pending_stream_default(session, stream) && media_type != AST_MEDIA_TYPE_VIDEO
+		&& media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) {
 		for (index = 1LL; index <= AST_RTP_MAX; index <<= 1) {
 			if (!(noncodec & index)) {
 				continue;
@@ -1356,23 +1346,62 @@
 	return 1;
 }
 
+static struct ast_frame *media_session_rtp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+	struct ast_frame *f;
+
+	if (!session_media->rtp) {
+		return &ast_null_frame;
+	}
+
+	f = ast_rtp_instance_read(session_media->rtp, 0);
+	if (!f) {
+		return NULL;
+	}
+
+	ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL));
+
+	return f;
+}
+
+static struct ast_frame *media_session_rtcp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+	struct ast_frame *f;
+
+	if (!session_media->rtp) {
+		return &ast_null_frame;
+	}
+
+	f = ast_rtp_instance_read(session_media->rtp, 1);
+	if (!f) {
+		return NULL;
+	}
+
+	ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL));
+
+	return f;
+}
+
+static int media_session_rtp_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame)
+{
+	if (!session_media->rtp) {
+		return 0;
+	}
+
+	return ast_rtp_instance_write(session_media->rtp, frame);
+}
+
 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)
+	struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local,
+	const struct pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
 {
 	RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
-	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;
+	int res;
 
 	if (!session->channel) {
-		return 1;
-	}
-
-	if (!local_stream->desc.port || !remote_stream->desc.port) {
 		return 1;
 	}
 
@@ -1415,16 +1444,16 @@
 	/* 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, index)) {
+	if (set_caps(session, session_media, remote_stream, 0, asterisk_stream)) {
 		return 1;
 	}
 
-	if ((fdno = media_type_to_fdno(media_type)) < 0) {
-		return -1;
-	}
-	ast_channel_set_fd(session->channel, fdno, ast_rtp_instance_fd(session_media->rtp, 0));
+	ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback);
+	ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0),
+		media_session_rtp_read_callback);
 	if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) {
-		ast_channel_set_fd(session->channel, fdno + 1, ast_rtp_instance_fd(session_media->rtp, 1));
+		ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1),
+			media_session_rtcp_read_callback);
 	}
 
 	/* If ICE support is enabled find all the needed attributes */
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index fac4eab..300953c 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -174,6 +174,112 @@
 	ao2_callback_data(sdp_handlers, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, remove_handler, (void *)stream_type, handler);
 }
 
+static struct ast_sip_session_media_state *sip_session_media_state_alloc(void)
+{
+	struct ast_sip_session_media_state *media_state;
+
+	media_state = ast_calloc(1, sizeof(*media_state));
+	if (!media_state) {
+		return NULL;
+	}
+
+	if (AST_VECTOR_INIT(&media_state->sessions, DEFAULT_NUM_SESSION_MEDIA) < 0) {
+		ast_free(media_state);
+		return NULL;
+	}
+
+	if (AST_VECTOR_INIT(&media_state->read_callbacks, DEFAULT_NUM_SESSION_MEDIA) < 0) {
+		AST_VECTOR_FREE(&media_state->sessions);
+		ast_free(media_state);
+		return NULL;
+	}
+
+	return media_state;
+}
+
+static void sip_session_media_state_reset(struct ast_sip_session_media_state *media_state)
+{
+	int index;
+
+	if (!media_state) {
+		return;
+	}
+
+	AST_VECTOR_RESET(&media_state->sessions, ao2_cleanup);
+	AST_VECTOR_RESET(&media_state->read_callbacks, AST_VECTOR_ELEM_CLEANUP_NOOP);
+
+	for (index = 0; index < AST_MEDIA_TYPE_END; ++index) {
+		media_state->default_session[index] = NULL;
+	}
+
+	ast_stream_topology_free(media_state->topology);
+	media_state->topology = NULL;
+}
+
+static void sip_session_media_state_free(struct ast_sip_session_media_state *media_state)
+{
+	if (!media_state) {
+		return;
+	}
+
+	sip_session_media_state_reset(media_state);
+
+	AST_VECTOR_FREE(&media_state->sessions);
+	AST_VECTOR_FREE(&media_state->read_callbacks);
+
+	ast_free(media_state);
+}
+
+int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream)
+{
+	int index;
+
+	ast_assert(session->pending_media_state->topology != NULL);
+
+	if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+		return 0;
+	}
+
+	for (index = 0; index < ast_stream_topology_get_count(session->pending_media_state->topology); ++index) {
+		if (ast_stream_get_type(ast_stream_topology_get_stream(session->pending_media_state->topology, index)) !=
+			ast_stream_get_type(stream)) {
+			continue;
+		}
+
+		return ast_stream_topology_get_stream(session->pending_media_state->topology, index) == stream ? 1 : 0;
+	}
+
+	return 0;
+}
+
+int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	int fd, ast_sip_session_media_read_cb callback)
+{
+	struct ast_sip_session_media_read_callback_state callback_state = {
+		.fd = fd,
+		.read_callback = callback,
+		.session = session_media,
+	};
+
+	return AST_VECTOR_APPEND(&session->pending_media_state->read_callbacks, callback_state);
+}
+
+int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	ast_sip_session_media_write_cb callback)
+{
+	if (session_media->write_callback) {
+		if (session_media->write_callback == callback) {
+			return 0;
+		}
+
+		return -1;
+	}
+
+	session_media->write_callback = callback;
+
+	return 0;
+}
+
 /*!
  * \brief Set an SDP stream handler for a corresponding session media.
  *
@@ -241,27 +347,48 @@
 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;
+	struct ast_sip_session_media *session_media = NULL;
 
-	session_media = ao2_alloc(sizeof(*session_media), session_media_dtor);
-	if (!session_media) {
-		return NULL;
+	/* If we already have a session media in place in pending then a prior callback has
+	 * created it.
+	 */
+	if (position < AST_VECTOR_SIZE(&session->pending_media_state->sessions)) {
+		return AST_VECTOR_GET(&session->pending_media_state->sessions, position);
 	}
-	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) {
-		struct ast_sip_session_media *old = NULL;
 
-		if (position < AST_VECTOR_SIZE(&session->media)) {
-			old = AST_VECTOR_GET(&session->media, position);
+	/* Determine if we can reuse the session media from the active media state if present */
+	if (position < AST_VECTOR_SIZE(&session->active_media_state->sessions)) {
+		session_media = AST_VECTOR_GET(&session->active_media_state->sessions, position);
+		/* A stream can never exist without an accompanying media session */
+		if (session_media->type == type) {
+			ao2_ref(session_media, +1);
+		} else {
+			session_media = NULL;
 		}
-		AST_VECTOR_REPLACE(&session->media, position, session_media);
-		ao2_cleanup(old);
-	} else {
-		AST_VECTOR_APPEND(&session->media, session_media);
 	}
+
+	if (!session_media) {
+		/* No existing media session we can use so create a new one */
+		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;
+		session_media->stream_num = position;
+	}
+
+	AST_VECTOR_REPLACE(&session->pending_media_state->sessions, position, session_media);
+
+	/* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */
+	if (ast_stream_get_state(ast_stream_topology_get_stream(session->pending_media_state->topology, position)) != AST_STREAM_STATE_REMOVED &&
+		!session->pending_media_state->default_session[type]) {
+		session->pending_media_state->default_session[type] = session_media;
+	}
+
 	return session_media;
 }
 
@@ -310,6 +437,14 @@
 		return -1;
 	}
 
+	/* It is possible for SDP deferral to have already created a pending topology */
+	if (!session->pending_media_state->topology) {
+		session->pending_media_state->topology = ast_stream_topology_alloc();
+		if (!session->pending_media_state->topology) {
+			return -1;
+		}
+	}
+
 	for (i = 0; i < sdp->media_count; ++i) {
 		/* See if there are registered handlers for this media stream type */
 		char media[20];
@@ -318,32 +453,33 @@
 		struct ast_sip_session_media *session_media = NULL;
 		int res;
 		enum ast_media_type type;
+		struct ast_stream *stream = NULL;
 
 		/* We need a null-terminated version of the media string */
 		ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
 		type = ast_media_type_from_str(media);
 
-		if (limitation_reached(type, session->endpoint, type_streams)) {
-			continue;
+		/* See if we have an already existing stream, which can occur from SDP deferral checking */
+		if (i < ast_stream_topology_get_count(session->pending_media_state->topology)) {
+			stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
 		}
-
-		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 = type;
-				session_media->handler = NULL;
-			}
-		}
-		if (!session_media) {
-			session_media = add_session_media(session, ast_media_type_from_str(media), i);
-			if (!session_media) {
+		if (!stream) {
+			stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
+			if (!stream) {
 				return -1;
 			}
+			ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);
+		}
+
+		session_media = add_session_media(session, ast_media_type_from_str(media), i);
+		if (!session_media) {
+			return -1;
+		}
+
+		/* Enforce the user configured maximum stream count for this type by marking it declined and moving on*/
+		if (limitation_reached(type, session->endpoint, type_streams)) {
+			ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+			continue;
 		}
 
 		if (session_media->handler) {
@@ -351,7 +487,7 @@
 			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
 				ast_codec_media_type2str(session_media->type),
 				session_media->handler->id);
-			res = handler->negotiate_incoming_sdp_stream(session, sdp, i);
+			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
 				return -1;
@@ -378,7 +514,7 @@
 			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
 				ast_codec_media_type2str(session_media->type),
 				handler->id);
-			res = handler->negotiate_incoming_sdp_stream(session, sdp, i);
+			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
 				return -1;
@@ -403,13 +539,31 @@
 
 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)
+		const pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
 {
 	/* See if there are registered handlers for this media stream type */
+	struct pjmedia_sdp_media *local_stream = local->media[index];
 	char media[20];
 	struct ast_sip_session_sdp_handler *handler;
 	RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
 	int res;
+
+	/* If this is a declined stream mark it as removed and don't even bother invoking a handler */
+	if (!local_stream->desc.port) {
+		ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_REMOVED);
+		return 1;
+	}
+
+	/* Determine the state of the stream based on our local SDP */
+	if (pjmedia_sdp_media_find_attr2(local_stream, "sendonly", NULL)) {
+		ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDONLY);
+	} else if (pjmedia_sdp_media_find_attr2(local_stream, "recvonly", NULL)) {
+		ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_RECVONLY);
+	} else if (pjmedia_sdp_media_find_attr2(local_stream, "inactive", NULL)) {
+		ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_INACTIVE);
+	} else {
+		ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDRECV);
+	}
 
 	/* We need a null-terminated version of the media string */
 	ast_copy_pj_str(media, &local->media[index]->desc.media, sizeof(media));
@@ -419,7 +573,7 @@
 		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);
+		res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
 		if (res >= 0) {
 			ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
 				ast_codec_media_type2str(session_media->type),
@@ -441,7 +595,7 @@
 		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);
+		res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
 		if (res < 0) {
 			/* Catastrophic failure. Abort! */
 			return -1;
@@ -469,33 +623,69 @@
 {
 	int i;
 	int type_streams[AST_MEDIA_TYPE_END] = {0};
+	struct ast_sip_session_media_state *media_state;
 
 	for (i = 0; i < local->media_count; ++i) {
 		struct ast_sip_session_media *session_media;
 		char media[20];
 		enum ast_media_type type;
+		struct ast_stream *stream;
 
 		if (!remote->media[i]) {
 			continue;
 		}
+
 		ast_copy_pj_str(media, &local->media[i]->desc.media, sizeof(media));
 		type = ast_media_type_from_str(media);
-		if (limitation_reached(type, session->endpoint, type_streams)) {
-			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
+		 * up session media instances (and Asterisk streams) that correspond to
+		 * the local SDP, and there should be the same number of session medias
+		 * and streams as there are local SDP streams
 		 */
-		ast_assert(i < AST_VECTOR_SIZE(&session->media));
+		ast_assert(i < AST_VECTOR_SIZE(&session->pending_media_state->sessions));
+		ast_assert(i < ast_stream_topology_get_count(session->pending_media_state->topology));
 
-		session_media = AST_VECTOR_GET(&session->media, i);
-		if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i)) {
+		session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, i);
+		stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
+
+		if (limitation_reached(type, session->endpoint, type_streams)) {
+			ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+		}
+
+		if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i, stream)) {
 			return -1;
 		}
 		++type_streams[type];
 	}
+
+	/* Apply the pending media state to the channel and make it active */
+	ast_channel_lock(session->channel);
+
+	/* Update the topology on the channel to match the accepted one */
+	ast_channel_set_stream_topology(session->channel, session->pending_media_state->topology);
+	session->pending_media_state->topology = NULL;
+
+	/* Remove all current file descriptors from the channel */
+	for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++i) {
+		ast_channel_internal_fd_clear(session->channel, i + AST_EXTENDED_FDS);
+	}
+
+	/* Add all the file descriptors from the pending media state */
+	for (i = 0; i < AST_VECTOR_SIZE(&session->pending_media_state->read_callbacks); ++i) {
+		struct ast_sip_session_media_read_callback_state *callback_state = AST_VECTOR_GET_ADDR(&session->pending_media_state->read_callbacks, i);
+
+		ast_channel_internal_fd_set(session->channel, i + AST_EXTENDED_FDS, callback_state->fd);
+	}
+
+	media_state = session->active_media_state;
+	session->active_media_state = session->pending_media_state;
+
+	/* Active and pending flip flop as needed, so clear the former active for the next pending change */
+	session->pending_media_state = media_state;
+	sip_session_media_state_reset(session->pending_media_state);
+
+	ast_channel_unlock(session->channel);
 
 	ast_queue_frame(session->channel, &ast_null_frame);
 
@@ -1098,34 +1288,38 @@
 {
 	int i;
 
+	/* It should not be possible to handle an incoming SDP while we have a pending topology already in progress */
+	ast_assert(session->pending_media_state->topology == NULL);
+
+	session->pending_media_state->topology = ast_stream_topology_alloc();
+	if (!session->pending_media_state->topology) {
+		return -1;
+	}
+
 	for (i = 0; i < sdp->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);
+		struct ast_stream *stream;
+		enum ast_media_type type;
 		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));
 
-		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;
-			}
+		type = ast_media_type_from_str(media);
+		stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
+		if (!stream) {
+			return -1;
 		}
+
+		ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);
+
+		session_media = add_session_media(session, ast_media_type_from_str(media), i);
 		if (!session_media) {
-			session_media = add_session_media(session, ast_media_type_from_str(media), i);
-			if (!session_media) {
-				return -1;
-			}
+			return -1;
 		}
 
 		if (session_media->handler) {
@@ -1415,8 +1609,8 @@
 
 	ast_taskprocessor_unreference(session->serializer);
 	ao2_cleanup(session->datastores);
-	AST_VECTOR_CALLBACK_VOID(&session->media, ao2_cleanup);
-	AST_VECTOR_FREE(&session->media);
+	sip_session_media_state_free(session->active_media_state);
+	sip_session_media_state_free(session->pending_media_state);
 
 	AST_LIST_HEAD_DESTROY(&session->supplements);
 	while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {
@@ -1426,7 +1620,7 @@
 	ao2_cleanup(session->endpoint);
 	ao2_cleanup(session->aor);
 	ao2_cleanup(session->contact);
-	ast_stream_topology_free(session->req_topology);
+	ast_stream_topology_free(session->pending_media_state->topology);
 	ao2_cleanup(session->direct_media_cap);
 
 	ast_dsp_free(session->dsp);
@@ -1499,12 +1693,16 @@
 	if (!session->direct_media_cap) {
 		return NULL;
 	}
-	session->req_topology = ast_stream_topology_alloc();
-	if (!session->req_topology) {
-		return NULL;
-	}
 	session->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
 	if (!session->datastores) {
+		return NULL;
+	}
+	session->active_media_state = sip_session_media_state_alloc();
+	if (!session->active_media_state) {
+		return NULL;
+	}
+	session->pending_media_state = sip_session_media_state_alloc();
+	if (!session->pending_media_state) {
 		return NULL;
 	}
 
@@ -1524,10 +1722,6 @@
 	}
 
 	session->endpoint = ao2_bump(endpoint);
-
-	if (AST_VECTOR_INIT(&session->media, DEFAULT_NUM_SESSION_MEDIA) < 0) {
-		return NULL;
-	}
 
 	if (rdata) {
 		/*
@@ -1879,25 +2073,29 @@
 
 			ast_stream_set_formats(clone_stream, joint_cap);
 
-			if (ast_stream_topology_append_stream(session->req_topology, clone_stream) < 0) {
+			if (!session->pending_media_state->topology) {
+				session->pending_media_state->topology = ast_stream_topology_alloc();
+				if (!session->pending_media_state->topology) {
+					pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+					ao2_ref(session, -1);
+					return NULL;
+				}
+			}
+
+			if (ast_stream_topology_append_stream(session->pending_media_state->topology, clone_stream) < 0) {
 				ast_stream_free(clone_stream);
 				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.topology); ++i) {
-			struct ast_stream *stream;
-
-			stream = ast_stream_topology_get_stream(endpoint->media.topology, i);
-			add_session_media(session, ast_stream_get_type(stream), -1);
+	if (!session->pending_media_state->topology) {
+		/* Use the configured topology on the endpoint as the pending one */
+		session->pending_media_state->topology = ast_stream_topology_clone(endpoint->media.topology);
+		if (!session->pending_media_state->topology) {
+			pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+			ao2_ref(session, -1);
+			return NULL;
 		}
 	}
 
@@ -3108,9 +3306,9 @@
 	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;
 	int type_streams[AST_MEDIA_TYPE_END] = {0};
+	int stream;
 
 	if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
 		ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n");
@@ -3131,77 +3329,78 @@
 	pj_strdup2(inv->pool_prov, &local->origin.user, session->endpoint->media.sdpowner);
 	pj_strdup2(inv->pool_prov, &local->name, session->endpoint->media.sdpsession);
 
-	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.
+	if (!session->pending_media_state->topology || !ast_stream_topology_get_count(session->pending_media_state->topology)) {
+		/* We've encountered a situation where we have been told to create a local SDP but noone has given us any indication
+		 * of what kind of stream topology they would like. As a fallback we use the topology from the configured endpoint.
 		 */
-		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.topology;
+		ast_stream_topology_free(session->pending_media_state->topology);
+		session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
+		if (!session->pending_media_state->topology) {
+			return NULL;
+		}
 	}
 
-	for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
+	for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) {
 		struct ast_sip_session_media *session_media;
 		struct ast_stream *stream;
 
-		stream = ast_stream_topology_get_stream(topology, i);
+		stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
 
 		if (limitation_reached(ast_stream_get_type(stream), session->endpoint, type_streams)) {
-			continue;
+			ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
 		}
 
-		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);
-		}
-
+		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;
 		}
+
 		++type_streams[ast_stream_get_type(stream)];
 	}
 
-	/* Use the connection details of the first media stream if possible for SDP level */
-	if (local->media_count) {
-		int stream;
+	/* Use the connection details of an available media if possible for SDP level */
+	for (stream = 0; stream < local->media_count; stream++) {
+		if (!local->media[stream]->conn) {
+			continue;
+		}
 
-		/* Since we are using the first media stream as the SDP level we can get rid of it
-		 * from the stream itself
-		 */
-		local->conn = local->media[0]->conn;
-		local->media[0]->conn = NULL;
-		pj_strassign(&local->origin.net_type, &local->conn->net_type);
-		pj_strassign(&local->origin.addr_type, &local->conn->addr_type);
-		pj_strassign(&local->origin.addr, &local->conn->addr);
-
-		/* Go through each media stream seeing if the connection details actually differ,
-		 * if not just use SDP level and reduce the SDP size
-		 */
-		for (stream = 1; stream < local->media_count; stream++) {
+		if (local->conn) {
 			if (!pj_strcmp(&local->conn->net_type, &local->media[stream]->conn->net_type) &&
 				!pj_strcmp(&local->conn->addr_type, &local->media[stream]->conn->addr_type) &&
 				!pj_strcmp(&local->conn->addr, &local->media[stream]->conn->addr)) {
 				local->media[stream]->conn = NULL;
 			}
+			continue;
 		}
-	} else {
-		local->origin.net_type = STR_IN;
-		local->origin.addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4;
+
+		/* This stream's connection info will serve as the connection details for SDP level */
+		local->conn = local->media[stream]->conn;
+		local->media[stream]->conn = NULL;
+
+		continue;
+	}
+
+	/* If no SDP level connection details are present then create some */
+	if (!local->conn) {
+		local->conn = pj_pool_zalloc(inv->pool_prov, sizeof(struct pjmedia_sdp_conn));
+		local->conn->net_type = STR_IN;
+		local->conn->addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4;
 
 		if (!ast_strlen_zero(session->endpoint->media.address)) {
-			pj_strdup2(inv->pool_prov, &local->origin.addr, session->endpoint->media.address);
+			pj_strdup2(inv->pool_prov, &local->conn->addr, session->endpoint->media.address);
 		} else {
-			pj_strdup2(inv->pool_prov, &local->origin.addr, ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()));
+			pj_strdup2(inv->pool_prov, &local->conn->addr, ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()));
 		}
 	}
 
+	pj_strassign(&local->origin.net_type, &local->conn->net_type);
+	pj_strassign(&local->origin.addr_type, &local->conn->addr_type);
+	pj_strassign(&local->origin.addr, &local->conn->addr);
+
 	return local;
 }
 
diff --git a/res/res_pjsip_session.exports.in b/res/res_pjsip_session.exports.in
index fdfc5fb..b7bd21b8 100644
--- a/res/res_pjsip_session.exports.in
+++ b/res/res_pjsip_session.exports.in
@@ -1,27 +1,7 @@
 {
 	global:
-		LINKER_SYMBOL_PREFIXast_sip_session_terminate;
-		LINKER_SYMBOL_PREFIXast_sip_session_defer_termination;
-		LINKER_SYMBOL_PREFIXast_sip_session_defer_termination_cancel;
-		LINKER_SYMBOL_PREFIXast_sip_session_end_if_deferred;
-		LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler;
-		LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler;
-		LINKER_SYMBOL_PREFIXast_sip_session_register_supplement;
-		LINKER_SYMBOL_PREFIXast_sip_session_unregister_supplement;
-		LINKER_SYMBOL_PREFIXast_sip_session_alloc_datastore;
-		LINKER_SYMBOL_PREFIXast_sip_session_add_datastore;
-		LINKER_SYMBOL_PREFIXast_sip_session_get_datastore;
-		LINKER_SYMBOL_PREFIXast_sip_session_remove_datastore;
-		LINKER_SYMBOL_PREFIXast_sip_session_get_identity;
-		LINKER_SYMBOL_PREFIXast_sip_session_refresh;
-		LINKER_SYMBOL_PREFIXast_sip_session_send_response;
-		LINKER_SYMBOL_PREFIXast_sip_session_send_request;
-		LINKER_SYMBOL_PREFIXast_sip_session_create_invite;
-		LINKER_SYMBOL_PREFIXast_sip_session_create_outgoing;
-		LINKER_SYMBOL_PREFIXast_sip_session_suspend;
-		LINKER_SYMBOL_PREFIXast_sip_session_unsuspend;
+		LINKER_SYMBOL_PREFIXast_sip_session_*;
 		LINKER_SYMBOL_PREFIXast_sip_dialog_get_session;
-		LINKER_SYMBOL_PREFIXast_sip_session_resume_reinvite;
 		LINKER_SYMBOL_PREFIXast_sip_channel_pvt_alloc;
 	local:
 		*;
diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c
index 924a2ee..026755c 100644
--- a/res/res_pjsip_t38.c
+++ b/res/res_pjsip_t38.c
@@ -195,10 +195,10 @@
 {
 	int i;
 
-	for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {
+	for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->sessions); ++i) {
 		struct ast_sip_session_media *session_media;
 
-		session_media = AST_VECTOR_GET(&session->media, i);
+		session_media = AST_VECTOR_GET(&session->active_media_state->sessions, i);
 		if (session_media->type == AST_MEDIA_TYPE_IMAGE) {
 			return session_media;
 		}
@@ -692,11 +692,11 @@
 
 /*! \brief Function which negotiates an incoming media stream */
 static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
-	const struct pjmedia_sdp_session *sdp, int index)
+	struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp,
+	int index, struct ast_stream *asterisk_stream)
 {
 	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);
 
@@ -845,11 +845,10 @@
 
 /*! \brief Function which applies a negotiated 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)
+	struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local,
+	const struct pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
 {
 	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/5876
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: I8afd8dd2eb538806a39b887af0abd046266e14c7
Gerrit-Change-Number: 5876
Gerrit-PatchSet: 1
Gerrit-Owner: Joshua Colp <jcolp at digium.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20170618/3dc0ed8b/attachment-0001.html>


More information about the asterisk-code-review mailing list