diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index 83dc77f3825be3d1b6b182c149fceaccb3c4bb6d..7cab428731ebaa905bf84cd9f86c7194ae51693e 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -64,6 +64,7 @@ #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_session.h" +#include "asterisk/stream.h" #include "pjsip/include/chan_pjsip.h" #include "pjsip/include/dialplan_functions.h" @@ -78,25 +79,22 @@ static unsigned int chan_idx; static void chan_pjsip_pvt_dtor(void *obj) { - struct chan_pjsip_pvt *pvt = obj; - int i; - - for (i = 0; i < SIP_MEDIA_SIZE; ++i) { - ao2_cleanup(pvt->media[i]); - pvt->media[i] = NULL; - } } /* \brief Asterisk core interaction functions */ 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); +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); static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text); static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit); static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration); static int chan_pjsip_call(struct ast_channel *ast, const char *dest, int timeout); static int chan_pjsip_hangup(struct ast_channel *ast); static int chan_pjsip_answer(struct ast_channel *ast); -static struct ast_frame *chan_pjsip_read(struct ast_channel *ast); +static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast); static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f); +static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *f); static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen); static int chan_pjsip_transfer(struct ast_channel *ast, const char *target); static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); @@ -109,16 +107,17 @@ struct ast_channel_tech chan_pjsip_tech = { .type = channel_type, .description = "PJSIP Channel Driver", .requester = chan_pjsip_request, + .requester_with_stream_topology = chan_pjsip_request_with_stream_topology, .send_text = chan_pjsip_sendtext, .send_digit_begin = chan_pjsip_digit_begin, .send_digit_end = chan_pjsip_digit_end, .call = chan_pjsip_call, .hangup = chan_pjsip_hangup, .answer = chan_pjsip_answer, - .read = chan_pjsip_read, + .read_stream = chan_pjsip_read_stream, .write = chan_pjsip_write, - .write_video = chan_pjsip_write, - .exception = chan_pjsip_read, + .write_stream = chan_pjsip_write_stream, + .exception = chan_pjsip_read_stream, .indicate = chan_pjsip_indicate, .transfer = chan_pjsip_transfer, .fixup = chan_pjsip_fixup, @@ -159,11 +158,20 @@ static struct ast_sip_session_supplement chan_pjsip_ack_supplement = { static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); - struct chan_pjsip_pvt *pvt; struct ast_sip_endpoint *endpoint; struct ast_datastore *datastore; + struct ast_sip_session_media *media; - if (!channel || !channel->session || !(pvt = channel->pvt) || !pvt->media[SIP_MEDIA_AUDIO]->rtp) { + if (!channel || !channel->session) { + return AST_RTP_GLUE_RESULT_FORBID; + } + + /* XXX Getting the first RTP instance for direct media related stuff seems just + * absolutely wrong. But the native RTP bridge knows no other method than single-stream + * for direct media. So this is the best we can do. + */ + media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]; + if (!media || !media->rtp) { return AST_RTP_GLUE_RESULT_FORBID; } @@ -175,7 +183,7 @@ static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan endpoint = channel->session->endpoint; - *instance = pvt->media[SIP_MEDIA_AUDIO]->rtp; + *instance = media->rtp; ao2_ref(*instance, +1); ast_assert(endpoint != NULL); @@ -194,16 +202,21 @@ static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan static enum ast_rtp_glue_result chan_pjsip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); - struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_endpoint *endpoint; + struct ast_sip_session_media *media; - if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO]->rtp) { + if (!channel || !channel->session) { + return AST_RTP_GLUE_RESULT_FORBID; + } + + media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]; + if (!media || !media->rtp) { return AST_RTP_GLUE_RESULT_FORBID; } endpoint = channel->session->endpoint; - *instance = pvt->media[SIP_MEDIA_VIDEO]->rtp; + *instance = media->rtp; ao2_ref(*instance, +1); ast_assert(endpoint != NULL); @@ -265,18 +278,43 @@ static int direct_media_mitigate_glare(struct ast_sip_session *session) return 0; } +/*! \brief Helper function to find the position for RTCP */ +static int rtp_find_rtcp_fd_position(struct ast_sip_session *session, struct ast_rtp_instance *rtp) +{ + int index; + + for (index = 0; index < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++index) { + struct ast_sip_session_media_read_callback_state *callback_state = + AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, index); + + if (callback_state->fd != ast_rtp_instance_fd(rtp, 1)) { + continue; + } + + return index; + } + + return -1; +} + /*! * \pre chan is locked */ static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instance *rtp, - struct ast_sip_session_media *media, int rtcp_fd) + struct ast_sip_session_media *media, struct ast_sip_session *session) { - int changed = 0; + int changed = 0, position = -1; + + if (media->rtp) { + position = rtp_find_rtcp_fd_position(session, media->rtp); + } if (rtp) { changed = ast_rtp_instance_get_and_cmp_remote_address(rtp, &media->direct_media_addr); if (media->rtp) { - ast_channel_set_fd(chan, rtcp_fd, -1); + if (position != -1) { + ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, -1); + } ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 0); } } else if (!ast_sockaddr_isnull(&media->direct_media_addr)){ @@ -284,7 +322,9 @@ static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instan changed = 1; if (media->rtp) { ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 1); - ast_channel_set_fd(chan, rtcp_fd, ast_rtp_instance_fd(media->rtp, 1)); + if (position != -1) { + ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, ast_rtp_instance_fd(media->rtp, 1)); + } } } @@ -333,22 +373,27 @@ static int send_direct_media_request(void *data) { struct rtp_direct_media_data *cdata = data; struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(cdata->chan); - struct chan_pjsip_pvt *pvt = channel->pvt; + struct ast_sip_session *session; int changed = 0; int res = 0; + /* XXX In an ideal world each media stream would be direct, but for now preserve behavior + * and connect only the default media sessions for audio and video. + */ + /* The channel needs to be locked when checking for RTP changes. * Otherwise, we could end up destroying an underlying RTCP structure * at the same time that the channel thread is attempting to read RTCP */ ast_channel_lock(cdata->chan); - if (pvt->media[SIP_MEDIA_AUDIO]) { + session = channel->session; + if (session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) { changed |= check_for_rtp_changes( - cdata->chan, cdata->rtp, pvt->media[SIP_MEDIA_AUDIO], 1); + cdata->chan, cdata->rtp, session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO], session); } - if (pvt->media[SIP_MEDIA_VIDEO]) { + if (session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]) { changed |= check_for_rtp_changes( - cdata->chan, cdata->vrtp, pvt->media[SIP_MEDIA_VIDEO], 3); + cdata->chan, cdata->vrtp, session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO], session); } ast_channel_unlock(cdata->chan); @@ -368,7 +413,7 @@ static int send_direct_media_request(void *data) if (changed) { ast_debug(4, "RTP changed on %s; initiating direct media update\n", ast_channel_name(cdata->chan)); res = ast_sip_session_refresh(cdata->session, NULL, NULL, NULL, - cdata->session->endpoint->media.direct_media.method, 1); + cdata->session->endpoint->media.direct_media.method, 1, NULL); } ao2_ref(cdata, -1); @@ -420,14 +465,53 @@ static struct ast_rtp_glue chan_pjsip_rtp_glue = { .update_peer = chan_pjsip_set_rtp_peer, }; -static void set_channel_on_rtp_instance(struct chan_pjsip_pvt *pvt, const char *channel_id) +static void set_channel_on_rtp_instance(const struct ast_sip_session *session, + const char *channel_id) { - if (pvt->media[SIP_MEDIA_AUDIO] && pvt->media[SIP_MEDIA_AUDIO]->rtp) { - ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_AUDIO]->rtp, channel_id); + int i; + + for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->sessions); ++i) { + struct ast_sip_session_media *session_media; + + session_media = AST_VECTOR_GET(&session->active_media_state->sessions, i); + if (!session_media || !session_media->rtp) { + continue; + } + + ast_rtp_instance_set_channel_id(session_media->rtp, channel_id); } - if (pvt->media[SIP_MEDIA_VIDEO] && pvt->media[SIP_MEDIA_VIDEO]->rtp) { - ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_VIDEO]->rtp, channel_id); +} + +/*! + * \brief Determine if a topology is compatible with format capabilities + * + * This will return true if ANY formats in the topology are compatible with the format + * capabilities. + * + * XXX When supporting true multistream, we will need to be sure to mark which streams from + * top1 are compatible with which streams from top2. Then the ones that are not compatible + * will need to be marked as "removed" so that they are negotiated as expected. + * + * \param top Topology + * \param cap Format capabilities + * \retval 1 The topology has at least one compatible format + * \retval 0 The topology has no compatible formats or an error occurred. + */ +static int compatible_formats_exist(struct ast_stream_topology *top, struct ast_format_cap *cap) +{ + struct ast_format_cap *cap_from_top; + int res; + + cap_from_top = ast_format_cap_from_stream_topology(top); + + if (!cap_from_top) { + return 0; } + + res = ast_format_cap_iscompatible(cap_from_top, cap); + ao2_ref(cap_from_top, -1); + + return res; } /*! \brief Function called to create a new PJSIP Asterisk channel */ @@ -438,12 +522,9 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s RAII_VAR(struct chan_pjsip_pvt *, pvt, NULL, ao2_cleanup); struct ast_sip_channel_pvt *channel; struct ast_variable *var; + struct ast_stream_topology *topology; - if (!(pvt = ao2_alloc(sizeof(*pvt), chan_pjsip_pvt_dtor))) { - return NULL; - } - caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); - if (!caps) { + if (!(pvt = ao2_alloc_options(sizeof(*pvt), chan_pjsip_pvt_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK))) { return NULL; } @@ -457,31 +538,46 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s ast_sorcery_object_get_id(session->endpoint), (unsigned) ast_atomic_fetchadd_int((int *) &chan_idx, +1)); if (!chan) { - ao2_ref(caps, -1); return NULL; } ast_channel_tech_set(chan, &chan_pjsip_tech); if (!(channel = ast_sip_channel_pvt_alloc(pvt, session))) { - ao2_ref(caps, -1); ast_channel_unlock(chan); ast_hangup(chan); return NULL; } - ast_channel_stage_snapshot(chan); - ast_channel_tech_pvt_set(chan, channel); - if (!ast_format_cap_count(session->req_caps) || - !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) { + if (!ast_stream_topology_get_count(session->pending_media_state->topology) || + !compatible_formats_exist(session->pending_media_state->topology, session->endpoint->media.codecs)) { + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps) { + ast_channel_unlock(chan); + ast_hangup(chan); + return NULL; + } ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN); + topology = ast_stream_topology_clone(session->endpoint->media.topology); } else { - ast_format_cap_append_from_cap(caps, session->req_caps, AST_MEDIA_TYPE_UNKNOWN); + caps = ast_format_cap_from_stream_topology(session->pending_media_state->topology); + topology = ast_stream_topology_clone(session->pending_media_state->topology); } + if (!topology || !caps) { + ao2_cleanup(caps); + ast_stream_topology_free(topology); + ast_channel_unlock(chan); + ast_hangup(chan); + return NULL; + } + + ast_channel_stage_snapshot(chan); + ast_channel_nativeformats_set(chan, caps); + ast_channel_set_stream_topology(chan, topology); if (!ast_format_cap_empty(caps)) { struct ast_format *fmt; @@ -538,12 +634,7 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s ast_channel_stage_snapshot_done(chan); ast_channel_unlock(chan); - /* If res_pjsip_session is ever updated to create/destroy ast_sip_session_media - * during a call such as if multiple same-type stream support is introduced, - * these will need to be recaptured as well */ - pvt->media[SIP_MEDIA_AUDIO] = ao2_find(session->media, "audio", OBJ_KEY); - pvt->media[SIP_MEDIA_VIDEO] = ao2_find(session->media, "video", OBJ_KEY); - set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(chan)); + set_channel_on_rtp_instance(session, ast_channel_uniqueid(chan)); return chan; } @@ -682,49 +773,32 @@ static struct ast_frame *chan_pjsip_cng_tone_detected(struct ast_sip_session *se * * \note The channel is already locked. */ -static struct ast_frame *chan_pjsip_read(struct ast_channel *ast) +static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); - struct ast_sip_session *session; - struct chan_pjsip_pvt *pvt = channel->pvt; + struct ast_sip_session *session = channel->session; + struct ast_sip_session_media_read_callback_state *callback_state; struct ast_frame *f; - struct ast_sip_session_media *media = NULL; - int rtcp = 0; - int fdno = ast_channel_fdno(ast); + int fdno = ast_channel_fdno(ast) - AST_EXTENDED_FDS; - switch (fdno) { - case 0: - media = pvt->media[SIP_MEDIA_AUDIO]; - break; - case 1: - media = pvt->media[SIP_MEDIA_AUDIO]; - rtcp = 1; - break; - case 2: - media = pvt->media[SIP_MEDIA_VIDEO]; - break; - case 3: - media = pvt->media[SIP_MEDIA_VIDEO]; - rtcp = 1; - break; - } - - if (!media || !media->rtp) { + if (fdno >= AST_VECTOR_SIZE(&session->active_media_state->read_callbacks)) { return &ast_null_frame; } - if (!(f = ast_rtp_instance_read(media->rtp, rtcp))) { + callback_state = AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, fdno); + f = callback_state->read_callback(session, callback_state->session); + + if (!f) { return f; } - ast_rtp_instance_set_last_rx(media->rtp, time(NULL)); + f->stream_num = callback_state->session->stream_num; - if (f->frametype != AST_FRAME_VOICE) { + if (f->frametype != AST_FRAME_VOICE || + callback_state->session != session->active_media_state->default_session[callback_state->session->type]) { return f; } - session = channel->session; - if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when it has not been negotiated\n", ast_format_get_name(f->subclass.format), ast_channel_name(ast)); @@ -794,22 +868,31 @@ static struct ast_frame *chan_pjsip_read(struct ast_channel *ast) return f; } -/*! \brief Function called by core to write frames */ -static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame) +static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *frame) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); - struct chan_pjsip_pvt *pvt = channel->pvt; - struct ast_sip_session_media *media; + struct ast_sip_session *session = channel->session; + struct ast_sip_session_media *media = NULL; int res = 0; + /* The core provides a guarantee that the stream will exist when we are called if stream_num is provided */ + if (stream_num >= 0) { + /* What is not guaranteed is that a media session will exist */ + if (stream_num < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions)) { + media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, stream_num); + } + } + switch (frame->frametype) { case AST_FRAME_VOICE: - media = pvt->media[SIP_MEDIA_AUDIO]; - if (!media) { return 0; - } - if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { + } else if (media->type != AST_MEDIA_TYPE_AUDIO) { + ast_debug(3, "Channel %s stream %d is of type '%s', not audio!\n", + ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type)); + return 0; + } else if (media == channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO] && + ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); struct ast_str *write_transpath = ast_str_alloca(256); struct ast_str *read_transpath = ast_str_alloca(256); @@ -826,17 +909,32 @@ static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame) ast_format_get_name(ast_channel_rawwriteformat(ast)), ast_translate_path_to_str(ast_channel_writetrans(ast), &write_transpath)); return 0; - } - if (media->rtp) { - res = ast_rtp_instance_write(media->rtp, frame); + } else if (media->write_callback) { + res = media->write_callback(session, media, frame); + } break; case AST_FRAME_VIDEO: - if ((media = pvt->media[SIP_MEDIA_VIDEO]) && media->rtp) { - res = ast_rtp_instance_write(media->rtp, frame); + if (!media) { + return 0; + } else if (media->type != AST_MEDIA_TYPE_VIDEO) { + ast_debug(3, "Channel %s stream %d is of type '%s', not video!\n", + ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type)); + return 0; + } else if (media->write_callback) { + res = media->write_callback(session, media, frame); } break; case AST_FRAME_MODEM: + if (!media) { + return 0; + } else if (media->type != AST_MEDIA_TYPE_IMAGE) { + ast_debug(3, "Channel %s stream %d is of type '%s', not image!\n", + ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type)); + return 0; + } else if (media->write_callback) { + res = media->write_callback(session, media, frame); + } break; default: ast_log(LOG_WARNING, "Can't send %u type frames with PJSIP\n", frame->frametype); @@ -846,11 +944,15 @@ static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame) return res; } +static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame) +{ + return chan_pjsip_write_stream(ast, -1, frame); +} + /*! \brief Function called by core to change the underlying owner channel */ static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(newchan); - struct chan_pjsip_pvt *pvt = channel->pvt; if (channel->session->channel != oldchan) { return -1; @@ -863,7 +965,7 @@ static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *new */ channel->session->channel = newchan; - set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(newchan)); + set_channel_on_rtp_instance(channel->session, ast_channel_uniqueid(newchan)); return 0; } @@ -1278,7 +1380,7 @@ static int update_connected_line_information(void *data) /* Only the INVITE method actually needs SDP, UPDATE can do without */ generate_new_sdp = (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE); - ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp); + ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp, NULL); } } else if (session->endpoint->id.rpid_immediate && session->inv_session->state != PJSIP_INV_STATE_DISCONNECTED @@ -1309,21 +1411,18 @@ static int update_connected_line_information(void *data) } /*! \brief Callback which changes the value of locally held on the media stream */ -static int local_hold_set_state(void *obj, void *arg, int flags) +static void local_hold_set_state(struct ast_sip_session_media *session_media, unsigned int held) { - struct ast_sip_session_media *session_media = obj; - unsigned int *held = arg; - - session_media->locally_held = *held; - - return 0; + if (session_media) { + session_media->locally_held = held; + } } /*! \brief Update local hold state and send a re-INVITE with the new SDP */ static int remote_send_hold_refresh(struct ast_sip_session *session, unsigned int held) { - ao2_callback(session->media, OBJ_NODATA, local_hold_set_state, &held); - ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1); + AST_VECTOR_CALLBACK_VOID(&session->active_media_state->sessions, local_hold_set_state, held); + ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, NULL); ao2_ref(session, -1); return 0; @@ -1341,16 +1440,103 @@ static int remote_send_unhold(void *data) return remote_send_hold_refresh(data, 0); } +struct topology_change_refresh_data { + struct ast_sip_session *session; + struct ast_sip_session_media_state *media_state; +}; + +static void topology_change_refresh_data_free(struct topology_change_refresh_data *refresh_data) +{ + ao2_cleanup(refresh_data->session); + + ast_sip_session_media_state_free(refresh_data->media_state); + ast_free(refresh_data); +} + +static struct topology_change_refresh_data *topology_change_refresh_data_alloc( + struct ast_sip_session *session, const struct ast_stream_topology *topology) +{ + struct topology_change_refresh_data *refresh_data; + + refresh_data = ast_calloc(1, sizeof(*refresh_data)); + if (!refresh_data) { + return NULL; + } + + refresh_data->session = ao2_bump(session); + refresh_data->media_state = ast_sip_session_media_state_alloc(); + if (!refresh_data->media_state) { + topology_change_refresh_data_free(refresh_data); + return NULL; + } + refresh_data->media_state->topology = ast_stream_topology_clone(topology); + if (!refresh_data->media_state->topology) { + topology_change_refresh_data_free(refresh_data); + return NULL; + } + + return refresh_data; +} + +static int on_topology_change_response(struct ast_sip_session *session, pjsip_rx_data *rdata) +{ + if (rdata->msg_info.msg->line.status.code == 200) { + /* The topology was changed to something new so give notice to what requested + * it so it queries the channel and updates accordingly. + */ + if (session->channel) { + ast_queue_control(session->channel, AST_CONTROL_STREAM_TOPOLOGY_CHANGED); + } + } else if (rdata->msg_info.msg->line.status.code != 100) { + /* The topology change failed, so drop the current pending media state */ + ast_sip_session_media_state_reset(session->pending_media_state); + } + + return 0; +} + +static int send_topology_change_refresh(void *data) +{ + struct topology_change_refresh_data *refresh_data = data; + int ret; + + ret = ast_sip_session_refresh(refresh_data->session, NULL, NULL, on_topology_change_response, + AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, refresh_data->media_state); + refresh_data->media_state = NULL; + topology_change_refresh_data_free(refresh_data); + + return ret; +} + +static int handle_topology_request_change(struct ast_sip_session *session, + const struct ast_stream_topology *proposed) +{ + struct topology_change_refresh_data *refresh_data; + int res; + + refresh_data = topology_change_refresh_data_alloc(session, proposed); + if (!refresh_data) { + return -1; + } + + res = ast_sip_push_task(session->serializer, send_topology_change_refresh, refresh_data); + if (res) { + topology_change_refresh_data_free(refresh_data); + } + return res; +} + /*! \brief Function called by core to ask the channel to indicate some sort of condition */ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); - struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_session_media *media; int response_code = 0; int res = 0; char *device_buf; size_t device_buf_size; + int i; + const struct ast_stream_topology *topology; switch (condition) { case AST_CONTROL_RINGING: @@ -1403,39 +1589,47 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_sorcery_object_get_id(channel->session->endpoint)); break; case AST_CONTROL_VIDUPDATE: - media = pvt->media[SIP_MEDIA_VIDEO]; - if (media && media->rtp) { - /* FIXME: Only use this for VP8. Additional work would have to be done to - * fully support other video codecs */ - - if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) { - /* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the - * RTP engine would provide a way to externally write/schedule RTCP - * packets */ - struct ast_frame fr; - fr.frametype = AST_FRAME_CONTROL; - fr.subclass.integer = AST_CONTROL_VIDUPDATE; - res = ast_rtp_instance_write(media->rtp, &fr); - } else { - ao2_ref(channel->session, +1); -#ifdef HAVE_PJSIP_INV_SESSION_REF - if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) { - ast_log(LOG_ERROR, "Can't increase the session reference counter\n"); - ao2_cleanup(channel->session); + for (i = 0; i < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions); ++i) { + media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, i); + if (!media || media->type != AST_MEDIA_TYPE_VIDEO) { + continue; + } + if (media->rtp) { + /* FIXME: Only use this for VP8. Additional work would have to be done to + * fully support other video codecs */ + + if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) { + /* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the + * RTP engine would provide a way to externally write/schedule RTCP + * packets */ + struct ast_frame fr; + fr.frametype = AST_FRAME_CONTROL; + fr.subclass.integer = AST_CONTROL_VIDUPDATE; + res = ast_rtp_instance_write(media->rtp, &fr); } else { -#endif - if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) { + ao2_ref(channel->session, +1); +#ifdef HAVE_PJSIP_INV_SESSION_REF + if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Can't increase the session reference counter\n"); ao2_cleanup(channel->session); - } + } else { +#endif + if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) { + ao2_cleanup(channel->session); + } #ifdef HAVE_PJSIP_INV_SESSION_REF - } + } #endif + } + ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success"); + } else { + ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure"); + res = -1; } - ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success"); - } else { - ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure"); - res = -1; } + /* XXX If there were no video streams, then this should set + * res to -1 + */ break; case AST_CONTROL_CONNECTED_LINE: ao2_ref(channel->session, +1); @@ -1530,6 +1724,10 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi } } + break; + case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE: + topology = data; + res = handle_topology_request_change(channel->session, topology); break; case -1: res = -1; @@ -1744,10 +1942,11 @@ static int chan_pjsip_transfer(struct ast_channel *chan, const char *target) static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); - struct chan_pjsip_pvt *pvt = channel->pvt; - struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO]; + struct ast_sip_session_media *media; int res = 0; + media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]; + switch (channel->session->endpoint->dtmf) { case AST_SIP_DTMF_RFC_4733: if (!media || !media->rtp) { @@ -1755,14 +1954,14 @@ static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit) } ast_rtp_instance_dtmf_begin(media->rtp, digit); - break; + break; case AST_SIP_DTMF_AUTO: - if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) { - return -1; - } + if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) { + return -1; + } - ast_rtp_instance_dtmf_begin(media->rtp, digit); - break; + ast_rtp_instance_dtmf_begin(media->rtp, digit); + break; case AST_SIP_DTMF_NONE: break; case AST_SIP_DTMF_INBAND: @@ -1858,10 +2057,11 @@ failure: static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); - struct chan_pjsip_pvt *pvt = channel->pvt; - struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO]; + struct ast_sip_session_media *media; int res = 0; + media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]; + switch (channel->session->endpoint->dtmf) { case AST_SIP_DTMF_INFO: { @@ -1943,7 +2143,6 @@ static int call(void *data) { struct ast_sip_channel_pvt *channel = data; struct ast_sip_session *session = channel->session; - struct chan_pjsip_pvt *pvt = channel->pvt; pjsip_tx_data *tdata; int res = ast_sip_session_create_invite(session, &tdata); @@ -1952,7 +2151,7 @@ static int call(void *data) ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0); ast_queue_hangup(session->channel); } else { - set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(session->channel)); + set_channel_on_rtp_instance(session, ast_channel_uniqueid(session->channel)); update_initial_connected_line(session); ast_sip_session_send_request(session, tdata); } @@ -2050,10 +2249,10 @@ static struct hangup_data *hangup_data_alloc(int cause, struct ast_channel *chan } /*! \brief Clear a channel from a session along with its PVT */ -static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast, struct chan_pjsip_pvt *pvt) +static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast) { session->channel = NULL; - set_channel_on_rtp_instance(pvt, ""); + set_channel_on_rtp_instance(session, ""); ast_channel_tech_pvt_set(ast, NULL); } @@ -2062,7 +2261,6 @@ static int hangup(void *data) struct hangup_data *h_data = data; struct ast_channel *ast = h_data->chan; struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); - struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_session *session = channel->session; int cause = h_data->cause; @@ -2072,7 +2270,7 @@ static int hangup(void *data) * afterwards. */ ast_sip_session_terminate(ao2_bump(session), cause); - clear_session_and_channel(session, ast, pvt); + clear_session_and_channel(session, ast); ao2_cleanup(session); ao2_cleanup(channel); ao2_cleanup(h_data); @@ -2083,7 +2281,6 @@ static int hangup(void *data) static int chan_pjsip_hangup(struct ast_channel *ast) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); - struct chan_pjsip_pvt *pvt; int cause; struct hangup_data *h_data; @@ -2091,7 +2288,6 @@ static int chan_pjsip_hangup(struct ast_channel *ast) return -1; } - pvt = channel->pvt; cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel)); h_data = hangup_data_alloc(cause, ast); @@ -2110,7 +2306,7 @@ failure: /* Go ahead and do our cleanup of the session and channel even if we're not going * to be able to send our SIP request/response */ - clear_session_and_channel(channel->session, ast, pvt); + clear_session_and_channel(channel->session, ast); ao2_cleanup(channel); ao2_cleanup(h_data); @@ -2119,7 +2315,7 @@ failure: struct request_data { struct ast_sip_session *session; - struct ast_format_cap *caps; + struct ast_stream_topology *topology; const char *dest; int cause; }; @@ -2193,7 +2389,7 @@ static int request(void *obj) } } - if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->caps))) { + if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->topology))) { ast_log(LOG_ERROR, "Failed to create outgoing session to endpoint '%s'\n", endpoint_name); req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION; return -1; @@ -2205,12 +2401,12 @@ static int request(void *obj) } /*! \brief Function called by core to create a new outgoing PJSIP session */ -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) +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) { struct request_data req_data; RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup); - req_data.caps = cap; + req_data.topology = topology; req_data.dest = data; if (ast_sip_push_task_synchronous(NULL, request, &req_data)) { @@ -2228,6 +2424,23 @@ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_forma return session->channel; } +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) +{ + struct ast_stream_topology *topology; + struct ast_channel *chan; + + topology = ast_stream_topology_create_from_format_cap(cap); + if (!topology) { + return NULL; + } + + chan = chan_pjsip_request_with_stream_topology(type, topology, assignedids, requestor, data, cause); + + ast_stream_topology_free(topology); + + return chan; +} + struct sendtext_data { struct ast_sip_session *session; char text[0]; diff --git a/channels/pjsip/cli_commands.c b/channels/pjsip/cli_commands.c index fc14b25a8d4bac0941fa92a5ebc1f0b32e245987..33d0e02c11ddbf836c99ee5b96f4c2b26c908caf 100644 --- a/channels/pjsip/cli_commands.c +++ b/channels/pjsip/cli_commands.c @@ -342,8 +342,9 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags) const struct ast_channel_snapshot *snapshot = obj; struct ast_channel *channel = ast_channel_get_by_name(snapshot->name); struct ast_sip_channel_pvt *cpvt = channel ? ast_channel_tech_pvt(channel) : NULL; - struct chan_pjsip_pvt *pvt = cpvt ? cpvt->pvt : NULL; - struct ast_sip_session_media *media = pvt ? pvt->media[SIP_MEDIA_AUDIO] : NULL; + struct ast_sip_session *session; + struct ast_sip_session_media *media; + struct ast_rtp_instance *rtp; struct ast_rtp_instance_stats stats; char *print_name = NULL; char *print_time = alloca(32); @@ -351,29 +352,46 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags) ast_assert(context->output_buffer != NULL); + if (!channel) { + ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name); + return -1; + } + + ast_channel_lock(channel); + + session = cpvt->session; + if (!session) { + ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name); + ast_channel_unlock(channel); + ao2_cleanup(channel); + return -1; + } + + media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]; if (!media || !media->rtp) { ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name); + ast_channel_unlock(channel); ao2_cleanup(channel); return -1; } + rtp = ao2_bump(media->rtp); + codec_in_use[0] = '\0'; - if (channel) { - ast_channel_lock(channel); - if (ast_channel_rawreadformat(channel)) { - ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use)); - } - ast_channel_unlock(channel); + if (ast_channel_rawreadformat(channel)) { + ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use)); } + ast_channel_unlock(channel); + print_name = ast_strdupa(snapshot->name); /* Skip the PJSIP/. We know what channel type it is and we need the space. */ print_name += 6; ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32); - if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { + if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { ast_str_append(&context->output_buffer, 0, "%s direct media\n", snapshot->name); } else { ast_str_append(&context->output_buffer, 0, @@ -398,6 +416,7 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags) ); } + ao2_cleanup(rtp); ao2_cleanup(channel); return 0; diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c index e2c78cd87b4bdaa65b2ec66aed87a0d8abffc7bd..59ca9d791c894d8392d11222a6170a6cd793e5d2 100644 --- a/channels/pjsip/dialplan_functions.c +++ b/channels/pjsip/dialplan_functions.c @@ -437,6 +437,7 @@ #include "asterisk/acl.h" #include "asterisk/app.h" #include "asterisk/channel.h" +#include "asterisk/stream.h" #include "asterisk/format.h" #include "asterisk/pbx.h" #include "asterisk/res_pjsip.h" @@ -461,8 +462,8 @@ static const char *t38state_to_string[T38_MAX_ENUM] = { static int channel_read_rtp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); - struct chan_pjsip_pvt *pvt; - struct ast_sip_session_media *media = NULL; + struct ast_sip_session *session; + struct ast_sip_session_media *media; struct ast_sockaddr addr; if (!channel) { @@ -470,9 +471,9 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch return -1; } - pvt = channel->pvt; - if (!pvt) { - ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan)); + session = channel->session; + if (!session) { + ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan)); return -1; } @@ -482,9 +483,9 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch } if (ast_strlen_zero(field) || !strcmp(field, "audio")) { - media = pvt->media[SIP_MEDIA_AUDIO]; + media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]; } else if (!strcmp(field, "video")) { - media = pvt->media[SIP_MEDIA_VIDEO]; + media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]; } else { ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field); return -1; @@ -522,17 +523,17 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch static int channel_read_rtcp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); - struct chan_pjsip_pvt *pvt; - struct ast_sip_session_media *media = NULL; + struct ast_sip_session *session; + struct ast_sip_session_media *media; if (!channel) { ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); return -1; } - pvt = channel->pvt; - if (!pvt) { - ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan)); + session = channel->session; + if (!session) { + ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan)); return -1; } @@ -542,9 +543,9 @@ static int channel_read_rtcp(struct ast_channel *chan, const char *type, const c } if (ast_strlen_zero(field) || !strcmp(field, "audio")) { - media = pvt->media[SIP_MEDIA_AUDIO]; + media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]; } else if (!strcmp(field, "video")) { - media = pvt->media[SIP_MEDIA_VIDEO]; + media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]; } else { ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field); return -1; @@ -924,22 +925,117 @@ int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char return 0; } +/*! \brief Session refresh state information */ +struct session_refresh_state { + /*! \brief Created proposed media state */ + struct ast_sip_session_media_state *media_state; +}; + +/*! \brief Destructor for session refresh information */ +static void session_refresh_state_destroy(void *obj) +{ + struct session_refresh_state *state = obj; + + ast_sip_session_media_state_free(state->media_state); + ast_free(obj); +} + +/*! \brief Datastore for attaching session refresh state information */ +static const struct ast_datastore_info session_refresh_datastore = { + .type = "pjsip_session_refresh", + .destroy = session_refresh_state_destroy, +}; + +/*! \brief Helper function which retrieves or allocates a session refresh state information datastore */ +static struct session_refresh_state *session_refresh_state_get_or_alloc(struct ast_sip_session *session) +{ + RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "pjsip_session_refresh"), ao2_cleanup); + struct session_refresh_state *state; + + /* While the datastore refcount is decremented this is operating in the serializer so it will remain valid regardless */ + if (datastore) { + return datastore->data; + } + + if (!(datastore = ast_sip_session_alloc_datastore(&session_refresh_datastore, "pjsip_session_refresh")) + || !(datastore->data = ast_calloc(1, sizeof(struct session_refresh_state))) + || ast_sip_session_add_datastore(session, datastore)) { + return NULL; + } + + state = datastore->data; + state->media_state = ast_sip_session_media_state_alloc(); + if (!state->media_state) { + ast_sip_session_remove_datastore(session, "pjsip_session_refresh"); + return NULL; + } + state->media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology); + if (!state->media_state->topology) { + ast_sip_session_remove_datastore(session, "pjsip_session_refresh"); + return NULL; + } + + datastore->data = state; + + return state; +} + static int media_offer_read_av(struct ast_sip_session *session, char *buf, size_t len, enum ast_media_type media_type) { + struct ast_stream_topology *topology; int idx; + struct ast_stream *stream = NULL; + struct ast_format_cap *caps; size_t accum = 0; + if (session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) { + struct session_refresh_state *state; + + /* As we've already answered we need to store our media state until we are ready to send it */ + state = session_refresh_state_get_or_alloc(session); + if (!state) { + return -1; + } + topology = state->media_state->topology; + } else { + /* The session is not yet up so we are initially answering or offering */ + if (!session->pending_media_state->topology) { + session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology); + if (!session->pending_media_state->topology) { + return -1; + } + } + topology = session->pending_media_state->topology; + } + + /* Find the first suitable stream */ + for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + + if (ast_stream_get_type(stream) != media_type || + ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) { + stream = NULL; + continue; + } + + break; + } + + /* If no suitable stream then exit early */ + if (!stream) { + buf[0] = '\0'; + return 0; + } + + caps = ast_stream_get_formats(stream); + /* Note: buf is not terminated while the string is being built. */ - for (idx = 0; idx < ast_format_cap_count(session->req_caps); ++idx) { + for (idx = 0; idx < ast_format_cap_count(caps); ++idx) { struct ast_format *fmt; size_t size; - fmt = ast_format_cap_get_format(session->req_caps, idx); - if (ast_format_get_type(fmt) != media_type) { - ao2_ref(fmt, -1); - continue; - } + fmt = ast_format_cap_get_format(caps, idx); /* Add one for a comma or terminator */ size = strlen(ast_format_get_name(fmt)) + 1; @@ -973,9 +1069,43 @@ struct media_offer_data { static int media_offer_write_av(void *obj) { struct media_offer_data *data = obj; + struct ast_stream_topology *topology; + struct ast_stream *stream; + struct ast_format_cap *caps; - ast_format_cap_remove_by_type(data->session->req_caps, data->media_type); - ast_format_cap_update_by_allow_disallow(data->session->req_caps, data->value, 1); + if (data->session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) { + struct session_refresh_state *state; + + /* As we've already answered we need to store our media state until we are ready to send it */ + state = session_refresh_state_get_or_alloc(data->session); + if (!state) { + return -1; + } + topology = state->media_state->topology; + } else { + /* The session is not yet up so we are initially answering or offering */ + if (!data->session->pending_media_state->topology) { + data->session->pending_media_state->topology = ast_stream_topology_clone(data->session->endpoint->media.topology); + if (!data->session->pending_media_state->topology) { + return -1; + } + } + topology = data->session->pending_media_state->topology; + } + + /* XXX This method won't work when it comes time to do multistream support. The proper way to do this + * will either be to + * a) Alter all media streams of a particular type. + * b) Change the dialplan function to be able to specify which stream to alter and alter only that + * one stream + */ + stream = ast_stream_topology_get_first_stream_by_type(topology, data->media_type); + if (!stream) { + return 0; + } + caps = ast_stream_get_formats(stream); + ast_format_cap_remove_by_type(caps, data->media_type); + ast_format_cap_update_by_allow_disallow(caps, data->value, 1); return 0; } @@ -1068,9 +1198,18 @@ static int sip_session_response_cb(struct ast_sip_session *session, pjsip_rx_dat static int refresh_write_cb(void *obj) { struct refresh_data *data = obj; + struct session_refresh_state *state; + + state = session_refresh_state_get_or_alloc(data->session); + if (!state) { + return -1; + } ast_sip_session_refresh(data->session, NULL, NULL, - sip_session_response_cb, data->method, 1); + sip_session_response_cb, data->method, 1, state->media_state); + + state->media_state = NULL; + ast_sip_session_remove_datastore(data->session, "pjsip_session_refresh"); return 0; } diff --git a/channels/pjsip/include/chan_pjsip.h b/channels/pjsip/include/chan_pjsip.h index b229a0487f3bdb00b8b771d2074ae36284998dcb..1fee86419e4c682e1b0c4f459c15877d00508378 100644 --- a/channels/pjsip/include/chan_pjsip.h +++ b/channels/pjsip/include/chan_pjsip.h @@ -34,25 +34,12 @@ struct transport_info_data { pj_sockaddr local_addr; }; -/*! - * \brief Positions of various media - */ -enum sip_session_media_position { - /*! \brief First is audio */ - SIP_MEDIA_AUDIO = 0, - /*! \brief Second is video */ - SIP_MEDIA_VIDEO, - /*! \brief Last is the size for media details */ - SIP_MEDIA_SIZE, -}; /*! * \brief The PJSIP channel driver pvt, stored in the \ref ast_sip_channel_pvt * data structure */ struct chan_pjsip_pvt { - /*! \brief The available media sessions */ - struct ast_sip_session_media *media[SIP_MEDIA_SIZE]; }; #endif /* _CHAN_PJSIP_HEADER */ diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 3b93bb61fc242285805620580063ba0782504f0e..ed5f93e71e3e3c7d123daeea800250579512c321 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -779,6 +779,10 @@ ; The value "yes" is useful for some SIP phones ; (Cisco SPA) to be able to indicate and pick up ; ringing devices. +;max_audio_streams= ; The maximum number of allowed negotiated audio streams + ; (default: 1) +;max_video_streams= ; The maximum number of allowed negotiated video streams + ; (default: 1) ;==========================AUTH SECTION OPTIONS========================= ;[auth] 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 new file mode 100644 index 0000000000000000000000000000000000000000..a091272b01b269eba01a543b83122014d1b4944c --- /dev/null +++ b/contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py @@ -0,0 +1,24 @@ +"""pjsip_stream_maximum + +Revision ID: 39959b9c2566 +Revises: d7983954dd96 +Create Date: 2017-06-15 13:18:12.372333 + +""" + +# revision identifiers, used by Alembic. +revision = '39959b9c2566' +down_revision = 'd7983954dd96' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('ps_endpoints', sa.Column('max_audio_streams', sa.Integer)) + op.add_column('ps_endpoints', sa.Column('max_video_streams', sa.Integer)) + + +def downgrade(): + op.drop_column('ps_endpoints', 'max_audio_streams') + op.drop_column('ps_endpoints', 'max_video_streams') diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index b9c50addad4e542ef5a284966c653a6172cba4dd..f907effcfdd7454d50b0a253dc4488bb284e737d 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -666,6 +666,8 @@ struct ast_sip_endpoint_media_configuration { struct ast_sip_t38_configuration t38; /*! Configured codecs */ struct ast_format_cap *codecs; + /*! Capabilities in topology form */ + struct ast_stream_topology *topology; /*! DSCP TOS bits for audio streams */ unsigned int tos_audio; /*! Priority for audio streams */ @@ -680,6 +682,10 @@ struct ast_sip_endpoint_media_configuration { unsigned int bind_rtp_to_media_address; /*! Use RTCP-MUX */ unsigned int rtcp_mux; + /*! Maximum number of audio streams to offer/accept */ + unsigned int max_audio_streams; + /*! Maximum number of video streams to offer/accept */ + unsigned int max_video_streams; }; /*! diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h index e2a90662ed02be5e1b503cfb1aa9319fbaa82bc2..e298e1f32da575710cbd3582baaab132b645c748 100644 --- a/include/asterisk/res_pjsip_session.h +++ b/include/asterisk/res_pjsip_session.h @@ -28,6 +28,8 @@ #include "asterisk/netsock2.h" /* Needed for ast_sdp_srtp struct */ #include "asterisk/sdp_srtp.h" +/* Needed for ast_media_type */ +#include "asterisk/codec.h" /* Forward declarations */ struct ast_sip_endpoint; @@ -56,17 +58,21 @@ enum ast_sip_session_t38state { }; struct ast_sip_session_sdp_handler; +struct ast_sip_session; +struct ast_sip_session_media; + +typedef struct ast_frame *(*ast_sip_session_media_read_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media); +typedef int (*ast_sip_session_media_write_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + struct ast_frame *frame); /*! * \brief A structure containing SIP session media information */ struct ast_sip_session_media { - union { - /*! \brief RTP instance itself */ - struct ast_rtp_instance *rtp; - /*! \brief UDPTL instance itself */ - struct ast_udptl *udptl; - }; + /*! \brief RTP instance itself */ + struct ast_rtp_instance *rtp; + /*! \brief UDPTL instance itself */ + struct ast_udptl *udptl; /*! \brief Direct media address */ struct ast_sockaddr direct_media_addr; /*! \brief SDP handler that setup the RTP */ @@ -87,8 +93,38 @@ struct ast_sip_session_media { unsigned int locally_held:1; /*! \brief Does remote support rtcp_mux */ unsigned int remote_rtcp_mux:1; - /*! \brief Stream type this session media handles */ - char stream_type[1]; + /*! \brief Media type of this session media */ + enum ast_media_type type; + /*! \brief The write callback when writing frames */ + ast_sip_session_media_write_cb write_callback; + /*! \brief The stream number to place into any resulting frames */ + int stream_num; +}; + +/*! + * \brief Structure which contains read callback information + */ +struct ast_sip_session_media_read_callback_state { + /*! \brief The file descriptor itself */ + int fd; + /*! \brief The callback to invoke */ + ast_sip_session_media_read_cb read_callback; + /*! \brief The media session */ + struct ast_sip_session_media *session; +}; + +/*! + * \brief Structure which contains media state information (streams, sessions) + */ +struct ast_sip_session_media_state { + /*! \brief Mapping of stream to media sessions */ + AST_VECTOR(, struct ast_sip_session_media *) sessions; + /*! \brief Added read callbacks - these are whole structs and not pointers */ + AST_VECTOR(, struct ast_sip_session_media_read_callback_state) read_callbacks; + /*! \brief Default media sessions for each type */ + struct ast_sip_session_media *default_session[AST_MEDIA_TYPE_END]; + /*! \brief The media stream topology */ + struct ast_stream_topology *topology; }; /*! @@ -123,8 +159,6 @@ struct ast_sip_session { AST_LIST_HEAD(, ast_sip_session_supplement) supplements; /*! Datastores added to the session by supplements to the session */ struct ao2_container *datastores; - /*! Media streams */ - struct ao2_container *media; /*! Serializer for tasks relating to this SIP session */ struct ast_taskprocessor *serializer; /*! Non-null if the session serializer is suspended or being suspended. */ @@ -139,8 +173,10 @@ struct ast_sip_session { pj_timer_entry scheduled_termination; /*! Identity of endpoint this session deals with */ struct ast_party_id id; - /*! Requested capabilities */ - struct ast_format_cap *req_caps; + /*! Active media state (sessions + streams) - contents are guaranteed not to change */ + struct ast_sip_session_media_state *active_media_state; + /*! Pending media state (sessions + streams) */ + struct ast_sip_session_media_state *pending_media_state; /*! Optional DSP, used only for inband DTMF/Fax-CNG detection if configured */ struct ast_dsp *dsp; /*! Whether the termination of the session should be deferred */ @@ -315,34 +351,29 @@ struct ast_sip_session_sdp_handler { /*! * \brief Set session details based on a stream in an incoming SDP offer or answer * \param session The session for which the media is being negotiated - * \param session_media The media to be setup for this session + * \param session_media The media session * \param sdp The entire SDP. Useful for getting "global" information, such as connections or attributes - * \param stream The stream on which to operate - * \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called. - * \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned. - * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called. - */ - 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); - /*! - * \brief Create an SDP media stream and add it to the outgoing SDP offer or answer - * \param session The session for which media is being added - * \param session_media The media to be setup for this session - * \param stream The stream on which to operate + * \param index The index for the session media, Asterisk stream, and PJMEDIA stream being negotiated + * \param asterisk_stream The Asterisk stream representation * \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called. * \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned. * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called. */ - 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); + int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + const struct pjmedia_sdp_session *sdp, int index, struct ast_stream *asterisk_stream); /*! * \brief Create an SDP media stream and add it to the outgoing SDP offer or answer * \param session The session for which media is being added * \param session_media The media to be setup for this session * \param sdp The entire SDP as currently built + * \param remote Optional remote SDP if this is an answer + * \param stream The stream that is to be added to the outgoing SDP * \retval 0 This handler has no stream to add. If there are other registered handlers for this stream type, they will be called. * \retval <0 There was an error encountered. No further operation will take place and the current SDP negotiation will be abandoned. * \retval >0 The handler has a stream to be added to the SDP. No further handler of this stream type will be called. */ - int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp); + int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp, + const struct pjmedia_sdp_session *remote, struct ast_stream *stream); /*! * \brief Update media stream with external address if applicable * \param tdata The outgoing message itself @@ -353,17 +384,18 @@ struct ast_sip_session_sdp_handler { /*! * \brief Apply a negotiated SDP media stream * \param session The session for which media is being applied - * \param session_media The media to be setup for this session + * \param session_media The media session * \param local The entire local negotiated SDP - * \param local_stream The local stream which to apply * \param remote The entire remote negotiated SDP - * \param remote_stream The remote stream which to apply + * \param index The index of the session media, SDP streams, and Asterisk streams + * \param asterisk_stream The Asterisk stream representation * \retval 0 The stream was not applied by this handler. If there are other registered handlers for this stream type, they will be called. * \retval <0 There was an error encountered. No further operation will take place and the current application will be abandoned. * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called. */ - 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, - const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream); + 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_session *remote, int index, + struct ast_stream *asterisk_stream); /*! * \brief Stop a session_media created by this handler but do not destroy resources * \param session The session for which media is being stopped @@ -393,7 +425,7 @@ struct ast_sip_channel_pvt { /*! * \brief Allocate a new SIP channel pvt structure * - * \param pvt Pointer to channel specific implementation + * \param pvt Pointer to channel specific information * \param session Pointer to SIP session * * \retval non-NULL success @@ -452,11 +484,11 @@ void ast_sip_session_unsuspend(struct ast_sip_session *session); * \param contact The contact that this session will communicate with * \param location Name of the location to call, be it named location or explicit URI. Overrides contact if present. * \param request_user Optional request user to place in the request URI if permitted - * \param req_caps The requested capabilities + * \param req_topology The requested capabilities */ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact, const char *location, const char *request_user, - struct ast_format_cap *req_caps); + struct ast_stream_topology *req_topology); /*! * \brief Terminate a session and, if possible, send the provided response code @@ -613,15 +645,20 @@ void ast_sip_session_remove_datastore(struct ast_sip_session *session, const cha * \param on_response Callback called when response for request is received * \param method The method that should be used when constructing the session refresh * \param generate_new_sdp Boolean to indicate if a new SDP should be created + * \param media_state Optional requested media state for the SDP + * * \retval 0 Successfully sent refresh * \retval -1 Failure to send refresh + * + * \note If a media_state is passed in ownership will be taken in all cases */ int ast_sip_session_refresh(struct ast_sip_session *session, ast_sip_session_request_creation_cb on_request_creation, ast_sip_session_sdp_creation_cb on_sdp_creation, ast_sip_session_response_cb on_response, enum ast_sip_session_refresh_method method, - int generate_new_sdp); + int generate_new_sdp, + struct ast_sip_session_media_state *media_state); /*! * \brief Send a SIP response @@ -692,6 +729,110 @@ struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg); */ void ast_sip_session_resume_reinvite(struct ast_sip_session *session); +/*! + * \brief Determines if a provided pending stream will be the default stream or not + * \since 15.0.0 + * + * \param session The session to check against + * \param stream The pending stream + * + * \retval 1 if stream will be default + * \retval 0 if stream will NOT be the default + */ +int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream); + +/*! + * \brief Allocate a session media state structure + * \since 15.0.0 + * + * \retval non-NULL success + * \retval NULL failure + */ +struct ast_sip_session_media_state *ast_sip_session_media_state_alloc(void); + +/*! + * \brief Allocate an ast_session_media and add it to the media state's vector. + * \since 15.0.0 + * + * This allocates a session media of the specified type. The position argument + * determines where in the vector that the new session media will be inserted. + * + * \note The returned ast_session_media is the reference held by the vector. Callers + * of this function must NOT decrement the refcount of the session media. + * + * \param session Session on which to query active media state for + * \param media_state Media state to place the session media into + * \param type The type of the session media + * \param position Position at which to insert the new session media. + * + * \note The active media state will be queried and if a media session already + * exists at the given position for the same type it will be reused instead of + * allocating a new one. + * + * \retval non-NULL success + * \retval NULL failure + */ +struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session, + struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position); + +/*! + * \brief Reset a media state to a clean state + * \since 15.0.0 + * + * \param media_state The media state to reset + */ +void ast_sip_session_media_state_reset(struct ast_sip_session_media_state *media_state); + +/*! + * \brief Clone a media state + * \since 15.0.0 + * + * \param media_state The media state to clone + * + * \retval non-NULL success + * \retval NULL failure + */ +struct ast_sip_session_media_state *ast_sip_session_media_state_clone(const struct ast_sip_session_media_state *media_state); + +/*! + * \brief Free a session media state structure + * \since 15.0.0 + */ +void ast_sip_session_media_state_free(struct ast_sip_session_media_state *media_state); + +/*! + * \brief Set a read callback for a media session with a specific file descriptor + * \since 15.0.0 + * + * \param session The session + * \param session_media The media session + * \param fd The file descriptor + * \param callback The read callback + * + * \retval 0 the read callback was successfully added + * \retval -1 the read callback could not be added + * + * \note This operations on the pending media state + */ +int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + int fd, ast_sip_session_media_read_cb callback); + +/*! + * \brief Set a write callback for a media session + * \since 15.0.0 + * + * \param session The session + * \param session_media The media session + * \param callback The write callback + * + * \retval 0 the write callback was successfully add + * \retval -1 the write callback is already set to something different + * + * \note This operates on the pending media state + */ +int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + ast_sip_session_media_write_cb callback); + /*! \brief Determines whether the res_pjsip_session module is loaded */ #define CHECK_PJSIP_SESSION_MODULE_LOADED() \ do { \ diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h index 00169a3f1f6f32e0a1a9db2cb63ef0ef4e6a08e9..4027231edd4ea4de77e8eef21abfa4feba7b5bd0 100644 --- a/include/asterisk/stream.h +++ b/include/asterisk/stream.h @@ -289,6 +289,20 @@ struct ast_stream_topology *ast_stream_topology_alloc(void); struct ast_stream_topology *ast_stream_topology_clone( const struct ast_stream_topology *topology); +/*! + * \brief Compare two stream topologies to see if they are equal + * + * \param left The left topology + * \param right The right topology + * + * \retval 1 topologies are equivalent + * \retval 0 topologies differ + * + * \since 15 + */ +int ast_stream_topology_equal(const struct ast_stream_topology *left, + const struct ast_stream_topology *right); + /*! * \brief Destroy a stream topology * @@ -391,7 +405,7 @@ int ast_stream_topology_del_stream(struct ast_stream_topology *topology, * since a new format capabilities structure is created for each media type. * * \note Each stream will have its name set to the corresponding media type. - * For example: "AST_MEDIA_TYPE_AUDIO". + * For example: "audio". * * \note Each stream will be set to the sendrecv state. * diff --git a/main/channel.c b/main/channel.c index 8b4dc75bec8fe73b6f5bc6f88406faa0f493f207..c7c2b9d1e6e9fed50faa331e7c8536b4343903e6 100644 --- a/main/channel.c +++ b/main/channel.c @@ -4928,17 +4928,28 @@ int ast_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame goto done; } - /* If this frame is writing an audio or video frame get the stream information */ - if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) { - /* Initially use the default stream unless an explicit stream is provided */ - stream = default_stream = ast_channel_get_default_stream(chan, ast_format_get_type(fr->subclass.format)); + if (stream_num >= 0) { + /* If we were told to write to an explicit stream then allow this frame through, no matter + * if the type is expected or not (a framehook could change) + */ + if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) { + goto done; + } + stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num); + default_stream = ast_channel_get_default_stream(chan, ast_stream_get_type(stream)); + } else if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO || fr->frametype == AST_FRAME_MODEM) { + /* If we haven't been told of a stream then we need to figure out which once we need */ + enum ast_media_type type = AST_MEDIA_TYPE_UNKNOWN; - if (stream_num >= 0) { - if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) { - goto done; - } - stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num); + /* Some frame types have a fixed media type */ + if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) { + type = ast_format_get_type(fr->subclass.format); + } else if (fr->frametype == AST_FRAME_MODEM) { + type = AST_MEDIA_TYPE_IMAGE; } + + /* No stream was specified, so use the default one */ + stream = default_stream = ast_channel_get_default_stream(chan, type); } /* Perform the framehook write event here. After the frame enters the framehook list @@ -5035,12 +5046,16 @@ int ast_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame res = ast_channel_tech(chan)->write_video(chan, fr); } else { res = 0; - } break; case AST_FRAME_MODEM: - res = (ast_channel_tech(chan)->write == NULL) ? 0 : - ast_channel_tech(chan)->write(chan, fr); + if (ast_channel_tech(chan)->write_stream) { + res = ast_channel_tech(chan)->write_stream(chan, ast_stream_get_position(stream), fr); + } else if ((stream == default_stream) && ast_channel_tech(chan)->write) { + res = ast_channel_tech(chan)->write(chan, fr); + } else { + res = 0; + } break; case AST_FRAME_VOICE: if (ast_opt_generic_plc && ast_format_cmp(fr->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) { @@ -10948,6 +10963,12 @@ int ast_channel_request_stream_topology_change(struct ast_channel *chan, return -1; } + if (ast_stream_topology_equal(ast_channel_get_stream_topology(chan), topology)) { + ast_debug(3, "Topology of %s already matches what is requested so ignoring topology change request\n", + ast_channel_name(chan)); + return 0; + } + ast_channel_internal_set_stream_topology_change_source(chan, change_source); return ast_channel_tech(chan)->indicate(chan, AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE, topology, sizeof(topology)); diff --git a/main/stream.c b/main/stream.c index 20179f331337fd45b04fa24cdfb6b48dc8b74ee5..093cd5450ba934b3ac14ebbbd5312bd3a07d8728 100644 --- a/main/stream.c +++ b/main/stream.c @@ -284,6 +284,53 @@ struct ast_stream_topology *ast_stream_topology_clone( return new_topology; } +int ast_stream_topology_equal(const struct ast_stream_topology *left, + const struct ast_stream_topology *right) +{ + int index; + + ast_assert(left != NULL); + ast_assert(right != NULL); + + if (ast_stream_topology_get_count(left) != ast_stream_topology_get_count(right)) { + return 0; + } + + for (index = 0; index < ast_stream_topology_get_count(left); ++index) { + const struct ast_stream *left_stream = ast_stream_topology_get_stream(left, index); + const struct ast_stream *right_stream = ast_stream_topology_get_stream(right, index); + + if (ast_stream_get_type(left_stream) != ast_stream_get_type(right_stream)) { + return 0; + } + + if (ast_stream_get_state(left_stream) != ast_stream_get_state(right_stream)) { + return 0; + } + + if (!ast_stream_get_formats(left_stream) && ast_stream_get_formats(right_stream) && + ast_format_cap_count(ast_stream_get_formats(right_stream))) { + /* A NULL format capabilities and an empty format capabilities are the same, as they have + * no formats inside. If one does though... they are not equal. + */ + return 0; + } else if (!ast_stream_get_formats(right_stream) && ast_stream_get_formats(left_stream) && + ast_format_cap_count(ast_stream_get_formats(left_stream))) { + return 0; + } else if (ast_stream_get_formats(left_stream) && ast_stream_get_formats(right_stream) && + !ast_format_cap_identical(ast_stream_get_formats(left_stream), ast_stream_get_formats(right_stream))) { + /* But if both are actually present we need to do an actual identical check. */ + return 0; + } + + if (strcmp(ast_stream_get_name(left_stream), ast_stream_get_name(right_stream))) { + return 0; + } + } + + return 1; +} + void ast_stream_topology_free(struct ast_stream_topology *topology) { if (!topology) { diff --git a/res/res_pjsip.c b/res/res_pjsip.c index f6d63c6400768cad073a3c6af17a815492f75b36..e717fdb40f9a847346e6a9bd85e54909bc8c99f5 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -978,6 +978,20 @@ on Ringing when already INUSE. </para></description> </configOption> + <configOption name="max_audio_streams" default="1"> + <synopsis>The maximum number of allowed audio streams for the endpoint</synopsis> + <description><para> + This option enforces a limit on the maximum simultaneous negotiated audio + streams allowed for the endpoint. + </para></description> + </configOption> + <configOption name="max_video_streams" default="1"> + <synopsis>The maximum number of allowed video streams for the endpoint</synopsis> + <description><para> + This option enforces a limit on the maximum simultaneous negotiated video + streams allowed for the endpoint. + </para></description> + </configOption> </configObject> <configObject name="auth"> <synopsis>Authentication type</synopsis> diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 7a05f87f08c9097ac822734d03a38cb7196bf499..56a8419a88be2849974fdf9abd85dc2382d1bc66 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -22,6 +22,7 @@ #include "asterisk/test.h" #include "asterisk/statsd.h" #include "asterisk/pbx.h" +#include "asterisk/stream.h" /*! \brief Number of buckets for persistent endpoint information */ #define PERSISTENT_BUCKETS 53 @@ -1321,6 +1322,11 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o return -1; } + endpoint->media.topology = ast_stream_topology_create_from_format_cap(endpoint->media.codecs); + if (!endpoint->media.topology) { + return -1; + } + return 0; } @@ -1941,6 +1947,8 @@ int ast_res_pjsip_initialize_configuration(void) ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "refer_blind_progress", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, refer_blind_progress)); 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)); + 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)); + 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)); if (ast_sip_initialize_sorcery_transport()) { ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n"); @@ -2060,7 +2068,8 @@ static void endpoint_destructor(void* obj) ast_string_field_free_memory(endpoint); - ao2_ref(endpoint->media.codecs, -1); + ao2_cleanup(endpoint->media.codecs); + ast_stream_topology_free(endpoint->media.topology); subscription_configuration_destroy(&endpoint->subscription); info_configuration_destroy(&endpoint->info); media_configuration_destroy(&endpoint->media); diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index c5a673aa4e05971de52079a624d8f9b1c5464461..03fef40cf4b20359e861225500d531cf5a802a06 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -51,6 +51,8 @@ #include "asterisk/sdp_srtp.h" #include "asterisk/dsp.h" #include "asterisk/linkedlists.h" /* for AST_LIST_NEXT */ +#include "asterisk/stream.h" +#include "asterisk/format_cache.h" #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_session.h" @@ -62,48 +64,7 @@ static struct ast_sched_context *sched; static struct ast_sockaddr address_rtp; static const char STR_AUDIO[] = "audio"; -static const int FD_AUDIO = 0; - static const char STR_VIDEO[] = "video"; -static const int FD_VIDEO = 2; - -/*! \brief Retrieves an ast_format_type based on the given stream_type */ -static enum ast_media_type stream_to_media_type(const char *stream_type) -{ - if (!strcasecmp(stream_type, STR_AUDIO)) { - return AST_MEDIA_TYPE_AUDIO; - } else if (!strcasecmp(stream_type, STR_VIDEO)) { - return AST_MEDIA_TYPE_VIDEO; - } - - return 0; -} - -/*! \brief Get the starting descriptor for a media type */ -static int media_type_to_fdno(enum ast_media_type media_type) -{ - switch (media_type) { - case AST_MEDIA_TYPE_AUDIO: return FD_AUDIO; - case AST_MEDIA_TYPE_VIDEO: return FD_VIDEO; - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_IMAGE: - case AST_MEDIA_TYPE_END: break; - } - return -1; -} - -/*! \brief Remove all other cap types but the one given */ -static void format_cap_only_type(struct ast_format_cap *caps, enum ast_media_type media_type) -{ - int i = 0; - while (i <= AST_MEDIA_TYPE_TEXT) { - if (i != media_type && i != AST_MEDIA_TYPE_UNKNOWN) { - ast_format_cap_remove_by_type(caps, i); - } - i += 1; - } -} static int send_keepalive(const void *data) { @@ -253,11 +214,11 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND); } - if (!strcmp(session_media->stream_type, STR_AUDIO) && + if (session_media->type == AST_MEDIA_TYPE_AUDIO && (session->endpoint->media.tos_audio || session->endpoint->media.cos_audio)) { ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_audio, session->endpoint->media.cos_audio, "SIP RTP Audio"); - } else if (!strcmp(session_media->stream_type, STR_VIDEO) && + } else if (session_media->type == AST_MEDIA_TYPE_VIDEO && (session->endpoint->media.tos_video || session->endpoint->media.cos_video)) { ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video, session->endpoint->media.cos_video, "SIP RTP Video"); @@ -347,12 +308,13 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp static int set_caps(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_media *stream, - int is_offer) + int is_offer, struct ast_stream *asterisk_stream) { RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup); RAII_VAR(struct ast_format_cap *, peer, NULL, ao2_cleanup); RAII_VAR(struct ast_format_cap *, joint, NULL, ao2_cleanup); - enum ast_media_type media_type = stream_to_media_type(session_media->stream_type); + RAII_VAR(struct ast_format_cap *, endpoint_caps, NULL, ao2_cleanup); + enum ast_media_type media_type = session_media->type; struct ast_rtp_codecs codecs = AST_RTP_CODECS_NULL_INIT; int fmts = 0; int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) && @@ -362,14 +324,14 @@ static int set_caps(struct ast_sip_session *session, if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) || !(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) || !(joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { - ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type); + ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", + ast_codec_media_type2str(session_media->type)); return -1; } /* get the endpoint capabilities */ if (direct_media_enabled) { ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps); - format_cap_only_type(caps, media_type); } else { ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type); } @@ -386,7 +348,7 @@ static int set_caps(struct ast_sip_session *session, ast_rtp_codecs_payloads_destroy(&codecs); ast_log(LOG_NOTICE, "No joint capabilities for '%s' media stream between our configuration(%s) and incoming SDP(%s)\n", - session_media->stream_type, + ast_codec_media_type2str(session_media->type), ast_format_cap_get_names(caps, &usbuf), ast_format_cap_get_names(peer, &thembuf)); return -1; @@ -402,9 +364,9 @@ static int set_caps(struct ast_sip_session *session, ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp), session_media->rtp); - ast_format_cap_append_from_cap(session->req_caps, joint, AST_MEDIA_TYPE_UNKNOWN); + ast_stream_set_formats(asterisk_stream, joint); - if (session->channel) { + if (session->channel && ast_sip_session_is_pending_stream_default(session, asterisk_stream)) { ast_channel_lock(session->channel); ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel), @@ -968,24 +930,21 @@ static void set_ice_components(struct ast_sip_session *session, struct ast_sip_s } /*! \brief Function which negotiates an incoming media stream */ -static 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) +static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, + struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp, + int index, struct ast_stream *asterisk_stream) { char host[NI_MAXHOST]; RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free); - enum ast_media_type media_type = stream_to_media_type(session_media->stream_type); + pjmedia_sdp_media *stream = sdp->media[index]; + enum ast_media_type media_type = session_media->type; enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE; int res; - /* If port is 0, ignore this media stream */ - if (!stream->desc.port) { - ast_debug(3, "Media stream '%s' is already declined\n", session_media->stream_type); - return 0; - } - /* If no type formats have been configured reject this stream */ if (!ast_format_cap_has_type(session->endpoint->media.codecs, media_type)) { - ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n", session_media->stream_type); + ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n", + ast_codec_media_type2str(session_media->type)); return 0; } @@ -1040,7 +999,7 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport); } - if (set_caps(session, session_media, stream, 1)) { + if (set_caps(session, session_media, stream, 1, asterisk_stream)) { return 0; } return 1; @@ -1161,9 +1120,10 @@ static int add_crypto_to_stream(struct ast_sip_session *session, /*! \brief Function which creates an outgoing stream */ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, - struct pjmedia_sdp_session *sdp) + struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, struct ast_stream *stream) { pj_pool_t *pool = session->inv_session->pool_prov; + static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 }; static const pj_str_t STR_IN = { "IN", 2 }; static const pj_str_t STR_IP4 = { "IP4", 3}; static const pj_str_t STR_IP6 = { "IP6", 3}; @@ -1180,33 +1140,60 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as int min_packet_size = 0, max_packet_size = 0; int rtp_code; RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup); - enum ast_media_type media_type = stream_to_media_type(session_media->stream_type); - int use_override_prefs = ast_format_cap_count(session->req_caps); + enum ast_media_type media_type = session_media->type; int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) && ast_format_cap_count(session->direct_media_cap); - if ((use_override_prefs && !ast_format_cap_has_type(session->req_caps, media_type)) || - (!use_override_prefs && !ast_format_cap_has_type(session->endpoint->media.codecs, media_type))) { - /* If no type formats are configured don't add a stream */ - return 0; - } else if (!session_media->rtp && create_rtp(session, session_media)) { + media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media)); + if (!media) { return -1; } + pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type)); - set_ice_components(session, session_media); - enable_rtcp(session, session_media, NULL); + /* If this is a removed (or declined) stream OR if no formats exist then construct a minimal stream in SDP */ + if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED || !ast_stream_get_formats(stream) || + !ast_format_cap_count(ast_stream_get_formats(stream))) { + media->desc.port = 0; + media->desc.port_count = 1; + + if (remote) { + pjmedia_sdp_media *remote_media = remote->media[ast_stream_get_position(stream)]; + int index; + + media->desc.transport = remote_media->desc.transport; + + /* Preserve existing behavior by copying the formats provided from the offer */ + for (index = 0; index < remote_media->desc.fmt_count; ++index) { + media->desc.fmt[index] = remote_media->desc.fmt[index]; + } + media->desc.fmt_count = remote_media->desc.fmt_count; + } else { + /* This is actually an offer so put a dummy payload in that is ignored and sane transport */ + media->desc.transport = STR_RTP_AVP; + pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], "32"); + } + + sdp->media[sdp->media_count++] = media; + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); + + return 1; + } - if (!(media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media))) || - !(media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)))) { + if (!session_media->rtp && create_rtp(session, session_media)) { return -1; } + set_ice_components(session, session_media); + enable_rtcp(session, session_media, NULL); + + /* Crypto has to be added before setting the media transport so that SRTP is properly + * set up according to the configuration. This ends up changing the media transport. + */ if (add_crypto_to_stream(session, session_media, pool, media)) { return -1; } - media->desc.media = pj_str(session_media->stream_type); if (pj_strlen(&session_media->transport)) { /* If a transport has already been specified use it */ media->desc.transport = session_media->transport; @@ -1219,6 +1206,11 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as session->endpoint->media.rtp.force_avp)); } + media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)); + if (!media->conn) { + return -1; + } + /* Add connection level details */ if (direct_media_enabled) { hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR); @@ -1229,7 +1221,8 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as } if (ast_strlen_zero(hostip)) { - ast_log(LOG_ERROR, "No local host IP available for stream %s\n", session_media->stream_type); + ast_log(LOG_ERROR, "No local host IP available for stream %s\n", + ast_codec_media_type2str(session_media->type)); return -1; } @@ -1247,25 +1240,23 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as } } + /* Add ICE attributes and candidates */ + add_ice_to_stream(session, session_media, pool, media); + ast_rtp_instance_get_local_address(session_media->rtp, &addr); media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr); media->desc.port_count = 1; - /* Add ICE attributes and candidates */ - add_ice_to_stream(session, session_media, pool, media); - if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { - ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type); + ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", + ast_codec_media_type2str(session_media->type)); return -1; } if (direct_media_enabled) { ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps); - } else if (!ast_format_cap_count(session->req_caps) || - !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) { - ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type); } else { - ast_format_cap_append_from_cap(caps, session->req_caps, media_type); + ast_format_cap_append_from_cap(caps, ast_stream_get_formats(stream), media_type); } for (index = 0; index < ast_format_cap_count(caps); ++index) { @@ -1302,7 +1293,8 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as } /* Add non-codec formats */ - if (media_type != AST_MEDIA_TYPE_VIDEO && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) { + if (ast_sip_session_is_pending_stream_default(session, stream) && media_type != AST_MEDIA_TYPE_VIDEO + && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) { for (index = 1LL; index <= AST_RTP_MAX; index <<= 1) { if (!(noncodec & index)) { continue; @@ -1368,23 +1360,65 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as return 1; } -static 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, - const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream) +static struct ast_frame *media_session_rtp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media) +{ + struct ast_frame *f; + + if (!session_media->rtp) { + return &ast_null_frame; + } + + f = ast_rtp_instance_read(session_media->rtp, 0); + if (!f) { + return NULL; + } + + ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL)); + + return f; +} + +static struct ast_frame *media_session_rtcp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media) +{ + struct ast_frame *f; + + if (!session_media->rtp) { + return &ast_null_frame; + } + + f = ast_rtp_instance_read(session_media->rtp, 1); + if (!f) { + return NULL; + } + + ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL)); + + return f; +} + +static int media_session_rtp_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame) +{ + if (!session_media->rtp) { + return 0; + } + + return ast_rtp_instance_write(session_media->rtp, frame); +} + +static 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_session *remote, int index, struct ast_stream *asterisk_stream) { RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free); - enum ast_media_type media_type = stream_to_media_type(session_media->stream_type); + struct pjmedia_sdp_media *remote_stream = remote->media[index]; + enum ast_media_type media_type = session_media->type; char host[NI_MAXHOST]; - int fdno, res; + int res; if (!session->channel) { return 1; } - if (!local_stream->desc.port || !remote_stream->desc.port) { - return 1; - } - /* Ensure incoming transport is compatible with the endpoint's configuration */ if (!session->endpoint->media.rtp.use_received_transport && check_endpoint_media_transport(session->endpoint, remote_stream) == AST_SIP_MEDIA_TRANSPORT_INVALID) { @@ -1424,21 +1458,26 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a /* Apply connection information to the RTP instance */ ast_sockaddr_set_port(addrs, remote_stream->desc.port); ast_rtp_instance_set_remote_address(session_media->rtp, addrs); - if (set_caps(session, session_media, remote_stream, 0)) { + if (set_caps(session, session_media, remote_stream, 0, asterisk_stream)) { return 1; } - if ((fdno = media_type_to_fdno(media_type)) < 0) { - return -1; - } - ast_channel_set_fd(session->channel, fdno, ast_rtp_instance_fd(session_media->rtp, 0)); + ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback); + ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0), + media_session_rtp_read_callback); if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) { - ast_channel_set_fd(session->channel, fdno + 1, ast_rtp_instance_fd(session_media->rtp, 1)); + ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1), + media_session_rtcp_read_callback); } /* If ICE support is enabled find all the needed attributes */ process_ice_attributes(session, session_media, remote, remote_stream); + /* Set the channel uniqueid on the RTP instance now that it is becoming active */ + ast_channel_lock(session->channel); + ast_rtp_instance_set_channel_id(session_media->rtp, ast_channel_uniqueid(session->channel)); + ast_channel_unlock(session->channel); + /* Ensure the RTP instance is active */ ast_rtp_instance_activate(session_media->rtp); @@ -1476,7 +1515,7 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a session_media->encryption = session->endpoint->media.rtp.encryption; if (session->endpoint->media.rtp.keepalive > 0 && - stream_to_media_type(session_media->stream_type) == AST_MEDIA_TYPE_AUDIO) { + session_media->type == AST_MEDIA_TYPE_AUDIO) { ast_rtp_instance_set_keepalive(session_media->rtp, session->endpoint->media.rtp.keepalive); /* Schedule the initial keepalive early in case this is being used to punch holes through * a NAT. This way there won't be an awkward delay before media starts flowing in some diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index ffd01cadf054ca01156281f8e8f46c3c74fab82b..ecda4990132bff2d77eb9e4fbea1dbb44417ddaf 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -47,12 +47,16 @@ #include "asterisk/features_config.h" #include "asterisk/pickup.h" #include "asterisk/test.h" +#include "asterisk/stream.h" #define SDP_HANDLER_BUCKETS 11 #define MOD_DATA_ON_RESPONSE "on_response" #define MOD_DATA_NAT_HOOK "nat_hook" +/* Most common case is one audio and one video stream */ +#define DEFAULT_NUM_SESSION_MEDIA 2 + /* Some forward declarations */ static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata); static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata, @@ -103,23 +107,6 @@ static int sdp_handler_list_cmp(void *obj, void *arg, int flags) return strcmp(handler_list1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP; } -static int session_media_hash(const void *obj, int flags) -{ - const struct ast_sip_session_media *session_media = obj; - const char *stream_type = flags & OBJ_KEY ? obj : session_media->stream_type; - - return ast_str_hash(stream_type); -} - -static int session_media_cmp(void *obj, void *arg, int flags) -{ - struct ast_sip_session_media *session_media1 = obj; - struct ast_sip_session_media *session_media2 = arg; - const char *stream_type2 = flags & OBJ_KEY ? arg : session_media2->stream_type; - - return strcmp(session_media1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP; -} - int ast_sip_session_register_sdp_handler(struct ast_sip_session_sdp_handler *handler, const char *stream_type) { RAII_VAR(struct sdp_handler_list *, handler_list, @@ -187,6 +174,156 @@ void ast_sip_session_unregister_sdp_handler(struct ast_sip_session_sdp_handler * ao2_callback_data(sdp_handlers, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, remove_handler, (void *)stream_type, handler); } +struct ast_sip_session_media_state *ast_sip_session_media_state_alloc(void) +{ + struct ast_sip_session_media_state *media_state; + + media_state = ast_calloc(1, sizeof(*media_state)); + if (!media_state) { + return NULL; + } + + if (AST_VECTOR_INIT(&media_state->sessions, DEFAULT_NUM_SESSION_MEDIA) < 0) { + ast_free(media_state); + return NULL; + } + + if (AST_VECTOR_INIT(&media_state->read_callbacks, DEFAULT_NUM_SESSION_MEDIA) < 0) { + AST_VECTOR_FREE(&media_state->sessions); + ast_free(media_state); + return NULL; + } + + return media_state; +} + +void ast_sip_session_media_state_reset(struct ast_sip_session_media_state *media_state) +{ + int index; + + if (!media_state) { + return; + } + + AST_VECTOR_RESET(&media_state->sessions, ao2_cleanup); + AST_VECTOR_RESET(&media_state->read_callbacks, AST_VECTOR_ELEM_CLEANUP_NOOP); + + for (index = 0; index < AST_MEDIA_TYPE_END; ++index) { + media_state->default_session[index] = NULL; + } + + ast_stream_topology_free(media_state->topology); + media_state->topology = NULL; +} + +struct ast_sip_session_media_state *ast_sip_session_media_state_clone(const struct ast_sip_session_media_state *media_state) +{ + struct ast_sip_session_media_state *cloned; + int index; + + if (!media_state) { + return NULL; + } + + cloned = ast_sip_session_media_state_alloc(); + if (!cloned) { + return NULL; + } + + if (media_state->topology) { + cloned->topology = ast_stream_topology_clone(media_state->topology); + if (!cloned->topology) { + ast_sip_session_media_state_free(cloned); + return NULL; + } + } + + for (index = 0; index < AST_VECTOR_SIZE(&media_state->sessions); ++index) { + struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index); + enum ast_media_type type = ast_stream_get_type(ast_stream_topology_get_stream(cloned->topology, index)); + + AST_VECTOR_REPLACE(&cloned->sessions, index, ao2_bump(session_media)); + if (ast_stream_get_state(ast_stream_topology_get_stream(cloned->topology, index)) != AST_STREAM_STATE_REMOVED && + !cloned->default_session[type]) { + cloned->default_session[type] = session_media; + } + } + + for (index = 0; index < AST_VECTOR_SIZE(&media_state->read_callbacks); ++index) { + struct ast_sip_session_media_read_callback_state *read_callback = AST_VECTOR_GET_ADDR(&media_state->read_callbacks, index); + + AST_VECTOR_REPLACE(&cloned->read_callbacks, index, *read_callback); + } + + return cloned; +} + +void ast_sip_session_media_state_free(struct ast_sip_session_media_state *media_state) +{ + if (!media_state) { + return; + } + + /* This will reset the internal state so we only have to free persistent things */ + ast_sip_session_media_state_reset(media_state); + + AST_VECTOR_FREE(&media_state->sessions); + AST_VECTOR_FREE(&media_state->read_callbacks); + + ast_free(media_state); +} + +int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream) +{ + int index; + + ast_assert(session->pending_media_state->topology != NULL); + + if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) { + return 0; + } + + for (index = 0; index < ast_stream_topology_get_count(session->pending_media_state->topology); ++index) { + if (ast_stream_get_type(ast_stream_topology_get_stream(session->pending_media_state->topology, index)) != + ast_stream_get_type(stream)) { + continue; + } + + return ast_stream_topology_get_stream(session->pending_media_state->topology, index) == stream ? 1 : 0; + } + + return 0; +} + +int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + int fd, ast_sip_session_media_read_cb callback) +{ + struct ast_sip_session_media_read_callback_state callback_state = { + .fd = fd, + .read_callback = callback, + .session = session_media, + }; + + /* The contents of the vector are whole structs and not pointers */ + return AST_VECTOR_APPEND(&session->pending_media_state->read_callbacks, callback_state); +} + +int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, + ast_sip_session_media_write_cb callback) +{ + if (session_media->write_callback) { + if (session_media->write_callback == callback) { + return 0; + } + + return -1; + } + + session_media->write_callback = callback; + + return 0; +} + /*! * \brief Set an SDP stream handler for a corresponding session media. * @@ -207,50 +344,178 @@ static void session_media_set_handler(struct ast_sip_session_media *session_medi session_media->handler = handler; } +static int stream_destroy(void *obj, void *arg, int flags) +{ + struct sdp_handler_list *handler_list = obj; + struct ast_sip_session_media *session_media = arg; + struct ast_sip_session_sdp_handler *handler; + + AST_LIST_TRAVERSE(&handler_list->list, handler, next) { + handler->stream_destroy(session_media); + } + + return 0; +} + +static void session_media_dtor(void *obj) +{ + struct ast_sip_session_media *session_media = obj; + + /* It is possible for multiple handlers to have allocated memory on the + * session media (usually through a stream changing types). Therefore, we + * traverse all the SDP handlers and let them all call stream_destroy on + * the session_media + */ + ao2_callback(sdp_handlers, 0, stream_destroy, session_media); + + if (session_media->srtp) { + ast_sdp_srtp_destroy(session_media->srtp); + } +} + +struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session, + struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position) +{ + struct ast_sip_session_media *session_media = NULL; + + /* It is possible for this media state to already contain a session for the stream. If this + * is the case we simply return it. + */ + if (position < AST_VECTOR_SIZE(&media_state->sessions)) { + return AST_VECTOR_GET(&media_state->sessions, position); + } + + /* Determine if we can reuse the session media from the active media state if present */ + if (position < AST_VECTOR_SIZE(&session->active_media_state->sessions)) { + session_media = AST_VECTOR_GET(&session->active_media_state->sessions, position); + /* A stream can never exist without an accompanying media session */ + if (session_media->type == type) { + ao2_ref(session_media, +1); + } else { + session_media = NULL; + } + } + + if (!session_media) { + /* No existing media session we can use so create a new one */ + session_media = ao2_alloc_options(sizeof(*session_media), session_media_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!session_media) { + return NULL; + } + + session_media->encryption = session->endpoint->media.rtp.encryption; + session_media->keepalive_sched_id = -1; + session_media->timeout_sched_id = -1; + session_media->type = type; + session_media->stream_num = position; + } + + AST_VECTOR_REPLACE(&media_state->sessions, position, session_media); + + /* 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 */ + if (!media_state->default_session[type] && + ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) { + media_state->default_session[type] = session_media; + } + + return session_media; +} + +static int is_stream_limitation_reached(enum ast_media_type type, const struct ast_sip_endpoint *endpoint, int *type_streams) +{ + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + return !(type_streams[type] < endpoint->media.max_audio_streams); + case AST_MEDIA_TYPE_VIDEO: + return !(type_streams[type] < endpoint->media.max_video_streams); + case AST_MEDIA_TYPE_IMAGE: + /* We don't have an option for image (T.38) streams so cap it to one. */ + return (type_streams[type] > 0); + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + default: + /* We don't want any unknown or "other" streams on our endpoint, + * so always just say we've reached the limit + */ + return 1; + } +} + static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp) { int i; int handled = 0; + int type_streams[AST_MEDIA_TYPE_END] = {0}; if (session->inv_session && session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { ast_log(LOG_ERROR, "Failed to handle incoming SDP. Session has been already disconnected\n"); return -1; } + /* It is possible for SDP deferral to have already created a pending topology */ + if (!session->pending_media_state->topology) { + session->pending_media_state->topology = ast_stream_topology_alloc(); + if (!session->pending_media_state->topology) { + return -1; + } + } + for (i = 0; i < sdp->media_count; ++i) { /* See if there are registered handlers for this media stream type */ char media[20]; struct ast_sip_session_sdp_handler *handler; RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup); - RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup); + struct ast_sip_session_media *session_media = NULL; int res; + enum ast_media_type type; + struct ast_stream *stream = NULL; + pjmedia_sdp_media *remote_stream = sdp->media[i]; /* We need a null-terminated version of the media string */ ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media)); + type = ast_media_type_from_str(media); + + /* See if we have an already existing stream, which can occur from SDP deferral checking */ + if (i < ast_stream_topology_get_count(session->pending_media_state->topology)) { + stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i); + } + if (!stream) { + stream = ast_stream_alloc(ast_codec_media_type2str(type), type); + if (!stream) { + return -1; + } + ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream); + } - session_media = ao2_find(session->media, media, OBJ_KEY); + session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i); if (!session_media) { - /* if the session_media doesn't exist, there weren't - * any handlers at the time of its creation */ + return -1; + } + + /* If this stream is already declined mark it as such, or mark it as such if we've reached the limit */ + if (!remote_stream->desc.port || is_stream_limitation_reached(type, session->endpoint, type_streams)) { + ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n", + ast_codec_media_type2str(type), i); + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); continue; } if (session_media->handler) { handler = session_media->handler; ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n", - session_media->stream_type, + ast_codec_media_type2str(session_media->type), session_media->handler->id); - res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, - sdp->media[i]); + res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream); if (res < 0) { /* Catastrophic failure. Abort! */ return -1; } else if (res > 0) { ast_debug(1, "Media stream '%s' handled by %s\n", - session_media->stream_type, + ast_codec_media_type2str(session_media->type), session_media->handler->id); /* Handled by this handler. Move to the next stream */ handled = 1; + ++type_streams[type]; continue; } } @@ -265,21 +530,21 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd continue; } ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n", - session_media->stream_type, + ast_codec_media_type2str(session_media->type), handler->id); - res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, - sdp->media[i]); + res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream); if (res < 0) { /* Catastrophic failure. Abort! */ return -1; } if (res > 0) { ast_debug(1, "Media stream '%s' handled by %s\n", - session_media->stream_type, + ast_codec_media_type2str(session_media->type), handler->id); /* Handled by this handler. Move to the next stream */ session_media_set_handler(session_media, handler); handled = 1; + ++type_streams[type]; break; } } @@ -290,110 +555,159 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd return 0; } -struct handle_negotiated_sdp_cb { - struct ast_sip_session *session; - const pjmedia_sdp_session *local; - const pjmedia_sdp_session *remote; -}; - -static int handle_negotiated_sdp_session_media(void *obj, void *arg, int flags) +static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *session_media, + struct ast_sip_session *session, const pjmedia_sdp_session *local, + const pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream) { - struct ast_sip_session_media *session_media = obj; - struct handle_negotiated_sdp_cb *callback_data = arg; - struct ast_sip_session *session = callback_data->session; - const pjmedia_sdp_session *local = callback_data->local; - const pjmedia_sdp_session *remote = callback_data->remote; - int i; - - for (i = 0; i < local->media_count; ++i) { - /* See if there are registered handlers for this media stream type */ - char media[20]; - struct ast_sip_session_sdp_handler *handler; - RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup); - int res; + /* See if there are registered handlers for this media stream type */ + struct pjmedia_sdp_media *local_stream = local->media[index]; + char media[20]; + struct ast_sip_session_sdp_handler *handler; + RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup); + int res; - if (!remote->media[i]) { - continue; + /* For backwards compatibility we only reflect the stream state correctly on + * the non-default streams. This is because the stream state is also used for + * signaling that someone has placed us on hold. This situation is not handled + * currently and can result in the remote side being sort of placed on hold too. + */ + if (!ast_sip_session_is_pending_stream_default(session, asterisk_stream)) { + /* Determine the state of the stream based on our local SDP */ + if (pjmedia_sdp_media_find_attr2(local_stream, "sendonly", NULL)) { + ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDONLY); + } else if (pjmedia_sdp_media_find_attr2(local_stream, "recvonly", NULL)) { + ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_RECVONLY); + } else if (pjmedia_sdp_media_find_attr2(local_stream, "inactive", NULL)) { + ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_INACTIVE); + } else { + ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDRECV); } + } else { + ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDRECV); + } - /* We need a null-terminated version of the media string */ - ast_copy_pj_str(media, &local->media[i]->desc.media, sizeof(media)); - - /* stream type doesn't match the one we're looking to fill */ - if (strcasecmp(session_media->stream_type, media)) { - continue; - } + /* We need a null-terminated version of the media string */ + ast_copy_pj_str(media, &local->media[index]->desc.media, sizeof(media)); - handler = session_media->handler; - if (handler) { - ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n", - session_media->stream_type, + handler = session_media->handler; + if (handler) { + ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n", + ast_codec_media_type2str(session_media->type), + handler->id); + res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream); + if (res >= 0) { + ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n", + ast_codec_media_type2str(session_media->type), handler->id); - res = handler->apply_negotiated_sdp_stream(session, session_media, local, - local->media[i], remote, remote->media[i]); - if (res >= 0) { - ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n", - session_media->stream_type, - handler->id); - return CMP_MATCH; - } return 0; } + return -1; + } - handler_list = ao2_find(sdp_handlers, media, OBJ_KEY); - if (!handler_list) { - ast_debug(1, "No registered SDP handlers for media type '%s'\n", media); + handler_list = ao2_find(sdp_handlers, media, OBJ_KEY); + if (!handler_list) { + ast_debug(1, "No registered SDP handlers for media type '%s'\n", media); + return -1; + } + AST_LIST_TRAVERSE(&handler_list->list, handler, next) { + if (handler == session_media->handler) { continue; } - AST_LIST_TRAVERSE(&handler_list->list, handler, next) { - if (handler == session_media->handler) { - continue; - } - ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n", - session_media->stream_type, + ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n", + ast_codec_media_type2str(session_media->type), + handler->id); + res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream); + if (res < 0) { + /* Catastrophic failure. Abort! */ + return -1; + } + if (res > 0) { + ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n", + ast_codec_media_type2str(session_media->type), handler->id); - res = handler->apply_negotiated_sdp_stream(session, session_media, local, - local->media[i], remote, remote->media[i]); - if (res < 0) { - /* Catastrophic failure. Abort! */ - return 0; - } - if (res > 0) { - ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n", - session_media->stream_type, - handler->id); - /* Handled by this handler. Move to the next stream */ - session_media_set_handler(session_media, handler); - return CMP_MATCH; - } + /* Handled by this handler. Move to the next stream */ + session_media_set_handler(session_media, handler); + return 0; } } if (session_media->handler && session_media->handler->stream_stop) { ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n", - session_media->stream_type); + ast_codec_media_type2str(session_media->type)); session_media->handler->stream_stop(session_media); } - return CMP_MATCH; + return 0; } static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote) { - RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup); - struct handle_negotiated_sdp_cb callback_data = { - .session = session, - .local = local, - .remote = remote, - }; + int i; + struct ast_stream_topology *topology; - successful = ao2_callback(session->media, OBJ_MULTIPLE, handle_negotiated_sdp_session_media, &callback_data); - if (successful && ao2_iterator_count(successful) == ao2_container_count(session->media)) { - /* Nothing experienced a catastrophic failure */ - ast_queue_frame(session->channel, &ast_null_frame); - return 0; + for (i = 0; i < local->media_count; ++i) { + struct ast_sip_session_media *session_media; + struct ast_stream *stream; + + if (!remote->media[i]) { + continue; + } + + /* If we're handling negotiated streams, then we should already have set + * up session media instances (and Asterisk streams) that correspond to + * the local SDP, and there should be the same number of session medias + * and streams as there are local SDP streams + */ + ast_assert(i < AST_VECTOR_SIZE(&session->pending_media_state->sessions)); + ast_assert(i < ast_stream_topology_get_count(session->pending_media_state->topology)); + + session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, i); + stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i); + + /* The stream state will have already been set to removed when either we + * negotiate the incoming SDP stream or when we produce our own local SDP. + * This can occur if an internal thing has requested it to be removed, or if + * we remove it as a result of the stream limit being reached. + */ + if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) { + continue; + } + + if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i, stream)) { + return -1; + } } - return -1; + + /* Apply the pending media state to the channel and make it active */ + ast_channel_lock(session->channel); + + /* Update the topology on the channel to match the accepted one */ + topology = ast_stream_topology_clone(session->pending_media_state->topology); + if (topology) { + ast_channel_set_stream_topology(session->channel, topology); + } + + /* Remove all current file descriptors from the channel */ + for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++i) { + ast_channel_internal_fd_clear(session->channel, i + AST_EXTENDED_FDS); + } + + /* Add all the file descriptors from the pending media state */ + for (i = 0; i < AST_VECTOR_SIZE(&session->pending_media_state->read_callbacks); ++i) { + struct ast_sip_session_media_read_callback_state *callback_state = AST_VECTOR_GET_ADDR(&session->pending_media_state->read_callbacks, i); + + ast_channel_internal_fd_set(session->channel, i + AST_EXTENDED_FDS, callback_state->fd); + } + + /* Active and pending flip flop as needed */ + SWAP(session->active_media_state, session->pending_media_state); + ast_sip_session_media_state_reset(session->pending_media_state); + + ast_channel_unlock(session->channel); + + ast_queue_frame(session->channel, &ast_null_frame); + + return 0; } AST_RWLIST_HEAD_STATIC(session_supplements, ast_sip_session_supplement); @@ -570,6 +884,8 @@ struct ast_sip_session_delayed_request { ast_sip_session_response_cb on_response; /*! Whether to generate new SDP */ int generate_new_sdp; + /*! Requested media state for the SDP */ + struct ast_sip_session_media_state *media_state; AST_LIST_ENTRY(ast_sip_session_delayed_request) next; }; @@ -578,7 +894,8 @@ static struct ast_sip_session_delayed_request *delayed_request_alloc( ast_sip_session_request_creation_cb on_request_creation, ast_sip_session_sdp_creation_cb on_sdp_creation, ast_sip_session_response_cb on_response, - int generate_new_sdp) + int generate_new_sdp, + struct ast_sip_session_media_state *media_state) { struct ast_sip_session_delayed_request *delay = ast_calloc(1, sizeof(*delay)); @@ -590,9 +907,16 @@ static struct ast_sip_session_delayed_request *delayed_request_alloc( delay->on_sdp_creation = on_sdp_creation; delay->on_response = on_response; delay->generate_new_sdp = generate_new_sdp; + delay->media_state = media_state; return delay; } +static void delayed_request_free(struct ast_sip_session_delayed_request *delay) +{ + ast_sip_session_media_state_free(delay->media_state); + ast_free(delay); +} + static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_session_delayed_request *delay) { ast_debug(3, "Endpoint '%s(%s)' sending delayed %s request.\n", @@ -604,12 +928,16 @@ static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_ case DELAYED_METHOD_INVITE: ast_sip_session_refresh(session, delay->on_request_creation, delay->on_sdp_creation, delay->on_response, - AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp); + AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->media_state); + /* Ownership of media state transitions to ast_sip_session_refresh */ + delay->media_state = NULL; return 0; case DELAYED_METHOD_UPDATE: ast_sip_session_refresh(session, delay->on_request_creation, delay->on_sdp_creation, delay->on_response, - AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp); + AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->media_state); + /* Ownership of media state transitions to ast_sip_session_refresh */ + delay->media_state = NULL; return 0; case DELAYED_METHOD_BYE: ast_sip_session_terminate(session, 0); @@ -644,7 +972,7 @@ static int invite_proceeding(void *vsession) case DELAYED_METHOD_UPDATE: AST_LIST_REMOVE_CURRENT(next); res = send_delayed_request(session, delay); - ast_free(delay); + delayed_request_free(delay); found = 1; break; case DELAYED_METHOD_BYE: @@ -698,7 +1026,7 @@ static int invite_terminated(void *vsession) if (found) { AST_LIST_REMOVE_CURRENT(next); res = send_delayed_request(session, delay); - ast_free(delay); + delayed_request_free(delay); break; } } @@ -775,12 +1103,14 @@ static int delay_request(struct ast_sip_session *session, ast_sip_session_sdp_creation_cb on_sdp_creation, ast_sip_session_response_cb on_response, int generate_new_sdp, - enum delayed_method method) + enum delayed_method method, + struct ast_sip_session_media_state *media_state) { struct ast_sip_session_delayed_request *delay = delayed_request_alloc(method, - on_request, on_sdp_creation, on_response, generate_new_sdp); + on_request, on_sdp_creation, on_response, generate_new_sdp, media_state); if (!delay) { + ast_sip_session_media_state_free(media_state); return -1; } @@ -881,16 +1211,23 @@ int ast_sip_session_refresh(struct ast_sip_session *session, ast_sip_session_request_creation_cb on_request_creation, ast_sip_session_sdp_creation_cb on_sdp_creation, ast_sip_session_response_cb on_response, - enum ast_sip_session_refresh_method method, int generate_new_sdp) + enum ast_sip_session_refresh_method method, int generate_new_sdp, + struct ast_sip_session_media_state *media_state) { pjsip_inv_session *inv_session = session->inv_session; pjmedia_sdp_session *new_sdp = NULL; pjsip_tx_data *tdata; + if (media_state && (!media_state->topology || !generate_new_sdp)) { + ast_sip_session_media_state_free(media_state); + return -1; + } + if (inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { /* Don't try to do anything with a hung-up call */ ast_debug(3, "Not sending reinvite to %s because of disconnected state...\n", ast_sorcery_object_get_id(session->endpoint)); + ast_sip_session_media_state_free(media_state); return 0; } @@ -901,7 +1238,8 @@ int ast_sip_session_refresh(struct ast_sip_session *session, return delay_request(session, on_request_creation, on_sdp_creation, on_response, generate_new_sdp, method == AST_SIP_SESSION_REFRESH_METHOD_INVITE - ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE); + ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, + media_state); } if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) { @@ -910,13 +1248,14 @@ int ast_sip_session_refresh(struct ast_sip_session *session, ast_debug(3, "Delay sending reinvite to %s because of outstanding transaction...\n", ast_sorcery_object_get_id(session->endpoint)); return delay_request(session, on_request_creation, on_sdp_creation, - on_response, generate_new_sdp, DELAYED_METHOD_INVITE); + on_response, generate_new_sdp, DELAYED_METHOD_INVITE, media_state); } else if (inv_session->state != PJSIP_INV_STATE_CONFIRMED) { /* Initial INVITE transaction failed to progress us to a confirmed state * which means re-invites are not possible */ ast_debug(3, "Not sending reinvite to %s because not in confirmed state...\n", ast_sorcery_object_get_id(session->endpoint)); + ast_sip_session_media_state_free(media_state); return 0; } } @@ -931,33 +1270,130 @@ int ast_sip_session_refresh(struct ast_sip_session *session, return delay_request(session, on_request_creation, on_sdp_creation, on_response, generate_new_sdp, method == AST_SIP_SESSION_REFRESH_METHOD_INVITE - ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE); + ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, media_state); + } + + /* If an explicitly requested media state has been provided use it instead of any pending one */ + if (media_state) { + int index; + int type_streams[AST_MEDIA_TYPE_END] = {0}; + struct ast_stream *stream; + + /* Prune the media state so the number of streams fit within the configured limits - we do it here + * so that the index of the resulting streams in the SDP match. If we simply left the streams out + * of the SDP when producing it we'd be in trouble. We also enforce formats here for media types that + * are configurable on the endpoint. + */ + for (index = 0; index < ast_stream_topology_get_count(media_state->topology); ++index) { + stream = ast_stream_topology_get_stream(media_state->topology, index); + + if (is_stream_limitation_reached(ast_stream_get_type(stream), session->endpoint, type_streams)) { + if (index < AST_VECTOR_SIZE(&media_state->sessions)) { + struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index); + + ao2_cleanup(session_media); + AST_VECTOR_REMOVE(&media_state->sessions, index, 1); + } + + ast_stream_topology_del_stream(media_state->topology, index); + + /* A stream has potentially moved into our spot so we need to jump back so we process it */ + index -= 1; + continue; + } + + + /* Enforce the configured allowed codecs on audio and video streams */ + if (ast_stream_get_type(stream) == AST_MEDIA_TYPE_AUDIO || ast_stream_get_type(stream) == AST_MEDIA_TYPE_VIDEO) { + struct ast_format_cap *joint_cap; + + joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!joint_cap) { + ast_sip_session_media_state_free(media_state); + return 0; + } + + ast_format_cap_get_compatible(ast_stream_get_formats(stream), session->endpoint->media.codecs, joint_cap); + if (!ast_format_cap_count(joint_cap)) { + ao2_ref(joint_cap, -1); + ast_sip_session_media_state_free(media_state); + return 0; + } + + ast_stream_set_formats(stream, joint_cap); + } + + ++type_streams[ast_stream_get_type(stream)]; + } + + if (session->active_media_state->topology) { + /* SDP is a fun thing. Take for example the fact that streams are never removed. They just become + * declined. To better handle this in the case where something requests a topology change for fewer + * streams than are currently present we fill in the topology to match the current number of streams + * that are active. + */ + for (index = ast_stream_topology_get_count(media_state->topology); + index < ast_stream_topology_get_count(session->active_media_state->topology); ++index) { + struct ast_stream *cloned; + + stream = ast_stream_topology_get_stream(session->active_media_state->topology, index); + ast_assert(stream != NULL); + + cloned = ast_stream_clone(stream, NULL); + if (!cloned) { + ast_sip_session_media_state_free(media_state); + return -1; + } + + ast_stream_set_state(cloned, AST_STREAM_STATE_REMOVED); + ast_stream_topology_append_stream(media_state->topology, cloned); + } + + /* If the resulting media state matches the existing active state don't bother doing a session refresh */ + if (ast_stream_topology_equal(session->active_media_state->topology, media_state->topology)) { + ast_sip_session_media_state_free(media_state); + return 0; + } + } + + ast_sip_session_media_state_free(session->pending_media_state); + session->pending_media_state = media_state; } new_sdp = generate_session_refresh_sdp(session); if (!new_sdp) { ast_log(LOG_ERROR, "Failed to generate session refresh SDP. Not sending session refresh\n"); + ast_sip_session_media_state_reset(session->pending_media_state); return -1; } if (on_sdp_creation) { if (on_sdp_creation(session, new_sdp)) { + ast_sip_session_media_state_reset(session->pending_media_state); return -1; } } } - if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) { if (pjsip_inv_reinvite(inv_session, NULL, new_sdp, &tdata)) { ast_log(LOG_WARNING, "Failed to create reinvite properly.\n"); + if (generate_new_sdp) { + ast_sip_session_media_state_reset(session->pending_media_state); + } return -1; } } else if (pjsip_inv_update(inv_session, NULL, new_sdp, &tdata)) { ast_log(LOG_WARNING, "Failed to create UPDATE properly.\n"); + if (generate_new_sdp) { + ast_sip_session_media_state_reset(session->pending_media_state); + } return -1; } if (on_request_creation) { if (on_request_creation(session, tdata)) { + if (generate_new_sdp) { + ast_sip_session_media_state_reset(session->pending_media_state); + } return -1; } } @@ -992,22 +1428,40 @@ static int sdp_requires_deferral(struct ast_sip_session *session, const pjmedia_ { int i; + if (!session->pending_media_state->topology) { + session->pending_media_state->topology = ast_stream_topology_alloc(); + if (!session->pending_media_state->topology) { + return -1; + } + } + for (i = 0; i < sdp->media_count; ++i) { /* See if there are registered handlers for this media stream type */ char media[20]; struct ast_sip_session_sdp_handler *handler; RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup); - RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup); + struct ast_stream *stream; + enum ast_media_type type; + struct ast_sip_session_media *session_media = NULL; enum ast_sip_session_sdp_stream_defer res; /* We need a null-terminated version of the media string */ ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media)); - session_media = ao2_find(session->media, media, OBJ_KEY); + type = ast_media_type_from_str(media); + stream = ast_stream_alloc(ast_codec_media_type2str(type), type); + if (!stream) { + return -1; + } + + /* As this is only called on an incoming SDP offer before processing it is not possible + * for streams and their media sessions to exist. + */ + ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream); + + session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i); if (!session_media) { - /* if the session_media doesn't exist, there weren't - * any handlers at the time of its creation */ - continue; + return -1; } if (session_media->handler) { @@ -1269,29 +1723,6 @@ static int datastore_cmp(void *obj, void *arg, int flags) return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP; } -static void session_media_dtor(void *obj) -{ - struct ast_sip_session_media *session_media = obj; - struct sdp_handler_list *handler_list; - /* It is possible for SDP handlers to allocate memory on a session_media but - * not end up getting set as the handler for this session_media. This traversal - * ensures that all memory allocated by SDP handlers on the session_media is - * cleared (as well as file descriptors, etc.). - */ - handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY); - if (handler_list) { - struct ast_sip_session_sdp_handler *handler; - - AST_LIST_TRAVERSE(&handler_list->list, handler, next) { - handler->stream_destroy(session_media); - } - } - ao2_cleanup(handler_list); - if (session_media->srtp) { - ast_sdp_srtp_destroy(session_media->srtp); - } -} - static void session_destructor(void *obj) { struct ast_sip_session *session = obj; @@ -1320,17 +1751,17 @@ static void session_destructor(void *obj) ast_taskprocessor_unreference(session->serializer); ao2_cleanup(session->datastores); - ao2_cleanup(session->media); + ast_sip_session_media_state_free(session->active_media_state); + ast_sip_session_media_state_free(session->pending_media_state); AST_LIST_HEAD_DESTROY(&session->supplements); while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) { - ast_free(delay); + delayed_request_free(delay); } ast_party_id_free(&session->id); ao2_cleanup(session->endpoint); ao2_cleanup(session->aor); ao2_cleanup(session->contact); - ao2_cleanup(session->req_caps); ao2_cleanup(session->direct_media_cap); ast_dsp_free(session->dsp); @@ -1357,25 +1788,6 @@ static int add_supplements(struct ast_sip_session *session) return 0; } -static int add_session_media(void *obj, void *arg, int flags) -{ - struct sdp_handler_list *handler_list = obj; - struct ast_sip_session *session = arg; - RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup); - - session_media = ao2_alloc(sizeof(*session_media) + strlen(handler_list->stream_type), session_media_dtor); - if (!session_media) { - return CMP_STOP; - } - session_media->encryption = session->endpoint->media.rtp.encryption; - session_media->keepalive_sched_id = -1; - session_media->timeout_sched_id = -1; - /* Safe use of strcpy */ - strcpy(session_media->stream_type, handler_list->stream_type); - ao2_link(session->media, session_media); - return 0; -} - /*! \brief Destructor for SIP channel */ static void sip_channel_destroy(void *obj) { @@ -1422,14 +1834,18 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, if (!session->direct_media_cap) { return NULL; } - session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); - if (!session->req_caps) { - return NULL; - } session->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp); if (!session->datastores) { return NULL; } + session->active_media_state = ast_sip_session_media_state_alloc(); + if (!session->active_media_state) { + return NULL; + } + session->pending_media_state = ast_sip_session_media_state_alloc(); + if (!session->pending_media_state) { + return NULL; + } if (endpoint->dtmf == AST_SIP_DTMF_INBAND || endpoint->dtmf == AST_SIP_DTMF_AUTO) { dsp_features |= DSP_FEATURE_DIGIT_DETECT; @@ -1448,13 +1864,6 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, session->endpoint = ao2_bump(endpoint); - session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp); - if (!session->media) { - return NULL; - } - /* fill session->media with available types */ - ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session); - if (rdata) { /* * We must continue using the serializer that the original @@ -1704,7 +2113,7 @@ static int setup_outbound_invite_auth(pjsip_dialog *dlg) struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact, const char *location, const char *request_user, - struct ast_format_cap *req_caps) + struct ast_stream_topology *req_topology) { const char *uri = NULL; RAII_VAR(struct ast_sip_aor *, found_aor, NULL, ao2_cleanup); @@ -1768,22 +2177,68 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint session->aor = ao2_bump(found_aor); ast_party_id_copy(&session->id, &endpoint->id.self); - if (ast_format_cap_count(req_caps)) { - /* get joint caps between req_caps and endpoint caps */ - struct ast_format_cap *joint_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (ast_stream_topology_get_count(req_topology) > 0) { + /* get joint caps between req_topology and endpoint topology */ + int i; + + for (i = 0; i < ast_stream_topology_get_count(req_topology); ++i) { + struct ast_stream *req_stream; + struct ast_format_cap *req_cap; + struct ast_format_cap *joint_cap; + struct ast_stream *clone_stream; + + req_stream = ast_stream_topology_get_stream(req_topology, i); + + if (ast_stream_get_state(req_stream) == AST_STREAM_STATE_REMOVED) { + continue; + } + + req_cap = ast_stream_get_formats(req_stream); - ast_format_cap_get_compatible(req_caps, endpoint->media.codecs, joint_caps); + joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!joint_cap) { + continue; + } + + ast_format_cap_get_compatible(req_cap, endpoint->media.codecs, joint_cap); + if (!ast_format_cap_count(joint_cap)) { + ao2_ref(joint_cap, -1); + continue; + } + + clone_stream = ast_stream_clone(req_stream, NULL); + if (!clone_stream) { + ao2_ref(joint_cap, -1); + continue; + } + + ast_stream_set_formats(clone_stream, joint_cap); + ao2_ref(joint_cap, -1); + + if (!session->pending_media_state->topology) { + session->pending_media_state->topology = ast_stream_topology_alloc(); + if (!session->pending_media_state->topology) { + pjsip_inv_terminate(inv_session, 500, PJ_FALSE); + ao2_ref(session, -1); + return NULL; + } + } - /* if joint caps */ - if (ast_format_cap_count(joint_caps)) { - /* copy endpoint caps into session->req_caps */ - ast_format_cap_append_from_cap(session->req_caps, - endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN); - /* replace instances of joint caps equivalents in session->req_caps */ - ast_format_cap_replace_from_cap(session->req_caps, joint_caps, - AST_MEDIA_TYPE_UNKNOWN); + if (ast_stream_topology_append_stream(session->pending_media_state->topology, clone_stream) < 0) { + ast_stream_free(clone_stream); + continue; + } + } + } + + if (!session->pending_media_state->topology) { + /* Use the configured topology on the endpoint as the pending one */ + session->pending_media_state->topology = ast_stream_topology_clone(endpoint->media.topology); + if (!session->pending_media_state->topology) { + pjsip_inv_terminate(inv_session, 500, PJ_FALSE); + ao2_ref(session, -1); + return NULL; } - ao2_cleanup(joint_caps); } if (pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) { @@ -1847,7 +2302,7 @@ void ast_sip_session_terminate(struct ast_sip_session *session, int response) /* If this is delayed the only thing that will happen is a BYE request so we don't * actually need to store the response code for when it happens. */ - delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE); + delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL); break; } /* Fall through */ @@ -1858,7 +2313,7 @@ void ast_sip_session_terminate(struct ast_sip_session *session, int response) /* Flush any delayed requests so they cannot overlap this transaction. */ while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) { - ast_free(delay); + delayed_request_free(delay); } if (packet->msg->type == PJSIP_RESPONSE_MSG) { @@ -2387,7 +2842,7 @@ static void reschedule_reinvite(struct ast_sip_session *session, ast_sip_session ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision.\n", ast_sorcery_object_get_id(session->endpoint), session->channel ? ast_channel_name(session->channel) : ""); - if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE)) { + if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, NULL)) { return; } if (pj_timer_entry_running(&session->rescheduled_reinvite)) { @@ -2944,27 +3399,27 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans } } -static int add_sdp_streams(void *obj, void *arg, void *data, int flags) +static int add_sdp_streams(struct ast_sip_session_media *session_media, + struct ast_sip_session *session, pjmedia_sdp_session *answer, + const struct pjmedia_sdp_session *remote, + struct ast_stream *stream) { - struct ast_sip_session_media *session_media = obj; - pjmedia_sdp_session *answer = arg; - struct ast_sip_session *session = data; struct ast_sip_session_sdp_handler *handler = session_media->handler; RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup); int res; if (handler) { /* if an already assigned handler reports a catastrophic error, fail */ - res = handler->create_outgoing_sdp_stream(session, session_media, answer); + res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream); if (res < 0) { - return 0; + return -1; } - return CMP_MATCH; + return 0; } - handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY); + handler_list = ao2_find(sdp_handlers, ast_codec_media_type2str(session_media->type), OBJ_KEY); if (!handler_list) { - return CMP_MATCH; + return 0; } /* no handler for this stream type and we have a list to search */ @@ -2972,29 +3427,30 @@ static int add_sdp_streams(void *obj, void *arg, void *data, int flags) if (handler == session_media->handler) { continue; } - res = handler->create_outgoing_sdp_stream(session, session_media, answer); + res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream); if (res < 0) { /* catastrophic error */ - return 0; + return -1; } if (res > 0) { /* Handled by this handler. Move to the next stream */ session_media_set_handler(session_media, handler); - return CMP_MATCH; + return 0; } } /* streams that weren't handled won't be included in generated outbound SDP */ - return CMP_MATCH; + return 0; } static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer) { - RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup); static const pj_str_t STR_IN = { "IN", 2 }; static const pj_str_t STR_IP4 = { "IP4", 3 }; static const pj_str_t STR_IP6 = { "IP6", 3 }; pjmedia_sdp_session *local; + int i; + int stream; if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n"); @@ -3015,47 +3471,81 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru pj_strdup2(inv->pool_prov, &local->origin.user, session->endpoint->media.sdpowner); pj_strdup2(inv->pool_prov, &local->name, session->endpoint->media.sdpsession); - /* Now let the handlers add streams of various types, pjmedia will automatically reorder the media streams for us */ - successful = ao2_callback_data(session->media, OBJ_MULTIPLE, add_sdp_streams, local, session); - if (!successful || ao2_iterator_count(successful) != ao2_container_count(session->media)) { - /* Something experienced a catastrophic failure */ - return NULL; + if (!session->pending_media_state->topology || !ast_stream_topology_get_count(session->pending_media_state->topology)) { + /* We've encountered a situation where we have been told to create a local SDP but noone has given us any indication + * of what kind of stream topology they would like. As a fallback we use the topology from the configured endpoint. + */ + ast_stream_topology_free(session->pending_media_state->topology); + session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology); + if (!session->pending_media_state->topology) { + return NULL; + } } - /* Use the connection details of the first media stream if possible for SDP level */ - if (local->media_count) { - int stream; + for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) { + struct ast_sip_session_media *session_media; + struct ast_stream *stream; - /* Since we are using the first media stream as the SDP level we can get rid of it - * from the stream itself + /* This code does not enforce any maximum stream count limitations as that is done on either + * the handling of an incoming SDP offer or on the handling of a session refresh. */ - local->conn = local->media[0]->conn; - local->media[0]->conn = NULL; - pj_strassign(&local->origin.net_type, &local->conn->net_type); - pj_strassign(&local->origin.addr_type, &local->conn->addr_type); - pj_strassign(&local->origin.addr, &local->conn->addr); - - /* Go through each media stream seeing if the connection details actually differ, - * if not just use SDP level and reduce the SDP size - */ - for (stream = 1; stream < local->media_count; stream++) { + + stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i); + + session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_stream_get_type(stream), i); + if (!session_media) { + return NULL; + } + + if (add_sdp_streams(session_media, session, local, offer, stream)) { + return NULL; + } + + /* Ensure that we never exceed the maximum number of streams PJMEDIA will allow. */ + if (local->media_count == PJMEDIA_MAX_SDP_MEDIA) { + break; + } + } + + /* Use the connection details of an available media if possible for SDP level */ + for (stream = 0; stream < local->media_count; stream++) { + if (!local->media[stream]->conn) { + continue; + } + + if (local->conn) { if (!pj_strcmp(&local->conn->net_type, &local->media[stream]->conn->net_type) && !pj_strcmp(&local->conn->addr_type, &local->media[stream]->conn->addr_type) && !pj_strcmp(&local->conn->addr, &local->media[stream]->conn->addr)) { local->media[stream]->conn = NULL; } + continue; } - } else { - local->origin.net_type = STR_IN; - local->origin.addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4; + + /* This stream's connection info will serve as the connection details for SDP level */ + local->conn = local->media[stream]->conn; + local->media[stream]->conn = NULL; + + continue; + } + + /* If no SDP level connection details are present then create some */ + if (!local->conn) { + local->conn = pj_pool_zalloc(inv->pool_prov, sizeof(struct pjmedia_sdp_conn)); + local->conn->net_type = STR_IN; + local->conn->addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4; if (!ast_strlen_zero(session->endpoint->media.address)) { - pj_strdup2(inv->pool_prov, &local->origin.addr, session->endpoint->media.address); + pj_strdup2(inv->pool_prov, &local->conn->addr, session->endpoint->media.address); } else { - 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())); + 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())); } } + pj_strassign(&local->origin.net_type, &local->conn->net_type); + pj_strassign(&local->origin.addr_type, &local->conn->addr_type); + pj_strassign(&local->origin.addr, &local->conn->addr); + return local; } diff --git a/res/res_pjsip_session.exports.in b/res/res_pjsip_session.exports.in index fdfc5fb4724615c568bf91985299bc34519a7ec3..b7bd21b893df630ab682906bc23df8cd88e14b9c 100644 --- a/res/res_pjsip_session.exports.in +++ b/res/res_pjsip_session.exports.in @@ -1,27 +1,7 @@ { global: - LINKER_SYMBOL_PREFIXast_sip_session_terminate; - LINKER_SYMBOL_PREFIXast_sip_session_defer_termination; - LINKER_SYMBOL_PREFIXast_sip_session_defer_termination_cancel; - LINKER_SYMBOL_PREFIXast_sip_session_end_if_deferred; - LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler; - LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler; - LINKER_SYMBOL_PREFIXast_sip_session_register_supplement; - LINKER_SYMBOL_PREFIXast_sip_session_unregister_supplement; - LINKER_SYMBOL_PREFIXast_sip_session_alloc_datastore; - LINKER_SYMBOL_PREFIXast_sip_session_add_datastore; - LINKER_SYMBOL_PREFIXast_sip_session_get_datastore; - LINKER_SYMBOL_PREFIXast_sip_session_remove_datastore; - LINKER_SYMBOL_PREFIXast_sip_session_get_identity; - LINKER_SYMBOL_PREFIXast_sip_session_refresh; - LINKER_SYMBOL_PREFIXast_sip_session_send_response; - LINKER_SYMBOL_PREFIXast_sip_session_send_request; - LINKER_SYMBOL_PREFIXast_sip_session_create_invite; - LINKER_SYMBOL_PREFIXast_sip_session_create_outgoing; - LINKER_SYMBOL_PREFIXast_sip_session_suspend; - LINKER_SYMBOL_PREFIXast_sip_session_unsuspend; + LINKER_SYMBOL_PREFIXast_sip_session_*; LINKER_SYMBOL_PREFIXast_sip_dialog_get_session; - LINKER_SYMBOL_PREFIXast_sip_session_resume_reinvite; LINKER_SYMBOL_PREFIXast_sip_channel_pvt_alloc; local: *; diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c index 6019412b00fc54f91fd6e7faa4bde8609c176238..a032bb12fb43a82e3d626dd37da5acf002bf30b9 100644 --- a/res/res_pjsip_t38.c +++ b/res/res_pjsip_t38.c @@ -43,6 +43,8 @@ #include "asterisk/netsock2.h" #include "asterisk/channel.h" #include "asterisk/acl.h" +#include "asterisk/stream.h" +#include "asterisk/format_cache.h" #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_session.h" @@ -63,11 +65,16 @@ struct t38_state { struct ast_control_t38_parameters their_parms; /*! \brief Timer entry for automatically rejecting an inbound re-invite */ pj_timer_entry timer; + /*! Preserved media state for when T.38 ends */ + struct ast_sip_session_media_state *media_state; }; /*! \brief Destructor for T.38 state information */ static void t38_state_destroy(void *obj) { + struct t38_state *state = obj; + + ast_sip_session_media_state_free(state->media_state); ast_free(obj); } @@ -195,7 +202,7 @@ static int t38_automatic_reject(void *obj) { RAII_VAR(struct ast_sip_session *, session, obj, ao2_cleanup); RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "t38"), ao2_cleanup); - RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(session->media, "image", OBJ_KEY), ao2_cleanup); + struct ast_sip_session_media *session_media; if (!datastore) { return 0; @@ -204,6 +211,7 @@ static int t38_automatic_reject(void *obj) ast_debug(2, "Automatically rejecting T.38 request on channel '%s'\n", session->channel ? ast_channel_name(session->channel) : "<gone>"); + session_media = session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; t38_change_state(session, session_media, datastore->data, T38_REJECTED); ast_sip_session_resume_reinvite(session); @@ -259,7 +267,6 @@ static int t38_initialize_session(struct ast_sip_session *session, struct ast_si return -1; } - ast_channel_set_fd(session->channel, 5, ast_udptl_fd(session_media->udptl)); ast_udptl_set_error_correction_scheme(session_media->udptl, session->endpoint->media.t38.error_correction); ast_udptl_setnat(session_media->udptl, session->endpoint->media.t38.nat); ast_udptl_set_far_max_datagram(session_media->udptl, session->endpoint->media.t38.maxdatagram); @@ -271,19 +278,15 @@ static int t38_initialize_session(struct ast_sip_session *session, struct ast_si /*! \brief Callback for when T.38 reinvite SDP is created */ static int t38_reinvite_sdp_cb(struct ast_sip_session *session, pjmedia_sdp_session *sdp) { - int stream; - - /* Move the image media stream to the front and have it as the only stream, pjmedia will fill in - * dummy streams for the rest - */ - for (stream = 0; stream < sdp->media_count; ++stream) { - if (!pj_strcmp2(&sdp->media[stream]->desc.media, "image")) { - sdp->media[0] = sdp->media[stream]; - sdp->media_count = 1; - break; - } + struct t38_state *state; + + state = t38_state_get_or_alloc(session); + if (!state) { + return -1; } + state->media_state = ast_sip_session_media_state_clone(session->active_media_state); + return 0; } @@ -292,34 +295,109 @@ static int t38_reinvite_response_cb(struct ast_sip_session *session, pjsip_rx_da { struct pjsip_status_line status = rdata->msg_info.msg->line.status; struct t38_state *state; - RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup); + struct ast_sip_session_media *session_media = NULL; if (status.code == 100) { return 0; } - if (!(state = t38_state_get_or_alloc(session)) || - !(session_media = ao2_find(session->media, "image", OBJ_KEY))) { + state = t38_state_get_or_alloc(session); + if (!state) { ast_log(LOG_WARNING, "Received response to T.38 re-invite on '%s' but state unavailable\n", ast_channel_name(session->channel)); return 0; } - t38_change_state(session, session_media, state, (status.code == 200) ? T38_ENABLED : T38_REJECTED); + if (status.code == 200) { + int index; + + session_media = session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; + t38_change_state(session, session_media, state, T38_ENABLED); + + /* Stop all the streams in the stored away active state, they'll go back to being active once + * we reinvite back. + */ + for (index = 0; index < AST_VECTOR_SIZE(&state->media_state->sessions); ++index) { + struct ast_sip_session_media *session_media = AST_VECTOR_GET(&state->media_state->sessions, index); + + if (session_media && session_media->handler && session_media->handler->stream_stop) { + session_media->handler->stream_stop(session_media); + } + } + } else { + session_media = session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; + t38_change_state(session, session_media, state, T38_REJECTED); + + /* Abort this attempt at switching to T.38 by resetting the pending state and freeing our stored away active state */ + ast_sip_session_media_state_free(state->media_state); + state->media_state = NULL; + ast_sip_session_media_state_reset(session->pending_media_state); + } return 0; } +/*! \brief Helper function which creates a media state for strictly T.38 */ +static struct ast_sip_session_media_state *t38_create_media_state(struct ast_sip_session *session) +{ + struct ast_sip_session_media_state *media_state; + struct ast_stream *stream; + struct ast_format_cap *caps; + struct ast_sip_session_media *session_media; + + media_state = ast_sip_session_media_state_alloc(); + if (!media_state) { + return NULL; + } + + media_state->topology = ast_stream_topology_alloc(); + if (!media_state->topology) { + ast_sip_session_media_state_free(media_state); + return NULL; + } + + stream = ast_stream_alloc("t38", AST_MEDIA_TYPE_IMAGE); + if (!stream) { + ast_sip_session_media_state_free(media_state); + return NULL; + } + + ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV); + ast_stream_topology_set_stream(media_state->topology, 0, stream); + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps) { + ast_sip_session_media_state_free(media_state); + return NULL; + } + + ast_format_cap_append(caps, ast_format_t38, 0); + ast_stream_set_formats(stream, caps); + ao2_ref(caps, -1); + + session_media = ast_sip_session_media_state_add(session, media_state, AST_MEDIA_TYPE_IMAGE, 0); + if (!session_media) { + ast_sip_session_media_state_free(media_state); + return NULL; + } + + if (t38_initialize_session(session, session_media)) { + ast_sip_session_media_state_free(media_state); + return NULL; + } + + return media_state; +} + /*! \brief Task for reacting to T.38 control frame */ static int t38_interpret_parameters(void *obj) { RAII_VAR(struct t38_parameters_task_data *, data, obj, ao2_cleanup); const struct ast_control_t38_parameters *parameters = data->frame->data.ptr; struct t38_state *state = t38_state_get_or_alloc(data->session); - RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(data->session->media, "image", OBJ_KEY), ao2_cleanup); + struct ast_sip_session_media *session_media = NULL; - /* Without session media or state we can't interpret parameters */ - if (!session_media || !state) { + if (!state) { return 0; } @@ -329,12 +407,15 @@ static int t38_interpret_parameters(void *obj) /* Negotiation can not take place without a valid max_ifp value. */ if (!parameters->max_ifp) { if (data->session->t38state == T38_PEER_REINVITE) { + session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; t38_change_state(data->session, session_media, state, T38_REJECTED); ast_sip_session_resume_reinvite(data->session); } else if (data->session->t38state == T38_ENABLED) { + session_media = data->session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; t38_change_state(data->session, session_media, state, T38_DISABLED); ast_sip_session_refresh(data->session, NULL, NULL, NULL, - AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1); + AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, state->media_state); + state->media_state = NULL; } break; } else if (data->session->t38state == T38_PEER_REINVITE) { @@ -353,37 +434,46 @@ static int t38_interpret_parameters(void *obj) } state->our_parms.version = MIN(state->our_parms.version, state->their_parms.version); state->our_parms.rate_management = state->their_parms.rate_management; + session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp); t38_change_state(data->session, session_media, state, T38_ENABLED); ast_sip_session_resume_reinvite(data->session); } else if ((data->session->t38state != T38_ENABLED) || ((data->session->t38state == T38_ENABLED) && (parameters->request_response == AST_T38_REQUEST_NEGOTIATE))) { - if (t38_initialize_session(data->session, session_media)) { + struct ast_sip_session_media_state *media_state; + + media_state = t38_create_media_state(data->session); + if (!media_state) { break; } state->our_parms = *parameters; + session_media = media_state->default_session[AST_MEDIA_TYPE_IMAGE]; ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp); t38_change_state(data->session, session_media, state, T38_LOCAL_REINVITE); ast_sip_session_refresh(data->session, NULL, t38_reinvite_sdp_cb, t38_reinvite_response_cb, - AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1); + AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, media_state); } break; case AST_T38_TERMINATED: case AST_T38_REFUSED: case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ if (data->session->t38state == T38_PEER_REINVITE) { + session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; t38_change_state(data->session, session_media, state, T38_REJECTED); ast_sip_session_resume_reinvite(data->session); } else if (data->session->t38state == T38_ENABLED) { + session_media = data->session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; t38_change_state(data->session, session_media, state, T38_DISABLED); - ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1); + ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, state->media_state); + state->media_state = NULL; } break; case AST_T38_REQUEST_PARMS: { /* Application wants remote's parameters re-sent */ struct ast_control_t38_parameters parameters = state->their_parms; if (data->session->t38state == T38_PEER_REINVITE) { + session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE]; parameters.max_ifp = ast_udptl_get_far_max_ifp(session_media->udptl); parameters.request_response = AST_T38_REQUEST_NEGOTIATE; ast_queue_control_data(data->session->channel, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); @@ -397,67 +487,27 @@ static int t38_interpret_parameters(void *obj) return 0; } -/*! \brief Frame hook callback for writing */ -static struct ast_frame *t38_framehook_write(struct ast_channel *chan, - struct ast_sip_session *session, struct ast_frame *f) +/*! \brief Frame hook callback for T.38 related stuff */ +static struct ast_frame *t38_framehook(struct ast_channel *chan, struct ast_frame *f, + enum ast_framehook_event event, void *data) { + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + + if (event != AST_FRAMEHOOK_EVENT_WRITE) { + return f; + } + if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS && - session->endpoint->media.t38.enabled) { - struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(session, f); + channel->session->endpoint->media.t38.enabled) { + struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(channel->session, f); if (!data) { return f; } - if (ast_sip_push_task(session->serializer, t38_interpret_parameters, data)) { + if (ast_sip_push_task(channel->session->serializer, t38_interpret_parameters, data)) { ao2_ref(data, -1); } - } else if (f->frametype == AST_FRAME_MODEM) { - struct ast_sip_session_media *session_media; - - /* Avoid deadlock between chan and the session->media container lock */ - ast_channel_unlock(chan); - session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY); - ast_channel_lock(chan); - if (session_media && session_media->udptl) { - ast_udptl_write(session_media->udptl, f); - } - ao2_cleanup(session_media); - } - - return f; -} - -/*! \brief Frame hook callback for reading */ -static struct ast_frame *t38_framehook_read(struct ast_channel *chan, - struct ast_sip_session *session, struct ast_frame *f) -{ - if (ast_channel_fdno(session->channel) == 5) { - struct ast_sip_session_media *session_media; - - /* Avoid deadlock between chan and the session->media container lock */ - ast_channel_unlock(chan); - session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY); - ast_channel_lock(chan); - if (session_media && session_media->udptl) { - f = ast_udptl_read(session_media->udptl); - } - ao2_cleanup(session_media); - } - - return f; -} - -/*! \brief Frame hook callback for T.38 related stuff */ -static struct ast_frame *t38_framehook(struct ast_channel *chan, struct ast_frame *f, - enum ast_framehook_event event, void *data) -{ - struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); - - if (event == AST_FRAMEHOOK_EVENT_READ) { - f = t38_framehook_read(chan, channel->session, f); - } else if (event == AST_FRAMEHOOK_EVENT_WRITE) { - f = t38_framehook_write(chan, channel->session, f); } return f; @@ -476,7 +526,7 @@ static void t38_masq(void *data, int framehook_id, static int t38_consume(void *data, enum ast_frame_type type) { - return 0; + return (type == AST_FRAME_CONTROL) ? 1 : 0; } static const struct ast_datastore_info t38_framehook_datastore = { @@ -676,11 +726,13 @@ static enum ast_sip_session_sdp_stream_defer defer_incoming_sdp_stream( } /*! \brief Function which negotiates an incoming media stream */ -static 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) +static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, + struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, + int index, struct ast_stream *asterisk_stream) { struct t38_state *state; char host[NI_MAXHOST]; + pjmedia_sdp_media *stream = sdp->media[index]; RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free); if (!session->endpoint->media.t38.enabled) { @@ -720,7 +772,7 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct /*! \brief Function which creates an outgoing stream */ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, - struct pjmedia_sdp_session *sdp) + struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, struct ast_stream *stream) { pj_pool_t *pool = session->inv_session->pool_prov; static const pj_str_t STR_IN = { "IN", 2 }; @@ -758,7 +810,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as return -1; } - media->desc.media = pj_str(session_media->stream_type); + pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type)); media->desc.transport = STR_UDPTL; if (ast_strlen_zero(session->endpoint->media.address)) { @@ -826,12 +878,31 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as return 1; } +static struct ast_frame *media_session_udptl_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media) +{ + if (!session_media->udptl) { + return &ast_null_frame; + } + + return ast_udptl_read(session_media->udptl); +} + +static int media_session_udptl_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame) +{ + if (!session_media->udptl) { + return 0; + } + + return ast_udptl_write(session_media->udptl, frame); +} + /*! \brief Function which applies a negotiated stream */ -static 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, - const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream) +static 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_session *remote, int index, struct ast_stream *asterisk_stream) { RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free); + pjmedia_sdp_media *remote_stream = remote->media[index]; char host[NI_MAXHOST]; struct t38_state *state; @@ -858,6 +929,10 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a t38_interpret_sdp(state, session, session_media, remote_stream); + ast_sip_session_media_set_write_callback(session, session_media, media_session_udptl_write_callback); + ast_sip_session_media_add_read_callback(session, session_media, ast_udptl_fd(session_media->udptl), + media_session_udptl_read_callback); + return 0; }