[Asterisk-code-review] ACN: Advanced Codec Negotiation for chan_pjsip (asterisk[master])

George Joseph asteriskteam at digium.com
Wed Jul 15 07:55:46 CDT 2020


George Joseph has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/14637 )


Change subject: ACN: Advanced Codec Negotiation for chan_pjsip
......................................................................

ACN: Advanced Codec Negotiation for chan_pjsip

This commit is the second in a series that implements
Advanced Codec Negotiation and does not represent a final
implementation.  Many existing features either do not yet work
or have yet to be tested including...

 * Direct media
 * 100rel/early media
 * Re-invites
 * Fax
 * Multi-stream
 * Deferred SDP
 * ARI channel operations
 * Operation with other channel technologies

There are also several refactors that still need to happen to remove
duplicated code, re-organize internal functions, consolidate
activities, etc.

Summary of functional changes by module:

app_dial
 * Capture resolved topology when a callee channel answers and pass
   it to the bridging code in features.c.

features
 * In pre_bridge_setup (which answers the caller channel), use
   the new ast_raw_answer_with_stream_topology() API to pass
   the callee channel's topology to the caller channel.

channel
 * Added a new callback "answer_with_stream_topology" to the
   ast_channel_tech structure.  Channels implementing it will
   receive the callee channel's topology if available.
 * Added peer_topology to the ast_bridge_config structure which
   is used to pass the callee channel's topology to pre_bridge_setup.
 * Added a new API "ast_raw_answer_with_stream_topology" which
   calls the channel's answer_with_stream_topology callback.

stream
 * Fixed a leak in ast_stream_topology_create_resolved.

res_pjsip
 * Updated documentation.
 * Removed obsolete code.

res_pjsip/pjsip_configuration
 * Updated endpoint configuration parsing.
 * Removed obsolete code.

res_pjsip_refer
 * Use ast_raw_answer_with_stream_topology instead of ast_raw_answer.

res_pjsip_sdp_rtp
 * Refactored get_codecs to return an ast_format_caps.
 * Removed obsolete code in set_incoming_call_offer_cap.
 * Removed obsolete code in set_caps.
 * Removed obsolete code in negotiate_incoming_sdp_stream.
 * Removed obsolete code in create_outgoing_sdp_stream.

res_pjsip_session
 * Preserve the pending topology in handle_negotiated_sdp so
   chan_pjsip can do a topology resolution.
 * Removed obsolete code in ast_sip_session_create_outgoing.
 * Removed obsolete code in new_invite.
 * Updated session_inv_on_tsx_state_changed to call handlers with
   the proper BEFORE/AFTER MEDIA flag.
 * Removed obsolete code in create_local_sdp.

res_pjsip_session/pjsip_session_caps
tests/test_res_pjsip_session_caps
 * Removed.

chan_pjsip
 * Implement chan_pjsip_answer_with_stream_topology
 * chan_pjsip_incoming_request now resolves the topology on an
   incoming invite with that of the endpoint.
 * chan_pjsip_request_with_stream_topology now resolves the
   topology passed from the core on an outgoing call with that
   of the endpoint.
 * chan_pjsip_incoming_response now resolves the topology received
   on a 200 OK with that which was sent on the original invite.
 * chan_pjsip_answer_with_stream_topology now resolves the topology
   passed from the core with that which was originally passed to
   the core.

ASTERISK-28856

Change-Id: Iad188ae997bdcb5c28e2eb12c6bb2b732538ad45
---
M apps/app_dial.c
M channels/chan_pjsip.c
M configs/samples/pjsip.conf.sample
M contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py
M include/asterisk/channel.h
M include/asterisk/res_pjsip.h
M include/asterisk/res_pjsip_session.h
D include/asterisk/res_pjsip_session_caps.h
M main/bridge_channel.c
M main/channel.c
M main/features.c
M main/stream.c
M res/Makefile
M res/res_pjsip.c
M res/res_pjsip/pjsip_configuration.c
M res/res_pjsip_refer.c
M res/res_pjsip_sdp_rtp.c
M res/res_pjsip_session.c
D res/res_pjsip_session/pjsip_session_caps.c
D tests/test_res_pjsip_session_caps.c
20 files changed, 894 insertions(+), 1,221 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/37/14637/1

diff --git a/apps/app_dial.c b/apps/app_dial.c
index 95f36d7..1a09cb9 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -1204,7 +1204,8 @@
 	struct privacy_args *pa,
 	const struct cause_args *num_in, int *result, char *dtmf_progress,
 	const int ignore_cc,
-	struct ast_party_id *forced_clid, struct ast_party_id *stored_clid)
+	struct ast_party_id *forced_clid, struct ast_party_id *stored_clid,
+	struct ast_bridge_config *config)
 {
 	struct cause_args num = *num_in;
 	int prestart = num.busy + num.congestion + num.nochan;
@@ -1418,6 +1419,13 @@
 							}
 						}
 						peer = c;
+						if (f->data.ptr != NULL) {
+							config->peer_topology = *((struct ast_stream_topology **)f->data.ptr);
+							ast_trace(2, "%s Found topology in frame: %p %p %s\n",
+								ast_channel_name(peer), f->data.ptr, config->peer_topology,
+								ast_str_tmp(256, ast_stream_topology_to_str(config->peer_topology, &STR_TMP)));
+						}
+
 						/* Inform everyone else that they've been canceled.
 						 * The dial end event for the peer will be sent out after
 						 * other Dial options have been handled.
@@ -2838,7 +2846,7 @@
 	}
 
 	peer = wait_for_answer(chan, &out_chans, &to, peerflags, opt_args, &pa, &num, &result,
-		dtmf_progress, ignore_cc, &forced_clid, &stored_clid);
+		dtmf_progress, ignore_cc, &forced_clid, &stored_clid, &config);
 
 	if (!peer) {
 		if (result) {
@@ -3267,6 +3275,7 @@
 				ast_channel_setoption(chan, AST_OPTION_OPRMODE, &oprmode, sizeof(oprmode), 0);
 			}
 			setup_peer_after_bridge_goto(chan, peer, &opts, opt_args);
+
 			res = ast_bridge_call(chan, peer, &config);
 		}
 	}
@@ -3304,6 +3313,13 @@
 	}
 
 done:
+	if (config.peer_topology) {
+		ast_trace(2, "%s Cleaning up topology: %p %s\n",
+			ast_channel_name(peer), &config.peer_topology,
+			ast_str_tmp(256, ast_stream_topology_to_str(config.peer_topology, &STR_TMP)));
+
+		ast_stream_topology_free(config.peer_topology);
+	}
 	if (config.warning_sound) {
 		ast_free((char *)config.warning_sound);
 	}
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index e99ec31..9f12e52 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -95,6 +95,7 @@
 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 int chan_pjsip_answer_with_stream_topology(struct ast_channel *ast, struct ast_stream_topology *topology);
 static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast);
 static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f);
 static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *f);
@@ -118,6 +119,7 @@
 	.call = chan_pjsip_call,
 	.hangup = chan_pjsip_hangup,
 	.answer = chan_pjsip_answer,
+	.answer_with_stream_topology = chan_pjsip_answer_with_stream_topology,
 	.read_stream = chan_pjsip_read_stream,
 	.write = chan_pjsip_write,
 	.write_stream = chan_pjsip_write_stream,
@@ -132,11 +134,16 @@
 	.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER | AST_CHAN_TP_SEND_TEXT_DATA
 };
 
+static struct ast_stream_topology *resolve_topology(struct ast_stream_topology *topology,
+	struct ast_sip_endpoint *endpoint, struct ast_stream_codec_negotiation_prefs *prefs, int *cause);
+
 /*! \brief SIP session interaction functions */
 static void chan_pjsip_session_begin(struct ast_sip_session *session);
 static void chan_pjsip_session_end(struct ast_sip_session *session);
 static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata);
-static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata);
+static void chan_pjsip_incoming_response_before_media(struct ast_sip_session *session, struct pjsip_rx_data *rdata);
+static void chan_pjsip_incoming_response_after_media(struct ast_sip_session *session, struct pjsip_rx_data *rdata);
+static void chan_pjsip_outgoing_response(struct ast_sip_session *session, struct pjsip_tx_data *tdata);
 
 /*! \brief SIP session supplement structure */
 static struct ast_sip_session_supplement chan_pjsip_supplement = {
@@ -145,16 +152,25 @@
 	.session_begin = chan_pjsip_session_begin,
 	.session_end = chan_pjsip_session_end,
 	.incoming_request = chan_pjsip_incoming_request,
+	.outgoing_response = chan_pjsip_outgoing_response,
 	/* It is important that this supplement runs after media has been negotiated */
 	.response_priority = AST_SIP_SESSION_AFTER_MEDIA,
 };
 
 /*! \brief SIP session supplement structure just for responses */
-static struct ast_sip_session_supplement chan_pjsip_supplement_response = {
+static struct ast_sip_session_supplement chan_pjsip_supplement_response_before_media = {
 	.method = "INVITE",
 	.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL,
-	.incoming_response = chan_pjsip_incoming_response,
-	.response_priority = AST_SIP_SESSION_BEFORE_MEDIA | AST_SIP_SESSION_AFTER_MEDIA,
+	.incoming_response = chan_pjsip_incoming_response_before_media,
+	.response_priority = AST_SIP_SESSION_BEFORE_MEDIA,
+};
+
+/*! \brief SIP session supplement structure just for responses */
+static struct ast_sip_session_supplement chan_pjsip_supplement_response_after_media = {
+	.method = "INVITE",
+	.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL,
+	.incoming_response = chan_pjsip_incoming_response_after_media,
+	.response_priority = AST_SIP_SESSION_AFTER_MEDIA,
 };
 
 static int chan_pjsip_incoming_ack(struct ast_sip_session *session, struct pjsip_rx_data *rdata);
@@ -243,6 +259,7 @@
 {
 	SCOPE_ENTER(1, "%s Native formats %s\n", ast_channel_name(chan),
 		ast_str_tmp(AST_FORMAT_CAP_NAMES_LEN, ast_format_cap_get_names(ast_channel_nativeformats(chan), &STR_TMP)));
+
 	ast_format_cap_append_from_cap(result, ast_channel_nativeformats(chan), AST_MEDIA_TYPE_UNKNOWN);
 	SCOPE_EXIT_RTN();
 }
@@ -506,43 +523,10 @@
 	}
 }
 
-/*!
- * \brief Determine if a topology is compatible with format capabilities
- *
- * This will return true if ANY formats in the topology are compatible with the format
- * capabilities.
- *
- * XXX When supporting true multistream, we will need to be sure to mark which streams from
- * top1 are compatible with which streams from top2. Then the ones that are not compatible
- * will need to be marked as "removed" so that they are negotiated as expected.
- *
- * \param top Topology
- * \param cap Format capabilities
- * \retval 1 The topology has at least one compatible format
- * \retval 0 The topology has no compatible formats or an error occurred.
- */
-static int compatible_formats_exist(struct ast_stream_topology *top, struct ast_format_cap *cap)
-{
-	struct ast_format_cap *cap_from_top;
-	int res;
-	SCOPE_ENTER(1, "Topology: %s Formats: %s\n",
-		ast_str_tmp(AST_FORMAT_CAP_NAMES_LEN, ast_stream_topology_to_str(top, &STR_TMP)),
-		ast_str_tmp(AST_FORMAT_CAP_NAMES_LEN, ast_format_cap_get_names(cap, &STR_TMP)));
-
-	cap_from_top = ast_stream_topology_get_formats(top);
-
-	if (!cap_from_top) {
-		SCOPE_EXIT_RTN_VALUE(0, "Topology had no formats\n");
-	}
-
-	res = ast_format_cap_iscompatible(cap_from_top, cap);
-	ao2_ref(cap_from_top, -1);
-
-	SCOPE_EXIT_RTN_VALUE(res, "Compatible? %s\n", res ? "yes" : "no");
-}
-
 /*! \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)
+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, struct ast_stream_topology *resolved_topology)
 {
 	struct ast_channel *chan;
 	struct ast_format_cap *caps;
@@ -550,10 +534,11 @@
 	struct ast_sip_channel_pvt *channel;
 	struct ast_variable *var;
 	struct ast_stream_topology *topology;
-	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
+	SCOPE_ENTER(1, "%s Topology: %s\n", ast_sip_session_get_name(session),
+		ast_str_tmp(256, ast_stream_topology_to_str(resolved_topology, &STR_TMP)));
 
 	if (!(pvt = ao2_alloc_options(sizeof(*pvt), chan_pjsip_pvt_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
-		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't create pvt\n");
+		SCOPE_EXIT_RTN_VALUE(NULL, "No pvt\n");
 	}
 
 	chan = ast_channel_alloc_with_endpoint(1, state,
@@ -579,24 +564,16 @@
 
 	ast_channel_tech_pvt_set(chan, channel);
 
-	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);
-			ast_hangup(chan);
-			SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't create caps\n");
-		}
-		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_stream_topology_get_formats(session->pending_media_state->topology);
-		topology = ast_stream_topology_clone(session->pending_media_state->topology);
+	if (!ast_stream_topology_get_active_count(resolved_topology)) {
+		ast_channel_unlock(chan);
+		ast_hangup(chan);
+		SCOPE_EXIT_RTN_VALUE(NULL, "Resolved topology empty\n");
 	}
 
-	if (!topology || !caps) {
+	caps = ast_stream_topology_get_formats(resolved_topology);
+	topology = ast_stream_topology_clone(resolved_topology);
+	if (!caps || !topology) {
 		ao2_cleanup(caps);
-		ast_stream_topology_free(topology);
 		ast_channel_unlock(chan);
 		ast_hangup(chan);
 		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't get caps or clone topology\n");
@@ -605,25 +582,20 @@
 	ast_channel_stage_snapshot(chan);
 
 	ast_channel_nativeformats_set(chan, caps);
-	ast_channel_set_stream_topology(chan, topology);
-
 	if (!ast_format_cap_empty(caps)) {
 		struct ast_format *fmt;
 
-		fmt = ast_format_cap_get_best_by_type(caps, AST_MEDIA_TYPE_AUDIO);
-		if (!fmt) {
-			/* Since our capabilities aren't empty, this will succeed */
-			fmt = ast_format_cap_get_format(caps, 0);
-		}
+		fmt = ast_format_cap_get_format(caps, 0);
 		ast_channel_set_writeformat(chan, fmt);
 		ast_channel_set_rawwriteformat(chan, fmt);
 		ast_channel_set_readformat(chan, fmt);
 		ast_channel_set_rawreadformat(chan, fmt);
 		ao2_ref(fmt, -1);
 	}
-
 	ao2_ref(caps, -1);
 
+	ast_channel_set_stream_topology(chan, topology);
+
 	if (state == AST_STATE_RING) {
 		ast_channel_rings_set(chan, 1);
 	}
@@ -674,6 +646,8 @@
 
 struct answer_data {
 	struct ast_sip_session *session;
+	struct ast_stream_topology *topology;
+	int cause;
 	unsigned long indent;
 };
 
@@ -683,9 +657,15 @@
 	pj_status_t status = PJ_SUCCESS;
 	pjsip_tx_data *packet = NULL;
 	struct ast_sip_session *session = ans_data->session;
+	struct ast_format_cap *active_caps;
+	const struct pjmedia_sdp_session *local;
+	struct ast_stream_topology *resolved_topology;
+	struct ast_str *error_message = ast_str_alloca(128);
 	SCOPE_ENTER_TASK(1, ans_data->indent, "%s\n", ast_sip_session_get_name(session));
+	ans_data->cause = AST_CAUSE_NORMAL_CLEARING;
 
 	if (session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
+		ans_data->cause = AST_CAUSE_INVALID_CALL_REFERENCE;
 		ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n",
 			session->inv_session->cause,
 			pjsip_get_status_text(session->inv_session->cause)->ptr);
@@ -696,11 +676,76 @@
 	}
 
 	pjsip_dlg_inc_lock(session->inv_session->dlg);
-	if (session->inv_session->invite_tsx) {
-		status = pjsip_inv_answer(session->inv_session, 200, NULL, NULL, &packet);
-	} else {
+	if (!session->inv_session->invite_tsx) {
+		ans_data->cause = AST_CAUSE_INVALID_CALL_REFERENCE;
 		ast_log(LOG_ERROR,"Cannot answer '%s' because there is no associated SIP transaction\n",
 			ast_channel_name(session->channel));
+		pjsip_dlg_dec_lock(session->inv_session->dlg);
+		SCOPE_EXIT_RTN_VALUE(0, "No associated sip transaction\n");
+	}
+
+	if (ans_data->topology) {
+		resolved_topology = ast_stream_topology_create_resolved(ans_data->topology,
+			session->pending_media_state->topology, &session->endpoint->media.outgoing_answer_codec_prefs,
+			&error_message);
+
+		if (!resolved_topology || ast_stream_topology_get_active_count(resolved_topology) == 0) {
+			if (session->endpoint->media.outgoing_answer_codec_prefs.transcode != CODEC_NEGOTIATION_TRANSCODE_ALLOW) {
+				ast_trace(1, "%s transcoding prevented.  Bail\n", ast_str_buffer(error_message));
+				packet = NULL;
+				if (pjsip_inv_end_session(session->inv_session, 500, NULL, &packet) == PJ_SUCCESS && packet) {
+					ast_sip_session_send_response(session, packet);
+				}
+				goto end;
+			}
+			ast_trace(1, "No topology in common but transcoding allowed.  Continue\n");
+			/* In this case, we're going to use the existing pending topology */
+		} else {
+			ast_trace(1, "Transcoding not needed.  Continue\n");
+			ast_stream_topology_free(session->pending_media_state->topology);
+			/* Transfer resolved_topology's reference to pending */
+			session->pending_media_state->topology = resolved_topology;
+		}
+
+		ast_trace(1, "Setting channel topology to %s\n",
+			ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)));
+	}
+
+	active_caps = ast_stream_topology_get_formats(session->pending_media_state->topology);
+	ast_channel_nativeformats_set(session->channel, active_caps);
+	if (!ast_format_cap_empty(active_caps)) {
+		struct ast_format *fmt;
+
+		fmt = ast_format_cap_get_format(active_caps, 0);
+		ast_channel_set_writeformat(session->channel, fmt);
+		ast_channel_set_rawwriteformat(session->channel, fmt);
+		ast_channel_set_readformat(session->channel, fmt);
+		ast_channel_set_rawreadformat(session->channel, fmt);
+		ao2_ref(fmt, -1);
+	}
+	ao2_cleanup(active_caps);
+	ast_channel_set_stream_topology(session->channel, ao2_bump(session->pending_media_state->topology));
+
+	local = ast_sip_session_create_local_sdp(session->inv_session, session, NULL);
+	if (!local) {
+		packet = NULL;
+		if (pjsip_inv_end_session(session->inv_session, 500, NULL, &packet) == PJ_SUCCESS && packet) {
+			ast_sip_session_send_response(session, packet);
+		}
+		ans_data->cause = AST_CAUSE_FAILURE;
+		goto end;
+	}
+
+	pjmedia_sdp_neg_set_prefer_remote_codec_order(session->inv_session->neg, PJ_FALSE);
+#ifdef PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS
+	if (!session->endpoint->preferred_codec_only) {
+		pjmedia_sdp_neg_set_answer_multiple_codecs(session->inv_session->neg, PJ_TRUE);
+	}
+#endif
+	{
+		SCOPE_ENTER(2, "pjsip_inv_answer\n");
+		status = pjsip_inv_answer(session->inv_session, 200, NULL, local, &packet);
+		SCOPE_EXIT("pjsip_inv_answer\n");
 	}
 	pjsip_dlg_dec_lock(session->inv_session->dlg);
 
@@ -708,12 +753,14 @@
 		ast_sip_session_send_response(session, packet);
 	}
 
+end:
 #ifdef HAVE_PJSIP_INV_SESSION_REF
 	pjsip_inv_dec_ref(session->inv_session);
 #endif
 
 	if (status != PJ_SUCCESS) {
 		char err[PJ_ERR_MSG_SIZE];
+		ans_data->cause = AST_CAUSE_FAILURE;
 
 		pj_strerror(status, err, sizeof(err));
 		ast_log(LOG_WARNING,"Cannot answer '%s': %s\n",
@@ -724,21 +771,23 @@
 		 */
 		SCOPE_EXIT_RTN_VALUE(-2, "pjproject failure\n");
 	}
+
 	SCOPE_EXIT_RTN_VALUE(0);
 }
 
 /*! \brief Function called by core when we should answer a PJSIP session */
-static int chan_pjsip_answer(struct ast_channel *ast)
+static int chan_pjsip_answer_with_stream_topology(struct ast_channel *ast,
+	struct ast_stream_topology *topology)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
 	struct ast_sip_session *session;
 	struct answer_data ans_data = { 0, };
 	int res;
-	SCOPE_ENTER(1, "%s\n", ast_channel_name(ast));
+	SCOPE_ENTER(1, "%s  Topology: %s\n", ast_channel_name(ast),
+		ast_str_tmp(256, ast_stream_topology_to_str(topology, &STR_TMP)));
 
 	if (ast_channel_state(ast) == AST_STATE_UP) {
 		SCOPE_EXIT_RTN_VALUE(0, "Already up\n");
-		return 0;
 	}
 
 	ast_setstate(ast, AST_STATE_UP);
@@ -757,6 +806,7 @@
 	   attempts to do direct media) */
 	ast_channel_unlock(ast);
 	ans_data.session = session;
+	ans_data.topology = topology;
 	ans_data.indent = ast_trace_get_indent();
 	res = ast_sip_push_task_wait_serializer(session->serializer, answer, &ans_data);
 	if (res) {
@@ -774,7 +824,12 @@
 	ao2_ref(session, -1);
 	ast_channel_lock(ast);
 
-	SCOPE_EXIT_RTN_VALUE(0);
+	SCOPE_EXIT_RTN_VALUE(0, "Cause: %d %s\n", ans_data.cause, ast_cause2str(ans_data.cause));
+}
+
+static int chan_pjsip_answer(struct ast_channel *ast)
+{
+	return chan_pjsip_answer_with_stream_topology(ast, NULL);
 }
 
 /*! \brief Internal helper function called when CNG tone is detected */
@@ -1620,9 +1675,9 @@
 static int handle_topology_request_change(struct ast_sip_session *session,
 	const struct ast_stream_topology *proposed)
 {
-	struct topology_change_refresh_data *refresh_data;
+	struct topology_change_refresh_data *refresh_data = { 0, };
 	int res;
-	SCOPE_ENTER(1);
+	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(refresh_data->session));
 
 	refresh_data = topology_change_refresh_data_alloc(session, proposed);
 	if (!refresh_data) {
@@ -1649,7 +1704,7 @@
 	const struct ast_stream_topology *topology;
 	struct ast_frame f = { .frametype = AST_FRAME_CONTROL, .subclass = { .integer = condition } };
 	char subclass[40] = "";
-	SCOPE_ENTER(1, "%s Handling %s\n", ast_channel_name(ast),
+	SCOPE_ENTER(1, "%s Handling %d %s\n", ast_channel_name(ast), condition,
 		ast_frame_subclass2str(&f, subclass, sizeof(subclass), NULL, 0));
 
 	switch (condition) {
@@ -2433,7 +2488,6 @@
 		ast_str_tmp(256, ast_stream_topology_to_str(channel->session->pending_media_state->topology, &STR_TMP))
 		);
 
-
 	res = ast_sip_session_create_invite(session, &tdata);
 
 	if (res) {
@@ -2444,6 +2498,7 @@
 		update_initial_connected_line(session);
 		ast_sip_session_send_request(session, tdata);
 	}
+
 	ao2_ref(channel, -1);
 	SCOPE_EXIT_RTN_VALUE(res, "RC: %d\n", res);
 }
@@ -2456,6 +2511,8 @@
 		ast_str_tmp(256, ast_stream_topology_to_str(channel->session->pending_media_state->topology, &STR_TMP)));
 
 	ao2_ref(channel, +1);
+	ast_trace(2, "%s Push call task\n", ast_sip_session_get_name(channel->session));
+
 	if (ast_sip_push_task(channel->session->serializer, call, channel)) {
 		ast_log(LOG_WARNING, "Error attempting to place outbound call to '%s'\n", dest);
 		ao2_cleanup(channel);
@@ -2591,7 +2648,7 @@
 		SCOPE_EXIT_RTN_VALUE(-1, "No channel or session\n");
 	}
 
-	cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel));
+	cause = hangup_cause2sip(ast_channel_hangupcause(ast));
 	h_data = hangup_data_alloc(cause, ast);
 
 	if (!h_data) {
@@ -2616,142 +2673,262 @@
 	SCOPE_EXIT_RTN_VALUE(-1, "Cause: %d\n", cause);
 }
 
+static struct ast_sip_endpoint *determine_endpoint(char *destination, char **request_user,
+	int *cause)
+{
+	struct ast_sip_endpoint *endpoint;
+	char *endpoint_name;
+	SCOPE_ENTER(1, "%s\n", destination);
+
+	if (ast_sip_get_disable_multi_domain()) {
+		/* If a request user has been specified extract it from the endpoint name portion */
+		if ((endpoint_name = strchr(destination, '@'))) {
+			*request_user = destination;
+			*endpoint_name++ = '\0';
+		} else {
+			endpoint_name = ast_strdupa(destination);
+			*request_user = NULL;
+		}
+
+		if (ast_strlen_zero(endpoint_name)) {
+			if (*request_user) {
+				ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name: %s@<endpoint-name>\n",
+					*request_user);
+			} else {
+				ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name\n");
+			}
+			*cause = AST_CAUSE_CHANNEL_UNACCEPTABLE;
+			SCOPE_EXIT_RTN_VALUE(NULL, "Empty endpoint name\n");
+		}
+		endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+		if (!endpoint) {
+			ast_log(LOG_ERROR, "Unable to create PJSIP channel - endpoint '%s' was not found\n", endpoint_name);
+			*cause = AST_CAUSE_NO_ROUTE_DESTINATION;
+			SCOPE_EXIT_RTN_VALUE(NULL, "Endpoint not found\n");
+		}
+		SCOPE_EXIT_RTN_VALUE(endpoint, "Endpoint: %s\n", endpoint_name);
+	}
+
+	/* Multi-domain is possible */
+
+	/* First try to find an exact endpoint match, for single (user) or multi-domain (user at domain) */
+	endpoint_name = ast_strdupa(destination);
+	if (ast_strlen_zero(endpoint_name)) {
+		ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name\n");
+		*cause = AST_CAUSE_CHANNEL_UNACCEPTABLE;
+		SCOPE_EXIT_RTN_VALUE(NULL, "Empty endpoint name\n");
+	}
+	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+	if (!endpoint) {
+		/* It seems it's not a multi-domain endpoint or single endpoint exact match,
+		 * it's possible that it's a SIP trunk with a specified user (user at trunkname),
+		 * so extract the user before @ sign.
+		 */
+		endpoint_name = strchr(destination, '@');
+		if (!endpoint_name) {
+			/*
+			 * Couldn't find an '@' so it had to be an endpoint
+			 * name that doesn't exist.
+			 */
+			ast_log(LOG_ERROR, "Unable to create PJSIP channel - endpoint '%s' was not found\n",
+				destination);
+			*cause = AST_CAUSE_NO_ROUTE_DESTINATION;
+			SCOPE_EXIT_RTN_VALUE(NULL, "Endpoint '%s' not found\n", destination);
+		}
+		*request_user = destination;
+		*endpoint_name++ = '\0';
+
+		if (ast_strlen_zero(endpoint_name)) {
+			ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name: %s@<endpoint-name>\n",
+				*request_user);
+			*cause = AST_CAUSE_CHANNEL_UNACCEPTABLE;
+			SCOPE_EXIT_RTN_VALUE(NULL, "Empty endpoint\n");
+		}
+
+		endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+		if (!endpoint) {
+			ast_log(LOG_ERROR, "Unable to create PJSIP channel - endpoint '%s' was not found\n", endpoint_name);
+			*cause = AST_CAUSE_NO_ROUTE_DESTINATION;
+			SCOPE_EXIT_RTN_VALUE(NULL, "Endpoint '%s' not found\n", destination);
+		}
+	}
+
+	SCOPE_EXIT_RTN_VALUE(endpoint, "Endpoint: %s\n", endpoint_name);
+}
+
+/*!
+ * \internal
+ * \brief Bounce a topology off an endpoint's configured topology
+ *
+ * \param topology The pending topology
+ * \param endpoint The endpoint to get the configured topology from
+ * \param prefs The codec negotiation prefs to use
+ * \param [out]cause A pointer to a cause in which to set the resulting cause code
+ *
+ * \retval Non-null topology which may be empty
+ * \retval NULL is there was a system error like a failure to create an object
+ *
+ * \note This function will always return a topology unless system issues prevent it.
+ * The topology MAY be empty if there were no codecs in common but transcoding was
+ * allowed.
+ *
+ *
+ */
+static struct ast_stream_topology *resolve_topology(struct ast_stream_topology *topology,
+	struct ast_sip_endpoint *endpoint, struct ast_stream_codec_negotiation_prefs *prefs, int *cause)
+{
+	struct ast_stream_topology *joint_topology;
+	const char *endpoint_name = ast_sorcery_object_get_id(endpoint);
+	int valid_stream_count = 0;
+	struct ast_str *error_message;
+	SCOPE_ENTER(2, "Endpoint: %s ReqTopology: %s ConfigTopology: %s Prefs: %s\n",
+		endpoint_name,
+		ast_str_tmp(256, ast_stream_topology_to_str(topology, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(endpoint->media.topology, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_codec_prefs_to_str(prefs, &STR_TMP)));
+
+	if (!endpoint->media.topology || !ast_stream_topology_get_active_count(endpoint->media.topology)) {
+		*cause = AST_CAUSE_FAILURE;
+		ast_log(LOG_ERROR, "Endpoint '%s': No configured topology\n", endpoint_name);
+		SCOPE_EXIT_RTN_VALUE(NULL, "No configured topology\n");
+	}
+
+	/*
+	 * Use the endpoint's topology if none was specified.  This can occur if the
+	 * caller's channel tech doesn't support streams.
+	 */
+	if (!topology || !ast_stream_topology_get_count(topology)) {
+		joint_topology = ast_stream_topology_clone(endpoint->media.topology);
+		if (!joint_topology) {
+			*cause = AST_CAUSE_FAILURE;
+			ast_log(LOG_ERROR, "Endpoint '%s': Couldn't clone endpoint's topology\n", endpoint_name);
+			SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't clone endpoint's topology\n");
+		}
+
+		/* No point in doing any comparisons since the joint topology == endpoint's */
+		*cause = AST_CAUSE_NORMAL_CLEARING;
+		SCOPE_EXIT_RTN_VALUE(joint_topology, "No request topology. Using endpoint's: %s\n",
+			ast_str_tmp(256, ast_stream_topology_to_str(joint_topology, &STR_TMP)));
+	}
+
+	error_message = ast_str_create(128);
+	if (!error_message) {
+		*cause = AST_CAUSE_FAILURE;
+		ast_log(LOG_ERROR, "Endpoint '%s': Couldn't create error_message\n", endpoint_name);
+		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't create error_message\n");
+	}
+
+	*cause = AST_CAUSE_NORMAL_CLEARING;
+	joint_topology = ast_stream_topology_create_resolved(topology, endpoint->media.topology,
+		prefs, &error_message);
+	if (!joint_topology) {
+		*cause = AST_CAUSE_FAILURE;
+		ast_log(LOG_ERROR, "Endpoint '%s': Couldn't create even an empty joint topology %s\n",
+			endpoint_name, error_message ? ast_str_buffer(error_message): "Not enough memory");
+		ast_free(error_message);
+		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't create even an empty joint topology\n");
+	}
+
+	valid_stream_count = ast_stream_topology_get_active_count(joint_topology);
+	/* TODO: Do this on a stream by stream basis */
+	if (valid_stream_count == 0) {
+		if (prefs->transcode == CODEC_NEGOTIATION_TRANSCODE_ALLOW) {
+			ao2_cleanup(joint_topology);
+			joint_topology = ast_stream_topology_clone(endpoint->media.topology);
+			if (!joint_topology) {
+				ast_log(LOG_ERROR, "Endpoint '%s': Fatal error %s\n", endpoint_name, error_message ? ast_str_buffer(error_message): "Not enough memory");
+				ast_free(error_message);
+				*cause = AST_CAUSE_FAILURE;
+				SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't clone endpoint's topology\n");
+			}
+			*cause = AST_CAUSE_NORMAL_CLEARING;
+			ast_trace(2, "No codecs in common but transcoding allowed\n");
+		} else {
+			*cause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
+			ast_trace(2, "No codecs in common and transcoding not allowed: %s\n", error_message ? ast_str_buffer(error_message): "Not enough memory");
+			ast_log(LOG_ERROR, "Endpoint '%s': %s\n", endpoint_name, error_message ? ast_str_buffer(error_message): "Not enough memory");
+		}
+	}
+
+	ast_free(error_message);
+	SCOPE_EXIT_RTN_VALUE(joint_topology, "Cause: %d %s Joint topology: %s\n", *cause, ast_cause2str(*cause),
+		ast_str_tmp(256, ast_stream_topology_to_str(joint_topology, &STR_TMP)));
+}
+
 struct request_data {
-	struct ast_sip_session *session;
+	struct ast_sip_endpoint *endpoint;
 	struct ast_stream_topology *topology;
-	const char *dest;
-	int cause;
+	struct ast_sip_session *session;
+	const char *aor;
+	char *request_user;
+	int *cause;
+	unsigned long indent;
 };
 
 static int request(void *obj)
 {
 	struct request_data *req_data = obj;
-	struct ast_sip_session *session = NULL;
-	char *tmp = ast_strdupa(req_data->dest), *endpoint_name = NULL, *request_user = NULL;
-	struct ast_sip_endpoint *endpoint;
+	const char *endpoint_name = ast_sorcery_object_get_id(req_data->endpoint);
+	SCOPE_ENTER_TASK(1, req_data->indent, "%s\n", endpoint_name);
 
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(endpoint);
-		AST_APP_ARG(aor);
-	);
-	SCOPE_ENTER(1, "%s\n",tmp);
+	req_data->session = ast_sip_session_create_outgoing(req_data->endpoint, NULL, req_data->aor,
+		req_data->request_user, req_data->topology);
 
-	if (ast_strlen_zero(tmp)) {
-		ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty destination\n");
-		req_data->cause = AST_CAUSE_CHANNEL_UNACCEPTABLE;
-		SCOPE_EXIT_RTN_VALUE(-1, "Empty destination\n");
-	}
-
-	AST_NONSTANDARD_APP_ARGS(args, tmp, '/');
-
-	if (ast_sip_get_disable_multi_domain()) {
-		/* If a request user has been specified extract it from the endpoint name portion */
-		if ((endpoint_name = strchr(args.endpoint, '@'))) {
-			request_user = args.endpoint;
-			*endpoint_name++ = '\0';
-		} else {
-			endpoint_name = args.endpoint;
-		}
-
-		if (ast_strlen_zero(endpoint_name)) {
-			if (request_user) {
-				ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name: %s@<endpoint-name>\n",
-					request_user);
-			} else {
-				ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name\n");
-			}
-			req_data->cause = AST_CAUSE_CHANNEL_UNACCEPTABLE;
-			SCOPE_EXIT_RTN_VALUE(-1, "Empty endpoint name\n");
-		}
-		endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",
-			endpoint_name);
-		if (!endpoint) {
-			ast_log(LOG_ERROR, "Unable to create PJSIP channel - endpoint '%s' was not found\n", endpoint_name);
-			req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION;
-			SCOPE_EXIT_RTN_VALUE(-1, "Endpoint not found\n");
-		}
-	} else {
-		/* First try to find an exact endpoint match, for single (user) or multi-domain (user at domain) */
-		endpoint_name = args.endpoint;
-		if (ast_strlen_zero(endpoint_name)) {
-			ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name\n");
-			req_data->cause = AST_CAUSE_CHANNEL_UNACCEPTABLE;
-			SCOPE_EXIT_RTN_VALUE(-1, "Empty endpoint name\n");
-		}
-		endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",
-			endpoint_name);
-		if (!endpoint) {
-			/* It seems it's not a multi-domain endpoint or single endpoint exact match,
-			 * it's possible that it's a SIP trunk with a specified user (user at trunkname),
-			 * so extract the user before @ sign.
-			 */
-			endpoint_name = strchr(args.endpoint, '@');
-			if (!endpoint_name) {
-				/*
-				 * Couldn't find an '@' so it had to be an endpoint
-				 * name that doesn't exist.
-				 */
-				ast_log(LOG_ERROR, "Unable to create PJSIP channel - endpoint '%s' was not found\n",
-					args.endpoint);
-				req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION;
-				SCOPE_EXIT_RTN_VALUE(-1, "Endpoint not found\n");
-			}
-			request_user = args.endpoint;
-			*endpoint_name++ = '\0';
-
-			if (ast_strlen_zero(endpoint_name)) {
-				ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name: %s@<endpoint-name>\n",
-					request_user);
-				req_data->cause = AST_CAUSE_CHANNEL_UNACCEPTABLE;
-				SCOPE_EXIT_RTN_VALUE(-1, "Empty endpoint name\n");
-			}
-
-			endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",
-				endpoint_name);
-			if (!endpoint) {
-				ast_log(LOG_ERROR, "Unable to create PJSIP channel - endpoint '%s' was not found\n", endpoint_name);
-				req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION;
-				SCOPE_EXIT_RTN_VALUE(-1, "Endpoint not found\n");
-			}
-		}
-	}
-
-	session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user,
-		req_data->topology);
-	ao2_ref(endpoint, -1);
-	if (!session) {
+	if (!req_data->session) {
 		ast_log(LOG_ERROR, "Failed to create outgoing session to endpoint '%s'\n", endpoint_name);
-		req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION;
-		SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create session\n");
+		*req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION;
+		SCOPE_EXIT_RTN_VALUE(-1, "No session\n");
 	}
 
-	req_data->session = session;
-
 	SCOPE_EXIT_RTN_VALUE(0);
 }
 
 /*! \brief Function called by core to create a new outgoing PJSIP session */
-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 struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type,
+	struct ast_stream_topology *req_topology, const struct ast_assigned_ids *assignedids,
+	const struct ast_channel *requestor, const char *data, int *cause)
 {
-	struct request_data req_data;
+	struct request_data req_data = { 0, };
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_stream_topology *, resolved_topology, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);
+	char *tmp_args = ast_strdupa(data);
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(endpoint);
+		AST_APP_ARG(aor);
+	);
 	SCOPE_ENTER(1, "%s Topology: %s\n", data,
-		ast_str_tmp(256, ast_stream_topology_to_str(topology, &STR_TMP)));
+		ast_str_tmp(256, ast_stream_topology_to_str(resolved_topology, &STR_TMP)));
 
-	req_data.topology = topology;
-	req_data.dest = data;
-	/* Default failure value in case ast_sip_push_task_wait_servant() itself fails. */
-	req_data.cause = AST_CAUSE_FAILURE;
+	AST_NONSTANDARD_APP_ARGS(args, tmp_args, '/');
+	endpoint = determine_endpoint(args.endpoint, &req_data.request_user, cause);
+	if (!endpoint) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "No endpoint\n");
+	}
 
+	resolved_topology = resolve_topology(req_topology, endpoint, &endpoint->media.outgoing_offer_codec_prefs, cause);
+	if (!resolved_topology || *cause != AST_CAUSE_NORMAL_CLEARING
+		|| ast_stream_topology_get_active_count(resolved_topology) == 0) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "No resolved_topology\n");
+	}
+
+	req_data.endpoint = endpoint;
+	req_data.topology = resolved_topology;
+	req_data.cause = cause;
+	req_data.aor = args.aor;
+	req_data.indent = ast_trace_get_indent();
 	if (ast_sip_push_task_wait_servant(NULL, request, &req_data)) {
-		*cause = req_data.cause;
+		*cause = *req_data.cause;
 		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't push task\n");
 	}
 
+	if (!req_data.session) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "No session\n");
+	}
 	session = req_data.session;
 
-	if (!(session->channel = chan_pjsip_new(session, AST_STATE_DOWN, NULL, NULL, assignedids, requestor, NULL))) {
-		/* Session needs to be terminated prematurely */
+	session->channel = chan_pjsip_new(session, AST_STATE_DOWN, NULL, NULL, assignedids, requestor, NULL, resolved_topology);
+	if (!session->channel) {
 		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't create channel\n");
 	}
 
@@ -3077,8 +3254,10 @@
 static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
 	RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_stream_topology *, resolved_topology, NULL, ao2_cleanup);
 	struct transport_info_data *transport_data;
 	pjsip_tx_data *packet = NULL;
+	int cause;
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
 
 	if (session->channel) {
@@ -3115,7 +3294,18 @@
 	datastore->data = transport_data;
 	ast_sip_session_add_datastore(session, datastore);
 
-	if (!(session->channel = chan_pjsip_new(session, AST_STATE_RING, session->exten, NULL, NULL, NULL, NULL))) {
+	resolved_topology = resolve_topology(session->pending_media_state->topology, session->endpoint,
+		&session->endpoint->media.incoming_offer_codec_prefs, &cause);
+	if (!resolved_topology || cause != AST_CAUSE_NORMAL_CLEARING
+		|| ast_stream_topology_get_active_count(resolved_topology) == 0) {
+		if (pjsip_inv_end_session(session->inv_session, 488, NULL, &packet) == PJ_SUCCESS
+			&& packet) {
+			ast_sip_session_send_response(session, packet);
+		}
+		SCOPE_EXIT_RTN_VALUE(-1, "No resolved topology\n");
+	}
+
+	if (!(session->channel = chan_pjsip_new(session, AST_STATE_RING, session->exten, NULL, NULL, NULL, NULL, resolved_topology))) {
 		if (pjsip_inv_end_session(session->inv_session, 503, NULL, &packet) == PJ_SUCCESS
 			&& packet) {
 			ast_sip_session_send_response(session, packet);
@@ -3124,8 +3314,10 @@
 		ast_log(LOG_ERROR, "Failed to allocate new PJSIP channel on incoming SIP INVITE\n");
 		SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create channel\n");
 	}
-	/* channel gets created on incoming request, but we wait to call start
-           so other supplements have a chance to run */
+
+	ast_stream_topology_free(session->pending_media_state->topology);
+	session->pending_media_state->topology = ao2_bump(resolved_topology);
+
 	SCOPE_EXIT_RTN_VALUE(0);
 }
 
@@ -3210,6 +3402,11 @@
 
 	ast_debug(3, "Started PBX on new PJSIP channel %s\n", ast_channel_name(session->channel));
 
+	ast_trace(1, "Channel: %p %d  ChanTopo: %p %d  SessionTopo: %p %d\n",
+		session->channel, ao2_ref(session->channel, 0), ast_channel_get_stream_topology(session->channel),
+		ao2_ref(ast_channel_get_stream_topology(session->channel), 0), session->pending_media_state->topology,
+		ao2_ref(session->pending_media_state->topology, 0));
+
 	SCOPE_EXIT_RTN_VALUE((res == AST_PBX_SUCCESS) ? 0 : -1, "RC: %d\n", res);
 }
 
@@ -3220,7 +3417,8 @@
 };
 
 /*! \brief Function called when a response is received on the session */
-static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+static void chan_pjsip_incoming_response(enum ast_sip_session_response_priority priority,
+	struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
 	struct pjsip_status_line status = rdata->msg_info.msg->line.status;
 	struct ast_control_pvt_cause_code *cause_code;
@@ -3273,10 +3471,32 @@
 		}
 		break;
 	case 200:
-		ast_trace(1, "%s Method: %.*s Status: %d  Queueing ANSWER\n", ast_sip_session_get_name(session),
-			(int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr, status.code);
+		if (priority == AST_SIP_SESSION_AFTER_MEDIA) {
+			struct ast_str *error_message = ast_str_alloca(128);
+			struct ast_stream_topology *resolved_topology = ast_stream_topology_create_resolved(
+				session->active_media_state->topology, session->pending_media_state->topology,
+				&session->endpoint->media.incoming_answer_codec_prefs, &error_message);
 
-		ast_queue_control(session->channel, AST_CONTROL_ANSWER);
+			ast_sip_session_media_state_reset(session->pending_media_state);
+
+			if (!resolved_topology || ast_stream_topology_get_active_count(resolved_topology) == 0) {
+				ast_log(LOG_ERROR, "%s %s\n", ast_sip_session_get_name(session), ast_str_buffer(error_message));
+				ao2_cleanup(resolved_topology);
+				ast_queue_hangup_with_cause(session->channel, AST_CAUSE_NO_ROUTE_DESTINATION);
+				SCOPE_EXIT_RTN("%s\n", ast_str_buffer(error_message));
+			}
+
+			ast_trace(1, "%s Method: %.*s Status: %d  Queueing ANSWER with topology %s\n",
+				ast_sip_session_get_name(session), (int)rdata->msg_info.cseq->method.name.slen,
+				rdata->msg_info.cseq->method.name.ptr, status.code,
+				ast_str_tmp(256, ast_stream_topology_to_str(resolved_topology, &STR_TMP)));
+
+			ast_queue_control_data(session->channel, AST_CONTROL_ANSWER,
+				&resolved_topology, sizeof(resolved_topology));
+		} else {
+			ast_trace(1, "%s Method: %.*s Status: %d  Deferring ANSWER until after media\n", ast_sip_session_get_name(session),
+				(int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr, status.code);
+		}
 		break;
 	default:
 		ast_trace(1, "%s Method: %.*s Status: %d  Ignored\n", ast_sip_session_get_name(session),
@@ -3287,6 +3507,55 @@
 	SCOPE_EXIT_RTN();
 }
 
+/*! \brief Function called when a response is received on the session before media */
+static void chan_pjsip_incoming_response_before_media(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	SCOPE_ENTER(1, "%s Method: %.*s Status: %d  Before Media\n", ast_sip_session_get_name(session),
+		(int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr,
+		rdata->msg_info.msg->line.status.code);
+
+	chan_pjsip_incoming_response(AST_SIP_SESSION_BEFORE_MEDIA, session, rdata);
+	SCOPE_EXIT_RTN();
+}
+
+/*! \brief Function called when a response is received on the session after media */
+static void chan_pjsip_incoming_response_after_media(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	struct ast_format_cap *active_caps;
+	SCOPE_ENTER(1, "%s Method: %.*s Status: %d  After Media  Active Topology: %s\n", ast_sip_session_get_name(session),
+		(int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr,
+		rdata->msg_info.msg->line.status.code,
+		ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP))
+	);
+
+	chan_pjsip_incoming_response(AST_SIP_SESSION_AFTER_MEDIA, session, rdata);
+
+	active_caps = ast_stream_topology_get_formats(session->active_media_state->topology);
+	ast_channel_nativeformats_set(session->channel, active_caps);
+	if (!ast_format_cap_empty(active_caps)) {
+		struct ast_format *fmt;
+
+		fmt = ast_format_cap_get_format(active_caps, 0);
+		ast_channel_set_writeformat(session->channel, fmt);
+		ast_channel_set_rawwriteformat(session->channel, fmt);
+		ast_channel_set_readformat(session->channel, fmt);
+		ast_channel_set_rawreadformat(session->channel, fmt);
+		ao2_ref(fmt, -1);
+	}
+
+	ast_channel_set_stream_topology(session->channel, ao2_bump(session->active_media_state->topology));
+
+	ao2_cleanup(active_caps);
+	SCOPE_EXIT_RTN();
+}
+
+static void chan_pjsip_outgoing_response(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
+{
+	SCOPE_ENTER(1, "%s Status: %d\n", ast_sip_session_get_name(session),
+		(int)tdata->msg->line.status.code);
+	SCOPE_EXIT_RTN();
+}
+
 static int chan_pjsip_incoming_ack(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
 	SCOPE_ENTER(1, "%s Method: %.*s Status: %d  After Media\n", ast_sip_session_get_name(session),
@@ -3403,7 +3672,8 @@
 	ast_sip_register_service(&refer_callback_module);
 
 	ast_sip_session_register_supplement(&chan_pjsip_supplement);
-	ast_sip_session_register_supplement(&chan_pjsip_supplement_response);
+	ast_sip_session_register_supplement(&chan_pjsip_supplement_response_before_media);
+	ast_sip_session_register_supplement(&chan_pjsip_supplement_response_after_media);
 
 	if (!(pjsip_uids_onhold = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK,
 			AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, 37, uid_hold_hash_fn,
@@ -3435,7 +3705,8 @@
 	pjsip_uids_onhold = NULL;
 	ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
 	ast_sip_session_unregister_supplement(&pbx_start_supplement);
-	ast_sip_session_unregister_supplement(&chan_pjsip_supplement_response);
+	ast_sip_session_unregister_supplement(&chan_pjsip_supplement_response_after_media);
+	ast_sip_session_unregister_supplement(&chan_pjsip_supplement_response_before_media);
 	ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
 	ast_sip_session_unregister_supplement(&call_pickup_supplement);
 	ast_sip_unregister_service(&refer_callback_module);
@@ -3459,7 +3730,8 @@
 
 	pjsip_channel_cli_unregister();
 
-	ast_sip_session_unregister_supplement(&chan_pjsip_supplement_response);
+	ast_sip_session_unregister_supplement(&chan_pjsip_supplement_response_after_media);
+	ast_sip_session_unregister_supplement(&chan_pjsip_supplement_response_before_media);
 	ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
 	ast_sip_session_unregister_supplement(&pbx_start_supplement);
 	ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 2636f63..894f287 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -802,48 +802,7 @@
                    ; "0" or not enabled)
 ;contact_user= ; On outgoing requests, force the user portion of the Contact
                ; header to this value (default: "")
-;incoming_call_offer_pref= ; Based on this setting, a joint list of
-                           ; preferred codecs between those received in an
-                           ; incoming SDP offer (remote), and those specified
-                           ; in the endpoint's "allow" parameter (local)
-                           ; is created and is passed to the Asterisk core.
-                           ;
-                           ; local - Include all codecs in the local list that
-                           ; are also in the remote list preserving the local
-                           ; order. (default).
-                           ; local_first - Include only the first codec in the
-                           ; local list that is also in the remote list.
-                           ; remote - Include all codecs in the remote list that
-                           ; are also in the local list preserving remote list
-                           ; order.
-                           ; remote_first - Include only the first codec in
-                           ; the remote list that is also in the local list.
-;outgoing_call_offer_pref= ; Based on this setting, a joint list of
-                           ; preferred codecs between those received from the
-                           ; Asterisk core (remote), and those specified in
-                           ; the endpoint's "allow" parameter (local) is
-                           ; created and is used to create the outgoing SDP
-                           ; offer.
-                           ;
-                           ; local - Include all codecs in the local list that
-                           ; are also in the remote list preserving the local
-                           ; order.
-                           ; local_merge - Include all codecs in BOTH lists
-                           ; preserving the local list order.  Codes in the
-                           ; remote list not in the local list will be placed
-                           ; at the end of the joint list.
-                           ; local_first - Include only the first codec in the
-                           ; local list.
-                           ; remote - Include all codecs in the remote list that
-                           ; are also in the local list preserving remote list
-                           ; order. (default)
-                           ; remote_merge - Include all codecs in BOTH lists
-                           ; preserving the remote list order.  Codes in the
-                           ; local list not in the remote list will be placed
-                           ; at the end of the joint list.
-                           ; remote_first - Include only the first codec in
-                           ; the remote list.
-;incoming_offer_codec_prefs=; This is a string that describes how the codecs
+;codec_prefs_incoming_offer=; This is a string that describes how the codecs
                             ; specified on an incoming SDP offer (pending) are
                             ; reconciled with the codecs specified on an endpoint
                             ; (configured) before being sent to the Asterisk core.
@@ -855,7 +814,7 @@
                             ;    | only_nonpreferred>,
                             ; keep: <first | all>,
                             ; transcode: <allow | prevent>
-;outgoing_offer_codec_prefs=; This is a string that describes how the codecs
+;codec_prefs_outgoing_offer=; This is a string that describes how the codecs
                             ; specified in the topology that comes from the
                             ; Asterisk core (pending) are reconciled with the
                             ; codecs specified on an endpoint (configured)
@@ -868,7 +827,7 @@
                             ;    | only_preferred | only_nonpreferred>,
                             ; keep: <first | all>,
                             ; transcode: <allow | prevent>
-;incoming_answer_codec_prefs=; This is a string that describes how the codecs
+;codec_prefs_incoming_answer=; This is a string that describes how the codecs
                              ; specified in an incoming SDP answer (pending)
                              ; are reconciled with the codecs specified on an
                              ; endpoint (configured) when receiving an SDP
@@ -880,7 +839,7 @@
                              ; operation: <intersect | union
                              ;    | only_preferred | only_nonpreferred>,
                              ; keep: <first | all>
-;outgoing_answer_codec_prefs=; This is a string that describes how the codecs
+;codec_prefs_outgoing_answer=; This is a string that describes how the codecs
                              ; that come from the core (pending) are reconciled
                              ; with the codecs specified on an endpoint
                              ; (configured) when sending an SDP answer.
diff --git a/contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py b/contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py
index 241185a..87b770c 100644
--- a/contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py
+++ b/contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py
@@ -8,7 +8,7 @@
 
 # revision identifiers, used by Alembic.
 revision = 'b80485ff4dd0'
-down_revision = '79290b511e4b'
+down_revision = '61797b9fced6'
 
 from alembic import op
 import sqlalchemy as sa
@@ -16,14 +16,14 @@
 max_value_length = 128
 
 def upgrade():
-    op.add_column('ps_endpoints', sa.Column('incoming_offer_codec_prefs', sa.String(max_value_length)))
-    op.add_column('ps_endpoints', sa.Column('outgoing_offer_codec_prefs', sa.String(max_value_length)))
-    op.add_column('ps_endpoints', sa.Column('incoming_answer_codec_prefs', sa.String(max_value_length)))
-    op.add_column('ps_endpoints', sa.Column('outgoing_answer_codec_prefs', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('codec_prefs_incoming_offer', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('codec_prefs_outgoing_offer', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('codec_prefs_incoming_answer', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('codec_prefs_outgoing_answer', sa.String(max_value_length)))
 
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'incoming_offer_codecs_prefs')
-    op.drop_column('ps_endpoints', 'outgoing_offer_codecs_prefs')
-    op.drop_column('ps_endpoints', 'incoming_answer_codecs_prefs')
-    op.drop_column('ps_endpoints', 'outgoing_answer_codecs_prefs')
+    op.drop_column('ps_endpoints', 'codec_prefs_incoming_offer')
+    op.drop_column('ps_endpoints', 'codec_prefs_outgoing_offer')
+    op.drop_column('ps_endpoints', 'codec_prefs_incoming_answer')
+    op.drop_column('ps_endpoints', 'codec_prefs_outgoing_answer')
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index cc90c83..20a159c 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -708,6 +708,19 @@
 	int (* const answer)(struct ast_channel *chan);
 
 	/*!
+	 * \brief Answer the channel with topology
+	 * \since 18
+	 *
+	 * \param chan The channel to answer
+	 * \param topology The topology to use, probably the peer's.
+	 *
+	 * \note The topology may be NULL when the peer doesn't support streams
+	 * or, in the case where transcoding is in effect, when this channel should use
+	 * its existing topology.
+	 */
+	int (* const answer_with_stream_topology)(struct ast_channel *chan, struct ast_stream_topology *topology);
+
+	/*!
 	 * \brief Read a frame (or chain of frames from the same stream), in standard format (see frame.h)
 	 *
 	 * \param chan channel to read frames from
@@ -1081,6 +1094,12 @@
 	 * exist when the end_bridge_callback is called, then it needs to be fixed up properly
 	 */
 	void (*end_bridge_callback_data_fixup)(struct ast_bridge_config *bconfig, struct ast_channel *originator, struct ast_channel *terminator);
+	/*!
+	 * When the peer queues an ANSWER control frame, it can indicate it's resolved topology.
+	 * If the calling channel implements the answer_with_stream_topology callback, the
+	 * peer's topology will be passed to the calling channel.
+	 */
+	struct ast_stream_topology *peer_topology;
 };
 
 struct chanmon;
@@ -1802,6 +1821,31 @@
 int ast_raw_answer(struct ast_channel *chan);
 
 /*!
+ * \brief Answer a channel passing in a stream topology
+ * \since 18
+ *
+ * \param chan channel to answer
+ * \param topology the peer's stream topology
+ *
+ * This function answers a channel and handles all necessary call
+ * setup functions.
+ *
+ * \note The channel passed does not need to be locked, but is locked
+ * by the function when needed.
+ *
+ * \note Unlike ast_answer(), this function will not wait for media
+ * flow to begin. The caller should be careful before sending media
+ * to the channel before incoming media arrives, as the outgoing
+ * media may be lost.
+ *
+ * \note The topology is usually that of the peer channel and may be NULL.
+ *
+ * \retval 0 on success
+ * \retval non-zero on failure
+ */
+int ast_raw_answer_with_stream_topology(struct ast_channel *chan, struct ast_stream_topology *topology);
+
+/*!
  * \brief Answer a channel, with a selectable delay before returning
  *
  * \param chan channel to answer
@@ -5054,4 +5098,18 @@
  */
 void *ast_channel_get_stream_topology_change_source(struct ast_channel *chan);
 
+/*!
+ * \brief Checks if a channel's technology implements a particular callback function
+ * \since 18
+ *
+ * \param chan The channel
+ * \param function The function to look for
+ *
+ * \retval 1 if the channel has a technology set and it implements the function
+ * \retval 0 if the channel doesn't have a technology set or it doesn't implement the function
+ */
+#define ast_channel_has_tech_function(chan, function) \
+	(ast_channel_tech(chan) ? ast_channel_tech(chan)->function != NULL : 0)
+
+
 #endif /* _ASTERISK_CHANNEL_H */
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index eaa9b21..d072638 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -523,42 +523,6 @@
 };
 
 /*!
- * \brief Incoming/Outgoing call offer/answer joint codec preference.
- *
- * The default is INTERSECT ALL LOCAL.
- */
-enum ast_sip_call_codec_pref {
-	/*! Two bits for merge */
-	/*! Intersection of local and remote */
-	AST_SIP_CALL_CODEC_PREF_INTERSECT =	1 << 0,
-	/*! Union of local and remote */
-	AST_SIP_CALL_CODEC_PREF_UNION =		1 << 1,
-
-	/*! Two bits for filter */
-	/*! No filter */
-	AST_SIP_CALL_CODEC_PREF_ALL =	 	1 << 2,
-	/*! Only the first */
-	AST_SIP_CALL_CODEC_PREF_FIRST = 	1 << 3,
-
-	/*! Two bits for preference and sort   */
-	/*! Prefer, and order by local values */
-	AST_SIP_CALL_CODEC_PREF_LOCAL = 	1 << 4,
-	/*! Prefer, and order by remote values */
-	AST_SIP_CALL_CODEC_PREF_REMOTE = 	1 << 5,
-};
-
-/*!
- * \brief Returns true if the preference is set in the parameter
- * \since 18.0.0
- *
- * \param param A ast_flags struct with one or more of enum ast_sip_call_codec_pref set
- * \param codec_pref The last component of one of the enum values
- * \retval 1 if the enum value is set
- * \retval 0 if not
- */
-#define ast_sip_call_codec_pref_test(__param, __codec_pref) (!!(ast_test_flag( &__param, AST_SIP_CALL_CODEC_PREF_ ## __codec_pref )))
-
-/*!
  * \brief Session timers options
  */
 struct ast_sip_timer_options {
@@ -799,10 +763,6 @@
 	unsigned int bundle;
 	/*! Enable webrtc settings and defaults */
 	unsigned int webrtc;
-	/*! Codec preference for an incoming offer */
-	struct ast_flags incoming_call_offer_pref;
-	/*! Codec preference for an outgoing offer */
-	struct ast_flags outgoing_call_offer_pref;
 	/*! Codec negotiation prefs for incoming offers */
 	struct ast_stream_codec_negotiation_prefs incoming_offer_codec_prefs;
 	/*! Codec negotiation prefs for outgoing offers */
@@ -3296,12 +3256,12 @@
  *
  * \param pref A pointer to an ast_flags structure to receive the preference flags
  * \param pref_str The call codec preference setting string
- * \param is_outgoing Is for outgoing calls?
+ * \param allow_merge Allow the "local_merge" and "remote_merge" options?
  *
  * \retval 0 The string was parsed successfully
  * \retval -1 The string option was invalid
  */
-int ast_sip_call_codec_str_to_pref(struct ast_flags *pref, const char *pref_str, int is_outgoing);
+int ast_sip_call_codec_str_to_pref(struct ast_flags *pref, const char *pref_str, int allow_merge);
 
 /*!
  * \brief Transport shutdown monitor callback.
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index 9db68a8..1422036 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -935,4 +935,7 @@
  */
 const char *ast_sip_session_get_name(const struct ast_sip_session *session);
 
+struct pjmedia_sdp_session *ast_sip_session_create_local_sdp(pjsip_inv_session *inv,
+	struct ast_sip_session *session, const pjmedia_sdp_session *offer);
+
 #endif /* _RES_PJSIP_SESSION_H */
diff --git a/include/asterisk/res_pjsip_session_caps.h b/include/asterisk/res_pjsip_session_caps.h
deleted file mode 100644
index 0d7020f..0000000
--- a/include/asterisk/res_pjsip_session_caps.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2020, Sangoma Technologies Corporation
- *
- * Kevin Harwell <kharwell at sangoma.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-#ifndef RES_PJSIP_SESSION_CAPS_H
-#define RES_PJSIP_SESSION_CAPS_H
-
-struct ast_format_cap;
-struct ast_sip_session;
-
-/*!
- * \brief Create joint capabilities
- * \since 18.0.0
- *
- * Creates a list of joint capabilities between the given remote capabilities, and local ones.
- * "local" and "remote" reference the values in ast_sip_call_codec_pref.
- *
- * \param remote The "remote" capabilities
- * \param local The "local" capabilities
- * \param media_type The media type
- * \param codec_prefs One or more of enum ast_sip_call_codec_pref
- *
- * \retval A pointer to the joint capabilities (which may be empty).
- *         NULL will be returned only if no memory was available to allocate the structure.
- * \note Returned object's reference must be released at some point,
- */
-struct ast_format_cap *ast_sip_create_joint_call_cap(const struct ast_format_cap *remote,
-	struct ast_format_cap *local, enum ast_media_type media_type,
-	struct ast_flags codec_pref);
-
-/*!
- * \brief Create a new stream of joint capabilities
- * \since 18.0.0
- *
- * Creates a new stream with capabilities between the given session's local capabilities,
- * and the remote stream's.  Codec selection is based on the session->endpoint's codecs, the
- * session->endpoint's codec call preferences, and the stream passed by the core (for
- * outgoing calls) or created by the incoming SDP (for incoming calls).
- *
- * \param session The session
- * \param remote The remote stream
- *
- * \retval A pointer to a new stream with the joint capabilities (which may be empty),
- *         NULL will be returned only if no memory was available to allocate the structure.
- */
-struct ast_stream *ast_sip_session_create_joint_call_stream(const struct ast_sip_session *session,
-	struct ast_stream *remote);
-
-/*!
- * \brief Create joint capabilities
- * \since 18.0.0
- *
- * Creates a list of joint capabilities between the given session's local capabilities,
- * and the remote capabilities. Codec selection is based on the session->endpoint's codecs, the
- * session->endpoint's codec call preferences, and the "remote" capabilities passed by the core (for
- * outgoing calls) or created by the incoming SDP (for incoming calls).
- *
- * \param session The session
- * \param media_type The media type
- * \param remote Capabilities received in an SDP offer or from the core
- *
- * \retval A pointer to the joint capabilities (which may be empty).
- *         NULL will be returned only if no memory was available to allocate the structure.
- * \note Returned object's reference must be released at some point,
- */
-struct ast_format_cap *ast_sip_session_create_joint_call_cap(const struct ast_sip_session *session,
-	enum ast_media_type media_type, const struct ast_format_cap *remote);
-
-#endif /* RES_PJSIP_SESSION_CAPS_H */
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index 251dea7..f9db06c 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -2307,11 +2307,10 @@
  */
 static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
 {
-	struct ast_channel *chan;
+	struct ast_channel *chan = bridge_channel->chan;
 	struct ast_option_header *aoh;
 	int is_caller;
 
-	chan = bridge_channel->chan;
 	switch (fr->subclass.integer) {
 	case AST_CONTROL_REDIRECTING:
 		is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
diff --git a/main/channel.c b/main/channel.c
index 8dd008d..8b2099e 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -2619,7 +2619,8 @@
 	}
 }
 
-int ast_raw_answer(struct ast_channel *chan)
+
+int ast_raw_answer_with_stream_topology(struct ast_channel *chan, struct ast_stream_topology *topology)
 {
 	int res = 0;
 	SCOPE_TRACE(1, "%s\n", ast_channel_name(chan));
@@ -2650,7 +2651,10 @@
 	case AST_STATE_RINGING:
 	case AST_STATE_RING:
 		ast_channel_lock(chan);
-		if (ast_channel_tech(chan)->answer) {
+		if (ast_channel_tech(chan)->answer_with_stream_topology) {
+			res = ast_channel_tech(chan)->answer_with_stream_topology(chan, topology);
+
+		} else if (ast_channel_tech(chan)->answer) {
 			res = ast_channel_tech(chan)->answer(chan);
 		}
 		ast_setstate(chan, AST_STATE_UP);
@@ -2667,6 +2671,11 @@
 	return res;
 }
 
+int ast_raw_answer(struct ast_channel *chan)
+{
+	return ast_raw_answer_with_stream_topology(chan, NULL);
+}
+
 int __ast_answer(struct ast_channel *chan, unsigned int delay)
 {
 	int res = 0;
diff --git a/main/features.c b/main/features.c
index 51cc3ed..20ed8f4 100644
--- a/main/features.c
+++ b/main/features.c
@@ -76,6 +76,7 @@
 #include "asterisk/stasis_channels.h"
 #include "asterisk/features_config.h"
 #include "asterisk/max_forwards.h"
+#include "asterisk/stream.h"
 
 /*** DOCUMENTATION
 	<application name="Bridge" language="en_US">
@@ -558,12 +559,17 @@
 	set_config_flags(chan, config);
 
 	/* Answer if need be */
+
+	res = 0;
+
 	if (ast_channel_state(chan) != AST_STATE_UP) {
-		if (ast_raw_answer(chan)) {
+		res = ast_raw_answer_with_stream_topology(chan, config->peer_topology);
+		if (res != 0) {
 			return -1;
 		}
 	}
 
+
 #ifdef FOR_DEBUG
 	/* show the two channels and cdrs involved in the bridge for debug & devel purposes */
 	ast_channel_log("Pre-bridge CHAN Channel info", chan);
diff --git a/main/stream.c b/main/stream.c
index a21177d..01b07ca 100644
--- a/main/stream.c
+++ b/main/stream.c
@@ -602,6 +602,16 @@
 			ast_format_cap_append(joint_caps, single, 0);
 			ao2_ref(single, -1);
 		}
+	} else {
+		if (error_message) {
+			ast_str_append(error_message, 0, "No common formats available for media type '%s' ",
+				ast_codec_media_type2str(pending_stream->type));
+			ast_format_cap_append_names(preferred_caps, error_message);
+			ast_str_append(error_message, 0, "<>");
+			ast_format_cap_append_names(nonpreferred_caps, error_message);
+			ast_str_append(error_message, 0, " with prefs: ");
+			ast_stream_codec_prefs_to_str(prefs, error_message);
+		}
 	}
 
 	joint_stream = ast_stream_clone(pending_stream, NULL);
@@ -613,7 +623,7 @@
 	/* ref to joint_caps will be transferred to the stream */
 	ast_stream_set_formats(joint_stream, joint_caps);
 
-	if (TRACE_ATLEAST(1)) {
+	if (TRACE_ATLEAST(3)) {
 		struct ast_str *buf = ast_str_create((AST_FORMAT_CAP_NAMES_LEN * 3) + AST_STREAM_MAX_CODEC_PREFS_LENGTH);
 		if (buf) {
 			ast_str_set(&buf, 0, "Resolved '%s' stream ", ast_codec_media_type2str(pending_stream->type));
@@ -1040,7 +1050,10 @@
 			ast_stream_set_state(joint_stream, AST_STREAM_STATE_REMOVED);
 		} else {
 			joint_stream = ast_stream_create_resolved(pending_stream, configured_stream, prefs, error_message);
-			if (ast_stream_get_format_count(joint_stream) == 0) {
+			if (!joint_stream) {
+				ao2_cleanup(joint_topology);
+				return NULL;
+			} else if (ast_stream_get_format_count(joint_stream) == 0) {
 				ast_stream_set_state(joint_stream, AST_STREAM_STATE_REMOVED);
 			}
 		}
diff --git a/res/Makefile b/res/Makefile
index fc48611..564c7a1 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -66,7 +66,6 @@
 $(call MOD_ADD_C,res_snmp,snmp/agent.c)
 $(call MOD_ADD_C,res_parking,$(wildcard parking/*.c))
 $(call MOD_ADD_C,res_pjsip,$(wildcard res_pjsip/*.c))
-$(call MOD_ADD_C,res_pjsip_session,$(wildcard res_pjsip_session/*.c))
 $(call MOD_ADD_C,res_prometheus,$(wildcard prometheus/*.c))
 $(call MOD_ADD_C,res_ari,ari/cli.c ari/config.c ari/ari_websockets.c)
 $(call MOD_ADD_C,res_ari_model,ari/ari_model_validators.c)
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index bb77e54..061508a 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -102,7 +102,7 @@
 				<configOption name="allow">
 					<synopsis>Media Codec(s) to allow</synopsis>
 				</configOption>
-				<configOption name="incoming_offer_codec_prefs">
+				<configOption name="codec_prefs_incoming_offer">
 					<synopsis>Codec negotiation prefs for incoming offers.</synopsis>
 					<description>
 						<para>
@@ -154,7 +154,7 @@
 						<para>
 						</para>
 						<example>
-							incoming_offer_codec_prefs = prefer: pending, operation: intersect, keep: all, transcode: allow
+							codec_prefs_incoming_offer = prefer: pending, operation: intersect, keep: all, transcode: allow
 						</example>
 						<para>
 							Prefer the codecs coming from the caller.  Use only the ones that are common.
@@ -162,7 +162,7 @@
 						</para>
 					</description>
 				</configOption>
-				<configOption name="outgoing_offer_codec_prefs">
+				<configOption name="codec_prefs_outgoing_offer">
 					<synopsis>Codec negotiation prefs for outgoing offers.</synopsis>
 					<description>
 						<para>
@@ -215,7 +215,7 @@
 						<para>
 						</para>
 						<example>
-						outgoing_offer_codec_prefs = prefer: configured, operation: union, keep: first, transcode: prevent
+						codec_prefs_outgoing_offer = prefer: configured, operation: union, keep: first, transcode: prevent
 						</example>
 						<para>
 						Prefer the codecs coming from the endpoint.  Merge them with the codecs from the core
@@ -223,7 +223,7 @@
 						</para>
 					</description>
 				</configOption>
-				<configOption name="incoming_answer_codec_prefs">
+				<configOption name="codec_prefs_incoming_answer">
 					<synopsis>Codec negotiation prefs for incoming answers.</synopsis>
 					<description>
 						<para>
@@ -272,14 +272,14 @@
 						<para>
 						</para>
 						<example>
-						incoming_answer_codec_prefs = keep: first
+						codec_prefs_incoming_answer = keep: first
 						</example>
 						<para>
 						Use the defaults but keep oinly the first codec.
 						</para>
 					</description>
 				</configOption>
-				<configOption name="outgoing_answer_codec_prefs">
+				<configOption name="codec_prefs_outgoing_answer">
 					<synopsis>Codec negotiation prefs for outgoing answers.</synopsis>
 					<description>
 						<para>
@@ -328,7 +328,7 @@
 						<para>
 						</para>
 						<example>
-						incoming_answer_codec_prefs = keep: first
+						codec_prefs_incoming_answer = keep: first
 						</example>
 						<para>
 						Use the defaults but keep oinly the first codec.
@@ -1180,70 +1180,6 @@
 					<literal>incoming_call_offer_pref</literal>.  Setting both options is unsupported.</para>
 					</warning>
 					</description>
-					<see-also>
-						<ref type="configOption">incoming_call_offer_pref</ref>
-					</see-also>
-				</configOption>
-				<configOption name="incoming_call_offer_pref" default="local">
-					<synopsis>Preferences for selecting codecs for an incoming call.</synopsis>
-					<description>
-						<para>Based on this setting, a joint list of preferred codecs between those
-						received in an incoming SDP offer (remote), and those specified in the
-						endpoint's "allow" parameter (local) es created and is passed to the Asterisk
-						core. </para>
-						<note><para>This list will consist of only those codecs found in both lists.</para></note>
-						<enumlist>
-							<enum name="local"><para>
-								Include all codecs in the local list that are also in the remote list
-								preserving the local order.  (default).
-							</para></enum>
-							<enum name="local_first"><para>
-								Include only the first codec in the local list that is also in the remote list.
-							</para></enum>
-							<enum name="remote"><para>
-								Include all codecs in the remote list that are also in the local list
-								preserving the remote order.
-							</para></enum>
-							<enum name="remote_first"><para>
-								Include only the first codec in the remote list that is also in the local list.
-							</para></enum>
-						</enumlist>
-					</description>
-				</configOption>
-				<configOption name="outgoing_call_offer_pref" default="local">
-					<synopsis>Preferences for selecting codecs for an outgoing call.</synopsis>
-					<description>
-						<para>Based on this setting, a joint list of preferred codecs between
-						those received from the Asterisk core (remote), and those specified in
-						the endpoint's "allow" parameter (local) is created and is used to create
-						the outgoing SDP offer.</para>
-						<enumlist>
-							<enum name="local"><para>
-								Include all codecs in the local list that are also in the remote list
-								preserving the local order.
-							</para></enum>
-							<enum name="local_merge"><para>
-								Include all codecs in BOTH lists preserving the local order.
-								Remote codecs not in the local list will be placed at the end
-								of the joint list.
-							</para></enum>
-							<enum name="local_first"><para>
-								Include only the first codec in the local list.
-							</para></enum>
-							<enum name="remote"><para>
-								Include all codecs in the remote list that are also in the local list
-								preserving the remote order. (default)
-							</para></enum>
-							<enum name="remote_merge"><para>
-								Include all codecs in BOTH lists preserving the remote order.
-								Local codecs not in the remote list will be placed at the end
-								of the joint list.
-							</para></enum>
-							<enum name="remote_first"><para>
-								Include only the first codec in the remote list.
-							</para></enum>
-						</enumlist>
-					</description>
 				</configOption>
 				<configOption name="rtp_keepalive">
 					<synopsis>Number of seconds between RTP comfort noise keepalive packets.</synopsis>
@@ -5378,52 +5314,6 @@
 	return result;
 }
 
-const char *ast_sip_call_codec_pref_to_str(struct ast_flags pref)
-{
-	const char *value;
-
-	if (ast_sip_call_codec_pref_test(pref, LOCAL) &&  ast_sip_call_codec_pref_test(pref, INTERSECT) && ast_sip_call_codec_pref_test(pref, ALL)) {
-		value = "local";
-	} else if (ast_sip_call_codec_pref_test(pref, LOCAL) &&  ast_sip_call_codec_pref_test(pref, UNION) && ast_sip_call_codec_pref_test(pref, ALL)) {
-		value = "local_merge";
-	} else if (ast_sip_call_codec_pref_test(pref, LOCAL) &&  ast_sip_call_codec_pref_test(pref, INTERSECT) && ast_sip_call_codec_pref_test(pref, FIRST)) {
-		value = "local_first";
-	} else if (ast_sip_call_codec_pref_test(pref, REMOTE) &&  ast_sip_call_codec_pref_test(pref, INTERSECT) && ast_sip_call_codec_pref_test(pref, ALL)) {
-		value = "remote";
-	} else if (ast_sip_call_codec_pref_test(pref, REMOTE) &&  ast_sip_call_codec_pref_test(pref, UNION) && ast_sip_call_codec_pref_test(pref, ALL)) {
-		value = "remote_merge";
-	} else if (ast_sip_call_codec_pref_test(pref, REMOTE) &&  ast_sip_call_codec_pref_test(pref, UNION) && ast_sip_call_codec_pref_test(pref, FIRST)) {
-		value = "remote_first";
-	} else {
-		value = "unknown";
-	}
-
-	return value;
-}
-
-int ast_sip_call_codec_str_to_pref(struct ast_flags *pref, const char *pref_str, int is_outgoing)
-{
-	pref->flags = 0;
-
-	if (strcmp(pref_str, "local") == 0) {
-		ast_set_flag(pref, AST_SIP_CALL_CODEC_PREF_LOCAL | AST_SIP_CALL_CODEC_PREF_INTERSECT | AST_SIP_CALL_CODEC_PREF_ALL);
-	} else if (is_outgoing && strcmp(pref_str, "local_merge") == 0) {
-		ast_set_flag(pref, AST_SIP_CALL_CODEC_PREF_LOCAL | AST_SIP_CALL_CODEC_PREF_UNION | AST_SIP_CALL_CODEC_PREF_ALL);
-	} else if (strcmp(pref_str, "local_first") == 0) {
-		ast_set_flag(pref, AST_SIP_CALL_CODEC_PREF_LOCAL | AST_SIP_CALL_CODEC_PREF_INTERSECT | AST_SIP_CALL_CODEC_PREF_FIRST);
-	} else if (strcmp(pref_str, "remote") == 0) {
-		ast_set_flag(pref, AST_SIP_CALL_CODEC_PREF_REMOTE | AST_SIP_CALL_CODEC_PREF_INTERSECT | AST_SIP_CALL_CODEC_PREF_ALL);
-	} else if (is_outgoing && strcmp(pref_str, "remote_merge") == 0) {
-		ast_set_flag(pref, AST_SIP_CALL_CODEC_PREF_REMOTE | AST_SIP_CALL_CODEC_PREF_UNION | AST_SIP_CALL_CODEC_PREF_ALL);
-	} else if (strcmp(pref_str, "remote_first") == 0) {
-		ast_set_flag(pref, AST_SIP_CALL_CODEC_PREF_REMOTE | AST_SIP_CALL_CODEC_PREF_UNION | AST_SIP_CALL_CODEC_PREF_FIRST);
-	} else {
-		return -1;
-	}
-
-	return 0;
-}
-
 /*!
  * \brief Set name and number information on an identity header.
  *
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 89ae3e1..70dc77d 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1121,51 +1121,6 @@
 	return 0;
 }
 
-static int call_offer_pref_handler(const struct aco_option *opt,
-	struct ast_variable *var, void *obj)
-{
-	struct ast_sip_endpoint *endpoint = obj;
-	struct ast_flags pref = { 0, };
-	int outgoing = strcmp(var->name, "outgoing_call_offer_pref") == 0;
-
-	int res = ast_sip_call_codec_str_to_pref(&pref, var->value, outgoing);
-	if (res != 0) {
-		return -1;
-	}
-
-	if (outgoing) {
-		endpoint->media.outgoing_call_offer_pref = pref;
-	} else {
-		endpoint->media.incoming_call_offer_pref = pref;
-	}
-
-	return 0;
-}
-
-static int incoming_call_offer_pref_to_str(const void *obj, const intptr_t *args, char **buf)
-{
-	const struct ast_sip_endpoint *endpoint = obj;
-
-	*buf = ast_strdup(ast_sip_call_codec_pref_to_str(endpoint->media.incoming_call_offer_pref));
-	if (!(*buf)) {
-		return -1;
-	}
-
-	return 0;
-}
-
-static int outgoing_call_offer_pref_to_str(const void *obj, const intptr_t *args, char **buf)
-{
-	const struct ast_sip_endpoint *endpoint = obj;
-
-	*buf = ast_strdup(ast_sip_call_codec_pref_to_str(endpoint->media.outgoing_call_offer_pref));
-	if (!(*buf)) {
-		return -1;
-	}
-
-	return 0;
-}
-
 static int codec_prefs_handler(const struct aco_option *opt,
 	struct ast_variable *var, void *obj)
 {
@@ -1185,7 +1140,7 @@
 	}
 	ast_free(error_message);
 
-	if (strcmp(var->name, "incoming_offer_codec_prefs") == 0) {
+	if (strcmp(var->name, "codec_prefs_incoming_offer") == 0) {
 		if (prefs.operation == CODEC_NEGOTIATION_OPERATION_UNION) {
 			ast_log(LOG_ERROR, "Endpoint '%s': Codec preference '%s' has invalid value '%s' for option: '%s'",
 				ast_sorcery_object_get_id(endpoint),
@@ -1194,19 +1149,15 @@
 				var->name);
 			return -1;
 		}
-		endpoint->media.incoming_offer_codec_prefs = prefs;
 		default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
 		default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
-	} else if (strcmp(var->name, "outgoing_offer_codec_prefs") == 0) {
-		endpoint->media.outgoing_offer_codec_prefs = prefs;
+	} else if (strcmp(var->name, "codec_prefs_outgoing_offer") == 0) {
 		default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
 		default_operation = CODEC_NEGOTIATION_OPERATION_UNION;
-	} else if (strcmp(var->name, "incoming_answer_codec_prefs") == 0) {
-		endpoint->media.incoming_answer_codec_prefs = prefs;
+	} else if (strcmp(var->name, "codec_prefs_incoming_answer") == 0) {
 		default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
 		default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
-	} else if (strcmp(var->name, "outgoing_answer_codec_prefs") == 0) {
-		endpoint->media.outgoing_answer_codec_prefs = prefs;
+	} else if (strcmp(var->name, "codec_prefs_outgoing_answer") == 0) {
 		default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
 		default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
 	}
@@ -1227,6 +1178,16 @@
 		prefs.transcode = CODEC_NEGOTIATION_TRANSCODE_ALLOW;
 	}
 
+	if (strcmp(var->name, "codec_prefs_incoming_offer") == 0) {
+		endpoint->media.incoming_offer_codec_prefs = prefs;
+	} else if (strcmp(var->name, "codec_prefs_outgoing_offer") == 0) {
+		endpoint->media.outgoing_offer_codec_prefs = prefs;
+	} else if (strcmp(var->name, "codec_prefs_incoming_answer") == 0) {
+		endpoint->media.incoming_answer_codec_prefs = prefs;
+	} else if (strcmp(var->name, "codec_prefs_outgoing_answer") == 0) {
+		endpoint->media.outgoing_answer_codec_prefs = prefs;
+	}
+
 	return 0;
 }
 
@@ -1451,14 +1412,15 @@
 		return -1;
 	}
 
-	if (endpoint->preferred_codec_only) {
-		if (endpoint->media.incoming_call_offer_pref.flags != (AST_SIP_CALL_CODEC_PREF_LOCAL | AST_SIP_CALL_CODEC_PREF_INTERSECT | AST_SIP_CALL_CODEC_PREF_ALL)) {
-			ast_log(LOG_ERROR, "Setting both preferred_codec_only and incoming_call_offer_pref is not supported on endpoint '%s'\n",
-				ast_sorcery_object_get_id(endpoint));
-			return -1;
-		}
-		ast_clear_flag(&endpoint->media.incoming_call_offer_pref, AST_SIP_CALL_CODEC_PREF_ALL);
-		ast_set_flag(&endpoint->media.incoming_call_offer_pref, AST_SIP_CALL_CODEC_PREF_FIRST);
+	if (endpoint->preferred_codec_only
+		&& endpoint->media.incoming_offer_codec_prefs.keep != CODEC_NEGOTIATION_KEEP_FIRST) {
+		ast_log(LOG_WARNING, "The preferred_codec_only parameter is deprecated. "
+			"Overriding value of incoming_offer_codec_prefs.keep from '%s' to '%s' for endpoint '%s'\n",
+			ast_stream_codec_keep_to_str(endpoint->media.incoming_offer_codec_prefs.keep),
+			ast_stream_codec_keep_to_str(CODEC_NEGOTIATION_KEEP_FIRST),
+			ast_sorcery_object_get_id(endpoint));
+
+		endpoint->media.incoming_offer_codec_prefs.keep = CODEC_NEGOTIATION_KEEP_FIRST;
 	}
 
 	endpoint->media.topology = ast_stream_topology_create_from_format_cap(endpoint->media.codecs);
@@ -2124,21 +2086,17 @@
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "accept_multiple_sdp_answers", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.accept_multiple_sdp_answers));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "suppress_q850_reason_headers", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, suppress_q850_reason_headers));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "ignore_183_without_sdp", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, ignore_183_without_sdp));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "incoming_call_offer_pref", "local",
-		call_offer_pref_handler, incoming_call_offer_pref_to_str, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_call_offer_pref", "remote",
-		call_offer_pref_handler, outgoing_call_offer_pref_to_str, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "incoming_offer_codec_prefs",
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_incoming_offer",
 		"prefer: pending, operation: intersect, keep: all, transcode: allow",
 		codec_prefs_handler, incoming_offer_codec_prefs_to_str, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_offer_codec_prefs",
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_outgoing_offer",
 		"prefer: pending, operation: union, keep: all, transcode: allow",
 		codec_prefs_handler, outgoing_offer_codec_prefs_to_str, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "incoming_answer_codec_prefs",
-		"prefer: pending, operation: intersect, keep: all",
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_incoming_answer",
+		"prefer: pending, operation: intersect, keep: all, transcode: allow",
 		codec_prefs_handler, incoming_answer_codec_prefs_to_str, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_answer_codec_prefs",
-		"prefer: pending, operation: intersect, keep: all",
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_outgoing_answer",
+		"prefer: pending, operation: intersect, keep: all, transcode: allow",
 		codec_prefs_handler, outgoing_answer_codec_prefs_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "stir_shaken", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, stir_shaken));
 
diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c
index 06ea0b6..6a496dc 100644
--- a/res/res_pjsip_refer.c
+++ b/res/res_pjsip_refer.c
@@ -983,7 +983,7 @@
 	ast_channel_lock(session->channel);
 	ast_setstate(session->channel, AST_STATE_RING);
 	ast_channel_unlock(session->channel);
-	ast_raw_answer(session->channel);
+	ast_raw_answer_with_stream_topology(session->channel, ast_channel_get_stream_topology(other_session->channel));
 
 	ast_debug(3, "INVITE with Replaces being attempted.  '%s' --> '%s'\n",
 		ast_channel_name(session->channel), ast_channel_name(invite.channel));
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index eacae22..d323e44 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -56,7 +56,6 @@
 
 #include "asterisk/res_pjsip.h"
 #include "asterisk/res_pjsip_session.h"
-#include "asterisk/res_pjsip_session_caps.h"
 
 /*! \brief Scheduler for RTCP purposes */
 static struct ast_sched_context *sched;
@@ -311,71 +310,144 @@
 	return 0;
 }
 
-static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp_media *stream, struct ast_rtp_codecs *codecs,
+static struct ast_format_cap *get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp_media *stream, struct ast_rtp_codecs *codecs,
 	struct ast_sip_session_media *session_media)
 {
 	pjmedia_sdp_attr *attr;
 	pjmedia_sdp_rtpmap *rtpmap;
 	pjmedia_sdp_fmtp fmtp;
 	struct ast_format *format;
-	int i, num = 0, tel_event = 0;
+	int i;
+	int tel_event = 0;
+	int res;
+	int framing;
 	char name[256];
 	char media[20];
 	char fmt_param[256];
 	enum ast_rtp_options options = session->endpoint->media.g726_non_standard ?
 		AST_RTP_OPT_G726_NONSTANDARD : 0;
+	struct ast_format_cap *caps;
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
 
-	ast_rtp_codecs_payloads_initialize(codecs);
+	res = ast_rtp_codecs_payloads_initialize(codecs);
+	if (res != 0) {
+		ast_log(LOG_ERROR, "%s: Failed to initialize codecs\n", ast_sip_session_get_name(session));
+		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't initialize codecs\n");
+	}
+
+	caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+	if (!caps) {
+		ast_log(LOG_ERROR, "%s: Failed to allocate %s capabilities\n", ast_sip_session_get_name(session),
+				ast_codec_media_type2str(session_media->type));
+		ast_rtp_codecs_payloads_destroy(codecs);
+		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't allocate caps\n");
+	}
 
 	/* Iterate through provided formats */
 	for (i = 0; i < stream->desc.fmt_count; ++i) {
+		const pj_str_t *payload_str = &stream->desc.fmt[i];
+		int payload = pj_strtoul(payload_str);
+
 		/* The payload is kept as a string for things like t38 but for video it is always numerical */
-		ast_rtp_codecs_payloads_set_m_type(codecs, NULL, pj_strtoul(&stream->desc.fmt[i]));
+		ast_rtp_codecs_payloads_set_m_type(codecs, NULL, payload);
+
 		/* Look for the optional rtpmap attribute */
-		if (!(attr = pjmedia_sdp_media_find_attr2(stream, "rtpmap", &stream->desc.fmt[i]))) {
-			continue;
-		}
-
-		/* Interpret the attribute as an rtpmap */
-		if ((pjmedia_sdp_attr_to_rtpmap(session->inv_session->pool_prov, attr, &rtpmap)) != PJ_SUCCESS) {
-			continue;
-		}
-
-		ast_copy_pj_str(name, &rtpmap->enc_name, sizeof(name));
-		if (strcmp(name, "telephone-event") == 0) {
-			tel_event++;
-		}
-
-		ast_copy_pj_str(media, (pj_str_t*)&stream->desc.media, sizeof(media));
-		ast_rtp_codecs_payloads_set_rtpmap_type_rate(codecs, NULL,
-			pj_strtoul(&stream->desc.fmt[i]), media, name, options, rtpmap->clock_rate);
-		/* Look for an optional associated fmtp attribute */
-		if (!(attr = pjmedia_sdp_media_find_attr2(stream, "fmtp", &rtpmap->pt))) {
-			continue;
-		}
-
-		if ((pjmedia_sdp_attr_get_fmtp(attr, &fmtp)) == PJ_SUCCESS) {
-			ast_copy_pj_str(fmt_param, &fmtp.fmt, sizeof(fmt_param));
-			if (sscanf(fmt_param, "%30d", &num) != 1) {
+		name[0] = '\0';
+		attr = pjmedia_sdp_media_find_attr2(stream, "rtpmap", payload_str);
+		if (attr) {
+			/* Interpret the attribute as an rtpmap */
+			res = pjmedia_sdp_attr_to_rtpmap(session->inv_session->pool_prov, attr, &rtpmap);
+			if (res != PJ_SUCCESS) {
+				/* We only get format here for the warning message */
+				format = ast_rtp_codecs_get_payload_format(codecs, payload);
+				ast_log(LOG_WARNING, "%s: Format %s (%d) skipped due to unparseable rtpmap attribute\n",
+					ast_sip_session_get_name(session), format ? ast_format_get_name(format) : "?", payload);
+				ast_rtp_codecs_payloads_unset(codecs, NULL, payload);
+				ao2_cleanup(format);
 				continue;
 			}
 
-			if ((format = ast_rtp_codecs_get_payload_format(codecs, num))) {
-				struct ast_format *format_parsed;
+			ast_copy_pj_str(name, &rtpmap->enc_name, sizeof(name));
+			if (strcmp(name, "telephone-event") == 0) {
+				tel_event++;
+			}
 
-				ast_copy_pj_str(fmt_param, &fmtp.fmt_param, sizeof(fmt_param));
+			ast_copy_pj_str(media, (pj_str_t*)&stream->desc.media, sizeof(media));
+			ast_rtp_codecs_payloads_set_rtpmap_type_rate(codecs, NULL, payload,
+				media, name, options, rtpmap->clock_rate);
 
-				format_parsed = ast_format_parse_sdp_fmtp(format, fmt_param);
-				if (format_parsed) {
-					ast_rtp_codecs_payload_replace_format(codecs, num, format_parsed);
-					ao2_ref(format_parsed, -1);
-				}
+		}
 
+		/*
+		 * We have to wait until an attempt is made to find an rtpmap attribute
+		 * to get the format.  For dynamic payloads, it's the call to
+		 * set_rtpmap_type_rate that creates its format.
+		 */
+		format = ast_rtp_codecs_get_payload_format(codecs, payload);
+
+		ast_debug(1, "%s: Media type: %s  Index: %d  Format code: %d  Format name: %s\n",
+			ast_sip_session_get_name(session), media, i, payload, format ? ast_format_get_name(format) : "?");
+
+		/*
+		 * Dynamic payloads MUST have an rtpmap so at this point,
+		 * all static and dynamic payloads should have a format except
+		 * those like telephone event for which we don't add a format to format caps.
+		 */
+		if (!format) {
+			continue;
+		}
+
+		/*
+		 * Look for an optional fmtp attribute that mnatches the payload code
+		 * on the "m" attribute.
+		 * FYI... Technically, for a static payload, you CAN have an fmtp attribute
+		 * without a corresponding rtpmap attribute.
+		 */
+		attr = pjmedia_sdp_media_find_attr2(stream, "fmtp", payload_str);
+		if (attr) {
+			struct ast_format *format_parsed;
+
+			res = pjmedia_sdp_attr_get_fmtp(attr, &fmtp);
+			/* If we can't parse the fmtp attribute, we're just going to skip the format */
+			if (res != PJ_SUCCESS) {
+				ast_log(LOG_WARNING, "%s: Format %s (%d) skipped due to unparseable fmtp attribute\n",
+					ast_sip_session_get_name(session), ast_format_get_name(format), payload);
 				ao2_ref(format, -1);
+				ast_rtp_codecs_payloads_unset(codecs, NULL, payload);
+				continue;
+			}
+
+			ast_copy_pj_str(fmt_param, &fmtp.fmt_param, sizeof(fmt_param));
+			format_parsed = ast_format_parse_sdp_fmtp(format, fmt_param);
+			/*
+			 * format_parsed can be one of 3 things...
+			 * * ao2_bump(format) if the format doesn't have an attribute parser.
+			 *   In this case we have no need to replace the format in the codecs list.
+			 * * NULL if it has a parser but it wasn't able to clone format.
+			 *   Again, no need to replace the format in the codecs list.
+			 * * A cloned format with the attributes filled out.
+			 *   Replace the format.
+			 *
+			 * We need to keep careful track of reference counting whatever happens.
+			 */
+
+			if (format_parsed && format_parsed != format) {
+				ast_rtp_codecs_payload_replace_format(codecs, payload, format_parsed);
+				ao2_ref(format, -1);
+				format = format_parsed;
 			}
 		}
+
+		res = ast_format_cap_append(caps, format, 0);
+		if (res != 0) {
+			ast_log(LOG_WARNING, "%s: Format %s (%d) skipped due to inability to append it to the format caps\n",
+				ast_sip_session_get_name(session), ast_format_get_name(format), payload);
+			ast_rtp_codecs_payloads_unset(codecs, NULL, payload);
+		}
+		ao2_ref(format, -1);
+
 	}
+
 	if (!tel_event && (session->dtmf == AST_SIP_DTMF_AUTO)) {
 		ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);
 	}
@@ -388,16 +460,16 @@
 		}
 	}
 
-
 	/* Get the packetization, if it exists */
 	if ((attr = pjmedia_sdp_media_find_attr2(stream, "ptime", NULL))) {
-		unsigned long framing = pj_strtoul(pj_strltrim(&attr->value));
+		framing = pj_strtoul(pj_strltrim(&attr->value));
 		if (framing && session->endpoint->media.rtp.use_ptime) {
 			ast_rtp_codecs_set_framing(codecs, framing);
+			ast_format_cap_set_framing(caps, framing);
 		}
 	}
 
-	SCOPE_EXIT_RTN();
+	SCOPE_EXIT_RTN_VALUE(caps);
 }
 
 static int apply_cap_to_bundled(struct ast_sip_session_media *session_media,
@@ -435,34 +507,12 @@
 	struct ast_sip_session *session, struct ast_sip_session_media *session_media,
 	const struct pjmedia_sdp_media *stream)
 {
-	struct ast_format_cap *incoming_call_offer_cap;
 	struct ast_format_cap *remote;
 	struct ast_rtp_codecs codecs = AST_RTP_CODECS_NULL_INIT;
-	int fmts = 0;
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
 
-
-	remote = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-	if (!remote) {
-		ast_log(LOG_ERROR, "Failed to allocate %s incoming remote capabilities\n",
-				ast_codec_media_type2str(session_media->type));
-		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't allocate caps\n");
-	}
-
 	/* Get the peer's capabilities*/
-	get_codecs(session, stream, &codecs, session_media);
-	ast_rtp_codecs_payload_formats(&codecs, remote, &fmts);
-
-	incoming_call_offer_cap = ast_sip_session_create_joint_call_cap(
-		session, session_media->type, remote);
-
-	ao2_ref(remote, -1);
-
-	if (!incoming_call_offer_cap || ast_format_cap_empty(incoming_call_offer_cap)) {
-		ao2_cleanup(incoming_call_offer_cap);
-		ast_rtp_codecs_payloads_destroy(&codecs);
-		SCOPE_EXIT_RTN_VALUE(NULL, "No incoming call offer caps\n");
-	}
+	remote = get_codecs(session, stream, &codecs, session_media);
 
 	/*
 	 * Setup rx payload type mapping to prefer the mapping
@@ -475,7 +525,7 @@
 
 	ast_rtp_codecs_payloads_destroy(&codecs);
 
-	SCOPE_EXIT_RTN_VALUE(incoming_call_offer_cap);
+	SCOPE_EXIT_RTN_VALUE(remote);
 }
 
 static int set_caps(struct ast_sip_session *session,
@@ -484,52 +534,20 @@
 	const struct pjmedia_sdp_media *stream,
 	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);
-	RAII_VAR(struct ast_format_cap *, joint, NULL, ao2_cleanup);
-	enum ast_media_type media_type = session_media->type;
 	struct ast_rtp_codecs codecs = AST_RTP_CODECS_NULL_INIT;
-	int fmts = 0;
-	int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
-		ast_format_cap_count(session->direct_media_cap);
 	int dsp_features = 0;
 	SCOPE_ENTER(1, "%s %s\n", ast_sip_session_get_name(session), is_offer ? "OFFER" : "ANSWER");
 
-	if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
-	    !(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
-	    !(joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
-			ast_codec_media_type2str(session_media->type));
-		SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create %s capabilities\n",
-			ast_codec_media_type2str(session_media->type));
-	}
-
-	/* get the endpoint capabilities */
-	if (direct_media_enabled) {
-		ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);
-	} else {
-		ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type);
-	}
-
 	/* get the capabilities on the peer */
-	get_codecs(session, stream, &codecs,  session_media);
-	ast_rtp_codecs_payload_formats(&codecs, peer, &fmts);
+	peer = get_codecs(session, stream, &codecs,  session_media);
 
-	/* get the joint capabilities between peer and endpoint */
-	ast_format_cap_get_compatible(caps, peer, joint);
-	if (!ast_format_cap_count(joint)) {
-		struct ast_str *usbuf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
-		struct ast_str *thembuf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
-
+	if (!ast_format_cap_count(peer)) {
 		ast_rtp_codecs_payloads_destroy(&codecs);
-		ast_log(LOG_NOTICE, "No joint capabilities for '%s' media stream between our configuration(%s) and incoming SDP(%s)\n",
-			ast_codec_media_type2str(session_media->type),
-			ast_format_cap_get_names(caps, &usbuf),
-			ast_format_cap_get_names(peer, &thembuf));
-		SCOPE_EXIT_RTN_VALUE(-1, "No joint capabilities for '%s' media stream between our configuration(%s) and incoming SDP(%s)\n",
-			ast_codec_media_type2str(session_media->type),
-			ast_format_cap_get_names(caps, &usbuf),
-			ast_format_cap_get_names(peer, &thembuf));
+		ast_log(LOG_NOTICE, "No joint capabilities for media stream between our configuration(%s) and incoming SDP(%s)\n",
+			ast_stream_topology_to_str(session->pending_media_state->topology, NULL),
+			ast_format_cap_get_names(peer, NULL));
+		SCOPE_EXIT_RTN_VALUE(-1, "Peer responded with no formats\n");
 	}
 
 	if (is_offer) {
@@ -539,49 +557,14 @@
 		 */
 		ast_rtp_codecs_payloads_xover(&codecs, &codecs, NULL);
 	}
+
 	ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),
 		session_media->rtp);
 
-	apply_cap_to_bundled(session_media, session_media_transport, asterisk_stream, joint);
+	apply_cap_to_bundled(session_media, session_media_transport, asterisk_stream, peer);
 
 	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),
-			AST_MEDIA_TYPE_UNKNOWN);
-		ast_format_cap_remove_by_type(caps, media_type);
-
-		if (session->endpoint->preferred_codec_only){
-			struct ast_format *preferred_fmt = ast_format_cap_get_format(joint, 0);
-			ast_format_cap_append(caps, preferred_fmt, 0);
-			ao2_ref(preferred_fmt, -1);
-		} else if (!session->endpoint->asymmetric_rtp_codec) {
-			struct ast_format *best;
-			/*
-			 * If we don't allow the sending codec to be changed on our side
-			 * then get the best codec from the joint capabilities of the media
-			 * type and use only that. This ensures the core won't start sending
-			 * out a format that we aren't currently sending.
-			 */
-
-			best = ast_format_cap_get_best_by_type(joint, media_type);
-			if (best) {
-				ast_format_cap_append(caps, best, ast_format_cap_get_framing(joint));
-				ao2_ref(best, -1);
-			}
-		} else {
-			ast_format_cap_append_from_cap(caps, joint, media_type);
-		}
-
-		/*
-		 * Apply the new formats to the channel, potentially changing
-		 * raw read/write formats and translation path while doing so.
-		 */
-		ast_channel_nativeformats_set(session->channel, caps);
-		if (media_type == AST_MEDIA_TYPE_AUDIO) {
-			ast_set_read_format(session->channel, ast_channel_readformat(session->channel));
-			ast_set_write_format(session->channel, ast_channel_writeformat(session->channel));
-		}
 
 		if ( ((session->dtmf == AST_SIP_DTMF_AUTO) || (session->dtmf == AST_SIP_DTMF_AUTO_INFO) )
 		    && (ast_rtp_instance_dtmf_mode_get(session_media->rtp) == AST_RTP_DTMF_MODE_RFC2833)
@@ -604,7 +587,8 @@
 	}
 
 	ast_rtp_codecs_payloads_destroy(&codecs);
-	SCOPE_EXIT_RTN_VALUE(0);
+	SCOPE_EXIT_RTN_VALUE(0, "Formats: %s\n",
+		ast_str_tmp(128, ast_format_cap_get_names(peer, &STR_TMP)));
 }
 
 static pjmedia_sdp_attr* generate_rtpmap_attr(struct ast_sip_session *session, pjmedia_sdp_media *media, pj_pool_t *pool,
@@ -1428,13 +1412,6 @@
 	int res;
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
 
-	/* If no type formats have been configured reject this stream */
-	if (!ast_format_cap_has_type(session->endpoint->media.codecs, media_type)) {
-		ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n",
-			ast_codec_media_type2str(session_media->type));
-		SCOPE_EXIT_RTN_VALUE(0, "Endpoint has no codecs\n");
-	}
-
 	/* Ensure incoming transport is compatible with the endpoint's configuration */
 	if (!session->endpoint->media.rtp.use_received_transport) {
 		encryption = check_endpoint_media_transport(session->endpoint, stream);
@@ -1651,8 +1628,10 @@
 }
 
 /*! \brief Function which creates an outgoing stream */
-static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
-				      struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, struct ast_stream *stream)
+static int create_outgoing_sdp_stream(struct ast_sip_session *session,
+	struct ast_sip_session_media *session_media,
+	struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote,
+	struct ast_stream *stream)
 {
 	pj_pool_t *pool = session->inv_session->pool_prov;
 	static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
@@ -1673,7 +1652,7 @@
 	int noncodec = (session->dtmf == AST_SIP_DTMF_RFC_4733 || session->dtmf == AST_SIP_DTMF_AUTO || session->dtmf == AST_SIP_DTMF_AUTO_INFO) ? AST_RTP_DTMF : 0;
 	int min_packet_size = 0, max_packet_size = 0;
 	int rtp_code;
-	RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
+	struct ast_format_cap *caps = NULL;
 	enum ast_media_type media_type = session_media->type;
 	struct ast_sip_session_media *session_media_transport;
 	pj_sockaddr ip;
@@ -1713,6 +1692,8 @@
 
 		sdp->media[sdp->media_count++] = media;
 		ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+		ast_trace(2, "%s stream removed.  Caps: %s\n", ast_codec_media_type2str(ast_stream_get_type(stream)),
+			ast_stream_to_str(stream, NULL));
 
 		SCOPE_EXIT_RTN_VALUE(1, "Stream removed or no formats\n");
 	}
@@ -1818,19 +1799,16 @@
 		enable_rtcp(session, session_media, NULL);
 	}
 
-	if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
-			ast_codec_media_type2str(session_media->type));
-		SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create caps\n");
-	}
-
 	if (direct_media_enabled) {
-		ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);
+//		ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);
 	} else {
-		ast_format_cap_append_from_cap(caps, ast_stream_get_formats(stream), media_type);
+		caps = (struct ast_format_cap *)ast_stream_get_formats(stream);
+		if (!caps) {
+			SCOPE_EXIT_RTN_VALUE(-1, "Couldn't get stream caps\n");
+		}
 	}
 
-	for (index = 0; index < ast_format_cap_count(caps); ++index) {
+	for (index = 0; caps && index < ast_format_cap_count(caps); ++index) {
 		struct ast_format *format = ast_format_cap_get_format(caps, index);
 
 		if (ast_format_get_type(format) != media_type) {
@@ -1905,7 +1883,6 @@
 		}
 	}
 
-
 	/* If no formats were actually added to the media stream don't add it to the SDP */
 	if (!media->desc.fmt_count) {
 		SCOPE_EXIT_RTN_VALUE(1, "No formats added to stream\n");
@@ -2022,8 +1999,11 @@
 	char host[NI_MAXHOST];
 	int res;
 	struct ast_sip_session_media *session_media_transport;
-	SCOPE_ENTER(1, "%s Stream: %s\n", ast_sip_session_get_name(session),
-		ast_str_tmp(128, ast_stream_to_str(asterisk_stream, &STR_TMP)));
+	SCOPE_ENTER(1, "%s Stream: %s %s %s\n", ast_sip_session_get_name(session),
+		ast_str_tmp(128, ast_stream_to_str(asterisk_stream, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP))
+	);
 
 	if (!session->channel) {
 		SCOPE_EXIT_RTN_VALUE(1, "No channel\n");
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 33bfd21..542d10c 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -31,7 +31,6 @@
 
 #include "asterisk/res_pjsip.h"
 #include "asterisk/res_pjsip_session.h"
-#include "asterisk/res_pjsip_session_caps.h"
 #include "asterisk/callerid.h"
 #include "asterisk/datastore.h"
 #include "asterisk/module.h"
@@ -101,8 +100,6 @@
 	char stream_type[1];
 };
 
-static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer);
-
 static int sdp_handler_list_hash(const void *obj, int flags)
 {
 	const struct sdp_handler_list *handler_list = obj;
@@ -812,7 +809,8 @@
 
 		/* If this stream is already declined mark it as such, or mark it as such if we've reached the limit */
 		if (!remote_stream->desc.port || is_stream_limitation_reached(type, session->endpoint, type_streams)) {
-			ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
+			ast_debug(1, "%s: Declining incoming SDP media stream '%s' at position '%d'\n",
+				ast_sip_session_get_name(session),
 				ast_codec_media_type2str(type), i);
 			remove_stream_from_bundle(session_media, stream);
 			continue;
@@ -823,7 +821,8 @@
 
 		if (session_media->handler) {
 			handler = session_media->handler;
-			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
+			ast_debug(1, "%s: Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
+				ast_sip_session_get_name(session),
 				ast_codec_media_type2str(session_media->type),
 				session_media->handler->id);
 			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
@@ -831,12 +830,14 @@
 				/* Catastrophic failure. Abort! */
 				SCOPE_EXIT_RTN_VALUE(-1, "Couldn't negotiate incoming sdp stream\n");
 			} else if (res == 0) {
-				ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
+				ast_debug(1, "%s: Declining incoming SDP media stream '%s' at position '%d'\n",
+					ast_sip_session_get_name(session),
 					ast_codec_media_type2str(type), i);
 				remove_stream_from_bundle(session_media, stream);
 				continue;
 			} else if (res > 0) {
-				ast_debug(1, "Media stream '%s' handled by %s\n",
+				ast_debug(1, "%s: Media stream '%s' handled by %s\n",
+					ast_sip_session_get_name(session),
 					ast_codec_media_type2str(session_media->type),
 					session_media->handler->id);
 				/* Handled by this handler. Move to the next stream */
@@ -848,27 +849,32 @@
 
 		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);
+			ast_debug(1, "%s: No registered SDP handlers for media type '%s'\n",
+				ast_sip_session_get_name(session),
+				media);
 			continue;
 		}
 		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
 			if (handler == session_media->handler) {
 				continue;
 			}
-			ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
+			ast_debug(1, "%s: Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
+				ast_sip_session_get_name(session),
 				ast_codec_media_type2str(session_media->type),
 				handler->id);
 			res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
 			if (res < 0) {
 				/* Catastrophic failure. Abort! */
-				return -1;
+				SCOPE_EXIT_RTN_VALUE(-1, "Couldn't negotiate incoming sdp stream\n");
 			} else if (res == 0) {
-				ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
+				ast_debug(1, "%s: Declining incoming SDP media stream '%s' at position '%d'\n",
+					ast_sip_session_get_name(session),
 					ast_codec_media_type2str(type), i);
 				remove_stream_from_bundle(session_media, stream);
 				continue;
 			} else if (res > 0) {
-				ast_debug(1, "Media stream '%s' handled by %s\n",
+				ast_debug(1, "%s: Media stream '%s' handled by %s\n",
+					ast_sip_session_get_name(session),
 					ast_codec_media_type2str(session_media->type),
 					handler->id);
 				/* Handled by this handler. Move to the next stream */
@@ -929,12 +935,17 @@
 
 	handler = session_media->handler;
 	if (handler) {
-		ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+		ast_debug(1, "%s: Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+			ast_sip_session_get_name(session),
 			ast_codec_media_type2str(session_media->type),
 			handler->id);
+
+		ast_trace(2, "Calling apply_negotiated_sdp_stream\n");
 		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_debug(1, "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+				ast_sip_session_get_name(session),
 				ast_codec_media_type2str(session_media->type),
 				handler->id);
 			SCOPE_EXIT_RTN_VALUE(0,  "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n",
@@ -956,17 +967,22 @@
 		if (handler == session_media->handler) {
 			continue;
 		}
-		ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+		ast_debug(1, "%s: Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+			ast_sip_session_get_name(session),
 			ast_codec_media_type2str(session_media->type),
 			handler->id);
+
+		ast_trace(2, "Calling apply_negotiated_sdp_stream\n");
 		res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
+		res = 1;
 		if (res < 0) {
 			/* Catastrophic failure. Abort! */
 			SCOPE_EXIT_RTN_VALUE(-1, "%s: Handler '%s' returned %d\n",
 				ast_sip_session_get_name(session), handler->id, res);
 		}
 		if (res > 0) {
-			ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+			ast_debug(1, "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+				ast_sip_session_get_name(session),
 				ast_codec_media_type2str(session_media->type),
 				handler->id);
 			/* Handled by this handler. Move to the next stream */
@@ -989,12 +1005,16 @@
 		res ? "not negotiated.  Stopped" : "handled");
 }
 
-static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote)
+static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local,
+	const pjmedia_sdp_session *remote, pjsip_inv_state inv_state)
 {
 	int i;
-	struct ast_stream_topology *topology;
 	unsigned int changed = 0; /* 0 = unchanged, 1 = new source, 2 = new topology */
-	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
+	struct ast_stream_topology *cloned_pending;
+	SCOPE_ENTER(1, "%s  Pending topology: %s  Active topology %s\n",
+		ast_sip_session_get_name(session),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
 
 	if (!session->pending_media_state->topology) {
 		if (session->active_media_state->topology) {
@@ -1035,6 +1055,15 @@
 		SCOPE_EXIT_RTN_VALUE(-1, "Media stream count mismatch\n");
 	}
 
+	/*
+	 * This process updates the pending stream topology so we need to save a copy first.
+	 * This shoule be done better.
+	 */
+	cloned_pending = ast_stream_topology_clone(session->pending_media_state->topology);
+	if (!cloned_pending) {
+		SCOPE_EXIT_RTN_VALUE(-1, "Couldn't clone pending topology\n");
+	}
+
 	for (i = 0; i < local->media_count; ++i) {
 		struct ast_sip_session_media *session_media;
 		struct ast_stream *stream;
@@ -1102,21 +1131,17 @@
 		}
 	}
 
-	/* Update the topology on the channel to match the accepted one */
-	topology = ast_stream_topology_clone(session->pending_media_state->topology);
-	if (topology) {
-		ast_channel_set_stream_topology(session->channel, topology);
-		/* If this is a remotely done renegotiation that has changed the stream topology notify what is
-		 * currently handling this channel. Note that fax uses its own process, so if we are transitioning
-		 * between audio and fax or vice versa we don't notify.
-		 */
-		if (pjmedia_sdp_neg_was_answer_remote(session->inv_session->neg) == PJ_FALSE &&
-			session->active_media_state && session->active_media_state->topology &&
-			!ast_stream_topology_equal(session->active_media_state->topology, topology) &&
-			!session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE] &&
-			!session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE]) {
-			changed = 2;
-		}
+	/* If this is a remotely done renegotiation that has changed the stream topology notify what is
+	 * currently handling this channel. Note that fax uses its own process, so if we are transitioning
+	 * between audio and fax or vice versa we don't notify.
+	 */
+	if (pjmedia_sdp_neg_was_answer_remote(session->inv_session->neg) == PJ_FALSE &&
+		session->active_media_state && session->active_media_state->topology &&
+		!ast_stream_topology_equal(session->active_media_state->topology,
+			session->pending_media_state->topology) &&
+		!session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE] &&
+		!session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE]) {
+		changed = 2;
 	}
 
 	/* Remove all current file descriptors from the channel */
@@ -1135,7 +1160,7 @@
 	/* Active and pending flip flop as needed */
 	ast_sip_session_media_stats_save(session, session->active_media_state);
 	SWAP(session->active_media_state, session->pending_media_state);
-	ast_sip_session_media_state_reset(session->pending_media_state);
+	session->pending_media_state->topology = cloned_pending;
 
 	ast_channel_unlock(session->channel);
 
@@ -1149,7 +1174,10 @@
 		ast_queue_frame(session->channel, &ast_null_frame);
 	}
 
-	SCOPE_EXIT_RTN_VALUE(0);
+	SCOPE_EXIT_RTN_VALUE(0, "%s  Pending topology: %s  Active topology %s\n",
+		ast_sip_session_get_name(session),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
 }
 
 #define DATASTORE_BUCKETS 53
@@ -1539,7 +1567,7 @@
 			pjmedia_sdp_neg_get_active_local(inv_session->neg, &previous_sdp);
 		}
 	}
-	SCOPE_EXIT_RTN_VALUE(create_local_sdp(inv_session, session, previous_sdp));
+	SCOPE_EXIT_RTN_VALUE(ast_sip_session_create_local_sdp(inv_session, session, previous_sdp));
 }
 
 static void set_from_header(struct ast_sip_session *session)
@@ -1927,7 +1955,7 @@
 		pjmedia_sdp_neg_set_remote_offer(inv_session->pool, inv_session->neg, previous_offer);
 	}
 
-	new_answer = create_local_sdp(inv_session, session, previous_offer);
+	new_answer = ast_sip_session_create_local_sdp(inv_session, session, previous_offer);
 	if (!new_answer) {
 		ast_log(LOG_WARNING, "Could not create a new local SDP answer for channel '%s'\n",
 			ast_channel_name(session->channel));
@@ -1947,9 +1975,10 @@
 
 void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
 {
+	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
 	handle_outgoing_response(session, tdata);
 	pjsip_inv_send_msg(session->inv_session, tdata);
-	return;
+	SCOPE_EXIT_RTN();
 }
 
 static pj_bool_t session_on_rx_request(pjsip_rx_data *rdata);
@@ -2265,7 +2294,7 @@
 	pjmedia_sdp_session *offer;
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
 
-	if (!(offer = create_local_sdp(session->inv_session, session, NULL))) {
+	if (!(offer = ast_sip_session_create_local_sdp(session->inv_session, session, NULL))) {
 		pjsip_inv_terminate(session->inv_session, 500, PJ_FALSE);
 		SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create offer\n");
 	}
@@ -2709,6 +2738,12 @@
 	SCOPE_ENTER(1, "%s %s Topology: %s\n", ast_sorcery_object_get_id(endpoint), request_user,
 		ast_str_tmp(256, ast_stream_topology_to_str(req_topology, &STR_TMP)));
 
+	if (!req_topology || ast_stream_topology_get_count(req_topology) == 0) {
+		ast_log(LOG_ERROR, "Endpoint '%s': No, or empty, topology specified\n",
+			ast_sorcery_object_get_id(endpoint));
+		SCOPE_EXIT_RTN_VALUE(NULL, "No or empty topology\n");
+	}
+
 	/* If no location has been provided use the AOR list from the endpoint itself */
 	if (location || !contact) {
 		location = S_OR(location, endpoint->aors);
@@ -2757,57 +2792,19 @@
 		inv_session, NULL);
 	if (!session) {
 		pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
-		return NULL;
+		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't create session\n");
 	}
 	session->aor = ao2_bump(found_aor);
 	session->call_direction = AST_SIP_SESSION_OUTGOING_CALL;
 
 	ast_party_id_copy(&session->id, &endpoint->id.self);
 
-	if (ast_stream_topology_get_count(req_topology) > 0) {
-		/* get joint caps between req_topology and endpoint topology */
-		int i;
-
-		for (i = 0; i < ast_stream_topology_get_count(req_topology); ++i) {
-			struct ast_stream *req_stream;
-			struct ast_stream *clone_stream;
-
-			req_stream = ast_stream_topology_get_stream(req_topology, i);
-
-			if (ast_stream_get_state(req_stream) == AST_STREAM_STATE_REMOVED) {
-				continue;
-			}
-
-			clone_stream = ast_sip_session_create_joint_call_stream(session, req_stream);
-			if (!clone_stream || ast_stream_get_format_count(clone_stream) == 0) {
-				ast_stream_free(clone_stream);
-				continue;
-			}
-
-			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);
-					SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't create topology\n");
-				}
-			}
-
-			if (ast_stream_topology_append_stream(session->pending_media_state->topology, clone_stream) < 0) {
-				ast_stream_free(clone_stream);
-				continue;
-			}
-		}
-	}
+	session->pending_media_state->topology = ast_stream_topology_clone(req_topology);
 
 	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);
-			SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't clone topology\n");
-		}
+		pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+		ao2_ref(session, -1);
+		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't clone topology\n");
 	}
 
 	if (pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) {
@@ -3215,14 +3212,9 @@
 	pjsip_tx_data *tdata = NULL;
 	pjsip_timer_setting timer;
 	pjsip_rdata_sdp_info *sdp_info;
-	pjmedia_sdp_session *local = NULL;
 	char buffer[AST_SOCKADDR_BUFLEN];
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(invite->session));
 
-	/* From this point on, any calls to pjsip_inv_terminate have the last argument as PJ_TRUE
-	 * so that we will be notified so we can destroy the session properly
-	 */
-
 	if (invite->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
 		ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n",
 			invite->session->inv_session->cause,
@@ -3309,31 +3301,8 @@
 			}
 			goto end;
 		}
-		/* We are creating a local SDP which is an answer to their offer */
-		local = create_local_sdp(invite->session->inv_session, invite->session, sdp_info->sdp);
-	} else {
-		/* We are creating a local SDP which is an offer */
-		local = create_local_sdp(invite->session->inv_session, invite->session, NULL);
 	}
 
-	/* If we were unable to create a local SDP terminate the session early, it won't go anywhere */
-	if (!local) {
-		tdata = NULL;
-		if (pjsip_inv_end_session(invite->session->inv_session, 500, NULL, &tdata) == PJ_SUCCESS
-			&& tdata) {
-			ast_sip_session_send_response(invite->session, tdata);
-		}
-		goto end;
-	}
-
-	pjsip_inv_set_local_sdp(invite->session->inv_session, local);
-	pjmedia_sdp_neg_set_prefer_remote_codec_order(invite->session->inv_session->neg, PJ_FALSE);
-#ifdef PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS
-	if (!invite->session->endpoint->preferred_codec_only) {
-		pjmedia_sdp_neg_set_answer_multiple_codecs(invite->session->inv_session->neg, PJ_TRUE);
-	}
-#endif
-
 	handle_incoming_request(invite->session, invite->rdata);
 
 end:
@@ -3518,8 +3487,7 @@
 		break;
 	default:
 		/* Handle other in-dialog methods if their supplements have been registered */
-		handled = dlg && (inv_session = pjsip_dlg_get_inv_session(dlg)) &&
-			has_supplement(inv_session->mod_data[session_module.id], rdata);
+		handled = (session && has_supplement(session, rdata));
 		break;
 	}
 
@@ -3642,10 +3610,11 @@
 	SCOPE_ENTER(1, "%s Method: %.*s\n", ast_sip_session_get_name(session),
 		(int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
 
-	ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
 		if (supplement->incoming_request && does_method_match(&req.method.name, supplement->method)) {
-			if (supplement->incoming_request(session, rdata)) {
+			int rc = 0;
+			rc = supplement->incoming_request(session, rdata);
+			if (rc) {
 				break;
 			}
 		}
@@ -3692,15 +3661,11 @@
 		enum ast_sip_session_response_priority response_priority)
 {
 	struct ast_sip_session_supplement *supplement;
-	struct pjsip_status_line status = rdata->msg_info.msg->line.status;
 	SCOPE_ENTER(1, "%s Method: %.*s Status: %d  Priority %s\n", ast_sip_session_get_name(session),
 		(int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr,
 		rdata->msg_info.msg->line.status.code,
 		response_priority == AST_SIP_SESSION_AFTER_MEDIA ? "after" : "before");
 
-	ast_debug(3, "Response is %d %.*s\n", status.code, (int) pj_strlen(&status.reason),
-			pj_strbuf(&status.reason));
-
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
 		if (!(supplement->response_priority & response_priority)) {
 			continue;
@@ -3717,6 +3682,7 @@
 		enum ast_sip_session_response_priority response_priority)
 {
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
+
 	ast_debug(3, "Received %s\n", rdata->msg_info.msg->type == PJSIP_REQUEST_MSG ?
 			"request" : "response");
 
@@ -3764,11 +3730,6 @@
 		ast_log(LOG_ERROR, "Cannot send response due to missing sequence header");
 		SCOPE_EXIT_RTN("Missing cseq\n");
 	}
-
-	ast_debug(3, "Method is %.*s, Response is %d %.*s\n", (int) pj_strlen(&cseq->method.name),
-		pj_strbuf(&cseq->method.name), status.code, (int) pj_strlen(&status.reason),
-		pj_strbuf(&status.reason));
-
 	ast_sip_message_apply_transport(session->endpoint->transport, tdata);
 
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
@@ -3999,8 +3960,6 @@
 		SCOPE_EXIT_RTN("Shutting down\n");
 	}
 
-	session = inv->mod_data[id];
-
 	print_debug_details(inv, tsx, e);
 	if (!session) {
 		/* The session has ended.  Ignore the transaction change. */
@@ -4036,7 +3995,9 @@
 		if ((e->body.tsx_state.src.rdata->msg_info.msg->type != PJSIP_REQUEST_MSG) ||
 			(tsx->method.id != PJSIP_BYE_METHOD)) {
 			handle_incoming(session, e->body.tsx_state.src.rdata,
-				AST_SIP_SESSION_AFTER_MEDIA);
+				(session->inv_session->state == PJSIP_INV_STATE_CALLING ||
+				session->inv_session->state == PJSIP_INV_STATE_EARLY)
+				? AST_SIP_SESSION_BEFORE_MEDIA : AST_SIP_SESSION_AFTER_MEDIA);
 		}
 		if (tsx->method.id == PJSIP_INVITE_METHOD) {
 			if (tsx->role == PJSIP_ROLE_UAC) {
@@ -4222,8 +4183,10 @@
 		ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
 
 	if (handler) {
+		ast_trace(2, "Handler '%s' already exists\n", handler->id);
 		/* if an already assigned handler reports a catastrophic error, fail */
-		res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream);
+		res = handler->create_outgoing_sdp_stream(session,
+			session_media, answer, remote, stream);
 		if (res < 0) {
 			SCOPE_EXIT_RTN_VALUE(-1, "Coudn't create sdp stream\n");
 		}
@@ -4238,9 +4201,15 @@
 	/* no handler for this stream type and we have a list to search */
 	AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
 		if (handler == session_media->handler) {
+			ast_trace(2, "Skipping handler '%s'\n", handler->id);
 			continue;
 		}
+
+		ast_trace(2, "Calling handler '%s' with stream %s\n", handler->id,
+			ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
 		res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream);
+		ast_trace(2, "Handler '%s' RC: %d stream %s\n", handler->id, res,
+			ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
 		if (res < 0) {
 			/* catastrophic error */
 			SCOPE_EXIT_RTN_VALUE(-1, "Coudn't create sdp stream\n");
@@ -4334,7 +4303,8 @@
 	return 0;
 }
 
-static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer)
+struct pjmedia_sdp_session *ast_sip_session_create_local_sdp(pjsip_inv_session *inv,
+	struct ast_sip_session *session, const pjmedia_sdp_session *offer)
 {
 	static const pj_str_t STR_IN = { "IN", 2 };
 	static const pj_str_t STR_IP4 = { "IP4", 3 };
@@ -4364,26 +4334,11 @@
 	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->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. We try to not alter the current state of the SDP negotiation
-		 * by using what is currently negotiated. If this is unavailable we fall back to what is configured on the endpoint.
-		 */
-		ast_stream_topology_free(session->pending_media_state->topology);
-		if (session->active_media_state->topology) {
-			session->pending_media_state->topology = ast_stream_topology_clone(session->active_media_state->topology);
-		} else {
-			session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
-		}
-		if (!session->pending_media_state->topology) {
-			SCOPE_EXIT_RTN_VALUE(NULL, "No pending topology\n");
-		}
-	}
-
 	for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) {
 		struct ast_sip_session_media *session_media;
 		struct ast_stream *stream;
 		unsigned int streams = local->media_count;
+		int res = 0;
 
 		/* This code does not enforce any maximum stream count limitations as that is done on either
 		 * the handling of an incoming SDP offer or on the handling of a session refresh.
@@ -4396,7 +4351,10 @@
 			SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't add state\n");
 		}
 
-		if (add_sdp_streams(session_media, session, local, offer, stream)) {
+		ast_trace(2, "Adding stream %s\n", ast_stream_to_str(stream, NULL));
+		res = add_sdp_streams(session_media, session, local, offer, stream);
+		ast_trace(2, "Added stream %s  RC: %d\n", ast_stream_to_str(stream, NULL), res);
+		if (res) {
 			SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't add stream\n");
 		}
 
@@ -4477,13 +4435,12 @@
 		SCOPE_EXIT_RTN("Shutting down\n");
 	}
 
-	session = inv->mod_data[session_module.id];
 	if (handle_incoming_sdp(session, offer)) {
 		ast_sip_session_media_state_reset(session->pending_media_state);
 		SCOPE_EXIT_RTN("Couldn't handle sdp\n");
 	}
 
-	if ((answer = create_local_sdp(inv, session, offer))) {
+	if ((answer = ast_sip_session_create_local_sdp(inv, session, offer))) {
 		pjsip_inv_set_sdp_answer(inv, answer);
 	}
 
@@ -4501,14 +4458,15 @@
 {
 	const pjmedia_sdp_session *local, *remote;
 	struct ast_sip_session *session = inv->mod_data[session_module.id];
-	SCOPE_ENTER(1, "%s  Inv State: %s\n", ast_sip_session_get_name(session),
-		pjsip_inv_state_name(inv->state));
+	SCOPE_ENTER(1, "%s  Inv State: %s  Pending topology: %s  Active topology %s\n",
+		ast_sip_session_get_name(session), pjsip_inv_state_name(inv->state),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
 
 	if (ast_shutdown_final()) {
 		SCOPE_EXIT_RTN("Shutting down\n");
 	}
 
-	session = inv->mod_data[session_module.id];
 	if (!session || !session->channel) {
 		/*
 		 * If we don't have a session or channel then we really
@@ -4562,11 +4520,14 @@
 		SCOPE_EXIT_RTN("Couldn't get active local\n");return;
 	}
 
-	if (handle_negotiated_sdp(session, local, remote)) {
+	if (handle_negotiated_sdp(session, local, remote, inv->state)) {
 		ast_sip_session_media_state_reset(session->pending_media_state);
 	}
 
-	SCOPE_EXIT_RTN();
+	SCOPE_EXIT_RTN("%s  Inv State: %s  Pending topology: %s  Active topology %s\n",
+		ast_sip_session_get_name(session), pjsip_inv_state_name(inv->state),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)),
+		ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
 }
 
 static pjsip_redirect_op session_inv_on_redirected(pjsip_inv_session *inv, const pjsip_uri *target, const pjsip_event *e)
@@ -4708,12 +4669,15 @@
 		return AST_MODULE_LOAD_DECLINE;
 	}
 	endpt = ast_sip_get_pjsip_endpoint();
+
 	pjsip_inv_usage_init(endpt, &inv_callback);
 	pjsip_100rel_init_module(endpt);
 	pjsip_timer_init_module(endpt);
+
 	if (ast_sip_register_service(&session_module)) {
 		return AST_MODULE_LOAD_DECLINE;
 	}
+
 	ast_sip_register_service(&session_reinvite_module);
 	ast_sip_register_service(&outbound_invite_auth_module);
 
diff --git a/res/res_pjsip_session/pjsip_session_caps.c b/res/res_pjsip_session/pjsip_session_caps.c
deleted file mode 100644
index ca7ef44..0000000
--- a/res/res_pjsip_session/pjsip_session_caps.c
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2020, Sangoma Technologies Corporation
- *
- * Kevin Harwell <kharwell at digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-#include "asterisk.h"
-
-#include "asterisk/astobj2.h"
-#include "asterisk/channel.h"
-#include "asterisk/format.h"
-#include "asterisk/format_cap.h"
-#include "asterisk/logger.h"
-#include "asterisk/sorcery.h"
-#include "asterisk/stream.h"
-#include "asterisk/res_pjsip.h"
-#include "asterisk/res_pjsip_session.h"
-#include "asterisk/res_pjsip_session_caps.h"
-
-static void log_caps(int level, const char *file, int line, const char *function,
-	const struct ast_sip_session *session, enum ast_media_type media_type,
-	const struct ast_format_cap *local, const struct ast_format_cap *remote,
-	const struct ast_format_cap *joint)
-{
-	struct ast_str *s1;
-	struct ast_str *s2;
-	struct ast_str *s3;
-	int outgoing = session->call_direction == AST_SIP_SESSION_OUTGOING_CALL;
-	struct ast_flags pref =
-		outgoing
-		? session->endpoint->media.outgoing_call_offer_pref
-		: session->endpoint->media.incoming_call_offer_pref;
-
-	if (level == __LOG_DEBUG && !DEBUG_ATLEAST(3)) {
-		return;
-	}
-
-	s1 = local ? ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN) : NULL;
-	s2 = remote ? ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN) : NULL;
-	s3 = joint ? ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN) : NULL;
-
-	ast_log(level, file, line, function, "'%s' Caps for %s %s call with pref '%s' - remote: %s local: %s joint: %s\n",
-		session->channel ? ast_channel_name(session->channel) :
-			ast_sorcery_object_get_id(session->endpoint),
-		outgoing? "outgoing" : "incoming",
-		ast_codec_media_type2str(media_type),
-		ast_sip_call_codec_pref_to_str(pref),
-		s2 ? ast_format_cap_get_names(remote, &s2) : "(NONE)",
-		s1 ? ast_format_cap_get_names(local, &s1) : "(NONE)",
-		s3 ? ast_format_cap_get_names(joint, &s3) : "(NONE)");
-}
-
-struct ast_format_cap *ast_sip_create_joint_call_cap(const struct ast_format_cap *remote,
-	struct ast_format_cap *local, enum ast_media_type media_type,
-	struct ast_flags codec_pref)
-{
-	struct ast_format_cap *joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-	struct ast_format_cap *local_filtered = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-
-	if (!joint || !local_filtered) {
-		ast_log(LOG_ERROR, "Failed to allocate %s call offer capabilities\n",
-				ast_codec_media_type2str(media_type));
-		ao2_cleanup(joint);
-		ao2_cleanup(local_filtered);
-		return NULL;
-	}
-
-	ast_format_cap_append_from_cap(local_filtered, local, media_type);
-
-	if (ast_sip_call_codec_pref_test(codec_pref, LOCAL)) {
-		if (ast_sip_call_codec_pref_test(codec_pref, INTERSECT)) {
-			ast_format_cap_get_compatible(local_filtered, remote, joint); /* Get common, prefer local */
-		} else {
-			ast_format_cap_append_from_cap(joint, local_filtered, media_type); /* Add local */
-			ast_format_cap_append_from_cap(joint, remote, media_type); /* Then remote */
-		}
-	} else {
-		if (ast_sip_call_codec_pref_test(codec_pref, INTERSECT)) {
-			ast_format_cap_get_compatible(remote, local_filtered, joint); /* Get common, prefer remote */
-		} else {
-			ast_format_cap_append_from_cap(joint, remote, media_type); /* Add remote */
-			ast_format_cap_append_from_cap(joint, local_filtered, media_type); /* Then local */
-		}
-	}
-
-	ao2_ref(local_filtered, -1);
-
-	if (ast_format_cap_empty(joint)) {
-		return joint;
-	}
-
-	if (ast_sip_call_codec_pref_test(codec_pref, FIRST)) {
-		/*
-		 * Save the most preferred one. Session capabilities are per stream and
-		 * a stream only carries a single media type, so no reason to worry with
-		 * the type here (i.e different or multiple types)
-		 */
-		struct ast_format *single = ast_format_cap_get_format(joint, 0);
-		/* Remove all formats */
-		ast_format_cap_remove_by_type(joint, AST_MEDIA_TYPE_UNKNOWN);
-		/* Put the most preferred one back */
-		ast_format_cap_append(joint, single, 0);
-		ao2_ref(single, -1);
-	}
-
-	return joint;
-}
-
-struct ast_stream *ast_sip_session_create_joint_call_stream(const struct ast_sip_session *session,
-	struct ast_stream *remote_stream)
-{
-	struct ast_stream *joint_stream = ast_stream_clone(remote_stream, NULL);
-	const struct ast_format_cap *remote = ast_stream_get_formats(remote_stream);
-	enum ast_media_type media_type = ast_stream_get_type(remote_stream);
-
-	struct ast_format_cap *joint = ast_sip_create_joint_call_cap(remote,
-		session->endpoint->media.codecs, media_type,
-		session->call_direction == AST_SIP_SESSION_OUTGOING_CALL
-				? session->endpoint->media.outgoing_call_offer_pref
-				: session->endpoint->media.incoming_call_offer_pref);
-
-	ast_stream_set_formats(joint_stream, joint);
-	ao2_cleanup(joint);
-
-	log_caps(LOG_DEBUG, session, media_type, session->endpoint->media.codecs, remote, joint);
-
-	return joint_stream;
-}
-
-struct ast_format_cap *ast_sip_session_create_joint_call_cap(
-	const struct ast_sip_session *session, enum ast_media_type media_type,
-	const struct ast_format_cap *remote)
-{
-	struct ast_format_cap *joint = ast_sip_create_joint_call_cap(remote,
-		session->endpoint->media.codecs, media_type,
-		session->call_direction == AST_SIP_SESSION_OUTGOING_CALL
-				? session->endpoint->media.outgoing_call_offer_pref
-				: session->endpoint->media.incoming_call_offer_pref);
-
-	log_caps(LOG_DEBUG, session, media_type, session->endpoint->media.codecs, remote, joint);
-
-	return joint;
-}
diff --git a/tests/test_res_pjsip_session_caps.c b/tests/test_res_pjsip_session_caps.c
deleted file mode 100644
index 54438f5..0000000
--- a/tests/test_res_pjsip_session_caps.c
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2020, Sangoma Technologies Corporation
- *
- * George Joseph <gjoseph at sangoma.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*!
- * \file
- * \brief res_pjsip_session format caps tests
- *
- * \author George Joseph <gjoseph at sangoma.com>
- *
- */
-
-/*** MODULEINFO
-	<depend>TEST_FRAMEWORK</depend>
-	<depend>pjproject</depend>
-	<depend>res_pjsip</depend>
-	<depend>res_pjsip_session</depend>
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-#include "asterisk/test.h"
-#include "asterisk/module.h"
-#include "asterisk/res_pjsip.h"
-#include "asterisk/utils.h"
-#include "asterisk/format.h"
-#include "asterisk/format_cap.h"
-#include "asterisk/res_pjsip_session.h"
-#include "asterisk/res_pjsip_session_caps.h"
-
-static enum ast_test_result_state test_create_joint(struct ast_test *test, const char *local_string,
-	const char *remote_string, const char *pref_string, int is_outgoing, const char *expected_string,
-	enum ast_test_result_state expected_result)
-{
-	RAII_VAR(struct ast_format_cap *, local, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
-	RAII_VAR(struct ast_format_cap *, remote, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
-	RAII_VAR(struct ast_format_cap *, joint, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
-	struct ast_str *joint_str = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
-	const char *joint_string;
-	char *stripped_joint;
-	struct ast_flags codec_prefs;
-	int rc;
-	int i;
-
-	ast_test_status_update(test, "Testing local: (%s), remote: (%s), pref: (%-12s), outgoing: (%s), expected: (%s) expected result: (%s)\n",
-		local_string, remote_string, pref_string, is_outgoing ? "yes" : "no ", expected_string,
-		expected_result == AST_TEST_PASS ? "PASS" : "FAIL");
-
-	ast_test_validate(test, local != NULL && remote != NULL && joint != NULL);
-
-	rc = ast_format_cap_update_by_allow_disallow(local, local_string, 1);
-	if (rc != 0) {
-		ast_test_status_update(test, "    %sxpected Failure: Coulldn't parse local codecs (%s)\n",
-			expected_result == AST_TEST_FAIL ? "E" : "Une", local_string);
-		return expected_result == AST_TEST_FAIL ? AST_TEST_PASS : AST_TEST_FAIL;
-	}
-	rc = ast_format_cap_update_by_allow_disallow(remote, remote_string, 1);
-	if (rc != 0) {
-		ast_test_status_update(test, "    %sxpected Failure: Coulldn't parse remote codecs (%s)\n",
-			expected_result == AST_TEST_FAIL ? "E" : "Une", remote_string);
-		return expected_result == AST_TEST_FAIL ? AST_TEST_PASS : AST_TEST_FAIL;
-	}
-
-	rc = ast_sip_call_codec_str_to_pref(&codec_prefs, pref_string, is_outgoing);
-	if (rc != 0) {
-		ast_test_status_update(test, "    %sxpected Failure: Invalid preference string incoming/outgoing combination.\n",
-			expected_result == AST_TEST_FAIL ? "E" : "Une");
-		return expected_result == AST_TEST_FAIL ? AST_TEST_PASS : AST_TEST_FAIL;
-	}
-
-	joint = ast_sip_create_joint_call_cap(remote, local, AST_MEDIA_TYPE_AUDIO, codec_prefs);
-	if (joint == NULL) {
-		ast_test_status_update(test, "    %sxpected Failure: No joint caps.\n",
-			expected_result == AST_TEST_FAIL ? "E" : "Une");
-		return expected_result == AST_TEST_FAIL ? AST_TEST_PASS : AST_TEST_FAIL;
-	}
-
-	joint_string = ast_format_cap_get_names(joint, &joint_str);
-	stripped_joint = ast_str_truncate(joint_str, ast_str_strlen(joint_str) - 1) + 1;
-	for(i = 0; i <= strlen(stripped_joint); i++) {
-		if(stripped_joint[i] == '|') {
-			stripped_joint[i] = ',';
-		}
-	}
-
-	if (!joint_string || strcmp(stripped_joint, expected_string) != 0) {
-		ast_test_status_update(test, "    %sxpected Failure: Expected: (%s) Actual: (%s)\n",
-			expected_result == AST_TEST_FAIL ? "E" : "Une", expected_string, stripped_joint);
-		return expected_result == AST_TEST_FAIL ? AST_TEST_PASS : AST_TEST_FAIL;
-	}
-
-	return AST_TEST_PASS;
-}
-
-#define RUN_CREATE_JOINT(local, remote, pref, outgoing, expected, result) \
-do { \
-	if (test_create_joint(test, local, remote, pref, outgoing, expected, result) != AST_TEST_PASS) { \
-		rc += 1; \
-	} \
-} while (0)
-
-AST_TEST_DEFINE(low_level)
-{
-	int rc = 0;
-
-	switch (cmd) {
-	case TEST_INIT:
-		info->name = __func__;
-		info->category = "/res/res_pjsip_session/caps/";
-		info->summary = "Test res_pjsip_session_caps";
-		info->description = "Test res_pjsip_session_caps";
-		return AST_TEST_NOT_RUN;
-	case TEST_EXECUTE:
-		break;
-	}
-
-	/* Incoming */
-
-	ast_test_status_update(test, "Testing incoming expected pass\n");
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,alaw,g729",	"local", 		0,	"alaw,g722",	AST_TEST_PASS);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,alaw,g729",	"local_first",	0,	"alaw",			AST_TEST_PASS);
-	RUN_CREATE_JOINT("slin",			"all",				"local",		0,	"slin",			AST_TEST_PASS);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,alaw,g729",	"remote",		0,	"g722,alaw",	AST_TEST_PASS);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,alaw,g729",	"remote_first",	0,	"g722",			AST_TEST_PASS);
-	RUN_CREATE_JOINT("all",				"slin",				"remote_first",	0,	"slin",			AST_TEST_PASS);
-
-	ast_test_status_update(test, "Testing incoming expected fail\n");
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g729",				"local",		0,	"",				AST_TEST_FAIL);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,alaw,g729",	"local_merge",	0,	"",				AST_TEST_FAIL);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,alaw,g729",	"remote_merge",	0,	"",				AST_TEST_FAIL);
-
-	ast_test_status_update(test, "Testing outgoing expected pass\n");
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,g729,alaw",	"local",		1,	"alaw,g722",			AST_TEST_PASS);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,g729,alaw",	"local_first",	1,	"alaw",					AST_TEST_PASS);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,g729,alaw",	"local_merge",	1,	"ulaw,alaw,g722,g729",	AST_TEST_PASS);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,g729,alaw",	"remote",		1,	"g722,alaw",			AST_TEST_PASS);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,g729,alaw",	"remote_first",	1,	"g722",					AST_TEST_PASS);
-	RUN_CREATE_JOINT("ulaw,alaw,g722",	"g722,g729,alaw",	"remote_merge",	1,	"g722,g729,alaw,ulaw",	AST_TEST_PASS);
-	RUN_CREATE_JOINT("!all",			"g722,g729,alaw",	"remote_merge",	1,	"g722,g729,alaw",		AST_TEST_PASS);
-
-	return rc >= 1 ? AST_TEST_FAIL : AST_TEST_PASS;
-}
-
-static int load_module(void)
-{
-	AST_TEST_REGISTER(low_level);
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-static int unload_module(void)
-{
-	AST_TEST_UNREGISTER(low_level);
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "res_pjsip_session caps test module",
-	.support_level = AST_MODULE_SUPPORT_CORE,
-	.load = load_module,
-	.unload = unload_module,
-	.requires = "res_pjsip_session",
-);

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/14637
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: Iad188ae997bdcb5c28e2eb12c6bb2b732538ad45
Gerrit-Change-Number: 14637
Gerrit-PatchSet: 1
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200715/64c8a915/attachment-0001.html>


More information about the asterisk-code-review mailing list