diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index a559dfafaa..cdb585d2a0 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -839,6 +839,54 @@ ; 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 + ; specified on an incoming SDP offer (pending) are + ; reconciled with the codecs specified on an endpoint + ; (configured) before being sent to the Asterisk core. + ; The string actually specifies 4 name:value pair + ; parameters separated by commas. Whitespace is + ; ignored and they may be specified in any order. + ; prefer: , + ; operation: , + ; keep: , + ; transcode: +;outgoing_offer_codec_prefs=; 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) + ; when sending an SDP offer. + ; The string actually specifies 4 name:value pair + ; parameters separated by commas. Whitespace is + ; ignored and they may be specified in any order. + ; prefer: , + ; operation: , + ; keep: , + ; transcode: +;incoming_answer_codec_prefs=; 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 + ; answer. + ; The string actually specifies 4 name:value pair + ; parameters separated by commas. Whitespace is + ; ignored and they may be specified in any order. + ; prefer: , + ; operation: , + ; keep: +;outgoing_answer_codec_prefs=; 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. + ; The string actually specifies 4 name:value pair + ; parameters separated by commas. Whitespace is + ; ignored and they may be specified in any order. + ; prefer: , + ; operation: , + ; keep: ;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 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 new file mode 100644 index 0000000000..241185a6a8 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py @@ -0,0 +1,29 @@ +"""Add pjsip endpoint ACN options + +Revision ID: b80485ff4dd0 +Revises: fbb7766f17bc +Create Date: 2020-07-06 08:29:53.974820 + +""" + +# revision identifiers, used by Alembic. +revision = 'b80485ff4dd0' +down_revision = '79290b511e4b' + +from alembic import op +import sqlalchemy as sa + +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))) + + +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') diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 299310312b..0ca29ff5d8 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -49,6 +49,7 @@ /* Needed for ast_sip_for_each_channel_snapshot struct */ #include "asterisk/stasis_channels.h" #include "asterisk/stasis_endpoints.h" +#include "asterisk/stream.h" #define PJSIP_MINVERSION(m,n,p) (((m << 24) | (n << 16) | (p << 8)) >= PJ_VERSION_NUM) @@ -802,6 +803,14 @@ struct ast_sip_endpoint_media_configuration { 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 */ + struct ast_stream_codec_negotiation_prefs outgoing_offer_codec_prefs; + /*! Codec negotiation prefs for incoming answers */ + struct ast_stream_codec_negotiation_prefs incoming_answer_codec_prefs; + /*! Codec negotiation prefs for outgoing answers */ + struct ast_stream_codec_negotiation_prefs outgoing_answer_codec_prefs; }; /*! diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 32907e307c..847a14c886 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -102,6 +102,239 @@ Media Codec(s) to allow + + Codec negotiation prefs for incoming offers. + + + 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. + The string actually specifies 4 name:value pair parameters + separated by commas. Whitespace is ignored and they may be specified in any order. + + + + Parameters: + + + + + + + The codec list from the caller. (default) + The codec list from the endpoint. + + + + + + + Only common codecs with the preferred codecs first. (default) + Use only the preferred codecs. + Use only the non-preferred codecs. + + + + + + + After the operation, keep all codecs. (default) + After the operation, keep only the first codec. + + + + + + + Allow transcoding. (default) + Prevent transcoding. + + + + + + + incoming_offer_codec_prefs = prefer: pending, operation: intersect, keep: all, transcode: allow + + + Prefer the codecs coming from the caller. Use only the ones that are common. + keeping the order of the preferred list. Keep all codecs in the result. Allow transcoding. + + + + + Codec negotiation prefs for outgoing offers. + + + 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) when sending an SDP offer. + The string actually specifies 4 name:value pair parameters + separated by commas. Whitespace is ignored and they may be specified in any order. + + + + Parameters: + + + + + + + The codec list from the core. (default) + The codec list from the endpoint. + + + + + + + Merge the lists with the preferred codecs first. (default) + Only common codecs with the preferred codecs first. (default) + Use only the preferred codecs. + Use only the non-preferred codecs. + + + + + + + After the operation, keep all codecs. (default) + After the operation, keep only the first codec. + + + + + + + Allow transcoding. (default) + Prevent transcoding. + + + + + + + outgoing_offer_codec_prefs = prefer: configured, operation: union, keep: first, transcode: prevent + + + Prefer the codecs coming from the endpoint. Merge them with the codecs from the core + keeping the order of the preferred list. Keep only the first one. No transcoding allowed. + + + + + Codec negotiation prefs for incoming answers. + + + 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 answer. + The string actually specifies 4 name:value pair parameters + separated by commas. Whitespace is ignored and they may be specified in any order. + + + Parameters: + + + + + + + The codec list in the received SDP answer. (default) + The codec list from the endpoint. + + + + + + + Merge the lists with the preferred codecs first. + Only common codecs with the preferred codecs first. (default) + Use only the preferred codecs. + Use only the non-preferred codecs. + + + + + + + After the operation, keep all codecs. (default) + After the operation, keep only the first codec. + + + + + The transcode parameter is ignored when processing answers. + + + + + + + incoming_answer_codec_prefs = keep: first + + + Use the defaults but keep oinly the first codec. + + + + + Codec negotiation prefs for outgoing answers. + + + 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. + The string actually specifies 4 name:value pair parameters + separated by commas. Whitespace is ignored and they may be specified in any order. + + + Parameters: + + + + + + + The codec list that came from the core. (default) + The codec list from the endpoint. + + + + + + + Merge the lists with the preferred codecs first. + Only common codecs with the preferred codecs first. (default) + Use only the preferred codecs. + Use only the non-preferred codecs. + + + + + + + After the operation, keep all codecs. (default) + After the operation, keep only the first codec. + + + + + The transcode parameter is ignored when processing answers. + + + + + + + incoming_answer_codec_prefs = keep: first + + + Use the defaults but keep oinly the first codec. + + + Enable RFC3578 overlap dialing support. diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index f95ee9e240..e3eab8ad0e 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -1166,6 +1166,109 @@ static int outgoing_call_offer_pref_to_str(const void *obj, const intptr_t *args return 0; } +static int codec_prefs_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + struct ast_stream_codec_negotiation_prefs prefs; + struct ast_str *error_message = ast_str_create(128); + enum ast_stream_codec_negotiation_prefs_prefer_values default_prefer; + enum ast_stream_codec_negotiation_prefs_operation_values default_operation; + int res = 0; + + res = ast_stream_codec_prefs_parse(var->value, &prefs, &error_message); + if (res < 0) { + ast_log(LOG_ERROR, "Endpoint '%s': %s for option '%s'\n", + ast_sorcery_object_get_id(endpoint), ast_str_buffer(error_message), var->name); + ast_free(error_message); + return -1; + } + ast_free(error_message); + + if (strcmp(var->name, "incoming_offer_codec_prefs") == 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), + ast_stream_codec_param_to_str(CODEC_NEGOTIATION_PARAM_OPERATION), + ast_stream_codec_operation_to_str(CODEC_NEGOTIATION_OPERATION_UNION), + 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; + 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; + 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; + default_prefer = CODEC_NEGOTIATION_PREFER_PENDING; + default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT; + } + + if (prefs.prefer == CODEC_NEGOTIATION_PREFER_UNSPECIFIED) { + prefs.prefer = default_prefer; + } + + if (prefs.operation == CODEC_NEGOTIATION_OPERATION_UNSPECIFIED) { + prefs.operation = default_operation; + } + + if (prefs.keep == CODEC_NEGOTIATION_KEEP_UNSPECIFIED) { + prefs.keep = CODEC_NEGOTIATION_KEEP_ALL; + } + + if (prefs.transcode == CODEC_NEGOTIATION_TRANSCODE_UNSPECIFIED) { + prefs.transcode = CODEC_NEGOTIATION_TRANSCODE_ALLOW; + } + + return 0; +} + +static int codec_prefs_to_str(const struct ast_stream_codec_negotiation_prefs *prefs, + const void *obj, const intptr_t *args, char **buf) +{ + struct ast_str *codecs = ast_str_create(AST_STREAM_MAX_CODEC_PREFS_LENGTH); + + if (!codecs) { + return -1; + } + + *buf = ast_strdup(ast_stream_codec_prefs_to_str(prefs, &codecs)); + ast_free(codecs); + + return 0; +} + +static int incoming_offer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_endpoint *endpoint = obj; + return codec_prefs_to_str(&endpoint->media.incoming_offer_codec_prefs, obj, args, buf); +} + +static int outgoing_offer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_endpoint *endpoint = obj; + return codec_prefs_to_str(&endpoint->media.outgoing_offer_codec_prefs, obj, args, buf); +} + +static int incoming_answer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_endpoint *endpoint = obj; + return codec_prefs_to_str(&endpoint->media.incoming_answer_codec_prefs, obj, args, buf); +} + +static int outgoing_answer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_endpoint *endpoint = obj; + return codec_prefs_to_str(&endpoint->media.outgoing_answer_codec_prefs, obj, args, buf); +} + static void *sip_nat_hook_alloc(const char *name) { return ast_sorcery_generic_alloc(sizeof(struct ast_sip_nat_hook), NULL); @@ -2025,6 +2128,18 @@ int ast_res_pjsip_initialize_configuration(void) 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", + "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", + "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", + 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", + codec_prefs_handler, outgoing_answer_codec_prefs_to_str, NULL, 0, 0); if (ast_sip_initialize_sorcery_transport()) { ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");