From dd6dbfe6e6bcfbc11056633ffc5908bf684aab9b Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Tue, 15 Jun 2010 09:56:39 +0000 Subject: Fix #476: - Added lock codec feature to make sure that only one codec is active, by updating media session using UPDATE (if remote supports it) or re-INVITE. - Added few SIPp test scenarios. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3206 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/include/pjsua-lib/pjsua_internal.h | 6 + pjsip/src/pjsua-lib/pjsua_call.c | 262 +++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+) (limited to 'pjsip') diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index 877e6b8e..f20b73de 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -90,6 +90,12 @@ typedef struct pjsua_call char last_text_buf_[128]; /**< Buffer for last_text. */ + struct { + pj_timer_entry reinv_timer;/**< Reinvite retry timer. */ + pjmedia_sdp_session *new_sdp;/**< The new SDP offer. */ + } lock_codec; /**< Data for codec locking when answer + contains multiple codecs. */ + } pjsua_call; diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 85db6d5f..f3ca5cba 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -24,6 +24,12 @@ #define THIS_FILE "pjsua_call.c" +/* Retry interval of sending re-INVITE for locking a codec when remote + * SDP answer contains multiple codec, in milliseconds. + */ +#define LOCK_CODEC_RETRY_INTERVAL 200 + + /* This callback receives notification from invite session when the * session state has changed. */ @@ -1513,6 +1519,13 @@ PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id, return status; } + /* Stop lock codec timer, if it is active */ + if (call->lock_codec.reinv_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer); + call->lock_codec.reinv_timer.id = PJ_FALSE; + } + pjsip_dlg_dec_lock(dlg); return PJ_SUCCESS; @@ -2913,6 +2926,226 @@ PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id, } +/* Timer callback to close sound device */ +static void reinv_timer_cb(pj_timer_heap_t *th, + pj_timer_entry *entry) +{ + pjsua_call_id call_id = (pjsua_call_id)(pj_size_t)entry->user_data; + pjsip_dialog *dlg; + pjsua_call *call; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_UNUSED_ARG(th); + + pjsua_var.calls[call_id].lock_codec.reinv_timer.id = PJ_FALSE; + + status = acquire_call("reinv_timer_cb()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + return; + + /* Verify if another SDP negotiation is in progress, e.g: session timer + * or another re-INVITE. + */ + if (call->inv==NULL || call->inv->neg==NULL || + pjmedia_sdp_neg_get_state(call->inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) + { + goto on_return; + } + + /* Verify if another SDP negotiation has been completed by comparing + * the SDP version. + */ + { + const pjmedia_sdp_session *sdp; + + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp); + if (status == PJ_SUCCESS && + sdp->origin.version > call->lock_codec.new_sdp->origin.version) + { + goto on_return; + } + } + + /* Create re-INVITE with the new offer */ + status = pjsip_inv_reinvite(call->inv, NULL, call->lock_codec.new_sdp, + &tdata); + if (status == PJ_EINVALIDOP) { + /* Ups, let's reschedule again */ + pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL}; + call->lock_codec.reinv_timer.id = PJ_TRUE; + pjsip_endpt_schedule_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer, &delay); + } else if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Failed creating re-INVITE in lock codec", + status); + } + + /* Send the UPDATE/re-INVITE request */ + status = pjsip_inv_send_msg(call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Failed sending re-INVITE in lock codec", + status); + } + +on_return: + pjsip_dlg_dec_lock(dlg); +} + + +/* Check if the specified format can be skipped in counting codecs */ +static pj_bool_t is_non_av_fmt(const pjmedia_sdp_media *m, + const pj_str_t *fmt) +{ + unsigned pt; + + pt = pj_strtoul(fmt); + + /* Check for comfort noise */ + if (pt == PJMEDIA_RTP_PT_CN) + return PJ_TRUE; + + /* Dynamic PT, check the format name */ + if (pt >= 96) { + pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap rtpmap; + + /* Get the format name */ + a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); + if (a && pjmedia_sdp_attr_get_rtpmap(a, &rtpmap)==PJ_SUCCESS) { + /* Check for telephone-event */ + if (pj_stricmp2(&rtpmap.enc_name, "telephone-event")==0) + return PJ_TRUE; + } else { + /* Invalid SDP, should not reach here */ + pj_assert(!"SDP should have been validated!"); + return PJ_TRUE; + } + } + + return PJ_FALSE; +} + + +/* Check if remote answerer has given us more than one codecs. If so, + * create another offer with one codec only to lock down the codec. + */ +static pj_status_t lock_codec(pjsua_call *call) +{ + const pj_str_t st_update = {"UPDATE", 6}; + pjsip_inv_session *inv = call->inv; + const pjmedia_sdp_session *local_sdp; + const pjmedia_sdp_session *remote_sdp; + const pjmedia_sdp_media *rem_m; + pjmedia_sdp_session *new_sdp; + pjmedia_sdp_media *m; + pjsip_tx_data *tdata; + unsigned i, codec_cnt = 0; + pj_status_t status; + + if (!pjmedia_sdp_neg_was_answer_remote(inv->neg)) + return PJ_SUCCESS; + + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); + if (status != PJ_SUCCESS) + return status; + status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); + if (status != PJ_SUCCESS) + return status; + + PJ_ASSERT_RETURN(call->audio_idx>=0 && + call->audio_idx < (int)remote_sdp->media_count, + PJ_EINVALIDOP); + + rem_m = remote_sdp->media[call->audio_idx]; + + /* Check if media is disabled or only one format in the answer. */ + if (rem_m->desc.port==0 || rem_m->desc.fmt_count==1) + return PJ_SUCCESS; + + /* Count the formats in the answer. */ + for (i=0; idesc.fmt_count && codec_cnt <= 1; ++i) { + if (!is_non_av_fmt(rem_m, &rem_m->desc.fmt[i])) + ++codec_cnt; + } + + if (codec_cnt <= 1) { + /* Answer contains single codec. */ + return PJ_SUCCESS; + } + + PJ_LOG(3, (THIS_FILE, "Got answer with multiple codecs, start " + "updating media session to use only one codec..")); + + /* Clone the offer */ + new_sdp = pjmedia_sdp_session_clone(inv->pool_prov, local_sdp); + /* Note that the usage of pool_prov above is risky when locking codec + * delays the re-INVITE (using timer) and there are two SDP negotiations + * done before the re-INVITE. + */ + + /* Update the new offer so it contains only a codec. Note that formats + * order in the offer should have been matched to the answer, so we can + * just directly update the offer without looking-up the answer. + */ + m = new_sdp->media[call->audio_idx]; + codec_cnt = 0; + i = 0; + while (i < m->desc.fmt_count) { + pjmedia_sdp_attr *a; + pj_str_t *fmt = &m->desc.fmt[i]; + + if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) { + ++i; + continue; + } + + /* Remove format */ + a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); + if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); + a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "fmtp", fmt); + if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); + pj_array_erase(m->desc.fmt, sizeof(m->desc.fmt[0]), + m->desc.fmt_count, i); + --m->desc.fmt_count; + } + + /* Send new SDP offer via UPDATE or re-INVITE */ + if (pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &st_update)== + PJSIP_DIALOG_CAP_SUPPORTED) + { + /* Create UPDATE with the new offer */ + status = pjsip_inv_update(inv, NULL, new_sdp, &tdata); + if (status != PJ_SUCCESS) + return status; + + } else { + /* Create re-INVITE with the new offer */ + status = pjsip_inv_reinvite(inv, NULL, new_sdp, &tdata); + if (status == PJ_EINVALIDOP) { + /* Current INVITE transaction is pending, reschedule re-INVITE. */ + pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL}; + + call->lock_codec.new_sdp = new_sdp; + pj_timer_entry_init(&call->lock_codec.reinv_timer, PJ_TRUE, + (void*)(pj_size_t)call->index, + &reinv_timer_cb); + pjsip_endpt_schedule_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer, &delay); + return PJ_SUCCESS; + + } else if (status != PJ_SUCCESS) + return status; + } + + /* Send the UPDATE/re-INVITE request */ + status = pjsip_inv_send_msg(inv, tdata); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + /* * This callback receives notification from invite session when the * session state has changed. @@ -2946,6 +3179,16 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv, break; case PJSIP_INV_STATE_CONFIRMED: pj_gettimeofday(&call->conn_time); + + /* Ticket #476, locking a codec in the media session. */ + { + pj_status_t status; + status = lock_codec(call); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to lock codec", status); + } + } + break; case PJSIP_INV_STATE_DISCONNECTED: pj_gettimeofday(&call->dis_time); @@ -2965,6 +3208,13 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv, pjsip_get_status_text(call->last_code), sizeof(call->last_text_buf_)); } + + /* Stop lock codec timer, if it is active */ + if (call->lock_codec.reinv_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer); + call->lock_codec.reinv_timer.id = PJ_FALSE; + } break; default: call->last_code = (pjsip_status_code) @@ -3169,6 +3419,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, pjsua_call *call; const pjmedia_sdp_session *local_sdp; const pjmedia_sdp_session *remote_sdp; + const pj_str_t st_update = {"UPDATE", 6}; PJSUA_LOCK(); @@ -3240,6 +3491,17 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, return; } + /* Ticket #476, handle the case of early media and remote support UPDATE */ + if (inv->state == PJSIP_INV_STATE_EARLY && + pjmedia_sdp_neg_was_answer_remote(inv->neg) && + pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &st_update)== + PJSIP_DIALOG_CAP_SUPPORTED) + { + status = lock_codec(call); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to lock codec", status); + } + } /* Call application callback, if any */ if (pjsua_var.ua_cfg.cb.on_call_media_state) -- cgit v1.2.3