<p>Joshua Colp has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/5876">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">chan_pjsip: Multistream: Actually do it.<br><br>This change allows chan_pjsip to negotiate and use<br>multiple streams on both incoming and outgoing SDP<br>negotiation.<br><br>For both offering and answering we will restrict what<br>streams are allowed by declining when the maximum<br>number of configured streams is reached. This allows<br>us to maintain the proper ordering for streams.<br><br>When sending an offer we will use the topology that<br>has been provided by a calling party or what has<br>been provided by an application.<br><br>When sending an answer we will accept how ever many<br>streams has been configured as the maximum and decline<br>any further streams. In the future it will be up to<br>the application currently handling the channel to<br>provide a response topology back.<br><br>When both of these negotiations are complete the<br>pending media updates are applied to PJSIP and the<br>Asterisk channel. This results in an updated stream<br>topology which is communicated to the application<br>currently handling the channel.<br><br>Some other bonus things as a result of this is that<br>some of the logic for handling media reading and writing<br>is now done via an API which allows it to more easily<br>live outside of chan_pjsip itself.<br><br>The res_pjsip_sdp_rtp has now also gained basic support<br>for declining media streams.<br><br>For the case of direct media, however, only the first<br>audio and video streams will be directly reinvited. In<br>the future when bridge_native_rtp is updated to understand<br>streams and the RTP glue API extended this could be improved.<br><br>Change-Id: I8afd8dd2eb538806a39b887af0abd046266e14c7<br>---<br>M channels/chan_pjsip.c<br>M channels/pjsip/cli_commands.c<br>M channels/pjsip/dialplan_functions.c<br>M channels/pjsip/include/chan_pjsip.h<br>M include/asterisk/res_pjsip_session.h<br>M res/res_pjsip_sdp_rtp.c<br>M res/res_pjsip_session.c<br>M res/res_pjsip_session.exports.in<br>M res/res_pjsip_t38.c<br>9 files changed, 893 insertions(+), 464 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/76/5876/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c<br>index ec6e3a3..d014348 100644<br>--- a/channels/chan_pjsip.c<br>+++ b/channels/chan_pjsip.c<br>@@ -77,27 +77,19 @@<br> <br> static unsigned int chan_idx;<br> <br>-static void chan_pjsip_pvt_dtor(void *obj)<br>-{<br>- struct chan_pjsip_pvt *pvt = obj;<br>- int i;<br>-<br>- for (i = 0; i < SIP_MEDIA_SIZE; ++i) {<br>- ao2_cleanup(pvt->media[i]);<br>- pvt->media[i] = NULL;<br>- }<br>-}<br>-<br> /* \brief Asterisk core interaction functions */<br> 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);<br>+static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type,<br>+ struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids,<br>+ const struct ast_channel *requestor, const char *data, int *cause);<br> static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text);<br> static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit);<br> static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration);<br> static int chan_pjsip_call(struct ast_channel *ast, const char *dest, int timeout);<br> static int chan_pjsip_hangup(struct ast_channel *ast);<br> static int chan_pjsip_answer(struct ast_channel *ast);<br>-static struct ast_frame *chan_pjsip_read(struct ast_channel *ast);<br>-static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f);<br>+static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast);<br>+static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *f);<br> static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);<br> static int chan_pjsip_transfer(struct ast_channel *ast, const char *target);<br> static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);<br>@@ -110,16 +102,16 @@<br> .type = channel_type,<br> .description = "PJSIP Channel Driver",<br> .requester = chan_pjsip_request,<br>+ .requester_with_stream_topology = chan_pjsip_request_with_stream_topology,<br> .send_text = chan_pjsip_sendtext,<br> .send_digit_begin = chan_pjsip_digit_begin,<br> .send_digit_end = chan_pjsip_digit_end,<br> .call = chan_pjsip_call,<br> .hangup = chan_pjsip_hangup,<br> .answer = chan_pjsip_answer,<br>- .read = chan_pjsip_read,<br>- .write = chan_pjsip_write,<br>- .write_video = chan_pjsip_write,<br>- .exception = chan_pjsip_read,<br>+ .read_stream = chan_pjsip_read_stream,<br>+ .write_stream = chan_pjsip_write_stream,<br>+ .exception = chan_pjsip_read_stream,<br> .indicate = chan_pjsip_indicate,<br> .transfer = chan_pjsip_transfer,<br> .fixup = chan_pjsip_fixup,<br>@@ -160,11 +152,20 @@<br> static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);<br>- struct chan_pjsip_pvt *pvt;<br> struct ast_sip_endpoint *endpoint;<br> struct ast_datastore *datastore;<br>+ struct ast_sip_session_media *media;<br> <br>- if (!channel || !channel->session || !(pvt = channel->pvt) || !pvt->media[SIP_MEDIA_AUDIO]->rtp) {<br>+ if (!channel || !channel->session) {<br>+ return AST_RTP_GLUE_RESULT_FORBID;<br>+ }<br>+<br>+ /* XXX Getting the first RTP instance for direct media related stuff seems just<br>+ * absolutely wrong. But the native RTP bridge knows no other method than single-stream<br>+ * for direct media. So this is the best we can do.<br>+ */<br>+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];<br>+ if (!media || !media->rtp) {<br> return AST_RTP_GLUE_RESULT_FORBID;<br> }<br> <br>@@ -176,7 +177,7 @@<br> <br> endpoint = channel->session->endpoint;<br> <br>- *instance = pvt->media[SIP_MEDIA_AUDIO]->rtp;<br>+ *instance = media->rtp;<br> ao2_ref(*instance, +1);<br> <br> ast_assert(endpoint != NULL);<br>@@ -195,16 +196,21 @@<br> static enum ast_rtp_glue_result chan_pjsip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br> struct ast_sip_endpoint *endpoint;<br>+ struct ast_sip_session_media *media;<br> <br>- if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO] || !pvt->media[SIP_MEDIA_VIDEO]->rtp) {<br>+ if (!channel || !channel->session) {<br>+ return AST_RTP_GLUE_RESULT_FORBID;<br>+ }<br>+<br>+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];<br>+ if (!media || !media->rtp) {<br> return AST_RTP_GLUE_RESULT_FORBID;<br> }<br> <br> endpoint = channel->session->endpoint;<br> <br>- *instance = pvt->media[SIP_MEDIA_VIDEO]->rtp;<br>+ *instance = media->rtp;<br> ao2_ref(*instance, +1);<br> <br> ast_assert(endpoint != NULL);<br>@@ -266,18 +272,43 @@<br> return 0;<br> }<br> <br>+/*! \brief Helper function to find the position for RTCP */<br>+static int rtp_find_rtcp_fd_position(struct ast_sip_session *session, struct ast_rtp_instance *rtp)<br>+{<br>+ int index;<br>+<br>+ for (index = 0; index < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++index) {<br>+ struct ast_sip_session_media_read_callback_state *callback_state =<br>+ AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, index);<br>+<br>+ if (callback_state->fd != ast_rtp_instance_fd(rtp, 1)) {<br>+ continue;<br>+ }<br>+<br>+ return index;<br>+ }<br>+<br>+ return -1;<br>+}<br>+<br> /*!<br> * \pre chan is locked<br> */<br> static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instance *rtp,<br>- struct ast_sip_session_media *media, int rtcp_fd)<br>+ struct ast_sip_session_media *media, struct ast_sip_session *session)<br> {<br>- int changed = 0;<br>+ int changed = 0, position = -1;<br>+<br>+ if (media->rtp) {<br>+ position = rtp_find_rtcp_fd_position(session, media->rtp);<br>+ }<br> <br> if (rtp) {<br> changed = ast_rtp_instance_get_and_cmp_remote_address(rtp, &media->direct_media_addr);<br> if (media->rtp) {<br>- ast_channel_set_fd(chan, rtcp_fd, -1);<br>+ if (position != -1) {<br>+ ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, -1);<br>+ }<br> ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 0);<br> }<br> } else if (!ast_sockaddr_isnull(&media->direct_media_addr)){<br>@@ -285,7 +316,9 @@<br> changed = 1;<br> if (media->rtp) {<br> ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 1);<br>- ast_channel_set_fd(chan, rtcp_fd, ast_rtp_instance_fd(media->rtp, 1));<br>+ if (position != -1) {<br>+ ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, ast_rtp_instance_fd(media->rtp, 1));<br>+ }<br> }<br> }<br> <br>@@ -334,22 +367,27 @@<br> {<br> struct rtp_direct_media_data *cdata = data;<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(cdata->chan);<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br>+ struct ast_sip_session *session;<br> int changed = 0;<br> int res = 0;<br>+<br>+ /* XXX In an ideal world each media stream would be direct, but for now preserve behavior<br>+ * and connect only the default media sessions for audio and video.<br>+ */<br> <br> /* The channel needs to be locked when checking for RTP changes.<br> * Otherwise, we could end up destroying an underlying RTCP structure<br> * at the same time that the channel thread is attempting to read RTCP<br> */<br> ast_channel_lock(cdata->chan);<br>- if (pvt->media[SIP_MEDIA_AUDIO]) {<br>+ session = channel->session;<br>+ if (session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {<br> changed |= check_for_rtp_changes(<br>- cdata->chan, cdata->rtp, pvt->media[SIP_MEDIA_AUDIO], 1);<br>+ cdata->chan, cdata->rtp, session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO], session);<br> }<br>- if (pvt->media[SIP_MEDIA_VIDEO]) {<br>+ if (session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]) {<br> changed |= check_for_rtp_changes(<br>- cdata->chan, cdata->vrtp, pvt->media[SIP_MEDIA_VIDEO], 3);<br>+ cdata->chan, cdata->vrtp, session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO], session);<br> }<br> ast_channel_unlock(cdata->chan);<br> <br>@@ -421,13 +459,20 @@<br> .update_peer = chan_pjsip_set_rtp_peer,<br> };<br> <br>-static void set_channel_on_rtp_instance(struct chan_pjsip_pvt *pvt, const char *channel_id)<br>+static void set_channel_on_rtp_instance(const struct ast_sip_session *session,<br>+ const char *channel_id)<br> {<br>- if (pvt->media[SIP_MEDIA_AUDIO] && pvt->media[SIP_MEDIA_AUDIO]->rtp) {<br>- ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_AUDIO]->rtp, channel_id);<br>- }<br>- if (pvt->media[SIP_MEDIA_VIDEO] && pvt->media[SIP_MEDIA_VIDEO]->rtp) {<br>- ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_VIDEO]->rtp, channel_id);<br>+ int i;<br>+<br>+ for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->sessions); ++i) {<br>+ struct ast_sip_session_media *session_media;<br>+<br>+ session_media = AST_VECTOR_GET(&session->active_media_state->sessions, i);<br>+ if (!session_media || !session_media->rtp) {<br>+ continue;<br>+ }<br>+<br>+ ast_rtp_instance_set_channel_id(session_media->rtp, channel_id);<br> }<br> }<br> <br>@@ -463,36 +508,6 @@<br> return res;<br> }<br> <br>-/*!<br>- * \brief Get the first ast_sip_session_media of a certain media type.<br>- *<br>- * XXX For multistream, we'll likely want to change this to be a function<br>- * to retrieve the nth session media instead of the first.<br>- *<br>- * \param session The session on which the session media lives<br>- * \param type The type of the session media<br>- */<br>-static struct ast_sip_session_media *get_first_session_media_by_type(<br>- const struct ast_sip_session *session, enum ast_media_type type)<br>-{<br>- int i;<br>-<br>- for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {<br>- struct ast_sip_session_media *session_media;<br>-<br>- session_media = AST_VECTOR_GET(&session->media, i);<br>- if (!session_media) {<br>- continue;<br>- }<br>-<br>- if (session_media->type == type) {<br>- return ao2_bump(session_media);<br>- }<br>- }<br>-<br>- return NULL;<br>-}<br>-<br> /*! \brief Function called to create a new PJSIP Asterisk channel */<br> static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int state, const char *exten, const char *title, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *cid_name)<br> {<br>@@ -501,8 +516,9 @@<br> RAII_VAR(struct chan_pjsip_pvt *, pvt, NULL, ao2_cleanup);<br> struct ast_sip_channel_pvt *channel;<br> struct ast_variable *var;<br>+ struct ast_stream_topology *topology;<br> <br>- if (!(pvt = ao2_alloc(sizeof(*pvt), chan_pjsip_pvt_dtor))) {<br>+ if (!(pvt = ao2_alloc(sizeof(*pvt), NULL))) {<br> return NULL;<br> }<br> <br>@@ -529,8 +545,8 @@<br> <br> ast_channel_tech_pvt_set(chan, channel);<br> <br>- if (!ast_stream_topology_get_count(session->req_topology) ||<br>- !compatible_formats_exist(session->req_topology, session->endpoint->media.codecs)) {<br>+ if (!ast_stream_topology_get_count(session->pending_media_state->topology) ||<br>+ !compatible_formats_exist(session->pending_media_state->topology, session->endpoint->media.codecs)) {<br> caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br> if (!caps) {<br> ast_channel_unlock(chan);<br>@@ -538,9 +554,20 @@<br> return NULL;<br> }<br> ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);<br>+ topology = ast_stream_topology_clone(session->endpoint->media.topology);<br> } else {<br>- caps = ast_format_cap_from_stream_topology(session->req_topology);<br>+ caps = ast_format_cap_from_stream_topology(session->pending_media_state->topology);<br>+ topology = ast_stream_topology_clone(session->pending_media_state->topology);<br> }<br>+<br>+ if (!topology) {<br>+ ao2_ref(caps, -1);<br>+ ast_channel_unlock(chan);<br>+ ast_hangup(chan);<br>+ return NULL;<br>+ }<br>+<br>+ ast_channel_set_stream_topology(chan, topology);<br> <br> if (!caps) {<br> ast_channel_unlock(chan);<br>@@ -607,13 +634,7 @@<br> ast_channel_stage_snapshot_done(chan);<br> ast_channel_unlock(chan);<br> <br>- /* XXX For multistream, the chan_pjsip_pvt structure will be completely<br>- * redone so it's not a fixed size array. It may have multiple audio / video<br>- * streams<br>- */<br>- pvt->media[SIP_MEDIA_AUDIO] = get_first_session_media_by_type(session, AST_MEDIA_TYPE_AUDIO);<br>- pvt->media[SIP_MEDIA_VIDEO] = get_first_session_media_by_type(session, AST_MEDIA_TYPE_VIDEO);<br>- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(chan));<br>+ set_channel_on_rtp_instance(session, ast_channel_uniqueid(chan));<br> <br> return chan;<br> }<br>@@ -752,48 +773,31 @@<br> *<br> * \note The channel is already locked.<br> */<br>-static struct ast_frame *chan_pjsip_read(struct ast_channel *ast)<br>+static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);<br>- struct ast_sip_session *session;<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br>+ struct ast_sip_session *session = channel->session;<br>+ struct ast_sip_session_media_read_callback_state *callback_state;<br> struct ast_frame *f;<br>- struct ast_sip_session_media *media = NULL;<br>- int rtcp = 0;<br>- int fdno = ast_channel_fdno(ast);<br>+ int fdno = ast_channel_fdno(ast) - AST_EXTENDED_FDS;<br> <br>- switch (fdno) {<br>- case 0:<br>- media = pvt->media[SIP_MEDIA_AUDIO];<br>- break;<br>- case 1:<br>- media = pvt->media[SIP_MEDIA_AUDIO];<br>- rtcp = 1;<br>- break;<br>- case 2:<br>- media = pvt->media[SIP_MEDIA_VIDEO];<br>- break;<br>- case 3:<br>- media = pvt->media[SIP_MEDIA_VIDEO];<br>- rtcp = 1;<br>- break;<br>- }<br>-<br>- if (!media || !media->rtp) {<br>+ if (fdno >= AST_VECTOR_SIZE(&session->active_media_state->read_callbacks)) {<br> return &ast_null_frame;<br> }<br> <br>- if (!(f = ast_rtp_instance_read(media->rtp, rtcp))) {<br>+ callback_state = AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, fdno);<br>+ f = callback_state->read_callback(session, callback_state->session);<br>+<br>+ if (!f) {<br> return f;<br> }<br> <br>- ast_rtp_instance_set_last_rx(media->rtp, time(NULL));<br>+ f->stream_num = callback_state->session->stream_num;<br> <br>- if (f->frametype != AST_FRAME_VOICE) {<br>+ if (f->frametype != AST_FRAME_VOICE ||<br>+ callback_state->session != session->active_media_state->default_session[callback_state->session->type]) {<br> return f;<br> }<br>-<br>- session = channel->session;<br> <br> if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {<br> ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when it has not been negotiated\n",<br>@@ -864,22 +868,27 @@<br> return f;<br> }<br> <br>-/*! \brief Function called by core to write frames */<br>-static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)<br>+static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *frame)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br>- struct ast_sip_session_media *media;<br>+ struct ast_sip_session *session = channel->session;<br>+ struct ast_sip_session_media *media = NULL;<br> int res = 0;<br>+<br>+ /* The core provides a guarantee that the stream will exist when we are called if stream_num is provided */<br>+ if (stream_num >= 0) {<br>+ media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, stream_num);<br>+ }<br> <br> switch (frame->frametype) {<br> case AST_FRAME_VOICE:<br>- media = pvt->media[SIP_MEDIA_AUDIO];<br>-<br> if (!media) {<br> return 0;<br>- }<br>- if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {<br>+ } else if (media->type != AST_MEDIA_TYPE_AUDIO) {<br>+ ast_log(LOG_WARNING, "Channel %s stream %d is of type '%s', not audio!\n",<br>+ ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));<br>+ return 0;<br>+ } else if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {<br> struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);<br> struct ast_str *write_transpath = ast_str_alloca(256);<br> struct ast_str *read_transpath = ast_str_alloca(256);<br>@@ -896,14 +905,20 @@<br> ast_format_get_name(ast_channel_rawwriteformat(ast)),<br> ast_translate_path_to_str(ast_channel_writetrans(ast), &write_transpath));<br> return 0;<br>- }<br>- if (media->rtp) {<br>- res = ast_rtp_instance_write(media->rtp, frame);<br>+ } else if (media->write_callback) {<br>+ res = media->write_callback(session, media, frame);<br>+<br> }<br> break;<br> case AST_FRAME_VIDEO:<br>- if ((media = pvt->media[SIP_MEDIA_VIDEO]) && media->rtp) {<br>- res = ast_rtp_instance_write(media->rtp, frame);<br>+ if (!media) {<br>+ return 0;<br>+ } else if (media->type != AST_MEDIA_TYPE_VIDEO) {<br>+ ast_log(LOG_WARNING, "Channel %s stream %d is of type '%s', not video!\n",<br>+ ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));<br>+ return 0;<br>+ } else if (media->write_callback) {<br>+ res = media->write_callback(session, media, frame);<br> }<br> break;<br> case AST_FRAME_MODEM:<br>@@ -920,7 +935,6 @@<br> static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(newchan);<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br> <br> if (channel->session->channel != oldchan) {<br> return -1;<br>@@ -933,7 +947,7 @@<br> */<br> channel->session->channel = newchan;<br> <br>- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(newchan));<br>+ set_channel_on_rtp_instance(channel->session, ast_channel_uniqueid(newchan));<br> <br> return 0;<br> }<br>@@ -1387,7 +1401,7 @@<br> /*! \brief Update local hold state and send a re-INVITE with the new SDP */<br> static int remote_send_hold_refresh(struct ast_sip_session *session, unsigned int held)<br> {<br>- AST_VECTOR_CALLBACK_VOID(&session->media, local_hold_set_state, held);<br>+ AST_VECTOR_CALLBACK_VOID(&session->active_media_state->sessions, local_hold_set_state, held);<br> ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);<br> ao2_ref(session, -1);<br> <br>@@ -1406,16 +1420,104 @@<br> return remote_send_hold_refresh(data, 0);<br> }<br> <br>+struct topology_change_refresh_data {<br>+ struct ast_sip_session *session;<br>+ struct ast_stream_topology *topology;<br>+};<br>+<br>+static void topology_change_refresh_data_free(struct topology_change_refresh_data *refresh_data)<br>+{<br>+ ao2_cleanup(refresh_data->session);<br>+ ast_stream_topology_free(refresh_data->topology);<br>+ ast_free(refresh_data);<br>+}<br>+<br>+static struct topology_change_refresh_data *topology_change_refresh_data_alloc(<br>+ struct ast_sip_session *session, const struct ast_stream_topology *topology)<br>+{<br>+ struct topology_change_refresh_data *refresh_data;<br>+<br>+ refresh_data = ast_calloc(1, sizeof(*refresh_data));<br>+ if (!refresh_data) {<br>+ return NULL;<br>+ }<br>+<br>+ refresh_data->session = ao2_bump(session);<br>+ refresh_data->topology = ast_stream_topology_clone(topology);<br>+ if (!refresh_data->topology) {<br>+ topology_change_refresh_data_free(refresh_data);<br>+ return NULL;<br>+ }<br>+<br>+ return refresh_data;<br>+}<br>+<br>+static int on_topology_change_response(struct ast_sip_session *session, pjsip_rx_data *rdata)<br>+{<br>+ if (rdata->msg_info.msg->line.status.code == 200) {<br>+ /* The topology was changed to something new so give notice to what requested<br>+ * it so it queries the channel and updates accordingly.<br>+ */<br>+ ast_queue_control(session->channel, AST_CONTROL_STREAM_TOPOLOGY_CHANGED);<br>+ } else {<br>+ /* The topology change failed, so drop the current pending media state */<br>+ ast_stream_topology_free(session->pending_media_state->topology);<br>+ session->pending_media_state->topology = NULL;<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+static int send_topology_change_refresh(void *data)<br>+{<br>+ struct topology_change_refresh_data *refresh_data = data;<br>+<br>+ refresh_data->session->pending_media_state->topology = ast_stream_topology_clone(refresh_data->topology);<br>+ if (!refresh_data->session->pending_media_state->topology) {<br>+ topology_change_refresh_data_free(refresh_data);<br>+ return -1;<br>+ }<br>+<br>+ if (ast_sip_session_refresh(refresh_data->session, NULL, NULL, on_topology_change_response,<br>+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1)) {<br>+ refresh_data->session->pending_media_state->topology = NULL;<br>+ topology_change_refresh_data_free(refresh_data);<br>+ return -1;<br>+ }<br>+<br>+ topology_change_refresh_data_free(refresh_data);<br>+ return 0;<br>+}<br>+<br>+static int handle_topology_request_change(struct ast_sip_session *session,<br>+ const struct ast_stream_topology *proposed)<br>+{<br>+ struct topology_change_refresh_data *refresh_data;<br>+ int res;<br>+<br>+ refresh_data = topology_change_refresh_data_alloc(session, proposed);<br>+ if (!refresh_data) {<br>+ return -1;<br>+ }<br>+<br>+ res = ast_sip_push_task(session->serializer, send_topology_change_refresh, refresh_data);<br>+ if (res) {<br>+ topology_change_refresh_data_free(refresh_data);<br>+ }<br>+ return res;<br>+}<br>+<br> /*! \brief Function called by core to ask the channel to indicate some sort of condition */<br> static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br> struct ast_sip_session_media *media;<br> int response_code = 0;<br> int res = 0;<br> char *device_buf;<br> size_t device_buf_size;<br>+ int i;<br>+ const struct ast_stream_topology *topology;<br> <br> switch (condition) {<br> case AST_CONTROL_RINGING:<br>@@ -1468,39 +1570,47 @@<br> ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_sorcery_object_get_id(channel->session->endpoint));<br> break;<br> case AST_CONTROL_VIDUPDATE:<br>- media = pvt->media[SIP_MEDIA_VIDEO];<br>- if (media && media->rtp) {<br>- /* FIXME: Only use this for VP8. Additional work would have to be done to<br>- * fully support other video codecs */<br>-<br>- if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) {<br>- /* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the<br>- * RTP engine would provide a way to externally write/schedule RTCP<br>- * packets */<br>- struct ast_frame fr;<br>- fr.frametype = AST_FRAME_CONTROL;<br>- fr.subclass.integer = AST_CONTROL_VIDUPDATE;<br>- res = ast_rtp_instance_write(media->rtp, &fr);<br>- } else {<br>- ao2_ref(channel->session, +1);<br>-#ifdef HAVE_PJSIP_INV_SESSION_REF<br>- if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) {<br>- ast_log(LOG_ERROR, "Can't increase the session reference counter\n");<br>- ao2_cleanup(channel->session);<br>- } else {<br>-#endif<br>- if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) {<br>- ao2_cleanup(channel->session);<br>- }<br>-#ifdef HAVE_PJSIP_INV_SESSION_REF<br>- }<br>-#endif<br>+ for (i = 0; i < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions); ++i) {<br>+ media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, i);<br>+ if (!media || media->type != AST_MEDIA_TYPE_VIDEO) {<br>+ continue;<br> }<br>- ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success");<br>- } else {<br>- ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure");<br>- res = -1;<br>+ if (media->rtp) {<br>+ /* FIXME: Only use this for VP8. Additional work would have to be done to<br>+ * fully support other video codecs */<br>+<br>+ if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) {<br>+ /* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the<br>+ * RTP engine would provide a way to externally write/schedule RTCP<br>+ * packets */<br>+ struct ast_frame fr;<br>+ fr.frametype = AST_FRAME_CONTROL;<br>+ fr.subclass.integer = AST_CONTROL_VIDUPDATE;<br>+ res = ast_rtp_instance_write(media->rtp, &fr);<br>+ } else {<br>+ ao2_ref(channel->session, +1);<br>+#ifdef HAVE_PJSIP_INV_SESSION_REF<br>+ if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) {<br>+ ast_log(LOG_ERROR, "Can't increase the session reference counter\n");<br>+ ao2_cleanup(channel->session);<br>+ } else {<br>+#endif<br>+ if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) {<br>+ ao2_cleanup(channel->session);<br>+ }<br>+#ifdef HAVE_PJSIP_INV_SESSION_REF<br>+ }<br>+#endif<br>+ }<br>+ ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success");<br>+ } else {<br>+ ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure");<br>+ res = -1;<br>+ }<br> }<br>+ /* XXX If there were no video streams, then this should set<br>+ * res to -1<br>+ */<br> break;<br> case AST_CONTROL_CONNECTED_LINE:<br> ao2_ref(channel->session, +1);<br>@@ -1595,6 +1705,10 @@<br> }<br> }<br> <br>+ break;<br>+ case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE:<br>+ topology = data;<br>+ res = handle_topology_request_change(channel->session, topology);<br> break;<br> case -1:<br> res = -1;<br>@@ -1809,9 +1923,10 @@<br> static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br>- struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];<br>+ struct ast_sip_session_media *media;<br> int res = 0;<br>+<br>+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];<br> <br> switch (channel->session->endpoint->dtmf) {<br> case AST_SIP_DTMF_RFC_4733:<br>@@ -1820,14 +1935,14 @@<br> }<br> <br> ast_rtp_instance_dtmf_begin(media->rtp, digit);<br>- break;<br>+ break;<br> case AST_SIP_DTMF_AUTO:<br>- if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {<br>- return -1;<br>- }<br>+ if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {<br>+ return -1;<br>+ }<br> <br>- ast_rtp_instance_dtmf_begin(media->rtp, digit);<br>- break;<br>+ ast_rtp_instance_dtmf_begin(media->rtp, digit);<br>+ break;<br> case AST_SIP_DTMF_NONE:<br> break;<br> case AST_SIP_DTMF_INBAND:<br>@@ -1923,9 +2038,10 @@<br> static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br>- struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];<br>+ struct ast_sip_session_media *media;<br> int res = 0;<br>+<br>+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];<br> <br> switch (channel->session->endpoint->dtmf) {<br> case AST_SIP_DTMF_INFO:<br>@@ -2008,7 +2124,6 @@<br> {<br> struct ast_sip_channel_pvt *channel = data;<br> struct ast_sip_session *session = channel->session;<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br> pjsip_tx_data *tdata;<br> <br> int res = ast_sip_session_create_invite(session, &tdata);<br>@@ -2017,7 +2132,7 @@<br> ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0);<br> ast_queue_hangup(session->channel);<br> } else {<br>- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(session->channel));<br>+ set_channel_on_rtp_instance(session, ast_channel_uniqueid(session->channel));<br> update_initial_connected_line(session);<br> ast_sip_session_send_request(session, tdata);<br> }<br>@@ -2115,10 +2230,10 @@<br> }<br> <br> /*! \brief Clear a channel from a session along with its PVT */<br>-static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast, struct chan_pjsip_pvt *pvt)<br>+static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast)<br> {<br> session->channel = NULL;<br>- set_channel_on_rtp_instance(pvt, "");<br>+ set_channel_on_rtp_instance(session, "");<br> ast_channel_tech_pvt_set(ast, NULL);<br> }<br> <br>@@ -2127,7 +2242,6 @@<br> struct hangup_data *h_data = data;<br> struct ast_channel *ast = h_data->chan;<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);<br>- struct chan_pjsip_pvt *pvt = channel->pvt;<br> struct ast_sip_session *session = channel->session;<br> int cause = h_data->cause;<br> <br>@@ -2137,7 +2251,7 @@<br> * afterwards.<br> */<br> ast_sip_session_terminate(ao2_bump(session), cause);<br>- clear_session_and_channel(session, ast, pvt);<br>+ clear_session_and_channel(session, ast);<br> ao2_cleanup(session);<br> ao2_cleanup(channel);<br> ao2_cleanup(h_data);<br>@@ -2148,7 +2262,6 @@<br> static int chan_pjsip_hangup(struct ast_channel *ast)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);<br>- struct chan_pjsip_pvt *pvt;<br> int cause;<br> struct hangup_data *h_data;<br> <br>@@ -2156,7 +2269,6 @@<br> return -1;<br> }<br> <br>- pvt = channel->pvt;<br> cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel));<br> h_data = hangup_data_alloc(cause, ast);<br> <br>@@ -2175,7 +2287,7 @@<br> /* Go ahead and do our cleanup of the session and channel even if we're not going<br> * to be able to send our SIP request/response<br> */<br>- clear_session_and_channel(channel->session, ast, pvt);<br>+ clear_session_and_channel(channel->session, ast);<br> ao2_cleanup(channel);<br> ao2_cleanup(h_data);<br> <br>@@ -2270,26 +2382,20 @@<br> }<br> <br> /*! \brief Function called by core to create a new outgoing PJSIP session */<br>-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)<br>+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)<br> {<br> struct request_data req_data;<br> RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);<br> <br>- req_data.topology = ast_stream_topology_create_from_format_cap(cap);<br>+ req_data.topology = topology;<br> req_data.dest = data;<br>-<br>- if (!req_data.topology) {<br>- return NULL;<br>- }<br> <br> if (ast_sip_push_task_synchronous(NULL, request, &req_data)) {<br> *cause = req_data.cause;<br>- ast_stream_topology_free(req_data.topology);<br> return NULL;<br> }<br> <br> session = req_data.session;<br>- ast_stream_topology_free(req_data.topology);<br> <br> if (!(session->channel = chan_pjsip_new(session, AST_STATE_DOWN, NULL, NULL, assignedids, requestor, NULL))) {<br> /* Session needs to be terminated prematurely */<br>@@ -2299,6 +2405,23 @@<br> return session->channel;<br> }<br> <br>+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)<br>+{<br>+ struct ast_stream_topology *topology;<br>+ struct ast_channel *chan;<br>+<br>+ topology = ast_stream_topology_create_from_format_cap(cap);<br>+ if (!topology) {<br>+ return NULL;<br>+ }<br>+<br>+ chan = chan_pjsip_request_with_stream_topology(type, topology, assignedids, requestor, data, cause);<br>+<br>+ ast_stream_topology_free(topology);<br>+<br>+ return chan;<br>+}<br>+<br> struct sendtext_data {<br> struct ast_sip_session *session;<br> char text[0];<br>diff --git a/channels/pjsip/cli_commands.c b/channels/pjsip/cli_commands.c<br>index fc14b25..7705bbd 100644<br>--- a/channels/pjsip/cli_commands.c<br>+++ b/channels/pjsip/cli_commands.c<br>@@ -336,14 +336,24 @@<br> return 0;<br> }<br> <br>+static int find_audio(struct ast_sip_session_media *session_media)<br>+{<br>+ if (session_media->type == AST_MEDIA_TYPE_AUDIO) {<br>+ return CMP_MATCH;<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br> static int cli_channelstats_print_body(void *obj, void *arg, int flags)<br> {<br> struct ast_sip_cli_context *context = arg;<br> const struct ast_channel_snapshot *snapshot = obj;<br> struct ast_channel *channel = ast_channel_get_by_name(snapshot->name);<br> struct ast_sip_channel_pvt *cpvt = channel ? ast_channel_tech_pvt(channel) : NULL;<br>- struct chan_pjsip_pvt *pvt = cpvt ? cpvt->pvt : NULL;<br>- struct ast_sip_session_media *media = pvt ? pvt->media[SIP_MEDIA_AUDIO] : NULL;<br>+ struct ast_sip_session *session;<br>+ struct ast_sip_session_media *media;<br>+ struct ast_rtp_instance *rtp;<br> struct ast_rtp_instance_stats stats;<br> char *print_name = NULL;<br> char *print_time = alloca(32);<br>@@ -351,21 +361,38 @@<br> <br> ast_assert(context->output_buffer != NULL);<br> <br>- if (!media || !media->rtp) {<br>+ if (!channel) {<br> ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);<br>+ return -1;<br>+ }<br>+<br>+ ast_channel_lock(channel);<br>+<br>+ session = cpvt->session;<br>+ if (!session) {<br>+ ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);<br>+ ast_channel_unlock(channel);<br> ao2_cleanup(channel);<br> return -1;<br> }<br> <br>+ media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_audio, NULL);<br>+ if (!media || !media->rtp) {<br>+ ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);<br>+ ast_channel_unlock(channel);<br>+ ao2_cleanup(channel);<br>+ return -1;<br>+ }<br>+<br>+ rtp = ao2_bump(media->rtp);<br>+<br> codec_in_use[0] = '\0';<br> <br>- if (channel) {<br>- ast_channel_lock(channel);<br>- if (ast_channel_rawreadformat(channel)) {<br>- ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));<br>- }<br>- ast_channel_unlock(channel);<br>+ if (ast_channel_rawreadformat(channel)) {<br>+ ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));<br> }<br>+<br>+ ast_channel_unlock(channel);<br> <br> print_name = ast_strdupa(snapshot->name);<br> /* Skip the PJSIP/. We know what channel type it is and we need the space. */<br>@@ -373,7 +400,7 @@<br> <br> ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);<br> <br>- if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {<br>+ if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {<br> ast_str_append(&context->output_buffer, 0, "%s direct media\n", snapshot->name);<br> } else {<br> ast_str_append(&context->output_buffer, 0,<br>@@ -398,6 +425,7 @@<br> );<br> }<br> <br>+ ao2_cleanup(rtp);<br> ao2_cleanup(channel);<br> <br> return 0;<br>diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c<br>index 9de7ceb..ca2d340 100644<br>--- a/channels/pjsip/dialplan_functions.c<br>+++ b/channels/pjsip/dialplan_functions.c<br>@@ -456,14 +456,23 @@<br> [T38_REJECTED] = "REJECTED",<br> };<br> <br>+static int find_media(struct ast_sip_session_media *session_media, enum ast_media_type type)<br>+{<br>+ if (session_media->type == type) {<br>+ return CMP_MATCH;<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br> /*!<br> * \internal \brief Handle reading RTP information<br> */<br> static int channel_read_rtp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);<br>- struct chan_pjsip_pvt *pvt;<br>- struct ast_sip_session_media *media = NULL;<br>+ struct ast_sip_session *session;<br>+ struct ast_sip_session_media *media;<br> struct ast_sockaddr addr;<br> <br> if (!channel) {<br>@@ -471,9 +480,9 @@<br> return -1;<br> }<br> <br>- pvt = channel->pvt;<br>- if (!pvt) {<br>- ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));<br>+ session = channel->session;<br>+ if (!session) {<br>+ ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan));<br> return -1;<br> }<br> <br>@@ -483,9 +492,9 @@<br> }<br> <br> if (ast_strlen_zero(field) || !strcmp(field, "audio")) {<br>- media = pvt->media[SIP_MEDIA_AUDIO];<br>+ media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_media, NULL, AST_MEDIA_TYPE_AUDIO);<br> } else if (!strcmp(field, "video")) {<br>- media = pvt->media[SIP_MEDIA_VIDEO];<br>+ media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_media, NULL, AST_MEDIA_TYPE_VIDEO);<br> } else {<br> ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field);<br> return -1;<br>@@ -523,17 +532,17 @@<br> static int channel_read_rtcp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)<br> {<br> struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);<br>- struct chan_pjsip_pvt *pvt;<br>- struct ast_sip_session_media *media = NULL;<br>+ struct ast_sip_session *session;<br>+ struct ast_sip_session_media *media;<br> <br> if (!channel) {<br> ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan));<br> return -1;<br> }<br> <br>- pvt = channel->pvt;<br>- if (!pvt) {<br>- ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));<br>+ session = channel->session;<br>+ if (!session) {<br>+ ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan));<br> return -1;<br> }<br> <br>@@ -543,9 +552,9 @@<br> }<br> <br> if (ast_strlen_zero(field) || !strcmp(field, "audio")) {<br>- media = pvt->media[SIP_MEDIA_AUDIO];<br>+ media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_media, NULL, AST_MEDIA_TYPE_AUDIO);<br> } else if (!strcmp(field, "video")) {<br>- media = pvt->media[SIP_MEDIA_VIDEO];<br>+ media = AST_VECTOR_CALLBACK(&session->active_media_state->sessions, find_media, NULL, AST_MEDIA_TYPE_AUDIO);<br> } else {<br> ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field);<br> return -1;<br>@@ -934,8 +943,8 @@<br> size_t accum = 0;<br> <br> /* Find the first suitable stream */<br>- for (idx = 0; idx < ast_stream_topology_get_count(session->req_topology); ++idx) {<br>- stream = ast_stream_topology_get_stream(session->req_topology, idx);<br>+ for (idx = 0; idx < ast_stream_topology_get_count(session->pending_media_state->topology); ++idx) {<br>+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, idx);<br> <br> if (ast_stream_get_type(stream) != media_type ||<br> ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {<br>@@ -1002,7 +1011,7 @@<br> * b) Change the dialplan function to be able to specify which stream to alter and alter only that<br> * one stream<br> */<br>- stream = ast_stream_topology_get_first_stream_by_type(data->session->req_topology, data->media_type);<br>+ stream = ast_stream_topology_get_first_stream_by_type(data->session->pending_media_state->topology, data->media_type);<br> if (!stream) {<br> return 0;<br> }<br>diff --git a/channels/pjsip/include/chan_pjsip.h b/channels/pjsip/include/chan_pjsip.h<br>index b229a04..1fee864 100644<br>--- a/channels/pjsip/include/chan_pjsip.h<br>+++ b/channels/pjsip/include/chan_pjsip.h<br>@@ -34,25 +34,12 @@<br> pj_sockaddr local_addr;<br> };<br> <br>-/*!<br>- * \brief Positions of various media<br>- */<br>-enum sip_session_media_position {<br>- /*! \brief First is audio */<br>- SIP_MEDIA_AUDIO = 0,<br>- /*! \brief Second is video */<br>- SIP_MEDIA_VIDEO,<br>- /*! \brief Last is the size for media details */<br>- SIP_MEDIA_SIZE,<br>-};<br> <br> /*!<br> * \brief The PJSIP channel driver pvt, stored in the \ref ast_sip_channel_pvt<br> * data structure<br> */<br> struct chan_pjsip_pvt {<br>- /*! \brief The available media sessions */<br>- struct ast_sip_session_media *media[SIP_MEDIA_SIZE];<br> };<br> <br> #endif /* _CHAN_PJSIP_HEADER */<br>diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h<br>index c6d1574..d6f8229 100644<br>--- a/include/asterisk/res_pjsip_session.h<br>+++ b/include/asterisk/res_pjsip_session.h<br>@@ -58,6 +58,12 @@<br> };<br> <br> struct ast_sip_session_sdp_handler;<br>+struct ast_sip_session;<br>+struct ast_sip_session_media;<br>+<br>+typedef struct ast_frame *(*ast_sip_session_media_read_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media);<br>+typedef int (*ast_sip_session_media_write_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+ struct ast_frame *frame);<br> <br> /*!<br> * \brief A structure containing SIP session media information<br>@@ -89,6 +95,36 @@<br> unsigned int remote_rtcp_mux:1;<br> /*! \brief Media type of this session media */<br> enum ast_media_type type;<br>+ /*! \brief The write callback when writing frames */<br>+ ast_sip_session_media_write_cb write_callback;<br>+ /*! \brief The stream number to place into any resulting frames */<br>+ int stream_num;<br>+};<br>+<br>+/*!<br>+ * \brief Structure which contains read callback information<br>+ */<br>+struct ast_sip_session_media_read_callback_state {<br>+ /*! \brief The file descriptor itself */<br>+ int fd;<br>+ /*! \brief The callback to invoke */<br>+ ast_sip_session_media_read_cb read_callback;<br>+ /*! \brief The media session */<br>+ struct ast_sip_session_media *session;<br>+};<br>+<br>+/*!<br>+ * \brief Structure which contains media state information (streams, sessions)<br>+ */<br>+struct ast_sip_session_media_state {<br>+ /*! \brief Mapping of stream to media sessions */<br>+ AST_VECTOR(, struct ast_sip_session_media *) sessions;<br>+ /*! \brief Added read callbacks */<br>+ AST_VECTOR(, struct ast_sip_session_media_read_callback_state) read_callbacks;<br>+ /*! \brief Default media sessions for each type */<br>+ struct ast_sip_session_media *default_session[AST_MEDIA_TYPE_END];<br>+ /*! \brief The media stream topology */<br>+ struct ast_stream_topology *topology;<br> };<br> <br> /*!<br>@@ -123,10 +159,6 @@<br> AST_LIST_HEAD(, ast_sip_session_supplement) supplements;<br> /*! Datastores added to the session by supplements to the session */<br> struct ao2_container *datastores;<br>- /*! Lock for vector of session media */<br>- ast_mutex_t media_streams_lock;<br>- /*! Media streams */<br>- AST_VECTOR(, struct ast_sip_session_media *) media;<br> /*! Serializer for tasks relating to this SIP session */<br> struct ast_taskprocessor *serializer;<br> /*! Non-null if the session serializer is suspended or being suspended. */<br>@@ -141,8 +173,10 @@<br> pj_timer_entry scheduled_termination;<br> /*! Identity of endpoint this session deals with */<br> struct ast_party_id id;<br>- /*! Requested capabilities */<br>- struct ast_stream_topology *req_topology;<br>+ /*! Active media state (sessions + streams) */<br>+ struct ast_sip_session_media_state *active_media_state;<br>+ /*! Pending media state (sessions + streams) */<br>+ struct ast_sip_session_media_state *pending_media_state;<br> /*! Optional DSP, used only for inband DTMF/Fax-CNG detection if configured */<br> struct ast_dsp *dsp;<br> /*! Whether the termination of the session should be deferred */<br>@@ -317,23 +351,16 @@<br> /*!<br> * \brief Set session details based on a stream in an incoming SDP offer or answer<br> * \param session The session for which the media is being negotiated<br>+ * \param session_media The media session<br> * \param sdp The entire SDP. Useful for getting "global" information, such as connections or attributes<br> * \param index The index for the session media, Asterisk stream, and PJMEDIA stream being negotiated<br>+ * \param asterisk_stream The Asterisk stream representation<br> * \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called.<br> * \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned.<br> * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.<br> */<br>- int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, const struct pjmedia_sdp_session *sdp, int index);<br>- /*!<br>- * \brief Create an SDP media stream and add it to the outgoing SDP offer or answer<br>- * \param session The session for which media is being added<br>- * \param session_media The media to be setup for this session<br>- * \param stream The stream on which to operate<br>- * \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called.<br>- * \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned.<br>- * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.<br>- */<br>- 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);<br>+ int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+ const struct pjmedia_sdp_session *sdp, int index, struct ast_stream *asterisk_stream);<br> /*!<br> * \brief Create an SDP media stream and add it to the outgoing SDP offer or answer<br> * \param session The session for which media is being added<br>@@ -355,15 +382,18 @@<br> /*!<br> * \brief Apply a negotiated SDP media stream<br> * \param session The session for which media is being applied<br>+ * \param session_media The media session<br> * \param local The entire local negotiated SDP<br> * \param remote The entire remote negotiated SDP<br> * \param index The index of the session media, SDP streams, and Asterisk streams<br>+ * \param asterisk_stream The Asterisk stream representation<br> * \retval 0 The stream was not applied by this handler. If there are other registered handlers for this stream type, they will be called.<br> * \retval <0 There was an error encountered. No further operation will take place and the current application will be abandoned.<br> * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.<br> */<br>- int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, const struct pjmedia_sdp_session *local,<br>- const struct pjmedia_sdp_session *remote, int index);<br>+ int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+ const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote, int index,<br>+ struct ast_stream *asterisk_stream);<br> /*!<br> * \brief Stop a session_media created by this handler but do not destroy resources<br> * \param session The session for which media is being stopped<br>@@ -393,7 +423,7 @@<br> /*!<br> * \brief Allocate a new SIP channel pvt structure<br> *<br>- * \param pvt Pointer to channel specific implementation<br>+ * \param pvt Pointer to channel specific information<br> * \param session Pointer to SIP session<br> *<br> * \retval non-NULL success<br>@@ -692,6 +722,51 @@<br> */<br> void ast_sip_session_resume_reinvite(struct ast_sip_session *session);<br> <br>+/*!<br>+ * \brief Determines if a provided pending stream will be the default stream or not<br>+ * \since 15.0.0<br>+ *<br>+ * \param session The session to check against<br>+ * \param stream The pending stream<br>+ *<br>+ * \retval 1 if stream will be default<br>+ * \retval 0 if stream will NOT be the default<br>+ */<br>+int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream);<br>+<br>+/*!<br>+ * \brief Set a read callback for a media session with a specific file descriptor<br>+ * \since 15.0.0<br>+ *<br>+ * \param session The session<br>+ * \param session_media The media session<br>+ * \param fd The file descriptor<br>+ * \param callback The read callback<br>+ *<br>+ * \retval 0 the read callback was successfully added<br>+ * \retval -1 the read callback could not be added<br>+ *<br>+ * \note This operations on the pending media state<br>+ */<br>+int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+ int fd, ast_sip_session_media_read_cb callback);<br>+<br>+/*!<br>+ * \brief Set a write callback for a media session<br>+ * \since 15.0.0<br>+ *<br>+ * \param session The session<br>+ * \param session_media The media session<br>+ * \param callback The write callback<br>+ *<br>+ * \retval 0 the write callback was successfully add<br>+ * \retval -1 the write callback is already set to something different<br>+ *<br>+ * \note This operates on the pending media state<br>+ */<br>+int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+ ast_sip_session_media_write_cb callback);<br>+<br> /*! \brief Determines whether the res_pjsip_session module is loaded */<br> #define CHECK_PJSIP_SESSION_MODULE_LOADED() \<br> do { \<br>diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c<br>index b6f9aa7..6c04a9a 100644<br>--- a/res/res_pjsip_sdp_rtp.c<br>+++ b/res/res_pjsip_sdp_rtp.c<br>@@ -52,6 +52,7 @@<br> #include "asterisk/dsp.h"<br> #include "asterisk/linkedlists.h" /* for AST_LIST_NEXT */<br> #include "asterisk/stream.h"<br>+#include "asterisk/format_cache.h"<br> <br> #include "asterisk/res_pjsip.h"<br> #include "asterisk/res_pjsip_session.h"<br>@@ -63,24 +64,7 @@<br> static struct ast_sockaddr address_rtp;<br> <br> static const char STR_AUDIO[] = "audio";<br>-static const int FD_AUDIO = 0;<br>-<br> static const char STR_VIDEO[] = "video";<br>-static const int FD_VIDEO = 2;<br>-<br>-/*! \brief Get the starting descriptor for a media type */<br>-static int media_type_to_fdno(enum ast_media_type media_type)<br>-{<br>- switch (media_type) {<br>- case AST_MEDIA_TYPE_AUDIO: return FD_AUDIO;<br>- case AST_MEDIA_TYPE_VIDEO: return FD_VIDEO;<br>- case AST_MEDIA_TYPE_TEXT:<br>- case AST_MEDIA_TYPE_UNKNOWN:<br>- case AST_MEDIA_TYPE_IMAGE:<br>- case AST_MEDIA_TYPE_END: break;<br>- }<br>- return -1;<br>-}<br> <br> static int send_keepalive(const void *data)<br> {<br>@@ -324,7 +308,7 @@<br> static int set_caps(struct ast_sip_session *session,<br> struct ast_sip_session_media *session_media,<br> const struct pjmedia_sdp_media *stream,<br>- int is_offer, int index)<br>+ int is_offer, struct ast_stream *asterisk_stream)<br> {<br> RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);<br> RAII_VAR(struct ast_format_cap *, peer, NULL, ao2_cleanup);<br>@@ -336,7 +320,6 @@<br> int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&<br> ast_format_cap_count(session->direct_media_cap);<br> int dsp_features = 0;<br>- struct ast_stream *req_stream = NULL;<br> <br> if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||<br> !(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||<br>@@ -381,20 +364,9 @@<br> ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),<br> session_media->rtp);<br> <br>- if (index < ast_stream_topology_get_count(session->req_topology)) {<br>- req_stream = ast_stream_topology_get_stream(session->req_topology, index);<br>- }<br>- if (!req_stream) {<br>- req_stream = ast_stream_alloc(ast_codec_media_type2str(media_type), media_type);<br>- if (!req_stream) {<br>- return -1;<br>- }<br>- ast_stream_topology_set_stream(session->req_topology, index, req_stream);<br>- }<br>+ ast_stream_set_formats(asterisk_stream, joint);<br> <br>- ast_stream_set_formats(req_stream, joint);<br>-<br>- if (session->channel) {<br>+ if (session->channel && ast_sip_session_is_pending_stream_default(session, asterisk_stream)) {<br> ast_channel_lock(session->channel);<br> ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_UNKNOWN);<br> ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel),<br>@@ -959,11 +931,11 @@<br> <br> /*! \brief Function which negotiates an incoming media stream */<br> static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,<br>- const pjmedia_sdp_session *sdp, int index)<br>+ struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,<br>+ int index, struct ast_stream *asterisk_stream)<br> {<br> char host[NI_MAXHOST];<br> RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);<br>- struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);<br> pjmedia_sdp_media *stream = sdp->media[index];<br> enum ast_media_type media_type = session_media->type;<br> enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE;<br>@@ -1034,7 +1006,7 @@<br> pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);<br> }<br> <br>- if (set_caps(session, session_media, stream, 1, index)) {<br>+ if (set_caps(session, session_media, stream, 1, asterisk_stream)) {<br> return 0;<br> }<br> return 1;<br>@@ -1158,6 +1130,7 @@<br> struct pjmedia_sdp_session *sdp, struct ast_stream *stream)<br> {<br> pj_pool_t *pool = session->inv_session->pool_prov;<br>+ static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };<br> static const pj_str_t STR_IN = { "IN", 2 };<br> static const pj_str_t STR_IP4 = { "IP4", 3};<br> static const pj_str_t STR_IP6 = { "IP6", 3};<br>@@ -1179,23 +1152,27 @@<br> int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&<br> ast_format_cap_count(session->direct_media_cap);<br> <br>+ media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media));<br>+ if (!media) {<br>+ return -1;<br>+ }<br>+ pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));<br>+<br>+ /* If this is a removed (or declined) stream then construct a minimal stream in SDP */<br>+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {<br>+ media->desc.transport = STR_RTP_AVP;<br>+ media->desc.port = 0;<br>+ media->desc.port_count = 1;<br>+ pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], "32");<br>+ sdp->media[sdp->media_count++] = media;<br>+<br>+ return 1;<br>+ }<br>+<br> if (!session_media->rtp && create_rtp(session, session_media)) {<br> return -1;<br> }<br> <br>- set_ice_components(session, session_media);<br>- enable_rtcp(session, session_media, NULL);<br>-<br>- if (!(media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media))) ||<br>- !(media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)))) {<br>- return -1;<br>- }<br>-<br>- if (add_crypto_to_stream(session, session_media, pool, media)) {<br>- return -1;<br>- }<br>-<br>- pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));<br> if (pj_strlen(&session_media->transport)) {<br> /* If a transport has already been specified use it */<br> media->desc.transport = session_media->transport;<br>@@ -1206,6 +1183,18 @@<br> (session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES),<br> session_media->rtp, session->endpoint->media.rtp.use_avpf,<br> session->endpoint->media.rtp.force_avp));<br>+ }<br>+<br>+ set_ice_components(session, session_media);<br>+ enable_rtcp(session, session_media, NULL);<br>+<br>+ media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));<br>+ if (!media->conn) {<br>+ return -1;<br>+ }<br>+<br>+ if (add_crypto_to_stream(session, session_media, pool, media)) {<br>+ return -1;<br> }<br> <br> /* Add connection level details */<br>@@ -1237,12 +1226,12 @@<br> }<br> }<br> <br>+ /* Add ICE attributes and candidates */<br>+ add_ice_to_stream(session, session_media, pool, media);<br>+<br> ast_rtp_instance_get_local_address(session_media->rtp, &addr);<br> media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr);<br> media->desc.port_count = 1;<br>-<br>- /* Add ICE attributes and candidates */<br>- add_ice_to_stream(session, session_media, pool, media);<br> <br> if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {<br> ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",<br>@@ -1290,7 +1279,8 @@<br> }<br> <br> /* Add non-codec formats */<br>- if (media_type != AST_MEDIA_TYPE_VIDEO && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) {<br>+ if (ast_sip_session_is_pending_stream_default(session, stream) && media_type != AST_MEDIA_TYPE_VIDEO<br>+ && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) {<br> for (index = 1LL; index <= AST_RTP_MAX; index <<= 1) {<br> if (!(noncodec & index)) {<br> continue;<br>@@ -1356,23 +1346,62 @@<br> return 1;<br> }<br> <br>+static struct ast_frame *media_session_rtp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)<br>+{<br>+ struct ast_frame *f;<br>+<br>+ if (!session_media->rtp) {<br>+ return &ast_null_frame;<br>+ }<br>+<br>+ f = ast_rtp_instance_read(session_media->rtp, 0);<br>+ if (!f) {<br>+ return NULL;<br>+ }<br>+<br>+ ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL));<br>+<br>+ return f;<br>+}<br>+<br>+static struct ast_frame *media_session_rtcp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)<br>+{<br>+ struct ast_frame *f;<br>+<br>+ if (!session_media->rtp) {<br>+ return &ast_null_frame;<br>+ }<br>+<br>+ f = ast_rtp_instance_read(session_media->rtp, 1);<br>+ if (!f) {<br>+ return NULL;<br>+ }<br>+<br>+ ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL));<br>+<br>+ return f;<br>+}<br>+<br>+static int media_session_rtp_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame)<br>+{<br>+ if (!session_media->rtp) {<br>+ return 0;<br>+ }<br>+<br>+ return ast_rtp_instance_write(session_media->rtp, frame);<br>+}<br>+<br> static int apply_negotiated_sdp_stream(struct ast_sip_session *session,<br>- const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote,<br>- int index)<br>+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local,<br>+ const struct pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)<br> {<br> RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);<br>- struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);<br>- struct pjmedia_sdp_media *local_stream = local->media[index];<br> struct pjmedia_sdp_media *remote_stream = remote->media[index];<br> enum ast_media_type media_type = session_media->type;<br> char host[NI_MAXHOST];<br>- int fdno, res;<br>+ int res;<br> <br> if (!session->channel) {<br>- return 1;<br>- }<br>-<br>- if (!local_stream->desc.port || !remote_stream->desc.port) {<br> return 1;<br> }<br> <br>@@ -1415,16 +1444,16 @@<br> /* Apply connection information to the RTP instance */<br> ast_sockaddr_set_port(addrs, remote_stream->desc.port);<br> ast_rtp_instance_set_remote_address(session_media->rtp, addrs);<br>- if (set_caps(session, session_media, remote_stream, 0, index)) {<br>+ if (set_caps(session, session_media, remote_stream, 0, asterisk_stream)) {<br> return 1;<br> }<br> <br>- if ((fdno = media_type_to_fdno(media_type)) < 0) {<br>- return -1;<br>- }<br>- ast_channel_set_fd(session->channel, fdno, ast_rtp_instance_fd(session_media->rtp, 0));<br>+ ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback);<br>+ ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0),<br>+ media_session_rtp_read_callback);<br> if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) {<br>- ast_channel_set_fd(session->channel, fdno + 1, ast_rtp_instance_fd(session_media->rtp, 1));<br>+ ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1),<br>+ media_session_rtcp_read_callback);<br> }<br> <br> /* If ICE support is enabled find all the needed attributes */<br>diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c<br>index fac4eab..300953c 100644<br>--- a/res/res_pjsip_session.c<br>+++ b/res/res_pjsip_session.c<br>@@ -174,6 +174,112 @@<br> ao2_callback_data(sdp_handlers, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, remove_handler, (void *)stream_type, handler);<br> }<br> <br>+static struct ast_sip_session_media_state *sip_session_media_state_alloc(void)<br>+{<br>+ struct ast_sip_session_media_state *media_state;<br>+<br>+ media_state = ast_calloc(1, sizeof(*media_state));<br>+ if (!media_state) {<br>+ return NULL;<br>+ }<br>+<br>+ if (AST_VECTOR_INIT(&media_state->sessions, DEFAULT_NUM_SESSION_MEDIA) < 0) {<br>+ ast_free(media_state);<br>+ return NULL;<br>+ }<br>+<br>+ if (AST_VECTOR_INIT(&media_state->read_callbacks, DEFAULT_NUM_SESSION_MEDIA) < 0) {<br>+ AST_VECTOR_FREE(&media_state->sessions);<br>+ ast_free(media_state);<br>+ return NULL;<br>+ }<br>+<br>+ return media_state;<br>+}<br>+<br>+static void sip_session_media_state_reset(struct ast_sip_session_media_state *media_state)<br>+{<br>+ int index;<br>+<br>+ if (!media_state) {<br>+ return;<br>+ }<br>+<br>+ AST_VECTOR_RESET(&media_state->sessions, ao2_cleanup);<br>+ AST_VECTOR_RESET(&media_state->read_callbacks, AST_VECTOR_ELEM_CLEANUP_NOOP);<br>+<br>+ for (index = 0; index < AST_MEDIA_TYPE_END; ++index) {<br>+ media_state->default_session[index] = NULL;<br>+ }<br>+<br>+ ast_stream_topology_free(media_state->topology);<br>+ media_state->topology = NULL;<br>+}<br>+<br>+static void sip_session_media_state_free(struct ast_sip_session_media_state *media_state)<br>+{<br>+ if (!media_state) {<br>+ return;<br>+ }<br>+<br>+ sip_session_media_state_reset(media_state);<br>+<br>+ AST_VECTOR_FREE(&media_state->sessions);<br>+ AST_VECTOR_FREE(&media_state->read_callbacks);<br>+<br>+ ast_free(media_state);<br>+}<br>+<br>+int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream)<br>+{<br>+ int index;<br>+<br>+ ast_assert(session->pending_media_state->topology != NULL);<br>+<br>+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {<br>+ return 0;<br>+ }<br>+<br>+ for (index = 0; index < ast_stream_topology_get_count(session->pending_media_state->topology); ++index) {<br>+ if (ast_stream_get_type(ast_stream_topology_get_stream(session->pending_media_state->topology, index)) !=<br>+ ast_stream_get_type(stream)) {<br>+ continue;<br>+ }<br>+<br>+ return ast_stream_topology_get_stream(session->pending_media_state->topology, index) == stream ? 1 : 0;<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+ int fd, ast_sip_session_media_read_cb callback)<br>+{<br>+ struct ast_sip_session_media_read_callback_state callback_state = {<br>+ .fd = fd,<br>+ .read_callback = callback,<br>+ .session = session_media,<br>+ };<br>+<br>+ return AST_VECTOR_APPEND(&session->pending_media_state->read_callbacks, callback_state);<br>+}<br>+<br>+int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>+ ast_sip_session_media_write_cb callback)<br>+{<br>+ if (session_media->write_callback) {<br>+ if (session_media->write_callback == callback) {<br>+ return 0;<br>+ }<br>+<br>+ return -1;<br>+ }<br>+<br>+ session_media->write_callback = callback;<br>+<br>+ return 0;<br>+}<br>+<br> /*!<br> * \brief Set an SDP stream handler for a corresponding session media.<br> *<br>@@ -241,27 +347,48 @@<br> static struct ast_sip_session_media *add_session_media(struct ast_sip_session *session,<br> enum ast_media_type type, int position)<br> {<br>- struct ast_sip_session_media *session_media;<br>+ struct ast_sip_session_media *session_media = NULL;<br> <br>- session_media = ao2_alloc(sizeof(*session_media), session_media_dtor);<br>- if (!session_media) {<br>- return NULL;<br>+ /* If we already have a session media in place in pending then a prior callback has<br>+ * created it.<br>+ */<br>+ if (position < AST_VECTOR_SIZE(&session->pending_media_state->sessions)) {<br>+ return AST_VECTOR_GET(&session->pending_media_state->sessions, position);<br> }<br>- session_media->encryption = session->endpoint->media.rtp.encryption;<br>- session_media->keepalive_sched_id = -1;<br>- session_media->timeout_sched_id = -1;<br>- session_media->type = type;<br>- if (position >= 0) {<br>- struct ast_sip_session_media *old = NULL;<br> <br>- if (position < AST_VECTOR_SIZE(&session->media)) {<br>- old = AST_VECTOR_GET(&session->media, position);<br>+ /* Determine if we can reuse the session media from the active media state if present */<br>+ if (position < AST_VECTOR_SIZE(&session->active_media_state->sessions)) {<br>+ session_media = AST_VECTOR_GET(&session->active_media_state->sessions, position);<br>+ /* A stream can never exist without an accompanying media session */<br>+ if (session_media->type == type) {<br>+ ao2_ref(session_media, +1);<br>+ } else {<br>+ session_media = NULL;<br> }<br>- AST_VECTOR_REPLACE(&session->media, position, session_media);<br>- ao2_cleanup(old);<br>- } else {<br>- AST_VECTOR_APPEND(&session->media, session_media);<br> }<br>+<br>+ if (!session_media) {<br>+ /* No existing media session we can use so create a new one */<br>+ session_media = ao2_alloc(sizeof(*session_media), session_media_dtor);<br>+ if (!session_media) {<br>+ return NULL;<br>+ }<br>+<br>+ session_media->encryption = session->endpoint->media.rtp.encryption;<br>+ session_media->keepalive_sched_id = -1;<br>+ session_media->timeout_sched_id = -1;<br>+ session_media->type = type;<br>+ session_media->stream_num = position;<br>+ }<br>+<br>+ AST_VECTOR_REPLACE(&session->pending_media_state->sessions, position, session_media);<br>+<br>+ /* 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 */<br>+ if (ast_stream_get_state(ast_stream_topology_get_stream(session->pending_media_state->topology, position)) != AST_STREAM_STATE_REMOVED &&<br>+ !session->pending_media_state->default_session[type]) {<br>+ session->pending_media_state->default_session[type] = session_media;<br>+ }<br>+<br> return session_media;<br> }<br> <br>@@ -310,6 +437,14 @@<br> return -1;<br> }<br> <br>+ /* It is possible for SDP deferral to have already created a pending topology */<br>+ if (!session->pending_media_state->topology) {<br>+ session->pending_media_state->topology = ast_stream_topology_alloc();<br>+ if (!session->pending_media_state->topology) {<br>+ return -1;<br>+ }<br>+ }<br>+<br> for (i = 0; i < sdp->media_count; ++i) {<br> /* See if there are registered handlers for this media stream type */<br> char media[20];<br>@@ -318,32 +453,33 @@<br> struct ast_sip_session_media *session_media = NULL;<br> int res;<br> enum ast_media_type type;<br>+ struct ast_stream *stream = NULL;<br> <br> /* We need a null-terminated version of the media string */<br> ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));<br> type = ast_media_type_from_str(media);<br> <br>- if (limitation_reached(type, session->endpoint, type_streams)) {<br>- continue;<br>+ /* See if we have an already existing stream, which can occur from SDP deferral checking */<br>+ if (i < ast_stream_topology_get_count(session->pending_media_state->topology)) {<br>+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);<br> }<br>-<br>- if (i < AST_VECTOR_SIZE(&session->media)) {<br>- session_media = AST_VECTOR_GET(&session->media, i);<br>- if (ast_media_type_from_str(media) != session_media->type) {<br>- /* The stream may have changed types (e.g. from audio to T.38). In this<br>- * case, we can just correct the type and clear the cached SDP handler<br>- * on this session media. The session media destructor will ensure that<br>- * all SDP handlers get a chance to free the memory they allocated<br>- */<br>- session_media->type = type;<br>- session_media->handler = NULL;<br>- }<br>- }<br>- if (!session_media) {<br>- session_media = add_session_media(session, ast_media_type_from_str(media), i);<br>- if (!session_media) {<br>+ if (!stream) {<br>+ stream = ast_stream_alloc(ast_codec_media_type2str(type), type);<br>+ if (!stream) {<br> return -1;<br> }<br>+ ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);<br>+ }<br>+<br>+ session_media = add_session_media(session, ast_media_type_from_str(media), i);<br>+ if (!session_media) {<br>+ return -1;<br>+ }<br>+<br>+ /* Enforce the user configured maximum stream count for this type by marking it declined and moving on*/<br>+ if (limitation_reached(type, session->endpoint, type_streams)) {<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<br>+ continue;<br> }<br> <br> if (session_media->handler) {<br>@@ -351,7 +487,7 @@<br> ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",<br> ast_codec_media_type2str(session_media->type),<br> session_media->handler->id);<br>- res = handler->negotiate_incoming_sdp_stream(session, sdp, i);<br>+ res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);<br> if (res < 0) {<br> /* Catastrophic failure. Abort! */<br> return -1;<br>@@ -378,7 +514,7 @@<br> ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",<br> ast_codec_media_type2str(session_media->type),<br> handler->id);<br>- res = handler->negotiate_incoming_sdp_stream(session, sdp, i);<br>+ res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);<br> if (res < 0) {<br> /* Catastrophic failure. Abort! */<br> return -1;<br>@@ -403,13 +539,31 @@<br> <br> static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *session_media,<br> struct ast_sip_session *session, const pjmedia_sdp_session *local,<br>- const pjmedia_sdp_session *remote, int index)<br>+ const pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)<br> {<br> /* See if there are registered handlers for this media stream type */<br>+ struct pjmedia_sdp_media *local_stream = local->media[index];<br> char media[20];<br> struct ast_sip_session_sdp_handler *handler;<br> RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);<br> int res;<br>+<br>+ /* If this is a declined stream mark it as removed and don't even bother invoking a handler */<br>+ if (!local_stream->desc.port) {<br>+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_REMOVED);<br>+ return 1;<br>+ }<br>+<br>+ /* Determine the state of the stream based on our local SDP */<br>+ if (pjmedia_sdp_media_find_attr2(local_stream, "sendonly", NULL)) {<br>+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDONLY);<br>+ } else if (pjmedia_sdp_media_find_attr2(local_stream, "recvonly", NULL)) {<br>+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_RECVONLY);<br>+ } else if (pjmedia_sdp_media_find_attr2(local_stream, "inactive", NULL)) {<br>+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_INACTIVE);<br>+ } else {<br>+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDRECV);<br>+ }<br> <br> /* We need a null-terminated version of the media string */<br> ast_copy_pj_str(media, &local->media[index]->desc.media, sizeof(media));<br>@@ -419,7 +573,7 @@<br> ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",<br> ast_codec_media_type2str(session_media->type),<br> handler->id);<br>- res = handler->apply_negotiated_sdp_stream(session, local, remote, index);<br>+ res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);<br> if (res >= 0) {<br> ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",<br> ast_codec_media_type2str(session_media->type),<br>@@ -441,7 +595,7 @@<br> ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",<br> ast_codec_media_type2str(session_media->type),<br> handler->id);<br>- res = handler->apply_negotiated_sdp_stream(session, local, remote, index);<br>+ res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);<br> if (res < 0) {<br> /* Catastrophic failure. Abort! */<br> return -1;<br>@@ -469,33 +623,69 @@<br> {<br> int i;<br> int type_streams[AST_MEDIA_TYPE_END] = {0};<br>+ struct ast_sip_session_media_state *media_state;<br> <br> for (i = 0; i < local->media_count; ++i) {<br> struct ast_sip_session_media *session_media;<br> char media[20];<br> enum ast_media_type type;<br>+ struct ast_stream *stream;<br> <br> if (!remote->media[i]) {<br> continue;<br> }<br>+<br> ast_copy_pj_str(media, &local->media[i]->desc.media, sizeof(media));<br> type = ast_media_type_from_str(media);<br>- if (limitation_reached(type, session->endpoint, type_streams)) {<br>- continue;<br>- }<br> <br> /* If we're handling negotiated streams, then we should already have set<br>- * up session media instances that correspond to the local SDP, and there should<br>- * be the same number of session medias as there are local streams<br>+ * up session media instances (and Asterisk streams) that correspond to<br>+ * the local SDP, and there should be the same number of session medias<br>+ * and streams as there are local SDP streams<br> */<br>- ast_assert(i < AST_VECTOR_SIZE(&session->media));<br>+ ast_assert(i < AST_VECTOR_SIZE(&session->pending_media_state->sessions));<br>+ ast_assert(i < ast_stream_topology_get_count(session->pending_media_state->topology));<br> <br>- session_media = AST_VECTOR_GET(&session->media, i);<br>- if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i)) {<br>+ session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, i);<br>+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);<br>+<br>+ if (limitation_reached(type, session->endpoint, type_streams)) {<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<br>+ }<br>+<br>+ if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i, stream)) {<br> return -1;<br> }<br> ++type_streams[type];<br> }<br>+<br>+ /* Apply the pending media state to the channel and make it active */<br>+ ast_channel_lock(session->channel);<br>+<br>+ /* Update the topology on the channel to match the accepted one */<br>+ ast_channel_set_stream_topology(session->channel, session->pending_media_state->topology);<br>+ session->pending_media_state->topology = NULL;<br>+<br>+ /* Remove all current file descriptors from the channel */<br>+ for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++i) {<br>+ ast_channel_internal_fd_clear(session->channel, i + AST_EXTENDED_FDS);<br>+ }<br>+<br>+ /* Add all the file descriptors from the pending media state */<br>+ for (i = 0; i < AST_VECTOR_SIZE(&session->pending_media_state->read_callbacks); ++i) {<br>+ struct ast_sip_session_media_read_callback_state *callback_state = AST_VECTOR_GET_ADDR(&session->pending_media_state->read_callbacks, i);<br>+<br>+ ast_channel_internal_fd_set(session->channel, i + AST_EXTENDED_FDS, callback_state->fd);<br>+ }<br>+<br>+ media_state = session->active_media_state;<br>+ session->active_media_state = session->pending_media_state;<br>+<br>+ /* Active and pending flip flop as needed, so clear the former active for the next pending change */<br>+ session->pending_media_state = media_state;<br>+ sip_session_media_state_reset(session->pending_media_state);<br>+<br>+ ast_channel_unlock(session->channel);<br> <br> ast_queue_frame(session->channel, &ast_null_frame);<br> <br>@@ -1098,34 +1288,38 @@<br> {<br> int i;<br> <br>+ /* It should not be possible to handle an incoming SDP while we have a pending topology already in progress */<br>+ ast_assert(session->pending_media_state->topology == NULL);<br>+<br>+ session->pending_media_state->topology = ast_stream_topology_alloc();<br>+ if (!session->pending_media_state->topology) {<br>+ return -1;<br>+ }<br>+<br> for (i = 0; i < sdp->media_count; ++i) {<br> /* See if there are registered handlers for this media stream type */<br> char media[20];<br> struct ast_sip_session_sdp_handler *handler;<br> RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);<br>+ struct ast_stream *stream;<br>+ enum ast_media_type type;<br> struct ast_sip_session_media *session_media = NULL;<br> enum ast_sip_session_sdp_stream_defer res;<br> <br> /* We need a null-terminated version of the media string */<br> ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));<br> <br>- if (i < AST_VECTOR_SIZE(&session->media)) {<br>- session_media = AST_VECTOR_GET(&session->media, i);<br>- if (ast_media_type_from_str(media) != session_media->type) {<br>- /* The stream may have changed types (e.g. from audio to T.38). In this<br>- * case, we can just correct the type and clear the cached SDP handler<br>- * on this session media. The session media destructor will ensure that<br>- * all SDP handlers get a chance to free the memory they allocated<br>- */<br>- session_media->type = ast_media_type_from_str(media);<br>- session_media->handler = NULL;<br>- }<br>+ type = ast_media_type_from_str(media);<br>+ stream = ast_stream_alloc(ast_codec_media_type2str(type), type);<br>+ if (!stream) {<br>+ return -1;<br> }<br>+<br>+ ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);<br>+<br>+ session_media = add_session_media(session, ast_media_type_from_str(media), i);<br> if (!session_media) {<br>- session_media = add_session_media(session, ast_media_type_from_str(media), i);<br>- if (!session_media) {<br>- return -1;<br>- }<br>+ return -1;<br> }<br> <br> if (session_media->handler) {<br>@@ -1415,8 +1609,8 @@<br> <br> ast_taskprocessor_unreference(session->serializer);<br> ao2_cleanup(session->datastores);<br>- AST_VECTOR_CALLBACK_VOID(&session->media, ao2_cleanup);<br>- AST_VECTOR_FREE(&session->media);<br>+ sip_session_media_state_free(session->active_media_state);<br>+ sip_session_media_state_free(session->pending_media_state);<br> <br> AST_LIST_HEAD_DESTROY(&session->supplements);<br> while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {<br>@@ -1426,7 +1620,7 @@<br> ao2_cleanup(session->endpoint);<br> ao2_cleanup(session->aor);<br> ao2_cleanup(session->contact);<br>- ast_stream_topology_free(session->req_topology);<br>+ ast_stream_topology_free(session->pending_media_state->topology);<br> ao2_cleanup(session->direct_media_cap);<br> <br> ast_dsp_free(session->dsp);<br>@@ -1499,12 +1693,16 @@<br> if (!session->direct_media_cap) {<br> return NULL;<br> }<br>- session->req_topology = ast_stream_topology_alloc();<br>- if (!session->req_topology) {<br>- return NULL;<br>- }<br> session->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);<br> if (!session->datastores) {<br>+ return NULL;<br>+ }<br>+ session->active_media_state = sip_session_media_state_alloc();<br>+ if (!session->active_media_state) {<br>+ return NULL;<br>+ }<br>+ session->pending_media_state = sip_session_media_state_alloc();<br>+ if (!session->pending_media_state) {<br> return NULL;<br> }<br> <br>@@ -1524,10 +1722,6 @@<br> }<br> <br> session->endpoint = ao2_bump(endpoint);<br>-<br>- if (AST_VECTOR_INIT(&session->media, DEFAULT_NUM_SESSION_MEDIA) < 0) {<br>- return NULL;<br>- }<br> <br> if (rdata) {<br> /*<br>@@ -1879,25 +2073,29 @@<br> <br> ast_stream_set_formats(clone_stream, joint_cap);<br> <br>- if (ast_stream_topology_append_stream(session->req_topology, clone_stream) < 0) {<br>+ if (!session->pending_media_state->topology) {<br>+ session->pending_media_state->topology = ast_stream_topology_alloc();<br>+ if (!session->pending_media_state->topology) {<br>+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);<br>+ ao2_ref(session, -1);<br>+ return NULL;<br>+ }<br>+ }<br>+<br>+ if (ast_stream_topology_append_stream(session->pending_media_state->topology, clone_stream) < 0) {<br> ast_stream_free(clone_stream);<br> continue;<br> }<br> }<br> }<br> <br>- if (ast_stream_topology_get_count(session->req_topology) == 0) {<br>- /* We haven't added anything yet to the session->req_topology, either because<br>- * 1) The passed in req_topology had no streams.<br>- * 2) The passed in req_topology had no overlapping capabilities with the endpoint.<br>- */<br>- int i;<br>-<br>- for (i = 0; i < ast_stream_topology_get_count(endpoint->media.topology); ++i) {<br>- struct ast_stream *stream;<br>-<br>- stream = ast_stream_topology_get_stream(endpoint->media.topology, i);<br>- add_session_media(session, ast_stream_get_type(stream), -1);<br>+ if (!session->pending_media_state->topology) {<br>+ /* Use the configured topology on the endpoint as the pending one */<br>+ session->pending_media_state->topology = ast_stream_topology_clone(endpoint->media.topology);<br>+ if (!session->pending_media_state->topology) {<br>+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);<br>+ ao2_ref(session, -1);<br>+ return NULL;<br> }<br> }<br> <br>@@ -3108,9 +3306,9 @@<br> static const pj_str_t STR_IP4 = { "IP4", 3 };<br> static const pj_str_t STR_IP6 = { "IP6", 3 };<br> pjmedia_sdp_session *local;<br>- struct ast_stream_topology *topology;<br> int i;<br> int type_streams[AST_MEDIA_TYPE_END] = {0};<br>+ int stream;<br> <br> if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {<br> ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n");<br>@@ -3131,77 +3329,78 @@<br> pj_strdup2(inv->pool_prov, &local->origin.user, session->endpoint->media.sdpowner);<br> pj_strdup2(inv->pool_prov, &local->name, session->endpoint->media.sdpsession);<br> <br>- if (session->req_topology && ast_stream_topology_get_count(session->req_topology)) {<br>- /* The most common case. An incoming call, an incoming SDP offer, or an application<br>- * has dictated what streams are requested on this call.<br>+ if (!session->pending_media_state->topology || !ast_stream_topology_get_count(session->pending_media_state->topology)) {<br>+ /* We've encountered a situation where we have been told to create a local SDP but noone has given us any indication<br>+ * of what kind of stream topology they would like. As a fallback we use the topology from the configured endpoint.<br> */<br>- topology = session->req_topology;<br>- } else {<br>- /* Weird case. We're originating a call where the codecs were not specified, or we're<br>- * answering an incoming call that had no SDP offer<br>- */<br>- topology = session->endpoint->media.topology;<br>+ ast_stream_topology_free(session->pending_media_state->topology);<br>+ session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);<br>+ if (!session->pending_media_state->topology) {<br>+ return NULL;<br>+ }<br> }<br> <br>- for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {<br>+ for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) {<br> struct ast_sip_session_media *session_media;<br> struct ast_stream *stream;<br> <br>- stream = ast_stream_topology_get_stream(topology, i);<br>+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);<br> <br> if (limitation_reached(ast_stream_get_type(stream), session->endpoint, type_streams)) {<br>- continue;<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<br> }<br> <br>- if (i < AST_VECTOR_SIZE(&session->media)) {<br>- session_media = AST_VECTOR_GET(&session->media, i);<br>- } else {<br>- session_media = add_session_media(session, ast_stream_get_type(stream), i);<br>- }<br>-<br>+ session_media = add_session_media(session, ast_stream_get_type(stream), i);<br> if (!session_media) {<br> return NULL;<br> }<br>+<br> if (add_sdp_streams(session_media, session, local, stream)) {<br> return NULL;<br> }<br>+<br> ++type_streams[ast_stream_get_type(stream)];<br> }<br> <br>- /* Use the connection details of the first media stream if possible for SDP level */<br>- if (local->media_count) {<br>- int stream;<br>+ /* Use the connection details of an available media if possible for SDP level */<br>+ for (stream = 0; stream < local->media_count; stream++) {<br>+ if (!local->media[stream]->conn) {<br>+ continue;<br>+ }<br> <br>- /* Since we are using the first media stream as the SDP level we can get rid of it<br>- * from the stream itself<br>- */<br>- local->conn = local->media[0]->conn;<br>- local->media[0]->conn = NULL;<br>- pj_strassign(&local->origin.net_type, &local->conn->net_type);<br>- pj_strassign(&local->origin.addr_type, &local->conn->addr_type);<br>- pj_strassign(&local->origin.addr, &local->conn->addr);<br>-<br>- /* Go through each media stream seeing if the connection details actually differ,<br>- * if not just use SDP level and reduce the SDP size<br>- */<br>- for (stream = 1; stream < local->media_count; stream++) {<br>+ if (local->conn) {<br> if (!pj_strcmp(&local->conn->net_type, &local->media[stream]->conn->net_type) &&<br> !pj_strcmp(&local->conn->addr_type, &local->media[stream]->conn->addr_type) &&<br> !pj_strcmp(&local->conn->addr, &local->media[stream]->conn->addr)) {<br> local->media[stream]->conn = NULL;<br> }<br>+ continue;<br> }<br>- } else {<br>- local->origin.net_type = STR_IN;<br>- local->origin.addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4;<br>+<br>+ /* This stream's connection info will serve as the connection details for SDP level */<br>+ local->conn = local->media[stream]->conn;<br>+ local->media[stream]->conn = NULL;<br>+<br>+ continue;<br>+ }<br>+<br>+ /* If no SDP level connection details are present then create some */<br>+ if (!local->conn) {<br>+ local->conn = pj_pool_zalloc(inv->pool_prov, sizeof(struct pjmedia_sdp_conn));<br>+ local->conn->net_type = STR_IN;<br>+ local->conn->addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4;<br> <br> if (!ast_strlen_zero(session->endpoint->media.address)) {<br>- pj_strdup2(inv->pool_prov, &local->origin.addr, session->endpoint->media.address);<br>+ pj_strdup2(inv->pool_prov, &local->conn->addr, session->endpoint->media.address);<br> } else {<br>- 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()));<br>+ 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()));<br> }<br> }<br> <br>+ pj_strassign(&local->origin.net_type, &local->conn->net_type);<br>+ pj_strassign(&local->origin.addr_type, &local->conn->addr_type);<br>+ pj_strassign(&local->origin.addr, &local->conn->addr);<br>+<br> return local;<br> }<br> <br>diff --git a/res/res_pjsip_session.exports.in b/res/res_pjsip_session.exports.in<br>index fdfc5fb..b7bd21b8 100644<br>--- a/res/res_pjsip_session.exports.in<br>+++ b/res/res_pjsip_session.exports.in<br>@@ -1,27 +1,7 @@<br> {<br> global:<br>- LINKER_SYMBOL_PREFIXast_sip_session_terminate;<br>- LINKER_SYMBOL_PREFIXast_sip_session_defer_termination;<br>- LINKER_SYMBOL_PREFIXast_sip_session_defer_termination_cancel;<br>- LINKER_SYMBOL_PREFIXast_sip_session_end_if_deferred;<br>- LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler;<br>- LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler;<br>- LINKER_SYMBOL_PREFIXast_sip_session_register_supplement;<br>- LINKER_SYMBOL_PREFIXast_sip_session_unregister_supplement;<br>- LINKER_SYMBOL_PREFIXast_sip_session_alloc_datastore;<br>- LINKER_SYMBOL_PREFIXast_sip_session_add_datastore;<br>- LINKER_SYMBOL_PREFIXast_sip_session_get_datastore;<br>- LINKER_SYMBOL_PREFIXast_sip_session_remove_datastore;<br>- LINKER_SYMBOL_PREFIXast_sip_session_get_identity;<br>- LINKER_SYMBOL_PREFIXast_sip_session_refresh;<br>- LINKER_SYMBOL_PREFIXast_sip_session_send_response;<br>- LINKER_SYMBOL_PREFIXast_sip_session_send_request;<br>- LINKER_SYMBOL_PREFIXast_sip_session_create_invite;<br>- LINKER_SYMBOL_PREFIXast_sip_session_create_outgoing;<br>- LINKER_SYMBOL_PREFIXast_sip_session_suspend;<br>- LINKER_SYMBOL_PREFIXast_sip_session_unsuspend;<br>+ LINKER_SYMBOL_PREFIXast_sip_session_*;<br> LINKER_SYMBOL_PREFIXast_sip_dialog_get_session;<br>- LINKER_SYMBOL_PREFIXast_sip_session_resume_reinvite;<br> LINKER_SYMBOL_PREFIXast_sip_channel_pvt_alloc;<br> local:<br> *;<br>diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c<br>index 924a2ee..026755c 100644<br>--- a/res/res_pjsip_t38.c<br>+++ b/res/res_pjsip_t38.c<br>@@ -195,10 +195,10 @@<br> {<br> int i;<br> <br>- for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {<br>+ for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->sessions); ++i) {<br> struct ast_sip_session_media *session_media;<br> <br>- session_media = AST_VECTOR_GET(&session->media, i);<br>+ session_media = AST_VECTOR_GET(&session->active_media_state->sessions, i);<br> if (session_media->type == AST_MEDIA_TYPE_IMAGE) {<br> return session_media;<br> }<br>@@ -692,11 +692,11 @@<br> <br> /*! \brief Function which negotiates an incoming media stream */<br> static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,<br>- const struct pjmedia_sdp_session *sdp, int index)<br>+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp,<br>+ int index, struct ast_stream *asterisk_stream)<br> {<br> struct t38_state *state;<br> char host[NI_MAXHOST];<br>- struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);<br> pjmedia_sdp_media *stream = sdp->media[index];<br> RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);<br> <br>@@ -845,11 +845,10 @@<br> <br> /*! \brief Function which applies a negotiated stream */<br> static int apply_negotiated_sdp_stream(struct ast_sip_session *session,<br>- const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote,<br>- int index)<br>+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local,<br>+ const struct pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)<br> {<br> RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);<br>- struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);<br> pjmedia_sdp_media *remote_stream = remote->media[index];<br> char host[NI_MAXHOST];<br> struct t38_state *state;<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/5876">change 5876</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/5876"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I8afd8dd2eb538806a39b887af0abd046266e14c7 </div>
<div style="display:none"> Gerrit-Change-Number: 5876 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Joshua Colp <jcolp@digium.com> </div>