summaryrefslogtreecommitdiff
path: root/channels
diff options
context:
space:
mode:
Diffstat (limited to 'channels')
-rw-r--r--channels/chan_pjsip.c523
-rw-r--r--channels/pjsip/cli_commands.c37
-rw-r--r--channels/pjsip/dialplan_functions.c185
-rw-r--r--channels/pjsip/include/chan_pjsip.h13
4 files changed, 558 insertions, 200 deletions
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 83dc77f38..7cab42873 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -64,6 +64,7 @@
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
+#include "asterisk/stream.h"
#include "pjsip/include/chan_pjsip.h"
#include "pjsip/include/dialplan_functions.h"
@@ -78,25 +79,22 @@ static unsigned int chan_idx;
static void chan_pjsip_pvt_dtor(void *obj)
{
- struct chan_pjsip_pvt *pvt = obj;
- int i;
-
- for (i = 0; i < SIP_MEDIA_SIZE; ++i) {
- ao2_cleanup(pvt->media[i]);
- pvt->media[i] = NULL;
- }
}
/* \brief Asterisk core interaction functions */
static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
+static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type,
+ struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids,
+ const struct ast_channel *requestor, const char *data, int *cause);
static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text);
static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit);
static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
static int chan_pjsip_call(struct ast_channel *ast, const char *dest, int timeout);
static int chan_pjsip_hangup(struct ast_channel *ast);
static int chan_pjsip_answer(struct ast_channel *ast);
-static struct ast_frame *chan_pjsip_read(struct ast_channel *ast);
+static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast);
static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f);
+static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *f);
static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
static int chan_pjsip_transfer(struct ast_channel *ast, const char *target);
static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
@@ -109,16 +107,17 @@ struct ast_channel_tech chan_pjsip_tech = {
.type = channel_type,
.description = "PJSIP Channel Driver",
.requester = chan_pjsip_request,
+ .requester_with_stream_topology = chan_pjsip_request_with_stream_topology,
.send_text = chan_pjsip_sendtext,
.send_digit_begin = chan_pjsip_digit_begin,
.send_digit_end = chan_pjsip_digit_end,
.call = chan_pjsip_call,
.hangup = chan_pjsip_hangup,
.answer = chan_pjsip_answer,
- .read = chan_pjsip_read,
+ .read_stream = chan_pjsip_read_stream,
.write = chan_pjsip_write,
- .write_video = chan_pjsip_write,
- .exception = chan_pjsip_read,
+ .write_stream = chan_pjsip_write_stream,
+ .exception = chan_pjsip_read_stream,
.indicate = chan_pjsip_indicate,
.transfer = chan_pjsip_transfer,
.fixup = chan_pjsip_fixup,
@@ -159,11 +158,20 @@ static struct ast_sip_session_supplement chan_pjsip_ack_supplement = {
static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt;
struct ast_sip_endpoint *endpoint;
struct ast_datastore *datastore;
+ struct ast_sip_session_media *media;
- if (!channel || !channel->session || !(pvt = channel->pvt) || !pvt->media[SIP_MEDIA_AUDIO]->rtp) {
+ if (!channel || !channel->session) {
+ return AST_RTP_GLUE_RESULT_FORBID;
+ }
+
+ /* XXX Getting the first RTP instance for direct media related stuff seems just
+ * absolutely wrong. But the native RTP bridge knows no other method than single-stream
+ * for direct media. So this is the best we can do.
+ */
+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+ if (!media || !media->rtp) {
return AST_RTP_GLUE_RESULT_FORBID;
}
@@ -175,7 +183,7 @@ static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan
endpoint = channel->session->endpoint;
- *instance = pvt->media[SIP_MEDIA_AUDIO]->rtp;
+ *instance = media->rtp;
ao2_ref(*instance, +1);
ast_assert(endpoint != NULL);
@@ -194,16 +202,21 @@ static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan
static enum ast_rtp_glue_result chan_pjsip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
struct ast_sip_endpoint *endpoint;
+ struct ast_sip_session_media *media;
- if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO]->rtp) {
+ if (!channel || !channel->session) {
+ return AST_RTP_GLUE_RESULT_FORBID;
+ }
+
+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
+ if (!media || !media->rtp) {
return AST_RTP_GLUE_RESULT_FORBID;
}
endpoint = channel->session->endpoint;
- *instance = pvt->media[SIP_MEDIA_VIDEO]->rtp;
+ *instance = media->rtp;
ao2_ref(*instance, +1);
ast_assert(endpoint != NULL);
@@ -265,18 +278,43 @@ static int direct_media_mitigate_glare(struct ast_sip_session *session)
return 0;
}
+/*! \brief Helper function to find the position for RTCP */
+static int rtp_find_rtcp_fd_position(struct ast_sip_session *session, struct ast_rtp_instance *rtp)
+{
+ int index;
+
+ for (index = 0; index < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++index) {
+ struct ast_sip_session_media_read_callback_state *callback_state =
+ AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, index);
+
+ if (callback_state->fd != ast_rtp_instance_fd(rtp, 1)) {
+ continue;
+ }
+
+ return index;
+ }
+
+ return -1;
+}
+
/*!
* \pre chan is locked
*/
static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instance *rtp,
- struct ast_sip_session_media *media, int rtcp_fd)
+ struct ast_sip_session_media *media, struct ast_sip_session *session)
{
- int changed = 0;
+ int changed = 0, position = -1;
+
+ if (media->rtp) {
+ position = rtp_find_rtcp_fd_position(session, media->rtp);
+ }
if (rtp) {
changed = ast_rtp_instance_get_and_cmp_remote_address(rtp, &media->direct_media_addr);
if (media->rtp) {
- ast_channel_set_fd(chan, rtcp_fd, -1);
+ if (position != -1) {
+ ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, -1);
+ }
ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 0);
}
} else if (!ast_sockaddr_isnull(&media->direct_media_addr)){
@@ -284,7 +322,9 @@ static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instan
changed = 1;
if (media->rtp) {
ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 1);
- ast_channel_set_fd(chan, rtcp_fd, ast_rtp_instance_fd(media->rtp, 1));
+ if (position != -1) {
+ ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, ast_rtp_instance_fd(media->rtp, 1));
+ }
}
}
@@ -333,22 +373,27 @@ static int send_direct_media_request(void *data)
{
struct rtp_direct_media_data *cdata = data;
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(cdata->chan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
+ struct ast_sip_session *session;
int changed = 0;
int res = 0;
+ /* XXX In an ideal world each media stream would be direct, but for now preserve behavior
+ * and connect only the default media sessions for audio and video.
+ */
+
/* The channel needs to be locked when checking for RTP changes.
* Otherwise, we could end up destroying an underlying RTCP structure
* at the same time that the channel thread is attempting to read RTCP
*/
ast_channel_lock(cdata->chan);
- if (pvt->media[SIP_MEDIA_AUDIO]) {
+ session = channel->session;
+ if (session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {
changed |= check_for_rtp_changes(
- cdata->chan, cdata->rtp, pvt->media[SIP_MEDIA_AUDIO], 1);
+ cdata->chan, cdata->rtp, session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO], session);
}
- if (pvt->media[SIP_MEDIA_VIDEO]) {
+ if (session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]) {
changed |= check_for_rtp_changes(
- cdata->chan, cdata->vrtp, pvt->media[SIP_MEDIA_VIDEO], 3);
+ cdata->chan, cdata->vrtp, session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO], session);
}
ast_channel_unlock(cdata->chan);
@@ -368,7 +413,7 @@ static int send_direct_media_request(void *data)
if (changed) {
ast_debug(4, "RTP changed on %s; initiating direct media update\n", ast_channel_name(cdata->chan));
res = ast_sip_session_refresh(cdata->session, NULL, NULL, NULL,
- cdata->session->endpoint->media.direct_media.method, 1);
+ cdata->session->endpoint->media.direct_media.method, 1, NULL);
}
ao2_ref(cdata, -1);
@@ -420,14 +465,53 @@ static struct ast_rtp_glue chan_pjsip_rtp_glue = {
.update_peer = chan_pjsip_set_rtp_peer,
};
-static void set_channel_on_rtp_instance(struct chan_pjsip_pvt *pvt, const char *channel_id)
+static void set_channel_on_rtp_instance(const struct ast_sip_session *session,
+ const char *channel_id)
{
- if (pvt->media[SIP_MEDIA_AUDIO] && pvt->media[SIP_MEDIA_AUDIO]->rtp) {
- ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_AUDIO]->rtp, channel_id);
+ int i;
+
+ for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->sessions); ++i) {
+ struct ast_sip_session_media *session_media;
+
+ session_media = AST_VECTOR_GET(&session->active_media_state->sessions, i);
+ if (!session_media || !session_media->rtp) {
+ continue;
+ }
+
+ ast_rtp_instance_set_channel_id(session_media->rtp, channel_id);
}
- if (pvt->media[SIP_MEDIA_VIDEO] && pvt->media[SIP_MEDIA_VIDEO]->rtp) {
- ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_VIDEO]->rtp, channel_id);
+}
+
+/*!
+ * \brief Determine if a topology is compatible with format capabilities
+ *
+ * This will return true if ANY formats in the topology are compatible with the format
+ * capabilities.
+ *
+ * XXX When supporting true multistream, we will need to be sure to mark which streams from
+ * top1 are compatible with which streams from top2. Then the ones that are not compatible
+ * will need to be marked as "removed" so that they are negotiated as expected.
+ *
+ * \param top Topology
+ * \param cap Format capabilities
+ * \retval 1 The topology has at least one compatible format
+ * \retval 0 The topology has no compatible formats or an error occurred.
+ */
+static int compatible_formats_exist(struct ast_stream_topology *top, struct ast_format_cap *cap)
+{
+ struct ast_format_cap *cap_from_top;
+ int res;
+
+ cap_from_top = ast_format_cap_from_stream_topology(top);
+
+ if (!cap_from_top) {
+ return 0;
}
+
+ res = ast_format_cap_iscompatible(cap_from_top, cap);
+ ao2_ref(cap_from_top, -1);
+
+ return res;
}
/*! \brief Function called to create a new PJSIP Asterisk channel */
@@ -438,12 +522,9 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
RAII_VAR(struct chan_pjsip_pvt *, pvt, NULL, ao2_cleanup);
struct ast_sip_channel_pvt *channel;
struct ast_variable *var;
+ struct ast_stream_topology *topology;
- if (!(pvt = ao2_alloc(sizeof(*pvt), chan_pjsip_pvt_dtor))) {
- return NULL;
- }
- caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
- if (!caps) {
+ if (!(pvt = ao2_alloc_options(sizeof(*pvt), chan_pjsip_pvt_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
return NULL;
}
@@ -457,31 +538,46 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
ast_sorcery_object_get_id(session->endpoint),
(unsigned) ast_atomic_fetchadd_int((int *) &chan_idx, +1));
if (!chan) {
- ao2_ref(caps, -1);
return NULL;
}
ast_channel_tech_set(chan, &chan_pjsip_tech);
if (!(channel = ast_sip_channel_pvt_alloc(pvt, session))) {
- ao2_ref(caps, -1);
ast_channel_unlock(chan);
ast_hangup(chan);
return NULL;
}
- ast_channel_stage_snapshot(chan);
-
ast_channel_tech_pvt_set(chan, channel);
- if (!ast_format_cap_count(session->req_caps) ||
- !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) {
+ if (!ast_stream_topology_get_count(session->pending_media_state->topology) ||
+ !compatible_formats_exist(session->pending_media_state->topology, session->endpoint->media.codecs)) {
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!caps) {
+ ast_channel_unlock(chan);
+ ast_hangup(chan);
+ return NULL;
+ }
ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);
+ topology = ast_stream_topology_clone(session->endpoint->media.topology);
} else {
- ast_format_cap_append_from_cap(caps, session->req_caps, AST_MEDIA_TYPE_UNKNOWN);
+ caps = ast_format_cap_from_stream_topology(session->pending_media_state->topology);
+ topology = ast_stream_topology_clone(session->pending_media_state->topology);
}
+ if (!topology || !caps) {
+ ao2_cleanup(caps);
+ ast_stream_topology_free(topology);
+ ast_channel_unlock(chan);
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ ast_channel_stage_snapshot(chan);
+
ast_channel_nativeformats_set(chan, caps);
+ ast_channel_set_stream_topology(chan, topology);
if (!ast_format_cap_empty(caps)) {
struct ast_format *fmt;
@@ -538,12 +634,7 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
ast_channel_stage_snapshot_done(chan);
ast_channel_unlock(chan);
- /* If res_pjsip_session is ever updated to create/destroy ast_sip_session_media
- * during a call such as if multiple same-type stream support is introduced,
- * these will need to be recaptured as well */
- pvt->media[SIP_MEDIA_AUDIO] = ao2_find(session->media, "audio", OBJ_KEY);
- pvt->media[SIP_MEDIA_VIDEO] = ao2_find(session->media, "video", OBJ_KEY);
- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(chan));
+ set_channel_on_rtp_instance(session, ast_channel_uniqueid(chan));
return chan;
}
@@ -682,49 +773,32 @@ static struct ast_frame *chan_pjsip_cng_tone_detected(struct ast_sip_session *se
*
* \note The channel is already locked.
*/
-static struct ast_frame *chan_pjsip_read(struct ast_channel *ast)
+static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct ast_sip_session *session;
- struct chan_pjsip_pvt *pvt = channel->pvt;
+ struct ast_sip_session *session = channel->session;
+ struct ast_sip_session_media_read_callback_state *callback_state;
struct ast_frame *f;
- struct ast_sip_session_media *media = NULL;
- int rtcp = 0;
- int fdno = ast_channel_fdno(ast);
+ int fdno = ast_channel_fdno(ast) - AST_EXTENDED_FDS;
- switch (fdno) {
- case 0:
- media = pvt->media[SIP_MEDIA_AUDIO];
- break;
- case 1:
- media = pvt->media[SIP_MEDIA_AUDIO];
- rtcp = 1;
- break;
- case 2:
- media = pvt->media[SIP_MEDIA_VIDEO];
- break;
- case 3:
- media = pvt->media[SIP_MEDIA_VIDEO];
- rtcp = 1;
- break;
- }
-
- if (!media || !media->rtp) {
+ if (fdno >= AST_VECTOR_SIZE(&session->active_media_state->read_callbacks)) {
return &ast_null_frame;
}
- if (!(f = ast_rtp_instance_read(media->rtp, rtcp))) {
+ callback_state = AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, fdno);
+ f = callback_state->read_callback(session, callback_state->session);
+
+ if (!f) {
return f;
}
- ast_rtp_instance_set_last_rx(media->rtp, time(NULL));
+ f->stream_num = callback_state->session->stream_num;
- if (f->frametype != AST_FRAME_VOICE) {
+ if (f->frametype != AST_FRAME_VOICE ||
+ callback_state->session != session->active_media_state->default_session[callback_state->session->type]) {
return f;
}
- session = channel->session;
-
if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when it has not been negotiated\n",
ast_format_get_name(f->subclass.format), ast_channel_name(ast));
@@ -794,22 +868,31 @@ static struct ast_frame *chan_pjsip_read(struct ast_channel *ast)
return f;
}
-/*! \brief Function called by core to write frames */
-static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
+static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *frame)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
- struct ast_sip_session_media *media;
+ struct ast_sip_session *session = channel->session;
+ struct ast_sip_session_media *media = NULL;
int res = 0;
+ /* The core provides a guarantee that the stream will exist when we are called if stream_num is provided */
+ if (stream_num >= 0) {
+ /* What is not guaranteed is that a media session will exist */
+ if (stream_num < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions)) {
+ media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, stream_num);
+ }
+ }
+
switch (frame->frametype) {
case AST_FRAME_VOICE:
- media = pvt->media[SIP_MEDIA_AUDIO];
-
if (!media) {
return 0;
- }
- if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
+ } else if (media->type != AST_MEDIA_TYPE_AUDIO) {
+ ast_debug(3, "Channel %s stream %d is of type '%s', not audio!\n",
+ ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
+ return 0;
+ } else if (media == channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO] &&
+ ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
struct ast_str *write_transpath = ast_str_alloca(256);
struct ast_str *read_transpath = ast_str_alloca(256);
@@ -826,17 +909,32 @@ static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
ast_format_get_name(ast_channel_rawwriteformat(ast)),
ast_translate_path_to_str(ast_channel_writetrans(ast), &write_transpath));
return 0;
- }
- if (media->rtp) {
- res = ast_rtp_instance_write(media->rtp, frame);
+ } else if (media->write_callback) {
+ res = media->write_callback(session, media, frame);
+
}
break;
case AST_FRAME_VIDEO:
- if ((media = pvt->media[SIP_MEDIA_VIDEO]) && media->rtp) {
- res = ast_rtp_instance_write(media->rtp, frame);
+ if (!media) {
+ return 0;
+ } else if (media->type != AST_MEDIA_TYPE_VIDEO) {
+ ast_debug(3, "Channel %s stream %d is of type '%s', not video!\n",
+ ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
+ return 0;
+ } else if (media->write_callback) {
+ res = media->write_callback(session, media, frame);
}
break;
case AST_FRAME_MODEM:
+ if (!media) {
+ return 0;
+ } else if (media->type != AST_MEDIA_TYPE_IMAGE) {
+ ast_debug(3, "Channel %s stream %d is of type '%s', not image!\n",
+ ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
+ return 0;
+ } else if (media->write_callback) {
+ res = media->write_callback(session, media, frame);
+ }
break;
default:
ast_log(LOG_WARNING, "Can't send %u type frames with PJSIP\n", frame->frametype);
@@ -846,11 +944,15 @@ static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
return res;
}
+static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ return chan_pjsip_write_stream(ast, -1, frame);
+}
+
/*! \brief Function called by core to change the underlying owner channel */
static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(newchan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
if (channel->session->channel != oldchan) {
return -1;
@@ -863,7 +965,7 @@ static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *new
*/
channel->session->channel = newchan;
- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(newchan));
+ set_channel_on_rtp_instance(channel->session, ast_channel_uniqueid(newchan));
return 0;
}
@@ -1278,7 +1380,7 @@ static int update_connected_line_information(void *data)
/* Only the INVITE method actually needs SDP, UPDATE can do without */
generate_new_sdp = (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE);
- ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp);
+ ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp, NULL);
}
} else if (session->endpoint->id.rpid_immediate
&& session->inv_session->state != PJSIP_INV_STATE_DISCONNECTED
@@ -1309,21 +1411,18 @@ static int update_connected_line_information(void *data)
}
/*! \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)
+static void local_hold_set_state(struct ast_sip_session_media *session_media, unsigned int held)
{
- struct ast_sip_session_media *session_media = obj;
- unsigned int *held = arg;
-
- session_media->locally_held = *held;
-
- return 0;
+ if (session_media) {
+ session_media->locally_held = held;
+ }
}
/*! \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);
+ AST_VECTOR_CALLBACK_VOID(&session->active_media_state->sessions, local_hold_set_state, held);
+ ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, NULL);
ao2_ref(session, -1);
return 0;
@@ -1341,16 +1440,103 @@ static int remote_send_unhold(void *data)
return remote_send_hold_refresh(data, 0);
}
+struct topology_change_refresh_data {
+ struct ast_sip_session *session;
+ struct ast_sip_session_media_state *media_state;
+};
+
+static void topology_change_refresh_data_free(struct topology_change_refresh_data *refresh_data)
+{
+ ao2_cleanup(refresh_data->session);
+
+ ast_sip_session_media_state_free(refresh_data->media_state);
+ ast_free(refresh_data);
+}
+
+static struct topology_change_refresh_data *topology_change_refresh_data_alloc(
+ struct ast_sip_session *session, const struct ast_stream_topology *topology)
+{
+ struct topology_change_refresh_data *refresh_data;
+
+ refresh_data = ast_calloc(1, sizeof(*refresh_data));
+ if (!refresh_data) {
+ return NULL;
+ }
+
+ refresh_data->session = ao2_bump(session);
+ refresh_data->media_state = ast_sip_session_media_state_alloc();
+ if (!refresh_data->media_state) {
+ topology_change_refresh_data_free(refresh_data);
+ return NULL;
+ }
+ refresh_data->media_state->topology = ast_stream_topology_clone(topology);
+ if (!refresh_data->media_state->topology) {
+ topology_change_refresh_data_free(refresh_data);
+ return NULL;
+ }
+
+ return refresh_data;
+}
+
+static int on_topology_change_response(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ if (rdata->msg_info.msg->line.status.code == 200) {
+ /* The topology was changed to something new so give notice to what requested
+ * it so it queries the channel and updates accordingly.
+ */
+ if (session->channel) {
+ ast_queue_control(session->channel, AST_CONTROL_STREAM_TOPOLOGY_CHANGED);
+ }
+ } else if (rdata->msg_info.msg->line.status.code != 100) {
+ /* The topology change failed, so drop the current pending media state */
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
+
+ return 0;
+}
+
+static int send_topology_change_refresh(void *data)
+{
+ struct topology_change_refresh_data *refresh_data = data;
+ int ret;
+
+ ret = ast_sip_session_refresh(refresh_data->session, NULL, NULL, on_topology_change_response,
+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, refresh_data->media_state);
+ refresh_data->media_state = NULL;
+ topology_change_refresh_data_free(refresh_data);
+
+ return ret;
+}
+
+static int handle_topology_request_change(struct ast_sip_session *session,
+ const struct ast_stream_topology *proposed)
+{
+ struct topology_change_refresh_data *refresh_data;
+ int res;
+
+ refresh_data = topology_change_refresh_data_alloc(session, proposed);
+ if (!refresh_data) {
+ return -1;
+ }
+
+ res = ast_sip_push_task(session->serializer, send_topology_change_refresh, refresh_data);
+ if (res) {
+ topology_change_refresh_data_free(refresh_data);
+ }
+ return res;
+}
+
/*! \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)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
struct ast_sip_session_media *media;
int response_code = 0;
int res = 0;
char *device_buf;
size_t device_buf_size;
+ int i;
+ const struct ast_stream_topology *topology;
switch (condition) {
case AST_CONTROL_RINGING:
@@ -1403,39 +1589,47 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_sorcery_object_get_id(channel->session->endpoint));
break;
case AST_CONTROL_VIDUPDATE:
- media = pvt->media[SIP_MEDIA_VIDEO];
- if (media && media->rtp) {
- /* FIXME: Only use this for VP8. Additional work would have to be done to
- * fully support other video codecs */
-
- if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) {
- /* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the
- * RTP engine would provide a way to externally write/schedule RTCP
- * packets */
- struct ast_frame fr;
- fr.frametype = AST_FRAME_CONTROL;
- fr.subclass.integer = AST_CONTROL_VIDUPDATE;
- res = ast_rtp_instance_write(media->rtp, &fr);
- } else {
- ao2_ref(channel->session, +1);
-#ifdef HAVE_PJSIP_INV_SESSION_REF
- if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) {
- ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
- ao2_cleanup(channel->session);
+ for (i = 0; i < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions); ++i) {
+ media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, i);
+ if (!media || media->type != AST_MEDIA_TYPE_VIDEO) {
+ continue;
+ }
+ if (media->rtp) {
+ /* FIXME: Only use this for VP8. Additional work would have to be done to
+ * fully support other video codecs */
+
+ if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) {
+ /* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the
+ * RTP engine would provide a way to externally write/schedule RTCP
+ * packets */
+ struct ast_frame fr;
+ fr.frametype = AST_FRAME_CONTROL;
+ fr.subclass.integer = AST_CONTROL_VIDUPDATE;
+ res = ast_rtp_instance_write(media->rtp, &fr);
} else {
-#endif
- if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) {
+ ao2_ref(channel->session, +1);
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+ if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
ao2_cleanup(channel->session);
- }
+ } else {
+#endif
+ if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) {
+ ao2_cleanup(channel->session);
+ }
#ifdef HAVE_PJSIP_INV_SESSION_REF
- }
+ }
#endif
+ }
+ ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success");
+ } else {
+ ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure");
+ res = -1;
}
- ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success");
- } else {
- ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure");
- res = -1;
}
+ /* XXX If there were no video streams, then this should set
+ * res to -1
+ */
break;
case AST_CONTROL_CONNECTED_LINE:
ao2_ref(channel->session, +1);
@@ -1531,6 +1725,10 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
}
break;
+ case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE:
+ topology = data;
+ res = handle_topology_request_change(channel->session, topology);
+ break;
case -1:
res = -1;
break;
@@ -1744,10 +1942,11 @@ static int chan_pjsip_transfer(struct ast_channel *chan, const char *target)
static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
- struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];
+ struct ast_sip_session_media *media;
int res = 0;
+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+
switch (channel->session->endpoint->dtmf) {
case AST_SIP_DTMF_RFC_4733:
if (!media || !media->rtp) {
@@ -1755,14 +1954,14 @@ static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit)
}
ast_rtp_instance_dtmf_begin(media->rtp, digit);
- break;
+ break;
case AST_SIP_DTMF_AUTO:
- if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
- return -1;
- }
+ if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
+ return -1;
+ }
- ast_rtp_instance_dtmf_begin(media->rtp, digit);
- break;
+ ast_rtp_instance_dtmf_begin(media->rtp, digit);
+ break;
case AST_SIP_DTMF_NONE:
break;
case AST_SIP_DTMF_INBAND:
@@ -1858,10 +2057,11 @@ failure:
static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
- struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];
+ struct ast_sip_session_media *media;
int res = 0;
+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+
switch (channel->session->endpoint->dtmf) {
case AST_SIP_DTMF_INFO:
{
@@ -1943,7 +2143,6 @@ static int call(void *data)
{
struct ast_sip_channel_pvt *channel = data;
struct ast_sip_session *session = channel->session;
- struct chan_pjsip_pvt *pvt = channel->pvt;
pjsip_tx_data *tdata;
int res = ast_sip_session_create_invite(session, &tdata);
@@ -1952,7 +2151,7 @@ static int call(void *data)
ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0);
ast_queue_hangup(session->channel);
} else {
- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(session->channel));
+ set_channel_on_rtp_instance(session, ast_channel_uniqueid(session->channel));
update_initial_connected_line(session);
ast_sip_session_send_request(session, tdata);
}
@@ -2050,10 +2249,10 @@ static struct hangup_data *hangup_data_alloc(int cause, struct ast_channel *chan
}
/*! \brief Clear a channel from a session along with its PVT */
-static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast, struct chan_pjsip_pvt *pvt)
+static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast)
{
session->channel = NULL;
- set_channel_on_rtp_instance(pvt, "");
+ set_channel_on_rtp_instance(session, "");
ast_channel_tech_pvt_set(ast, NULL);
}
@@ -2062,7 +2261,6 @@ static int hangup(void *data)
struct hangup_data *h_data = data;
struct ast_channel *ast = h_data->chan;
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
struct ast_sip_session *session = channel->session;
int cause = h_data->cause;
@@ -2072,7 +2270,7 @@ static int hangup(void *data)
* afterwards.
*/
ast_sip_session_terminate(ao2_bump(session), cause);
- clear_session_and_channel(session, ast, pvt);
+ clear_session_and_channel(session, ast);
ao2_cleanup(session);
ao2_cleanup(channel);
ao2_cleanup(h_data);
@@ -2083,7 +2281,6 @@ static int hangup(void *data)
static int chan_pjsip_hangup(struct ast_channel *ast)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt;
int cause;
struct hangup_data *h_data;
@@ -2091,7 +2288,6 @@ static int chan_pjsip_hangup(struct ast_channel *ast)
return -1;
}
- pvt = channel->pvt;
cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel));
h_data = hangup_data_alloc(cause, ast);
@@ -2110,7 +2306,7 @@ failure:
/* Go ahead and do our cleanup of the session and channel even if we're not going
* to be able to send our SIP request/response
*/
- clear_session_and_channel(channel->session, ast, pvt);
+ clear_session_and_channel(channel->session, ast);
ao2_cleanup(channel);
ao2_cleanup(h_data);
@@ -2119,7 +2315,7 @@ failure:
struct request_data {
struct ast_sip_session *session;
- struct ast_format_cap *caps;
+ struct ast_stream_topology *topology;
const char *dest;
int cause;
};
@@ -2193,7 +2389,7 @@ static int request(void *obj)
}
}
- if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->caps))) {
+ if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->topology))) {
ast_log(LOG_ERROR, "Failed to create outgoing session to endpoint '%s'\n", endpoint_name);
req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION;
return -1;
@@ -2205,12 +2401,12 @@ static int request(void *obj)
}
/*! \brief Function called by core to create a new outgoing PJSIP session */
-static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
+static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type, struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
{
struct request_data req_data;
RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);
- req_data.caps = cap;
+ req_data.topology = topology;
req_data.dest = data;
if (ast_sip_push_task_synchronous(NULL, request, &req_data)) {
@@ -2228,6 +2424,23 @@ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_forma
return session->channel;
}
+static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
+{
+ struct ast_stream_topology *topology;
+ struct ast_channel *chan;
+
+ topology = ast_stream_topology_create_from_format_cap(cap);
+ if (!topology) {
+ return NULL;
+ }
+
+ chan = chan_pjsip_request_with_stream_topology(type, topology, assignedids, requestor, data, cause);
+
+ ast_stream_topology_free(topology);
+
+ return chan;
+}
+
struct sendtext_data {
struct ast_sip_session *session;
char text[0];
diff --git a/channels/pjsip/cli_commands.c b/channels/pjsip/cli_commands.c
index fc14b25a8..33d0e02c1 100644
--- a/channels/pjsip/cli_commands.c
+++ b/channels/pjsip/cli_commands.c
@@ -342,8 +342,9 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags)
const struct ast_channel_snapshot *snapshot = obj;
struct ast_channel *channel = ast_channel_get_by_name(snapshot->name);
struct ast_sip_channel_pvt *cpvt = channel ? ast_channel_tech_pvt(channel) : NULL;
- struct chan_pjsip_pvt *pvt = cpvt ? cpvt->pvt : NULL;
- struct ast_sip_session_media *media = pvt ? pvt->media[SIP_MEDIA_AUDIO] : NULL;
+ struct ast_sip_session *session;
+ struct ast_sip_session_media *media;
+ struct ast_rtp_instance *rtp;
struct ast_rtp_instance_stats stats;
char *print_name = NULL;
char *print_time = alloca(32);
@@ -351,29 +352,46 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags)
ast_assert(context->output_buffer != NULL);
+ if (!channel) {
+ ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+ return -1;
+ }
+
+ ast_channel_lock(channel);
+
+ session = cpvt->session;
+ if (!session) {
+ ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+ ast_channel_unlock(channel);
+ ao2_cleanup(channel);
+ return -1;
+ }
+
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
if (!media || !media->rtp) {
ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+ ast_channel_unlock(channel);
ao2_cleanup(channel);
return -1;
}
+ rtp = ao2_bump(media->rtp);
+
codec_in_use[0] = '\0';
- if (channel) {
- ast_channel_lock(channel);
- if (ast_channel_rawreadformat(channel)) {
- ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));
- }
- ast_channel_unlock(channel);
+ if (ast_channel_rawreadformat(channel)) {
+ ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));
}
+ ast_channel_unlock(channel);
+
print_name = ast_strdupa(snapshot->name);
/* Skip the PJSIP/. We know what channel type it is and we need the space. */
print_name += 6;
ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);
- if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
+ if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
ast_str_append(&context->output_buffer, 0, "%s direct media\n", snapshot->name);
} else {
ast_str_append(&context->output_buffer, 0,
@@ -398,6 +416,7 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags)
);
}
+ ao2_cleanup(rtp);
ao2_cleanup(channel);
return 0;
diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c
index e2c78cd87..59ca9d791 100644
--- a/channels/pjsip/dialplan_functions.c
+++ b/channels/pjsip/dialplan_functions.c
@@ -437,6 +437,7 @@
#include "asterisk/acl.h"
#include "asterisk/app.h"
#include "asterisk/channel.h"
+#include "asterisk/stream.h"
#include "asterisk/format.h"
#include "asterisk/pbx.h"
#include "asterisk/res_pjsip.h"
@@ -461,8 +462,8 @@ static const char *t38state_to_string[T38_MAX_ENUM] = {
static int channel_read_rtp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt;
- struct ast_sip_session_media *media = NULL;
+ struct ast_sip_session *session;
+ struct ast_sip_session_media *media;
struct ast_sockaddr addr;
if (!channel) {
@@ -470,9 +471,9 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch
return -1;
}
- pvt = channel->pvt;
- if (!pvt) {
- ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));
+ session = channel->session;
+ if (!session) {
+ ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan));
return -1;
}
@@ -482,9 +483,9 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch
}
if (ast_strlen_zero(field) || !strcmp(field, "audio")) {
- media = pvt->media[SIP_MEDIA_AUDIO];
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
} else if (!strcmp(field, "video")) {
- media = pvt->media[SIP_MEDIA_VIDEO];
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
} else {
ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field);
return -1;
@@ -522,17 +523,17 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch
static int channel_read_rtcp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt;
- struct ast_sip_session_media *media = NULL;
+ struct ast_sip_session *session;
+ struct ast_sip_session_media *media;
if (!channel) {
ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan));
return -1;
}
- pvt = channel->pvt;
- if (!pvt) {
- ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));
+ session = channel->session;
+ if (!session) {
+ ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan));
return -1;
}
@@ -542,9 +543,9 @@ static int channel_read_rtcp(struct ast_channel *chan, const char *type, const c
}
if (ast_strlen_zero(field) || !strcmp(field, "audio")) {
- media = pvt->media[SIP_MEDIA_AUDIO];
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
} else if (!strcmp(field, "video")) {
- media = pvt->media[SIP_MEDIA_VIDEO];
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
} else {
ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field);
return -1;
@@ -924,22 +925,117 @@ int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char
return 0;
}
+/*! \brief Session refresh state information */
+struct session_refresh_state {
+ /*! \brief Created proposed media state */
+ struct ast_sip_session_media_state *media_state;
+};
+
+/*! \brief Destructor for session refresh information */
+static void session_refresh_state_destroy(void *obj)
+{
+ struct session_refresh_state *state = obj;
+
+ ast_sip_session_media_state_free(state->media_state);
+ ast_free(obj);
+}
+
+/*! \brief Datastore for attaching session refresh state information */
+static const struct ast_datastore_info session_refresh_datastore = {
+ .type = "pjsip_session_refresh",
+ .destroy = session_refresh_state_destroy,
+};
+
+/*! \brief Helper function which retrieves or allocates a session refresh state information datastore */
+static struct session_refresh_state *session_refresh_state_get_or_alloc(struct ast_sip_session *session)
+{
+ RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "pjsip_session_refresh"), ao2_cleanup);
+ struct session_refresh_state *state;
+
+ /* While the datastore refcount is decremented this is operating in the serializer so it will remain valid regardless */
+ if (datastore) {
+ return datastore->data;
+ }
+
+ if (!(datastore = ast_sip_session_alloc_datastore(&session_refresh_datastore, "pjsip_session_refresh"))
+ || !(datastore->data = ast_calloc(1, sizeof(struct session_refresh_state)))
+ || ast_sip_session_add_datastore(session, datastore)) {
+ return NULL;
+ }
+
+ state = datastore->data;
+ state->media_state = ast_sip_session_media_state_alloc();
+ if (!state->media_state) {
+ ast_sip_session_remove_datastore(session, "pjsip_session_refresh");
+ return NULL;
+ }
+ state->media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
+ if (!state->media_state->topology) {
+ ast_sip_session_remove_datastore(session, "pjsip_session_refresh");
+ return NULL;
+ }
+
+ datastore->data = state;
+
+ return state;
+}
+
static int media_offer_read_av(struct ast_sip_session *session, char *buf,
size_t len, enum ast_media_type media_type)
{
+ struct ast_stream_topology *topology;
int idx;
+ struct ast_stream *stream = NULL;
+ struct ast_format_cap *caps;
size_t accum = 0;
+ if (session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {
+ struct session_refresh_state *state;
+
+ /* As we've already answered we need to store our media state until we are ready to send it */
+ state = session_refresh_state_get_or_alloc(session);
+ if (!state) {
+ return -1;
+ }
+ topology = state->media_state->topology;
+ } else {
+ /* The session is not yet up so we are initially answering or offering */
+ if (!session->pending_media_state->topology) {
+ session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
+ if (!session->pending_media_state->topology) {
+ return -1;
+ }
+ }
+ topology = session->pending_media_state->topology;
+ }
+
+ /* Find the first suitable stream */
+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {
+ stream = ast_stream_topology_get_stream(topology, idx);
+
+ if (ast_stream_get_type(stream) != media_type ||
+ ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+ stream = NULL;
+ continue;
+ }
+
+ break;
+ }
+
+ /* If no suitable stream then exit early */
+ if (!stream) {
+ buf[0] = '\0';
+ return 0;
+ }
+
+ caps = ast_stream_get_formats(stream);
+
/* Note: buf is not terminated while the string is being built. */
- for (idx = 0; idx < ast_format_cap_count(session->req_caps); ++idx) {
+ for (idx = 0; idx < ast_format_cap_count(caps); ++idx) {
struct ast_format *fmt;
size_t size;
- fmt = ast_format_cap_get_format(session->req_caps, idx);
- if (ast_format_get_type(fmt) != media_type) {
- ao2_ref(fmt, -1);
- continue;
- }
+ fmt = ast_format_cap_get_format(caps, idx);
/* Add one for a comma or terminator */
size = strlen(ast_format_get_name(fmt)) + 1;
@@ -973,9 +1069,43 @@ struct media_offer_data {
static int media_offer_write_av(void *obj)
{
struct media_offer_data *data = obj;
+ struct ast_stream_topology *topology;
+ struct ast_stream *stream;
+ struct ast_format_cap *caps;
- ast_format_cap_remove_by_type(data->session->req_caps, data->media_type);
- ast_format_cap_update_by_allow_disallow(data->session->req_caps, data->value, 1);
+ if (data->session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {
+ struct session_refresh_state *state;
+
+ /* As we've already answered we need to store our media state until we are ready to send it */
+ state = session_refresh_state_get_or_alloc(data->session);
+ if (!state) {
+ return -1;
+ }
+ topology = state->media_state->topology;
+ } else {
+ /* The session is not yet up so we are initially answering or offering */
+ if (!data->session->pending_media_state->topology) {
+ data->session->pending_media_state->topology = ast_stream_topology_clone(data->session->endpoint->media.topology);
+ if (!data->session->pending_media_state->topology) {
+ return -1;
+ }
+ }
+ topology = data->session->pending_media_state->topology;
+ }
+
+ /* XXX This method won't work when it comes time to do multistream support. The proper way to do this
+ * will either be to
+ * a) Alter all media streams of a particular type.
+ * b) Change the dialplan function to be able to specify which stream to alter and alter only that
+ * one stream
+ */
+ stream = ast_stream_topology_get_first_stream_by_type(topology, data->media_type);
+ if (!stream) {
+ return 0;
+ }
+ caps = ast_stream_get_formats(stream);
+ ast_format_cap_remove_by_type(caps, data->media_type);
+ ast_format_cap_update_by_allow_disallow(caps, data->value, 1);
return 0;
}
@@ -1068,9 +1198,18 @@ static int sip_session_response_cb(struct ast_sip_session *session, pjsip_rx_dat
static int refresh_write_cb(void *obj)
{
struct refresh_data *data = obj;
+ struct session_refresh_state *state;
+
+ state = session_refresh_state_get_or_alloc(data->session);
+ if (!state) {
+ return -1;
+ }
ast_sip_session_refresh(data->session, NULL, NULL,
- sip_session_response_cb, data->method, 1);
+ sip_session_response_cb, data->method, 1, state->media_state);
+
+ state->media_state = NULL;
+ ast_sip_session_remove_datastore(data->session, "pjsip_session_refresh");
return 0;
}
diff --git a/channels/pjsip/include/chan_pjsip.h b/channels/pjsip/include/chan_pjsip.h
index b229a0487..1fee86419 100644
--- a/channels/pjsip/include/chan_pjsip.h
+++ b/channels/pjsip/include/chan_pjsip.h
@@ -34,25 +34,12 @@ struct transport_info_data {
pj_sockaddr local_addr;
};
-/*!
- * \brief Positions of various media
- */
-enum sip_session_media_position {
- /*! \brief First is audio */
- SIP_MEDIA_AUDIO = 0,
- /*! \brief Second is video */
- SIP_MEDIA_VIDEO,
- /*! \brief Last is the size for media details */
- SIP_MEDIA_SIZE,
-};
/*!
* \brief The PJSIP channel driver pvt, stored in the \ref ast_sip_channel_pvt
* data structure
*/
struct chan_pjsip_pvt {
- /*! \brief The available media sessions */
- struct ast_sip_session_media *media[SIP_MEDIA_SIZE];
};
#endif /* _CHAN_PJSIP_HEADER */