<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>