diff options
Diffstat (limited to 'pjsip/src/pjsua-lib/pjsua_call.c')
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 724 |
1 files changed, 461 insertions, 263 deletions
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 6f14709..7329333 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -1,4 +1,4 @@ -/* $Id: pjsua_call.c 4176 2012-06-23 03:06:52Z nanang $ */ +/* $Id: pjsua_call.c 4411 2013-03-04 04:34:38Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> @@ -106,6 +106,12 @@ static pj_status_t create_sdp_of_call_hold(pjsua_call *call, static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +/* Timer callback to send re-INVITE/UPDATE to lock codec or ICE update */ +static void reinv_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry); + +/* Check and send reinvite for lock codec and ICE update */ +static pj_status_t process_pending_reinvite(pjsua_call *call); + /* * Reset call descriptor. */ @@ -128,6 +134,8 @@ static void reset_call(pjsua_call_id id) call_med->tp_auto_del = PJ_TRUE; } pjsua_call_setting_default(&call->opt); + pj_timer_entry_init(&call->reinv_timer, PJ_FALSE, + (void*)(pj_size_t)id, &reinv_timer_cb); } @@ -179,6 +187,10 @@ pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg) pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub); + /* Add "INFO" in Allow header, for DTMF and video key frame request. */ + pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_ALLOW, + NULL, 1, &pjsip_info_method.name); + return status; } @@ -359,6 +371,7 @@ on_make_call_med_tp_complete(pjsua_call_id call_id, pjsip_dialog *dlg = call->async_call.dlg; unsigned options = 0; pjsip_tx_data *tdata; + pj_bool_t cb_called = PJ_FALSE; pj_status_t status = (info? info->status: PJ_SUCCESS); PJSUA_LOCK(); @@ -387,9 +400,16 @@ on_make_call_med_tp_complete(pjsua_call_id call_id, goto on_error; } - /* pjsua_media_channel_deinit() has been called. */ - if (call->async_call.med_ch_deinit) + /* pjsua_media_channel_deinit() has been called or + * call has been hung up. + */ + if (call->async_call.med_ch_deinit || + call->async_call.call_var.out_call.hangup) + { + PJ_LOG(4,(THIS_FILE, "Call has been hung up or media channel has " + "been deinitialized")); goto on_error; + } /* Create offer */ status = pjsua_media_channel_create_sdp(call->index, dlg->pool, NULL, @@ -476,8 +496,7 @@ on_make_call_med_tp_complete(pjsua_call_id call_id, status = pjsip_inv_send_msg(inv, tdata); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", - status); + cb_called = PJ_TRUE; /* Upon failure to send first request, the invite * session would have been cleared. @@ -487,6 +506,7 @@ on_make_call_med_tp_complete(pjsua_call_id call_id, } /* Done. */ + call->med_ch_cb = NULL; pjsip_dlg_dec_lock(dlg); PJSUA_UNLOCK(); @@ -494,8 +514,11 @@ on_make_call_med_tp_complete(pjsua_call_id call_id, return PJ_SUCCESS; on_error: - if (inv == NULL && call_id != -1 && pjsua_var.ua_cfg.cb.on_call_state) + if (inv == NULL && call_id != -1 && !cb_called && + pjsua_var.ua_cfg.cb.on_call_state) + { (*pjsua_var.ua_cfg.cb.on_call_state)(call_id, NULL); + } if (dlg) { /* This may destroy the dialog */ @@ -511,6 +534,10 @@ on_error: pjsua_media_channel_deinit(call_id); } + call->med_ch_cb = NULL; + + pjsua_check_snd_dev_idle(); + PJSUA_UNLOCK(); return status; } @@ -547,35 +574,23 @@ static pj_status_t apply_call_setting(pjsua_call *call, pj_assert(opt->vid_cnt == 0); #endif + call->opt = *opt; + /* If call is established, reinit media channel */ if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) { - pjsua_call_setting old_opt; + pjsip_role_e role = rem_sdp? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC; pj_status_t status; - old_opt = call->opt; - call->opt = *opt; - - /* Reinit media channel when media count is changed or we are the - * answerer (as remote offer may 'extremely' modify the existing - * media session, e.g: media type order). - */ - if (rem_sdp || - opt->aud_cnt!=old_opt.aud_cnt || opt->vid_cnt!=old_opt.vid_cnt) - { - pjsip_role_e role = rem_sdp? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC; - status = pjsua_media_channel_init(call->index, role, - call->secure_level, - call->inv->pool_prov, - rem_sdp, NULL, - PJ_FALSE, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error re-initializing media channel", - status); - return status; - } + status = pjsua_media_channel_init(call->index, role, + call->secure_level, + call->inv->pool_prov, + rem_sdp, NULL, + PJ_FALSE, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error re-initializing media channel", + status); + return status; } - } else { - call->opt = *opt; } return PJ_SUCCESS; @@ -811,8 +826,8 @@ static pj_status_t process_incoming_call_replace(pjsua_call *call, { pjsip_inv_session *replaced_inv; struct pjsua_call *replaced_call; - pjsip_tx_data *tdata; - pj_status_t status; + pjsip_tx_data *tdata = NULL; + pj_status_t status = PJ_SUCCESS; /* Get the invite session in the dialog */ replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg); @@ -825,12 +840,29 @@ static pj_status_t process_incoming_call_replace(pjsua_call *call, pjsua_var.ua_cfg.cb.on_call_replaced(replaced_call->index, call->index); - PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with 200/OK", - call->index)); + if (replaced_call->inv->state <= PJSIP_INV_STATE_EARLY && + replaced_call->inv->role != PJSIP_ROLE_UAC) + { + if (replaced_call->last_code > 100 && replaced_call->last_code < 200) + { + pjsip_status_code code = replaced_call->last_code; + pj_str_t *text = &replaced_call->last_text; + + PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with %d/%.*s", + call->index, code, text->slen, text->ptr)); + + /* Answer the new call with last response in the replaced call */ + status = pjsip_inv_answer(call->inv, code, text, NULL, &tdata); + } + } else { + PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with 200/OK", + call->index)); + + /* Answer the new call with 200 response */ + status = pjsip_inv_answer(call->inv, 200, NULL, NULL, &tdata); + } - /* Answer the new call with 200 response */ - status = pjsip_inv_answer(call->inv, 200, NULL, NULL, &tdata); - if (status == PJ_SUCCESS) + if (status == PJ_SUCCESS && tdata) status = pjsip_inv_send_msg(call->inv, tdata); if (status != PJ_SUCCESS) @@ -997,7 +1029,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) int acc_id; pjsua_call *call; int call_id = -1; - int sip_err_code; + int sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR; pjmedia_sdp_session *offer=NULL; pj_status_t status; @@ -1182,7 +1214,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) options |= PJSIP_INV_SUPPORT_TIMER; if (pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_MANDATORY) options |= PJSIP_INV_REQUIRE_100REL; - if (pjsua_var.media_cfg.enable_ice) + if (pjsua_var.acc[acc_id].cfg.ice_cfg.enable_ice) options |= PJSIP_INV_SUPPORT_ICE; if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED) options |= PJSIP_INV_REQUIRE_TIMER; @@ -1488,6 +1520,7 @@ pj_status_t acquire_call(const char *title, pj_bool_t has_pjsua_lock = PJ_FALSE; pj_status_t status = PJ_SUCCESS; pj_time_val time_start, timeout; + pjsip_dialog *dlg = NULL; pj_gettimeofday(&time_start); timeout.sec = 0; @@ -1515,14 +1548,18 @@ pj_status_t acquire_call(const char *title, has_pjsua_lock = PJ_TRUE; call = &pjsua_var.calls[call_id]; + if (call->inv) + dlg = call->inv->dlg; + else + dlg = call->async_call.dlg; - if (call->inv == NULL) { + if (dlg == NULL) { PJSUA_UNLOCK(); PJ_LOG(3,(THIS_FILE, "Invalid call_id %d in %s", call_id, title)); return PJSIP_ESESSIONTERMINATED; } - status = pjsip_dlg_try_inc_lock(call->inv->dlg); + status = pjsip_dlg_try_inc_lock(dlg); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); pj_thread_sleep(retry/10); @@ -1547,7 +1584,7 @@ pj_status_t acquire_call(const char *title, } *p_call = call; - *p_dlg = call->inv->dlg; + *p_dlg = dlg; return PJ_SUCCESS; } @@ -1992,7 +2029,7 @@ PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id, * answer code 183 or 2xx is issued */ if (!call->med_ch_cb && - (call->opt_inited || (code==183 && code/100==2)) && + (call->opt_inited || (code==183 || code/100==2)) && (!call->inv->neg || pjmedia_sdp_neg_get_state(call->inv->neg) == PJMEDIA_SDP_NEG_STATE_NULL)) @@ -2120,6 +2157,25 @@ PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id, if (status != PJ_SUCCESS) goto on_return; + /* If media transport creation is not yet completed, we will hangup + * the call in the media transport creation callback instead. + */ + if (call->med_ch_cb && !call->inv) { + PJ_LOG(4,(THIS_FILE, "Pending call %d hangup upon completion " + "of media transport", call_id)); + call->async_call.call_var.out_call.hangup = PJ_TRUE; + if (code == 0) + call->last_code = PJSIP_SC_REQUEST_TERMINATED; + else + call->last_code = (pjsip_status_code)code; + if (reason) { + pj_strncpy(&call->last_text, reason, + sizeof(call->last_text_buf_)); + } + + goto on_return; + } + if (code==0) { if (call->inv->state == PJSIP_INV_STATE_CONFIRMED) code = PJSIP_SC_OK; @@ -2156,11 +2212,10 @@ PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id, goto on_return; } - /* 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; + /* Stop reinvite timer, if it is active */ + if (call->reinv_timer.id) { + pjsua_cancel_timer(&call->reinv_timer); + call->reinv_timer.id = PJ_FALSE; } on_return: @@ -2337,7 +2392,7 @@ PJ_DEF(pj_status_t) pjsua_call_reinvite2(pjsua_call_id call_id, goto on_return; } - if ((call->opt.flag & PJSUA_CALL_UPDATE_CONTACT) & + if ((call->opt.flag & PJSUA_CALL_UPDATE_CONTACT) && pjsua_acc_is_valid(call->acc_id)) { new_contact = &pjsua_var.acc[call->acc_id].contact; @@ -2433,7 +2488,7 @@ PJ_DEF(pj_status_t) pjsua_call_update2(pjsua_call_id call_id, goto on_return; } - if ((call->opt.flag & PJSUA_CALL_UPDATE_CONTACT) & + if ((call->opt.flag & PJSUA_CALL_UPDATE_CONTACT) && pjsua_acc_is_valid(call->acc_id)) { new_contact = &pjsua_var.acc[call->acc_id].contact; @@ -2850,12 +2905,8 @@ PJ_DEF(void) pjsua_call_hangup_all(void) } -/* Proto */ -static pj_status_t perform_lock_codec(pjsua_call *call); - -/* Timer callback to send re-INVITE or UPDATE to lock codec */ -static void reinv_timer_cb(pj_timer_heap_t *th, - pj_timer_entry *entry) +/* Timer callback to send re-INVITE/UPDATE to lock codec or ICE update */ +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; @@ -2864,21 +2915,27 @@ static void reinv_timer_cb(pj_timer_heap_t *th, PJ_UNUSED_ARG(th); - pjsua_var.calls[call_id].lock_codec.reinv_timer.id = PJ_FALSE; + pjsua_var.calls[call_id].reinv_timer.id = PJ_FALSE; + + pj_log_push_indent(); status = acquire_call("reinv_timer_cb()", call_id, &call, &dlg); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); return; + } - status = perform_lock_codec(call); + process_pending_reinvite(call); pjsip_dlg_dec_lock(dlg); + + pj_log_pop_indent(); } /* 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) + const pj_str_t *fmt) { const pj_str_t STR_TEL = {"telephone-event", 15}; unsigned pt; @@ -2911,62 +2968,62 @@ static pj_bool_t is_non_av_fmt(const pjmedia_sdp_media *m, } -/* Send re-INVITE or UPDATE with new SDP offer to select only one codec - * out of several codecs presented by callee in his answer. +/* Schedule check for the need of re-INVITE/UPDATE after media update, cases: + * - lock codec if remote answerer has given us more than one codecs + * - update ICE default transport address if it has changed after ICE + * connectivity check. */ -static pj_status_t perform_lock_codec(pjsua_call *call) +void pjsua_call_schedule_reinvite_check(pjsua_call *call, unsigned delay_ms) +{ + pj_time_val delay; + + /* Stop reinvite timer, if it is active */ + if (call->reinv_timer.id) + pjsua_cancel_timer(&call->reinv_timer); + + delay.sec = 0; + delay.msec = delay_ms; + pj_time_val_normalize(&delay); + call->reinv_timer.id = PJ_TRUE; + pjsua_schedule_timer(&call->reinv_timer, &delay); +} + + +/* Check if lock codec is needed */ +static pj_bool_t check_lock_codec(pjsua_call *call) { - const pj_str_t STR_UPDATE = {"UPDATE", 6}; - const pjmedia_sdp_session *local_sdp = NULL, *new_sdp; + const pjmedia_sdp_session *local_sdp, *remote_sdp; + pj_bool_t has_mult_fmt = PJ_FALSE; unsigned i; - pj_bool_t rem_can_update; - pj_bool_t need_lock_codec = PJ_FALSE; - pjsip_tx_data *tdata; pj_status_t status; - PJ_ASSERT_RETURN(call->lock_codec.reinv_timer.id==PJ_FALSE, - PJ_EINVALIDOP); + /* Check if lock codec is disabled */ + if (!pjsua_var.acc[call->acc_id].cfg.lock_codec) + return PJ_FALSE; - /* 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) - { - return PJMEDIA_SDPNEG_EINSTATE; - } + /* Check lock codec retry count */ + if (call->lock_codec.retry_cnt >= LOCK_CODEC_MAX_RETRY) + return PJ_FALSE; - /* Don't do this if call is disconnecting! */ - if (call->inv->state > PJSIP_INV_STATE_CONFIRMED || - call->inv->cause >= 200) - { - return PJ_EINVALIDOP; - } + /* Check if we are the answerer, we shouldn't need to lock codec */ + if (!call->inv->neg || !pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) + return PJ_FALSE; - /* Verify if another SDP negotiation has been completed by comparing - * the SDP version. - */ + /* Check if remote answerer has given us more than one codecs. */ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); if (status != PJ_SUCCESS) - return status; - if (local_sdp->origin.version > call->lock_codec.sdp_ver) - return PJMEDIA_SDP_EINVER; - - PJ_LOG(3, (THIS_FILE, "Updating media session to use only one codec..")); - - /* 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. - */ - new_sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, local_sdp); + return PJ_FALSE; + status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); + if (status != PJ_SUCCESS) + return PJ_FALSE; - for (i = 0; i < call->med_cnt; ++i) { - unsigned j = 0, codec_cnt = 0; - const pjmedia_sdp_media *ref_m; - pjmedia_sdp_media *m; + for (i = 0; i < call->med_cnt && !has_mult_fmt; ++i) { pjsua_call_media *call_med = &call->media[i]; + const pjmedia_sdp_media *rem_m, *loc_m; + unsigned codec_cnt = 0; + unsigned j; - /* Verify if media is deactivated */ + /* Skip this if the media is inactive or error */ if (call_med->state == PJSUA_CALL_MEDIA_NONE || call_med->state == PJSUA_CALL_MEDIA_ERROR || call_med->dir == PJMEDIA_DIR_NONE) @@ -2974,192 +3031,336 @@ static pj_status_t perform_lock_codec(pjsua_call *call) continue; } - ref_m = local_sdp->media[i]; - m = new_sdp->media[i]; - - /* Verify that media must be active. */ - pj_assert(ref_m->desc.port); + /* Remote may answer with less media lines. */ + if (i >= remote_sdp->media_count) + continue; - while (j < m->desc.fmt_count) { - pjmedia_sdp_attr *a; - pj_str_t *fmt = &m->desc.fmt[j]; + rem_m = remote_sdp->media[i]; + loc_m = local_sdp->media[i]; - if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) { - ++j; - continue; - } + /* Verify that media must be active. */ + pj_assert(loc_m->desc.port && rem_m->desc.port); - /* 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, j); - --m->desc.fmt_count; + /* Count the formats in the answer. */ + for (j=0; j<rem_m->desc.fmt_count && codec_cnt <= 1; ++j) { + if (!is_non_av_fmt(rem_m, &rem_m->desc.fmt[j]) && ++codec_cnt > 1) + has_mult_fmt = PJ_TRUE; } - - need_lock_codec |= (ref_m->desc.fmt_count > m->desc.fmt_count); } - /* Last check if SDP trully needs to be updated. It is possible that OA - * negotiations have completed and SDP has changed but we didn't - * increase the SDP version (should not happen!). - */ - if (!need_lock_codec) - return PJ_SUCCESS; + /* Reset retry count when remote answer has one codec */ + if (!has_mult_fmt) + call->lock_codec.retry_cnt = 0; - /* Send UPDATE or re-INVITE */ - rem_can_update = pjsip_dlg_remote_has_cap(call->inv->dlg, - PJSIP_H_ALLOW, - NULL, &STR_UPDATE) == - PJSIP_DIALOG_CAP_SUPPORTED; - if (rem_can_update) { - status = pjsip_inv_update(call->inv, NULL, new_sdp, &tdata); - } else { - status = pjsip_inv_reinvite(call->inv, NULL, new_sdp, &tdata); - } + return has_mult_fmt; +} - if (status==PJ_EINVALIDOP && - ++call->lock_codec.retry_cnt <= LOCK_CODEC_MAX_RETRY) - { - /* Ups, let's reschedule again */ - pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL}; - pj_time_val_normalize(&delay); - call->lock_codec.reinv_timer.id = PJ_TRUE; - pjsip_endpt_schedule_timer(pjsua_var.endpt, - &call->lock_codec.reinv_timer, &delay); - return status; - } else if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error creating UPDATE/re-INVITE to lock codec", - status); - return status; - } +/* Check if ICE setup is complete and if it needs to send reinvite */ +static pj_bool_t check_ice_complete(pjsua_call *call, pj_bool_t *need_reinv) +{ + pj_bool_t ice_need_reinv = PJ_FALSE; + pj_bool_t ice_complete = PJ_TRUE; + unsigned i; - /* Send the UPDATE/re-INVITE request */ - status = pjsip_inv_send_msg(call->inv, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error sending UPDATE/re-INVITE in lock codec", - status); - return status; - } + /* Check if ICE setup is complete and if it needs reinvite */ + for (i = 0; i < call->med_cnt; ++i) { + pjsua_call_media *call_med = &call->media[i]; + pjmedia_transport_info tpinfo; + pjmedia_ice_transport_info *ice_info; + + if (call_med->tp_st == PJSUA_MED_TP_NULL || + call_med->tp_st == PJSUA_MED_TP_DISABLED || + call_med->state == PJSUA_CALL_MEDIA_ERROR) + { + continue; + } + + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + ice_info = (pjmedia_ice_transport_info*) + pjmedia_transport_info_get_spc_info( + &tpinfo, PJMEDIA_TRANSPORT_TYPE_ICE); + + /* Check if ICE is active */ + if (!ice_info || !ice_info->active) + continue; - return status; + /* Check if ICE setup not completed yet */ + if (ice_info->sess_state < PJ_ICE_STRANS_STATE_RUNNING) { + ice_complete = PJ_FALSE; + break; + } + + /* Check if ICE needs to send reinvite */ + if (!ice_need_reinv && + ice_info->sess_state == PJ_ICE_STRANS_STATE_RUNNING && + ice_info->role == PJ_ICE_SESS_ROLE_CONTROLLING) + { + pjsua_ice_config *cfg=&pjsua_var.acc[call->acc_id].cfg.ice_cfg; + if ((cfg->ice_always_update && !call->reinv_ice_sent) || + pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name, + &call_med->rtp_addr)) + { + ice_need_reinv = PJ_TRUE; + } + } + } + + if (ice_complete && need_reinv) + *need_reinv = ice_need_reinv; + + return ice_complete; } -/* 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) +/* Check and send reinvite for lock codec and ICE update */ +static pj_status_t process_pending_reinvite(pjsua_call *call) { + const pj_str_t ST_UPDATE = {"UPDATE", 6}; + pj_pool_t *pool = call->inv->pool_prov; pjsip_inv_session *inv = call->inv; - const pjmedia_sdp_session *local_sdp, *remote_sdp; - pj_time_val delay = {0, 0}; - const pj_str_t st_update = {"UPDATE", 6}; + pj_bool_t ice_need_reinv; + pj_bool_t ice_completed; + pj_bool_t need_lock_codec; + pj_bool_t rem_can_update; + pjmedia_sdp_session *new_offer; + pjsip_tx_data *tdata; unsigned i; - pj_bool_t has_mult_fmt = PJ_FALSE; pj_status_t 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; + /* Verify if another SDP negotiation is in progress, e.g: session timer + * or another re-INVITE. + */ + if (inv==NULL || inv->neg==NULL || + pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) + { + return PJMEDIA_SDPNEG_EINSTATE; } - /* Skip this if we are the answerer */ - if (!inv->neg || !pjmedia_sdp_neg_was_answer_remote(inv->neg)) { - return PJ_SUCCESS; + /* Don't do this if call is disconnecting! */ + if (inv->state > PJSIP_INV_STATE_CONFIRMED || inv->cause >= 200) + { + return PJ_EINVALIDOP; } /* Delay this when the SDP negotiation done in call state EARLY and * remote does not support UPDATE method. */ if (inv->state == PJSIP_INV_STATE_EARLY && - pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &st_update)!= + pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &ST_UPDATE)!= PJSIP_DIALOG_CAP_SUPPORTED) { - call->lock_codec.pending = PJ_TRUE; - return PJ_SUCCESS; + call->reinv_pending = PJ_TRUE; + return PJ_EPENDING; } - status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp); - if (status != PJ_SUCCESS) - return status; - status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp); - if (status != PJ_SUCCESS) - return status; + /* Check if ICE setup is complete and if it needs reinvite */ + ice_completed = check_ice_complete(call, &ice_need_reinv); + if (!ice_completed) + return PJ_EPENDING; - /* Find multiple codecs answer in all media */ - for (i = 0; i < call->med_cnt; ++i) { - pjsua_call_media *call_med = &call->media[i]; - const pjmedia_sdp_media *rem_m, *loc_m; - unsigned codec_cnt = 0; + /* Check if we need to lock codec */ + need_lock_codec = check_lock_codec(call); - /* Skip this if the media is inactive or error */ - if (call_med->state == PJSUA_CALL_MEDIA_NONE || - call_med->state == PJSUA_CALL_MEDIA_ERROR || - call_med->dir == PJMEDIA_DIR_NONE) - { - continue; - } + /* Check if reinvite is really needed */ + if (!need_lock_codec && !ice_need_reinv) + return PJ_SUCCESS; - /* Remote may answer with less media lines. */ - if (i >= remote_sdp->media_count) - continue; + + /* Okay! So we need to send re-INVITE/UPDATE */ - rem_m = remote_sdp->media[i]; - loc_m = local_sdp->media[i]; + /* Check if remote support UPDATE */ + rem_can_update = pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, + &ST_UPDATE) == + PJSIP_DIALOG_CAP_SUPPORTED; - /* Verify that media must be active. */ - pj_assert(loc_m->desc.port && rem_m->desc.port); + /* Logging stuff */ + { + const char *ST_ICE_UPDATE = "ICE transport address after " + "ICE negotiation"; + const char *ST_LOCK_CODEC = "media session to use only one codec"; + PJ_LOG(4,(THIS_FILE, "Call %d sending %s for updating %s%s%s", + call->index, + (rem_can_update? "UPDATE" : "re-INVITE"), + (ice_need_reinv? ST_ICE_UPDATE : ST_LOCK_CODEC), + (ice_need_reinv && need_lock_codec? " and " : ""), + (ice_need_reinv && need_lock_codec? ST_LOCK_CODEC : "") + )); + } + + /* Generate SDP re-offer */ + status = pjsua_media_channel_create_sdp(call->index, pool, NULL, + &new_offer, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + return status; + } - /* Count the formats in the answer. */ - if (rem_m->desc.fmt_count==1) { - codec_cnt = 1; - } else { - unsigned j; - for (j=0; j<rem_m->desc.fmt_count && codec_cnt <= 1; ++j) { - if (!is_non_av_fmt(rem_m, &rem_m->desc.fmt[j])) - ++codec_cnt; - } + /* Update the new offer so it contains only a codec. Note that + * SDP nego has removed unmatched codecs from the offer and the codec + * order in the offer has been matched to the answer, so we'll override + * the codecs in the just generated SDP with the ones from the active + * local SDP and leave just one codec for the next SDP re-offer. + */ + if (need_lock_codec) { + const pjmedia_sdp_session *ref_sdp; + + /* Get local active SDP as reference */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &ref_sdp); + if (status != PJ_SUCCESS) + return status; + + /* Verify media count. Note that remote may add/remove media line + * in the answer. When answer has less media, it must have been + * handled by pjsua_media_channel_update() as disabled media. + * When answer has more media, it must have been ignored (treated + * as non-exist) anywhere. Local media count should not be updated + * at this point, as modifying media count operation (i.e: reinvite, + * update, vid_set_strm) is currently blocking, protected with + * dialog mutex, and eventually reset SDP nego state to LOCAL OFFER. + */ + if (call->med_cnt != ref_sdp->media_count || + ref_sdp->media_count != new_offer->media_count) + { + /* Anyway, just in case, let's just return error */ + return PJMEDIA_SDPNEG_EINSTATE; } - if (codec_cnt > 1) { - has_mult_fmt = PJ_TRUE; - break; + for (i = 0; i < call->med_cnt; ++i) { + unsigned j, codec_cnt = 0; + const pjmedia_sdp_media *ref_m = ref_sdp->media[i]; + pjmedia_sdp_media *m = new_offer->media[i]; + pjsua_call_media *call_med = &call->media[i]; + + /* Verify if media is deactivated */ + if (call_med->state == PJSUA_CALL_MEDIA_NONE || + call_med->state == PJSUA_CALL_MEDIA_ERROR || + call_med->dir == PJMEDIA_DIR_NONE) + { + continue; + } + + /* Reset formats */ + m->desc.fmt_count = 0; + pjmedia_sdp_attr_remove_all(&m->attr_count, m->attr, "rtpmap"); + pjmedia_sdp_attr_remove_all(&m->attr_count, m->attr, "fmtp"); + + /* Copy only the first format + any non-AV formats from + * the active local SDP. + */ + for (j = 0; j < ref_m->desc.fmt_count; ++j) { + const pj_str_t *fmt = &ref_m->desc.fmt[j]; + + if (is_non_av_fmt(ref_m, fmt) || (++codec_cnt == 1)) { + pjmedia_sdp_attr *a; + + m->desc.fmt[m->desc.fmt_count++] = *fmt; + a = pjmedia_sdp_attr_find2(ref_m->attr_count, ref_m->attr, + "rtpmap", fmt); + if (a) pjmedia_sdp_attr_add(&m->attr_count, m->attr, a); + a = pjmedia_sdp_attr_find2(ref_m->attr_count, ref_m->attr, + "fmtp", fmt); + if (a) pjmedia_sdp_attr_add(&m->attr_count, m->attr, a); + } + } } } - /* Each media in the answer already contains single codec. */ - if (!has_mult_fmt) { - call->lock_codec.retry_cnt = 0; - return PJ_SUCCESS; - } + /* Put back original direction and "c=0.0.0.0" line */ + { + const pjmedia_sdp_session *cur_sdp; + + /* Get local active SDP */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &cur_sdp); + if (status != PJ_SUCCESS) + return status; - /* Remote keeps answering with multiple codecs, let's just give up - * locking codec to avoid infinite retry loop. - */ - if (++call->lock_codec.retry_cnt > LOCK_CODEC_MAX_RETRY) - return PJ_SUCCESS; + /* Make sure media count has not been changed */ + if (call->med_cnt != cur_sdp->media_count) + return PJMEDIA_SDPNEG_EINSTATE; + + for (i = 0; i < call->med_cnt; ++i) { + const pjmedia_sdp_media *m = cur_sdp->media[i]; + pjmedia_sdp_media *new_m = new_offer->media[i]; + pjsua_call_media *call_med = &call->media[i]; + pjmedia_sdp_attr *a = NULL; + + /* Update direction to the current dir */ + pjmedia_sdp_media_remove_all_attr(new_m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(new_m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(new_m, "recvonly"); + pjmedia_sdp_media_remove_all_attr(new_m, "inactive"); + + if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING) { + a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL); + } else if (call_med->dir == PJMEDIA_DIR_ENCODING) { + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + } else if (call_med->dir == PJMEDIA_DIR_DECODING) { + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + } else { + const pjmedia_sdp_conn *conn; + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + + /* Also check if the original c= line address is zero */ + conn = m->conn; + if (!conn) + conn = cur_sdp->conn; + if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 || + pj_strcmp2(&conn->addr, "0")==0) + { + if (!new_m->conn) { + new_m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + } + + if (pj_strcmp2(&new_m->conn->addr, "0.0.0.0")) { + new_m->conn->net_type = pj_str("IN"); + new_m->conn->addr_type = pj_str("IP4"); + new_m->conn->addr = pj_str("0.0.0.0"); + } + } + } - PJ_LOG(4, (THIS_FILE, "Got answer with multiple codecs, scheduling " - "updating media session to use only one codec..")); + pj_assert(a); + pjmedia_sdp_media_add_attr(new_m, a); + } + } - call->lock_codec.sdp_ver = local_sdp->origin.version; + + if (rem_can_update) { + status = pjsip_inv_update(inv, NULL, new_offer, &tdata); + } else { + status = pjsip_inv_reinvite(inv, NULL, new_offer, &tdata); + } - /* Can't send UPDATE or re-INVITE now, so just schedule it immediately. - * See: https://trac.pjsip.org/repos/ticket/1149 - */ - 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); + if (status==PJ_EINVALIDOP && + ++call->lock_codec.retry_cnt < LOCK_CODEC_MAX_RETRY) + { + /* Ups, let's reschedule again */ + pjsua_call_schedule_reinvite_check(call, LOCK_CODEC_RETRY_INTERVAL); + return PJ_SUCCESS; + } else if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating UPDATE/re-INVITE", + status); + return status; + } + /* Send the UPDATE/re-INVITE request */ + status = pjsip_inv_send_msg(inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error sending UPDATE/re-INVITE", + status); + return status; + } + + /* Update flags */ + if (ice_need_reinv) + call->reinv_ice_sent = PJ_TRUE; + if (need_lock_codec) + ++call->lock_codec.retry_cnt; + return PJ_SUCCESS; } + /* * This callback receives notification from invite session when the * session state has changed. @@ -3194,16 +3395,12 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv, case PJSIP_INV_STATE_CONFIRMED: pj_gettimeofday(&call->conn_time); - /* See if lock codec was pended as media update was done in the + /* See if auto reinvite was pended as media update was done in the * EARLY state and remote does not support UPDATE. */ - if (call->lock_codec.pending) { - pj_status_t status; - status = lock_codec(call); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to lock codec", status); - } - call->lock_codec.pending = PJ_FALSE; + if (call->reinv_pending) { + call->reinv_pending = PJ_FALSE; + pjsua_call_schedule_reinvite_check(call, 0); } break; case PJSIP_INV_STATE_DISCONNECTED: @@ -3225,11 +3422,10 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv, 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; + /* Stop reinvite timer, if it is active */ + if (call->reinv_timer.id) { + pjsua_cancel_timer(&call->reinv_timer); + call->reinv_timer.id = PJ_FALSE; } break; default: @@ -3307,6 +3503,15 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv, } } + /* Ticket #1627: Invoke on_call_tsx_state() when call is disconnected. */ + if (inv->state == PJSIP_INV_STATE_DISCONNECTED && + e->type == PJSIP_EVENT_TSX_STATE && + call->inv && + pjsua_var.ua_cfg.cb.on_call_tsx_state) + { + (*pjsua_var.ua_cfg.cb.on_call_tsx_state)(call->index, + e->body.tsx_state.tsx, e); + } if (pjsua_var.ua_cfg.cb.on_call_state) (*pjsua_var.ua_cfg.cb.on_call_state)(call->index, e); @@ -3512,10 +3717,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, } /* Ticket #476: make sure only one codec is specified in the answer. */ - status = lock_codec(call); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to lock codec", status); - } + pjsua_call_schedule_reinvite_check(call, 0); /* Call application callback, if any */ if (pjsua_var.ua_cfg.cb.on_call_media_state) @@ -4221,11 +4423,7 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, goto on_return; if (call->inv == NULL) { - /* Shouldn't happen. It happens only when we don't terminate the - * server subscription caused by REFER after the call has been - * transfered (and this call has been disconnected), and we - * receive another REFER for this call. - */ + /* Call has been disconnected. */ goto on_return; } |