<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, ¶meters, 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>