#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"
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);
.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,
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;
}
endpoint = channel->session->endpoint;
- *instance = pvt->media[SIP_MEDIA_AUDIO]->rtp;
+ *instance = media->rtp;
ao2_ref(*instance, +1);
ast_assert(endpoint != NULL);
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);
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)){
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));
+ }
}
}
{
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);
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);
.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 */
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;
}
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;
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;
}
*
* \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));
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);
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);
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;
*/
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;
}
/* 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
}
/*! \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;
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:
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);
}
}
+ break;
+ case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE:
+ topology = data;
+ res = handle_topology_request_change(channel->session, topology);
break;
case -1:
res = -1;
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) {
}
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:
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:
{
{
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);
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);
}
}
/*! \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);
}
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;
* 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);
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;
return -1;
}
- pvt = channel->pvt;
cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel));
h_data = hangup_data_alloc(cause, ast);
/* 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);
struct request_data {
struct ast_sip_session *session;
- struct ast_format_cap *caps;
+ struct ast_stream_topology *topology;
const char *dest;
int cause;
};
}
}
- 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;
}
/*! \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)) {
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];
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);
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,
);
}
+ ao2_cleanup(rtp);
ao2_cleanup(channel);
return 0;
#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"
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) {
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;
}
}
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;
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;
}
}
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;
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;
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;
}
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;
}
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 */
; The value "yes" is useful for some SIP phones
; (Cisco SPA) to be able to indicate and pick up
; ringing devices.
+;max_audio_streams= ; The maximum number of allowed negotiated audio streams
+ ; (default: 1)
+;max_video_streams= ; The maximum number of allowed negotiated video streams
+ ; (default: 1)
;==========================AUTH SECTION OPTIONS=========================
;[auth]
--- /dev/null
+"""pjsip_stream_maximum
+
+Revision ID: 39959b9c2566
+Revises: d7983954dd96
+Create Date: 2017-06-15 13:18:12.372333
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '39959b9c2566'
+down_revision = 'd7983954dd96'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_endpoints', sa.Column('max_audio_streams', sa.Integer))
+ op.add_column('ps_endpoints', sa.Column('max_video_streams', sa.Integer))
+
+
+def downgrade():
+ op.drop_column('ps_endpoints', 'max_audio_streams')
+ op.drop_column('ps_endpoints', 'max_video_streams')
struct ast_sip_t38_configuration t38;
/*! Configured codecs */
struct ast_format_cap *codecs;
+ /*! Capabilities in topology form */
+ struct ast_stream_topology *topology;
/*! DSCP TOS bits for audio streams */
unsigned int tos_audio;
/*! Priority for audio streams */
unsigned int bind_rtp_to_media_address;
/*! Use RTCP-MUX */
unsigned int rtcp_mux;
+ /*! Maximum number of audio streams to offer/accept */
+ unsigned int max_audio_streams;
+ /*! Maximum number of video streams to offer/accept */
+ unsigned int max_video_streams;
};
/*!
#include "asterisk/netsock2.h"
/* Needed for ast_sdp_srtp struct */
#include "asterisk/sdp_srtp.h"
+/* Needed for ast_media_type */
+#include "asterisk/codec.h"
/* Forward declarations */
struct ast_sip_endpoint;
};
struct ast_sip_session_sdp_handler;
+struct ast_sip_session;
+struct ast_sip_session_media;
+
+typedef struct ast_frame *(*ast_sip_session_media_read_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media);
+typedef int (*ast_sip_session_media_write_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ struct ast_frame *frame);
/*!
* \brief A structure containing SIP session media information
*/
struct ast_sip_session_media {
- union {
- /*! \brief RTP instance itself */
- struct ast_rtp_instance *rtp;
- /*! \brief UDPTL instance itself */
- struct ast_udptl *udptl;
- };
+ /*! \brief RTP instance itself */
+ struct ast_rtp_instance *rtp;
+ /*! \brief UDPTL instance itself */
+ struct ast_udptl *udptl;
/*! \brief Direct media address */
struct ast_sockaddr direct_media_addr;
/*! \brief SDP handler that setup the RTP */
unsigned int locally_held:1;
/*! \brief Does remote support rtcp_mux */
unsigned int remote_rtcp_mux:1;
- /*! \brief Stream type this session media handles */
- char stream_type[1];
+ /*! \brief Media type of this session media */
+ enum ast_media_type type;
+ /*! \brief The write callback when writing frames */
+ ast_sip_session_media_write_cb write_callback;
+ /*! \brief The stream number to place into any resulting frames */
+ int stream_num;
+};
+
+/*!
+ * \brief Structure which contains read callback information
+ */
+struct ast_sip_session_media_read_callback_state {
+ /*! \brief The file descriptor itself */
+ int fd;
+ /*! \brief The callback to invoke */
+ ast_sip_session_media_read_cb read_callback;
+ /*! \brief The media session */
+ struct ast_sip_session_media *session;
+};
+
+/*!
+ * \brief Structure which contains media state information (streams, sessions)
+ */
+struct ast_sip_session_media_state {
+ /*! \brief Mapping of stream to media sessions */
+ AST_VECTOR(, struct ast_sip_session_media *) sessions;
+ /*! \brief Added read callbacks - these are whole structs and not pointers */
+ AST_VECTOR(, struct ast_sip_session_media_read_callback_state) read_callbacks;
+ /*! \brief Default media sessions for each type */
+ struct ast_sip_session_media *default_session[AST_MEDIA_TYPE_END];
+ /*! \brief The media stream topology */
+ struct ast_stream_topology *topology;
};
/*!
AST_LIST_HEAD(, ast_sip_session_supplement) supplements;
/*! Datastores added to the session by supplements to the session */
struct ao2_container *datastores;
- /*! Media streams */
- struct ao2_container *media;
/*! Serializer for tasks relating to this SIP session */
struct ast_taskprocessor *serializer;
/*! Non-null if the session serializer is suspended or being suspended. */
pj_timer_entry scheduled_termination;
/*! Identity of endpoint this session deals with */
struct ast_party_id id;
- /*! Requested capabilities */
- struct ast_format_cap *req_caps;
+ /*! Active media state (sessions + streams) - contents are guaranteed not to change */
+ struct ast_sip_session_media_state *active_media_state;
+ /*! Pending media state (sessions + streams) */
+ struct ast_sip_session_media_state *pending_media_state;
/*! Optional DSP, used only for inband DTMF/Fax-CNG detection if configured */
struct ast_dsp *dsp;
/*! Whether the termination of the session should be deferred */
/*!
* \brief Set session details based on a stream in an incoming SDP offer or answer
* \param session The session for which the media is being negotiated
- * \param session_media The media to be setup for this session
+ * \param session_media The media session
* \param sdp The entire SDP. Useful for getting "global" information, such as connections or attributes
- * \param stream The stream on which to operate
- * \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called.
- * \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned.
- * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.
- */
- int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream);
- /*!
- * \brief Create an SDP media stream and add it to the outgoing SDP offer or answer
- * \param session The session for which media is being added
- * \param session_media The media to be setup for this session
- * \param stream The stream on which to operate
+ * \param index The index for the session media, Asterisk stream, and PJMEDIA stream being negotiated
+ * \param asterisk_stream The Asterisk stream representation
* \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called.
* \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned.
* \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.
*/
- int (*handle_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, struct pjmedia_sdp_media *stream);
+ int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_session *sdp, int index, struct ast_stream *asterisk_stream);
/*!
* \brief Create an SDP media stream and add it to the outgoing SDP offer or answer
* \param session The session for which media is being added
* \param session_media The media to be setup for this session
* \param sdp The entire SDP as currently built
+ * \param remote Optional remote SDP if this is an answer
+ * \param stream The stream that is to be added to the outgoing SDP
* \retval 0 This handler has no stream to add. If there are other registered handlers for this stream type, they will be called.
* \retval <0 There was an error encountered. No further operation will take place and the current SDP negotiation will be abandoned.
* \retval >0 The handler has a stream to be added to the SDP. No further handler of this stream type will be called.
*/
- int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp);
+ int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp,
+ const struct pjmedia_sdp_session *remote, struct ast_stream *stream);
/*!
* \brief Update media stream with external address if applicable
* \param tdata The outgoing message itself
/*!
* \brief Apply a negotiated SDP media stream
* \param session The session for which media is being applied
- * \param session_media The media to be setup for this session
+ * \param session_media The media session
* \param local The entire local negotiated SDP
- * \param local_stream The local stream which to apply
* \param remote The entire remote negotiated SDP
- * \param remote_stream The remote stream which to apply
+ * \param index The index of the session media, SDP streams, and Asterisk streams
+ * \param asterisk_stream The Asterisk stream representation
* \retval 0 The stream was not applied by this handler. If there are other registered handlers for this stream type, they will be called.
* \retval <0 There was an error encountered. No further operation will take place and the current application will be abandoned.
* \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.
*/
- int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
- const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream);
+ int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote, int index,
+ struct ast_stream *asterisk_stream);
/*!
* \brief Stop a session_media created by this handler but do not destroy resources
* \param session The session for which media is being stopped
/*!
* \brief Allocate a new SIP channel pvt structure
*
- * \param pvt Pointer to channel specific implementation
+ * \param pvt Pointer to channel specific information
* \param session Pointer to SIP session
*
* \retval non-NULL success
* \param contact The contact that this session will communicate with
* \param location Name of the location to call, be it named location or explicit URI. Overrides contact if present.
* \param request_user Optional request user to place in the request URI if permitted
- * \param req_caps The requested capabilities
+ * \param req_topology The requested capabilities
*/
struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint,
struct ast_sip_contact *contact, const char *location, const char *request_user,
- struct ast_format_cap *req_caps);
+ struct ast_stream_topology *req_topology);
/*!
* \brief Terminate a session and, if possible, send the provided response code
* \param on_response Callback called when response for request is received
* \param method The method that should be used when constructing the session refresh
* \param generate_new_sdp Boolean to indicate if a new SDP should be created
+ * \param media_state Optional requested media state for the SDP
+ *
* \retval 0 Successfully sent refresh
* \retval -1 Failure to send refresh
+ *
+ * \note If a media_state is passed in ownership will be taken in all cases
*/
int ast_sip_session_refresh(struct ast_sip_session *session,
ast_sip_session_request_creation_cb on_request_creation,
ast_sip_session_sdp_creation_cb on_sdp_creation,
ast_sip_session_response_cb on_response,
enum ast_sip_session_refresh_method method,
- int generate_new_sdp);
+ int generate_new_sdp,
+ struct ast_sip_session_media_state *media_state);
/*!
* \brief Send a SIP response
*/
void ast_sip_session_resume_reinvite(struct ast_sip_session *session);
+/*!
+ * \brief Determines if a provided pending stream will be the default stream or not
+ * \since 15.0.0
+ *
+ * \param session The session to check against
+ * \param stream The pending stream
+ *
+ * \retval 1 if stream will be default
+ * \retval 0 if stream will NOT be the default
+ */
+int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream);
+
+/*!
+ * \brief Allocate a session media state structure
+ * \since 15.0.0
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_sip_session_media_state *ast_sip_session_media_state_alloc(void);
+
+/*!
+ * \brief Allocate an ast_session_media and add it to the media state's vector.
+ * \since 15.0.0
+ *
+ * This allocates a session media of the specified type. The position argument
+ * determines where in the vector that the new session media will be inserted.
+ *
+ * \note The returned ast_session_media is the reference held by the vector. Callers
+ * of this function must NOT decrement the refcount of the session media.
+ *
+ * \param session Session on which to query active media state for
+ * \param media_state Media state to place the session media into
+ * \param type The type of the session media
+ * \param position Position at which to insert the new session media.
+ *
+ * \note The active media state will be queried and if a media session already
+ * exists at the given position for the same type it will be reused instead of
+ * allocating a new one.
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,
+ struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position);
+
+/*!
+ * \brief Reset a media state to a clean state
+ * \since 15.0.0
+ *
+ * \param media_state The media state to reset
+ */
+void ast_sip_session_media_state_reset(struct ast_sip_session_media_state *media_state);
+
+/*!
+ * \brief Clone a media state
+ * \since 15.0.0
+ *
+ * \param media_state The media state to clone
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_sip_session_media_state *ast_sip_session_media_state_clone(const struct ast_sip_session_media_state *media_state);
+
+/*!
+ * \brief Free a session media state structure
+ * \since 15.0.0
+ */
+void ast_sip_session_media_state_free(struct ast_sip_session_media_state *media_state);
+
+/*!
+ * \brief Set a read callback for a media session with a specific file descriptor
+ * \since 15.0.0
+ *
+ * \param session The session
+ * \param session_media The media session
+ * \param fd The file descriptor
+ * \param callback The read callback
+ *
+ * \retval 0 the read callback was successfully added
+ * \retval -1 the read callback could not be added
+ *
+ * \note This operations on the pending media state
+ */
+int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ int fd, ast_sip_session_media_read_cb callback);
+
+/*!
+ * \brief Set a write callback for a media session
+ * \since 15.0.0
+ *
+ * \param session The session
+ * \param session_media The media session
+ * \param callback The write callback
+ *
+ * \retval 0 the write callback was successfully add
+ * \retval -1 the write callback is already set to something different
+ *
+ * \note This operates on the pending media state
+ */
+int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ ast_sip_session_media_write_cb callback);
+
/*! \brief Determines whether the res_pjsip_session module is loaded */
#define CHECK_PJSIP_SESSION_MODULE_LOADED() \
do { \
struct ast_stream_topology *ast_stream_topology_clone(
const struct ast_stream_topology *topology);
+/*!
+ * \brief Compare two stream topologies to see if they are equal
+ *
+ * \param left The left topology
+ * \param right The right topology
+ *
+ * \retval 1 topologies are equivalent
+ * \retval 0 topologies differ
+ *
+ * \since 15
+ */
+int ast_stream_topology_equal(const struct ast_stream_topology *left,
+ const struct ast_stream_topology *right);
+
/*!
* \brief Destroy a stream topology
*
* since a new format capabilities structure is created for each media type.
*
* \note Each stream will have its name set to the corresponding media type.
- * For example: "AST_MEDIA_TYPE_AUDIO".
+ * For example: "audio".
*
* \note Each stream will be set to the sendrecv state.
*
goto done;
}
- /* If this frame is writing an audio or video frame get the stream information */
- if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) {
- /* Initially use the default stream unless an explicit stream is provided */
- stream = default_stream = ast_channel_get_default_stream(chan, ast_format_get_type(fr->subclass.format));
+ if (stream_num >= 0) {
+ /* If we were told to write to an explicit stream then allow this frame through, no matter
+ * if the type is expected or not (a framehook could change)
+ */
+ if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) {
+ goto done;
+ }
+ stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num);
+ default_stream = ast_channel_get_default_stream(chan, ast_stream_get_type(stream));
+ } else if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO || fr->frametype == AST_FRAME_MODEM) {
+ /* If we haven't been told of a stream then we need to figure out which once we need */
+ enum ast_media_type type = AST_MEDIA_TYPE_UNKNOWN;
- if (stream_num >= 0) {
- if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) {
- goto done;
- }
- stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num);
+ /* Some frame types have a fixed media type */
+ if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) {
+ type = ast_format_get_type(fr->subclass.format);
+ } else if (fr->frametype == AST_FRAME_MODEM) {
+ type = AST_MEDIA_TYPE_IMAGE;
}
+
+ /* No stream was specified, so use the default one */
+ stream = default_stream = ast_channel_get_default_stream(chan, type);
}
/* Perform the framehook write event here. After the frame enters the framehook list
res = ast_channel_tech(chan)->write_video(chan, fr);
} else {
res = 0;
-
}
break;
case AST_FRAME_MODEM:
- res = (ast_channel_tech(chan)->write == NULL) ? 0 :
- ast_channel_tech(chan)->write(chan, fr);
+ if (ast_channel_tech(chan)->write_stream) {
+ res = ast_channel_tech(chan)->write_stream(chan, ast_stream_get_position(stream), fr);
+ } else if ((stream == default_stream) && ast_channel_tech(chan)->write) {
+ res = ast_channel_tech(chan)->write(chan, fr);
+ } else {
+ res = 0;
+ }
break;
case AST_FRAME_VOICE:
if (ast_opt_generic_plc && ast_format_cmp(fr->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
return -1;
}
+ if (ast_stream_topology_equal(ast_channel_get_stream_topology(chan), topology)) {
+ ast_debug(3, "Topology of %s already matches what is requested so ignoring topology change request\n",
+ ast_channel_name(chan));
+ return 0;
+ }
+
ast_channel_internal_set_stream_topology_change_source(chan, change_source);
return ast_channel_tech(chan)->indicate(chan, AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE, topology, sizeof(topology));
return new_topology;
}
+int ast_stream_topology_equal(const struct ast_stream_topology *left,
+ const struct ast_stream_topology *right)
+{
+ int index;
+
+ ast_assert(left != NULL);
+ ast_assert(right != NULL);
+
+ if (ast_stream_topology_get_count(left) != ast_stream_topology_get_count(right)) {
+ return 0;
+ }
+
+ for (index = 0; index < ast_stream_topology_get_count(left); ++index) {
+ const struct ast_stream *left_stream = ast_stream_topology_get_stream(left, index);
+ const struct ast_stream *right_stream = ast_stream_topology_get_stream(right, index);
+
+ if (ast_stream_get_type(left_stream) != ast_stream_get_type(right_stream)) {
+ return 0;
+ }
+
+ if (ast_stream_get_state(left_stream) != ast_stream_get_state(right_stream)) {
+ return 0;
+ }
+
+ if (!ast_stream_get_formats(left_stream) && ast_stream_get_formats(right_stream) &&
+ ast_format_cap_count(ast_stream_get_formats(right_stream))) {
+ /* A NULL format capabilities and an empty format capabilities are the same, as they have
+ * no formats inside. If one does though... they are not equal.
+ */
+ return 0;
+ } else if (!ast_stream_get_formats(right_stream) && ast_stream_get_formats(left_stream) &&
+ ast_format_cap_count(ast_stream_get_formats(left_stream))) {
+ return 0;
+ } else if (ast_stream_get_formats(left_stream) && ast_stream_get_formats(right_stream) &&
+ !ast_format_cap_identical(ast_stream_get_formats(left_stream), ast_stream_get_formats(right_stream))) {
+ /* But if both are actually present we need to do an actual identical check. */
+ return 0;
+ }
+
+ if (strcmp(ast_stream_get_name(left_stream), ast_stream_get_name(right_stream))) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
void ast_stream_topology_free(struct ast_stream_topology *topology)
{
if (!topology) {
on Ringing when already INUSE.
</para></description>
</configOption>
+ <configOption name="max_audio_streams" default="1">
+ <synopsis>The maximum number of allowed audio streams for the endpoint</synopsis>
+ <description><para>
+ This option enforces a limit on the maximum simultaneous negotiated audio
+ streams allowed for the endpoint.
+ </para></description>
+ </configOption>
+ <configOption name="max_video_streams" default="1">
+ <synopsis>The maximum number of allowed video streams for the endpoint</synopsis>
+ <description><para>
+ This option enforces a limit on the maximum simultaneous negotiated video
+ streams allowed for the endpoint.
+ </para></description>
+ </configOption>
</configObject>
<configObject name="auth">
<synopsis>Authentication type</synopsis>
#include "asterisk/test.h"
#include "asterisk/statsd.h"
#include "asterisk/pbx.h"
+#include "asterisk/stream.h"
/*! \brief Number of buckets for persistent endpoint information */
#define PERSISTENT_BUCKETS 53
return -1;
}
+ endpoint->media.topology = ast_stream_topology_create_from_format_cap(endpoint->media.codecs);
+ if (!endpoint->media.topology) {
+ return -1;
+ }
+
return 0;
}
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "refer_blind_progress", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, refer_blind_progress));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_audio_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_audio_streams));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_video_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_video_streams));
if (ast_sip_initialize_sorcery_transport()) {
ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
ast_string_field_free_memory(endpoint);
- ao2_ref(endpoint->media.codecs, -1);
+ ao2_cleanup(endpoint->media.codecs);
+ ast_stream_topology_free(endpoint->media.topology);
subscription_configuration_destroy(&endpoint->subscription);
info_configuration_destroy(&endpoint->info);
media_configuration_destroy(&endpoint->media);
#include "asterisk/sdp_srtp.h"
#include "asterisk/dsp.h"
#include "asterisk/linkedlists.h" /* for AST_LIST_NEXT */
+#include "asterisk/stream.h"
+#include "asterisk/format_cache.h"
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
static struct ast_sockaddr address_rtp;
static const char STR_AUDIO[] = "audio";
-static const int FD_AUDIO = 0;
-
static const char STR_VIDEO[] = "video";
-static const int FD_VIDEO = 2;
-
-/*! \brief Retrieves an ast_format_type based on the given stream_type */
-static enum ast_media_type stream_to_media_type(const char *stream_type)
-{
- if (!strcasecmp(stream_type, STR_AUDIO)) {
- return AST_MEDIA_TYPE_AUDIO;
- } else if (!strcasecmp(stream_type, STR_VIDEO)) {
- return AST_MEDIA_TYPE_VIDEO;
- }
-
- return 0;
-}
-
-/*! \brief Get the starting descriptor for a media type */
-static int media_type_to_fdno(enum ast_media_type media_type)
-{
- switch (media_type) {
- case AST_MEDIA_TYPE_AUDIO: return FD_AUDIO;
- case AST_MEDIA_TYPE_VIDEO: return FD_VIDEO;
- case AST_MEDIA_TYPE_TEXT:
- case AST_MEDIA_TYPE_UNKNOWN:
- case AST_MEDIA_TYPE_IMAGE:
- case AST_MEDIA_TYPE_END: break;
- }
- return -1;
-}
-
-/*! \brief Remove all other cap types but the one given */
-static void format_cap_only_type(struct ast_format_cap *caps, enum ast_media_type media_type)
-{
- int i = 0;
- while (i <= AST_MEDIA_TYPE_TEXT) {
- if (i != media_type && i != AST_MEDIA_TYPE_UNKNOWN) {
- ast_format_cap_remove_by_type(caps, i);
- }
- i += 1;
- }
-}
static int send_keepalive(const void *data)
{
ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);
}
- if (!strcmp(session_media->stream_type, STR_AUDIO) &&
+ if (session_media->type == AST_MEDIA_TYPE_AUDIO &&
(session->endpoint->media.tos_audio || session->endpoint->media.cos_audio)) {
ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_audio,
session->endpoint->media.cos_audio, "SIP RTP Audio");
- } else if (!strcmp(session_media->stream_type, STR_VIDEO) &&
+ } else if (session_media->type == AST_MEDIA_TYPE_VIDEO &&
(session->endpoint->media.tos_video || session->endpoint->media.cos_video)) {
ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video,
session->endpoint->media.cos_video, "SIP RTP Video");
static int set_caps(struct ast_sip_session *session,
struct ast_sip_session_media *session_media,
const struct pjmedia_sdp_media *stream,
- int is_offer)
+ int is_offer, struct ast_stream *asterisk_stream)
{
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
RAII_VAR(struct ast_format_cap *, peer, NULL, ao2_cleanup);
RAII_VAR(struct ast_format_cap *, joint, NULL, ao2_cleanup);
- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+ RAII_VAR(struct ast_format_cap *, endpoint_caps, NULL, ao2_cleanup);
+ enum ast_media_type media_type = session_media->type;
struct ast_rtp_codecs codecs = AST_RTP_CODECS_NULL_INIT;
int fmts = 0;
int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
!(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
!(joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
- ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);
+ ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
+ ast_codec_media_type2str(session_media->type));
return -1;
}
/* get the endpoint capabilities */
if (direct_media_enabled) {
ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);
- format_cap_only_type(caps, media_type);
} else {
ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type);
}
ast_rtp_codecs_payloads_destroy(&codecs);
ast_log(LOG_NOTICE, "No joint capabilities for '%s' media stream between our configuration(%s) and incoming SDP(%s)\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
ast_format_cap_get_names(caps, &usbuf),
ast_format_cap_get_names(peer, &thembuf));
return -1;
ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),
session_media->rtp);
- ast_format_cap_append_from_cap(session->req_caps, joint, AST_MEDIA_TYPE_UNKNOWN);
+ ast_stream_set_formats(asterisk_stream, joint);
- if (session->channel) {
+ if (session->channel && ast_sip_session_is_pending_stream_default(session, asterisk_stream)) {
ast_channel_lock(session->channel);
ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_UNKNOWN);
ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel),
}
/*! \brief Function which negotiates an incoming media stream */
-static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
+static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
+ struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,
+ int index, struct ast_stream *asterisk_stream)
{
char host[NI_MAXHOST];
RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+ pjmedia_sdp_media *stream = sdp->media[index];
+ enum ast_media_type media_type = session_media->type;
enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
int res;
- /* If port is 0, ignore this media stream */
- if (!stream->desc.port) {
- ast_debug(3, "Media stream '%s' is already declined\n", session_media->stream_type);
- return 0;
- }
-
/* If no type formats have been configured reject this stream */
if (!ast_format_cap_has_type(session->endpoint->media.codecs, media_type)) {
- ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n", session_media->stream_type);
+ ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n",
+ ast_codec_media_type2str(session_media->type));
return 0;
}
pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);
}
- if (set_caps(session, session_media, stream, 1)) {
+ if (set_caps(session, session_media, stream, 1, asterisk_stream)) {
return 0;
}
return 1;
/*! \brief Function which creates an outgoing stream */
static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- struct pjmedia_sdp_session *sdp)
+ struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, struct ast_stream *stream)
{
pj_pool_t *pool = session->inv_session->pool_prov;
+ static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
static const pj_str_t STR_IN = { "IN", 2 };
static const pj_str_t STR_IP4 = { "IP4", 3};
static const pj_str_t STR_IP6 = { "IP6", 3};
int min_packet_size = 0, max_packet_size = 0;
int rtp_code;
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
- int use_override_prefs = ast_format_cap_count(session->req_caps);
+ enum ast_media_type media_type = session_media->type;
int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
ast_format_cap_count(session->direct_media_cap);
- if ((use_override_prefs && !ast_format_cap_has_type(session->req_caps, media_type)) ||
- (!use_override_prefs && !ast_format_cap_has_type(session->endpoint->media.codecs, media_type))) {
- /* If no type formats are configured don't add a stream */
- return 0;
- } else if (!session_media->rtp && create_rtp(session, session_media)) {
+ media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media));
+ if (!media) {
return -1;
}
+ pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));
- set_ice_components(session, session_media);
- enable_rtcp(session, session_media, NULL);
+ /* If this is a removed (or declined) stream OR if no formats exist then construct a minimal stream in SDP */
+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED || !ast_stream_get_formats(stream) ||
+ !ast_format_cap_count(ast_stream_get_formats(stream))) {
+ media->desc.port = 0;
+ media->desc.port_count = 1;
+
+ if (remote) {
+ pjmedia_sdp_media *remote_media = remote->media[ast_stream_get_position(stream)];
+ int index;
+
+ media->desc.transport = remote_media->desc.transport;
+
+ /* Preserve existing behavior by copying the formats provided from the offer */
+ for (index = 0; index < remote_media->desc.fmt_count; ++index) {
+ media->desc.fmt[index] = remote_media->desc.fmt[index];
+ }
+ media->desc.fmt_count = remote_media->desc.fmt_count;
+ } else {
+ /* This is actually an offer so put a dummy payload in that is ignored and sane transport */
+ media->desc.transport = STR_RTP_AVP;
+ pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], "32");
+ }
+
+ sdp->media[sdp->media_count++] = media;
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+
+ return 1;
+ }
- if (!(media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media))) ||
- !(media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)))) {
+ if (!session_media->rtp && create_rtp(session, session_media)) {
return -1;
}
+ set_ice_components(session, session_media);
+ enable_rtcp(session, session_media, NULL);
+
+ /* Crypto has to be added before setting the media transport so that SRTP is properly
+ * set up according to the configuration. This ends up changing the media transport.
+ */
if (add_crypto_to_stream(session, session_media, pool, media)) {
return -1;
}
- media->desc.media = pj_str(session_media->stream_type);
if (pj_strlen(&session_media->transport)) {
/* If a transport has already been specified use it */
media->desc.transport = session_media->transport;
session->endpoint->media.rtp.force_avp));
}
+ media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
+ if (!media->conn) {
+ return -1;
+ }
+
/* Add connection level details */
if (direct_media_enabled) {
hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR);
}
if (ast_strlen_zero(hostip)) {
- ast_log(LOG_ERROR, "No local host IP available for stream %s\n", session_media->stream_type);
+ ast_log(LOG_ERROR, "No local host IP available for stream %s\n",
+ ast_codec_media_type2str(session_media->type));
return -1;
}
}
}
+ /* Add ICE attributes and candidates */
+ add_ice_to_stream(session, session_media, pool, media);
+
ast_rtp_instance_get_local_address(session_media->rtp, &addr);
media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr);
media->desc.port_count = 1;
- /* Add ICE attributes and candidates */
- add_ice_to_stream(session, session_media, pool, media);
-
if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
- ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);
+ ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
+ ast_codec_media_type2str(session_media->type));
return -1;
}
if (direct_media_enabled) {
ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);
- } else if (!ast_format_cap_count(session->req_caps) ||
- !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) {
- ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type);
} else {
- ast_format_cap_append_from_cap(caps, session->req_caps, media_type);
+ ast_format_cap_append_from_cap(caps, ast_stream_get_formats(stream), media_type);
}
for (index = 0; index < ast_format_cap_count(caps); ++index) {
}
/* Add non-codec formats */
- if (media_type != AST_MEDIA_TYPE_VIDEO && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) {
+ if (ast_sip_session_is_pending_stream_default(session, stream) && media_type != AST_MEDIA_TYPE_VIDEO
+ && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) {
for (index = 1LL; index <= AST_RTP_MAX; index <<= 1) {
if (!(noncodec & index)) {
continue;
return 1;
}
-static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
- const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)
+static struct ast_frame *media_session_rtp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+ struct ast_frame *f;
+
+ if (!session_media->rtp) {
+ return &ast_null_frame;
+ }
+
+ f = ast_rtp_instance_read(session_media->rtp, 0);
+ if (!f) {
+ return NULL;
+ }
+
+ ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL));
+
+ return f;
+}
+
+static struct ast_frame *media_session_rtcp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+ struct ast_frame *f;
+
+ if (!session_media->rtp) {
+ return &ast_null_frame;
+ }
+
+ f = ast_rtp_instance_read(session_media->rtp, 1);
+ if (!f) {
+ return NULL;
+ }
+
+ ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL));
+
+ return f;
+}
+
+static int media_session_rtp_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame)
+{
+ if (!session_media->rtp) {
+ return 0;
+ }
+
+ return ast_rtp_instance_write(session_media->rtp, frame);
+}
+
+static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local,
+ const struct pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
{
RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+ struct pjmedia_sdp_media *remote_stream = remote->media[index];
+ enum ast_media_type media_type = session_media->type;
char host[NI_MAXHOST];
- int fdno, res;
+ int res;
if (!session->channel) {
return 1;
}
- if (!local_stream->desc.port || !remote_stream->desc.port) {
- return 1;
- }
-
/* Ensure incoming transport is compatible with the endpoint's configuration */
if (!session->endpoint->media.rtp.use_received_transport &&
check_endpoint_media_transport(session->endpoint, remote_stream) == AST_SIP_MEDIA_TRANSPORT_INVALID) {
/* Apply connection information to the RTP instance */
ast_sockaddr_set_port(addrs, remote_stream->desc.port);
ast_rtp_instance_set_remote_address(session_media->rtp, addrs);
- if (set_caps(session, session_media, remote_stream, 0)) {
+ if (set_caps(session, session_media, remote_stream, 0, asterisk_stream)) {
return 1;
}
- if ((fdno = media_type_to_fdno(media_type)) < 0) {
- return -1;
- }
- ast_channel_set_fd(session->channel, fdno, ast_rtp_instance_fd(session_media->rtp, 0));
+ ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback);
+ ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0),
+ media_session_rtp_read_callback);
if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) {
- ast_channel_set_fd(session->channel, fdno + 1, ast_rtp_instance_fd(session_media->rtp, 1));
+ ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1),
+ media_session_rtcp_read_callback);
}
/* If ICE support is enabled find all the needed attributes */
process_ice_attributes(session, session_media, remote, remote_stream);
+ /* Set the channel uniqueid on the RTP instance now that it is becoming active */
+ ast_channel_lock(session->channel);
+ ast_rtp_instance_set_channel_id(session_media->rtp, ast_channel_uniqueid(session->channel));
+ ast_channel_unlock(session->channel);
+
/* Ensure the RTP instance is active */
ast_rtp_instance_activate(session_media->rtp);
session_media->encryption = session->endpoint->media.rtp.encryption;
if (session->endpoint->media.rtp.keepalive > 0 &&
- stream_to_media_type(session_media->stream_type) == AST_MEDIA_TYPE_AUDIO) {
+ session_media->type == AST_MEDIA_TYPE_AUDIO) {
ast_rtp_instance_set_keepalive(session_media->rtp, session->endpoint->media.rtp.keepalive);
/* Schedule the initial keepalive early in case this is being used to punch holes through
* a NAT. This way there won't be an awkward delay before media starts flowing in some
#include "asterisk/features_config.h"
#include "asterisk/pickup.h"
#include "asterisk/test.h"
+#include "asterisk/stream.h"
#define SDP_HANDLER_BUCKETS 11
#define MOD_DATA_ON_RESPONSE "on_response"
#define MOD_DATA_NAT_HOOK "nat_hook"
+/* Most common case is one audio and one video stream */
+#define DEFAULT_NUM_SESSION_MEDIA 2
+
/* Some forward declarations */
static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata);
static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata,
return strcmp(handler_list1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;
}
-static int session_media_hash(const void *obj, int flags)
-{
- const struct ast_sip_session_media *session_media = obj;
- const char *stream_type = flags & OBJ_KEY ? obj : session_media->stream_type;
-
- return ast_str_hash(stream_type);
-}
-
-static int session_media_cmp(void *obj, void *arg, int flags)
-{
- struct ast_sip_session_media *session_media1 = obj;
- struct ast_sip_session_media *session_media2 = arg;
- const char *stream_type2 = flags & OBJ_KEY ? arg : session_media2->stream_type;
-
- return strcmp(session_media1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;
-}
-
int ast_sip_session_register_sdp_handler(struct ast_sip_session_sdp_handler *handler, const char *stream_type)
{
RAII_VAR(struct sdp_handler_list *, handler_list,
ao2_callback_data(sdp_handlers, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, remove_handler, (void *)stream_type, handler);
}
+struct ast_sip_session_media_state *ast_sip_session_media_state_alloc(void)
+{
+ struct ast_sip_session_media_state *media_state;
+
+ media_state = ast_calloc(1, sizeof(*media_state));
+ if (!media_state) {
+ return NULL;
+ }
+
+ if (AST_VECTOR_INIT(&media_state->sessions, DEFAULT_NUM_SESSION_MEDIA) < 0) {
+ ast_free(media_state);
+ return NULL;
+ }
+
+ if (AST_VECTOR_INIT(&media_state->read_callbacks, DEFAULT_NUM_SESSION_MEDIA) < 0) {
+ AST_VECTOR_FREE(&media_state->sessions);
+ ast_free(media_state);
+ return NULL;
+ }
+
+ return media_state;
+}
+
+void ast_sip_session_media_state_reset(struct ast_sip_session_media_state *media_state)
+{
+ int index;
+
+ if (!media_state) {
+ return;
+ }
+
+ AST_VECTOR_RESET(&media_state->sessions, ao2_cleanup);
+ AST_VECTOR_RESET(&media_state->read_callbacks, AST_VECTOR_ELEM_CLEANUP_NOOP);
+
+ for (index = 0; index < AST_MEDIA_TYPE_END; ++index) {
+ media_state->default_session[index] = NULL;
+ }
+
+ ast_stream_topology_free(media_state->topology);
+ media_state->topology = NULL;
+}
+
+struct ast_sip_session_media_state *ast_sip_session_media_state_clone(const struct ast_sip_session_media_state *media_state)
+{
+ struct ast_sip_session_media_state *cloned;
+ int index;
+
+ if (!media_state) {
+ return NULL;
+ }
+
+ cloned = ast_sip_session_media_state_alloc();
+ if (!cloned) {
+ return NULL;
+ }
+
+ if (media_state->topology) {
+ cloned->topology = ast_stream_topology_clone(media_state->topology);
+ if (!cloned->topology) {
+ ast_sip_session_media_state_free(cloned);
+ return NULL;
+ }
+ }
+
+ for (index = 0; index < AST_VECTOR_SIZE(&media_state->sessions); ++index) {
+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index);
+ enum ast_media_type type = ast_stream_get_type(ast_stream_topology_get_stream(cloned->topology, index));
+
+ AST_VECTOR_REPLACE(&cloned->sessions, index, ao2_bump(session_media));
+ if (ast_stream_get_state(ast_stream_topology_get_stream(cloned->topology, index)) != AST_STREAM_STATE_REMOVED &&
+ !cloned->default_session[type]) {
+ cloned->default_session[type] = session_media;
+ }
+ }
+
+ for (index = 0; index < AST_VECTOR_SIZE(&media_state->read_callbacks); ++index) {
+ struct ast_sip_session_media_read_callback_state *read_callback = AST_VECTOR_GET_ADDR(&media_state->read_callbacks, index);
+
+ AST_VECTOR_REPLACE(&cloned->read_callbacks, index, *read_callback);
+ }
+
+ return cloned;
+}
+
+void ast_sip_session_media_state_free(struct ast_sip_session_media_state *media_state)
+{
+ if (!media_state) {
+ return;
+ }
+
+ /* This will reset the internal state so we only have to free persistent things */
+ ast_sip_session_media_state_reset(media_state);
+
+ AST_VECTOR_FREE(&media_state->sessions);
+ AST_VECTOR_FREE(&media_state->read_callbacks);
+
+ ast_free(media_state);
+}
+
+int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream)
+{
+ int index;
+
+ ast_assert(session->pending_media_state->topology != NULL);
+
+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+ return 0;
+ }
+
+ for (index = 0; index < ast_stream_topology_get_count(session->pending_media_state->topology); ++index) {
+ if (ast_stream_get_type(ast_stream_topology_get_stream(session->pending_media_state->topology, index)) !=
+ ast_stream_get_type(stream)) {
+ continue;
+ }
+
+ return ast_stream_topology_get_stream(session->pending_media_state->topology, index) == stream ? 1 : 0;
+ }
+
+ return 0;
+}
+
+int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ int fd, ast_sip_session_media_read_cb callback)
+{
+ struct ast_sip_session_media_read_callback_state callback_state = {
+ .fd = fd,
+ .read_callback = callback,
+ .session = session_media,
+ };
+
+ /* The contents of the vector are whole structs and not pointers */
+ return AST_VECTOR_APPEND(&session->pending_media_state->read_callbacks, callback_state);
+}
+
+int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ ast_sip_session_media_write_cb callback)
+{
+ if (session_media->write_callback) {
+ if (session_media->write_callback == callback) {
+ return 0;
+ }
+
+ return -1;
+ }
+
+ session_media->write_callback = callback;
+
+ return 0;
+}
+
/*!
* \brief Set an SDP stream handler for a corresponding session media.
*
session_media->handler = handler;
}
+static int stream_destroy(void *obj, void *arg, int flags)
+{
+ struct sdp_handler_list *handler_list = obj;
+ struct ast_sip_session_media *session_media = arg;
+ struct ast_sip_session_sdp_handler *handler;
+
+ AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+ handler->stream_destroy(session_media);
+ }
+
+ return 0;
+}
+
+static void session_media_dtor(void *obj)
+{
+ struct ast_sip_session_media *session_media = obj;
+
+ /* It is possible for multiple handlers to have allocated memory on the
+ * session media (usually through a stream changing types). Therefore, we
+ * traverse all the SDP handlers and let them all call stream_destroy on
+ * the session_media
+ */
+ ao2_callback(sdp_handlers, 0, stream_destroy, session_media);
+
+ if (session_media->srtp) {
+ ast_sdp_srtp_destroy(session_media->srtp);
+ }
+}
+
+struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,
+ struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position)
+{
+ struct ast_sip_session_media *session_media = NULL;
+
+ /* It is possible for this media state to already contain a session for the stream. If this
+ * is the case we simply return it.
+ */
+ if (position < AST_VECTOR_SIZE(&media_state->sessions)) {
+ return AST_VECTOR_GET(&media_state->sessions, position);
+ }
+
+ /* Determine if we can reuse the session media from the active media state if present */
+ if (position < AST_VECTOR_SIZE(&session->active_media_state->sessions)) {
+ session_media = AST_VECTOR_GET(&session->active_media_state->sessions, position);
+ /* A stream can never exist without an accompanying media session */
+ if (session_media->type == type) {
+ ao2_ref(session_media, +1);
+ } else {
+ session_media = NULL;
+ }
+ }
+
+ if (!session_media) {
+ /* No existing media session we can use so create a new one */
+ session_media = ao2_alloc_options(sizeof(*session_media), session_media_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!session_media) {
+ return NULL;
+ }
+
+ session_media->encryption = session->endpoint->media.rtp.encryption;
+ session_media->keepalive_sched_id = -1;
+ session_media->timeout_sched_id = -1;
+ session_media->type = type;
+ session_media->stream_num = position;
+ }
+
+ AST_VECTOR_REPLACE(&media_state->sessions, position, session_media);
+
+ /* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */
+ if (!media_state->default_session[type] &&
+ ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) {
+ media_state->default_session[type] = session_media;
+ }
+
+ return session_media;
+}
+
+static int is_stream_limitation_reached(enum ast_media_type type, const struct ast_sip_endpoint *endpoint, int *type_streams)
+{
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ return !(type_streams[type] < endpoint->media.max_audio_streams);
+ case AST_MEDIA_TYPE_VIDEO:
+ return !(type_streams[type] < endpoint->media.max_video_streams);
+ case AST_MEDIA_TYPE_IMAGE:
+ /* We don't have an option for image (T.38) streams so cap it to one. */
+ return (type_streams[type] > 0);
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ default:
+ /* We don't want any unknown or "other" streams on our endpoint,
+ * so always just say we've reached the limit
+ */
+ return 1;
+ }
+}
+
static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp)
{
int i;
int handled = 0;
+ int type_streams[AST_MEDIA_TYPE_END] = {0};
if (session->inv_session && session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
ast_log(LOG_ERROR, "Failed to handle incoming SDP. Session has been already disconnected\n");
return -1;
}
+ /* It is possible for SDP deferral to have already created a pending topology */
+ if (!session->pending_media_state->topology) {
+ session->pending_media_state->topology = ast_stream_topology_alloc();
+ if (!session->pending_media_state->topology) {
+ return -1;
+ }
+ }
+
for (i = 0; i < sdp->media_count; ++i) {
/* See if there are registered handlers for this media stream type */
char media[20];
struct ast_sip_session_sdp_handler *handler;
RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ struct ast_sip_session_media *session_media = NULL;
int res;
+ enum ast_media_type type;
+ struct ast_stream *stream = NULL;
+ pjmedia_sdp_media *remote_stream = sdp->media[i];
/* We need a null-terminated version of the media string */
ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
+ type = ast_media_type_from_str(media);
+
+ /* See if we have an already existing stream, which can occur from SDP deferral checking */
+ if (i < ast_stream_topology_get_count(session->pending_media_state->topology)) {
+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
+ }
+ if (!stream) {
+ stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
+ if (!stream) {
+ return -1;
+ }
+ ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);
+ }
- session_media = ao2_find(session->media, media, OBJ_KEY);
+ session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i);
if (!session_media) {
- /* if the session_media doesn't exist, there weren't
- * any handlers at the time of its creation */
+ return -1;
+ }
+
+ /* If this stream is already declined mark it as such, or mark it as such if we've reached the limit */
+ if (!remote_stream->desc.port || is_stream_limitation_reached(type, session->endpoint, type_streams)) {
+ ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
+ ast_codec_media_type2str(type), i);
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
continue;
}
if (session_media->handler) {
handler = session_media->handler;
ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
session_media->handler->id);
- res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp,
- sdp->media[i]);
+ res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
if (res < 0) {
/* Catastrophic failure. Abort! */
return -1;
} else if (res > 0) {
ast_debug(1, "Media stream '%s' handled by %s\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
session_media->handler->id);
/* Handled by this handler. Move to the next stream */
handled = 1;
+ ++type_streams[type];
continue;
}
}
continue;
}
ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
handler->id);
- res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp,
- sdp->media[i]);
+ res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
if (res < 0) {
/* Catastrophic failure. Abort! */
return -1;
}
if (res > 0) {
ast_debug(1, "Media stream '%s' handled by %s\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
handler->id);
/* Handled by this handler. Move to the next stream */
session_media_set_handler(session_media, handler);
handled = 1;
+ ++type_streams[type];
break;
}
}
return 0;
}
-struct handle_negotiated_sdp_cb {
- struct ast_sip_session *session;
- const pjmedia_sdp_session *local;
- const pjmedia_sdp_session *remote;
-};
-
-static int handle_negotiated_sdp_session_media(void *obj, void *arg, int flags)
+static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *session_media,
+ struct ast_sip_session *session, const pjmedia_sdp_session *local,
+ const pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
{
- struct ast_sip_session_media *session_media = obj;
- struct handle_negotiated_sdp_cb *callback_data = arg;
- struct ast_sip_session *session = callback_data->session;
- const pjmedia_sdp_session *local = callback_data->local;
- const pjmedia_sdp_session *remote = callback_data->remote;
- int i;
-
- for (i = 0; i < local->media_count; ++i) {
- /* See if there are registered handlers for this media stream type */
- char media[20];
- struct ast_sip_session_sdp_handler *handler;
- RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
- int res;
+ /* See if there are registered handlers for this media stream type */
+ struct pjmedia_sdp_media *local_stream = local->media[index];
+ char media[20];
+ struct ast_sip_session_sdp_handler *handler;
+ RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
+ int res;
- if (!remote->media[i]) {
- continue;
+ /* For backwards compatibility we only reflect the stream state correctly on
+ * the non-default streams. This is because the stream state is also used for
+ * signaling that someone has placed us on hold. This situation is not handled
+ * currently and can result in the remote side being sort of placed on hold too.
+ */
+ if (!ast_sip_session_is_pending_stream_default(session, asterisk_stream)) {
+ /* Determine the state of the stream based on our local SDP */
+ if (pjmedia_sdp_media_find_attr2(local_stream, "sendonly", NULL)) {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDONLY);
+ } else if (pjmedia_sdp_media_find_attr2(local_stream, "recvonly", NULL)) {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_RECVONLY);
+ } else if (pjmedia_sdp_media_find_attr2(local_stream, "inactive", NULL)) {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_INACTIVE);
+ } else {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDRECV);
}
+ } else {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDRECV);
+ }
- /* We need a null-terminated version of the media string */
- ast_copy_pj_str(media, &local->media[i]->desc.media, sizeof(media));
-
- /* stream type doesn't match the one we're looking to fill */
- if (strcasecmp(session_media->stream_type, media)) {
- continue;
- }
+ /* We need a null-terminated version of the media string */
+ ast_copy_pj_str(media, &local->media[index]->desc.media, sizeof(media));
- handler = session_media->handler;
- if (handler) {
- ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
+ handler = session_media->handler;
+ if (handler) {
+ ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+ ast_codec_media_type2str(session_media->type),
+ handler->id);
+ res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
+ if (res >= 0) {
+ ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+ ast_codec_media_type2str(session_media->type),
handler->id);
- res = handler->apply_negotiated_sdp_stream(session, session_media, local,
- local->media[i], remote, remote->media[i]);
- if (res >= 0) {
- ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
- handler->id);
- return CMP_MATCH;
- }
return 0;
}
+ return -1;
+ }
- handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
- if (!handler_list) {
- ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+ handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
+ if (!handler_list) {
+ ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+ return -1;
+ }
+ AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+ if (handler == session_media->handler) {
continue;
}
- AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
- if (handler == session_media->handler) {
- continue;
- }
- ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
+ ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+ ast_codec_media_type2str(session_media->type),
+ handler->id);
+ res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
+ if (res < 0) {
+ /* Catastrophic failure. Abort! */
+ return -1;
+ }
+ if (res > 0) {
+ ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+ ast_codec_media_type2str(session_media->type),
handler->id);
- res = handler->apply_negotiated_sdp_stream(session, session_media, local,
- local->media[i], remote, remote->media[i]);
- if (res < 0) {
- /* Catastrophic failure. Abort! */
- return 0;
- }
- if (res > 0) {
- ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
- handler->id);
- /* Handled by this handler. Move to the next stream */
- session_media_set_handler(session_media, handler);
- return CMP_MATCH;
- }
+ /* Handled by this handler. Move to the next stream */
+ session_media_set_handler(session_media, handler);
+ return 0;
}
}
if (session_media->handler && session_media->handler->stream_stop) {
ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n",
- session_media->stream_type);
+ ast_codec_media_type2str(session_media->type));
session_media->handler->stream_stop(session_media);
}
- return CMP_MATCH;
+ return 0;
}
static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote)
{
- RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);
- struct handle_negotiated_sdp_cb callback_data = {
- .session = session,
- .local = local,
- .remote = remote,
- };
+ int i;
+ struct ast_stream_topology *topology;
- successful = ao2_callback(session->media, OBJ_MULTIPLE, handle_negotiated_sdp_session_media, &callback_data);
- if (successful && ao2_iterator_count(successful) == ao2_container_count(session->media)) {
- /* Nothing experienced a catastrophic failure */
- ast_queue_frame(session->channel, &ast_null_frame);
- return 0;
+ for (i = 0; i < local->media_count; ++i) {
+ struct ast_sip_session_media *session_media;
+ struct ast_stream *stream;
+
+ if (!remote->media[i]) {
+ continue;
+ }
+
+ /* If we're handling negotiated streams, then we should already have set
+ * up session media instances (and Asterisk streams) that correspond to
+ * the local SDP, and there should be the same number of session medias
+ * and streams as there are local SDP streams
+ */
+ ast_assert(i < AST_VECTOR_SIZE(&session->pending_media_state->sessions));
+ ast_assert(i < ast_stream_topology_get_count(session->pending_media_state->topology));
+
+ session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, i);
+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
+
+ /* The stream state will have already been set to removed when either we
+ * negotiate the incoming SDP stream or when we produce our own local SDP.
+ * This can occur if an internal thing has requested it to be removed, or if
+ * we remove it as a result of the stream limit being reached.
+ */
+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+ continue;
+ }
+
+ if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i, stream)) {
+ return -1;
+ }
}
- return -1;
+
+ /* Apply the pending media state to the channel and make it active */
+ ast_channel_lock(session->channel);
+
+ /* Update the topology on the channel to match the accepted one */
+ topology = ast_stream_topology_clone(session->pending_media_state->topology);
+ if (topology) {
+ ast_channel_set_stream_topology(session->channel, topology);
+ }
+
+ /* Remove all current file descriptors from the channel */
+ for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++i) {
+ ast_channel_internal_fd_clear(session->channel, i + AST_EXTENDED_FDS);
+ }
+
+ /* Add all the file descriptors from the pending media state */
+ for (i = 0; i < AST_VECTOR_SIZE(&session->pending_media_state->read_callbacks); ++i) {
+ struct ast_sip_session_media_read_callback_state *callback_state = AST_VECTOR_GET_ADDR(&session->pending_media_state->read_callbacks, i);
+
+ ast_channel_internal_fd_set(session->channel, i + AST_EXTENDED_FDS, callback_state->fd);
+ }
+
+ /* Active and pending flip flop as needed */
+ SWAP(session->active_media_state, session->pending_media_state);
+ ast_sip_session_media_state_reset(session->pending_media_state);
+
+ ast_channel_unlock(session->channel);
+
+ ast_queue_frame(session->channel, &ast_null_frame);
+
+ return 0;
}
AST_RWLIST_HEAD_STATIC(session_supplements, ast_sip_session_supplement);
ast_sip_session_response_cb on_response;
/*! Whether to generate new SDP */
int generate_new_sdp;
+ /*! Requested media state for the SDP */
+ struct ast_sip_session_media_state *media_state;
AST_LIST_ENTRY(ast_sip_session_delayed_request) next;
};
ast_sip_session_request_creation_cb on_request_creation,
ast_sip_session_sdp_creation_cb on_sdp_creation,
ast_sip_session_response_cb on_response,
- int generate_new_sdp)
+ int generate_new_sdp,
+ struct ast_sip_session_media_state *media_state)
{
struct ast_sip_session_delayed_request *delay = ast_calloc(1, sizeof(*delay));
delay->on_sdp_creation = on_sdp_creation;
delay->on_response = on_response;
delay->generate_new_sdp = generate_new_sdp;
+ delay->media_state = media_state;
return delay;
}
+static void delayed_request_free(struct ast_sip_session_delayed_request *delay)
+{
+ ast_sip_session_media_state_free(delay->media_state);
+ ast_free(delay);
+}
+
static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_session_delayed_request *delay)
{
ast_debug(3, "Endpoint '%s(%s)' sending delayed %s request.\n",
case DELAYED_METHOD_INVITE:
ast_sip_session_refresh(session, delay->on_request_creation,
delay->on_sdp_creation, delay->on_response,
- AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp);
+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->media_state);
+ /* Ownership of media state transitions to ast_sip_session_refresh */
+ delay->media_state = NULL;
return 0;
case DELAYED_METHOD_UPDATE:
ast_sip_session_refresh(session, delay->on_request_creation,
delay->on_sdp_creation, delay->on_response,
- AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp);
+ AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->media_state);
+ /* Ownership of media state transitions to ast_sip_session_refresh */
+ delay->media_state = NULL;
return 0;
case DELAYED_METHOD_BYE:
ast_sip_session_terminate(session, 0);
case DELAYED_METHOD_UPDATE:
AST_LIST_REMOVE_CURRENT(next);
res = send_delayed_request(session, delay);
- ast_free(delay);
+ delayed_request_free(delay);
found = 1;
break;
case DELAYED_METHOD_BYE:
if (found) {
AST_LIST_REMOVE_CURRENT(next);
res = send_delayed_request(session, delay);
- ast_free(delay);
+ delayed_request_free(delay);
break;
}
}
ast_sip_session_sdp_creation_cb on_sdp_creation,
ast_sip_session_response_cb on_response,
int generate_new_sdp,
- enum delayed_method method)
+ enum delayed_method method,
+ struct ast_sip_session_media_state *media_state)
{
struct ast_sip_session_delayed_request *delay = delayed_request_alloc(method,
- on_request, on_sdp_creation, on_response, generate_new_sdp);
+ on_request, on_sdp_creation, on_response, generate_new_sdp, media_state);
if (!delay) {
+ ast_sip_session_media_state_free(media_state);
return -1;
}
ast_sip_session_request_creation_cb on_request_creation,
ast_sip_session_sdp_creation_cb on_sdp_creation,
ast_sip_session_response_cb on_response,
- enum ast_sip_session_refresh_method method, int generate_new_sdp)
+ enum ast_sip_session_refresh_method method, int generate_new_sdp,
+ struct ast_sip_session_media_state *media_state)
{
pjsip_inv_session *inv_session = session->inv_session;
pjmedia_sdp_session *new_sdp = NULL;
pjsip_tx_data *tdata;
+ if (media_state && (!media_state->topology || !generate_new_sdp)) {
+ ast_sip_session_media_state_free(media_state);
+ return -1;
+ }
+
if (inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
/* Don't try to do anything with a hung-up call */
ast_debug(3, "Not sending reinvite to %s because of disconnected state...\n",
ast_sorcery_object_get_id(session->endpoint));
+ ast_sip_session_media_state_free(media_state);
return 0;
}
return delay_request(session, on_request_creation, on_sdp_creation, on_response,
generate_new_sdp,
method == AST_SIP_SESSION_REFRESH_METHOD_INVITE
- ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE);
+ ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE,
+ media_state);
}
if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
ast_debug(3, "Delay sending reinvite to %s because of outstanding transaction...\n",
ast_sorcery_object_get_id(session->endpoint));
return delay_request(session, on_request_creation, on_sdp_creation,
- on_response, generate_new_sdp, DELAYED_METHOD_INVITE);
+ on_response, generate_new_sdp, DELAYED_METHOD_INVITE, media_state);
} else if (inv_session->state != PJSIP_INV_STATE_CONFIRMED) {
/* Initial INVITE transaction failed to progress us to a confirmed state
* which means re-invites are not possible
*/
ast_debug(3, "Not sending reinvite to %s because not in confirmed state...\n",
ast_sorcery_object_get_id(session->endpoint));
+ ast_sip_session_media_state_free(media_state);
return 0;
}
}
return delay_request(session, on_request_creation, on_sdp_creation,
on_response, generate_new_sdp,
method == AST_SIP_SESSION_REFRESH_METHOD_INVITE
- ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE);
+ ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, media_state);
+ }
+
+ /* If an explicitly requested media state has been provided use it instead of any pending one */
+ if (media_state) {
+ int index;
+ int type_streams[AST_MEDIA_TYPE_END] = {0};
+ struct ast_stream *stream;
+
+ /* Prune the media state so the number of streams fit within the configured limits - we do it here
+ * so that the index of the resulting streams in the SDP match. If we simply left the streams out
+ * of the SDP when producing it we'd be in trouble. We also enforce formats here for media types that
+ * are configurable on the endpoint.
+ */
+ for (index = 0; index < ast_stream_topology_get_count(media_state->topology); ++index) {
+ stream = ast_stream_topology_get_stream(media_state->topology, index);
+
+ if (is_stream_limitation_reached(ast_stream_get_type(stream), session->endpoint, type_streams)) {
+ if (index < AST_VECTOR_SIZE(&media_state->sessions)) {
+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index);
+
+ ao2_cleanup(session_media);
+ AST_VECTOR_REMOVE(&media_state->sessions, index, 1);
+ }
+
+ ast_stream_topology_del_stream(media_state->topology, index);
+
+ /* A stream has potentially moved into our spot so we need to jump back so we process it */
+ index -= 1;
+ continue;
+ }
+
+
+ /* Enforce the configured allowed codecs on audio and video streams */
+ if (ast_stream_get_type(stream) == AST_MEDIA_TYPE_AUDIO || ast_stream_get_type(stream) == AST_MEDIA_TYPE_VIDEO) {
+ struct ast_format_cap *joint_cap;
+
+ joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!joint_cap) {
+ ast_sip_session_media_state_free(media_state);
+ return 0;
+ }
+
+ ast_format_cap_get_compatible(ast_stream_get_formats(stream), session->endpoint->media.codecs, joint_cap);
+ if (!ast_format_cap_count(joint_cap)) {
+ ao2_ref(joint_cap, -1);
+ ast_sip_session_media_state_free(media_state);
+ return 0;
+ }
+
+ ast_stream_set_formats(stream, joint_cap);
+ }
+
+ ++type_streams[ast_stream_get_type(stream)];
+ }
+
+ if (session->active_media_state->topology) {
+ /* SDP is a fun thing. Take for example the fact that streams are never removed. They just become
+ * declined. To better handle this in the case where something requests a topology change for fewer
+ * streams than are currently present we fill in the topology to match the current number of streams
+ * that are active.
+ */
+ for (index = ast_stream_topology_get_count(media_state->topology);
+ index < ast_stream_topology_get_count(session->active_media_state->topology); ++index) {
+ struct ast_stream *cloned;
+
+ stream = ast_stream_topology_get_stream(session->active_media_state->topology, index);
+ ast_assert(stream != NULL);
+
+ cloned = ast_stream_clone(stream, NULL);
+ if (!cloned) {
+ ast_sip_session_media_state_free(media_state);
+ return -1;
+ }
+
+ ast_stream_set_state(cloned, AST_STREAM_STATE_REMOVED);
+ ast_stream_topology_append_stream(media_state->topology, cloned);
+ }
+
+ /* If the resulting media state matches the existing active state don't bother doing a session refresh */
+ if (ast_stream_topology_equal(session->active_media_state->topology, media_state->topology)) {
+ ast_sip_session_media_state_free(media_state);
+ return 0;
+ }
+ }
+
+ ast_sip_session_media_state_free(session->pending_media_state);
+ session->pending_media_state = media_state;
}
new_sdp = generate_session_refresh_sdp(session);
if (!new_sdp) {
ast_log(LOG_ERROR, "Failed to generate session refresh SDP. Not sending session refresh\n");
+ ast_sip_session_media_state_reset(session->pending_media_state);
return -1;
}
if (on_sdp_creation) {
if (on_sdp_creation(session, new_sdp)) {
+ ast_sip_session_media_state_reset(session->pending_media_state);
return -1;
}
}
}
-
if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
if (pjsip_inv_reinvite(inv_session, NULL, new_sdp, &tdata)) {
ast_log(LOG_WARNING, "Failed to create reinvite properly.\n");
+ if (generate_new_sdp) {
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
return -1;
}
} else if (pjsip_inv_update(inv_session, NULL, new_sdp, &tdata)) {
ast_log(LOG_WARNING, "Failed to create UPDATE properly.\n");
+ if (generate_new_sdp) {
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
return -1;
}
if (on_request_creation) {
if (on_request_creation(session, tdata)) {
+ if (generate_new_sdp) {
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
return -1;
}
}
{
int i;
+ if (!session->pending_media_state->topology) {
+ session->pending_media_state->topology = ast_stream_topology_alloc();
+ if (!session->pending_media_state->topology) {
+ return -1;
+ }
+ }
+
for (i = 0; i < sdp->media_count; ++i) {
/* See if there are registered handlers for this media stream type */
char media[20];
struct ast_sip_session_sdp_handler *handler;
RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ struct ast_stream *stream;
+ enum ast_media_type type;
+ struct ast_sip_session_media *session_media = NULL;
enum ast_sip_session_sdp_stream_defer res;
/* We need a null-terminated version of the media string */
ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
- session_media = ao2_find(session->media, media, OBJ_KEY);
+ type = ast_media_type_from_str(media);
+ stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
+ if (!stream) {
+ return -1;
+ }
+
+ /* As this is only called on an incoming SDP offer before processing it is not possible
+ * for streams and their media sessions to exist.
+ */
+ ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);
+
+ session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i);
if (!session_media) {
- /* if the session_media doesn't exist, there weren't
- * any handlers at the time of its creation */
- continue;
+ return -1;
}
if (session_media->handler) {
return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
}
-static void session_media_dtor(void *obj)
-{
- struct ast_sip_session_media *session_media = obj;
- struct sdp_handler_list *handler_list;
- /* It is possible for SDP handlers to allocate memory on a session_media but
- * not end up getting set as the handler for this session_media. This traversal
- * ensures that all memory allocated by SDP handlers on the session_media is
- * cleared (as well as file descriptors, etc.).
- */
- handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);
- if (handler_list) {
- struct ast_sip_session_sdp_handler *handler;
-
- AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
- handler->stream_destroy(session_media);
- }
- }
- ao2_cleanup(handler_list);
- if (session_media->srtp) {
- ast_sdp_srtp_destroy(session_media->srtp);
- }
-}
-
static void session_destructor(void *obj)
{
struct ast_sip_session *session = obj;
ast_taskprocessor_unreference(session->serializer);
ao2_cleanup(session->datastores);
- ao2_cleanup(session->media);
+ ast_sip_session_media_state_free(session->active_media_state);
+ ast_sip_session_media_state_free(session->pending_media_state);
AST_LIST_HEAD_DESTROY(&session->supplements);
while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {
- ast_free(delay);
+ delayed_request_free(delay);
}
ast_party_id_free(&session->id);
ao2_cleanup(session->endpoint);
ao2_cleanup(session->aor);
ao2_cleanup(session->contact);
- ao2_cleanup(session->req_caps);
ao2_cleanup(session->direct_media_cap);
ast_dsp_free(session->dsp);
return 0;
}
-static int add_session_media(void *obj, void *arg, int flags)
-{
- struct sdp_handler_list *handler_list = obj;
- struct ast_sip_session *session = arg;
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
-
- session_media = ao2_alloc(sizeof(*session_media) + strlen(handler_list->stream_type), session_media_dtor);
- if (!session_media) {
- return CMP_STOP;
- }
- session_media->encryption = session->endpoint->media.rtp.encryption;
- session_media->keepalive_sched_id = -1;
- session_media->timeout_sched_id = -1;
- /* Safe use of strcpy */
- strcpy(session_media->stream_type, handler_list->stream_type);
- ao2_link(session->media, session_media);
- return 0;
-}
-
/*! \brief Destructor for SIP channel */
static void sip_channel_destroy(void *obj)
{
if (!session->direct_media_cap) {
return NULL;
}
- session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
- if (!session->req_caps) {
- return NULL;
- }
session->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
if (!session->datastores) {
return NULL;
}
+ session->active_media_state = ast_sip_session_media_state_alloc();
+ if (!session->active_media_state) {
+ return NULL;
+ }
+ session->pending_media_state = ast_sip_session_media_state_alloc();
+ if (!session->pending_media_state) {
+ return NULL;
+ }
if (endpoint->dtmf == AST_SIP_DTMF_INBAND || endpoint->dtmf == AST_SIP_DTMF_AUTO) {
dsp_features |= DSP_FEATURE_DIGIT_DETECT;
session->endpoint = ao2_bump(endpoint);
- session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp);
- if (!session->media) {
- return NULL;
- }
- /* fill session->media with available types */
- ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session);
-
if (rdata) {
/*
* We must continue using the serializer that the original
struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint,
struct ast_sip_contact *contact, const char *location, const char *request_user,
- struct ast_format_cap *req_caps)
+ struct ast_stream_topology *req_topology)
{
const char *uri = NULL;
RAII_VAR(struct ast_sip_aor *, found_aor, NULL, ao2_cleanup);
session->aor = ao2_bump(found_aor);
ast_party_id_copy(&session->id, &endpoint->id.self);
- if (ast_format_cap_count(req_caps)) {
- /* get joint caps between req_caps and endpoint caps */
- struct ast_format_cap *joint_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (ast_stream_topology_get_count(req_topology) > 0) {
+ /* get joint caps between req_topology and endpoint topology */
+ int i;
+
+ for (i = 0; i < ast_stream_topology_get_count(req_topology); ++i) {
+ struct ast_stream *req_stream;
+ struct ast_format_cap *req_cap;
+ struct ast_format_cap *joint_cap;
+ struct ast_stream *clone_stream;
+
+ req_stream = ast_stream_topology_get_stream(req_topology, i);
+
+ if (ast_stream_get_state(req_stream) == AST_STREAM_STATE_REMOVED) {
+ continue;
+ }
+
+ req_cap = ast_stream_get_formats(req_stream);
- ast_format_cap_get_compatible(req_caps, endpoint->media.codecs, joint_caps);
+ joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!joint_cap) {
+ continue;
+ }
+
+ ast_format_cap_get_compatible(req_cap, endpoint->media.codecs, joint_cap);
+ if (!ast_format_cap_count(joint_cap)) {
+ ao2_ref(joint_cap, -1);
+ continue;
+ }
+
+ clone_stream = ast_stream_clone(req_stream, NULL);
+ if (!clone_stream) {
+ ao2_ref(joint_cap, -1);
+ continue;
+ }
+
+ ast_stream_set_formats(clone_stream, joint_cap);
+ ao2_ref(joint_cap, -1);
+
+ if (!session->pending_media_state->topology) {
+ session->pending_media_state->topology = ast_stream_topology_alloc();
+ if (!session->pending_media_state->topology) {
+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+ ao2_ref(session, -1);
+ return NULL;
+ }
+ }
- /* if joint caps */
- if (ast_format_cap_count(joint_caps)) {
- /* copy endpoint caps into session->req_caps */
- ast_format_cap_append_from_cap(session->req_caps,
- endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);
- /* replace instances of joint caps equivalents in session->req_caps */
- ast_format_cap_replace_from_cap(session->req_caps, joint_caps,
- AST_MEDIA_TYPE_UNKNOWN);
+ if (ast_stream_topology_append_stream(session->pending_media_state->topology, clone_stream) < 0) {
+ ast_stream_free(clone_stream);
+ continue;
+ }
+ }
+ }
+
+ if (!session->pending_media_state->topology) {
+ /* Use the configured topology on the endpoint as the pending one */
+ session->pending_media_state->topology = ast_stream_topology_clone(endpoint->media.topology);
+ if (!session->pending_media_state->topology) {
+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+ ao2_ref(session, -1);
+ return NULL;
}
- ao2_cleanup(joint_caps);
}
if (pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) {
/* If this is delayed the only thing that will happen is a BYE request so we don't
* actually need to store the response code for when it happens.
*/
- delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE);
+ delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL);
break;
}
/* Fall through */
/* Flush any delayed requests so they cannot overlap this transaction. */
while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {
- ast_free(delay);
+ delayed_request_free(delay);
}
if (packet->msg->type == PJSIP_RESPONSE_MSG) {
ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision.\n",
ast_sorcery_object_get_id(session->endpoint),
session->channel ? ast_channel_name(session->channel) : "");
- if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE)) {
+ if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, NULL)) {
return;
}
if (pj_timer_entry_running(&session->rescheduled_reinvite)) {
}
}
-static int add_sdp_streams(void *obj, void *arg, void *data, int flags)
+static int add_sdp_streams(struct ast_sip_session_media *session_media,
+ struct ast_sip_session *session, pjmedia_sdp_session *answer,
+ const struct pjmedia_sdp_session *remote,
+ struct ast_stream *stream)
{
- struct ast_sip_session_media *session_media = obj;
- pjmedia_sdp_session *answer = arg;
- struct ast_sip_session *session = data;
struct ast_sip_session_sdp_handler *handler = session_media->handler;
RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
int res;
if (handler) {
/* if an already assigned handler reports a catastrophic error, fail */
- res = handler->create_outgoing_sdp_stream(session, session_media, answer);
+ res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream);
if (res < 0) {
- return 0;
+ return -1;
}
- return CMP_MATCH;
+ return 0;
}
- handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);
+ handler_list = ao2_find(sdp_handlers, ast_codec_media_type2str(session_media->type), OBJ_KEY);
if (!handler_list) {
- return CMP_MATCH;
+ return 0;
}
/* no handler for this stream type and we have a list to search */
if (handler == session_media->handler) {
continue;
}
- res = handler->create_outgoing_sdp_stream(session, session_media, answer);
+ res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream);
if (res < 0) {
/* catastrophic error */
- return 0;
+ return -1;
}
if (res > 0) {
/* Handled by this handler. Move to the next stream */
session_media_set_handler(session_media, handler);
- return CMP_MATCH;
+ return 0;
}
}
/* streams that weren't handled won't be included in generated outbound SDP */
- return CMP_MATCH;
+ return 0;
}
static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer)
{
- RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);
static const pj_str_t STR_IN = { "IN", 2 };
static const pj_str_t STR_IP4 = { "IP4", 3 };
static const pj_str_t STR_IP6 = { "IP6", 3 };
pjmedia_sdp_session *local;
+ int i;
+ int stream;
if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n");
pj_strdup2(inv->pool_prov, &local->origin.user, session->endpoint->media.sdpowner);
pj_strdup2(inv->pool_prov, &local->name, session->endpoint->media.sdpsession);
- /* Now let the handlers add streams of various types, pjmedia will automatically reorder the media streams for us */
- successful = ao2_callback_data(session->media, OBJ_MULTIPLE, add_sdp_streams, local, session);
- if (!successful || ao2_iterator_count(successful) != ao2_container_count(session->media)) {
- /* Something experienced a catastrophic failure */
- return NULL;
+ if (!session->pending_media_state->topology || !ast_stream_topology_get_count(session->pending_media_state->topology)) {
+ /* We've encountered a situation where we have been told to create a local SDP but noone has given us any indication
+ * of what kind of stream topology they would like. As a fallback we use the topology from the configured endpoint.
+ */
+ ast_stream_topology_free(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 NULL;
+ }
}
- /* Use the connection details of the first media stream if possible for SDP level */
- if (local->media_count) {
- int stream;
+ for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) {
+ struct ast_sip_session_media *session_media;
+ struct ast_stream *stream;
- /* Since we are using the first media stream as the SDP level we can get rid of it
- * from the stream itself
+ /* This code does not enforce any maximum stream count limitations as that is done on either
+ * the handling of an incoming SDP offer or on the handling of a session refresh.
*/
- local->conn = local->media[0]->conn;
- local->media[0]->conn = NULL;
- pj_strassign(&local->origin.net_type, &local->conn->net_type);
- pj_strassign(&local->origin.addr_type, &local->conn->addr_type);
- pj_strassign(&local->origin.addr, &local->conn->addr);
-
- /* Go through each media stream seeing if the connection details actually differ,
- * if not just use SDP level and reduce the SDP size
- */
- for (stream = 1; stream < local->media_count; stream++) {
+
+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
+
+ session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_stream_get_type(stream), i);
+ if (!session_media) {
+ return NULL;
+ }
+
+ if (add_sdp_streams(session_media, session, local, offer, stream)) {
+ return NULL;
+ }
+
+ /* Ensure that we never exceed the maximum number of streams PJMEDIA will allow. */
+ if (local->media_count == PJMEDIA_MAX_SDP_MEDIA) {
+ break;
+ }
+ }
+
+ /* Use the connection details of an available media if possible for SDP level */
+ for (stream = 0; stream < local->media_count; stream++) {
+ if (!local->media[stream]->conn) {
+ continue;
+ }
+
+ if (local->conn) {
if (!pj_strcmp(&local->conn->net_type, &local->media[stream]->conn->net_type) &&
!pj_strcmp(&local->conn->addr_type, &local->media[stream]->conn->addr_type) &&
!pj_strcmp(&local->conn->addr, &local->media[stream]->conn->addr)) {
local->media[stream]->conn = NULL;
}
+ continue;
}
- } else {
- local->origin.net_type = STR_IN;
- local->origin.addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4;
+
+ /* This stream's connection info will serve as the connection details for SDP level */
+ local->conn = local->media[stream]->conn;
+ local->media[stream]->conn = NULL;
+
+ continue;
+ }
+
+ /* If no SDP level connection details are present then create some */
+ if (!local->conn) {
+ local->conn = pj_pool_zalloc(inv->pool_prov, sizeof(struct pjmedia_sdp_conn));
+ local->conn->net_type = STR_IN;
+ local->conn->addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4;
if (!ast_strlen_zero(session->endpoint->media.address)) {
- pj_strdup2(inv->pool_prov, &local->origin.addr, session->endpoint->media.address);
+ pj_strdup2(inv->pool_prov, &local->conn->addr, session->endpoint->media.address);
} else {
- pj_strdup2(inv->pool_prov, &local->origin.addr, ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()));
+ pj_strdup2(inv->pool_prov, &local->conn->addr, ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()));
}
}
+ pj_strassign(&local->origin.net_type, &local->conn->net_type);
+ pj_strassign(&local->origin.addr_type, &local->conn->addr_type);
+ pj_strassign(&local->origin.addr, &local->conn->addr);
+
return local;
}
{
global:
- LINKER_SYMBOL_PREFIXast_sip_session_terminate;
- LINKER_SYMBOL_PREFIXast_sip_session_defer_termination;
- LINKER_SYMBOL_PREFIXast_sip_session_defer_termination_cancel;
- LINKER_SYMBOL_PREFIXast_sip_session_end_if_deferred;
- LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler;
- LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler;
- LINKER_SYMBOL_PREFIXast_sip_session_register_supplement;
- LINKER_SYMBOL_PREFIXast_sip_session_unregister_supplement;
- LINKER_SYMBOL_PREFIXast_sip_session_alloc_datastore;
- LINKER_SYMBOL_PREFIXast_sip_session_add_datastore;
- LINKER_SYMBOL_PREFIXast_sip_session_get_datastore;
- LINKER_SYMBOL_PREFIXast_sip_session_remove_datastore;
- LINKER_SYMBOL_PREFIXast_sip_session_get_identity;
- LINKER_SYMBOL_PREFIXast_sip_session_refresh;
- LINKER_SYMBOL_PREFIXast_sip_session_send_response;
- LINKER_SYMBOL_PREFIXast_sip_session_send_request;
- LINKER_SYMBOL_PREFIXast_sip_session_create_invite;
- LINKER_SYMBOL_PREFIXast_sip_session_create_outgoing;
- LINKER_SYMBOL_PREFIXast_sip_session_suspend;
- LINKER_SYMBOL_PREFIXast_sip_session_unsuspend;
+ LINKER_SYMBOL_PREFIXast_sip_session_*;
LINKER_SYMBOL_PREFIXast_sip_dialog_get_session;
- LINKER_SYMBOL_PREFIXast_sip_session_resume_reinvite;
LINKER_SYMBOL_PREFIXast_sip_channel_pvt_alloc;
local:
*;
#include "asterisk/netsock2.h"
#include "asterisk/channel.h"
#include "asterisk/acl.h"
+#include "asterisk/stream.h"
+#include "asterisk/format_cache.h"
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
struct ast_control_t38_parameters their_parms;
/*! \brief Timer entry for automatically rejecting an inbound re-invite */
pj_timer_entry timer;
+ /*! Preserved media state for when T.38 ends */
+ struct ast_sip_session_media_state *media_state;
};
/*! \brief Destructor for T.38 state information */
static void t38_state_destroy(void *obj)
{
+ struct t38_state *state = obj;
+
+ ast_sip_session_media_state_free(state->media_state);
ast_free(obj);
}
{
RAII_VAR(struct ast_sip_session *, session, obj, ao2_cleanup);
RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "t38"), ao2_cleanup);
- RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(session->media, "image", OBJ_KEY), ao2_cleanup);
+ struct ast_sip_session_media *session_media;
if (!datastore) {
return 0;
ast_debug(2, "Automatically rejecting T.38 request on channel '%s'\n",
session->channel ? ast_channel_name(session->channel) : "<gone>");
+ session_media = session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(session, session_media, datastore->data, T38_REJECTED);
ast_sip_session_resume_reinvite(session);
return -1;
}
- ast_channel_set_fd(session->channel, 5, ast_udptl_fd(session_media->udptl));
ast_udptl_set_error_correction_scheme(session_media->udptl, session->endpoint->media.t38.error_correction);
ast_udptl_setnat(session_media->udptl, session->endpoint->media.t38.nat);
ast_udptl_set_far_max_datagram(session_media->udptl, session->endpoint->media.t38.maxdatagram);
/*! \brief Callback for when T.38 reinvite SDP is created */
static int t38_reinvite_sdp_cb(struct ast_sip_session *session, pjmedia_sdp_session *sdp)
{
- int stream;
-
- /* Move the image media stream to the front and have it as the only stream, pjmedia will fill in
- * dummy streams for the rest
- */
- for (stream = 0; stream < sdp->media_count; ++stream) {
- if (!pj_strcmp2(&sdp->media[stream]->desc.media, "image")) {
- sdp->media[0] = sdp->media[stream];
- sdp->media_count = 1;
- break;
- }
+ struct t38_state *state;
+
+ state = t38_state_get_or_alloc(session);
+ if (!state) {
+ return -1;
}
+ state->media_state = ast_sip_session_media_state_clone(session->active_media_state);
+
return 0;
}
{
struct pjsip_status_line status = rdata->msg_info.msg->line.status;
struct t38_state *state;
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ struct ast_sip_session_media *session_media = NULL;
if (status.code == 100) {
return 0;
}
- if (!(state = t38_state_get_or_alloc(session)) ||
- !(session_media = ao2_find(session->media, "image", OBJ_KEY))) {
+ state = t38_state_get_or_alloc(session);
+ if (!state) {
ast_log(LOG_WARNING, "Received response to T.38 re-invite on '%s' but state unavailable\n",
ast_channel_name(session->channel));
return 0;
}
- t38_change_state(session, session_media, state, (status.code == 200) ? T38_ENABLED : T38_REJECTED);
+ if (status.code == 200) {
+ int index;
+
+ session_media = session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
+ t38_change_state(session, session_media, state, T38_ENABLED);
+
+ /* Stop all the streams in the stored away active state, they'll go back to being active once
+ * we reinvite back.
+ */
+ for (index = 0; index < AST_VECTOR_SIZE(&state->media_state->sessions); ++index) {
+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&state->media_state->sessions, index);
+
+ if (session_media && session_media->handler && session_media->handler->stream_stop) {
+ session_media->handler->stream_stop(session_media);
+ }
+ }
+ } else {
+ session_media = session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
+ t38_change_state(session, session_media, state, T38_REJECTED);
+
+ /* Abort this attempt at switching to T.38 by resetting the pending state and freeing our stored away active state */
+ ast_sip_session_media_state_free(state->media_state);
+ state->media_state = NULL;
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
return 0;
}
+/*! \brief Helper function which creates a media state for strictly T.38 */
+static struct ast_sip_session_media_state *t38_create_media_state(struct ast_sip_session *session)
+{
+ struct ast_sip_session_media_state *media_state;
+ struct ast_stream *stream;
+ struct ast_format_cap *caps;
+ struct ast_sip_session_media *session_media;
+
+ media_state = ast_sip_session_media_state_alloc();
+ if (!media_state) {
+ return NULL;
+ }
+
+ media_state->topology = ast_stream_topology_alloc();
+ if (!media_state->topology) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ stream = ast_stream_alloc("t38", AST_MEDIA_TYPE_IMAGE);
+ if (!stream) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);
+ ast_stream_topology_set_stream(media_state->topology, 0, stream);
+
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!caps) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ ast_format_cap_append(caps, ast_format_t38, 0);
+ ast_stream_set_formats(stream, caps);
+ ao2_ref(caps, -1);
+
+ session_media = ast_sip_session_media_state_add(session, media_state, AST_MEDIA_TYPE_IMAGE, 0);
+ if (!session_media) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ if (t38_initialize_session(session, session_media)) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ return media_state;
+}
+
/*! \brief Task for reacting to T.38 control frame */
static int t38_interpret_parameters(void *obj)
{
RAII_VAR(struct t38_parameters_task_data *, data, obj, ao2_cleanup);
const struct ast_control_t38_parameters *parameters = data->frame->data.ptr;
struct t38_state *state = t38_state_get_or_alloc(data->session);
- RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(data->session->media, "image", OBJ_KEY), ao2_cleanup);
+ struct ast_sip_session_media *session_media = NULL;
- /* Without session media or state we can't interpret parameters */
- if (!session_media || !state) {
+ if (!state) {
return 0;
}
/* Negotiation can not take place without a valid max_ifp value. */
if (!parameters->max_ifp) {
if (data->session->t38state == T38_PEER_REINVITE) {
+ session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(data->session, session_media, state, T38_REJECTED);
ast_sip_session_resume_reinvite(data->session);
} else if (data->session->t38state == T38_ENABLED) {
+ session_media = data->session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(data->session, session_media, state, T38_DISABLED);
ast_sip_session_refresh(data->session, NULL, NULL, NULL,
- AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, state->media_state);
+ state->media_state = NULL;
}
break;
} else if (data->session->t38state == T38_PEER_REINVITE) {
}
state->our_parms.version = MIN(state->our_parms.version, state->their_parms.version);
state->our_parms.rate_management = state->their_parms.rate_management;
+ session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp);
t38_change_state(data->session, session_media, state, T38_ENABLED);
ast_sip_session_resume_reinvite(data->session);
} else if ((data->session->t38state != T38_ENABLED) ||
((data->session->t38state == T38_ENABLED) &&
(parameters->request_response == AST_T38_REQUEST_NEGOTIATE))) {
- if (t38_initialize_session(data->session, session_media)) {
+ struct ast_sip_session_media_state *media_state;
+
+ media_state = t38_create_media_state(data->session);
+ if (!media_state) {
break;
}
state->our_parms = *parameters;
+ session_media = media_state->default_session[AST_MEDIA_TYPE_IMAGE];
ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp);
t38_change_state(data->session, session_media, state, T38_LOCAL_REINVITE);
ast_sip_session_refresh(data->session, NULL, t38_reinvite_sdp_cb, t38_reinvite_response_cb,
- AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, media_state);
}
break;
case AST_T38_TERMINATED:
case AST_T38_REFUSED:
case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */
if (data->session->t38state == T38_PEER_REINVITE) {
+ session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(data->session, session_media, state, T38_REJECTED);
ast_sip_session_resume_reinvite(data->session);
} else if (data->session->t38state == T38_ENABLED) {
+ session_media = data->session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(data->session, session_media, state, T38_DISABLED);
- ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
+ ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, state->media_state);
+ state->media_state = NULL;
}
break;
case AST_T38_REQUEST_PARMS: { /* Application wants remote's parameters re-sent */
struct ast_control_t38_parameters parameters = state->their_parms;
if (data->session->t38state == T38_PEER_REINVITE) {
+ session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
parameters.max_ifp = ast_udptl_get_far_max_ifp(session_media->udptl);
parameters.request_response = AST_T38_REQUEST_NEGOTIATE;
ast_queue_control_data(data->session->channel, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters));
return 0;
}
-/*! \brief Frame hook callback for writing */
-static struct ast_frame *t38_framehook_write(struct ast_channel *chan,
- struct ast_sip_session *session, struct ast_frame *f)
+/*! \brief Frame hook callback for T.38 related stuff */
+static struct ast_frame *t38_framehook(struct ast_channel *chan, struct ast_frame *f,
+ enum ast_framehook_event event, void *data)
{
+ struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
+
+ if (event != AST_FRAMEHOOK_EVENT_WRITE) {
+ return f;
+ }
+
if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS &&
- session->endpoint->media.t38.enabled) {
- struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(session, f);
+ channel->session->endpoint->media.t38.enabled) {
+ struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(channel->session, f);
if (!data) {
return f;
}
- if (ast_sip_push_task(session->serializer, t38_interpret_parameters, data)) {
+ if (ast_sip_push_task(channel->session->serializer, t38_interpret_parameters, data)) {
ao2_ref(data, -1);
}
- } else if (f->frametype == AST_FRAME_MODEM) {
- struct ast_sip_session_media *session_media;
-
- /* Avoid deadlock between chan and the session->media container lock */
- ast_channel_unlock(chan);
- session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY);
- ast_channel_lock(chan);
- if (session_media && session_media->udptl) {
- ast_udptl_write(session_media->udptl, f);
- }
- ao2_cleanup(session_media);
- }
-
- return f;
-}
-
-/*! \brief Frame hook callback for reading */
-static struct ast_frame *t38_framehook_read(struct ast_channel *chan,
- struct ast_sip_session *session, struct ast_frame *f)
-{
- if (ast_channel_fdno(session->channel) == 5) {
- struct ast_sip_session_media *session_media;
-
- /* Avoid deadlock between chan and the session->media container lock */
- ast_channel_unlock(chan);
- session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY);
- ast_channel_lock(chan);
- if (session_media && session_media->udptl) {
- f = ast_udptl_read(session_media->udptl);
- }
- ao2_cleanup(session_media);
- }
-
- return f;
-}
-
-/*! \brief Frame hook callback for T.38 related stuff */
-static struct ast_frame *t38_framehook(struct ast_channel *chan, struct ast_frame *f,
- enum ast_framehook_event event, void *data)
-{
- struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
-
- if (event == AST_FRAMEHOOK_EVENT_READ) {
- f = t38_framehook_read(chan, channel->session, f);
- } else if (event == AST_FRAMEHOOK_EVENT_WRITE) {
- f = t38_framehook_write(chan, channel->session, f);
}
return f;
static int t38_consume(void *data, enum ast_frame_type type)
{
- return 0;
+ return (type == AST_FRAME_CONTROL) ? 1 : 0;
}
static const struct ast_datastore_info t38_framehook_datastore = {
}
/*! \brief Function which negotiates an incoming media stream */
-static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
+static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp,
+ int index, struct ast_stream *asterisk_stream)
{
struct t38_state *state;
char host[NI_MAXHOST];
+ pjmedia_sdp_media *stream = sdp->media[index];
RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
if (!session->endpoint->media.t38.enabled) {
/*! \brief Function which creates an outgoing stream */
static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- struct pjmedia_sdp_session *sdp)
+ struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, struct ast_stream *stream)
{
pj_pool_t *pool = session->inv_session->pool_prov;
static const pj_str_t STR_IN = { "IN", 2 };
return -1;
}
- media->desc.media = pj_str(session_media->stream_type);
+ pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));
media->desc.transport = STR_UDPTL;
if (ast_strlen_zero(session->endpoint->media.address)) {
return 1;
}
+static struct ast_frame *media_session_udptl_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+ if (!session_media->udptl) {
+ return &ast_null_frame;
+ }
+
+ return ast_udptl_read(session_media->udptl);
+}
+
+static int media_session_udptl_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame)
+{
+ if (!session_media->udptl) {
+ return 0;
+ }
+
+ return ast_udptl_write(session_media->udptl, frame);
+}
+
/*! \brief Function which applies a negotiated stream */
-static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
- const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)
+static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local,
+ const struct pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
{
RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
+ pjmedia_sdp_media *remote_stream = remote->media[index];
char host[NI_MAXHOST];
struct t38_state *state;
t38_interpret_sdp(state, session, session_media, remote_stream);
+ ast_sip_session_media_set_write_callback(session, session_media, media_session_udptl_write_callback);
+ ast_sip_session_media_add_read_callback(session, session_media, ast_udptl_fd(session_media->udptl),
+ media_session_udptl_read_callback);
+
return 0;
}