From 483805f79570115ab95c69698792d238c1719b1b Mon Sep 17 00:00:00 2001 From: Jason Parker Date: Mon, 11 Mar 2013 15:09:56 -0500 Subject: Import pjproject-2.1 --- pjsip/src/pjsua-lib/pjsua_acc.c | 290 +++++++++++--- pjsip/src/pjsua-lib/pjsua_aud.c | 36 +- pjsip/src/pjsua-lib/pjsua_call.c | 724 ++++++++++++++++++++++------------- pjsip/src/pjsua-lib/pjsua_core.c | 178 +++++++-- pjsip/src/pjsua-lib/pjsua_dump.c | 30 +- pjsip/src/pjsua-lib/pjsua_media.c | 767 +++++++++++++++++++++++++++----------- pjsip/src/pjsua-lib/pjsua_pres.c | 76 +++- pjsip/src/pjsua-lib/pjsua_vid.c | 32 +- 8 files changed, 1531 insertions(+), 602 deletions(-) (limited to 'pjsip/src/pjsua-lib') diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index c5278c4..5d7695d 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -1,4 +1,4 @@ -/* $Id: pjsua_acc.c 4185 2012-06-28 14:16:05Z ming $ */ +/* $Id: pjsua_acc.c 4318 2013-01-16 09:51:45Z bennylp $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono @@ -133,6 +133,9 @@ PJ_DEF(void) pjsua_acc_config_dup( pj_pool_t *pool, pjsua_transport_config_dup(pool, &dst->rtp_cfg, &src->rtp_cfg); + pjsua_ice_config_dup(pool, &dst->ice_cfg, &src->ice_cfg); + pjsua_turn_config_dup(pool, &dst->turn_cfg, &src->turn_cfg); + pj_strdup(pool, &dst->ka_data, &src->ka_data); } @@ -283,7 +286,7 @@ static pj_status_t initialize_acc(unsigned acc_id) * contact params. */ #if PJSUA_ADD_ICE_TAGS - if (pjsua_var.media_cfg.enable_ice) { + if (acc_cfg->ice_cfg.enable_ice) { unsigned new_len; pj_str_t new_prm; @@ -348,6 +351,18 @@ static pj_status_t initialize_acc(unsigned acc_id) } } + /* If account's ICE and TURN customization is not set, then + * initialize it with the settings from the global media config. + */ + if (acc->cfg.ice_cfg_use == PJSUA_ICE_CONFIG_USE_DEFAULT) { + pjsua_ice_config_from_media_config(NULL, &acc->cfg.ice_cfg, + &pjsua_var.media_cfg); + } + if (acc->cfg.turn_cfg_use == PJSUA_TURN_CONFIG_USE_DEFAULT) { + pjsua_turn_config_from_media_config(NULL, &acc->cfg.turn_cfg, + &pjsua_var.media_cfg); + } + /* Mark account as valid */ pjsua_var.acc[acc_id].valid = PJ_TRUE; @@ -470,6 +485,10 @@ PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg, /* Otherwise subscribe to MWI, if it's enabled */ if (pjsua_var.acc[id].cfg.mwi_enabled) pjsua_start_mwi(id, PJ_TRUE); + + /* Start publish too */ + if (acc->cfg.publish_enabled) + pjsua_pres_init_publish_acc(id); } pj_log_pop_indent(); @@ -1152,6 +1171,70 @@ PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id, update_reg = PJ_TRUE; } + /* Video settings */ + acc->cfg.vid_in_auto_show = cfg->vid_in_auto_show; + acc->cfg.vid_out_auto_transmit = cfg->vid_out_auto_transmit; + acc->cfg.vid_wnd_flags = cfg->vid_wnd_flags; + acc->cfg.vid_cap_dev = cfg->vid_cap_dev; + acc->cfg.vid_rend_dev = cfg->vid_rend_dev; + acc->cfg.vid_stream_rc_cfg = cfg->vid_stream_rc_cfg; + + /* Media settings */ + if (pj_stricmp(&acc->cfg.rtp_cfg.public_addr, &cfg->rtp_cfg.public_addr) || + pj_stricmp(&acc->cfg.rtp_cfg.bound_addr, &cfg->rtp_cfg.bound_addr)) + { + pjsua_transport_config_dup(acc->pool, &acc->cfg.rtp_cfg, + &cfg->rtp_cfg); + } else { + /* ..to save memory by not using the pool */ + acc->cfg.rtp_cfg = cfg->rtp_cfg; + } + + acc->cfg.ipv6_media_use = cfg->ipv6_media_use; + + /* STUN and Media customization */ + if (acc->cfg.sip_stun_use != cfg->sip_stun_use) { + acc->cfg.sip_stun_use = cfg->sip_stun_use; + update_reg = PJ_TRUE; + } + acc->cfg.media_stun_use = cfg->media_stun_use; + + /* ICE settings */ + acc->cfg.ice_cfg_use = cfg->ice_cfg_use; + switch (acc->cfg.ice_cfg_use) { + case PJSUA_ICE_CONFIG_USE_DEFAULT: + /* Copy ICE settings from media settings so that we don't need to + * check the media config if we look for ICE config. + */ + pjsua_ice_config_from_media_config(NULL, &acc->cfg.ice_cfg, + &pjsua_var.media_cfg); + break; + case PJSUA_ICE_CONFIG_USE_CUSTOM: + pjsua_ice_config_dup(acc->pool, &acc->cfg.ice_cfg, &cfg->ice_cfg); + break; + } + + /* TURN settings */ + acc->cfg.turn_cfg_use = cfg->turn_cfg_use; + switch (acc->cfg.turn_cfg_use) { + case PJSUA_TURN_CONFIG_USE_DEFAULT: + /* Copy TURN settings from media settings so that we don't need to + * check the media config if we look for TURN config. + */ + pjsua_turn_config_from_media_config(NULL, &acc->cfg.turn_cfg, + &pjsua_var.media_cfg); + break; + case PJSUA_TURN_CONFIG_USE_CUSTOM: + pjsua_turn_config_dup(acc->pool, &acc->cfg.turn_cfg, + &cfg->turn_cfg); + break; + } + + acc->cfg.use_srtp = cfg->use_srtp; + + /* Call hold type */ + acc->cfg.call_hold_type = cfg->call_hold_type; + /* Unregister first */ if (unreg_first) { pjsua_acc_set_registration(acc->index, PJ_FALSE); @@ -1174,16 +1257,6 @@ PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id, pjsua_start_mwi(acc_id, PJ_TRUE); } - /* Video settings */ - acc->cfg.vid_in_auto_show = cfg->vid_in_auto_show; - acc->cfg.vid_out_auto_transmit = cfg->vid_out_auto_transmit; - acc->cfg.vid_wnd_flags = cfg->vid_wnd_flags; - acc->cfg.vid_cap_dev = cfg->vid_cap_dev; - acc->cfg.vid_rend_dev = cfg->vid_rend_dev; - - /* Call hold type */ - acc->cfg.call_hold_type = cfg->call_hold_type; - on_return: PJSUA_UNLOCK(); pj_log_pop_indent(); @@ -1515,6 +1588,7 @@ static pj_bool_t acc_check_nat_addr(pjsua_acc *acc, const char *ob = ";ob"; char *tmp; const char *beginquote, *endquote; + char transport_param[32]; int len; /* Enclose IPv6 address in square brackets */ @@ -1525,9 +1599,21 @@ static pj_bool_t acc_check_nat_addr(pjsua_acc *acc, beginquote = endquote = ""; } + /* Don't add transport parameter if it's UDP */ + if (tp->key.type != PJSIP_TRANSPORT_UDP && + tp->key.type != PJSIP_TRANSPORT_UDP6) + { + pj_ansi_snprintf(transport_param, sizeof(transport_param), + ";transport=%s", + pjsip_transport_get_type_name( + (pjsip_transport_type_e)tp->key.type)); + } else { + transport_param[0] = '\0'; + } + tmp = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); len = pj_ansi_snprintf(tmp, PJSIP_MAX_URL_SIZE, - "%.*s", + "%.*s", (int)acc->user_part.slen, acc->user_part.ptr, (acc->user_part.slen? "@" : ""), @@ -1536,7 +1622,7 @@ static pj_bool_t acc_check_nat_addr(pjsua_acc *acc, via_addr->ptr, endquote, rport, - tp->type_name, + transport_param, (int)acc->cfg.contact_uri_params.slen, acc->cfg.contact_uri_params.ptr, (acc->cfg.use_rfc5626? ob: ""), @@ -1776,9 +1862,26 @@ static void update_keep_alive(pjsua_acc *acc, pj_bool_t start, /* Save transport and destination address. */ acc->ka_transport = param->rdata->tp_info.transport; pjsip_transport_add_ref(acc->ka_transport); - pj_memcpy(&acc->ka_target, ¶m->rdata->pkt_info.src_addr, - param->rdata->pkt_info.src_addr_len); - acc->ka_target_len = param->rdata->pkt_info.src_addr_len; + + /* https://trac.pjsip.org/repos/ticket/1607: + * Calculate the destination address from the original request. Some + * (broken) servers send the response using different source address + * than the one that receives the request, which is forbidden by RFC + * 3581. + */ + { + pjsip_transaction *tsx; + pjsip_tx_data *req; + + tsx = pjsip_rdata_get_tsx(param->rdata); + PJ_ASSERT_ON_FAIL(tsx, return); + + req = tsx->last_tx; + + pj_memcpy(&acc->ka_target, &req->tp_info.dst_addr, + req->tp_info.dst_addr_len); + acc->ka_target_len = req->tp_info.dst_addr_len; + } /* Setup and start the timer */ acc->ka_timer.cb = &keep_alive_timer_cb; @@ -2146,6 +2249,13 @@ static pj_status_t pjsua_regc_init(int acc_id) return PJ_SUCCESS; } +pj_bool_t pjsua_sip_acc_is_using_stun(pjsua_acc_id acc_id) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + return acc->cfg.sip_stun_use != PJSUA_STUN_USE_DISABLED && + pjsua_var.ua_cfg.stun_srv_cnt != 0; +} /* * Update registration or perform unregistration. @@ -2153,6 +2263,7 @@ static pj_status_t pjsua_regc_init(int acc_id) PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, pj_bool_t renew) { + pjsua_acc *acc; pj_status_t status = 0; pjsip_tx_data *tdata = 0; @@ -2166,6 +2277,8 @@ PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, PJSUA_LOCK(); + acc = &pjsua_var.acc[acc_id]; + /* Cancel any re-registration timer */ if (pjsua_var.acc[acc_id].auto_rereg.timer.id) { pjsua_var.acc[acc_id].auto_rereg.timer.id = PJ_FALSE; @@ -2232,6 +2345,15 @@ PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, pjsip_regc_set_via_sent_by(pjsua_var.acc[acc_id].regc, &pjsua_var.acc[acc_id].via_addr, pjsua_var.acc[acc_id].via_tp); + } else if (!pjsua_sip_acc_is_using_stun(acc_id)) { + /* Choose local interface to use in Via if acc is not using + * STUN + */ + pjsua_acc_get_uac_addr(acc_id, tdata->pool, + &acc->cfg.reg_uri, + &tdata->via_addr, + NULL, NULL, + &tdata->via_tp); } //pjsua_process_msg_data(tdata, NULL); @@ -2605,6 +2727,15 @@ PJ_DEF(pj_status_t) pjsua_acc_create_request(pjsua_acc_id acc_id, { tdata->via_addr = pjsua_var.acc[acc_id].via_addr; tdata->via_tp = pjsua_var.acc[acc_id].via_tp; + } else if (!pjsua_sip_acc_is_using_stun(acc_id)) { + /* Choose local interface to use in Via if acc is not using + * STUN + */ + pjsua_acc_get_uac_addr(acc_id, tdata->pool, + target, + &tdata->via_addr, + NULL, NULL, + &tdata->via_tp); } /* Done */ @@ -2612,46 +2743,40 @@ PJ_DEF(pj_status_t) pjsua_acc_create_request(pjsua_acc_id acc_id, return PJ_SUCCESS; } - -PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool, - pj_str_t *contact, - pjsua_acc_id acc_id, - const pj_str_t *suri) +/* Get local transport address suitable to be used for Via or Contact address + * to send request to the specified destination URI. + */ +pj_status_t pjsua_acc_get_uac_addr(pjsua_acc_id acc_id, + pj_pool_t *pool, + const pj_str_t *dst_uri, + pjsip_host_port *addr, + pjsip_transport_type_e *p_tp_type, + int *secure, + const void **p_tp) { pjsua_acc *acc; pjsip_sip_uri *sip_uri; pj_status_t status; pjsip_transport_type_e tp_type = PJSIP_TRANSPORT_UNSPECIFIED; - pj_str_t local_addr; - pjsip_tpselector tp_sel; unsigned flag; - int secure; - int local_port; - const char *beginquote, *endquote; - char transport_param[32]; - const char *ob = ";ob"; + pjsip_tpselector tp_sel; + pjsip_tpmgr *tpmgr; + pjsip_tpmgr_fla2_param tfla2_prm; - PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); acc = &pjsua_var.acc[acc_id]; - /* If force_contact is configured, then use use it */ - if (acc->cfg.force_contact.slen) { - *contact = acc->cfg.force_contact; - return PJ_SUCCESS; - } - - /* If route-set is configured for the account, then URI is the + /* If route-set is configured for the account, then URI is the * first entry of the route-set. */ if (!pj_list_empty(&acc->route_set)) { - sip_uri = (pjsip_sip_uri*) + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(acc->route_set.next->name_addr.uri); } else { pj_str_t tmp; pjsip_uri *uri; - pj_strdup_with_null(pool, &tmp, suri); + pj_strdup_with_null(pool, &tmp, dst_uri); uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0); if (uri == NULL) @@ -2671,7 +2796,7 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool, tp_type = PJSIP_TRANSPORT_UDP; } else tp_type = pjsip_transport_get_type_from_name(&sip_uri->transport_param); - + if (tp_type == PJSIP_TRANSPORT_UNSPECIFIED) return PJSIP_EUNSUPTRANSPORT; @@ -2682,15 +2807,66 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool, tp_type = (pjsip_transport_type_e)(((int)tp_type) + PJSIP_TRANSPORT_IPV6); flag = pjsip_transport_get_flag_from_type(tp_type); - secure = (flag & PJSIP_TRANSPORT_SECURE) != 0; /* Init transport selector. */ - pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel); + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); /* Get local address suitable to send request from */ - status = pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(pjsua_var.endpt), - pool, tp_type, &tp_sel, - &local_addr, &local_port); + pjsip_tpmgr_fla2_param_default(&tfla2_prm); + tfla2_prm.tp_type = tp_type; + tfla2_prm.tp_sel = &tp_sel; + tfla2_prm.dst_host = sip_uri->host; + tfla2_prm.local_if = (!pjsua_sip_acc_is_using_stun(acc_id) || + (flag & PJSIP_TRANSPORT_RELIABLE)); + + tpmgr = pjsip_endpt_get_tpmgr(pjsua_var.endpt); + status = pjsip_tpmgr_find_local_addr2(tpmgr, pool, &tfla2_prm); + if (status != PJ_SUCCESS) + return status; + + addr->host = tfla2_prm.ret_addr; + addr->port = tfla2_prm.ret_port; + + if (p_tp_type) + *p_tp_type = tp_type; + + if (secure) { + *secure = (flag & PJSIP_TRANSPORT_SECURE) != 0; + } + + if (p_tp) + *p_tp = tfla2_prm.ret_tp; + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool, + pj_str_t *contact, + pjsua_acc_id acc_id, + const pj_str_t *suri) +{ + pjsua_acc *acc; + pj_status_t status; + pjsip_transport_type_e tp_type; + pjsip_host_port addr; + int secure; + const char *beginquote, *endquote; + char transport_param[32]; + const char *ob = ";ob"; + + + PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); + acc = &pjsua_var.acc[acc_id]; + + /* If force_contact is configured, then use use it */ + if (acc->cfg.force_contact.slen) { + *contact = acc->cfg.force_contact; + return PJ_SUCCESS; + } + + status = pjsua_acc_get_uac_addr(acc_id, pool, suri, &addr, + &tp_type, &secure, NULL); if (status != PJ_SUCCESS) return status; @@ -2725,10 +2901,10 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool, acc->user_part.ptr, (acc->user_part.slen?"@":""), beginquote, - (int)local_addr.slen, - local_addr.ptr, + (int)addr.host.slen, + addr.host.ptr, endquote, - local_port, + addr.port, transport_param, (int)acc->cfg.contact_uri_params.slen, acc->cfg.contact_uri_params.ptr, @@ -2760,6 +2936,8 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uas_contact( pj_pool_t *pool, pjsip_transport_type_e tp_type = PJSIP_TRANSPORT_UNSPECIFIED; pj_str_t local_addr; pjsip_tpselector tp_sel; + pjsip_tpmgr *tpmgr; + pjsip_tpmgr_fla2_param tfla2_prm; unsigned flag; int secure; int local_port; @@ -2847,12 +3025,22 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uas_contact( pj_pool_t *pool, pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel); /* Get local address suitable to send request from */ - status = pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(pjsua_var.endpt), - pool, tp_type, &tp_sel, - &local_addr, &local_port); + pjsip_tpmgr_fla2_param_default(&tfla2_prm); + tfla2_prm.tp_type = tp_type; + tfla2_prm.tp_sel = &tp_sel; + tfla2_prm.dst_host = sip_uri->host; + tfla2_prm.local_if = (!pjsua_sip_acc_is_using_stun(acc_id) || + (flag & PJSIP_TRANSPORT_RELIABLE)); + + tpmgr = pjsip_endpt_get_tpmgr(pjsua_var.endpt); + status = pjsip_tpmgr_find_local_addr2(tpmgr, pool, &tfla2_prm); if (status != PJ_SUCCESS) return status; + local_addr = tfla2_prm.ret_addr; + local_port = tfla2_prm.ret_port; + + /* Enclose IPv6 address in square brackets */ if (tp_type & PJSIP_TRANSPORT_IPV6) { beginquote = "["; diff --git a/pjsip/src/pjsua-lib/pjsua_aud.c b/pjsip/src/pjsua-lib/pjsua_aud.c index 557a147..1163e08 100644 --- a/pjsip/src/pjsua-lib/pjsua_aud.c +++ b/pjsip/src/pjsua-lib/pjsua_aud.c @@ -1,4 +1,4 @@ -/* $Id: pjsua_aud.c 4145 2012-05-22 23:13:22Z bennylp $ */ +/* $Id: pjsua_aud.c 4336 2013-01-29 08:15:02Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono @@ -291,7 +291,8 @@ pj_status_t pjsua_aud_subsys_init() /* Init the passthrough codec with supported formats only */ codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt; codec_cfg.passthrough.setting.fmts = ext_fmts; - codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode; + codec_cfg.passthrough.setting.ilbc_mode = + pjsua_var.media_cfg.ilbc_mode; } #endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */ @@ -559,7 +560,12 @@ static void dtmf_callback(pjmedia_stream *strm, void *user_data, pj_log_pop_indent(); } - +/* Internal function: update audio channel after SDP negotiation. + * Warning: do not use temporary/flip-flop pool, e.g: inv->pool_prov, + * for creating stream, etc, as after SDP negotiation and when + * the SDP media is not changed, the stream should remain running + * while the temporary/flip-flop pool may be released. + */ pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med, pj_pool_t *tmp_pool, pjmedia_stream_info *si, @@ -583,20 +589,6 @@ pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med, /* Check if no media is active */ if (si->dir != PJMEDIA_DIR_NONE) { - /* Override ptime, if this option is specified. */ - if (pjsua_var.media_cfg.ptime != 0) { - si->param->setting.frm_per_pkt = (pj_uint8_t) - (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime); - if (si->param->setting.frm_per_pkt == 0) - si->param->setting.frm_per_pkt = 1; - } - - /* Disable VAD, if this option is specified. */ - if (pjsua_var.media_cfg.no_vad) { - si->param->setting.vad = 0; - } - - /* Optionally, application may modify other stream settings here * (such as jitter buffer parameters, codec ptime, etc.) */ @@ -678,7 +670,7 @@ pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med, port_name = pj_str("call"); } status = pjmedia_conf_add_port( pjsua_var.mconf, - call->inv->pool_prov, + call->inv->pool, media_port, &port_name, (unsigned*) @@ -1571,6 +1563,14 @@ static pj_status_t create_aud_param(pjmedia_aud_param *param, param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL); } + /* VAD settings */ + if (pjsua_var.media_cfg.no_vad) { + param->flags &= ~PJMEDIA_AUD_DEV_CAP_VAD; + } else { + param->flags |= PJMEDIA_AUD_DEV_CAP_VAD; + param->vad_enabled = PJ_TRUE; + } + return PJ_SUCCESS; } 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 @@ -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; jdesc.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; jdesc.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; } diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index f4f9508..e352238 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -1,4 +1,4 @@ -/* $Id: pjsua_core.c 4173 2012-06-20 10:39:05Z ming $ */ +/* $Id: pjsua_core.c 4370 2013-02-26 05:30:00Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono @@ -195,13 +195,66 @@ PJ_DEF(void) pjsua_transport_config_default(pjsua_transport_config *cfg) PJ_DEF(void) pjsua_transport_config_dup(pj_pool_t *pool, pjsua_transport_config *dst, const pjsua_transport_config *src) +{ + pj_memcpy(dst, src, sizeof(*src)); + pj_strdup(pool, &dst->public_addr, &src->public_addr); + pj_strdup(pool, &dst->bound_addr, &src->bound_addr); +} + +PJ_DEF(void) pjsua_ice_config_from_media_config( pj_pool_t *pool, + pjsua_ice_config *dst, + const pjsua_media_config *src) +{ + PJ_UNUSED_ARG(pool); + + dst->enable_ice = src->enable_ice; + dst->ice_max_host_cands = src->ice_max_host_cands; + dst->ice_opt = src->ice_opt; + dst->ice_no_rtcp = src->ice_no_rtcp; + dst->ice_always_update = src->ice_always_update; +} + +PJ_DEF(void) pjsua_ice_config_dup( pj_pool_t *pool, + pjsua_ice_config *dst, + const pjsua_ice_config *src) { PJ_UNUSED_ARG(pool); pj_memcpy(dst, src, sizeof(*src)); } +PJ_DEF(void) pjsua_turn_config_from_media_config(pj_pool_t *pool, + pjsua_turn_config *dst, + const pjsua_media_config *src) +{ + dst->enable_turn = src->enable_turn; + dst->turn_conn_type = src->turn_conn_type; + if (pool == NULL) { + dst->turn_server = src->turn_server; + dst->turn_auth_cred = src->turn_auth_cred; + } else { + if (pj_stricmp(&dst->turn_server, &src->turn_server)) + pj_strdup(pool, &dst->turn_server, &src->turn_server); + pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, + &src->turn_auth_cred); + } +} + +PJ_DEF(void) pjsua_turn_config_dup(pj_pool_t *pool, + pjsua_turn_config *dst, + const pjsua_turn_config *src) +{ + pj_memcpy(dst, src, sizeof(*src)); + if (pool) { + pj_strdup(pool, &dst->turn_server, &src->turn_server); + pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, + &src->turn_auth_cred); + } +} + PJ_DEF(void) pjsua_acc_config_default(pjsua_acc_config *cfg) { + pjsua_media_config med_cfg; + pj_bzero(cfg, sizeof(*cfg)); cfg->reg_timeout = PJSUA_REG_INTERVAL; @@ -215,6 +268,7 @@ PJ_DEF(void) pjsua_acc_config_default(pjsua_acc_config *cfg) cfg->require_100rel = pjsua_var.ua_cfg.require_100rel; cfg->use_timer = pjsua_var.ua_cfg.use_timer; cfg->timer_setting = pjsua_var.ua_cfg.timer_setting; + cfg->lock_codec = 1; cfg->ka_interval = 15; cfg->ka_data = pj_str("\r\n"); cfg->vid_cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; @@ -223,6 +277,11 @@ PJ_DEF(void) pjsua_acc_config_default(pjsua_acc_config *cfg) pjmedia_vid_stream_rc_config_default(&cfg->vid_stream_rc_cfg); #endif pjsua_transport_config_default(&cfg->rtp_cfg); + + pjsua_media_config_default(&med_cfg); + pjsua_ice_config_from_media_config(NULL, &cfg->ice_cfg, &med_cfg); + pjsua_turn_config_from_media_config(NULL, &cfg->turn_cfg, &med_cfg); + cfg->use_srtp = pjsua_var.ua_cfg.use_srtp; cfg->srtp_secure_signaling = pjsua_var.ua_cfg.srtp_secure_signaling; cfg->srtp_optional_dup_offer = pjsua_var.ua_cfg.srtp_optional_dup_offer; @@ -266,6 +325,7 @@ PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg) cfg->snd_auto_close_time = 1; cfg->ice_max_host_cands = -1; + cfg->ice_always_update = PJ_TRUE; pj_ice_sess_options_default(&cfg->ice_opt); cfg->turn_conn_type = PJ_TURN_TP_UDP; @@ -898,7 +958,7 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, if (pjsua_var.ua_cfg.force_lr) { pjsip_sip_uri *sip_url; if (!PJSIP_URI_SCHEME_IS_SIP(r->name_addr.uri) && - !PJSIP_URI_SCHEME_IS_SIP(r->name_addr.uri)) + !PJSIP_URI_SCHEME_IS_SIPS(r->name_addr.uri)) { status = PJSIP_EINVALIDSCHEME; goto on_error; @@ -1017,7 +1077,7 @@ static void busy_sleep(unsigned msec) { pj_time_val timeout, now; - pj_gettimeofday(&timeout); + pj_gettickcount(&timeout); timeout.msec += msec; pj_time_val_normalize(&timeout); @@ -1026,15 +1086,21 @@ static void busy_sleep(unsigned msec) i = msec / 10; while (pjsua_handle_events(10) > 0 && i > 0) --i; - pj_gettimeofday(&now); + pj_gettickcount(&now); } while (PJ_TIME_VAL_LT(now, timeout)); } -/* Internal function to destroy STUN resolution session - * (pj_stun_resolve). - */ +static void stun_resolve_add_ref(pjsua_stun_resolve *sess) +{ + ++sess->ref_cnt; +} + static void destroy_stun_resolve(pjsua_stun_resolve *sess) { + sess->destroy_flag = PJ_TRUE; + if (sess->ref_cnt > 0) + return; + PJSUA_LOCK(); pj_list_erase(sess); PJSUA_UNLOCK(); @@ -1043,6 +1109,14 @@ static void destroy_stun_resolve(pjsua_stun_resolve *sess) pj_pool_release(sess->pool); } +static void stun_resolve_dec_ref(pjsua_stun_resolve *sess) +{ + --sess->ref_cnt; + if (sess->ref_cnt <= 0 && sess->destroy_flag) + destroy_stun_resolve(sess); +} + + /* This is the internal function to be called when STUN resolution * session (pj_stun_resolve) has completed. */ @@ -1050,11 +1124,15 @@ static void stun_resolve_complete(pjsua_stun_resolve *sess) { pj_stun_resolve_result result; + if (sess->has_result) + goto on_return; + pj_bzero(&result, sizeof(result)); result.token = sess->token; result.status = sess->status; result.name = sess->srv[sess->idx]; pj_memcpy(&result.addr, &sess->addr, sizeof(result.addr)); + sess->has_result = PJ_TRUE; if (result.status == PJ_SUCCESS) { char addr[PJ_INET6_ADDRSTRLEN+10]; @@ -1070,8 +1148,11 @@ static void stun_resolve_complete(pjsua_stun_resolve *sess) PJ_LOG(1,(THIS_FILE, "STUN resolution failed: %s", errmsg)); } + stun_resolve_add_ref(sess); sess->cb(&result); + stun_resolve_dec_ref(sess); +on_return: if (!sess->blocking) { destroy_stun_resolve(sess); } @@ -1133,22 +1214,27 @@ static pj_bool_t test_stun_on_status(pj_stun_sock *stun_sock, */ static void resolve_stun_entry(pjsua_stun_resolve *sess) { + stun_resolve_add_ref(sess); + /* Loop while we have entry to try */ for (; sess->idx < sess->count; ++sess->idx) { const int af = pj_AF_INET(); + char target[64]; pj_str_t hostpart; pj_uint16_t port; pj_stun_sock_cb stun_sock_cb; pj_assert(sess->idx < sess->count); + pj_ansi_snprintf(target, sizeof(target), "%.*s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr); + /* Parse the server entry into host:port */ sess->status = pj_sockaddr_parse2(af, 0, &sess->srv[sess->idx], &hostpart, &port, NULL); if (sess->status != PJ_SUCCESS) { - PJ_LOG(2,(THIS_FILE, "Invalid STUN server entry %.*s", - (int)sess->srv[sess->idx].slen, - sess->srv[sess->idx].ptr)); + PJ_LOG(2,(THIS_FILE, "Invalid STUN server entry %s", target)); continue; } @@ -1158,10 +1244,8 @@ static void resolve_stun_entry(pjsua_stun_resolve *sess) pj_assert(sess->stun_sock == NULL); - PJ_LOG(4,(THIS_FILE, "Trying STUN server %.*s (%d of %d)..", - (int)sess->srv[sess->idx].slen, - sess->srv[sess->idx].ptr, - sess->idx+1, sess->count)); + PJ_LOG(4,(THIS_FILE, "Trying STUN server %s (%d of %d)..", + target, sess->idx+1, sess->count)); /* Use STUN_sock to test this entry */ pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); @@ -1173,9 +1257,8 @@ static void resolve_stun_entry(pjsua_stun_resolve *sess) char errmsg[PJ_ERR_MSG_SIZE]; pj_strerror(sess->status, errmsg, sizeof(errmsg)); PJ_LOG(4,(THIS_FILE, - "Error creating STUN socket for %.*s: %s", - (int)sess->srv[sess->idx].slen, - sess->srv[sess->idx].ptr, errmsg)); + "Error creating STUN socket for %s: %s", + target, errmsg)); continue; } @@ -1186,19 +1269,20 @@ static void resolve_stun_entry(pjsua_stun_resolve *sess) char errmsg[PJ_ERR_MSG_SIZE]; pj_strerror(sess->status, errmsg, sizeof(errmsg)); PJ_LOG(4,(THIS_FILE, - "Error starting STUN socket for %.*s: %s", - (int)sess->srv[sess->idx].slen, - sess->srv[sess->idx].ptr, errmsg)); + "Error starting STUN socket for %s: %s", + target, errmsg)); - pj_stun_sock_destroy(sess->stun_sock); - sess->stun_sock = NULL; + if (sess->stun_sock) { + pj_stun_sock_destroy(sess->stun_sock); + sess->stun_sock = NULL; + } continue; } /* Done for now, testing will resume/complete asynchronously in * stun_sock_cb() */ - return; + goto on_return; } if (sess->idx >= sess->count) { @@ -1207,6 +1291,9 @@ static void resolve_stun_entry(pjsua_stun_resolve *sess) sess->status = PJ_EUNKNOWN); stun_resolve_complete(sess); } + +on_return: + stun_resolve_dec_ref(sess); } @@ -1837,6 +1924,8 @@ static pj_status_t create_sip_udp_sock(int af, pj_sockaddr_set_port(p_pub_addr, (pj_uint16_t)port); } else if (stun_srv.slen) { + pjstun_setting stun_opt; + /* * STUN is specified, resolve the address with STUN. */ @@ -1846,10 +1935,13 @@ static pj_status_t create_sip_udp_sock(int af, return PJ_EAFNOTSUP; } - status = pjstun_get_mapped_addr(&pjsua_var.cp.factory, 1, &sock, - &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port), - &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port), - &p_pub_addr->ipv4); + pj_bzero(&stun_opt, sizeof(stun_opt)); + stun_opt.use_stun2 = pjsua_var.ua_cfg.stun_map_use_stun2; + stun_opt.srv1 = stun_opt.srv2 = stun_srv; + stun_opt.port1 = stun_opt.port2 = + pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port); + status = pjstun_get_mapped_addr2(&pjsua_var.cp.factory, &stun_opt, + 1, &sock, &p_pub_addr->ipv4); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error contacting STUN server", status); pj_sock_close(sock); @@ -1977,8 +2069,10 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, pjsua_transport_config config; pjsip_tpfactory *tcp; pjsip_tcp_transport_cfg tcp_cfg; + int af; - pjsip_tcp_transport_cfg_default(&tcp_cfg, pj_AF_INET()); + af = (type==PJSIP_TRANSPORT_TCP6) ? pj_AF_INET6() : pj_AF_INET(); + pjsip_tcp_transport_cfg_default(&tcp_cfg, af); /* Supply default config if it's not specified */ if (cfg == NULL) { @@ -2028,14 +2122,15 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, #endif /* PJ_HAS_TCP */ #if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 - } else if (type == PJSIP_TRANSPORT_TLS) { + } else if (type == PJSIP_TRANSPORT_TLS || type == PJSIP_TRANSPORT_TLS6) { /* * Create TLS transport. */ pjsua_transport_config config; pjsip_host_port a_name; pjsip_tpfactory *tls; - pj_sockaddr_in local_addr; + pj_sockaddr local_addr; + int af; /* Supply default config if it's not specified */ if (cfg == NULL) { @@ -2045,13 +2140,15 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, } /* Init local address */ - pj_sockaddr_in_init(&local_addr, 0, 0); + af = (type==PJSIP_TRANSPORT_TLS) ? pj_AF_INET() : pj_AF_INET6(); + pj_sockaddr_init(af, &local_addr, NULL, 0); if (cfg->port) - local_addr.sin_port = pj_htons((pj_uint16_t)cfg->port); + pj_sockaddr_set_port(&local_addr, (pj_uint16_t)cfg->port); if (cfg->bound_addr.slen) { - status = pj_sockaddr_in_set_str_addr(&local_addr,&cfg->bound_addr); + status = pj_sockaddr_set_str_addr(af, &local_addr, + &cfg->bound_addr); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to resolve transport bound address", @@ -2065,9 +2162,9 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, if (cfg->public_addr.slen) a_name.host = cfg->public_addr; - status = pjsip_tls_transport_start(pjsua_var.endpt, - &cfg->tls_setting, - &local_addr, &a_name, 1, &tls); + status = pjsip_tls_transport_start2(pjsua_var.endpt, + &cfg->tls_setting, + &local_addr, &a_name, 1, &tls); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error creating SIP TLS listener", status); @@ -2087,7 +2184,7 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, } /* Set transport state callback */ - if (pjsua_var.ua_cfg.cb.on_transport_state) { + { pjsip_tp_state_callback tpcb; pjsip_tpmgr *tpmgr; @@ -2743,7 +2840,7 @@ pj_status_t normalize_route_uri(pj_pool_t *pool, pj_str_t *uri) } if (!PJSIP_URI_SCHEME_IS_SIP(uri_obj) && - !PJSIP_URI_SCHEME_IS_SIP(uri_obj)) + !PJSIP_URI_SCHEME_IS_SIPS(uri_obj)) { PJ_LOG(1,(THIS_FILE, "Route URI must be SIP URI: %.*s", (int)uri->slen, uri->ptr)); @@ -2804,6 +2901,7 @@ PJ_DEF(void) pjsua_dump(pj_bool_t detail) PJ_LOG(3,(THIS_FILE, "Dumping media transports:")); for (i=0; iacc_id].cfg; + /* Dump the media transports in this call */ for (j = 0; j < tp_cnt; ++j) { pjmedia_transport_info tpinfo; @@ -2837,7 +2937,7 @@ PJ_DEF(void) pjsua_dump(pj_bool_t detail) pjmedia_transport_info_init(&tpinfo); pjmedia_transport_get_info(tp[j], &tpinfo); PJ_LOG(3,(THIS_FILE, " %s: %s", - (pjsua_var.media_cfg.enable_ice ? "ICE" : "UDP"), + (acc_cfg->ice_cfg.enable_ice ? "ICE" : "UDP"), pj_sockaddr_print(&tpinfo.sock_info.rtp_addr_name, addr_buf, sizeof(addr_buf), 3))); diff --git a/pjsip/src/pjsua-lib/pjsua_dump.c b/pjsip/src/pjsua-lib/pjsua_dump.c index 0b23a97..a24b71a 100644 --- a/pjsip/src/pjsua-lib/pjsua_dump.c +++ b/pjsip/src/pjsua-lib/pjsua_dump.c @@ -1,4 +1,4 @@ -/* $Id: pjsua_dump.c 4085 2012-04-25 07:45:22Z nanang $ */ +/* $Id: pjsua_dump.c 4300 2012-11-26 02:04:17Z ming $ */ /* * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) * @@ -212,6 +212,10 @@ static unsigned dump_media_stat(const char *indent, /* Dump media session */ +#if PJSUA_MEDIA_HAS_PJMEDIA || \ + (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO && \ + PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT) + static void dump_media_session(const char *indent, char *buf, unsigned maxlen, pjsua_call *call) @@ -858,6 +862,24 @@ static void dump_media_session(const char *indent, } } +#else /* PJSUA_MEDIA_HAS_PJMEDIA || + (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO && + PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT) */ + +static void dump_media_session(const char *indent, + char *buf, unsigned maxlen, + pjsua_call *call) +{ + PJ_UNUSED_ARG(indent); + PJ_UNUSED_ARG(buf); + PJ_UNUSED_ARG(maxlen); + PJ_UNUSED_ARG(call); +} + +#endif /* PJSUA_MEDIA_HAS_PJMEDIA || + (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO && + PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT) */ + /* Print call info */ void print_call(const char *title, @@ -866,11 +888,12 @@ void print_call(const char *title, { int len; pjsip_inv_session *inv = pjsua_var.calls[call_id].inv; - pjsip_dialog *dlg = inv->dlg; + pjsip_dialog *dlg; char userinfo[128]; /* Dump invite sesion info. */ + dlg = (inv? inv->dlg: pjsua_var.calls[call_id].async_call.dlg); len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); if (len < 0) pj_ansi_strcpy(userinfo, "<--uri too long-->"); @@ -879,7 +902,8 @@ void print_call(const char *title, len = pj_ansi_snprintf(buf, size, "%s[%s] %s", title, - pjsip_inv_state_name(inv->state), + pjsip_inv_state_name(inv? inv->state: + PJSIP_INV_STATE_DISCONNECTED), userinfo); if (len < 1 || len >= (int)size) { pj_ansi_strcpy(buf, "<--uri too long-->"); diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 8bcb0da..27069a1 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -1,4 +1,4 @@ -/* $Id: pjsua_media.c 4182 2012-06-27 07:12:23Z ming $ */ +/* $Id: pjsua_media.c 4412 2013-03-05 03:12:32Z riza $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono @@ -165,9 +165,11 @@ pj_status_t pjsua_media_subsys_start(void) #endif /* Perform NAT detection */ - status = pjsua_detect_nat_type(); - if (status != PJ_SUCCESS) { - PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed")); + if (pjsua_var.ua_cfg.stun_srv_cnt) { + status = pjsua_detect_nat_type(); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed")); + } } pj_log_pop_indent(); @@ -226,24 +228,33 @@ pj_status_t pjsua_media_subsys_destroy(unsigned flags) * Create RTP and RTCP socket pair, and possibly resolve their public * address via STUN. */ -static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, +static pj_status_t create_rtp_rtcp_sock(pjsua_call_media *call_med, + const pjsua_transport_config *cfg, pjmedia_sock_info *skinfo) { enum { RTP_RETRY = 100 }; int i; - pj_sockaddr_in bound_addr; - pj_sockaddr_in mapped_addr[2]; + pj_bool_t use_ipv6; + int af; + pj_sockaddr bound_addr; + pj_sockaddr mapped_addr[2]; pj_status_t status = PJ_SUCCESS; - char addr_buf[PJ_INET6_ADDRSTRLEN+2]; + char addr_buf[PJ_INET6_ADDRSTRLEN+10]; pj_sock_t sock[2]; + use_ipv6 = (pjsua_var.acc[call_med->call->acc_id].cfg.ipv6_media_use != + PJSUA_IPV6_DISABLED); + af = use_ipv6 ? pj_AF_INET6() : pj_AF_INET(); + /* Make sure STUN server resolution has completed */ - status = resolve_stun_server(PJ_TRUE); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error resolving STUN server", status); - return status; + if (!use_ipv6 && pjsua_sip_acc_is_using_stun(call_med->call->acc_id)) { + status = resolve_stun_server(PJ_TRUE); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error resolving STUN server", status); + return status; + } } if (next_rtp_port == 0) @@ -255,9 +266,9 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, for (i=0; i<2; ++i) sock[i] = PJ_INVALID_SOCKET; - bound_addr.sin_addr.s_addr = PJ_INADDR_ANY; + pj_sockaddr_init(af, &bound_addr, NULL, 0); if (cfg->bound_addr.slen) { - status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr); + status = pj_sockaddr_set_str_addr(af, &bound_addr, &cfg->bound_addr); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to resolve transport bind address", status); @@ -269,7 +280,7 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, for (i=0; icall->acc_id) && + pjsua_var.stun_srv.addr.sa_family != 0) + { char ip_addr[32]; pj_str_t stun_srv; + pj_sockaddr_in resolved_addr[2]; + pjstun_setting stun_opt; pj_ansi_strcpy(ip_addr, pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr)); stun_srv = pj_str(ip_addr); - status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock, - &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port), - &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port), - mapped_addr); + pj_bzero(&stun_opt, sizeof(stun_opt)); + stun_opt.use_stun2 = pjsua_var.ua_cfg.stun_map_use_stun2; + stun_opt.srv1 = stun_opt.srv2 = stun_srv; + stun_opt.port1 = stun_opt.port2 = + pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port); + status=pjstun_get_mapped_addr2(&pjsua_var.cp.factory, &stun_opt, + 2, sock, resolved_addr); +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \ + PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0 + /* Handle EPIPE (Broken Pipe) error, which happens on UDP socket + * after app wakes up from suspended state. In this case, simply + * just retry. + * P.S.: The magic status is PJ_STATUS_FROM_OS(EPIPE) + */ + if (status == 120032) { + PJ_LOG(4,(THIS_FILE, "Got EPIPE error, retrying..")); + pj_sock_close(sock[0]); + sock[0] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[1]); + sock[1] = PJ_INVALID_SOCKET; + + continue; + } + else +#endif if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "STUN resolve error", status); goto on_error; } + pj_sockaddr_cp(&mapped_addr[0], &resolved_addr[0]); + pj_sockaddr_cp(&mapped_addr[1], &resolved_addr[1]); + #if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT - if (pj_ntohs(mapped_addr[1].sin_port) == - pj_ntohs(mapped_addr[0].sin_port)+1) + if (pj_sockaddr_get_port(&mapped_addr[1]) == + pj_sockaddr_get_port(&mapped_addr[0])+1) { /* Success! */ break; @@ -349,14 +391,14 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, pj_sock_close(sock[1]); sock[1] = PJ_INVALID_SOCKET; #else - if (pj_ntohs(mapped_addr[1].sin_port) != - pj_ntohs(mapped_addr[0].sin_port)+1) + if (pj_sockaddr_get_port(&mapped_addr[1]) != + pj_sockaddr_get_port(&mapped_addr[0])+1) { PJ_LOG(4,(THIS_FILE, "Note: STUN mapped RTCP port %d is not adjacent" " to RTP port %d", - pj_ntohs(mapped_addr[1].sin_port), - pj_ntohs(mapped_addr[0].sin_port))); + pj_sockaddr_get_port(&mapped_addr[1]), + pj_sockaddr_get_port(&mapped_addr[0]))); } /* Success! */ break; @@ -364,13 +406,13 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, } else if (cfg->public_addr.slen) { - status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr, - (pj_uint16_t)next_rtp_port); + status = pj_sockaddr_init(af, &mapped_addr[0], &cfg->public_addr, + (pj_uint16_t)next_rtp_port); if (status != PJ_SUCCESS) goto on_error; - status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr, - (pj_uint16_t)(next_rtp_port+1)); + status = pj_sockaddr_init(af, &mapped_addr[1], &cfg->public_addr, + (pj_uint16_t)(next_rtp_port+1)); if (status != PJ_SUCCESS) goto on_error; @@ -378,24 +420,24 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, } else { - if (bound_addr.sin_addr.s_addr == 0) { + if (!pj_sockaddr_has_addr(&bound_addr)) { pj_sockaddr addr; /* Get local IP address. */ - status = pj_gethostip(pj_AF_INET(), &addr); + status = pj_gethostip(af, &addr); if (status != PJ_SUCCESS) goto on_error; - bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr; + pj_sockaddr_copy_addr(&bound_addr, &addr); } for (i=0; i<2; ++i) { - pj_sockaddr_in_init(&mapped_addr[i], NULL, 0); - mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr; + pj_sockaddr_init(af, &mapped_addr[i], NULL, 0); + pj_sockaddr_copy_addr(&mapped_addr[i], &bound_addr); + pj_sockaddr_set_port(&mapped_addr[i], + (pj_uint16_t)(next_rtp_port+i)); } - mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port); - mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1)); break; } } @@ -408,12 +450,10 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, skinfo->rtp_sock = sock[0]; - pj_memcpy(&skinfo->rtp_addr_name, - &mapped_addr[0], sizeof(pj_sockaddr_in)); + pj_sockaddr_cp(&skinfo->rtp_addr_name, &mapped_addr[0]); skinfo->rtcp_sock = sock[1]; - pj_memcpy(&skinfo->rtcp_addr_name, - &mapped_addr[1], sizeof(pj_sockaddr_in)); + pj_sockaddr_cp(&skinfo->rtcp_addr_name, &mapped_addr[1]); PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s", pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf, @@ -440,7 +480,7 @@ static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg, pjmedia_sock_info skinfo; pj_status_t status; - status = create_rtp_rtcp_sock(cfg, &skinfo); + status = create_rtp_rtcp_sock(call_med, cfg, &skinfo); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket", status); @@ -512,21 +552,59 @@ on_error: } #endif -static void med_tp_timer_cb(void *user_data) +/* Deferred callback to notify ICE init complete */ +static void ice_init_complete_cb(void *user_data) { pjsua_call_media *call_med = (pjsua_call_media*)user_data; - pjsua_call *call = NULL; - pjsip_dialog *dlg = NULL; - acquire_call("med_tp_timer_cb", call_med->call->index, &call, &dlg); + if (call_med->call == NULL) + return; + /* No need to acquire_call() if we only change the tp_ready flag + * (i.e. transport is being created synchronously). Otherwise + * calling acquire_call() here may cause deadlock. See + * https://trac.pjsip.org/repos/ticket/1578 + */ call_med->tp_ready = call_med->tp_result; - if (call_med->med_create_cb) + + if (call_med->med_create_cb) { + pjsua_call *call = NULL; + pjsip_dialog *dlg = NULL; + + if (acquire_call("ice_init_complete_cb", call_med->call->index, + &call, &dlg) != PJ_SUCCESS) + { + /* Call have been terminated */ + return; + } + (*call_med->med_create_cb)(call_med, call_med->tp_ready, call_med->call->secure_level, NULL); + if (dlg) + pjsip_dlg_dec_lock(dlg); + } +} + +/* Deferred callback to notify ICE negotiation failure */ +static void ice_failed_nego_cb(void *user_data) +{ + int call_id = (int)(long)user_data; + pjsua_call *call = NULL; + pjsip_dialog *dlg = NULL; + + if (acquire_call("ice_failed_nego_cb", call_id, + &call, &dlg) != PJ_SUCCESS) + { + /* Call have been terminated */ + return; + } + + pjsua_var.ua_cfg.cb.on_call_media_state(call_id); + if (dlg) - pjsip_dlg_dec_lock(dlg); + pjsip_dlg_dec_lock(dlg); + } /* This callback is called when ICE negotiation completes */ @@ -535,73 +613,43 @@ static void on_ice_complete(pjmedia_transport *tp, pj_status_t result) { pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data; + pjsua_call *call; if (!call_med) return; + call = call_med->call; + switch (op) { case PJ_ICE_STRANS_OP_INIT: call_med->tp_result = result; - pjsua_schedule_timer2(&med_tp_timer_cb, call_med, 1); + pjsua_schedule_timer2(&ice_init_complete_cb, call_med, 1); break; case PJ_ICE_STRANS_OP_NEGOTIATION: - if (result != PJ_SUCCESS) { + if (result == PJ_SUCCESS) { + /* Update RTP address */ + pjmedia_transport_info tpinfo; + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + pj_sockaddr_cp(&call_med->rtp_addr, &tpinfo.sock_info.rtp_addr_name); + } else { call_med->state = PJSUA_CALL_MEDIA_ERROR; call_med->dir = PJMEDIA_DIR_NONE; - - if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) { - pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index); - } - } else if (call_med->call) { - /* Send UPDATE if default transport address is different than - * what was advertised (ticket #881) - */ - pjmedia_transport_info tpinfo; - pjmedia_ice_transport_info *ii = NULL; - unsigned i; - - pjmedia_transport_info_init(&tpinfo); - pjmedia_transport_get_info(tp, &tpinfo); - for (i=0; irole==PJ_ICE_SESS_ROLE_CONTROLLING && - pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name, - &call_med->rtp_addr)) - { - pj_bool_t use_update; - const pj_str_t STR_UPDATE = { "UPDATE", 6 }; - pjsip_dialog_cap_status support_update; - pjsip_dialog *dlg; - - dlg = call_med->call->inv->dlg; - support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW, - NULL, &STR_UPDATE); - use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED); - - PJ_LOG(4,(THIS_FILE, - "ICE default transport address has changed for " - "call %d, sending %s", - call_med->call->index, - (use_update ? "UPDATE" : "re-INVITE"))); - - if (use_update) - pjsua_call_update(call_med->call->index, 0, NULL); - else - pjsua_call_reinvite(call_med->call->index, 0, NULL); + if (call && pjsua_var.ua_cfg.cb.on_call_media_state) { + /* Defer the callback to a timer */ + pjsua_schedule_timer2(&ice_failed_nego_cb, + (void*)(long)call->index, 1); } - } + } + /* Check if default ICE transport address is changed */ + call->reinv_ice_sent = PJ_FALSE; + pjsua_call_schedule_reinvite_check(call, 0); break; case PJ_ICE_STRANS_OP_KEEP_ALIVE: if (result != PJ_SUCCESS) { PJ_PERROR(4,(THIS_FILE, result, "ICE keep alive failure for transport %d:%d", - call_med->call->index, call_med->idx)); + call->index, call_med->idx)); } if (pjsua_var.ua_cfg.cb.on_call_media_transport_state) { pjsua_med_tp_state_info info; @@ -612,10 +660,10 @@ static void on_ice_complete(pjmedia_transport *tp, info.status = result; info.ext_info = &op; (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)( - call_med->call->index, &info); + call->index, &info); } if (pjsua_var.ua_cfg.cb.on_ice_transport_error) { - pjsua_call_id id = call_med->call->index; + pjsua_call_id id = call->index; (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result, NULL); } @@ -657,12 +705,15 @@ static pj_status_t create_ice_media_transport( pj_bool_t async) { char stunip[PJ_INET6_ADDRSTRLEN]; + pjsua_acc_config *acc_cfg; pj_ice_strans_cfg ice_cfg; pjmedia_ice_cb ice_cb; char name[32]; unsigned comp_cnt; pj_status_t status; + acc_cfg = &pjsua_var.acc[call_med->call->acc_id].cfg; + /* Make sure STUN server resolution has completed */ status = resolve_stun_server(PJ_TRUE); if (status != PJ_SUCCESS) { @@ -679,7 +730,7 @@ static pj_status_t create_ice_media_transport( ice_cfg.af = pj_AF_INET(); ice_cfg.resolver = pjsua_var.resolver; - ice_cfg.opt = pjsua_var.media_cfg.ice_opt; + ice_cfg.opt = acc_cfg->ice_cfg.ice_opt; /* Configure STUN settings */ if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) { @@ -687,8 +738,16 @@ static pj_status_t create_ice_media_transport( ice_cfg.stun.server = pj_str(stunip); ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv); } - if (pjsua_var.media_cfg.ice_max_host_cands >= 0) - ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands; + if (acc_cfg->ice_cfg.ice_max_host_cands >= 0) + ice_cfg.stun.max_host_cands = acc_cfg->ice_cfg.ice_max_host_cands; + + /* Copy binding port setting to STUN setting */ + pj_sockaddr_init(ice_cfg.af, &ice_cfg.stun.cfg.bound_addr, + &cfg->bound_addr, (pj_uint16_t)cfg->port); + ice_cfg.stun.cfg.port_range = (pj_uint16_t)cfg->port_range; + if (cfg->port != 0 && ice_cfg.stun.cfg.port_range == 0) + ice_cfg.stun.cfg.port_range = + (pj_uint16_t)(pjsua_var.ua_cfg.max_calls * 10); /* Copy QoS setting to STUN setting */ ice_cfg.stun.cfg.qos_type = cfg->qos_type; @@ -696,8 +755,8 @@ static pj_status_t create_ice_media_transport( sizeof(cfg->qos_params)); /* Configure TURN settings */ - if (pjsua_var.media_cfg.enable_turn) { - status = parse_host_port(&pjsua_var.media_cfg.turn_server, + if (acc_cfg->turn_cfg.enable_turn) { + status = parse_host_port(&acc_cfg->turn_cfg.turn_server, &ice_cfg.turn.server, &ice_cfg.turn.port); if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) { @@ -706,24 +765,36 @@ static pj_status_t create_ice_media_transport( } if (ice_cfg.turn.port == 0) ice_cfg.turn.port = 3479; - ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type; + ice_cfg.turn.conn_type = acc_cfg->turn_cfg.turn_conn_type; pj_memcpy(&ice_cfg.turn.auth_cred, - &pjsua_var.media_cfg.turn_auth_cred, + &acc_cfg->turn_cfg.turn_auth_cred, sizeof(ice_cfg.turn.auth_cred)); /* Copy QoS setting to TURN setting */ ice_cfg.turn.cfg.qos_type = cfg->qos_type; pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params, sizeof(cfg->qos_params)); + + /* Copy binding port setting to TURN setting */ + pj_sockaddr_init(ice_cfg.af, &ice_cfg.turn.cfg.bound_addr, + &cfg->bound_addr, (pj_uint16_t)cfg->port); + ice_cfg.turn.cfg.port_range = (pj_uint16_t)cfg->port_range; + if (cfg->port != 0 && ice_cfg.turn.cfg.port_range == 0) + ice_cfg.turn.cfg.port_range = + (pj_uint16_t)(pjsua_var.ua_cfg.max_calls * 10); } + /* Configure packet size for STUN and TURN sockets */ + ice_cfg.stun.cfg.max_pkt_size = PJMEDIA_MAX_MRU; + ice_cfg.turn.cfg.max_pkt_size = PJMEDIA_MAX_MRU; + pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb)); ice_cb.on_ice_complete = &on_ice_complete; pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx); call_med->tp_ready = PJ_EPENDING; comp_cnt = 1; - if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp) + if (PJMEDIA_ADVERTISE_RTCP && !acc_cfg->ice_cfg.ice_no_rtcp) ++comp_cnt; status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt, @@ -761,7 +832,7 @@ static pj_status_t create_ice_media_transport( pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING, pjsua_var.media_cfg.rx_drop_pct); - + return PJ_SUCCESS; on_error: @@ -851,7 +922,7 @@ PJ_DEF(pj_status_t) pjsua_media_transports_create( pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg); /* Create the transports */ - if (pjsua_var.media_cfg.enable_ice) { + if (pjsua_var.ice_cfg.enable_ice) { status = create_ice_media_transports(&cfg); } else { status = create_udp_media_transports(&cfg); @@ -1237,7 +1308,7 @@ pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_CREATING); - if (pjsua_var.media_cfg.enable_ice) { + if (pjsua_var.acc[call_med->call->acc_id].cfg.ice_cfg.enable_ice) { status = create_ice_media_transport(tcfg, call_med, async); if (async && status == PJ_EPENDING) { /* We will resume call media initialization in the @@ -1245,7 +1316,7 @@ pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, */ call_med->med_create_cb = &call_media_init_cb; call_med->med_init_cb = cb; - + return PJ_EPENDING; } } else { @@ -1344,7 +1415,7 @@ static pj_status_t media_channel_init_cb(pjsua_call_id call_id, } if (call_med->use_custom_med_tp) { - unsigned custom_med_tp_flags = 0; + unsigned custom_med_tp_flags = PJSUA_MED_TP_CLOSE_MEMBER; /* Use custom media transport returned by the application */ call_med->tp = @@ -1388,12 +1459,12 @@ on_return: /* Clean up media transports in provisional media that is not used * by call media. */ -void pjsua_media_prov_clean_up(pjsua_call_id call_id) +static void media_prov_clean_up(pjsua_call_id call_id, int idx) { pjsua_call *call = &pjsua_var.calls[call_id]; unsigned i; - for (i = 0; i < call->med_prov_cnt; ++i) { + for (i = idx; i < call->med_prov_cnt; ++i) { pjsua_call_media *call_med = &call->media_prov[i]; unsigned j; pj_bool_t used = PJ_FALSE; @@ -1420,6 +1491,11 @@ void pjsua_media_prov_clean_up(pjsua_call_id call_id) } } +void pjsua_media_prov_clean_up(pjsua_call_id call_id) +{ + media_prov_clean_up(call_id, 0); +} + pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, pjsip_role_e role, @@ -1452,8 +1528,10 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, * (e.g. in reinvites, updates, etc). */ - if (pjsua_get_state() != PJSUA_STATE_RUNNING) + if (pjsua_get_state() != PJSUA_STATE_RUNNING) { + if (sip_err_code) *sip_err_code = PJSIP_SC_SERVICE_UNAVAILABLE; return PJ_EBUSY; + } if (async) { pj_pool_t *tmppool = (call->inv? call->inv->pool_prov: @@ -1831,6 +1909,8 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, if (rem_sdp && mi >= rem_sdp->media_count) { /* Remote might have removed some media lines. */ + media_prov_clean_up(call->index, rem_sdp->media_count); + call->med_prov_cnt = rem_sdp->media_count; break; } @@ -1848,10 +1928,6 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); m->desc.transport = pj_str("RTP/AVP"); m->desc.fmt_count = 1; - m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); - m->conn->net_type = pj_str("IN"); - m->conn->addr_type = pj_str("IP4"); - m->conn->addr = pj_str("127.0.0.1"); switch (call_med->type) { case PJMEDIA_TYPE_AUDIO: @@ -1881,6 +1957,24 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, } } + /* Add connection line, if none */ + if (m->conn == NULL && sdp->conn == NULL) { + pj_bool_t use_ipv6; + + use_ipv6 = (pjsua_var.acc[call->acc_id].cfg.ipv6_media_use != + PJSUA_IPV6_DISABLED); + + m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + m->conn->net_type = pj_str("IN"); + if (use_ipv6) { + m->conn->addr_type = pj_str("IP6"); + m->conn->addr = pj_str("::1"); + } else { + m->conn->addr_type = pj_str("IP4"); + m->conn->addr = pj_str("127.0.0.1"); + } + } + sdp->media[sdp->media_count++] = m; continue; } @@ -1919,12 +2013,14 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, return status; } +#if PJSUA_SDP_SESS_HAS_CONN /* Copy c= line of the first media to session level, * if there's none. */ if (sdp->conn == NULL) { sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn); } +#endif /* Find media bandwidth info */ @@ -2049,63 +2145,73 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, } -static void stop_media_session(pjsua_call_id call_id) +static void stop_media_stream(pjsua_call *call, unsigned med_idx) { - pjsua_call *call = &pjsua_var.calls[call_id]; - unsigned mi; + pjsua_call_media *call_med = &call->media[med_idx]; - pj_log_push_indent(); + /* Check if stream does not exist */ + if (med_idx >= call->med_cnt) + return; - for (mi=0; mimed_cnt; ++mi) { - pjsua_call_media *call_med = &call->media[mi]; + pj_log_push_indent(); - if (call_med->type == PJMEDIA_TYPE_AUDIO) { - pjsua_aud_stop_stream(call_med); - } + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + pjsua_aud_stop_stream(call_med); + } #if PJMEDIA_HAS_VIDEO - else if (call_med->type == PJMEDIA_TYPE_VIDEO) { - pjsua_vid_stop_stream(call_med); - } + else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + pjsua_vid_stop_stream(call_med); + } #endif - PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed", - call_id, mi)); - call_med->prev_state = call_med->state; - call_med->state = PJSUA_CALL_MEDIA_NONE; + PJ_LOG(4,(THIS_FILE, "Media stream call%02d:%d is destroyed", + call->index, med_idx)); + call_med->prev_state = call_med->state; + call_med->state = PJSUA_CALL_MEDIA_NONE; - /* Try to sync recent changes to provisional media */ - if (mimed_prov_cnt && call->media_prov[mi].tp==call_med->tp) - { - pjsua_call_media *prov_med = &call->media_prov[mi]; + /* Try to sync recent changes to provisional media */ + if (med_idx < call->med_prov_cnt && + call->media_prov[med_idx].tp == call_med->tp) + { + pjsua_call_media *prov_med = &call->media_prov[med_idx]; - /* Media state */ - prov_med->prev_state = call_med->prev_state; - prov_med->state = call_med->state; + /* Media state */ + prov_med->prev_state = call_med->prev_state; + prov_med->state = call_med->state; - /* RTP seq/ts */ - prov_med->rtp_tx_seq_ts_set = call_med->rtp_tx_seq_ts_set; - prov_med->rtp_tx_seq = call_med->rtp_tx_seq; - prov_med->rtp_tx_ts = call_med->rtp_tx_ts; + /* RTP seq/ts */ + prov_med->rtp_tx_seq_ts_set = call_med->rtp_tx_seq_ts_set; + prov_med->rtp_tx_seq = call_med->rtp_tx_seq; + prov_med->rtp_tx_ts = call_med->rtp_tx_ts; - /* Stream */ - if (call_med->type == PJMEDIA_TYPE_AUDIO) { - prov_med->strm.a.conf_slot = call_med->strm.a.conf_slot; - prov_med->strm.a.stream = call_med->strm.a.stream; - } + /* Stream */ + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + prov_med->strm.a.conf_slot = call_med->strm.a.conf_slot; + prov_med->strm.a.stream = call_med->strm.a.stream; + } #if PJMEDIA_HAS_VIDEO - else if (call_med->type == PJMEDIA_TYPE_VIDEO) { - prov_med->strm.v.cap_win_id = call_med->strm.v.cap_win_id; - prov_med->strm.v.rdr_win_id = call_med->strm.v.rdr_win_id; - prov_med->strm.v.stream = call_med->strm.v.stream; - } -#endif + else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + prov_med->strm.v.cap_win_id = call_med->strm.v.cap_win_id; + prov_med->strm.v.rdr_win_id = call_med->strm.v.rdr_win_id; + prov_med->strm.v.stream = call_med->strm.v.stream; } +#endif } pj_log_pop_indent(); } +static void stop_media_session(pjsua_call_id call_id) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + unsigned mi; + + for (mi=0; mimed_cnt; ++mi) { + stop_media_stream(call, mi); + } +} + pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id) { pjsua_call *call = &pjsua_var.calls[call_id]; @@ -2153,6 +2259,197 @@ pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id) } +/* Match codec fmtp. This will compare the values and the order. */ +static pj_bool_t match_codec_fmtp(const pjmedia_codec_fmtp *fmtp1, + const pjmedia_codec_fmtp *fmtp2) +{ + unsigned i; + + if (fmtp1->cnt != fmtp2->cnt) + return PJ_FALSE; + + for (i = 0; i < fmtp1->cnt; ++i) { + if (pj_stricmp(&fmtp1->param[i].name, &fmtp2->param[i].name)) + return PJ_FALSE; + if (pj_stricmp(&fmtp1->param[i].val, &fmtp2->param[i].val)) + return PJ_FALSE; + } + + return PJ_TRUE; +} + +#if PJSUA_MEDIA_HAS_PJMEDIA || PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO + +static pj_bool_t is_ice_running(pjmedia_transport *tp) +{ + pjmedia_transport_info tpinfo; + pjmedia_ice_transport_info *ice_info; + + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(tp, &tpinfo); + ice_info = (pjmedia_ice_transport_info*) + pjmedia_transport_info_get_spc_info(&tpinfo, + PJMEDIA_TRANSPORT_TYPE_ICE); + return (ice_info && ice_info->sess_state == PJ_ICE_STRANS_STATE_RUNNING); +} + + +static pj_bool_t is_media_changed(const pjsua_call *call, + unsigned med_idx, + const pjsua_stream_info *new_si_) +{ + const pjsua_call_media *call_med = &call->media[med_idx]; + + /* Check for newly added media */ + if (med_idx >= call->med_cnt) + return PJ_TRUE; + + /* Compare media type */ + if (call_med->type != new_si_->type) + return PJ_TRUE; + + /* Audio update checks */ + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + pjmedia_stream_info the_old_si; + const pjmedia_stream_info *old_si = NULL; + const pjmedia_stream_info *new_si = &new_si_->info.aud; + const pjmedia_codec_info *old_ci = NULL; + const pjmedia_codec_info *new_ci = &new_si->fmt; + const pjmedia_codec_param *old_cp = NULL; + const pjmedia_codec_param *new_cp = new_si->param; + + /* Compare media direction */ + if (call_med->dir != new_si->dir) + return PJ_TRUE; + + /* Get current active stream info */ + if (call_med->strm.a.stream) { + pjmedia_stream_get_info(call_med->strm.a.stream, &the_old_si); + old_si = &the_old_si; + old_ci = &old_si->fmt; + old_cp = old_si->param; + } else { + /* The stream is inactive. */ + return (new_si->dir != PJMEDIA_DIR_NONE); + } + + /* Compare remote RTP address. If ICE is running, change in default + * address can happen after negotiation, this can be handled + * internally by ICE and does not need to cause media restart. + */ + if (!is_ice_running(call_med->tp) && + pj_sockaddr_cmp(&old_si->rem_addr, &new_si->rem_addr)) + { + return PJ_TRUE; + } + + /* Compare codec info */ + if (pj_stricmp(&old_ci->encoding_name, &new_ci->encoding_name) || + old_ci->clock_rate != new_ci->clock_rate || + old_ci->channel_cnt != new_ci->channel_cnt || + old_si->rx_pt != new_si->rx_pt || + old_si->tx_pt != new_si->tx_pt || + old_si->rx_event_pt != new_si->tx_event_pt || + old_si->tx_event_pt != new_si->tx_event_pt) + { + return PJ_TRUE; + } + + /* Compare codec param */ + if (old_cp->setting.frm_per_pkt != new_cp->setting.frm_per_pkt || + old_cp->setting.vad != new_cp->setting.vad || + old_cp->setting.cng != new_cp->setting.cng || + old_cp->setting.plc != new_cp->setting.plc || + old_cp->setting.penh != new_cp->setting.penh || + !match_codec_fmtp(&old_cp->setting.dec_fmtp, + &new_cp->setting.dec_fmtp) || + !match_codec_fmtp(&old_cp->setting.enc_fmtp, + &new_cp->setting.enc_fmtp)) + { + return PJ_TRUE; + } + } + +#if PJMEDIA_HAS_VIDEO + else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + pjmedia_vid_stream_info the_old_si; + const pjmedia_vid_stream_info *old_si = NULL; + const pjmedia_vid_stream_info *new_si = &new_si_->info.vid; + const pjmedia_vid_codec_info *old_ci = NULL; + const pjmedia_vid_codec_info *new_ci = &new_si->codec_info; + const pjmedia_vid_codec_param *old_cp = NULL; + const pjmedia_vid_codec_param *new_cp = new_si->codec_param; + + /* Compare media direction */ + if (call_med->dir != new_si->dir) + return PJ_TRUE; + + /* Get current active stream info */ + if (call_med->strm.v.stream) { + pjmedia_vid_stream_get_info(call_med->strm.v.stream, &the_old_si); + old_si = &the_old_si; + old_ci = &old_si->codec_info; + old_cp = old_si->codec_param; + } else { + /* The stream is inactive. */ + return (new_si->dir != PJMEDIA_DIR_NONE); + } + + /* Compare remote RTP address. If ICE is running, change in default + * address can happen after negotiation, this can be handled + * internally by ICE and does not need to cause media restart. + */ + if (!is_ice_running(call_med->tp) && + pj_sockaddr_cmp(&old_si->rem_addr, &new_si->rem_addr)) + { + return PJ_TRUE; + } + + /* Compare codec info */ + if (pj_stricmp(&old_ci->encoding_name, &new_ci->encoding_name) || + old_si->rx_pt != new_si->rx_pt || + old_si->tx_pt != new_si->tx_pt) + { + return PJ_TRUE; + } + + /* Compare codec param */ + if (/* old_cp->enc_mtu != new_cp->enc_mtu || */ + pj_memcmp(&old_cp->enc_fmt.det, &new_cp->enc_fmt.det, + sizeof(pjmedia_video_format_detail)) || + !match_codec_fmtp(&old_cp->dec_fmtp, &new_cp->dec_fmtp) || + !match_codec_fmtp(&old_cp->enc_fmtp, &new_cp->enc_fmtp)) + { + return PJ_TRUE; + } + } + +#endif + + else { + /* Just return PJ_TRUE for other media type */ + return PJ_TRUE; + } + + return PJ_FALSE; +} + +#else /* PJSUA_MEDIA_HAS_PJMEDIA || PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO */ + +static pj_bool_t is_media_changed(const pjsua_call *call, + unsigned med_idx, + const pjsua_stream_info *new_si_) +{ + PJ_UNUSED_ARG(call); + PJ_UNUSED_ARG(med_idx); + PJ_UNUSED_ARG(new_si_); + /* Always assume that media has been changed */ + return PJ_TRUE; +} + +#endif /* PJSUA_MEDIA_HAS_PJMEDIA || PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO */ + + pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, const pjmedia_sdp_session *local_sdp, const pjmedia_sdp_session *remote_sdp) @@ -2181,7 +2478,7 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, pj_log_push_indent(); /* Destroy existing media session, if any. */ - stop_media_session(call->index); + //stop_media_session(call->index); /* Call media count must be at least equal to SDP media. Note that * it may not be equal when remote removed any SDP media line. @@ -2235,6 +2532,7 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, /* Process each media stream */ for (mi=0; mi < call->med_prov_cnt; ++mi) { pjsua_call_media *call_med = &call->media_prov[mi]; + pj_bool_t media_changed = PJ_FALSE; if (mi >= local_sdp->media_count || mi >= remote_sdp->media_count) @@ -2242,8 +2540,12 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, /* This may happen when remote removed any SDP media lines in * its re-offer. */ + + /* Stop stream */ + stop_media_stream(call, mi); + + /* Close the media transport */ if (call_med->tp) { - /* Close the media transport */ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL); pjmedia_transport_close(call_med->tp); call_med->tp = call_med->tp_orig = NULL; @@ -2258,11 +2560,14 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, #endif } + /* Apply media update action */ if (call_med->type==PJMEDIA_TYPE_AUDIO) { pjmedia_stream_info the_si, *si = &the_si; + pjsua_stream_info stream_info; - status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, - local_sdp, remote_sdp, mi); + status = pjmedia_stream_info_from_sdp( + si, tmp_pool, pjsua_var.med_endpt, + local_sdp, remote_sdp, mi); if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "pjmedia_stream_info_from_sdp() failed " @@ -2271,14 +2576,43 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, continue; } + /* Override ptime, if this option is specified. */ + if (pjsua_var.media_cfg.ptime != 0) { + si->param->setting.frm_per_pkt = (pj_uint8_t) + (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime); + if (si->param->setting.frm_per_pkt == 0) + si->param->setting.frm_per_pkt = 1; + } + + /* Disable VAD, if this option is specified. */ + if (pjsua_var.media_cfg.no_vad) { + si->param->setting.vad = 0; + } + + /* Check if this media is changed */ + stream_info.type = PJMEDIA_TYPE_AUDIO; + stream_info.info.aud = the_si; + if (pjsua_var.media_cfg.no_smart_media_update || + is_media_changed(call, mi, &stream_info)) + { + media_changed = PJ_TRUE; + /* Stop the media */ + stop_media_stream(call, mi); + } else { + PJ_LOG(4,(THIS_FILE, "Call %d: stream #%d (audio) unchanged.", + call_id, mi)); + } + /* Check if no media is active */ if (si->dir == PJMEDIA_DIR_NONE) { + /* Update call media state and direction */ call_med->state = PJSUA_CALL_MEDIA_NONE; call_med->dir = PJMEDIA_DIR_NONE; } else { pjmedia_transport_info tp_info; + pjmedia_srtp_info *srtp_info; /* Start/restart media transport based on info in SDP */ status = pjmedia_transport_media_start(call_med->tp, @@ -2297,17 +2631,24 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, /* Get remote SRTP usage policy */ pjmedia_transport_info_init(&tp_info); pjmedia_transport_get_info(call_med->tp, &tp_info); - if (tp_info.specific_info_cnt > 0) { - unsigned i; - for (i = 0; i < tp_info.specific_info_cnt; ++i) { - if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP) - { - pjmedia_srtp_info *srtp_info = - (pjmedia_srtp_info*) tp_info.spc_info[i].buffer; - - call_med->rem_srtp_use = srtp_info->peer_use; - break; - } + srtp_info = (pjmedia_srtp_info*) + pjmedia_transport_info_get_spc_info( + &tp_info, PJMEDIA_TRANSPORT_TYPE_SRTP); + if (srtp_info) { + call_med->rem_srtp_use = srtp_info->peer_use; + } + + /* Update audio channel */ + if (media_changed) { + status = pjsua_aud_channel_update(call_med, + call->inv->pool, si, + local_sdp, remote_sdp); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "pjsua_aud_channel_update() failed " + "for call_id %d media %d", + call_id, mi)); + continue; } } @@ -2323,17 +2664,6 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, call_med->state = PJSUA_CALL_MEDIA_ACTIVE; } - /* Call implementation */ - status = pjsua_aud_channel_update(call_med, tmp_pool, si, - local_sdp, remote_sdp); - if (status != PJ_SUCCESS) { - PJ_PERROR(1,(THIS_FILE, status, - "pjsua_aud_channel_update() failed " - "for call_id %d media %d", - call_id, mi)); - continue; - } - /* Print info. */ if (status == PJ_SUCCESS) { char info[80]; @@ -2378,9 +2708,11 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) } else if (call_med->type==PJMEDIA_TYPE_VIDEO) { pjmedia_vid_stream_info the_si, *si = &the_si; + pjsua_stream_info stream_info; - status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, - local_sdp, remote_sdp, mi); + status = pjmedia_vid_stream_info_from_sdp( + si, tmp_pool, pjsua_var.med_endpt, + local_sdp, remote_sdp, mi); if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "pjmedia_vid_stream_info_from_sdp() failed " @@ -2389,14 +2721,28 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, continue; } + /* Check if this media is changed */ + stream_info.type = PJMEDIA_TYPE_VIDEO; + stream_info.info.vid = the_si; + if (is_media_changed(call, mi, &stream_info)) { + media_changed = PJ_TRUE; + /* Stop the media */ + stop_media_stream(call, mi); + } else { + PJ_LOG(4,(THIS_FILE, "Call %d: stream #%d (video) unchanged.", + call_id, mi)); + } + /* Check if no media is active */ if (si->dir == PJMEDIA_DIR_NONE) { + /* Update call media state and direction */ call_med->state = PJSUA_CALL_MEDIA_NONE; call_med->dir = PJMEDIA_DIR_NONE; } else { pjmedia_transport_info tp_info; + pjmedia_srtp_info *srtp_info; /* Start/restart media transport */ status = pjmedia_transport_media_start(call_med->tp, @@ -2415,17 +2761,24 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, /* Get remote SRTP usage policy */ pjmedia_transport_info_init(&tp_info); pjmedia_transport_get_info(call_med->tp, &tp_info); - if (tp_info.specific_info_cnt > 0) { - unsigned i; - for (i = 0; i < tp_info.specific_info_cnt; ++i) { - if (tp_info.spc_info[i].type == - PJMEDIA_TRANSPORT_TYPE_SRTP) - { - pjmedia_srtp_info *sri; - sri=(pjmedia_srtp_info*)tp_info.spc_info[i].buffer; - call_med->rem_srtp_use = sri->peer_use; - break; - } + srtp_info = (pjmedia_srtp_info*) + pjmedia_transport_info_get_spc_info( + &tp_info, PJMEDIA_TRANSPORT_TYPE_SRTP); + if (srtp_info) { + call_med->rem_srtp_use = srtp_info->peer_use; + } + + /* Update audio channel */ + if (media_changed) { + status = pjsua_vid_channel_update(call_med, + call->inv->pool, si, + local_sdp, remote_sdp); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "pjsua_vid_channel_update() failed " + "for call_id %d media %d", + call_id, mi)); + continue; } } @@ -2441,16 +2794,6 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, call_med->state = PJSUA_CALL_MEDIA_ACTIVE; } - status = pjsua_vid_channel_update(call_med, tmp_pool, si, - local_sdp, remote_sdp); - if (status != PJ_SUCCESS) { - PJ_PERROR(1,(THIS_FILE, status, - "pjsua_vid_channel_update() failed " - "for call_id %d media %d", - call_id, mi)); - continue; - } - /* Print info. */ { char info[80]; diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c index 4551c91..591f65b 100644 --- a/pjsip/src/pjsua-lib/pjsua_pres.c +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -1,4 +1,4 @@ -/* $Id: pjsua_pres.c 4186 2012-06-29 01:37:50Z ming $ */ +/* $Id: pjsua_pres.c 4294 2012-11-06 05:02:10Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono @@ -864,8 +864,31 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) return PJ_TRUE; } - if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) { pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp); + } else if (!pjsua_sip_acc_is_using_stun(acc_id)) { + /* Choose local interface to use in Via if acc is not using + * STUN. See https://trac.pjsip.org/repos/ticket/1412 + */ + char target_buf[PJSIP_MAX_URL_SIZE]; + pj_str_t target; + pjsip_host_port via_addr; + const void *via_tp; + + target.ptr = target_buf; + target.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + dlg->target, + target_buf, sizeof(target_buf)); + if (target.slen < 0) target.slen = 0; + + if (pjsua_acc_get_uac_addr(acc_id, dlg->pool, &target, + &via_addr, NULL, NULL, + &via_tp) == PJ_SUCCESS) + { + pjsip_dlg_set_via_sent_by(dlg, &via_addr, + (pjsip_transport*)via_tp); + } + } /* Set credentials and preference. */ pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->cred_cnt, acc->cred); @@ -1236,6 +1259,20 @@ static pj_status_t send_publish(int acc_id, pj_bool_t active) if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) { pjsip_publishc_set_via_sent_by(acc->publish_sess, &acc->via_addr, acc->via_tp); + } else if (!pjsua_sip_acc_is_using_stun(acc_id)) { + /* Choose local interface to use in Via if acc is not using + * STUN. See https://trac.pjsip.org/repos/ticket/1412 + */ + pjsip_host_port via_addr; + const void *via_tp; + + if (pjsua_acc_get_uac_addr(acc_id, acc->pool, &acc_cfg->id, + &via_addr, NULL, NULL, + &via_tp) == PJ_SUCCESS) + { + pjsip_publishc_set_via_sent_by(acc->publish_sess, &via_addr, + (pjsip_transport*)via_tp); + } } /* Send the PUBLISH request */ @@ -1789,8 +1826,24 @@ static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) */ pjsip_dlg_inc_lock(buddy->dlg); - if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) { pjsip_dlg_set_via_sent_by(buddy->dlg, &acc->via_addr, acc->via_tp); + } else if (!pjsua_sip_acc_is_using_stun(acc_id)) { + /* Choose local interface to use in Via if acc is not using + * STUN. See https://trac.pjsip.org/repos/ticket/1412 + */ + pjsip_host_port via_addr; + const void *via_tp; + + if (pjsua_acc_get_uac_addr(acc_id, buddy->dlg->pool, &buddy->uri, + &via_addr, NULL, NULL, + &via_tp) == PJ_SUCCESS) + { + pjsip_dlg_set_via_sent_by(buddy->dlg, &via_addr, + (pjsip_transport*)via_tp); + } + } + status = pjsip_pres_create_uac( buddy->dlg, &pres_callback, PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub); @@ -2114,8 +2167,23 @@ pj_status_t pjsua_start_mwi(pjsua_acc_id acc_id, pj_bool_t force_renew) */ pjsip_dlg_inc_lock(acc->mwi_dlg); - if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) { pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &acc->via_addr, acc->via_tp); + } else if (!pjsua_sip_acc_is_using_stun(acc_id)) { + /* Choose local interface to use in Via if acc is not using + * STUN. See https://trac.pjsip.org/repos/ticket/1412 + */ + pjsip_host_port via_addr; + const void *via_tp; + + if (pjsua_acc_get_uac_addr(acc_id, acc->mwi_dlg->pool, &acc->cfg.id, + &via_addr, NULL, NULL, + &via_tp) == PJ_SUCCESS) + { + pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &via_addr, + (pjsip_transport*)via_tp); + } + } /* Create UAC subscription */ status = pjsip_mwi_create_uac(acc->mwi_dlg, &mwi_cb, diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c index eb74ee1..c92ec56 100644 --- a/pjsip/src/pjsua-lib/pjsua_vid.c +++ b/pjsip/src/pjsua-lib/pjsua_vid.c @@ -1,4 +1,4 @@ -/* $Id: pjsua_vid.c 4071 2012-04-24 05:40:32Z nanang $ */ +/* $Id: pjsua_vid.c 4341 2013-02-05 12:21:30Z nanang $ */ /* * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) * @@ -705,7 +705,12 @@ pj_status_t pjsua_vid_channel_init(pjsua_call_media *call_med) return PJ_SUCCESS; } -/* Internal function: update video channel after SDP negotiation */ +/* Internal function: update video channel after SDP negotiation. + * Warning: do not use temporary/flip-flop pool, e.g: inv->pool_prov, + * for creating stream, etc, as after SDP negotiation and when + * the SDP media is not changed, the stream should remain running + * while the temporary/flip-flop pool may be released. + */ pj_status_t pjsua_vid_channel_update(pjsua_call_media *call_med, pj_pool_t *tmp_pool, pjmedia_vid_stream_info *si, @@ -1743,11 +1748,13 @@ static pj_status_t call_modify_video(pjsua_call *call, sdp->media[med_idx] = sdp_m; - /* Update SDP media line by media transport */ - status = pjmedia_transport_encode_sdp(call_med->tp, pool, - sdp, NULL, call_med->idx); - if (status != PJ_SUCCESS) - goto on_error; + if (call_med->dir == PJMEDIA_DIR_NONE) { + /* Update SDP media line by media transport */ + status = pjmedia_transport_encode_sdp(call_med->tp, pool, + sdp, NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + } on_error: if (status != PJ_SUCCESS) { @@ -2029,6 +2036,7 @@ PJ_DEF(pj_status_t) pjsua_call_set_vid_strm ( const pjsua_call_vid_strm_op_param *param) { pjsua_call *call; + pjsip_dialog *dlg; pjsua_call_vid_strm_op_param param_; pj_status_t status; @@ -2040,9 +2048,9 @@ PJ_DEF(pj_status_t) pjsua_call_set_vid_strm ( call_id, op)); pj_log_push_indent(); - PJSUA_LOCK(); - - call = &pjsua_var.calls[call_id]; + status = acquire_call("pjsua_call_set_vid_strm()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; if (param) { param_ = *param; @@ -2097,9 +2105,9 @@ PJ_DEF(pj_status_t) pjsua_call_set_vid_strm ( break; } - PJSUA_UNLOCK(); +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); pj_log_pop_indent(); - return status; } -- cgit v1.2.3