mirror of
https://github.com/asterisk/asterisk.git
synced 2025-11-15 22:38:08 +00:00
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/ Change-Id: Ib6294e906e577e1a4245cb1f058d3976ff484c52
This commit is contained in:
committed by
Torrey Searle
parent
ae70c2a428
commit
d775bc5add
@@ -1349,6 +1349,39 @@ static int update_connected_line_information(void *data)
|
|||||||
return 0;
|
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 */
|
/*! \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)
|
static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
|
||||||
{
|
{
|
||||||
@@ -1493,7 +1526,15 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
|
|||||||
device_buf = alloca(device_buf_size);
|
device_buf = alloca(device_buf_size);
|
||||||
ast_channel_get_device_name(ast, device_buf, 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_devstate_changed_literal(AST_DEVICE_ONHOLD, 1, device_buf);
|
||||||
|
if (!channel->session->endpoint->moh_passthrough) {
|
||||||
ast_moh_start(ast, data, NULL);
|
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;
|
break;
|
||||||
case AST_CONTROL_UNHOLD:
|
case AST_CONTROL_UNHOLD:
|
||||||
chan_pjsip_remove_hold(ast_channel_uniqueid(ast));
|
chan_pjsip_remove_hold(ast_channel_uniqueid(ast));
|
||||||
@@ -1501,7 +1542,15 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
|
|||||||
device_buf = alloca(device_buf_size);
|
device_buf = alloca(device_buf_size);
|
||||||
ast_channel_get_device_name(ast, device_buf, 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_devstate_changed_literal(AST_DEVICE_UNKNOWN, 1, device_buf);
|
||||||
|
if (!channel->session->endpoint->moh_passthrough) {
|
||||||
ast_moh_stop(ast);
|
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;
|
break;
|
||||||
case AST_CONTROL_SRCUPDATE:
|
case AST_CONTROL_SRCUPDATE:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -592,7 +592,7 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch
|
|||||||
snprintf(buf, buflen, "%d", 0);
|
snprintf(buf, buflen, "%d", 0);
|
||||||
}
|
}
|
||||||
} else if (!strcmp(type, "hold")) {
|
} else if (!strcmp(type, "hold")) {
|
||||||
snprintf(buf, buflen, "%d", media->held ? 1 : 0);
|
snprintf(buf, buflen, "%d", media->remotely_held ? 1 : 0);
|
||||||
} else {
|
} else {
|
||||||
ast_log(AST_LOG_WARNING, "Unknown type field '%s' specified for 'rtp' information\n", type);
|
ast_log(AST_LOG_WARNING, "Unknown type field '%s' specified for 'rtp' information\n", type);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -655,6 +655,8 @@
|
|||||||
; An MWI subscribe will replace unsoliticed NOTIFYs
|
; An MWI subscribe will replace unsoliticed NOTIFYs
|
||||||
; (default: "no")
|
; (default: "no")
|
||||||
;moh_suggest=default ; Default Music On Hold class (default: "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_auth= ; Authentication object used for outbound requests (default:
|
||||||
; "")
|
; "")
|
||||||
;outbound_proxy= ; Proxy through which to send requests, a full SIP URI
|
;outbound_proxy= ; Proxy through which to send requests, a full SIP URI
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""Add moh_passthrough option to pjsip
|
||||||
|
|
||||||
|
Revision ID: 339e1dfa644d
|
||||||
|
Revises: 3a094a18e75b
|
||||||
|
Create Date: 2014-10-21 14:55:34.197448
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '339e1dfa644d'
|
||||||
|
down_revision = '3a094a18e75b'
|
||||||
|
|
||||||
|
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')
|
||||||
@@ -802,6 +802,8 @@ struct ast_sip_endpoint {
|
|||||||
unsigned int send_connected_line;
|
unsigned int send_connected_line;
|
||||||
/*! Ignore 183 if no SDP is present */
|
/*! Ignore 183 if no SDP is present */
|
||||||
unsigned int ignore_183_without_sdp;
|
unsigned int ignore_183_without_sdp;
|
||||||
|
/*! Whether to pass through hold and unhold using re-invites with recvonly and sendrecv */
|
||||||
|
unsigned int moh_passthrough;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*! URI parameter for symmetric transport */
|
/*! URI parameter for symmetric transport */
|
||||||
|
|||||||
@@ -81,8 +81,10 @@ struct ast_sip_session_media {
|
|||||||
int keepalive_sched_id;
|
int keepalive_sched_id;
|
||||||
/*! \brief Scheduler ID for RTP timeout */
|
/*! \brief Scheduler ID for RTP timeout */
|
||||||
int timeout_sched_id;
|
int timeout_sched_id;
|
||||||
/*! \brief Stream is on hold */
|
/*! \brief Stream is on hold by remote side */
|
||||||
unsigned int held:1;
|
unsigned int remotely_held:1;
|
||||||
|
/*! \brief Stream is on hold by local side */
|
||||||
|
unsigned int locally_held:1;
|
||||||
/*! \brief Does remote support rtcp_mux */
|
/*! \brief Does remote support rtcp_mux */
|
||||||
unsigned int remote_rtcp_mux:1;
|
unsigned int remote_rtcp_mux:1;
|
||||||
/*! \brief Does remote support ice */
|
/*! \brief Does remote support ice */
|
||||||
|
|||||||
@@ -744,6 +744,9 @@
|
|||||||
<configOption name="user_eq_phone" default="no">
|
<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>
|
<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>
|
||||||
|
<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="-">
|
<configOption name="sdp_owner" default="-">
|
||||||
<synopsis>String placed as the username portion of an SDP origin (o=) line.</synopsis>
|
<synopsis>String placed as the username portion of an SDP origin (o=) line.</synopsis>
|
||||||
</configOption>
|
</configOption>
|
||||||
@@ -2286,6 +2289,9 @@
|
|||||||
<parameter name="UserEqPhone">
|
<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>
|
<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>
|
||||||
|
<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">
|
<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>
|
<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>
|
</parameter>
|
||||||
|
|||||||
@@ -1858,6 +1858,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", "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", "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", "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_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(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);
|
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_audio", "0", tos_handler, tos_audio_to_str, NULL, 0, 0);
|
||||||
|
|||||||
@@ -1184,6 +1184,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_IP4 = { "IP4", 3};
|
||||||
static const pj_str_t STR_IP6 = { "IP6", 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_SENDRECV = { "sendrecv", 8 };
|
||||||
|
static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
|
||||||
pjmedia_sdp_media *media;
|
pjmedia_sdp_media *media;
|
||||||
const char *hostip = NULL;
|
const char *hostip = NULL;
|
||||||
struct ast_sockaddr addr;
|
struct ast_sockaddr addr;
|
||||||
@@ -1369,7 +1370,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 */
|
/* 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 = 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;
|
media->attr[media->attr_count++] = attr;
|
||||||
|
|
||||||
/* If we've got rtcp-mux enabled, add it unless we received an offer without it */
|
/* If we've got rtcp-mux enabled, add it unless we received an offer without it */
|
||||||
@@ -1471,18 +1472,18 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
|
|||||||
ast_sockaddr_is_any(addrs) ||
|
ast_sockaddr_is_any(addrs) ||
|
||||||
pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL) ||
|
pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL) ||
|
||||||
pjmedia_sdp_media_find_attr2(remote_stream, "inactive", NULL)) {
|
pjmedia_sdp_media_find_attr2(remote_stream, "inactive", NULL)) {
|
||||||
if (!session_media->held) {
|
if (!session_media->remotely_held) {
|
||||||
/* The remote side has put us on hold */
|
/* The remote side has put us on hold */
|
||||||
ast_queue_hold(session->channel, session->endpoint->mohsuggest);
|
ast_queue_hold(session->channel, session->endpoint->mohsuggest);
|
||||||
ast_rtp_instance_stop(session_media->rtp);
|
ast_rtp_instance_stop(session_media->rtp);
|
||||||
ast_queue_frame(session->channel, &ast_null_frame);
|
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 */
|
/* The remote side has taken us off hold */
|
||||||
ast_queue_unhold(session->channel);
|
ast_queue_unhold(session->channel);
|
||||||
ast_queue_frame(session->channel, &ast_null_frame);
|
ast_queue_frame(session->channel, &ast_null_frame);
|
||||||
session_media->held = 0;
|
session_media->remotely_held = 0;
|
||||||
} else if ((pjmedia_sdp_neg_was_answer_remote(session->inv_session->neg) == PJ_FALSE)
|
} else if ((pjmedia_sdp_neg_was_answer_remote(session->inv_session->neg) == PJ_FALSE)
|
||||||
&& (session->inv_session->state == PJSIP_INV_STATE_CONFIRMED)) {
|
&& (session->inv_session->state == PJSIP_INV_STATE_CONFIRMED)) {
|
||||||
ast_queue_control(session->channel, AST_CONTROL_UPDATE_RTP_PEER);
|
ast_queue_control(session->channel, AST_CONTROL_UPDATE_RTP_PEER);
|
||||||
@@ -1513,9 +1514,9 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
|
|||||||
* instance itself.
|
* instance itself.
|
||||||
*/
|
*/
|
||||||
ast_rtp_instance_set_timeout(session_media->rtp, 0);
|
ast_rtp_instance_set_timeout(session_media->rtp, 0);
|
||||||
if (session->endpoint->media.rtp.timeout && !session_media->held) {
|
if (session->endpoint->media.rtp.timeout && !session_media->remotely_held) {
|
||||||
ast_rtp_instance_set_timeout(session_media->rtp, session->endpoint->media.rtp.timeout);
|
ast_rtp_instance_set_timeout(session_media->rtp, session->endpoint->media.rtp.timeout);
|
||||||
} else if (session->endpoint->media.rtp.timeout_hold && session_media->held) {
|
} else if (session->endpoint->media.rtp.timeout_hold && session_media->remotely_held) {
|
||||||
ast_rtp_instance_set_timeout(session_media->rtp, session->endpoint->media.rtp.timeout_hold);
|
ast_rtp_instance_set_timeout(session_media->rtp, session->endpoint->media.rtp.timeout_hold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user