summaryrefslogtreecommitdiff
path: root/channels
diff options
context:
space:
mode:
authorMark Michelson <mmichelson@digium.com>2017-05-30 09:12:47 -0500
committerJoshua Colp <jcolp@digium.com>2017-06-28 18:36:29 +0000
commit45df25a579edd5423c9d319758d109a74fe8ef33 (patch)
treef7138bb2b13915f0542c5c6fff30b4c73025eb71 /channels
parenta48d3e4d31c8060856d785fca00c5213d5e012bc (diff)
chan_pjsip: Add support for multiple streams of the same type.
The stream topology (list of streams and order) is now stored with the configured PJSIP endpoints and used during the negotiation process. Media negotiation state information has been changed to be stored in a separate object. Two of these objects exist at any one time on a session. The active media state information is what was previously negotiated and the pending media state information is what the media state will become if negotiation succeeds. Streams and other state information is stored in this object using the index (or position) of each individual stream for easy lookup. The ability for a media type handler to specify a callback for writing has been added as well as the ability to add file descriptors with a callback which is invoked when data is available to be read on them. This allows media logic to live outside of the chan_pjsip module. Direct media has been changed so that only the first audio and video stream are directly connected. In the future once the RTP engine glue API has been updated to know about streams each individual stream can be directly connected as appropriate. Media negotiation itself will currently answer all the provided streams on an offer within configured limits and on an offer will use the topology created as a result of the disallow/allow codec lines. If a stream has been removed or declined we will now mark it as such within the resulting SDP. Applications can now also request that the stream topology change. If we are told to do so we will limit any provided formats to the ones configured on the endpoint and send a re-invite with the new topology. Two new configuration options have also been added to PJSIP endpoints: max_audio_streams: determines the maximum number of audio streams to offer/accept from an endpoint. Defaults to 1. max_video_streams: determines the maximum number of video streams to offer/accept from an endpoint. Defaults to 1. ASTERISK-27076 Change-Id: I8afd8dd2eb538806a39b887af0abd046266e14c7
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 */