<p>Joshua Colp <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/5876">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Kevin Harwell: Looks good to me, approved
  Joshua Colp: Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">chan_pjsip: Add support for multiple streams of the same type.<br><br>The stream topology (list of streams and order) is now stored with the<br>configured PJSIP endpoints and used during the negotiation process.<br><br>Media negotiation state information has been changed to be stored<br>in a separate object. Two of these objects exist at any one time<br>on a session. The active media state information is what was previously<br>negotiated and the pending media state information is what the<br>media state will become if negotiation succeeds. Streams and other<br>state information is stored in this object using the index (or<br>position) of each individual stream for easy lookup.<br><br>The ability for a media type handler to specify a callback for<br>writing has been added as well as the ability to add file<br>descriptors with a callback which is invoked when data is available<br>to be read on them. This allows media logic to live outside of<br>the chan_pjsip module.<br><br>Direct media has been changed so that only the first audio and<br>video stream are directly connected. In the future once the RTP<br>engine glue API has been updated to know about streams each individual<br>stream can be directly connected as appropriate.<br><br>Media negotiation itself will currently answer all the provided streams<br>on an offer within configured limits and on an offer will use the<br>topology created as a result of the disallow/allow codec lines.<br><br>If a stream has been removed or declined we will now mark it as such<br>within the resulting SDP.<br><br>Applications can now also request that the stream topology change.<br>If we are told to do so we will limit any provided formats to the ones<br>configured on the endpoint and send a re-invite with the new topology.<br><br>Two new configuration options have also been added to PJSIP endpoints:<br><br>max_audio_streams: determines the maximum number of audio streams to<br>offer/accept from an endpoint. Defaults to 1.<br><br>max_video_streams: determines the maximum number of video streams to<br>offer/accept from an endpoint. Defaults to 1.<br><br>ASTERISK-27076<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 configs/samples/pjsip.conf.sample<br>A contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py<br>M include/asterisk/res_pjsip.h<br>M include/asterisk/res_pjsip_session.h<br>M include/asterisk/stream.h<br>M main/channel.c<br>M main/stream.c<br>M res/res_pjsip.c<br>M res/res_pjsip/pjsip_configuration.c<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>17 files changed, 1,925 insertions(+), 703 deletions(-)<br><br></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 83dc77f..7cab428 100644<br>--- a/channels/chan_pjsip.c<br>+++ b/channels/chan_pjsip.c<br>@@ -64,6 +64,7 @@<br> <br> #include "asterisk/res_pjsip.h"<br> #include "asterisk/res_pjsip_session.h"<br>+#include "asterisk/stream.h"<br> <br> #include "pjsip/include/chan_pjsip.h"<br> #include "pjsip/include/dialplan_functions.h"<br>@@ -78,25 +79,22 @@<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 struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast);<br> static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f);<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>@@ -109,16 +107,17 @@<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>+     .read_stream = chan_pjsip_read_stream,<br>        .write = chan_pjsip_write,<br>-   .write_video = chan_pjsip_write,<br>-     .exception = chan_pjsip_read,<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>@@ -159,11 +158,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>@@ -175,7 +183,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>@@ -194,16 +202,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]->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>@@ -265,18 +278,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>@@ -284,7 +322,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>@@ -333,22 +373,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>@@ -368,7 +413,7 @@<br>   if (changed) {<br>                ast_debug(4, "RTP changed on %s; initiating direct media update\n", ast_channel_name(cdata->chan));<br>              res = ast_sip_session_refresh(cdata->session, NULL, NULL, NULL,<br>-                   cdata->session->endpoint->media.direct_media.method, 1);<br>+                    cdata->session->endpoint->media.direct_media.method, 1, NULL);<br>       }<br> <br>  ao2_ref(cdata, -1);<br>@@ -420,14 +465,53 @@<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>+ 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>-    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>+}<br>+<br>+/*!<br>+ * \brief Determine if a topology is compatible with format capabilities<br>+ *<br>+ * This will return true if ANY formats in the topology are compatible with the format<br>+ * capabilities.<br>+ *<br>+ * XXX When supporting true multistream, we will need to be sure to mark which streams from<br>+ * top1 are compatible with which streams from top2. Then the ones that are not compatible<br>+ * will need to be marked as "removed" so that they are negotiated as expected.<br>+ *<br>+ * \param top Topology<br>+ * \param cap Format capabilities<br>+ * \retval 1 The topology has at least one compatible format<br>+ * \retval 0 The topology has no compatible formats or an error occurred.<br>+ */<br>+static int compatible_formats_exist(struct ast_stream_topology *top, struct ast_format_cap *cap)<br>+{<br>+  struct ast_format_cap *cap_from_top;<br>+ int res;<br>+<br>+  cap_from_top = ast_format_cap_from_stream_topology(top);<br>+<br>+  if (!cap_from_top) {<br>+         return 0;<br>     }<br>+<br>+ res = ast_format_cap_iscompatible(cap_from_top, cap);<br>+        ao2_ref(cap_from_top, -1);<br>+<br>+        return res;<br> }<br> <br> /*! \brief Function called to create a new PJSIP Asterisk channel */<br>@@ -438,12 +522,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>-         return NULL;<br>- }<br>-    caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>-    if (!caps) {<br>+ if (!(pvt = ao2_alloc_options(sizeof(*pvt), chan_pjsip_pvt_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {<br>               return NULL;<br>  }<br> <br>@@ -457,14 +538,37 @@<br>           ast_sorcery_object_get_id(session->endpoint),<br>              (unsigned) ast_atomic_fetchadd_int((int *) &chan_idx, +1));<br>       if (!chan) {<br>-         ao2_ref(caps, -1);<br>            return NULL;<br>  }<br> <br>  ast_channel_tech_set(chan, &chan_pjsip_tech);<br> <br>  if (!(channel = ast_sip_channel_pvt_alloc(pvt, session))) {<br>-          ao2_ref(caps, -1);<br>+           ast_channel_unlock(chan);<br>+            ast_hangup(chan);<br>+            return NULL;<br>+ }<br>+<br>+ ast_channel_tech_pvt_set(chan, channel);<br>+<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>+                    ast_hangup(chan);<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->pending_media_state->topology);<br>+            topology = ast_stream_topology_clone(session->pending_media_state->topology);<br>+  }<br>+<br>+ if (!topology || !caps) {<br>+            ao2_cleanup(caps);<br>+           ast_stream_topology_free(topology);<br>           ast_channel_unlock(chan);<br>             ast_hangup(chan);<br>             return NULL;<br>@@ -472,16 +576,8 @@<br> <br>         ast_channel_stage_snapshot(chan);<br> <br>- ast_channel_tech_pvt_set(chan, channel);<br>-<br>-  if (!ast_format_cap_count(session->req_caps) ||<br>-           !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) {<br>-         ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);<br>- } else {<br>-             ast_format_cap_append_from_cap(caps, session->req_caps, AST_MEDIA_TYPE_UNKNOWN);<br>-  }<br>-<br>  ast_channel_nativeformats_set(chan, caps);<br>+   ast_channel_set_stream_topology(chan, topology);<br> <br>   if (!ast_format_cap_empty(caps)) {<br>            struct ast_format *fmt;<br>@@ -538,12 +634,7 @@<br>         ast_channel_stage_snapshot_done(chan);<br>        ast_channel_unlock(chan);<br> <br>- /* If res_pjsip_session is ever updated to create/destroy ast_sip_session_media<br>-       * during a call such as if multiple same-type stream support is introduced,<br>-  * these will need to be recaptured as well */<br>-       pvt->media[SIP_MEDIA_AUDIO] = ao2_find(session->media, "audio", OBJ_KEY);<br>-    pvt->media[SIP_MEDIA_VIDEO] = ao2_find(session->media, "video", OBJ_KEY);<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>@@ -682,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>@@ -794,22 +868,31 @@<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>+            /* What is not guaranteed is that a media session will exist */<br>+              if (stream_num < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions)) {<br>+                 media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, stream_num);<br>+            }<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_debug(3, "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 (media == channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO] &&<br>+                  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>@@ -826,17 +909,32 @@<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_debug(3, "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>+                if (!media) {<br>+                        return 0;<br>+            } else if (media->type != AST_MEDIA_TYPE_IMAGE) {<br>+                 ast_debug(3, "Channel %s stream %d is of type '%s', not image!\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>        default:<br>              ast_log(LOG_WARNING, "Can't send %u type frames with PJSIP\n", frame->frametype);<br>@@ -846,11 +944,15 @@<br>     return res;<br> }<br> <br>+static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)<br>+{<br>+       return chan_pjsip_write_stream(ast, -1, frame);<br>+}<br>+<br> /*! \brief Function called by core to change the underlying owner channel */<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>@@ -863,7 +965,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>@@ -1278,7 +1380,7 @@<br>                  /* Only the INVITE method actually needs SDP, UPDATE can do without */<br>                        generate_new_sdp = (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE);<br> <br>-                     ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp);<br>+                        ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp, NULL);<br>           }<br>     } else if (session->endpoint->id.rpid_immediate<br>                 && session->inv_session->state != PJSIP_INV_STATE_DISCONNECTED<br>@@ -1309,21 +1411,18 @@<br> }<br> <br> /*! \brief Callback which changes the value of locally held on the media stream */<br>-static int local_hold_set_state(void *obj, void *arg, int flags)<br>+static void local_hold_set_state(struct ast_sip_session_media *session_media, unsigned int held)<br> {<br>-  struct ast_sip_session_media *session_media = obj;<br>-   unsigned int *held = arg;<br>-<br>- session_media->locally_held = *held;<br>-<br>-   return 0;<br>+    if (session_media) {<br>+         session_media->locally_held = held;<br>+       }<br> }<br> <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>- ao2_callback(session->media, OBJ_NODATA, local_hold_set_state, &held);<br>-        ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);<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, NULL);<br>   ao2_ref(session, -1);<br> <br>      return 0;<br>@@ -1341,16 +1440,103 @@<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_sip_session_media_state *media_state;<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>+<br>+    ast_sip_session_media_state_free(refresh_data->media_state);<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->media_state = ast_sip_session_media_state_alloc();<br>+  if (!refresh_data->media_state) {<br>+         topology_change_refresh_data_free(refresh_data);<br>+             return NULL;<br>+ }<br>+    refresh_data->media_state->topology = ast_stream_topology_clone(topology);<br>+     if (!refresh_data->media_state->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>+          if (session->channel) {<br>+                   ast_queue_control(session->channel, AST_CONTROL_STREAM_TOPOLOGY_CHANGED);<br>+         }<br>+    } else if (rdata->msg_info.msg->line.status.code != 100) {<br>+             /* The topology change failed, so drop the current pending media state */<br>+            ast_sip_session_media_state_reset(session->pending_media_state);<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>+    int ret;<br>+<br>+  ret = ast_sip_session_refresh(refresh_data->session, NULL, NULL, on_topology_change_response,<br>+             AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, refresh_data->media_state);<br>+     refresh_data->media_state = NULL;<br>+ topology_change_refresh_data_free(refresh_data);<br>+<br>+  return ret;<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>@@ -1403,39 +1589,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>@@ -1530,6 +1724,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>@@ -1744,9 +1942,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>@@ -1755,14 +1954,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>@@ -1858,9 +2057,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>@@ -1943,7 +2143,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>@@ -1952,7 +2151,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>@@ -2050,10 +2249,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>@@ -2062,7 +2261,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>@@ -2072,7 +2270,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>@@ -2083,7 +2281,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>@@ -2091,7 +2288,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>@@ -2110,7 +2306,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>@@ -2119,7 +2315,7 @@<br> <br> struct request_data {<br>     struct ast_sip_session *session;<br>-     struct ast_format_cap *caps;<br>+ struct ast_stream_topology *topology;<br>         const char *dest;<br>     int cause;<br> };<br>@@ -2193,7 +2389,7 @@<br>                }<br>     }<br> <br>- if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->caps))) {<br>+       if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->topology))) {<br>            ast_log(LOG_ERROR, "Failed to create outgoing session to endpoint '%s'\n", endpoint_name);<br>          req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION;<br>          return -1;<br>@@ -2205,12 +2401,12 @@<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.caps = cap;<br>+ req_data.topology = topology;<br>         req_data.dest = data;<br> <br>      if (ast_sip_push_task_synchronous(NULL, request, &req_data)) {<br>@@ -2228,6 +2424,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..33d0e02 100644<br>--- a/channels/pjsip/cli_commands.c<br>+++ b/channels/pjsip/cli_commands.c<br>@@ -342,8 +342,9 @@<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 +352,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 = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];<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 +391,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 +416,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 e2c78cd..59ca9d7 100644<br>--- a/channels/pjsip/dialplan_functions.c<br>+++ b/channels/pjsip/dialplan_functions.c<br>@@ -437,6 +437,7 @@<br> #include "asterisk/acl.h"<br> #include "asterisk/app.h"<br> #include "asterisk/channel.h"<br>+#include "asterisk/stream.h"<br> #include "asterisk/format.h"<br> #include "asterisk/pbx.h"<br> #include "asterisk/res_pjsip.h"<br>@@ -461,8 +462,8 @@<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>@@ -470,9 +471,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>@@ -482,9 +483,9 @@<br>     }<br> <br>  if (ast_strlen_zero(field) || !strcmp(field, "audio")) {<br>-           media = pvt->media[SIP_MEDIA_AUDIO];<br>+              media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];<br>     } else if (!strcmp(field, "video")) {<br>-              media = pvt->media[SIP_MEDIA_VIDEO];<br>+              media = session->active_media_state->default_session[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>@@ -522,17 +523,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>@@ -542,9 +543,9 @@<br>     }<br> <br>  if (ast_strlen_zero(field) || !strcmp(field, "audio")) {<br>-           media = pvt->media[SIP_MEDIA_AUDIO];<br>+              media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];<br>     } else if (!strcmp(field, "video")) {<br>-              media = pvt->media[SIP_MEDIA_VIDEO];<br>+              media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];<br>     } else {<br>              ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field);<br>          return -1;<br>@@ -924,22 +925,117 @@<br>    return 0;<br> }<br> <br>+/*! \brief Session refresh state information */<br>+struct session_refresh_state {<br>+  /*! \brief Created proposed media state */<br>+   struct ast_sip_session_media_state *media_state;<br>+};<br>+<br>+/*! \brief Destructor for session refresh information */<br>+static void session_refresh_state_destroy(void *obj)<br>+{<br>+       struct session_refresh_state *state = obj;<br>+<br>+        ast_sip_session_media_state_free(state->media_state);<br>+     ast_free(obj);<br>+}<br>+<br>+/*! \brief Datastore for attaching session refresh state information */<br>+static const struct ast_datastore_info session_refresh_datastore = {<br>+       .type = "pjsip_session_refresh",<br>+   .destroy = session_refresh_state_destroy,<br>+};<br>+<br>+/*! \brief Helper function which retrieves or allocates a session refresh state information datastore */<br>+static struct session_refresh_state *session_refresh_state_get_or_alloc(struct ast_sip_session *session)<br>+{<br>+  RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "pjsip_session_refresh"), ao2_cleanup);<br>+ struct session_refresh_state *state;<br>+<br>+      /* While the datastore refcount is decremented this is operating in the serializer so it will remain valid regardless */<br>+     if (datastore) {<br>+             return datastore->data;<br>+   }<br>+<br>+ if (!(datastore = ast_sip_session_alloc_datastore(&session_refresh_datastore, "pjsip_session_refresh"))<br>+                || !(datastore->data = ast_calloc(1, sizeof(struct session_refresh_state)))<br>+               || ast_sip_session_add_datastore(session, datastore)) {<br>+              return NULL;<br>+ }<br>+<br>+ state = datastore->data;<br>+  state->media_state = ast_sip_session_media_state_alloc();<br>+ if (!state->media_state) {<br>+                ast_sip_session_remove_datastore(session, "pjsip_session_refresh");<br>+                return NULL;<br>+ }<br>+    state->media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);<br>+     if (!state->media_state->topology) {<br>+           ast_sip_session_remove_datastore(session, "pjsip_session_refresh");<br>+                return NULL;<br>+ }<br>+<br>+ datastore->data = state;<br>+<br>+       return state;<br>+}<br>+<br> static int media_offer_read_av(struct ast_sip_session *session, char *buf,<br>                            size_t len, enum ast_media_type media_type)<br> {<br>+       struct ast_stream_topology *topology;<br>         int idx;<br>+     struct ast_stream *stream = NULL;<br>+    struct ast_format_cap *caps;<br>  size_t accum = 0;<br> <br>+ if (session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {<br>+           struct session_refresh_state *state;<br>+<br>+              /* As we've already answered we need to store our media state until we are ready to send it */<br>+           state = session_refresh_state_get_or_alloc(session);<br>+         if (!state) {<br>+                        return -1;<br>+           }<br>+            topology = state->media_state->topology;<br>+       } else {<br>+             /* The session is not yet up so we are initially answering or offering */<br>+            if (!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 -1;<br>+                   }<br>+            }<br>+            topology = session->pending_media_state->topology;<br>+     }<br>+<br>+ /* Find the first suitable stream */<br>+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {<br>+             stream = ast_stream_topology_get_stream(topology, idx);<br>+<br>+           if (ast_stream_get_type(stream) != media_type ||<br>+                     ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {<br>+                  stream = NULL;<br>+                       continue;<br>+            }<br>+<br>+         break;<br>+       }<br>+<br>+ /* If no suitable stream then exit early */<br>+  if (!stream) {<br>+               buf[0] = '\0';<br>+               return 0;<br>+    }<br>+<br>+ caps = ast_stream_get_formats(stream);<br>+<br>     /* Note: buf is not terminated while the string is being built. */<br>-   for (idx = 0; idx < ast_format_cap_count(session->req_caps); ++idx) {<br>+  for (idx = 0; idx < ast_format_cap_count(caps); ++idx) {<br>           struct ast_format *fmt;<br>               size_t size;<br> <br>-              fmt = ast_format_cap_get_format(session->req_caps, idx);<br>-          if (ast_format_get_type(fmt) != media_type) {<br>-                        ao2_ref(fmt, -1);<br>-                    continue;<br>-            }<br>+            fmt = ast_format_cap_get_format(caps, idx);<br> <br>                /* Add one for a comma or terminator */<br>               size = strlen(ast_format_get_name(fmt)) + 1;<br>@@ -973,9 +1069,43 @@<br> static int media_offer_write_av(void *obj)<br> {<br>  struct media_offer_data *data = obj;<br>+ struct ast_stream_topology *topology;<br>+        struct ast_stream *stream;<br>+   struct ast_format_cap *caps;<br> <br>-      ast_format_cap_remove_by_type(data->session->req_caps, data->media_type);<br>-   ast_format_cap_update_by_allow_disallow(data->session->req_caps, data->value, 1);<br>+   if (data->session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {<br>+          struct session_refresh_state *state;<br>+<br>+              /* As we've already answered we need to store our media state until we are ready to send it */<br>+           state = session_refresh_state_get_or_alloc(data->session);<br>+                if (!state) {<br>+                        return -1;<br>+           }<br>+            topology = state->media_state->topology;<br>+       } else {<br>+             /* The session is not yet up so we are initially answering or offering */<br>+            if (!data->session->pending_media_state->topology) {<br>+                        data->session->pending_media_state->topology = ast_stream_topology_clone(data->session->endpoint->media.topology);<br>+                 if (!data->session->pending_media_state->topology) {<br>+                                return -1;<br>+                   }<br>+            }<br>+            topology = data->session->pending_media_state->topology;<br>+    }<br>+<br>+ /* XXX This method won't work when it comes time to do multistream support. The proper way to do this<br>+     * will either be to<br>+  * a) Alter all media streams of a particular type.<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(topology, data->media_type);<br>+        if (!stream) {<br>+               return 0;<br>+    }<br>+    caps = ast_stream_get_formats(stream);<br>+       ast_format_cap_remove_by_type(caps, data->media_type);<br>+    ast_format_cap_update_by_allow_disallow(caps, data->value, 1);<br> <br>  return 0;<br> }<br>@@ -1068,9 +1198,18 @@<br> static int refresh_write_cb(void *obj)<br> {<br>    struct refresh_data *data = obj;<br>+     struct session_refresh_state *state;<br>+<br>+      state = session_refresh_state_get_or_alloc(data->session);<br>+        if (!state) {<br>+                return -1;<br>+   }<br> <br>  ast_sip_session_refresh(data->session, NULL, NULL,<br>-                sip_session_response_cb, data->method, 1);<br>+                sip_session_response_cb, data->method, 1, state->media_state);<br>+<br>+      state->media_state = NULL;<br>+        ast_sip_session_remove_datastore(data->session, "pjsip_session_refresh");<br> <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/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample<br>index 3b93bb6..ed5f93e 100644<br>--- a/configs/samples/pjsip.conf.sample<br>+++ b/configs/samples/pjsip.conf.sample<br>@@ -779,6 +779,10 @@<br>                               ; The value "yes" is useful for some SIP phones<br>                               ; (Cisco SPA) to be able to indicate and pick up<br>                               ; ringing devices.<br>+;max_audio_streams= ; The maximum number of allowed negotiated audio streams<br>+                    ; (default: 1)<br>+;max_video_streams= ; The maximum number of allowed negotiated video streams<br>+                    ; (default: 1)<br> <br> ;==========================AUTH SECTION OPTIONS=========================<br> ;[auth]<br>diff --git a/contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py b/contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py<br>new file mode 100644<br>index 0000000..a091272<br>--- /dev/null<br>+++ b/contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py<br>@@ -0,0 +1,24 @@<br>+"""pjsip_stream_maximum<br>+<br>+Revision ID: 39959b9c2566<br>+Revises: d7983954dd96<br>+Create Date: 2017-06-15 13:18:12.372333<br>+<br>+"""<br>+<br>+# revision identifiers, used by Alembic.<br>+revision = '39959b9c2566'<br>+down_revision = 'd7983954dd96'<br>+<br>+from alembic import op<br>+import sqlalchemy as sa<br>+<br>+<br>+def upgrade():<br>+    op.add_column('ps_endpoints', sa.Column('max_audio_streams', sa.Integer))<br>+    op.add_column('ps_endpoints', sa.Column('max_video_streams', sa.Integer))<br>+<br>+<br>+def downgrade():<br>+    op.drop_column('ps_endpoints', 'max_audio_streams')<br>+    op.drop_column('ps_endpoints', 'max_video_streams')<br>diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h<br>index b9c50ad..f907eff 100644<br>--- a/include/asterisk/res_pjsip.h<br>+++ b/include/asterisk/res_pjsip.h<br>@@ -666,6 +666,8 @@<br>   struct ast_sip_t38_configuration t38;<br>         /*! Configured codecs */<br>      struct ast_format_cap *codecs;<br>+       /*! Capabilities in topology form */<br>+ struct ast_stream_topology *topology;<br>         /*! DSCP TOS bits for audio streams */<br>        unsigned int tos_audio;<br>       /*! Priority for audio streams */<br>@@ -680,6 +682,10 @@<br>       unsigned int bind_rtp_to_media_address;<br>       /*! Use RTCP-MUX */<br>   unsigned int rtcp_mux;<br>+       /*! Maximum number of audio streams to offer/accept */<br>+       unsigned int max_audio_streams;<br>+      /*! Maximum number of video streams to offer/accept */<br>+       unsigned int max_video_streams;<br> };<br> <br> /*!<br>diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h<br>index e2a9066..e298e1f 100644<br>--- a/include/asterisk/res_pjsip_session.h<br>+++ b/include/asterisk/res_pjsip_session.h<br>@@ -28,6 +28,8 @@<br> #include "asterisk/netsock2.h"<br> /* Needed for ast_sdp_srtp struct */<br> #include "asterisk/sdp_srtp.h"<br>+/* Needed for ast_media_type */<br>+#include "asterisk/codec.h"<br> <br> /* Forward declarations */<br> struct ast_sip_endpoint;<br>@@ -56,17 +58,21 @@<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>  */<br> struct ast_sip_session_media {<br>-   union {<br>-              /*! \brief RTP instance itself */<br>-            struct ast_rtp_instance *rtp;<br>-                /*! \brief UDPTL instance itself */<br>-          struct ast_udptl *udptl;<br>-     };<br>+   /*! \brief RTP instance itself */<br>+    struct ast_rtp_instance *rtp;<br>+        /*! \brief UDPTL instance itself */<br>+  struct ast_udptl *udptl;<br>      /*! \brief Direct media address */<br>    struct ast_sockaddr direct_media_addr;<br>        /*! \brief SDP handler that setup the RTP */<br>@@ -87,8 +93,38 @@<br>      unsigned int locally_held:1;<br>  /*! \brief Does remote support rtcp_mux */<br>    unsigned int remote_rtcp_mux:1;<br>-      /*! \brief Stream type this session media handles */<br>- char stream_type[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 - these are whole structs and not pointers */<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,8 +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>-    /*! Media streams */<br>- struct ao2_container *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>@@ -139,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_format_cap *req_caps;<br>+     /*! Active media state (sessions + streams) - contents are guaranteed not to change */<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>@@ -315,34 +351,29 @@<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 to be setup for this session<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 stream The stream on which to operate<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, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *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>-  * \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>   * \param session_media The media to be setup for this session<br>         * \param sdp The entire SDP as currently built<br>+       * \param remote Optional remote SDP if this is an answer<br>+     * \param stream The stream that is to be added to the outgoing SDP<br>    * \retval 0 This handler has no stream to add. 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 SDP negotiation will be abandoned.<br>       * \retval >0 The handler has a stream to be added to the SDP. No further handler of this stream type will be called.<br>       */<br>-  int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp);<br>+    int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp,<br>+             const struct pjmedia_sdp_session *remote, struct ast_stream *stream);<br>         /*!<br>    * \brief Update media stream with external address if applicable<br>      * \param tdata The outgoing message itself<br>@@ -353,17 +384,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 to be setup for this session<br>+        * \param session_media The media session<br>      * \param local The entire local negotiated SDP<br>-       * \param local_stream The local stream which to apply<br>         * \param remote The entire remote negotiated SDP<br>-     * \param remote_stream The remote stream which to apply<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, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,<br>-              const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream);<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 +425,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>@@ -452,11 +484,11 @@<br>  * \param contact The contact that this session will communicate with<br>  * \param location Name of the location to call, be it named location or explicit URI. Overrides contact if present.<br>  * \param request_user Optional request user to place in the request URI if permitted<br>- * \param req_caps The requested capabilities<br>+ * \param req_topology The requested capabilities<br>  */<br> struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint,<br>     struct ast_sip_contact *contact, const char *location, const char *request_user,<br>-     struct ast_format_cap *req_caps);<br>+    struct ast_stream_topology *req_topology);<br> <br> /*!<br>  * \brief Terminate a session and, if possible, send the provided response code<br>@@ -613,15 +645,20 @@<br>  * \param on_response Callback called when response for request is received<br>  * \param method The method that should be used when constructing the session refresh<br>  * \param generate_new_sdp Boolean to indicate if a new SDP should be created<br>+ * \param media_state Optional requested media state for the SDP<br>+ *<br>  * \retval 0 Successfully sent refresh<br>  * \retval -1 Failure to send refresh<br>+ *<br>+ * \note If a media_state is passed in ownership will be taken in all cases<br>  */<br> int ast_sip_session_refresh(struct ast_sip_session *session,<br>           ast_sip_session_request_creation_cb on_request_creation,<br>              ast_sip_session_sdp_creation_cb on_sdp_creation,<br>              ast_sip_session_response_cb on_response,<br>              enum ast_sip_session_refresh_method method,<br>-          int generate_new_sdp);<br>+               int generate_new_sdp,<br>+                struct ast_sip_session_media_state *media_state);<br> <br> /*!<br>  * \brief Send a SIP response<br>@@ -692,6 +729,110 @@<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 Allocate a session media state structure<br>+ * \since 15.0.0<br>+ *<br>+ * \retval non-NULL success<br>+ * \retval NULL failure<br>+ */<br>+struct ast_sip_session_media_state *ast_sip_session_media_state_alloc(void);<br>+<br>+/*!<br>+ * \brief Allocate an ast_session_media and add it to the media state's vector.<br>+ * \since 15.0.0<br>+ *<br>+ * This allocates a session media of the specified type. The position argument<br>+ * determines where in the vector that the new session media will be inserted.<br>+ *<br>+ * \note The returned ast_session_media is the reference held by the vector. Callers<br>+ * of this function must NOT decrement the refcount of the session media.<br>+ *<br>+ * \param session Session on which to query active media state for<br>+ * \param media_state Media state to place the session media into<br>+ * \param type The type of the session media<br>+ * \param position Position at which to insert the new session media.<br>+ *<br>+ * \note The active media state will be queried and if a media session already<br>+ * exists at the given position for the same type it will be reused instead of<br>+ * allocating a new one.<br>+ *<br>+ * \retval non-NULL success<br>+ * \retval NULL failure<br>+ */<br>+struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,<br>+  struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position);<br>+<br>+/*!<br>+ * \brief Reset a media state to a clean state<br>+ * \since 15.0.0<br>+ *<br>+ * \param media_state The media state to reset<br>+ */<br>+void ast_sip_session_media_state_reset(struct ast_sip_session_media_state *media_state);<br>+<br>+/*!<br>+ * \brief Clone a media state<br>+ * \since 15.0.0<br>+ *<br>+ * \param media_state The media state to clone<br>+ *<br>+ * \retval non-NULL success<br>+ * \retval NULL failure<br>+ */<br>+struct ast_sip_session_media_state *ast_sip_session_media_state_clone(const struct ast_sip_session_media_state *media_state);<br>+<br>+/*!<br>+ * \brief Free a session media state structure<br>+ * \since 15.0.0<br>+ */<br>+void ast_sip_session_media_state_free(struct ast_sip_session_media_state *media_state);<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/include/asterisk/stream.h b/include/asterisk/stream.h<br>index 00169a3..4027231 100644<br>--- a/include/asterisk/stream.h<br>+++ b/include/asterisk/stream.h<br>@@ -290,6 +290,20 @@<br>  const struct ast_stream_topology *topology);<br> <br> /*!<br>+ * \brief Compare two stream topologies to see if they are equal<br>+ *<br>+ * \param left The left topology<br>+ * \param right The right topology<br>+ *<br>+ * \retval 1 topologies are equivalent<br>+ * \retval 0 topologies differ<br>+ *<br>+ * \since 15<br>+ */<br>+int ast_stream_topology_equal(const struct ast_stream_topology *left,<br>+       const struct ast_stream_topology *right);<br>+<br>+/*!<br>  * \brief Destroy a stream topology<br>  *<br>  * \param topology The topology of streams<br>@@ -391,7 +405,7 @@<br>  * since a new format capabilities structure is created for each media type.<br>  *<br>  * \note Each stream will have its name set to the corresponding media type.<br>- * For example: "AST_MEDIA_TYPE_AUDIO".<br>+ * For example: "audio".<br>  *<br>  * \note Each stream will be set to the sendrecv state.<br>  *<br>diff --git a/main/channel.c b/main/channel.c<br>index 8b4dc75..c7c2b9d 100644<br>--- a/main/channel.c<br>+++ b/main/channel.c<br>@@ -4928,17 +4928,28 @@<br>             goto done;<br>    }<br> <br>- /* If this frame is writing an audio or video frame get the stream information */<br>-    if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) {<br>-            /* Initially use the default stream unless an explicit stream is provided */<br>-         stream = default_stream = ast_channel_get_default_stream(chan, ast_format_get_type(fr->subclass.format));<br>-<br>-              if (stream_num >= 0) {<br>-                    if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) {<br>-                         goto done;<br>-                   }<br>-                    stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num);<br>+  if (stream_num >= 0) {<br>+            /* If we were told to write to an explicit stream then allow this frame through, no matter<br>+            * if the type is expected or not (a framehook could change)<br>+          */<br>+          if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) {<br>+                 goto done;<br>            }<br>+            stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num);<br>+          default_stream = ast_channel_get_default_stream(chan, ast_stream_get_type(stream));<br>+  } else if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO || fr->frametype == AST_FRAME_MODEM) {<br>+              /* If we haven't been told of a stream then we need to figure out which once we need */<br>+          enum ast_media_type type = AST_MEDIA_TYPE_UNKNOWN;<br>+<br>+                /* Some frame types have a fixed media type */<br>+               if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) {<br>+                    type = ast_format_get_type(fr->subclass.format);<br>+          } else if (fr->frametype == AST_FRAME_MODEM) {<br>+                    type = AST_MEDIA_TYPE_IMAGE;<br>+         }<br>+<br>+         /* No stream was specified, so use the default one */<br>+                stream = default_stream = ast_channel_get_default_stream(chan, type);<br>         }<br> <br>  /* Perform the framehook write event here. After the frame enters the framehook list<br>@@ -5035,12 +5046,16 @@<br>                         res = ast_channel_tech(chan)->write_video(chan, fr);<br>               } else {<br>                      res = 0;<br>-<br>           }<br>             break;<br>        case AST_FRAME_MODEM:<br>-                res = (ast_channel_tech(chan)->write == NULL) ? 0 :<br>-                       ast_channel_tech(chan)->write(chan, fr);<br>+          if (ast_channel_tech(chan)->write_stream) {<br>+                       res = ast_channel_tech(chan)->write_stream(chan, ast_stream_get_position(stream), fr);<br>+            } else if ((stream == default_stream) && ast_channel_tech(chan)->write) {<br>+                 res = ast_channel_tech(chan)->write(chan, fr);<br>+            } else {<br>+                     res = 0;<br>+             }<br>             break;<br>        case AST_FRAME_VOICE:<br>                 if (ast_opt_generic_plc && ast_format_cmp(fr->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {<br>@@ -10948,6 +10963,12 @@<br>               return -1;<br>    }<br> <br>+ if (ast_stream_topology_equal(ast_channel_get_stream_topology(chan), topology)) {<br>+            ast_debug(3, "Topology of %s already matches what is requested so ignoring topology change request\n",<br>+                             ast_channel_name(chan));<br>+             return 0;<br>+    }<br>+<br>  ast_channel_internal_set_stream_topology_change_source(chan, change_source);<br> <br>       return ast_channel_tech(chan)->indicate(chan, AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE, topology, sizeof(topology));<br>diff --git a/main/stream.c b/main/stream.c<br>index 20179f3..093cd54 100644<br>--- a/main/stream.c<br>+++ b/main/stream.c<br>@@ -284,6 +284,53 @@<br>  return new_topology;<br> }<br> <br>+int ast_stream_topology_equal(const struct ast_stream_topology *left,<br>+  const struct ast_stream_topology *right)<br>+{<br>+ int index;<br>+<br>+        ast_assert(left != NULL);<br>+    ast_assert(right != NULL);<br>+<br>+        if (ast_stream_topology_get_count(left) != ast_stream_topology_get_count(right)) {<br>+           return 0;<br>+    }<br>+<br>+ for (index = 0; index < ast_stream_topology_get_count(left); ++index) {<br>+           const struct ast_stream *left_stream = ast_stream_topology_get_stream(left, index);<br>+          const struct ast_stream *right_stream = ast_stream_topology_get_stream(right, index);<br>+<br>+             if (ast_stream_get_type(left_stream) != ast_stream_get_type(right_stream)) {<br>+                 return 0;<br>+            }<br>+<br>+         if (ast_stream_get_state(left_stream) != ast_stream_get_state(right_stream)) {<br>+                       return 0;<br>+            }<br>+<br>+         if (!ast_stream_get_formats(left_stream) && ast_stream_get_formats(right_stream) &&<br>+                  ast_format_cap_count(ast_stream_get_formats(right_stream))) {<br>+                        /* A NULL format capabilities and an empty format capabilities are the same, as they have<br>+                     * no formats inside. If one does though... they are not equal.<br>+                       */<br>+                  return 0;<br>+            } else if (!ast_stream_get_formats(right_stream) && ast_stream_get_formats(left_stream) &&<br>+                   ast_format_cap_count(ast_stream_get_formats(left_stream))) {<br>+                 return 0;<br>+            } else if (ast_stream_get_formats(left_stream) && ast_stream_get_formats(right_stream) &&<br>+                    !ast_format_cap_identical(ast_stream_get_formats(left_stream), ast_stream_get_formats(right_stream))) {<br>+                      /* But if both are actually present we need to do an actual identical check. */<br>+                      return 0;<br>+            }<br>+<br>+         if (strcmp(ast_stream_get_name(left_stream), ast_stream_get_name(right_stream))) {<br>+                   return 0;<br>+            }<br>+    }<br>+<br>+ return 1;<br>+}<br>+<br> void ast_stream_topology_free(struct ast_stream_topology *topology)<br> {<br>    if (!topology) {<br>diff --git a/res/res_pjsip.c b/res/res_pjsip.c<br>index f6d63c6..e717fdb 100644<br>--- a/res/res_pjsip.c<br>+++ b/res/res_pjsip.c<br>@@ -978,6 +978,20 @@<br>                                           on Ringing when already INUSE.<br>                                        </para></description><br>                             </configOption><br>+                                <configOption name="max_audio_streams" default="1"><br>+                                        <synopsis>The maximum number of allowed audio streams for the endpoint</synopsis><br>+                                        <description><para><br>+                                              This option enforces a limit on the maximum simultaneous negotiated audio<br>+                                            streams allowed for the endpoint.<br>+                                    </para></description><br>+                            </configOption><br>+                                <configOption name="max_video_streams" default="1"><br>+                                        <synopsis>The maximum number of allowed video streams for the endpoint</synopsis><br>+                                        <description><para><br>+                                              This option enforces a limit on the maximum simultaneous negotiated video<br>+                                            streams allowed for the endpoint.<br>+                                    </para></description><br>+                            </configOption><br>                         </configObject><br>                         <configObject name="auth"><br>                            <synopsis>Authentication type</synopsis><br>diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c<br>index 7a05f87..56a8419 100644<br>--- a/res/res_pjsip/pjsip_configuration.c<br>+++ b/res/res_pjsip/pjsip_configuration.c<br>@@ -22,6 +22,7 @@<br> #include "asterisk/test.h"<br> #include "asterisk/statsd.h"<br> #include "asterisk/pbx.h"<br>+#include "asterisk/stream.h"<br> <br> /*! \brief Number of buckets for persistent endpoint information */<br> #define PERSISTENT_BUCKETS 53<br>@@ -1321,6 +1322,11 @@<br>          return -1;<br>    }<br> <br>+ endpoint->media.topology = ast_stream_topology_create_from_format_cap(endpoint->media.codecs);<br>+ if (!endpoint->media.topology) {<br>+          return -1;<br>+   }<br>+<br>  return 0;<br> }<br> <br>@@ -1941,6 +1947,8 @@<br>       ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap));<br>      ast_sorcery_object_field_register(sip_sorcery, "endpoint", "refer_blind_progress", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, refer_blind_progress));<br>        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing));<br>+    ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_audio_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_audio_streams));<br>+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_video_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_video_streams));<br> <br>       if (ast_sip_initialize_sorcery_transport()) {<br>                 ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");<br>@@ -2060,7 +2068,8 @@<br> <br>    ast_string_field_free_memory(endpoint);<br> <br>-   ao2_ref(endpoint->media.codecs, -1);<br>+      ao2_cleanup(endpoint->media.codecs);<br>+      ast_stream_topology_free(endpoint->media.topology);<br>        subscription_configuration_destroy(&endpoint->subscription);<br>   info_configuration_destroy(&endpoint->info);<br>   media_configuration_destroy(&endpoint->media);<br>diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c<br>index c5a673a..03fef40 100644<br>--- a/res/res_pjsip_sdp_rtp.c<br>+++ b/res/res_pjsip_sdp_rtp.c<br>@@ -51,6 +51,8 @@<br> #include "asterisk/sdp_srtp.h"<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>@@ -62,48 +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 Retrieves an ast_format_type based on the given stream_type */<br>-static enum ast_media_type stream_to_media_type(const char *stream_type)<br>-{<br>-  if (!strcasecmp(stream_type, STR_AUDIO)) {<br>-           return AST_MEDIA_TYPE_AUDIO;<br>- } else if (!strcasecmp(stream_type, STR_VIDEO)) {<br>-            return AST_MEDIA_TYPE_VIDEO;<br>- }<br>-<br>- return 0;<br>-}<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>-/*! \brief Remove all other cap types but the one given */<br>-static void format_cap_only_type(struct ast_format_cap *caps, enum ast_media_type media_type)<br>-{<br>-   int i = 0;<br>-   while (i <= AST_MEDIA_TYPE_TEXT) {<br>-                if (i != media_type && i != AST_MEDIA_TYPE_UNKNOWN) {<br>-                        ast_format_cap_remove_by_type(caps, i);<br>-              }<br>-            i += 1;<br>-      }<br>-}<br> <br> static int send_keepalive(const void *data)<br> {<br>@@ -253,11 +214,11 @@<br>             ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);<br>      }<br> <br>- if (!strcmp(session_media->stream_type, STR_AUDIO) &&<br>+     if (session_media->type == AST_MEDIA_TYPE_AUDIO &&<br>                         (session->endpoint->media.tos_audio || session->endpoint->media.cos_audio)) {<br>             ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_audio,<br>                             session->endpoint->media.cos_audio, "SIP RTP Audio");<br>-        } else if (!strcmp(session_media->stream_type, STR_VIDEO) &&<br>+      } else if (session_media->type == AST_MEDIA_TYPE_VIDEO &&<br>                  (session->endpoint->media.tos_video || session->endpoint->media.cos_video)) {<br>             ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video,<br>                             session->endpoint->media.cos_video, "SIP RTP Video");<br>@@ -347,12 +308,13 @@<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)<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>   RAII_VAR(struct ast_format_cap *, joint, NULL, ao2_cleanup);<br>- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);<br>+        RAII_VAR(struct ast_format_cap *, endpoint_caps, NULL, ao2_cleanup);<br>+ enum ast_media_type media_type = session_media->type;<br>      struct ast_rtp_codecs codecs = AST_RTP_CODECS_NULL_INIT;<br>      int fmts = 0;<br>         int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&<br>@@ -362,14 +324,14 @@<br>   if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||<br>        !(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||<br>        !(joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {<br>-          ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);<br>+         ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",<br>+                 ast_codec_media_type2str(session_media->type));<br>            return -1;<br>    }<br> <br>  /* get the endpoint capabilities */<br>   if (direct_media_enabled) {<br>           ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);<br>-            format_cap_only_type(caps, media_type);<br>       } else {<br>              ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type);<br>      }<br>@@ -386,7 +348,7 @@<br> <br>             ast_rtp_codecs_payloads_destroy(&codecs);<br>                 ast_log(LOG_NOTICE, "No joint capabilities for '%s' media stream between our configuration(%s) and incoming SDP(%s)\n",<br>-                    session_media->stream_type,<br>+                       ast_codec_media_type2str(session_media->type),<br>                     ast_format_cap_get_names(caps, &usbuf),<br>                   ast_format_cap_get_names(peer, &thembuf));<br>                return -1;<br>@@ -402,9 +364,9 @@<br>       ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),<br>                 session_media->rtp);<br> <br>-   ast_format_cap_append_from_cap(session->req_caps, joint, AST_MEDIA_TYPE_UNKNOWN);<br>+ ast_stream_set_formats(asterisk_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>@@ -968,24 +930,21 @@<br> }<br> <br> /*! \brief Function which negotiates an incoming media stream */<br>-static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>-                                        const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)<br>+static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,<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>-      enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);<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>        int res;<br> <br>-  /* If port is 0, ignore this media stream */<br>- if (!stream->desc.port) {<br>-         ast_debug(3, "Media stream '%s' is already declined\n", session_media->stream_type);<br>-            return 0;<br>-    }<br>-<br>  /* If no type formats have been configured reject this stream */<br>      if (!ast_format_cap_has_type(session->endpoint->media.codecs, media_type)) {<br>-           ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n", session_media->stream_type);<br>+             ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n",<br>+                     ast_codec_media_type2str(session_media->type));<br>            return 0;<br>     }<br> <br>@@ -1040,7 +999,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)) {<br>+   if (set_caps(session, session_media, stream, 1, asterisk_stream)) {<br>           return 0;<br>     }<br>     return 1;<br>@@ -1161,9 +1120,10 @@<br> <br> /*! \brief Function which creates an outgoing stream */<br> static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>-                               struct pjmedia_sdp_session *sdp)<br>+                                     struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, 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>@@ -1180,33 +1140,60 @@<br>       int min_packet_size = 0, max_packet_size = 0;<br>         int rtp_code;<br>         RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);<br>-  enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);<br>-        int use_override_prefs = ast_format_cap_count(session->req_caps);<br>+ enum ast_media_type media_type = session_media->type;<br> <br>   int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&<br>          ast_format_cap_count(session->direct_media_cap);<br> <br>-       if ((use_override_prefs && !ast_format_cap_has_type(session->req_caps, media_type)) ||<br>-        (!use_override_prefs && !ast_format_cap_has_type(session->endpoint->media.codecs, media_type))) {<br>-          /* If no type formats are configured don't add a stream */<br>-               return 0;<br>-    } else if (!session_media->rtp && create_rtp(session, session_media)) {<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 OR if no formats exist then construct a minimal stream in SDP */<br>+        if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED || !ast_stream_get_formats(stream) ||<br>+           !ast_format_cap_count(ast_stream_get_formats(stream))) {<br>+             media->desc.port = 0;<br>+             media->desc.port_count = 1;<br>+<br>+            if (remote) {<br>+                        pjmedia_sdp_media *remote_media = remote->media[ast_stream_get_position(stream)];<br>+                 int index;<br>+<br>+                        media->desc.transport = remote_media->desc.transport;<br>+<br>+                       /* Preserve existing behavior by copying the formats provided from the offer */<br>+                      for (index = 0; index < remote_media->desc.fmt_count; ++index) {<br>+                               media->desc.fmt[index] = remote_media->desc.fmt[index];<br>+                        }<br>+                    media->desc.fmt_count = remote_media->desc.fmt_count;<br>+          } else {<br>+                     /* This is actually an offer so put a dummy payload in that is ignored and sane transport */<br>+                 media->desc.transport = STR_RTP_AVP;<br>+                      pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], "32");<br>+               }<br>+<br>+         sdp->media[sdp->media_count++] = media;<br>+                ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<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>+ /* Crypto has to be added before setting the media transport so that SRTP is properly<br>+         * set up according to the configuration. This ends up changing the media transport.<br>+  */<br>   if (add_crypto_to_stream(session, session_media, pool, media)) {<br>              return -1;<br>    }<br> <br>- media->desc.media = pj_str(session_media->stream_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>@@ -1219,6 +1206,11 @@<br>                       session->endpoint->media.rtp.force_avp));<br>       }<br> <br>+ media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));<br>+      if (!media->conn) {<br>+               return -1;<br>+   }<br>+<br>  /* Add connection level details */<br>    if (direct_media_enabled) {<br>           hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR);<br>@@ -1229,7 +1221,8 @@<br>  }<br> <br>  if (ast_strlen_zero(hostip)) {<br>-               ast_log(LOG_ERROR, "No local host IP available for stream %s\n", session_media->stream_type);<br>+           ast_log(LOG_ERROR, "No local host IP available for stream %s\n",<br>+                   ast_codec_media_type2str(session_media->type));<br>            return -1;<br>    }<br> <br>@@ -1247,25 +1240,23 @@<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", session_media->stream_type);<br>+         ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",<br>+                 ast_codec_media_type2str(session_media->type));<br>            return -1;<br>    }<br> <br>  if (direct_media_enabled) {<br>           ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);<br>-    } else if (!ast_format_cap_count(session->req_caps) ||<br>-            !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) {<br>-         ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type);<br>      } else {<br>-             ast_format_cap_append_from_cap(caps, session->req_caps, media_type);<br>+              ast_format_cap_append_from_cap(caps, ast_stream_get_formats(stream), media_type);<br>     }<br> <br>  for (index = 0; index < ast_format_cap_count(caps); ++index) {<br>@@ -1302,7 +1293,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>@@ -1368,20 +1360,62 @@<br>    return 1;<br> }<br> <br>-static 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_media *local_stream,<br>-                                       const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)<br>+static struct ast_frame *media_session_rtp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)<br> {<br>- RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);<br>-      enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);<br>-        char host[NI_MAXHOST];<br>-       int fdno, res;<br>+       struct ast_frame *f;<br> <br>-      if (!session->channel) {<br>-          return 1;<br>+    if (!session_media->rtp) {<br>+                return &ast_null_frame;<br>   }<br> <br>- if (!local_stream->desc.port || !remote_stream->desc.port) {<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>+        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 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 res;<br>+<br>+  if (!session->channel) {<br>           return 1;<br>     }<br> <br>@@ -1424,20 +1458,25 @@<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)) {<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>        process_ice_attributes(session, session_media, remote, remote_stream);<br>+<br>+    /* Set the channel uniqueid on the RTP instance now that it is becoming active */<br>+    ast_channel_lock(session->channel);<br>+       ast_rtp_instance_set_channel_id(session_media->rtp, ast_channel_uniqueid(session->channel));<br>+   ast_channel_unlock(session->channel);<br> <br>   /* Ensure the RTP instance is active */<br>       ast_rtp_instance_activate(session_media->rtp);<br>@@ -1476,7 +1515,7 @@<br>      session_media->encryption = session->endpoint->media.rtp.encryption;<br> <br>      if (session->endpoint->media.rtp.keepalive > 0 &&<br>-                   stream_to_media_type(session_media->stream_type) == AST_MEDIA_TYPE_AUDIO) {<br>+                       session_media->type == AST_MEDIA_TYPE_AUDIO) {<br>             ast_rtp_instance_set_keepalive(session_media->rtp, session->endpoint->media.rtp.keepalive);<br>          /* Schedule the initial keepalive early in case this is being used to punch holes through<br>              * a NAT. This way there won't be an awkward delay before media starts flowing in some<br>diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c<br>index ffd01ca..ecda499 100644<br>--- a/res/res_pjsip_session.c<br>+++ b/res/res_pjsip_session.c<br>@@ -47,11 +47,15 @@<br> #include "asterisk/features_config.h"<br> #include "asterisk/pickup.h"<br> #include "asterisk/test.h"<br>+#include "asterisk/stream.h"<br> <br> #define SDP_HANDLER_BUCKETS 11<br> <br> #define MOD_DATA_ON_RESPONSE "on_response"<br> #define MOD_DATA_NAT_HOOK "nat_hook"<br>+<br>+/* Most common case is one audio and one video stream */<br>+#define DEFAULT_NUM_SESSION_MEDIA 2<br> <br> /* Some forward declarations */<br> static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata);<br>@@ -101,23 +105,6 @@<br>   const char *stream_type2 = flags & OBJ_KEY ? arg : handler_list2->stream_type;<br> <br>      return strcmp(handler_list1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;<br>-}<br>-<br>-static int session_media_hash(const void *obj, int flags)<br>-{<br>-        const struct ast_sip_session_media *session_media = obj;<br>-     const char *stream_type = flags & OBJ_KEY ? obj : session_media->stream_type;<br>-<br>-      return ast_str_hash(stream_type);<br>-}<br>-<br>-static int session_media_cmp(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_session_media *session_media1 = obj;<br>-  struct ast_sip_session_media *session_media2 = arg;<br>-  const char *stream_type2 = flags & OBJ_KEY ? arg : session_media2->stream_type;<br>-<br>-    return strcmp(session_media1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;<br> }<br> <br> int ast_sip_session_register_sdp_handler(struct ast_sip_session_sdp_handler *handler, const char *stream_type)<br>@@ -187,6 +174,156 @@<br>        ao2_callback_data(sdp_handlers, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, remove_handler, (void *)stream_type, handler);<br> }<br> <br>+struct ast_sip_session_media_state *ast_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>+void ast_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>+struct ast_sip_session_media_state *ast_sip_session_media_state_clone(const struct ast_sip_session_media_state *media_state)<br>+{<br>+   struct ast_sip_session_media_state *cloned;<br>+  int index;<br>+<br>+        if (!media_state) {<br>+          return NULL;<br>+ }<br>+<br>+ cloned = ast_sip_session_media_state_alloc();<br>+        if (!cloned) {<br>+               return NULL;<br>+ }<br>+<br>+ if (media_state->topology) {<br>+              cloned->topology = ast_stream_topology_clone(media_state->topology);<br>+           if (!cloned->topology) {<br>+                  ast_sip_session_media_state_free(cloned);<br>+                    return NULL;<br>+         }<br>+    }<br>+<br>+ for (index = 0; index < AST_VECTOR_SIZE(&media_state->sessions); ++index) {<br>+                struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index);<br>+          enum ast_media_type type = ast_stream_get_type(ast_stream_topology_get_stream(cloned->topology, index));<br>+<br>+               AST_VECTOR_REPLACE(&cloned->sessions, index, ao2_bump(session_media));<br>+                if (ast_stream_get_state(ast_stream_topology_get_stream(cloned->topology, index)) != AST_STREAM_STATE_REMOVED &&<br>+                  !cloned->default_session[type]) {<br>+                 cloned->default_session[type] = session_media;<br>+            }<br>+    }<br>+<br>+ for (index = 0; index < AST_VECTOR_SIZE(&media_state->read_callbacks); ++index) {<br>+          struct ast_sip_session_media_read_callback_state *read_callback = AST_VECTOR_GET_ADDR(&media_state->read_callbacks, index);<br>+<br>+                AST_VECTOR_REPLACE(&cloned->read_callbacks, index, *read_callback);<br>+   }<br>+<br>+ return cloned;<br>+}<br>+<br>+void ast_sip_session_media_state_free(struct ast_sip_session_media_state *media_state)<br>+{<br>+   if (!media_state) {<br>+          return;<br>+      }<br>+<br>+ /* This will reset the internal state so we only have to free persistent things */<br>+   ast_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>+        /* The contents of the vector are whole structs and not pointers */<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>@@ -207,14 +344,120 @@<br>  session_media->handler = handler;<br> }<br> <br>+static int stream_destroy(void *obj, void *arg, int flags)<br>+{<br>+ struct sdp_handler_list *handler_list = obj;<br>+ struct ast_sip_session_media *session_media = arg;<br>+   struct ast_sip_session_sdp_handler *handler;<br>+<br>+      AST_LIST_TRAVERSE(&handler_list->list, handler, next) {<br>+               handler->stream_destroy(session_media);<br>+   }<br>+<br>+ return 0;<br>+}<br>+<br>+static void session_media_dtor(void *obj)<br>+{<br>+     struct ast_sip_session_media *session_media = obj;<br>+<br>+        /* It is possible for multiple handlers to have allocated memory on the<br>+       * session media (usually through a stream changing types). Therefore, we<br>+     * traverse all the SDP handlers and let them all call stream_destroy on<br>+      * the session_media<br>+  */<br>+  ao2_callback(sdp_handlers, 0, stream_destroy, session_media);<br>+<br>+     if (session_media->srtp) {<br>+                ast_sdp_srtp_destroy(session_media->srtp);<br>+        }<br>+}<br>+<br>+struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,<br>+    struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position)<br>+{<br>+ struct ast_sip_session_media *session_media = NULL;<br>+<br>+       /* It is possible for this media state to already contain a session for the stream. If this<br>+   * is the case we simply return it.<br>+   */<br>+  if (position < AST_VECTOR_SIZE(&media_state->sessions)) {<br>+          return AST_VECTOR_GET(&media_state->sessions, position);<br>+      }<br>+<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>+    }<br>+<br>+ if (!session_media) {<br>+                /* No existing media session we can use so create a new one */<br>+               session_media = ao2_alloc_options(sizeof(*session_media), session_media_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);<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(&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 (!media_state->default_session[type] &&<br>+                ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) {<br>+              media_state->default_session[type] = session_media;<br>+       }<br>+<br>+ return session_media;<br>+}<br>+<br>+static int is_stream_limitation_reached(enum ast_media_type type, const struct ast_sip_endpoint *endpoint, int *type_streams)<br>+{<br>+     switch (type) {<br>+      case AST_MEDIA_TYPE_AUDIO:<br>+           return !(type_streams[type] < endpoint->media.max_audio_streams);<br>+      case AST_MEDIA_TYPE_VIDEO:<br>+           return !(type_streams[type] < endpoint->media.max_video_streams);<br>+      case AST_MEDIA_TYPE_IMAGE:<br>+           /* We don't have an option for image (T.38) streams so cap it to one. */<br>+         return (type_streams[type] > 0);<br>+  case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+    default:<br>+             /* We don't want any unknown or "other" streams on our endpoint,<br>+                * so always just say we've reached the limit<br>+             */<br>+          return 1;<br>+    }<br>+}<br>+<br> static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp)<br> {<br>        int i;<br>        int handled = 0;<br>+     int type_streams[AST_MEDIA_TYPE_END] = {0};<br> <br>        if (session->inv_session && session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {<br>           ast_log(LOG_ERROR, "Failed to handle incoming SDP. Session has been already disconnected\n");<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>@@ -222,35 +465,57 @@<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>-                RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);<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>+            pjmedia_sdp_media *remote_stream = sdp->media[i];<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>-            session_media = ao2_find(session->media, media, OBJ_KEY);<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>+            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 = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i);<br>                 if (!session_media) {<br>-                        /* if the session_media doesn't exist, there weren't<br>-                  * any handlers at the time of its creation */<br>+                       return -1;<br>+           }<br>+<br>+         /* If this stream is already declined mark it as such, or mark it as such if we've reached the limit */<br>+          if (!remote_stream->desc.port || is_stream_limitation_reached(type, session->endpoint, type_streams)) {<br>+                        ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",<br>+                                ast_codec_media_type2str(type), i);<br>+                  ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<br>                       continue;<br>             }<br> <br>          if (session_media->handler) {<br>                      handler = session_media->handler;<br>                  ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",<br>-                          session_media->stream_type,<br>+                               ast_codec_media_type2str(session_media->type),<br>                             session_media->handler->id);<br>-                   res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp,<br>-                         sdp->media[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>                    } else if (res > 0) {<br>                              ast_debug(1, "Media stream '%s' handled by %s\n",<br>-                                  session_media->stream_type,<br>+                                       ast_codec_media_type2str(session_media->type),<br>                                     session_media->handler->id);<br>                            /* Handled by this handler. Move to the next stream */<br>                                handled = 1;<br>+                         ++type_streams[type];<br>                                 continue;<br>                     }<br>             }<br>@@ -265,21 +530,21 @@<br>                              continue;<br>                     }<br>                     ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",<br>-                          session_media->stream_type,<br>+                               ast_codec_media_type2str(session_media->type),<br>                             handler->id);<br>-                     res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp,<br>-                         sdp->media[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>                    }<br>                     if (res > 0) {<br>                             ast_debug(1, "Media stream '%s' handled by %s\n",<br>-                                  session_media->stream_type,<br>+                                       ast_codec_media_type2str(session_media->type),<br>                                     handler->id);<br>                              /* Handled by this handler. Move to the next stream */<br>                                session_media_set_handler(session_media, handler);<br>                            handled = 1;<br>+                         ++type_streams[type];<br>                                 break;<br>                        }<br>             }<br>@@ -290,110 +555,159 @@<br>    return 0;<br> }<br> <br>-struct handle_negotiated_sdp_cb {<br>- struct ast_sip_session *session;<br>-     const pjmedia_sdp_session *local;<br>-    const pjmedia_sdp_session *remote;<br>-};<br>-<br>-static int handle_negotiated_sdp_session_media(void *obj, void *arg, int flags)<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, struct ast_stream *asterisk_stream)<br> {<br>-        struct ast_sip_session_media *session_media = obj;<br>-   struct handle_negotiated_sdp_cb *callback_data = arg;<br>-        struct ast_sip_session *session = callback_data->session;<br>- const pjmedia_sdp_session *local = callback_data->local;<br>-  const pjmedia_sdp_session *remote = callback_data->remote;<br>-        int i;<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>-  for (i = 0; i < local->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>-                int res;<br>-<br>-          if (!remote->media[i]) {<br>-                  continue;<br>+    /* For backwards compatibility we only reflect the stream state correctly on<br>+  * the non-default streams. This is because the stream state is also used for<br>+         * signaling that someone has placed us on hold. This situation is not handled<br>+        * currently and can result in the remote side being sort of placed on hold too.<br>+      */<br>+  if (!ast_sip_session_is_pending_stream_default(session, asterisk_stream)) {<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>+    } 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[i]->desc.media, sizeof(media));<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> <br>-                /* stream type doesn't match the one we're looking to fill */<br>-                if (strcasecmp(session_media->stream_type, media)) {<br>-                      continue;<br>-            }<br>-<br>-         handler = session_media->handler;<br>-         if (handler) {<br>-                       ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",<br>-                           session_media->stream_type,<br>+       handler = session_media->handler;<br>+ if (handler) {<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, 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>                             handler->id);<br>-                     res = handler->apply_negotiated_sdp_stream(session, session_media, local,<br>-                         local->media[i], remote, remote->media[i]);<br>-                    if (res >= 0) {<br>-                           ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",<br>-                                    session_media->stream_type,<br>-                                       handler->id);<br>-                             return CMP_MATCH;<br>-                    }<br>                     return 0;<br>             }<br>+            return -1;<br>+   }<br> <br>-         handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);<br>-               if (!handler_list) {<br>-                 ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);<br>+   handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);<br>+       if (!handler_list) {<br>+         ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);<br>+           return -1;<br>+   }<br>+    AST_LIST_TRAVERSE(&handler_list->list, handler, next) {<br>+               if (handler == session_media->handler) {<br>                   continue;<br>             }<br>-            AST_LIST_TRAVERSE(&handler_list->list, handler, next) {<br>-                       if (handler == session_media->handler) {<br>-                          continue;<br>-                    }<br>-                    ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",<br>-                           session_media->stream_type,<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, session_media, local, remote, index, asterisk_stream);<br>+                if (res < 0) {<br>+                    /* Catastrophic failure. Abort! */<br>+                   return -1;<br>+           }<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>                             handler->id);<br>-                     res = handler->apply_negotiated_sdp_stream(session, session_media, local,<br>-                         local->media[i], remote, remote->media[i]);<br>-                    if (res < 0) {<br>-                            /* Catastrophic failure. Abort! */<br>-                           return 0;<br>-                    }<br>-                    if (res > 0) {<br>-                            ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",<br>-                                    session_media->stream_type,<br>-                                       handler->id);<br>-                             /* Handled by this handler. Move to the next stream */<br>-                               session_media_set_handler(session_media, handler);<br>-                           return CMP_MATCH;<br>-                    }<br>+                    /* Handled by this handler. Move to the next stream */<br>+                       session_media_set_handler(session_media, handler);<br>+                   return 0;<br>             }<br>     }<br> <br>  if (session_media->handler && session_media->handler->stream_stop) {<br>                 ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n",<br>-                 session_media->stream_type);<br>+                      ast_codec_media_type2str(session_media->type));<br>            session_media->handler->stream_stop(session_media);<br>     }<br> <br>- return CMP_MATCH;<br>+    return 0;<br> }<br> <br> static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote)<br> {<br>-        RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);<br>-     struct handle_negotiated_sdp_cb callback_data = {<br>-            .session = session,<br>-          .local = local,<br>-              .remote = remote,<br>-    };<br>+   int i;<br>+       struct ast_stream_topology *topology;<br> <br>-     successful = ao2_callback(session->media, OBJ_MULTIPLE, handle_negotiated_sdp_session_media, &callback_data);<br>- if (successful && ao2_iterator_count(successful) == ao2_container_count(session->media)) {<br>-                /* Nothing experienced a catastrophic failure */<br>-             ast_queue_frame(session->channel, &ast_null_frame);<br>-           return 0;<br>+    for (i = 0; i < local->media_count; ++i) {<br>+             struct ast_sip_session_media *session_media;<br>+         struct ast_stream *stream;<br>+<br>+                if (!remote->media[i]) {<br>+                  continue;<br>+            }<br>+<br>+         /* If we're handling negotiated streams, then we should already have set<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->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->pending_media_state->sessions, i);<br>+                stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);<br>+<br>+         /* The stream state will have already been set to removed when either we<br>+              * negotiate the incoming SDP stream or when we produce our own local SDP.<br>+            * This can occur if an internal thing has requested it to be removed, or if<br>+          * we remove it as a result of the stream limit being reached.<br>+                */<br>+          if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {<br>+                      continue;<br>+            }<br>+<br>+         if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i, stream)) {<br>+                 return -1;<br>+           }<br>     }<br>-    return -1;<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>+   topology = ast_stream_topology_clone(session->pending_media_state->topology);<br>+  if (topology) {<br>+              ast_channel_set_stream_topology(session->channel, topology);<br>+      }<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>+ /* Active and pending flip flop as needed */<br>+ SWAP(session->active_media_state, session->pending_media_state);<br>+       ast_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>+        return 0;<br> }<br> <br> AST_RWLIST_HEAD_STATIC(session_supplements, ast_sip_session_supplement);<br>@@ -570,6 +884,8 @@<br>      ast_sip_session_response_cb on_response;<br>      /*! Whether to generate new SDP */<br>    int generate_new_sdp;<br>+        /*! Requested media state for the SDP */<br>+     struct ast_sip_session_media_state *media_state;<br>      AST_LIST_ENTRY(ast_sip_session_delayed_request) next;<br> };<br> <br>@@ -578,7 +894,8 @@<br>    ast_sip_session_request_creation_cb on_request_creation,<br>      ast_sip_session_sdp_creation_cb on_sdp_creation,<br>      ast_sip_session_response_cb on_response,<br>-     int generate_new_sdp)<br>+        int generate_new_sdp,<br>+        struct ast_sip_session_media_state *media_state)<br> {<br>  struct ast_sip_session_delayed_request *delay = ast_calloc(1, sizeof(*delay));<br> <br>@@ -590,7 +907,14 @@<br>       delay->on_sdp_creation = on_sdp_creation;<br>  delay->on_response = on_response;<br>  delay->generate_new_sdp = generate_new_sdp;<br>+       delay->media_state = media_state;<br>  return delay;<br>+}<br>+<br>+static void delayed_request_free(struct ast_sip_session_delayed_request *delay)<br>+{<br>+   ast_sip_session_media_state_free(delay->media_state);<br>+     ast_free(delay);<br> }<br> <br> static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_session_delayed_request *delay)<br>@@ -604,12 +928,16 @@<br>      case DELAYED_METHOD_INVITE:<br>           ast_sip_session_refresh(session, delay->on_request_creation,<br>                       delay->on_sdp_creation, delay->on_response,<br>-                    AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp);<br>+                  AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->media_state);<br>+           /* Ownership of media state transitions to ast_sip_session_refresh */<br>+                delay->media_state = NULL;<br>                 return 0;<br>     case DELAYED_METHOD_UPDATE:<br>           ast_sip_session_refresh(session, delay->on_request_creation,<br>                       delay->on_sdp_creation, delay->on_response,<br>-                    AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp);<br>+                  AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->media_state);<br>+           /* Ownership of media state transitions to ast_sip_session_refresh */<br>+                delay->media_state = NULL;<br>                 return 0;<br>     case DELAYED_METHOD_BYE:<br>              ast_sip_session_terminate(session, 0);<br>@@ -644,7 +972,7 @@<br>           case DELAYED_METHOD_UPDATE:<br>                   AST_LIST_REMOVE_CURRENT(next);<br>                        res = send_delayed_request(session, delay);<br>-                  ast_free(delay);<br>+                     delayed_request_free(delay);<br>                  found = 1;<br>                    break;<br>                case DELAYED_METHOD_BYE:<br>@@ -698,7 +1026,7 @@<br>                if (found) {<br>                  AST_LIST_REMOVE_CURRENT(next);<br>                        res = send_delayed_request(session, delay);<br>-                  ast_free(delay);<br>+                     delayed_request_free(delay);<br>                  break;<br>                }<br>     }<br>@@ -775,12 +1103,14 @@<br>     ast_sip_session_sdp_creation_cb on_sdp_creation,<br>      ast_sip_session_response_cb on_response,<br>      int generate_new_sdp,<br>-        enum delayed_method method)<br>+  enum delayed_method method,<br>+  struct ast_sip_session_media_state *media_state)<br> {<br>  struct ast_sip_session_delayed_request *delay = delayed_request_alloc(method,<br>-                        on_request, on_sdp_creation, on_response, generate_new_sdp);<br>+                 on_request, on_sdp_creation, on_response, generate_new_sdp, media_state);<br> <br>  if (!delay) {<br>+                ast_sip_session_media_state_free(media_state);<br>                return -1;<br>    }<br> <br>@@ -881,16 +1211,23 @@<br>          ast_sip_session_request_creation_cb on_request_creation,<br>              ast_sip_session_sdp_creation_cb on_sdp_creation,<br>              ast_sip_session_response_cb on_response,<br>-             enum ast_sip_session_refresh_method method, int generate_new_sdp)<br>+            enum ast_sip_session_refresh_method method, int generate_new_sdp,<br>+            struct ast_sip_session_media_state *media_state)<br> {<br>  pjsip_inv_session *inv_session = session->inv_session;<br>     pjmedia_sdp_session *new_sdp = NULL;<br>  pjsip_tx_data *tdata;<br> <br>+     if (media_state && (!media_state->topology || !generate_new_sdp)) {<br>+               ast_sip_session_media_state_free(media_state);<br>+               return -1;<br>+   }<br>+<br>  if (inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {<br>          /* Don't try to do anything with a hung-up call */<br>                ast_debug(3, "Not sending reinvite to %s because of disconnected state...\n",<br>                               ast_sorcery_object_get_id(session->endpoint));<br>+            ast_sip_session_media_state_free(media_state);<br>                return 0;<br>     }<br> <br>@@ -901,7 +1238,8 @@<br>            return delay_request(session, on_request_creation, on_sdp_creation, on_response,<br>                      generate_new_sdp,<br>                     method == AST_SIP_SESSION_REFRESH_METHOD_INVITE<br>-                              ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE);<br>+                            ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE,<br>+                     media_state);<br>         }<br> <br>  if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {<br>@@ -910,13 +1248,14 @@<br>                        ast_debug(3, "Delay sending reinvite to %s because of outstanding transaction...\n",<br>                                        ast_sorcery_object_get_id(session->endpoint));<br>                     return delay_request(session, on_request_creation, on_sdp_creation,<br>-                          on_response, generate_new_sdp, DELAYED_METHOD_INVITE);<br>+                               on_response, generate_new_sdp, DELAYED_METHOD_INVITE, media_state);<br>           } else if (inv_session->state != PJSIP_INV_STATE_CONFIRMED) {<br>                      /* Initial INVITE transaction failed to progress us to a confirmed state<br>                       * which means re-invites are not possible<br>                     */<br>                   ast_debug(3, "Not sending reinvite to %s because not in confirmed state...\n",<br>                                      ast_sorcery_object_get_id(session->endpoint));<br>+                    ast_sip_session_media_state_free(media_state);<br>                        return 0;<br>             }<br>     }<br>@@ -931,33 +1270,130 @@<br>                    return delay_request(session, on_request_creation, on_sdp_creation,<br>                           on_response, generate_new_sdp,<br>                                method == AST_SIP_SESSION_REFRESH_METHOD_INVITE<br>-                                      ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE);<br>+                                    ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, media_state);<br>+               }<br>+<br>+         /* If an explicitly requested media state has been provided use it instead of any pending one */<br>+             if (media_state) {<br>+                   int index;<br>+                   int type_streams[AST_MEDIA_TYPE_END] = {0};<br>+                  struct ast_stream *stream;<br>+<br>+                        /* Prune the media state so the number of streams fit within the configured limits - we do it here<br>+                    * so that the index of the resulting streams in the SDP match. If we simply left the streams out<br>+                     * of the SDP when producing it we'd be in trouble. We also enforce formats here for media types that<br>+                     * are configurable on the endpoint.<br>+                  */<br>+                  for (index = 0; index < ast_stream_topology_get_count(media_state->topology); ++index) {<br>+                               stream = ast_stream_topology_get_stream(media_state->topology, index);<br>+<br>+                         if (is_stream_limitation_reached(ast_stream_get_type(stream), session->endpoint, type_streams)) {<br>+                                 if (index < AST_VECTOR_SIZE(&media_state->sessions)) {<br>+                                             struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index);<br>+<br>+                                               ao2_cleanup(session_media);<br>+                                          AST_VECTOR_REMOVE(&media_state->sessions, index, 1);<br>+                                  }<br>+<br>+                                 ast_stream_topology_del_stream(media_state->topology, index);<br>+<br>+                                  /* A stream has potentially moved into our spot so we need to jump back so we process it */<br>+                                  index -= 1;<br>+                                  continue;<br>+                            }<br>+<br>+<br>+                              /* Enforce the configured allowed codecs on audio and video streams */<br>+                               if (ast_stream_get_type(stream) == AST_MEDIA_TYPE_AUDIO || ast_stream_get_type(stream) == AST_MEDIA_TYPE_VIDEO) {<br>+                                    struct ast_format_cap *joint_cap;<br>+<br>+                                 joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+                                       if (!joint_cap) {<br>+                                            ast_sip_session_media_state_free(media_state);<br>+                                               return 0;<br>+                                    }<br>+<br>+                                 ast_format_cap_get_compatible(ast_stream_get_formats(stream), session->endpoint->media.codecs, joint_cap);<br>+                                     if (!ast_format_cap_count(joint_cap)) {<br>+                                              ao2_ref(joint_cap, -1);<br>+                                              ast_sip_session_media_state_free(media_state);<br>+                                               return 0;<br>+                                    }<br>+<br>+                                 ast_stream_set_formats(stream, joint_cap);<br>+                           }<br>+<br>+                         ++type_streams[ast_stream_get_type(stream)];<br>+                 }<br>+<br>+                 if (session->active_media_state->topology) {<br>+                           /* SDP is a fun thing. Take for example the fact that streams are never removed. They just become<br>+                             * declined. To better handle this in the case where something requests a topology change for fewer<br>+                           * streams than are currently present we fill in the topology to match the current number of streams<br>+                          * that are active.<br>+                           */<br>+                          for (index = ast_stream_topology_get_count(media_state->topology);<br>+                                        index < ast_stream_topology_get_count(session->active_media_state->topology); ++index) {<br>+                                    struct ast_stream *cloned;<br>+<br>+                                        stream = ast_stream_topology_get_stream(session->active_media_state->topology, index);<br>+                                 ast_assert(stream != NULL);<br>+<br>+                                       cloned = ast_stream_clone(stream, NULL);<br>+                                     if (!cloned) {<br>+                                               ast_sip_session_media_state_free(media_state);<br>+                                               return -1;<br>+                                   }<br>+<br>+                                 ast_stream_set_state(cloned, AST_STREAM_STATE_REMOVED);<br>+                                      ast_stream_topology_append_stream(media_state->topology, cloned);<br>+                         }<br>+<br>+                         /* If the resulting media state matches the existing active state don't bother doing a session refresh */<br>+                                if (ast_stream_topology_equal(session->active_media_state->topology, media_state->topology)) {<br>+                                      ast_sip_session_media_state_free(media_state);<br>+                                       return 0;<br>+                            }<br>+                    }<br>+<br>+                 ast_sip_session_media_state_free(session->pending_media_state);<br>+                   session->pending_media_state = media_state;<br>                }<br> <br>          new_sdp = generate_session_refresh_sdp(session);<br>              if (!new_sdp) {<br>                       ast_log(LOG_ERROR, "Failed to generate session refresh SDP. Not sending session refresh\n");<br>+                       ast_sip_session_media_state_reset(session->pending_media_state);<br>                   return -1;<br>            }<br>             if (on_sdp_creation) {<br>                        if (on_sdp_creation(session, new_sdp)) {<br>+                             ast_sip_session_media_state_reset(session->pending_media_state);<br>                           return -1;<br>                    }<br>             }<br>     }<br> <br>-<br>       if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {<br>                if (pjsip_inv_reinvite(inv_session, NULL, new_sdp, &tdata)) {<br>                     ast_log(LOG_WARNING, "Failed to create reinvite properly.\n");<br>+                     if (generate_new_sdp) {<br>+                              ast_sip_session_media_state_reset(session->pending_media_state);<br>+                  }<br>                     return -1;<br>            }<br>     } else if (pjsip_inv_update(inv_session, NULL, new_sdp, &tdata)) {<br>                ast_log(LOG_WARNING, "Failed to create UPDATE properly.\n");<br>+               if (generate_new_sdp) {<br>+                      ast_sip_session_media_state_reset(session->pending_media_state);<br>+          }<br>             return -1;<br>    }<br>     if (on_request_creation) {<br>            if (on_request_creation(session, tdata)) {<br>+                   if (generate_new_sdp) {<br>+                              ast_sip_session_media_state_reset(session->pending_media_state);<br>+                  }<br>                     return -1;<br>            }<br>     }<br>@@ -992,22 +1428,40 @@<br> {<br>         int i;<br> <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>               struct ast_sip_session_sdp_handler *handler;<br>          RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);<br>-                RAII_VAR(struct ast_sip_session_media *, session_media, 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>-              session_media = ao2_find(session->media, media, OBJ_KEY);<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>+         /* As this is only called on an incoming SDP offer before processing it is not possible<br>+               * for streams and their media sessions to exist.<br>+             */<br>+          ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);<br>+<br>+          session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i);<br>                 if (!session_media) {<br>-                        /* if the session_media doesn't exist, there weren't<br>-                  * any handlers at the time of its creation */<br>-                       continue;<br>+                    return -1;<br>            }<br> <br>          if (session_media->handler) {<br>@@ -1269,29 +1723,6 @@<br>      return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;<br> }<br> <br>-static void session_media_dtor(void *obj)<br>-{<br>-   struct ast_sip_session_media *session_media = obj;<br>-   struct sdp_handler_list *handler_list;<br>-       /* It is possible for SDP handlers to allocate memory on a session_media but<br>-  * not end up getting set as the handler for this session_media. This traversal<br>-       * ensures that all memory allocated by SDP handlers on the session_media is<br>-  * cleared (as well as file descriptors, etc.).<br>-       */<br>-  handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);<br>-       if (handler_list) {<br>-          struct ast_sip_session_sdp_handler *handler;<br>-<br>-              AST_LIST_TRAVERSE(&handler_list->list, handler, next) {<br>-                       handler->stream_destroy(session_media);<br>-           }<br>-    }<br>-    ao2_cleanup(handler_list);<br>-   if (session_media->srtp) {<br>-                ast_sdp_srtp_destroy(session_media->srtp);<br>-        }<br>-}<br>-<br> static void session_destructor(void *obj)<br> {<br>      struct ast_sip_session *session = obj;<br>@@ -1320,17 +1751,17 @@<br> <br>    ast_taskprocessor_unreference(session->serializer);<br>        ao2_cleanup(session->datastores);<br>- ao2_cleanup(session->media);<br>+      ast_sip_session_media_state_free(session->active_media_state);<br>+    ast_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>-            ast_free(delay);<br>+             delayed_request_free(delay);<br>  }<br>     ast_party_id_free(&session->id);<br>       ao2_cleanup(session->endpoint);<br>    ao2_cleanup(session->aor);<br>         ao2_cleanup(session->contact);<br>-    ao2_cleanup(session->req_caps);<br>    ao2_cleanup(session->direct_media_cap);<br> <br>         ast_dsp_free(session->dsp);<br>@@ -1354,25 +1785,6 @@<br>                }<br>             AST_LIST_INSERT_TAIL(&session->supplements, copy, next);<br>       }<br>-    return 0;<br>-}<br>-<br>-static int add_session_media(void *obj, void *arg, int flags)<br>-{<br>- struct sdp_handler_list *handler_list = obj;<br>- struct ast_sip_session *session = arg;<br>-       RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);<br>-<br>-       session_media = ao2_alloc(sizeof(*session_media) + strlen(handler_list->stream_type), session_media_dtor);<br>-        if (!session_media) {<br>-                return CMP_STOP;<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>-     /* Safe use of strcpy */<br>-     strcpy(session_media->stream_type, handler_list->stream_type);<br>- ao2_link(session->media, session_media);<br>   return 0;<br> }<br> <br>@@ -1422,12 +1834,16 @@<br>     if (!session->direct_media_cap) {<br>          return NULL;<br>  }<br>-    session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>-    if (!session->req_caps) {<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 = ast_sip_session_media_state_alloc();<br>+        if (!session->active_media_state) {<br>+               return NULL;<br>+ }<br>+    session->pending_media_state = ast_sip_session_media_state_alloc();<br>+       if (!session->pending_media_state) {<br>               return NULL;<br>  }<br> <br>@@ -1447,13 +1863,6 @@<br>  }<br> <br>  session->endpoint = ao2_bump(endpoint);<br>-<br>-        session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp);<br>-       if (!session->media) {<br>-            return NULL;<br>- }<br>-    /* fill session->media with available types */<br>-    ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session);<br> <br>        if (rdata) {<br>          /*<br>@@ -1704,7 +2113,7 @@<br> <br> struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint,<br>     struct ast_sip_contact *contact, const char *location, const char *request_user,<br>-     struct ast_format_cap *req_caps)<br>+     struct ast_stream_topology *req_topology)<br> {<br>         const char *uri = NULL;<br>       RAII_VAR(struct ast_sip_aor *, found_aor, NULL, ao2_cleanup);<br>@@ -1768,22 +2177,68 @@<br>        session->aor = ao2_bump(found_aor);<br>        ast_party_id_copy(&session->id, &endpoint->id.self);<br> <br>-        if (ast_format_cap_count(req_caps)) {<br>-                /* get joint caps between req_caps and endpoint caps */<br>-              struct ast_format_cap *joint_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+       if (ast_stream_topology_get_count(req_topology) > 0) {<br>+            /* get joint caps between req_topology and endpoint topology */<br>+              int i;<br> <br>-            ast_format_cap_get_compatible(req_caps, endpoint->media.codecs, joint_caps);<br>+              for (i = 0; i < ast_stream_topology_get_count(req_topology); ++i) {<br>+                       struct ast_stream *req_stream;<br>+                       struct ast_format_cap *req_cap;<br>+                      struct ast_format_cap *joint_cap;<br>+                    struct ast_stream *clone_stream;<br> <br>-          /* if joint caps */<br>-          if (ast_format_cap_count(joint_caps)) {<br>-                      /* copy endpoint caps into session->req_caps */<br>-                   ast_format_cap_append_from_cap(session->req_caps,<br>-                         endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);<br>-                  /* replace instances of joint caps equivalents in session->req_caps */<br>-                    ast_format_cap_replace_from_cap(session->req_caps, joint_caps,<br>-                            AST_MEDIA_TYPE_UNKNOWN);<br>+                     req_stream = ast_stream_topology_get_stream(req_topology, i);<br>+<br>+                     if (ast_stream_get_state(req_stream) == AST_STREAM_STATE_REMOVED) {<br>+                          continue;<br>+                    }<br>+<br>+                 req_cap = ast_stream_get_formats(req_stream);<br>+<br>+                     joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+                       if (!joint_cap) {<br>+                            continue;<br>+                    }<br>+<br>+                 ast_format_cap_get_compatible(req_cap, endpoint->media.codecs, joint_cap);<br>+                        if (!ast_format_cap_count(joint_cap)) {<br>+                              ao2_ref(joint_cap, -1);<br>+                              continue;<br>+                    }<br>+<br>+                 clone_stream = ast_stream_clone(req_stream, NULL);<br>+                   if (!clone_stream) {<br>+                         ao2_ref(joint_cap, -1);<br>+                              continue;<br>+                    }<br>+<br>+                 ast_stream_set_formats(clone_stream, joint_cap);<br>+                     ao2_ref(joint_cap, -1);<br>+<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>-            ao2_cleanup(joint_caps);<br>+     }<br>+<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>  if (pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) {<br>@@ -1847,7 +2302,7 @@<br>                       /* If this is delayed the only thing that will happen is a BYE request so we don't<br>                         * actually need to store the response code for when it happens.<br>                       */<br>-                  delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE);<br>+                     delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL);<br>                        break;<br>                }<br>             /* Fall through */<br>@@ -1858,7 +2313,7 @@<br> <br>                  /* Flush any delayed requests so they cannot overlap this transaction. */<br>                     while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {<br>-                            ast_free(delay);<br>+                             delayed_request_free(delay);<br>                  }<br> <br>                  if (packet->msg->type == PJSIP_RESPONSE_MSG) {<br>@@ -2387,7 +2842,7 @@<br>   ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision.\n",<br>            ast_sorcery_object_get_id(session->endpoint),<br>              session->channel ? ast_channel_name(session->channel) : "");<br>- if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE)) {<br>+     if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, NULL)) {<br>                return;<br>       }<br>     if (pj_timer_entry_running(&session->rescheduled_reinvite)) {<br>@@ -2944,27 +3399,27 @@<br>         }<br> }<br> <br>-static int add_sdp_streams(void *obj, void *arg, void *data, int flags)<br>+static int add_sdp_streams(struct ast_sip_session_media *session_media,<br>+ struct ast_sip_session *session, pjmedia_sdp_session *answer,<br>+        const struct pjmedia_sdp_session *remote,<br>+    struct ast_stream *stream)<br> {<br>-       struct ast_sip_session_media *session_media = obj;<br>-   pjmedia_sdp_session *answer = arg;<br>-   struct ast_sip_session *session = data;<br>       struct ast_sip_session_sdp_handler *handler = session_media->handler;<br>      RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);<br>         int res;<br> <br>   if (handler) {<br>                /* if an already assigned handler reports a catastrophic error, fail */<br>-              res = handler->create_outgoing_sdp_stream(session, session_media, answer);<br>+                res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream);<br>                 if (res < 0) {<br>-                    return 0;<br>+                    return -1;<br>            }<br>-            return CMP_MATCH;<br>+            return 0;<br>     }<br> <br>- handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);<br>+       handler_list = ao2_find(sdp_handlers, ast_codec_media_type2str(session_media->type), OBJ_KEY);<br>     if (!handler_list) {<br>-         return CMP_MATCH;<br>+            return 0;<br>     }<br> <br>  /* no handler for this stream type and we have a list to search */<br>@@ -2972,29 +3427,30 @@<br>           if (handler == session_media->handler) {<br>                   continue;<br>             }<br>-            res = handler->create_outgoing_sdp_stream(session, session_media, answer);<br>+                res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream);<br>                 if (res < 0) {<br>                     /* catastrophic error */<br>-                     return 0;<br>+                    return -1;<br>            }<br>             if (res > 0) {<br>                     /* Handled by this handler. Move to the next stream */<br>                        session_media_set_handler(session_media, handler);<br>-                   return CMP_MATCH;<br>+                    return 0;<br>             }<br>     }<br> <br>  /* streams that weren't handled won't be included in generated outbound SDP */<br>-       return CMP_MATCH;<br>+    return 0;<br> }<br> <br> static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer)<br> {<br>-        RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);<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>       pjmedia_sdp_session *local;<br>+  int i;<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>@@ -3015,47 +3471,81 @@<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>-    /* Now let the handlers add streams of various types, pjmedia will automatically reorder the media streams for us */<br>- successful = ao2_callback_data(session->media, OBJ_MULTIPLE, add_sdp_streams, local, session);<br>-    if (!successful || ao2_iterator_count(successful) != ao2_container_count(session->media)) {<br>-               /* Something experienced a catastrophic failure */<br>-           return NULL;<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>+          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>- /* Use the connection details of the first media stream if possible for SDP level */<br>- if (local->media_count) {<br>-         int stream;<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>-                /* Since we are using the first media stream as the SDP level we can get rid of it<br>-            * from the stream itself<br>+            /* This code does not enforce any maximum stream count limitations as that is done on either<br>+          * the handling of an incoming SDP offer or on the handling of a session refresh.<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>+              stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);<br>+<br>+         session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_stream_get_type(stream), i);<br>+           if (!session_media) {<br>+                        return NULL;<br>+         }<br>+<br>+         if (add_sdp_streams(session_media, session, local, offer, stream)) {<br>+                 return NULL;<br>+         }<br>+<br>+         /* Ensure that we never exceed the maximum number of streams PJMEDIA will allow. */<br>+          if (local->media_count == PJMEDIA_MAX_SDP_MEDIA) {<br>+                        break;<br>+               }<br>+    }<br>+<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>+         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 6019412..a032bb1 100644<br>--- a/res/res_pjsip_t38.c<br>+++ b/res/res_pjsip_t38.c<br>@@ -43,6 +43,8 @@<br> #include "asterisk/netsock2.h"<br> #include "asterisk/channel.h"<br> #include "asterisk/acl.h"<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,11 +65,16 @@<br>         struct ast_control_t38_parameters their_parms;<br>        /*! \brief Timer entry for automatically rejecting an inbound re-invite */<br>    pj_timer_entry timer;<br>+        /*! Preserved media state for when T.38 ends */<br>+      struct ast_sip_session_media_state *media_state;<br> };<br> <br> /*! \brief Destructor for T.38 state information */<br> static void t38_state_destroy(void *obj)<br> {<br>+        struct t38_state *state = obj;<br>+<br>+    ast_sip_session_media_state_free(state->media_state);<br>      ast_free(obj);<br> }<br> <br>@@ -195,7 +202,7 @@<br> {<br>        RAII_VAR(struct ast_sip_session *, session, obj, ao2_cleanup);<br>        RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "t38"), ao2_cleanup);<br>-   RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(session->media, "image", OBJ_KEY), ao2_cleanup);<br>+       struct ast_sip_session_media *session_media;<br> <br>       if (!datastore) {<br>             return 0;<br>@@ -204,6 +211,7 @@<br>        ast_debug(2, "Automatically rejecting T.38 request on channel '%s'\n",<br>              session->channel ? ast_channel_name(session->channel) : "<gone>");<br> <br>+  session_media = session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>    t38_change_state(session, session_media, datastore->data, T38_REJECTED);<br>   ast_sip_session_resume_reinvite(session);<br> <br>@@ -259,7 +267,6 @@<br>             return -1;<br>    }<br> <br>- ast_channel_set_fd(session->channel, 5, ast_udptl_fd(session_media->udptl));<br>    ast_udptl_set_error_correction_scheme(session_media->udptl, session->endpoint->media.t38.error_correction);<br>  ast_udptl_setnat(session_media->udptl, session->endpoint->media.t38.nat);<br>    ast_udptl_set_far_max_datagram(session_media->udptl, session->endpoint->media.t38.maxdatagram);<br>@@ -271,18 +278,14 @@<br> /*! \brief Callback for when T.38 reinvite SDP is created */<br> static int t38_reinvite_sdp_cb(struct ast_sip_session *session, pjmedia_sdp_session *sdp)<br> {<br>-       int stream;<br>+  struct t38_state *state;<br> <br>-  /* Move the image media stream to the front and have it as the only stream, pjmedia will fill in<br>-      * dummy streams for the rest<br>-         */<br>-  for (stream = 0; stream < sdp->media_count; ++stream) {<br>-                if (!pj_strcmp2(&sdp->media[stream]->desc.media, "image")) {<br>-                     sdp->media[0] = sdp->media[stream];<br>-                    sdp->media_count = 1;<br>-                     break;<br>-               }<br>+    state = t38_state_get_or_alloc(session);<br>+     if (!state) {<br>+                return -1;<br>    }<br>+<br>+ state->media_state = ast_sip_session_media_state_clone(session->active_media_state);<br> <br>         return 0;<br> }<br>@@ -292,22 +295,98 @@<br> {<br>      struct pjsip_status_line status = rdata->msg_info.msg->line.status;<br>     struct t38_state *state;<br>-     RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);<br>+  struct ast_sip_session_media *session_media = NULL;<br> <br>        if (status.code == 100) {<br>             return 0;<br>     }<br> <br>- if (!(state = t38_state_get_or_alloc(session)) ||<br>-            !(session_media = ao2_find(session->media, "image", OBJ_KEY))) {<br>+        state = t38_state_get_or_alloc(session);<br>+     if (!state) {<br>                 ast_log(LOG_WARNING, "Received response to T.38 re-invite on '%s' but state unavailable\n",<br>                         ast_channel_name(session->channel));<br>               return 0;<br>     }<br> <br>- t38_change_state(session, session_media, state, (status.code == 200) ? T38_ENABLED : T38_REJECTED);<br>+  if (status.code == 200) {<br>+            int index;<br>+<br>+                session_media = session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>+            t38_change_state(session, session_media, state, T38_ENABLED);<br>+<br>+             /* Stop all the streams in the stored away active state, they'll go back to being active once<br>+             * we reinvite back.<br>+          */<br>+          for (index = 0; index < AST_VECTOR_SIZE(&state->media_state->sessions); ++index) {<br>+                      struct ast_sip_session_media *session_media = AST_VECTOR_GET(&state->media_state->sessions, index);<br>+<br>+                     if (session_media && session_media->handler && session_media->handler->stream_stop) {<br>+                               session_media->handler->stream_stop(session_media);<br>+                    }<br>+            }<br>+    } else {<br>+             session_media = session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>+           t38_change_state(session, session_media, state, T38_REJECTED);<br>+<br>+            /* Abort this attempt at switching to T.38 by resetting the pending state and freeing our stored away active state */<br>+                ast_sip_session_media_state_free(state->media_state);<br>+             state->media_state = NULL;<br>+                ast_sip_session_media_state_reset(session->pending_media_state);<br>+  }<br> <br>  return 0;<br>+}<br>+<br>+/*! \brief Helper function which creates a media state for strictly T.38 */<br>+static struct ast_sip_session_media_state *t38_create_media_state(struct ast_sip_session *session)<br>+{<br>+      struct ast_sip_session_media_state *media_state;<br>+     struct ast_stream *stream;<br>+   struct ast_format_cap *caps;<br>+ struct ast_sip_session_media *session_media;<br>+<br>+      media_state = ast_sip_session_media_state_alloc();<br>+   if (!media_state) {<br>+          return NULL;<br>+ }<br>+<br>+ media_state->topology = ast_stream_topology_alloc();<br>+      if (!media_state->topology) {<br>+             ast_sip_session_media_state_free(media_state);<br>+               return NULL;<br>+ }<br>+<br>+ stream = ast_stream_alloc("t38", AST_MEDIA_TYPE_IMAGE);<br>+    if (!stream) {<br>+               ast_sip_session_media_state_free(media_state);<br>+               return NULL;<br>+ }<br>+<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);<br>+     ast_stream_topology_set_stream(media_state->topology, 0, stream);<br>+<br>+      caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+    if (!caps) {<br>+         ast_sip_session_media_state_free(media_state);<br>+               return NULL;<br>+ }<br>+<br>+ ast_format_cap_append(caps, ast_format_t38, 0);<br>+      ast_stream_set_formats(stream, caps);<br>+        ao2_ref(caps, -1);<br>+<br>+        session_media = ast_sip_session_media_state_add(session, media_state, AST_MEDIA_TYPE_IMAGE, 0);<br>+      if (!session_media) {<br>+                ast_sip_session_media_state_free(media_state);<br>+               return NULL;<br>+ }<br>+<br>+ if (t38_initialize_session(session, session_media)) {<br>+                ast_sip_session_media_state_free(media_state);<br>+               return NULL;<br>+ }<br>+<br>+ return media_state;<br> }<br> <br> /*! \brief Task for reacting to T.38 control frame */<br>@@ -316,10 +395,9 @@<br>      RAII_VAR(struct t38_parameters_task_data *, data, obj, ao2_cleanup);<br>  const struct ast_control_t38_parameters *parameters = data->frame->data.ptr;<br>    struct t38_state *state = t38_state_get_or_alloc(data->session);<br>-  RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(data->session->media, "image", OBJ_KEY), ao2_cleanup);<br>+      struct ast_sip_session_media *session_media = NULL;<br> <br>-       /* Without session media or state we can't interpret parameters */<br>-       if (!session_media || !state) {<br>+      if (!state) {<br>                 return 0;<br>     }<br> <br>@@ -329,12 +407,15 @@<br>           /* Negotiation can not take place without a valid max_ifp value. */<br>           if (!parameters->max_ifp) {<br>                        if (data->session->t38state == T38_PEER_REINVITE) {<br>+                            session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>                           t38_change_state(data->session, session_media, state, T38_REJECTED);<br>                               ast_sip_session_resume_reinvite(data->session);<br>                    } else if (data->session->t38state == T38_ENABLED) {<br>+                           session_media = data->session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>                            t38_change_state(data->session, session_media, state, T38_DISABLED);<br>                               ast_sip_session_refresh(data->session, NULL, NULL, NULL,<br>-                                  AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);<br>+                                   AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, state->media_state);<br>+                            state->media_state = NULL;<br>                         }<br>                     break;<br>                } else if (data->session->t38state == T38_PEER_REINVITE) {<br>@@ -353,37 +434,46 @@<br>                       }<br>                     state->our_parms.version = MIN(state->our_parms.version, state->their_parms.version);<br>                        state->our_parms.rate_management = state->their_parms.rate_management;<br>+                 session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>                   ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp);<br>                    t38_change_state(data->session, session_media, state, T38_ENABLED);<br>                        ast_sip_session_resume_reinvite(data->session);<br>            } else if ((data->session->t38state != T38_ENABLED) ||<br>                          ((data->session->t38state == T38_ENABLED) &&<br>                                 (parameters->request_response == AST_T38_REQUEST_NEGOTIATE))) {<br>-                      if (t38_initialize_session(data->session, session_media)) {<br>+                       struct ast_sip_session_media_state *media_state;<br>+<br>+                  media_state = t38_create_media_state(data->session);<br>+                      if (!media_state) {<br>                           break;<br>                        }<br>                     state->our_parms = *parameters;<br>+                   session_media = media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>                        ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp);<br>                    t38_change_state(data->session, session_media, state, T38_LOCAL_REINVITE);<br>                         ast_sip_session_refresh(data->session, NULL, t38_reinvite_sdp_cb, t38_reinvite_response_cb,<br>-                               AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);<br>+                           AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, media_state);<br>               }<br>             break;<br>        case AST_T38_TERMINATED:<br>      case AST_T38_REFUSED:<br>         case AST_T38_REQUEST_TERMINATE:         /* Shutdown T38 */<br>            if (data->session->t38state == T38_PEER_REINVITE) {<br>+                    session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>                   t38_change_state(data->session, session_media, state, T38_REJECTED);<br>                       ast_sip_session_resume_reinvite(data->session);<br>            } else if (data->session->t38state == T38_ENABLED) {<br>+                   session_media = data->session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>                    t38_change_state(data->session, session_media, state, T38_DISABLED);<br>-                      ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);<br>+                       ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, state->media_state);<br>+                        state->media_state = NULL;<br>                 }<br>             break;<br>        case AST_T38_REQUEST_PARMS: {           /* Application wants remote's parameters re-sent */<br>               struct ast_control_t38_parameters parameters = state->their_parms;<br> <br>              if (data->session->t38state == T38_PEER_REINVITE) {<br>+                    session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];<br>                   parameters.max_ifp = ast_udptl_get_far_max_ifp(session_media->udptl);<br>                      parameters.request_response = AST_T38_REQUEST_NEGOTIATE;<br>                      ast_queue_control_data(data->session->channel, AST_CONTROL_T38_PARAMETERS, &parameters, sizeof(parameters));<br>@@ -397,67 +487,27 @@<br>         return 0;<br> }<br> <br>-/*! \brief Frame hook callback for writing */<br>-static struct ast_frame *t38_framehook_write(struct ast_channel *chan,<br>-    struct ast_sip_session *session, struct ast_frame *f)<br>-{<br>-    if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS &&<br>-          session->endpoint->media.t38.enabled) {<br>-                struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(session, f);<br>-<br>-               if (!data) {<br>-                 return f;<br>-            }<br>-<br>-         if (ast_sip_push_task(session->serializer, t38_interpret_parameters, data)) {<br>-                     ao2_ref(data, -1);<br>-           }<br>-    } else if (f->frametype == AST_FRAME_MODEM) {<br>-             struct ast_sip_session_media *session_media;<br>-<br>-              /* Avoid deadlock between chan and the session->media container lock */<br>-           ast_channel_unlock(chan);<br>-            session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY);<br>-              ast_channel_lock(chan);<br>-              if (session_media && session_media->udptl) {<br>-                      ast_udptl_write(session_media->udptl, f);<br>-         }<br>-            ao2_cleanup(session_media);<br>-  }<br>-<br>- return f;<br>-}<br>-<br>-/*! \brief Frame hook callback for reading */<br>-static struct ast_frame *t38_framehook_read(struct ast_channel *chan,<br>-     struct ast_sip_session *session, struct ast_frame *f)<br>-{<br>-    if (ast_channel_fdno(session->channel) == 5) {<br>-            struct ast_sip_session_media *session_media;<br>-<br>-              /* Avoid deadlock between chan and the session->media container lock */<br>-           ast_channel_unlock(chan);<br>-            session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY);<br>-              ast_channel_lock(chan);<br>-              if (session_media && session_media->udptl) {<br>-                      f = ast_udptl_read(session_media->udptl);<br>-         }<br>-            ao2_cleanup(session_media);<br>-  }<br>-<br>- return f;<br>-}<br>-<br> /*! \brief Frame hook callback for T.38 related stuff */<br> static struct ast_frame *t38_framehook(struct ast_channel *chan, struct ast_frame *f,<br>   enum ast_framehook_event event, void *data)<br> {<br>       struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);<br> <br>- if (event == AST_FRAMEHOOK_EVENT_READ) {<br>-             f = t38_framehook_read(chan, channel->session, f);<br>-        } else if (event == AST_FRAMEHOOK_EVENT_WRITE) {<br>-             f = t38_framehook_write(chan, channel->session, f);<br>+       if (event != AST_FRAMEHOOK_EVENT_WRITE) {<br>+            return f;<br>+    }<br>+<br>+ if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS &&<br>+          channel->session->endpoint->media.t38.enabled) {<br>+            struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(channel->session, f);<br>+<br>+           if (!data) {<br>+                 return f;<br>+            }<br>+<br>+         if (ast_sip_push_task(channel->session->serializer, t38_interpret_parameters, data)) {<br>+                 ao2_ref(data, -1);<br>+           }<br>     }<br> <br>  return f;<br>@@ -476,7 +526,7 @@<br> <br> static int t38_consume(void *data, enum ast_frame_type type)<br> {<br>- return 0;<br>+    return (type == AST_FRAME_CONTROL) ? 1 : 0;<br> }<br> <br> static const struct ast_datastore_info t38_framehook_datastore = {<br>@@ -676,11 +726,13 @@<br> }<br> <br> /*! \brief Function which negotiates an incoming media stream */<br>-static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>-                                     const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)<br>+static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,<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>+       pjmedia_sdp_media *stream = sdp->media[index];<br>     RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);<br> <br>    if (!session->endpoint->media.t38.enabled) {<br>@@ -720,7 +772,7 @@<br> <br> /*! \brief Function which creates an outgoing stream */<br> static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,<br>-                                 struct pjmedia_sdp_session *sdp)<br>+                                     struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, struct ast_stream *stream)<br> {<br>       pj_pool_t *pool = session->inv_session->pool_prov;<br>      static const pj_str_t STR_IN = { "IN", 2 };<br>@@ -758,7 +810,7 @@<br>            return -1;<br>    }<br> <br>- media->desc.media = pj_str(session_media->stream_type);<br>+        pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));<br>        media->desc.transport = STR_UDPTL;<br> <br>      if (ast_strlen_zero(session->endpoint->media.address)) {<br>@@ -826,12 +878,31 @@<br>         return 1;<br> }<br> <br>+static struct ast_frame *media_session_udptl_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)<br>+{<br>+      if (!session_media->udptl) {<br>+              return &ast_null_frame;<br>+  }<br>+<br>+ return ast_udptl_read(session_media->udptl);<br>+}<br>+<br>+static int media_session_udptl_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame)<br>+{<br>+    if (!session_media->udptl) {<br>+              return 0;<br>+    }<br>+<br>+ return ast_udptl_write(session_media->udptl, frame);<br>+}<br>+<br> /*! \brief Function which applies a negotiated stream */<br>-static 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_media *local_stream,<br>-                                       const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)<br>+static int apply_negotiated_sdp_stream(struct ast_sip_session *session,<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>+      pjmedia_sdp_media *remote_stream = remote->media[index];<br>   char host[NI_MAXHOST];<br>        struct t38_state *state;<br> <br>@@ -858,6 +929,10 @@<br> <br>  t38_interpret_sdp(state, session, session_media, remote_stream);<br> <br>+  ast_sip_session_media_set_write_callback(session, session_media, media_session_udptl_write_callback);<br>+        ast_sip_session_media_add_read_callback(session, session_media, ast_udptl_fd(session_media->udptl),<br>+               media_session_udptl_read_callback);<br>+<br>        return 0;<br> }<br> <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: merged </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: 19 </div>
<div style="display:none"> Gerrit-Owner: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Mark Michelson <mmichelson@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Matthew Fredrickson <creslin@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Richard Mudgett <rmudgett@digium.com> </div>