diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 0e2f283ea0..32af1b2d13 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -1015,7 +1015,21 @@ ; using the same CHANNEL function if needed. Setting tenant ID here ; will cause it to show up on channel creation and the initial ; channel snapshot. - +; +; suppress_moh_on_sendonly = no + ; Normally, when one party in a call sends Asterisk an SDP with + ; a "sendonly" or "inactive" attribute it means "hold" and + ; causes Asterisk to start playing MOH back to the other party. + ; This can be problematic if it happens at certain times, such + ; as in a 183 Progress message, because the MOH will replace + ; any early media you may be playing to the calling party. If + ; you set this option to "yes" on an endpoint and the endpoint + ; receives an SDP with "sendonly" or "inactive", Asterisk will + ; NOT play MOH back to the other party. + ; NOTE: This doesn't just apply to 183 responses. MOH will + ; be suppressed when the attribute appears in any SDP received + ; including INVITEs, re-INVITES, and other responses. + ; (default: no) ;==========================AUTH SECTION OPTIONS========================= ;[auth] diff --git a/contrib/ast-db-manage/config/versions/4f91fc18c979_add_suppress_moh_on_sendonly.py b/contrib/ast-db-manage/config/versions/4f91fc18c979_add_suppress_moh_on_sendonly.py new file mode 100644 index 0000000000..fb6a3efc71 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/4f91fc18c979_add_suppress_moh_on_sendonly.py @@ -0,0 +1,30 @@ +"""Add suppress_moh_on_sendonly + +Revision ID: 4f91fc18c979 +Revises: 801b9fced8b7 +Create Date: 2024-11-05 11:37:33.604448 + +""" + +# revision identifiers, used by Alembic. +revision = '4f91fc18c979' +down_revision = '801b9fced8b7' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import ENUM + +YESNO_NAME = 'yesno_values' +YESNO_VALUES = ['yes', 'no'] + + +def upgrade(): + yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False) + + op.add_column('ps_endpoints', sa.Column('suppress_moh_on_sendonly', yesno_values)) + + +def downgrade(): + if op.get_context().bind.dialect.name == 'mssql': + op.drop_constraint('ck_ps_endpoints_suppress_moh_on_sendonly_yesno_values', 'ps_endpoints') + op.drop_column('ps_endpoints', 'suppress_moh_on_sendonly') diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 46e376325c..cdc71a5f74 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -1069,6 +1069,8 @@ struct ast_sip_endpoint { unsigned int send_aoc; /*! Tenant ID for the endpoint */ AST_STRING_FIELD_EXTENDED(tenantid); + /*! Ignore remote hold requests */ + int suppress_moh_on_sendonly; }; /*! URI parameter for symmetric transport */ diff --git a/res/res_pjsip/pjsip_config.xml b/res/res_pjsip/pjsip_config.xml index ab1c6332f3..71c73dc2ae 100644 --- a/res/res_pjsip/pjsip_config.xml +++ b/res/res_pjsip/pjsip_config.xml @@ -1533,6 +1533,27 @@ Send Advice-of-Charge messages + + Suppress playing MOH to party A if party B sends + "sendonly" or "inactive" in an SDP + + Normally, when one party in a call sends Asterisk an SDP with a "sendonly" + or "inactive" attribute it means "hold" and causes Asterisk to start + playing MOH back to the other party. This can be problematic if it happens at + certain times, such as in a 183 Progress message, because the MOH will + replace any early media you may be playing to the calling party. If you set + this option to "yes" on an endpoint and the endpoint receives an SDP + with "sendonly" or "inactive", Asterisk will NOT play MOH back to the other + party. + + + This doesn't just apply to 183 responses. MOH will be suppressed when + the attribute appears in any SDP received including INVITEs, re-INVITES, + and other responses. + + + + Authentication type diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index c6f4d83b38..8ac1d2c240 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -2304,6 +2304,8 @@ int ast_res_pjsip_initialize_configuration(void) ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "security_negotiation", "no", security_negotiation_handler, security_negotiation_to_str, NULL, 0, 0); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_aoc", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_aoc)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "tenantid", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, tenantid)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "suppress_moh_on_sendonly", + "no", OPT_YESNO_T, 1, FLDSET(struct ast_sip_endpoint, suppress_moh_on_sendonly)); 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 4423dfda5c..7d2a5e7c4a 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -2321,14 +2321,18 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, if (session_media->remotely_held_changed) { if (session_media->remotely_held) { /* The remote side has put us on hold */ - ast_queue_hold(session->channel, session->endpoint->mohsuggest); - ast_rtp_instance_stop(session_media->rtp); - ast_queue_frame(session->channel, &ast_null_frame); + if (!session->endpoint->suppress_moh_on_sendonly) { + ast_queue_hold(session->channel, session->endpoint->mohsuggest); + ast_rtp_instance_stop(session_media->rtp); + ast_queue_frame(session->channel, &ast_null_frame); + } session_media->remotely_held_changed = 0; } else { /* The remote side has taken us off hold */ - ast_queue_unhold(session->channel); - ast_queue_frame(session->channel, &ast_null_frame); + if (!session->endpoint->suppress_moh_on_sendonly) { + ast_queue_unhold(session->channel); + ast_queue_frame(session->channel, &ast_null_frame); + } session_media->remotely_held_changed = 0; } } else if ((pjmedia_sdp_neg_was_answer_remote(session->inv_session->neg) == PJ_FALSE)