diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 12d8f7a395..a66c619378 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -979,7 +979,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 2ae806e0e3..876bb1cfb6 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -986,6 +986,8 @@ struct ast_sip_endpoint { AST_STRING_FIELD_EXTENDED(geoloc_outgoing_call_profile); /*! 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 92d5631a80..9fa6ae6137 100644 --- a/res/res_pjsip/pjsip_config.xml +++ b/res/res_pjsip/pjsip_config.xml @@ -1474,6 +1474,27 @@ + + 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 93bf9898b7..d514aa427e 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -2228,6 +2228,8 @@ int ast_res_pjsip_initialize_configuration(void) ast_sorcery_object_field_register(sip_sorcery, "endpoint", "geoloc_incoming_call_profile", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, geoloc_incoming_call_profile)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "geoloc_outgoing_call_profile", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, geoloc_outgoing_call_profile)); 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 d89a37d198..dc13c5a6b9 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -2179,14 +2179,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)