summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--channels/chan_pjsip.c53
-rw-r--r--channels/pjsip/dialplan_functions.c2
-rw-r--r--configs/samples/pjsip.conf.sample2
-rw-r--r--contrib/ast-db-manage/config/versions/339e1dfa644d_add_moh_passthrough_option_to_pjsip.py30
-rw-r--r--include/asterisk/res_pjsip.h2
-rw-r--r--include/asterisk/res_pjsip_session.h6
-rw-r--r--res/res_pjsip.c6
-rw-r--r--res/res_pjsip/pjsip_configuration.c1
-rw-r--r--res/res_pjsip_sdp_rtp.c11
9 files changed, 103 insertions, 10 deletions
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index f200a05d3..a37258a69 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 6c0aff30b..6cc88017a 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 d6932e38c..017aa5912 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 000000000..d3ee2e518
--- /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 1fe0b040e..1c21c1ee4 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 d50b43179..887d52a1a 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 b350b7b77..dcf771bb3 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 dabbfaed8..798066777 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 1f863008f..74c980d39 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;