<p>Mark Michelson has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/5824">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">chan_pjsip: Multistream: Modify use of ast_sip_session_media<br><br>This commit reworks the use of ast_sip_session media structures in order<br>to facilitate multistream behavior in chan_pjsip.<br><br>Prior to this commit the ast_sip_session structure contained an<br>ao2_container of ast_sip_session_media structures. This container was a<br>hash table keyed on the media type. At session creation, a session media<br>structure was pre-allocated for each supported media type and added to<br>the container. This effectively limited the allocation of session media<br>to being one per media type.<br><br>With this commit, this limitation is lifted. The following has been<br>changed:<br><br>* Rather than using an ao2_container, session medias are stored in a<br> vector on the session. This allows for the index of the session media<br> to be important, rather than the media type. The index of the session<br> media vector corresponds to the index of our local SDP as well as the<br> index of the stream topology on which the local SDP is based.<br><br>* Session media structures are not pre-allocated at session creation.<br> Rather, they are created as needed due to the circumstances<br> surrounding the session. Typically, session media structures are<br> allocated based on a stream topology that will be represented by our<br> local SDP. So typically the session media is allocated at the time of<br> SDP creation. The big exception to this is during outbound session<br> creation. We create the session media at that time due to assumptions<br> made in chan_pjsip about session media already being allocated.<br><br>In addition to the primary changes, there are some changes that rippled<br>out of this change<br><br>* Because the index is important to have in the SDP handlers, some SDP<br> handler functions have had their parameters changed. Rather than<br> explicitly passing in SDP streams and a session media, we pass in a<br> session, an SDP (or two), and an index. The index can be used to find<br> the appropriate SDP stream, session media, and Asterisk stream this<br> way, since the all correspond to the same index.<br><br>* Because a session media corresponds to an index in the SDP, this can<br> result in a session media changing types. The most common cause of<br> this would be for a session media changing from audio to T.38. In such<br> a case, we need to be able to change the session media's type as<br> necessary. Because of the type being variable, it means that we can no<br> longer store the RTP instance and UDPTL as a union. They need to be<br> distinct structures. This way, a session media could switch back and<br> forth between types without causing memory problems.<br><br>NOTE: This change breaks some tests in the testsuite. Specifically, it<br>breaks the tests that make sure that Asterisk only accepts one stream of<br>each media type. The next change I will be working on will add<br>configuration values that govern the number of streams allowed on a<br>session. With that change in place, the default values will result in<br>those tests passing again. Therefore, it's not worth it for me to go to<br>special effort with this change to ensure those tests are passing.<br><br>Change-Id: I144b04f43633387b8e42a43ef3b25d7c5682b451<br>---<br>M channels/chan_pjsip.c<br>M include/asterisk/res_pjsip_session.h<br>M res/res_pjsip_sdp_rtp.c<br>M res/res_pjsip_session.c<br>M res/res_pjsip_t38.c<br>5 files changed, 377 insertions(+), 289 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/24/5824/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c<br>index f092383..bd76110 100644<br>--- a/channels/chan_pjsip.c<br>+++ b/channels/chan_pjsip.c<br>@@ -198,7 +198,7 @@<br> struct chan_pjsip_pvt *pvt = channel->pvt;<br> struct ast_sip_endpoint *endpoint;<br> <br>- if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO]->rtp) {<br>+ if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO] || !pvt->media[SIP_MEDIA_VIDEO]->rtp) {<br> return AST_RTP_GLUE_RESULT_FORBID;<br> }<br> <br>@@ -461,6 +461,35 @@<br> return ast_format_cap_iscompatible(cap1, cap2);<br> }<br> <br>+/*!<br>+ * \brief Get the first ast_sip_session_media of a certain media type.<br>+ *<br>+ * XXX For multistream, we'll likely want to change this to be a function<br>+ * to retrieve the nth session media instead of the first.<br>+ *<br>+ * \param session The session on which the session media lives<br>+ * \param type The type of the session media<br>+ */<br>+static struct ast_sip_session_media *get_first_session_media_by_type(<br>+ const struct ast_sip_session *session, enum ast_media_type type)<br>+{<br>+ int i;<br>+<br>+ for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {<br>+ struct ast_sip_session_media *session_media;<br>+<br>+ session_media = AST_VECTOR_GET(&session->media, i);<br>+<br>+ ast_assert(session_media != NULL);<br>+<br>+ if (session_media->type == type) {<br>+ return ao2_bump(session_media);<br>+ }<br>+ }<br>+<br>+ return NULL;<br>+}<br>+<br> /*! \brief Function called to create a new PJSIP Asterisk channel */<br> static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int state, const char *exten, const char *title, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *cid_name)<br> {<br>@@ -569,11 +598,12 @@<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>+ /* XXX For multistream, the chan_pjsip_pvt structure will be completely<br>+ * redone so it's not a fixed size array. It may have multiple audio / video<br>+ * streams<br>+ */<br>+ pvt->media[SIP_MEDIA_AUDIO] = get_first_session_media_by_type(session, AST_MEDIA_TYPE_AUDIO);<br>+ pvt->media[SIP_MEDIA_VIDEO] = get_first_session_media_by_type(session, AST_MEDIA_TYPE_VIDEO);<br> set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(chan));<br> <br> return chan;<br>@@ -1340,20 +1370,15 @@<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>+ session_media->locally_held = held;<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_VECTOR_CALLBACK_VOID(&session->media, local_hold_set_state, held);<br> ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);<br> ao2_ref(session, -1);<br> <br>diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h<br>index 9bffa16..8829828 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>@@ -61,12 +63,10 @@<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 +87,8 @@<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> };<br> <br> /*!<br>@@ -124,7 +124,7 @@<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>+ AST_VECTOR(, struct ast_sip_session_media *) media;<br> /*! Serializer for tasks relating to this SIP session */<br> struct ast_taskprocessor *serializer;<br> /*! Non-null if the session serializer is suspended or being suspended. */<br>@@ -311,14 +311,13 @@<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 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> * \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>+ int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, const struct pjmedia_sdp_session *sdp, int index);<br> /*!<br> * \brief Create an SDP media stream and add it to the outgoing SDP offer or answer<br> * \param session The session for which media is being added<br>@@ -334,11 +333,12 @@<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 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, struct ast_stream *stream);<br> /*!<br> * \brief Update media stream with external address if applicable<br> * \param tdata The outgoing message itself<br>@@ -349,17 +349,15 @@<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 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> * \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, const struct pjmedia_sdp_session *local,<br>+ const struct pjmedia_sdp_session *remote, int index);<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>diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c<br>index 7b5f297..52245c8 100644<br>--- a/res/res_pjsip_sdp_rtp.c<br>+++ b/res/res_pjsip_sdp_rtp.c<br>@@ -68,18 +68,6 @@<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>@@ -242,11 +230,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>@@ -336,34 +324,33 @@<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, int index)<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> ast_format_cap_count(session->direct_media_cap);<br> int dsp_features = 0;<br>- struct ast_stream *req_stream;<br>+ struct ast_stream *req_stream = NULL;<br> struct ast_format_cap *req_caps;<br>- struct ast_stream *endpoint_stream;<br>- struct ast_format_cap *endpoint_caps;<br> <br> if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||<br> !(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||<br> !(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>- endpoint_stream = ast_stream_topology_get_first_stream_by_type(session->endpoint->media.codecs, media_type);<br>- if (!endpoint_stream) {<br>+ endpoint_caps = ast_format_cap_from_stream_topology(session->endpoint->media.codecs);<br>+ if (!endpoint_caps) {<br> return -1;<br> }<br>- endpoint_caps = ast_stream_get_formats(endpoint_stream);<br> <br> /* get the endpoint capabilities */<br> if (direct_media_enabled) {<br>@@ -384,7 +371,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>@@ -400,14 +387,16 @@<br> ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),<br> session_media->rtp);<br> <br>- req_stream = ast_stream_topology_get_first_stream_by_type(session->req_topology, media_type);<br>+ if (index < ast_stream_topology_get_count(session->req_topology)) {<br>+ req_stream = ast_stream_topology_get_stream(session->req_topology, index);<br>+ }<br> if (!req_stream) {<br> req_stream = ast_stream_alloc(ast_codec_media_type2str(media_type), media_type);<br> if (!req_stream) {<br> return -1;<br> }<br> ast_stream_set_formats(req_stream, joint);<br>- ast_stream_topology_append_stream(session->req_topology, req_stream);<br>+ ast_stream_topology_set_stream(session->req_topology, index, req_stream);<br> } else {<br> req_caps = ast_stream_get_formats(req_stream);<br> ast_format_cap_replace_from_cap(req_caps, joint, AST_MEDIA_TYPE_UNKNOWN);<br>@@ -977,24 +966,28 @@<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>+ const pjmedia_sdp_session *sdp, int index)<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>+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);<br>+ pjmedia_sdp_media *stream = sdp->media[index];<br>+ enum ast_media_type media_type = session_media->type;<br> enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE;<br> 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>+ ast_debug(3, "Media stream '%s' is already declined\n",<br>+ ast_codec_media_type2str(session_media->type));<br> return 0;<br> }<br> <br> /* If no type formats have been configured reject this stream */<br> if (!ast_stream_topology_get_first_stream_by_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>@@ -1049,7 +1042,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, index)) {<br> return 0;<br> }<br> return 1;<br>@@ -1170,7 +1163,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, 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>@@ -1189,27 +1182,14 @@<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_stream_topology_get_count(session->req_topology);<br>- /* XXX Getting first stream by media type is not going to cut it when we're creating multiple streams of the same type */<br>- struct ast_stream *req_stream = ast_stream_topology_get_first_stream_by_type(session->req_topology, media_type);<br>- struct ast_stream *endpoint_config_stream = ast_stream_topology_get_first_stream_by_type(session->endpoint->media.codecs, media_type);<br>- struct ast_format_cap *req_caps;<br>- struct ast_format_cap *endpoint_config_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 && !req_stream) ||<br>- (!use_override_prefs && !endpoint_config_stream)) {<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>+ if (!session_media->rtp && create_rtp(session, session_media)) {<br> return -1;<br> }<br>-<br>- req_caps = req_stream ? ast_stream_get_formats(req_stream) : NULL;<br>- endpoint_config_caps = ast_stream_get_formats(endpoint_config_stream);<br> <br> set_ice_components(session, session_media);<br> enable_rtcp(session, session_media, NULL);<br>@@ -1223,7 +1203,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> if (pj_strlen(&session_media->transport)) {<br> /* If a transport has already been specified use it */<br> media->desc.transport = session_media->transport;<br>@@ -1246,7 +1226,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>@@ -1272,17 +1253,22 @@<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>+ struct ast_format_cap *endpoint_config_caps;<br>+<br>+ endpoint_config_caps = ast_format_cap_from_stream_topology(session->endpoint->media.codecs);<br>+ if (!endpoint_config_caps) {<br>+ return -1;<br>+ }<br> ast_format_cap_get_compatible(endpoint_config_caps, session->direct_media_cap, caps);<br>- } else if (!req_caps || !ast_format_cap_count(req_caps) ||<br>- !ast_format_cap_iscompatible(req_caps, endpoint_config_caps)) {<br>- ast_format_cap_append_from_cap(caps, endpoint_config_caps, media_type);<br>+ ao2_ref(endpoint_config_caps, -1);<br> } else {<br>- ast_format_cap_append_from_cap(caps, 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>@@ -1385,12 +1371,15 @@<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 int apply_negotiated_sdp_stream(struct ast_sip_session *session,<br>+ const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote,<br>+ int index)<br> {<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>+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);<br>+ struct pjmedia_sdp_media *local_stream = local->media[index];<br>+ struct pjmedia_sdp_media *remote_stream = remote->media[index];<br>+ enum ast_media_type media_type = session_media->type;<br> char host[NI_MAXHOST];<br> int fdno, res;<br> <br>@@ -1441,7 +1430,7 @@<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, index)) {<br> return 1;<br> }<br> <br>@@ -1493,7 +1482,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 41ef0ce..56acbd3 100644<br>--- a/res/res_pjsip_session.c<br>+++ b/res/res_pjsip_session.c<br>@@ -54,6 +54,9 @@<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> static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata,<br>@@ -102,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>@@ -208,6 +194,71 @@<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>+/*!<br>+ * \brief Allocate an ast_session_media and add it to the session's vector.<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>+ * If the position is less than 0, then the session media is appended to the end<br>+ * of the vector. Otherwise, it is inserted at the given index.<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 add the session media<br>+ * \param type The type of the session media<br>+ * \param position Position at which to insert the new session media.<br>+ */<br>+static struct ast_sip_session_media *add_session_media(struct ast_sip_session *session,<br>+ enum ast_media_type type, int position)<br>+{<br>+ struct ast_sip_session_media *session_media;<br>+<br>+ session_media = ao2_alloc(sizeof(*session_media), session_media_dtor);<br>+ if (!session_media) {<br>+ return NULL;<br>+ }<br>+ session_media->encryption = session->endpoint->media.rtp.encryption;<br>+ session_media->keepalive_sched_id = -1;<br>+ session_media->timeout_sched_id = -1;<br>+ session_media->type = type;<br>+ if (position >= 0) {<br>+ AST_VECTOR_REPLACE(&session->media, position, session_media);<br>+ } else {<br>+ AST_VECTOR_APPEND(&session->media, session_media);<br>+ }<br>+ return session_media;<br>+}<br>+<br> static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp)<br> {<br> int i;<br>@@ -223,32 +274,43 @@<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> <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>+ if (i < AST_VECTOR_SIZE(&session->media)) {<br>+ session_media = AST_VECTOR_GET(&session->media, i);<br>+ if (ast_media_type_from_str(media) != session_media->type) {<br>+ /* The stream may have changed types (e.g. from audio to T.38). In this<br>+ * case, we can just correct the type and clear the cached SDP handler<br>+ * on this session media. The session media destructor will ensure that<br>+ * all SDP handlers get a chance to free the memory they allocated<br>+ */<br>+ session_media->type = ast_media_type_from_str(media);<br>+ session_media->handler = NULL;<br>+ }<br>+ }<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>+ session_media = add_session_media(session, ast_media_type_from_str(media), i);<br>+ if (!session_media) {<br>+ return -1;<br>+ }<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, sdp, i);<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>@@ -266,17 +328,16 @@<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, sdp, i);<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>@@ -291,110 +352,93 @@<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)<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>+ 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>+ /* 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>- if (!remote->media[i]) {<br>- continue;<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>-<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, local, remote, index);<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, local, remote, index);<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> <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>+ 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 that correspond to the local SDP, and there should<br>+ * be the same number of session medias as there are local streams<br>+ */<br>+ ast_assert(i < AST_VECTOR_SIZE(&session->media));<br>+<br>+ session_media = AST_VECTOR_GET(&session->media, i);<br>+ if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i)) {<br>+ return -1;<br>+ }<br> }<br>- return -1;<br>+<br>+ return 0;<br> }<br> <br> AST_RWLIST_HEAD_STATIC(session_supplements, ast_sip_session_supplement);<br>@@ -998,17 +1042,29 @@<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> 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>+ if (i < AST_VECTOR_SIZE(&session->media)) {<br>+ session_media = AST_VECTOR_GET(&session->media, i);<br>+ if (ast_media_type_from_str(media) != session_media->type) {<br>+ /* The stream may have changed types (e.g. from audio to T.38). In this<br>+ * case, we can just correct the type and clear the cached SDP handler<br>+ * on this session media. The session media destructor will ensure that<br>+ * all SDP handlers get a chance to free the memory they allocated<br>+ */<br>+ session_media->type = ast_media_type_from_str(media);<br>+ session_media->handler = NULL;<br>+ }<br>+ }<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>+ session_media = add_session_media(session, ast_media_type_from_str(media), i);<br>+ if (!session_media) {<br>+ return -1;<br>+ }<br> }<br> <br> if (session_media->handler) {<br>@@ -1270,29 +1326,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>@@ -1300,6 +1333,7 @@<br> struct ast_sip_session_delayed_request *delay;<br> const char *endpoint_name = session->endpoint ?<br> ast_sorcery_object_get_id(session->endpoint) : "<none>";<br>+ int i;<br> <br> ast_debug(3, "Destroying SIP session with endpoint %s\n", endpoint_name);<br> <br>@@ -1321,7 +1355,10 @@<br> <br> ast_taskprocessor_unreference(session->serializer);<br> ao2_cleanup(session->datastores);<br>- ao2_cleanup(session->media);<br>+ for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {<br>+ ao2_cleanup(AST_VECTOR_GET(&session->media, i));<br>+ }<br>+ AST_VECTOR_FREE(&session->media);<br> <br> AST_LIST_HEAD_DESTROY(&session->supplements);<br> while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {<br>@@ -1355,25 +1392,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>@@ -1449,12 +1467,9 @@<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>+ if (AST_VECTOR_INIT(&session->media, DEFAULT_NUM_SESSION_MEDIA) < 0) {<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>@@ -1811,6 +1826,9 @@<br> /* replace instances of joint caps equivalents in req_caps */<br> ast_format_cap_replace_from_cap(req_cap, joint_cap,<br> AST_MEDIA_TYPE_UNKNOWN);<br>+ if (add_session_media(session, type, -1) == NULL) {<br>+ continue;<br>+ }<br> clone_stream = ast_stream_clone(req_stream, NULL);<br> if (!clone_stream) {<br> continue;<br>@@ -1819,6 +1837,21 @@<br> continue;<br> }<br> }<br>+ }<br>+ }<br>+<br>+ if (ast_stream_topology_get_count(session->req_topology) == 0) {<br>+ /* We haven't added anything yet to the session->req_topology, either because<br>+ * 1) The passed in req_topology had no streams.<br>+ * 2) The passed in req_topology had no overlapping capabilities with the endpoint.<br>+ */<br>+ int i;<br>+<br>+ for (i = 0; i < ast_stream_topology_get_count(endpoint->media.codecs); ++i) {<br>+ struct ast_stream *stream;<br>+<br>+ stream = ast_stream_topology_get_stream(endpoint->media.codecs, i);<br>+ add_session_media(session, ast_stream_get_type(stream), -1);<br> }<br> }<br> <br>@@ -2954,27 +2987,26 @@<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>+ 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, 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>@@ -2982,29 +3014,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, 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>+ struct ast_stream_topology *topology;<br>+ int i;<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>@@ -3025,11 +3058,35 @@<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->req_topology && ast_stream_topology_get_count(session->req_topology)) {<br>+ /* The most common case. An incoming call, an incoming SDP offer, or an application<br>+ * has dictated what streams are requested on this call.<br>+ */<br>+ topology = session->req_topology;<br>+ } else {<br>+ /* Weird case. We're originating a call where the codecs were not specified, or we're<br>+ * answering an incoming call that had no SDP offer<br>+ */<br>+ topology = session->endpoint->media.codecs;<br>+ }<br>+<br>+ for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {<br>+ struct ast_sip_session_media *session_media;<br>+ struct ast_stream *stream;<br>+<br>+ stream = ast_stream_topology_get_stream(topology, i);<br>+ if (i < AST_VECTOR_SIZE(&session->media)) {<br>+ session_media = AST_VECTOR_GET(&session->media, i);<br>+ } else {<br>+ session_media = add_session_media(session, ast_stream_get_type(stream), i);<br>+ }<br>+<br>+ if (!session_media) {<br>+ return NULL;<br>+ }<br>+ if (add_sdp_streams(session_media, session, local, stream)) {<br>+ return NULL;<br>+ }<br> }<br> <br> /* Use the connection details of the first media stream if possible for SDP level */<br>diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c<br>index bb1641a..96043d8 100644<br>--- a/res/res_pjsip_t38.c<br>+++ b/res/res_pjsip_t38.c<br>@@ -190,12 +190,29 @@<br> }<br> }<br> <br>+/*! \brief Find the first instance of a session media of type "image" */<br>+static struct ast_sip_session_media *find_image_session_media(struct ast_sip_session *session)<br>+{<br>+ int i;<br>+<br>+ for (i = 0; i < AST_VECTOR_SIZE(&session->media); ++i) {<br>+ struct ast_sip_session_media *session_media;<br>+<br>+ session_media = AST_VECTOR_GET(&session->media, i);<br>+ if (session_media->type == AST_MEDIA_TYPE_IMAGE) {<br>+ return session_media;<br>+ }<br>+ }<br>+<br>+ return NULL;<br>+}<br>+<br> /*! \brief Task function which rejects a T.38 re-invite and resumes handling it */<br> static int t38_automatic_reject(void *obj)<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 = find_image_session_media(session);<br> <br> if (!datastore) {<br> return 0;<br>@@ -292,14 +309,14 @@<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>+ !(session_media = find_image_session_media(session))) {<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>@@ -316,7 +333,7 @@<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 = find_image_session_media(data->session);<br> <br> /* Without session media or state we can't interpret parameters */<br> if (!session_media || !state) {<br>@@ -417,12 +434,11 @@<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>+ session_media = find_image_session_media(session);<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>@@ -437,12 +453,11 @@<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>+ session_media = find_image_session_media(session);<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>@@ -676,11 +691,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>+ const struct pjmedia_sdp_session *sdp, int index)<br> {<br> struct t38_state *state;<br> char host[NI_MAXHOST];<br>+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);<br>+ pjmedia_sdp_media *stream = sdp->media[index];<br> RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);<br> <br> if (!session->endpoint->media.t38.enabled) {<br>@@ -720,7 +737,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, 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 +775,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>@@ -827,11 +844,13 @@<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>+ const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote,<br>+ int index)<br> {<br> RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);<br>+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->media, index);<br>+ pjmedia_sdp_media *remote_stream = remote->media[index];<br> char host[NI_MAXHOST];<br> struct t38_state *state;<br> <br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/5824">change 5824</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/5824"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I144b04f43633387b8e42a43ef3b25d7c5682b451 </div>
<div style="display:none"> Gerrit-Change-Number: 5824 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Mark Michelson <mmichelson@digium.com> </div>