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; }