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_media.c | 767 +++++++++++++++++++++++++++----------- 1 file changed, 555 insertions(+), 212 deletions(-) (limited to 'pjsip/src/pjsua-lib/pjsua_media.c') 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]; -- cgit v1.2.3