From ac091d41844a9a4a0f7d539164bcd154351b6da7 Mon Sep 17 00:00:00 2001 From: Joshua Colp <jcolp@digium.com> Date: Mon, 3 Nov 2014 14:45:01 +0000 Subject: [PATCH] chan_pjsip: Add support for passing hold and unhold requests through. This change adds an option, moh_passthrough, that when enabled will pass hold and unhold requests through using a SIP re-invite. When placing on hold a re-invite with sendonly will be sent and when taking off hold a re-invite with sendrecv will be sent. This allows remote servers to handle the musiconhold instead of the local Asterisk instance being responsible. Review: https://reviewboard.asterisk.org/r/4103/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@427112 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- channels/chan_pjsip.c | 53 ++++++++++++++++++- channels/pjsip/dialplan_functions.c | 2 +- configs/samples/pjsip.conf.sample | 2 + ...44d_add_moh_passthrough_option_to_pjsip.py | 30 +++++++++++ include/asterisk/res_pjsip.h | 2 + include/asterisk/res_pjsip_session.h | 6 ++- res/res_pjsip.c | 6 +++ res/res_pjsip/pjsip_configuration.c | 1 + res/res_pjsip_sdp_rtp.c | 11 ++-- 9 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 contrib/ast-db-manage/config/versions/339e1dfa644d_add_moh_passthrough_option_to_pjsip.py diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index f200a05d34..a37258a695 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -1097,6 +1097,39 @@ static int update_connected_line_information(void *data) return 0; } +/*! \brief Callback which changes the value of locally held on the media stream */ +static int local_hold_set_state(void *obj, void *arg, int flags) +{ + struct ast_sip_session_media *session_media = obj; + unsigned int *held = arg; + + session_media->locally_held = *held; + + return 0; +} + +/*! \brief Update local hold state and send a re-INVITE with the new SDP */ +static int remote_send_hold_refresh(struct ast_sip_session *session, unsigned int held) +{ + ao2_callback(session->media, OBJ_NODATA, local_hold_set_state, &held); + ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1); + ao2_ref(session, -1); + + return 0; +} + +/*! \brief Update local hold state to be held */ +static int remote_send_hold(void *data) +{ + return remote_send_hold_refresh(data, 1); +} + +/*! \brief Update local hold state to be unheld */ +static int remote_send_unhold(void *data) +{ + return remote_send_hold_refresh(data, 0); +} + /*! \brief Function called by core to ask the channel to indicate some sort of condition */ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen) { @@ -1219,7 +1252,15 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi device_buf = alloca(device_buf_size); ast_channel_get_device_name(ast, device_buf, device_buf_size); ast_devstate_changed_literal(AST_DEVICE_ONHOLD, 1, device_buf); - ast_moh_start(ast, data, NULL); + if (!channel->session->endpoint->moh_passthrough) { + ast_moh_start(ast, data, NULL); + } else { + if (ast_sip_push_task(channel->session->serializer, remote_send_hold, ao2_bump(channel->session))) { + ast_log(LOG_WARNING, "Could not queue task to remotely put session '%s' on hold with endpoint '%s'\n", + ast_sorcery_object_get_id(channel->session), ast_sorcery_object_get_id(channel->session->endpoint)); + ao2_ref(channel->session, -1); + } + } break; case AST_CONTROL_UNHOLD: chan_pjsip_remove_hold(ast_channel_uniqueid(ast)); @@ -1227,7 +1268,15 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi device_buf = alloca(device_buf_size); ast_channel_get_device_name(ast, device_buf, device_buf_size); ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, 1, device_buf); - ast_moh_stop(ast); + if (!channel->session->endpoint->moh_passthrough) { + ast_moh_stop(ast); + } else { + if (ast_sip_push_task(channel->session->serializer, remote_send_unhold, ao2_bump(channel->session))) { + ast_log(LOG_WARNING, "Could not queue task to remotely take session '%s' off hold with endpoint '%s'\n", + ast_sorcery_object_get_id(channel->session), ast_sorcery_object_get_id(channel->session->endpoint)); + ao2_ref(channel->session, -1); + } + } break; case AST_CONTROL_SRCUPDATE: break; diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c index 6c0aff30b1..6cc88017a7 100644 --- a/channels/pjsip/dialplan_functions.c +++ b/channels/pjsip/dialplan_functions.c @@ -434,7 +434,7 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch } else if (!strcmp(type, "secure")) { snprintf(buf, buflen, "%d", media->srtp ? 1 : 0); } else if (!strcmp(type, "hold")) { - snprintf(buf, buflen, "%d", media->held ? 1 : 0); + snprintf(buf, buflen, "%d", media->remotely_held ? 1 : 0); } else { ast_log(AST_LOG_WARNING, "Unknown type field '%s' specified for 'rtp' information\n", type); return -1; diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index d6932e38c6..017aa59129 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -616,6 +616,8 @@ ; (default: "user") ;mailboxes= ; Mailbox es to be associated with (default: "") ;moh_suggest=default ; Default Music On Hold class (default: "default") +;moh_passthrough=yes ; Pass Music On Hold through using SIP re-invites with sendonly + ; when placing on hold and sendrecv when taking off hold ;outbound_auth= ; Authentication object used for outbound requests (default: ; "") ;outbound_proxy= ; Proxy through which to send requests a full SIP URI diff --git a/contrib/ast-db-manage/config/versions/339e1dfa644d_add_moh_passthrough_option_to_pjsip.py b/contrib/ast-db-manage/config/versions/339e1dfa644d_add_moh_passthrough_option_to_pjsip.py new file mode 100644 index 0000000000..d3ee2e518e --- /dev/null +++ b/contrib/ast-db-manage/config/versions/339e1dfa644d_add_moh_passthrough_option_to_pjsip.py @@ -0,0 +1,30 @@ +"""Add moh_passthrough option to pjsip + +Revision ID: 339e1dfa644d +Revises: 1443687dda65 +Create Date: 2014-10-21 14:55:34.197448 + +""" + +# revision identifiers, used by Alembic. +revision = '339e1dfa644d' +down_revision = '1443687dda65' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import ENUM + +YESNO_NAME = 'yesno_values' +YESNO_VALUES = ['yes', 'no'] + +def upgrade(): + ############################# Enums ############################## + + # yesno_values have already been created, so use postgres enum object + # type to get around "already created" issue - works okay with mysql + yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False) + + op.add_column('ps_endpoints', sa.Column('moh_passthrough', yesno_values)) + +def downgrade(): + op.drop_column('ps_endpoints', 'moh_passthrough') diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 1fe0b040e5..1c21c1ee46 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -609,6 +609,8 @@ struct ast_sip_endpoint { struct ast_variable *channel_vars; /*! Whether to place a 'user=phone' parameter into the request URI if user is a number */ unsigned int usereqphone; + /*! Whether to pass through hold and unhold using re-invites with recvonly and sendrecv */ + unsigned int moh_passthrough; }; /*! diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h index d50b431792..887d52a1a8 100644 --- a/include/asterisk/res_pjsip_session.h +++ b/include/asterisk/res_pjsip_session.h @@ -75,8 +75,10 @@ struct ast_sip_session_media { struct ast_sdp_srtp *srtp; /*! \brief The media transport in use for this stream */ pj_str_t transport; - /*! \brief Stream is on hold */ - unsigned int held:1; + /*! \brief Stream is on hold by remote side */ + unsigned int remotely_held:1; + /*! \brief Stream is on hold by local side */ + unsigned int locally_held:1; /*! \brief Stream type this session media handles */ char stream_type[1]; }; diff --git a/res/res_pjsip.c b/res/res_pjsip.c index b350b7b775..dcf771bb33 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -576,6 +576,9 @@ <configOption name="user_eq_phone" default="no"> <synopsis>Determines whether a user=phone parameter is placed into the request URI if the user is determined to be a phone number</synopsis> </configOption> + <configOption name="moh_passthrough" default="no"> + <synopsis>Determines whether hold and unhold will be passed through using re-INVITEs with recvonly and sendrecv to the remote side</synopsis> + </configOption> <configOption name="sdp_owner" default="-"> <synopsis>String placed as the username portion of an SDP origin (o=) line.</synopsis> </configOption> @@ -1560,6 +1563,9 @@ <parameter name="UserEqPhone"> <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='user_eq_phone']/synopsis/node())"/></para> </parameter> + <parameter name="MohPassthrough"> + <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='moh_passthrough']/synopsis/node())"/></para> + </parameter> <parameter name="SdpOwner"> <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='sdp_owner']/synopsis/node())"/></para> </parameter> diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index dabbfaed8a..7980667774 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -1733,6 +1733,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod ast_sorcery_object_field_register(sip_sorcery, "endpoint", "record_off_feature", "automixmon", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, info.recording.offfeature)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_transfer", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allowtransfer)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "user_eq_phone", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, usereqphone)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "moh_passthrough", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, moh_passthrough)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sdp_owner", "-", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.sdpowner)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sdp_session", "Asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.sdpsession)); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_audio", "0", tos_handler, tos_audio_to_str, NULL, 0, 0); diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index 1f863008f5..74c980d39a 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -887,6 +887,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as static const pj_str_t STR_IP4 = { "IP4", 3}; static const pj_str_t STR_IP6 = { "IP6", 3}; static const pj_str_t STR_SENDRECV = { "sendrecv", 8 }; + static const pj_str_t STR_SENDONLY = { "sendonly", 8 }; pjmedia_sdp_media *media; char hostip[PJ_INET6_ADDRSTRLEN+2]; struct ast_sockaddr addr; @@ -1046,7 +1047,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as /* Add the sendrecv attribute - we purposely don't keep track because pjmedia-sdp will automatically change our offer for us */ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); - attr->name = STR_SENDRECV; + attr->name = !session_media->locally_held ? STR_SENDRECV : STR_SENDONLY; media->attr[media->attr_count++] = attr; /* Add the media stream to the SDP */ @@ -1122,18 +1123,18 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a if (ast_sockaddr_isnull(addrs) || ast_sockaddr_is_any(addrs) || pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL)) { - if (!session_media->held) { + 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); - session_media->held = 1; + session_media->remotely_held = 1; } - } else if (session_media->held) { + } else if (session_media->remotely_held) { /* The remote side has taken us off hold */ ast_queue_unhold(session->channel); ast_queue_frame(session->channel, &ast_null_frame); - session_media->held = 0; + session_media->remotely_held = 0; } return 1; -- GitLab