diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 695ba5d56d03473debea41761d6c49b2542908f5..93fb701af1ef8eacb7a3e2452c10fcacf2a4a508 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -798,20 +798,55 @@
                    ; "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= ; Sets the preferred codecs, and order to use between
-                           ; those received in the offer, and those set in this
-                           ; configuration's allow line. Valid values include:
+;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 - prefer and order by configuration (default).
-                           ; local_single - prefer and order by configuration,
-                           ;     but only choose 'top' most codec
-                           ; remote - prefer and order by incoming sdp.
-                           ; remote_single - prefer and order by incoming sdp,
-                           ;     but only choose 'top' most codec
-;preferred_codec_only=yes       ; Respond to a SIP invite with the single most preferred codec
-                                ; rather than advertising all joint codec capabilities. This
-                                ; limits the other side's codec choice to exactly what we prefer.
-                                ; default is no.
+                           ; 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.
+;preferred_codec_only=no   ; Respond to a SIP invite with the single most
+                           ; preferred codec rather than advertising all joint
+                           ; codec capabilities. This limits the other side's
+                           ; codec choice to exactly what we prefer.
+                           ; default is no.
+                           ; NOTE: This option is deprecated in favor
+                           ; of incoming_call_offer_pref.  Setting both
+                           ; options is unsupported.
 ;asymmetric_rtp_codec= ; Allow the sending and receiving codec to differ and
                        ; not be automatically matched (default: "no")
 ;refer_blind_progress= ; Whether to notifies all the progress details on blind
diff --git a/doc/CHANGES-staging/res_pjsip_call_offer_pref.txt b/doc/CHANGES-staging/res_pjsip_call_offer_pref.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c8b8747c27b7226e546f1908bd5d5f9c46cb794c
--- /dev/null
+++ b/doc/CHANGES-staging/res_pjsip_call_offer_pref.txt
@@ -0,0 +1,8 @@
+Subject: res_pjsip
+Subject: res_pjsip_session
+Master-Only: True
+
+Two new options, incoming_call_offer_pref and outgoing_call_offer_pref
+have been added to res_pjsip endpoints that specify the preferred order
+of codecs to use between those received/sent in an SDP offer and those
+set in the endpoint configuration.
diff --git a/doc/CHANGES-staging/res_pjsip_incoming_call_offer_pref.txt b/doc/CHANGES-staging/res_pjsip_incoming_call_offer_pref.txt
deleted file mode 100644
index 5a12052534a56c43799daea6ab2d80aea03b45ec..0000000000000000000000000000000000000000
--- a/doc/CHANGES-staging/res_pjsip_incoming_call_offer_pref.txt
+++ /dev/null
@@ -1,53 +0,0 @@
-Subject: res_pjsip
-Subject: res_pjsip_session
-Master-Only: True
-
-A new option, incoming_call_offer_pref, was added to res_pjsip endpoints that
-specifies the preferred order of codecs to use between those received in the
-offer, and those set in the configuration.
-
-Valid values include:
-  local - prefer and order by configuration (default).
-  local_single - prefer and order by configuration, but only choose 'top'
-                 most codec
-  remote - prefer and order by incoming sdp.
-  remote_single - prefer and order by incoming sdp, but only choose 'top' most
-                  most codec
-
-Example A:
-  [alice]
-  type=endpoint
-  incoming_call_offer_pref=local
-  allow=!all,opus,alaw,ulaw
-
-  Alice's incoming sdp=g722,ulaw,alaw
-  RESULT: alaw,ulaw
-
-Example B:
-  [alice]
-  type=endpoint
-  incoming_call_offer_pref=local_single
-  allow=!all,opus,alaw,ulaw
-
-  Alice's incoming sdp=g722,ulaw,alaw
-  RESULT: alaw
-
-Example C:
-  [alice]
-  type=endpoint
-  incoming_call_offer_pref=remote
-  allow=!all,opus,alaw,ulaw
-
-  Alice's incoming sdp=g722,ulaw,alaw
-  RESULT: ulaw,alaw
-
-Example D:
-  [alice]
-  type=endpoint
-  incoming_call_offer_pref=remote_single
-  allow=!all,opus,alaw,ulaw
-
-  Alice's incoming sdp=g722,ulaw,alaw
-  RESULT: ulaw
-
-
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 816e614133ac5af8f4ee266978de5d8cc0971308..c1d5490f0b8db4212d443095e15a2161cf9459f7 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -511,22 +511,40 @@ enum ast_sip_session_redirect {
 
 /*!
  * \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,
-	/*! Prefer, and order by local values (intersection) */
-	AST_SIP_CALL_CODEC_PREF_LOCAL_LIMIT,
-	/*! Prefer, and order by local values (top/first only) */
-	AST_SIP_CALL_CODEC_PREF_LOCAL_SINGLE,
+	AST_SIP_CALL_CODEC_PREF_LOCAL = 	1 << 4,
 	/*! Prefer, and order by remote values */
-	AST_SIP_CALL_CODEC_PREF_REMOTE,
-	/*! Prefer, and order by remote values (intersection) */
-	AST_SIP_CALL_CODEC_PREF_REMOTE_LIMIT,
-	/*! Prefer, and order by remote values (top/first only) */
-	AST_SIP_CALL_CODEC_PREF_REMOTE_SINGLE,
+	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
  */
@@ -769,7 +787,9 @@ struct ast_sip_endpoint_media_configuration {
 	/*! Enable webrtc settings and defaults */
 	unsigned int webrtc;
 	/*! Codec preference for an incoming offer */
-	enum ast_sip_call_codec_pref incoming_call_offer_pref;
+	struct ast_flags incoming_call_offer_pref;
+	/*! Codec preference for an outgoing offer */
+	struct ast_flags outgoing_call_offer_pref;
 };
 
 /*!
@@ -3222,6 +3242,18 @@ int ast_sip_dtmf_to_str(const enum ast_sip_dtmf_mode dtmf,
  */
 int ast_sip_str_to_dtmf(const char *dtmf_mode);
 
+/*!
+ * \brief Convert the call codec preference flags to a string
+ * \since 18.0.0
+ *
+ * \param pref the call codec preference setting
+ *
+ * \returns a constant string with either the setting value or 'unknown'
+ * \note Don't try to free the string!
+ *
+ */
+const char *ast_sip_call_codec_pref_to_str(struct ast_flags pref);
+
 /*!
  * \brief Transport shutdown monitor callback.
  * \since 13.18.0
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index a5ae6f13bc3cc918042818c419df54cdcb0e2759..fd49a7ba5e42bddbafbc7bab60cf0a5cd7b233ee 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -30,6 +30,9 @@
 #include "asterisk/sdp_srtp.h"
 /* Needed for ast_media_type */
 #include "asterisk/codec.h"
+/* Needed for pjmedia_sdp_session and pjsip_inv_session */
+#include <pjsip_ua.h>
+
 
 /* Forward declarations */
 struct ast_sip_endpoint;
@@ -80,8 +83,6 @@ struct ast_sip_session_media {
 	struct ast_sip_session_sdp_handler *handler;
 	/*! \brief Holds SRTP information */
 	struct ast_sdp_srtp *srtp;
-	/*! \brief Media format capabilities */
-	struct ast_sip_session_caps *caps;
 	/*! \brief What type of encryption is in use on this stream */
 	enum ast_sip_session_media_encryption encryption;
 	/*! \brief The media transport in use for this stream */
@@ -157,6 +158,12 @@ struct ast_sip_session_delayed_request;
 /*! \brief Opaque struct controlling the suspension of the session's serializer. */
 struct ast_sip_session_suspender;
 
+/*! \brief Indicates the call direction respective to Asterisk */
+enum ast_sip_session_call_direction {
+	AST_SIP_SESSION_INCOMING_CALL = 0,
+	AST_SIP_SESSION_OUTGOING_CALL,
+};
+
 /*!
  * \brief A structure describing a SIP session
  *
@@ -222,8 +229,10 @@ struct ast_sip_session {
 	enum ast_sip_dtmf_mode dtmf;
 	/*! Initial incoming INVITE Request-URI.  NULL otherwise. */
 	pjsip_uri *request_uri;
-	/* Media statistics for negotiated RTP streams */
+	/*! Media statistics for negotiated RTP streams */
 	AST_VECTOR(, struct ast_rtp_instance_stats *) media_stats;
+	/*! The direction of the call respective to Asterisk */
+	enum ast_sip_session_call_direction call_direction;
 };
 
 typedef int (*ast_sip_session_request_creation_cb)(struct ast_sip_session *session, pjsip_tx_data *tdata);
diff --git a/include/asterisk/res_pjsip_session_caps.h b/include/asterisk/res_pjsip_session_caps.h
index 810a1e624b1e0898096c4e1274630c63b9b82064..0d7020ff7613abd823a8c79e961e9abd80acd937 100644
--- a/include/asterisk/res_pjsip_session_caps.h
+++ b/include/asterisk/res_pjsip_session_caps.h
@@ -20,63 +20,63 @@
 
 struct ast_format_cap;
 struct ast_sip_session;
-struct ast_sip_session_media;
-struct ast_sip_session_caps;
 
 /*!
- * \brief Allocate a SIP session capabilities object.
+ * \brief Create joint capabilities
  * \since 18.0.0
  *
- * \retval An ao2 allocated SIP session capabilities object, or NULL on error
- */
-struct ast_sip_session_caps *ast_sip_session_caps_alloc(void);
-
-/*!
- * \brief Set the incoming call offer capabilities for a session.
- * \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.
  *
- * This will replace any capabilities already present.
+ * \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
  *
- * \param caps A session's capabilities object
- * \param cap The capabilities to set it to
+ * \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,
  */
-void ast_sip_session_set_incoming_call_offer_cap(struct ast_sip_session_caps *caps,
-	struct ast_format_cap *cap);
+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 Get the incoming call offer capabilities.
+ * \brief Create a new stream of joint capabilities
  * \since 18.0.0
  *
- * \note Returned objects reference is not incremented.
+ * 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 caps A session's capabilities object
+ * \param session The session
+ * \param remote The remote stream
  *
- * \retval An incoming call offer capabilities object
+ * \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.
  */
-const struct ast_format_cap *ast_sip_session_get_incoming_call_offer_cap(
-	const struct ast_sip_session_caps *caps);
+struct ast_stream *ast_sip_session_create_joint_call_stream(const struct ast_sip_session *session,
+	struct ast_stream *remote);
 
 /*!
- * \brief Make the incoming call offer capabilities for a session.
+ * \brief Create joint capabilities
  * \since 18.0.0
  *
- * Creates and sets a list of joint capabilities between the given remote
- * capabilities, and pre-configured ones. The resulting joint list is then
- * stored, and 'owned' (reference held) by the session.
- *
- * If the incoming capabilities have been set elsewhere, this will not replace
- * those. It will however, return a pointer to the current set.
- *
- * \note Returned object's reference is not incremented.
+ * 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 session_media An associated media session
- * \param remote Capabilities of a device
+ * \param media_type The media type
+ * \param remote Capabilities received in an SDP offer or from the core
  *
- * \retval A pointer to the incoming call offer capabilities
+ * \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,
  */
-const struct ast_format_cap *ast_sip_session_join_incoming_call_offer_cap(
-	const struct ast_sip_session *session, const struct ast_sip_session_media *session_media,
-	const struct ast_format_cap *remote);
+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/include/asterisk/stream.h b/include/asterisk/stream.h
index ade740d19da84062a29562db59b3848779bd3a48..17275c58ae6444dd923ba6b4d1e8b77fac288cc1 100644
--- a/include/asterisk/stream.h
+++ b/include/asterisk/stream.h
@@ -166,6 +166,17 @@ void ast_stream_set_type(struct ast_stream *stream, enum ast_media_type type);
  */
 struct ast_format_cap *ast_stream_get_formats(const struct ast_stream *stream);
 
+/*!
+ * \brief Get the count of the current negotiated formats of a stream
+ *
+ * \param stream The media stream
+ *
+ * \return The count of negotiated formats
+ *
+ * \since 18
+ */
+int ast_stream_get_format_count(const struct ast_stream *stream);
+
 /*!
  * \brief Set the current negotiated formats of a stream
  *
diff --git a/main/stream.c b/main/stream.c
index 626fa3a9caaf4ff48d2be3bf2b4206abe9dfcc21..41b7948d3f9a46e2b770f20f9174cd2c020c13d8 100644
--- a/main/stream.c
+++ b/main/stream.c
@@ -186,6 +186,13 @@ struct ast_format_cap *ast_stream_get_formats(const struct ast_stream *stream)
 	return stream->formats;
 }
 
+int ast_stream_get_format_count(const struct ast_stream *stream)
+{
+	ast_assert(stream != NULL);
+
+	return stream->formats ? ast_format_cap_count(stream->formats) : 0;
+}
+
 void ast_stream_set_formats(struct ast_stream *stream, struct ast_format_cap *caps)
 {
 	ast_assert(stream != NULL);
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 4d77a6d658343937f40710db48a75282febf2d8b..12e41cc868530710ad71211568b5112794dc2b04 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -923,25 +923,75 @@
 					</para></description>
 				</configOption>
 				<configOption name="preferred_codec_only" default="no">
-					<synopsis>Respond to a SIP invite with the single most preferred codec rather than advertising all joint codec capabilities. This limits the other side's codec choice to exactly what we prefer.</synopsis>
+					<synopsis>Respond to a SIP invite with the single most preferred codec (DEPRECATED)</synopsis>
+					<description><para>Respond to a SIP invite with the single most preferred codec
+					rather than advertising all joint codec capabilities. This limits the other side's codec
+					choice to exactly what we prefer.</para>
+					<warning><para>This option has been deprecated in favor of
+					<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>After receiving an incoming offer create a list of preferred codecs between
-					those received in the SDP offer, and those specified in endpoint configuration.</synopsis>
+					<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>
-						<note><para>This list will consist of only those codecs found in both.</para></note>
+						<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>
-								Order by the endpoint configuration allow line (default)
+								Include all codecs in the local list that are also in the remote list
+								preserving the local order.
 							</para></enum>
-							<enum name="local_single"><para>
-								Order by the endpoint configuration allow line, but the list will only contain the first, or 'top' item
+							<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>
-								Order by what is received in the SDP offer
+								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_single"><para>
-								Order by what is received in the SDP offer, but the list will only contain the first, or 'top' item
+							<enum name="remote_first"><para>
+								Include only the first codec in the remote list.
 							</para></enum>
 						</enumlist>
 					</description>
@@ -5044,6 +5094,29 @@ int ast_sip_str_to_dtmf(const char * dtmf_mode)
 	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;
+}
+
 /*!
  * \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 1d615588bf745966b112babfc6db0f1aeea30723..5a4842b159e5c0c41fe3f0dcaedf4f38e5240752 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1121,43 +1121,57 @@ static int contact_user_to_str(const void *obj, const intptr_t *args, char **buf
 	return 0;
 }
 
-static const char *sip_call_codec_pref_strings[] = {
-	[AST_SIP_CALL_CODEC_PREF_LOCAL] = "local",
-	[AST_SIP_CALL_CODEC_PREF_LOCAL_LIMIT] = "local_limit",
-	[AST_SIP_CALL_CODEC_PREF_LOCAL_SINGLE] = "local_single",
-	[AST_SIP_CALL_CODEC_PREF_REMOTE] = "remote",
-	[AST_SIP_CALL_CODEC_PREF_REMOTE_LIMIT] = "remote_limit",
-	[AST_SIP_CALL_CODEC_PREF_REMOTE_SINGLE] = "remote_single",
-};
-
-static int incoming_call_offer_pref_handler(const struct aco_option *opt,
+static int call_offer_pref_handler(const struct aco_option *opt,
 	struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
-	unsigned int i;
-
-	for (i = 0; i < ARRAY_LEN(sip_call_codec_pref_strings); ++i) {
-		if (!strcmp(var->value, sip_call_codec_pref_strings[i])) {
-			/* Local and remote limit are not available values for this option */
-			if (i == AST_SIP_CALL_CODEC_PREF_LOCAL_LIMIT ||
-				i == AST_SIP_CALL_CODEC_PREF_REMOTE_LIMIT) {
-				return -1;
-			}
+	struct ast_flags pref = { 0, };
+	int outgoing = strcmp(var->name, "outgoing_call_offer_pref") == 0;
+
+	if (strcmp(var->value, "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 (outgoing && strcmp(var->value, "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(var->value, "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(var->value, "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 (outgoing && strcmp(var->value, "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(var->value, "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;
+	}
 
-			endpoint->media.incoming_call_offer_pref = i;
-			return 0;
-		}
+	if (outgoing) {
+		endpoint->media.outgoing_call_offer_pref = pref;
+	} else {
+		endpoint->media.incoming_call_offer_pref = pref;
 	}
 
-	return -1;
+	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;
 
-	if (ARRAY_IN_BOUNDS(endpoint->media.incoming_call_offer_pref, sip_call_codec_pref_strings)) {
-		*buf = ast_strdup(sip_call_codec_pref_strings[endpoint->media.incoming_call_offer_pref]);
+	*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;
@@ -1345,6 +1359,16 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
 		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);
+	}
+
 	endpoint->media.topology = ast_stream_topology_create_from_format_cap(endpoint->media.codecs);
 	if (!endpoint->media.topology) {
 		return -1;
@@ -2009,7 +2033,9 @@ int ast_res_pjsip_initialize_configuration(void)
 	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",
-		incoming_call_offer_pref_handler, incoming_call_offer_pref_to_str, NULL, 0, 0);
+		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);
 
 	if (ast_sip_initialize_sorcery_transport()) {
 		ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index bc60d4171db4718c2a6c11a9ab9c7ca0048699fe..bb7c43e6227e161a365837e2ac02778eb12c80ff 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -399,13 +399,13 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp
 
 static int apply_cap_to_bundled(struct ast_sip_session_media *session_media,
 	struct ast_sip_session_media *session_media_transport,
-	struct ast_stream *asterisk_stream, const struct ast_format_cap *joint)
+	struct ast_stream *asterisk_stream, struct ast_format_cap *joint)
 {
 	if (!joint) {
 		return -1;
 	}
 
-	ast_stream_set_formats(asterisk_stream, (struct ast_format_cap *)joint);
+	ast_stream_set_formats(asterisk_stream, joint);
 
 	/* If this is a bundled stream then apply the payloads to RTP instance acting as transport to prevent conflicts */
 	if (session_media_transport != session_media && session_media->bundled) {
@@ -428,11 +428,11 @@ static int apply_cap_to_bundled(struct ast_sip_session_media *session_media,
 	return 0;
 }
 
-static const struct ast_format_cap *set_incoming_call_offer_cap(
+static struct ast_format_cap *set_incoming_call_offer_cap(
 	struct ast_sip_session *session, struct ast_sip_session_media *session_media,
 	const struct pjmedia_sdp_media *stream)
 {
-	const struct ast_format_cap *incoming_call_offer_cap;
+	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;
@@ -448,12 +448,13 @@ static const struct ast_format_cap *set_incoming_call_offer_cap(
 	get_codecs(session, stream, &codecs, session_media);
 	ast_rtp_codecs_payload_formats(&codecs, remote, &fmts);
 
-	incoming_call_offer_cap = ast_sip_session_join_incoming_call_offer_cap(
-		session, session_media, remote);
+	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) {
+	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);
 		return NULL;
 	}
@@ -1413,6 +1414,7 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 	struct ast_sip_session_media *session_media_transport;
 	enum ast_media_type media_type = session_media->type;
 	enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
+	struct ast_format_cap *joint;
 	int res;
 
 	/* If no type formats have been configured reject this stream */
@@ -1504,8 +1506,10 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 		}
 	}
 
-	if (apply_cap_to_bundled(session_media, session_media_transport, asterisk_stream,
-			set_incoming_call_offer_cap(session, session_media, stream))) {
+	joint = set_incoming_call_offer_cap(session, session_media, stream);
+	res = apply_cap_to_bundled(session_media, session_media_transport, asterisk_stream, joint);
+	ao2_cleanup(joint);
+	if (res != 0) {
 		return 0;
 	}
 
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 34d9c0b786a6414f62158ea69748eb850a74c8d9..3a7d562c050da52b8faef335b9a32e9d53f4eea8 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -468,8 +468,6 @@ static void session_media_dtor(void *obj)
 
 	ast_free(session_media->mid);
 	ast_free(session_media->remote_mslabel);
-
-	ao2_cleanup(session_media->caps);
 }
 
 struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,
@@ -528,12 +526,6 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses
 		} else {
 			session_media->bundle_group = -1;
 		}
-
-		session_media->caps = ast_sip_session_caps_alloc();
-		if (!session_media->caps) {
-			ao2_ref(session_media, -1);
-			return NULL;
-		}
 	}
 
 	if (AST_VECTOR_REPLACE(&media_state->sessions, position, session_media)) {
@@ -2701,6 +2693,8 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint
 		return NULL;
 	}
 	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) {
@@ -2709,8 +2703,6 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint
 
 		for (i = 0; i < ast_stream_topology_get_count(req_topology); ++i) {
 			struct ast_stream *req_stream;
-			struct ast_format_cap *req_cap;
-			struct ast_format_cap *joint_cap;
 			struct ast_stream *clone_stream;
 
 			req_stream = ast_stream_topology_get_stream(req_topology, i);
@@ -2719,39 +2711,12 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint
 				continue;
 			}
 
-			req_cap = ast_stream_get_formats(req_stream);
-
-			joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-			if (!joint_cap) {
-				continue;
-			}
-
-			ast_format_cap_get_compatible(req_cap, endpoint->media.codecs, joint_cap);
-
-			if (ast_stream_get_type(req_stream) == AST_MEDIA_TYPE_AUDIO) {
-				/*
-				 * By appending codecs from the endpoint after compatible ones this
-				 * guarantees that priority is given to those while also allowing
-				 * translation to occur for non-compatible.
-				 */
-				ast_format_cap_append_from_cap(joint_cap,
-					endpoint->media.codecs, AST_MEDIA_TYPE_AUDIO);
-			}
-
-			if (!ast_format_cap_count(joint_cap)) {
-				ao2_ref(joint_cap, -1);
-				continue;
-			}
-
-			clone_stream = ast_stream_clone(req_stream, NULL);
-			if (!clone_stream) {
-				ao2_ref(joint_cap, -1);
+			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;
 			}
 
-			ast_stream_set_formats(clone_stream, joint_cap);
-			ao2_ref(joint_cap, -1);
-
 			if (!session->pending_media_state->topology) {
 				session->pending_media_state->topology = ast_stream_topology_alloc();
 				if (!session->pending_media_state->topology) {
@@ -3351,6 +3316,7 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
 #endif
 		return;
 	}
+	session->call_direction = AST_SIP_SESSION_INCOMING_CALL;
 
 	/*
 	 * The current thread is supposed be the session serializer to prevent
diff --git a/res/res_pjsip_session/pjsip_session_caps.c b/res/res_pjsip_session/pjsip_session_caps.c
index e131200bce0817d6a205cf6ad5bcee27bb22c123..7aa4c1fd49f7a525227b414692162cf1b3f3c742 100644
--- a/res/res_pjsip_session/pjsip_session_caps.c
+++ b/res/res_pjsip_session/pjsip_session_caps.c
@@ -24,25 +24,24 @@
 #include "asterisk/format_cap.h"
 #include "asterisk/logger.h"
 #include "asterisk/sorcery.h"
-
-#include <pjsip_ua.h>
-
+#include "asterisk/stream.h"
 #include "asterisk/res_pjsip.h"
 #include "asterisk/res_pjsip_session.h"
 #include "asterisk/res_pjsip_session_caps.h"
 
-struct ast_sip_session_caps {
-	struct ast_format_cap *incoming_call_offer_cap;
-};
-
 static void log_caps(int level, const char *file, int line, const char *function,
-	const char *msg, const struct ast_sip_session *session,
-	const struct ast_sip_session_media *session_media, const struct ast_format_cap *local,
-	const struct ast_format_cap *remote, const struct ast_format_cap *joint)
+	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;
@@ -52,93 +51,57 @@ static void log_caps(int level, const char *file, int line, const char *function
 	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' %s '%s' capabilities -%s%s%s%s%s%s\n",
+	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),
-		msg ? msg : "-", ast_codec_media_type2str(session_media->type),
-		s1 ? " local: " : "", s1 ? ast_format_cap_get_names(local, &s1) : "",
-		s2 ? " remote: " : "", s2 ? ast_format_cap_get_names(remote, &s2) : "",
-		s3 ? " joint: " : "", s3 ? ast_format_cap_get_names(joint, &s3) : "");
+		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)");
 }
 
-static void sip_session_caps_destroy(void *obj)
+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_sip_session_caps *caps = obj;
-
-	ao2_cleanup(caps->incoming_call_offer_cap);
-}
-
-struct ast_sip_session_caps *ast_sip_session_caps_alloc(void)
-{
-	return ao2_alloc_options(sizeof(struct ast_sip_session_caps),
-		sip_session_caps_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
-}
-
-void ast_sip_session_set_incoming_call_offer_cap(struct ast_sip_session_caps *caps,
-	struct ast_format_cap *cap)
-{
-	ao2_cleanup(caps->incoming_call_offer_cap);
-	caps->incoming_call_offer_cap = ao2_bump(cap);
-}
-
-const struct ast_format_cap *ast_sip_session_get_incoming_call_offer_cap(
-	const struct ast_sip_session_caps *caps)
-{
-	return caps->incoming_call_offer_cap;
-}
-
-const struct ast_format_cap *ast_sip_session_join_incoming_call_offer_cap(
-	const struct ast_sip_session *session, const struct ast_sip_session_media *session_media,
-	const struct ast_format_cap *remote)
-{
-	enum ast_sip_call_codec_pref pref;
-	struct ast_format_cap *joint;
-	struct ast_format_cap *local;
-
-	joint = session_media->caps->incoming_call_offer_cap;
-
-	if (joint) {
-		/*
-		 * If the incoming call offer capabilities have been set elsewhere, e.g. dialplan
-		 * then those take precedence.
-		 */
-		return joint;
-	}
-
-	joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-	local = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-
-	if (!joint || !local) {
-		ast_log(LOG_ERROR, "Failed to allocate %s incoming call offer capabilities\n",
-				ast_codec_media_type2str(session_media->type));
+	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);
+		ao2_cleanup(local_filtered);
 		return NULL;
 	}
 
-	pref = session->endpoint->media.incoming_call_offer_pref;
-	ast_format_cap_append_from_cap(local, session->endpoint->media.codecs,
-		session_media->type);
+	ast_format_cap_append_from_cap(local_filtered, local, media_type);
 
-	if (pref < AST_SIP_CALL_CODEC_PREF_REMOTE) {
-		ast_format_cap_get_compatible(local, remote, joint); /* Prefer local */
+	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 {
-		ast_format_cap_get_compatible(remote, local, joint); /* Prefer remote */
+		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 */
+		}
 	}
 
-	if (ast_format_cap_empty(joint)) {
-		log_caps(LOG_NOTICE, "No joint incoming", session, session_media, local, remote, NULL);
+	ao2_ref(local_filtered, -1);
 
-		ao2_ref(joint, -1);
-		ao2_ref(local, -1);
-		return NULL;
+	if (ast_format_cap_empty(joint)) {
+		return joint;
 	}
 
-	if (pref == AST_SIP_CALL_CODEC_PREF_LOCAL_SINGLE ||
-		pref == AST_SIP_CALL_CODEC_PREF_REMOTE_SINGLE ||
-		session->endpoint->preferred_codec_only) {
-
+	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
@@ -152,11 +115,41 @@ const struct ast_format_cap *ast_sip_session_join_incoming_call_offer_cap(
 		ao2_ref(single, -1);
 	}
 
-	log_caps(LOG_DEBUG, "Joint incoming", session, session_media, local, remote, joint);
+	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);
+	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);
 
-	ao2_ref(local, -1);
+	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);
 
-	ast_sip_session_set_incoming_call_offer_cap(session_media->caps, joint);
+	log_caps(LOG_DEBUG, session, media_type, session->endpoint->media.codecs, remote, joint);
 
 	return joint;
 }