diff options
author | Nanang Izzuddin <nanang@teluu.com> | 2011-07-19 03:42:28 +0000 |
---|---|---|
committer | Nanang Izzuddin <nanang@teluu.com> | 2011-07-19 03:42:28 +0000 |
commit | cd283c8825c9a94400f27735acb1c9385e90ffc8 (patch) | |
tree | 56d5722310fa8957ce5d1ba7cbd137cf8802dcc7 /pjsip/src/pjsua-lib | |
parent | ed8f8d08abba9040f769e922aa0c1adbde86fbbc (diff) |
Re #1326: Initial code integration from branch 2.0-dev to trunk as "2.0-pre-alpha-svn".
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3664 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src/pjsua-lib')
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_acc.c | 3 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 1326 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 60 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_dump.c | 944 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 1933 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_vid.c | 1648 |
6 files changed, 4138 insertions, 1776 deletions
diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index aec9627c..6947c1aa 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -130,7 +130,8 @@ PJ_DEF(void) pjsua_acc_config_dup( pj_pool_t *pool, pjsip_auth_clt_pref_dup(pool, &dst->auth_pref, &src->auth_pref); - dst->ka_interval = src->ka_interval; + pjsua_transport_config_dup(pool, &dst->rtp_cfg, &src->rtp_cfg); + pj_strdup(pool, &dst->ka_data, &src->ka_data); } diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 8c9f16db..1c835e15 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -101,29 +101,21 @@ static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); static void reset_call(pjsua_call_id id) { pjsua_call *call = &pjsua_var.calls[id]; + unsigned i; + pj_bzero(call, sizeof(*call)); call->index = id; - call->inv = NULL; - call->user_data = NULL; - call->session = NULL; - call->audio_idx = -1; - call->ssrc = pj_rand(); - call->rtp_tx_seq = 0; - call->rtp_tx_ts = 0; - call->rtp_tx_seq_ts_set = 0; - call->xfer_sub = NULL; - call->last_code = (pjsip_status_code) 0; - call->conf_slot = PJSUA_INVALID_ID; call->last_text.ptr = call->last_text_buf_; - call->last_text.slen = 0; - call->conn_time.sec = 0; - call->conn_time.msec = 0; - call->res_time.sec = 0; - call->res_time.msec = 0; - call->rem_nat_type = PJ_STUN_NAT_TYPE_UNKNOWN; - call->rem_srtp_use = PJMEDIA_SRTP_DISABLED; - call->local_hold = PJ_FALSE; - pj_bzero(&call->lock_codec, sizeof(call->lock_codec)); + for (i=0; i<PJ_ARRAY_SIZE(call->media); ++i) { + pjsua_call_media *call_med = &call->media[i]; + call_med->ssrc = pj_rand(); + call_med->strm.a.conf_slot = PJSUA_INVALID_ID; + call_med->strm.v.cap_win_id = PJSUA_INVALID_ID; + call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID; + call_med->call = call; + call_med->idx = i; + call_med->tp_auto_del = PJ_TRUE; + } } @@ -823,7 +815,8 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) /* Init media channel */ status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS, call->secure_level, - rdata->tp_info.pool, offer, + rdata->tp_info.pool, + offer, &sip_err_code); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing media channel", status); @@ -1118,31 +1111,10 @@ PJ_DEF(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id) */ PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id) { + pjsua_call *call = &pjsua_var.calls[call_id]; PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, PJ_EINVAL); - return pjsua_var.calls[call_id].session != NULL; -} - - -/* - * Retrieve the media session associated with this call. - */ -PJ_DEF(pjmedia_session*) pjsua_call_get_media_session(pjsua_call_id call_id) -{ - PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, - NULL); - return pjsua_var.calls[call_id].session; -} - - -/* - * Retrieve the media transport instance that is used for this call. - */ -PJ_DEF(pjmedia_transport*) pjsua_call_get_media_transport(pjsua_call_id cid) -{ - PJ_ASSERT_RETURN(cid>=0 && cid<(int)pjsua_var.ua_cfg.max_calls, - NULL); - return pjsua_var.calls[cid].med_tp; + return call->audio_idx >= 0 && call->media[call->audio_idx].strm.a.stream; } @@ -1238,7 +1210,7 @@ PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id) if (status != PJ_SUCCESS) return PJSUA_INVALID_ID; - port_id = call->conf_slot; + port_id = call->media[call->audio_idx].strm.a.conf_slot; pjsip_dlg_dec_lock(dlg); @@ -1255,6 +1227,7 @@ PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id, { pjsua_call *call; pjsip_dialog *dlg; + unsigned mi; pj_status_t status; PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, @@ -1329,13 +1302,43 @@ PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id, sizeof(info->buf_.last_status_text)); } - /* media status and dir */ - info->media_status = call->media_st; - info->media_dir = call->media_dir; + /* Build array of media status and dir */ + info->media_cnt = 0; + for (mi=0; mi < call->med_cnt && + info->media_cnt < PJ_ARRAY_SIZE(info->media); ++mi) + { + pjsua_call_media *call_med = &call->media[mi]; + + info->media[info->media_cnt].index = mi; + info->media[info->media_cnt].status = call_med->state; + info->media[info->media_cnt].dir = call_med->dir; + info->media[info->media_cnt].type = call_med->type; + + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + info->media[info->media_cnt].stream.aud.conf_slot = + call_med->strm.a.conf_slot; + } else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + pjmedia_vid_dev_index cap_dev = PJMEDIA_VID_INVALID_DEV; + + info->media[info->media_cnt].stream.vid.win_in = + call_med->strm.v.rdr_win_id; + if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) { + pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.cap_win_id]; + cap_dev = w->preview_cap_id; + } + info->media[info->media_cnt].stream.vid.cap_dev = cap_dev; + } else { + continue; + } + ++info->media_cnt; + } - /* conference slot number */ - info->conf_slot = call->conf_slot; + if (call->audio_idx != -1) { + info->media_status = call->media[call->audio_idx].state; + info->media_dir = call->media[call->audio_idx].dir; + info->conf_slot = call->media[call->audio_idx].strm.a.conf_slot; + } /* calculate duration */ if (info->state >= PJSIP_INV_STATE_DISCONNECTED) { @@ -1433,6 +1436,135 @@ PJ_DEF(pj_status_t) pjsua_call_get_rem_nat_type(pjsua_call_id call_id, /* + * Get media stream info for the specified media index. + */ +PJ_DEF(pj_status_t) pjsua_call_get_stream_info( pjsua_call_id call_id, + unsigned med_idx, + pjsua_stream_info *psi) +{ + pjsua_call *call; + pjsua_call_media *call_med; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(psi, PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (med_idx >= call->med_cnt) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + call_med = &call->media[med_idx]; + psi->type = call_med->type; + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + status = pjmedia_stream_get_info(call_med->strm.a.stream, + &psi->info.aud); + break; + case PJMEDIA_TYPE_VIDEO: + status = pjmedia_vid_stream_get_info(call_med->strm.v.stream, + &psi->info.vid); + break; + default: + status = PJMEDIA_EINVALIMEDIATYPE; + break; + } + + PJSUA_UNLOCK(); + return status; +} + + +/* + * Get media stream statistic for the specified media index. + */ +PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id, + unsigned med_idx, + pjsua_stream_stat *stat) +{ + pjsua_call *call; + pjsua_call_media *call_med; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(stat, PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (med_idx >= call->med_cnt) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + call_med = &call->media[med_idx]; + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + status = pjmedia_stream_get_stat(call_med->strm.a.stream, + &stat->rtcp); + if (status == PJ_SUCCESS) + status = pjmedia_stream_get_stat_jbuf(call_med->strm.a.stream, + &stat->jbuf); + break; + case PJMEDIA_TYPE_VIDEO: + status = pjmedia_vid_stream_get_stat(call_med->strm.v.stream, + &stat->rtcp); + if (status == PJ_SUCCESS) + status = pjmedia_vid_stream_get_stat_jbuf(call_med->strm.v.stream, + &stat->jbuf); + break; + default: + status = PJMEDIA_EINVALIMEDIATYPE; + break; + } + + PJSUA_UNLOCK(); + return status; +} + + +/* + * Get media transport info for the specified media index. + */ +PJ_DEF(pj_status_t) pjsua_call_get_transport_info( pjsua_call_id call_id, + unsigned med_idx, + pjmedia_transport_info *t) +{ + pjsua_call *call; + pjsua_call_media *call_med; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(t, PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (med_idx >= call->med_cnt) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + call_med = &call->media[med_idx]; + + pjmedia_transport_info_init(t); + status = pjmedia_transport_get_info(call_med->tp, t); + + PJSUA_UNLOCK(); + return status; +} + + +/* * Send response to incoming INVITE request. */ PJ_DEF(pj_status_t) pjsua_call_answer( pjsua_call_id call_id, @@ -1973,13 +2105,14 @@ PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, if (status != PJ_SUCCESS) return status; - if (!call->session) { + if (!pjsua_call_has_media(call_id)) { PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); pjsip_dlg_dec_lock(dlg); return PJ_EINVALIDOP; } - status = pjmedia_session_dial_dtmf( call->session, 0, digits); + status = pjmedia_stream_dial_dtmf( + call->media[call->audio_idx].strm.a.stream, digits); pjsip_dlg_dec_lock(dlg); @@ -2178,835 +2311,6 @@ PJ_DEF(void) pjsua_call_hangup_all(void) } -const char *good_number(char *buf, pj_int32_t val) -{ - if (val < 1000) { - pj_ansi_sprintf(buf, "%d", val); - } else if (val < 1000000) { - pj_ansi_sprintf(buf, "%d.%dK", - val / 1000, - (val % 1000) / 100); - } else { - pj_ansi_sprintf(buf, "%d.%02dM", - val / 1000000, - (val % 1000000) / 10000); - } - - return buf; -} - - -/* Dump media session */ -static void dump_media_session(const char *indent, - char *buf, unsigned maxlen, - pjsua_call *call) -{ - unsigned i; - char *p = buf, *end = buf+maxlen; - int len; - pjmedia_session_info info; - pjmedia_session *session = call->session; - pjmedia_transport_info tp_info; - - pjmedia_transport_info_init(&tp_info); - - pjmedia_transport_get_info(call->med_tp, &tp_info); - pjmedia_session_get_info(session, &info); - - for (i=0; i<info.stream_cnt; ++i) { - pjmedia_rtcp_stat stat; - char rem_addr_buf[80]; - const char *rem_addr; - const char *dir; - char last_update[64]; - char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32]; - pj_time_val media_duration, now; - - pjmedia_session_get_stream_stat(session, i, &stat); - // rem_addr will contain actual address of RTP originator, instead of - // remote RTP address specified by stream which is fetched from the SDP. - // Please note that we are assuming only one stream per call. - //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr, - // rem_addr_buf, sizeof(rem_addr_buf), 3); - if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) { - rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf, - sizeof(rem_addr_buf), 3); - } else { - pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-"); - rem_addr = rem_addr_buf; - } - - if (call->media_dir == PJMEDIA_DIR_NONE) { - /* To handle when the stream that is currently being paused - * (http://trac.pjsip.org/repos/ticket/1079) - */ - dir = "inactive"; - } else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING) - dir = "sendonly"; - else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING) - dir = "recvonly"; - else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING) - dir = "sendrecv"; - else - dir = "inactive"; - - - len = pj_ansi_snprintf(buf, end-p, - "%s #%d %.*s @%dKHz, %s, peer=%s", - indent, i, - (int)info.stream_info[i].fmt.encoding_name.slen, - info.stream_info[i].fmt.encoding_name.ptr, - info.stream_info[i].fmt.clock_rate / 1000, - dir, - rem_addr); - if (len < 1 || len > end-p) { - *p = '\0'; - return; - } - - p += len; - *p++ = '\n'; - *p = '\0'; - - if (stat.rx.update_cnt == 0) - strcpy(last_update, "never"); - else { - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, stat.rx.update); - sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", - now.sec / 3600, - (now.sec % 3600) / 60, - now.sec % 60, - now.msec); - } - - pj_gettimeofday(&media_duration); - PJ_TIME_VAL_SUB(media_duration, stat.start); - if (PJ_TIME_VAL_MSEC(media_duration) == 0) - media_duration.msec = 1; - - /* protect against division by zero */ - if (stat.rx.pkt == 0) - stat.rx.pkt = 1; - if (stat.tx.pkt == 0) - stat.tx.pkt = 1; - - len = pj_ansi_snprintf(p, end-p, - "%s RX pt=%d, stat last update: %s\n" - "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n" - "%s pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n" - "%s (msec) min avg max last dev\n" - "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n" - "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f" -#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 - "\n" - "%s raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f" -#endif -#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 - "\n" - "%s IPDV : %7.3f %7.3f %7.3f %7.3f %7.3f" -#endif - "%s", - indent, info.stream_info[i].fmt.pt, - last_update, - indent, - good_number(packets, stat.rx.pkt), - good_number(bytes, stat.rx.bytes), - good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 40), - good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat.rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), - good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat.rx.bytes + stat.rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), - indent, - stat.rx.loss, - stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss), - stat.rx.discard, - stat.rx.discard * 100.0 / (stat.rx.pkt + stat.rx.loss), - stat.rx.dup, - stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss), - stat.rx.reorder, - stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss), - indent, indent, - stat.rx.loss_period.min / 1000.0, - stat.rx.loss_period.mean / 1000.0, - stat.rx.loss_period.max / 1000.0, - stat.rx.loss_period.last / 1000.0, - pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0, - indent, - stat.rx.jitter.min / 1000.0, - stat.rx.jitter.mean / 1000.0, - stat.rx.jitter.max / 1000.0, - stat.rx.jitter.last / 1000.0, - pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0, -#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 - indent, - stat.rx_raw_jitter.min / 1000.0, - stat.rx_raw_jitter.mean / 1000.0, - stat.rx_raw_jitter.max / 1000.0, - stat.rx_raw_jitter.last / 1000.0, - pj_math_stat_get_stddev(&stat.rx_raw_jitter) / 1000.0, -#endif -#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 - indent, - stat.rx_ipdv.min / 1000.0, - stat.rx_ipdv.mean / 1000.0, - stat.rx_ipdv.max / 1000.0, - stat.rx_ipdv.last / 1000.0, - pj_math_stat_get_stddev(&stat.rx_ipdv) / 1000.0, -#endif - "" - ); - - if (len < 1 || len > end-p) { - *p = '\0'; - return; - } - - p += len; - *p++ = '\n'; - *p = '\0'; - - if (stat.tx.update_cnt == 0) - strcpy(last_update, "never"); - else { - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, stat.tx.update); - sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", - now.sec / 3600, - (now.sec % 3600) / 60, - now.sec % 60, - now.msec); - } - - len = pj_ansi_snprintf(p, end-p, - "%s TX pt=%d, ptime=%dms, stat last update: %s\n" - "%s total %spkt %sB (%sB +IP hdr) @avg %sbps/%sbps\n" - "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n" - "%s (msec) min avg max last dev \n" - "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n" - "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s", - indent, - info.stream_info[i].tx_pt, - info.stream_info[i].param->info.frm_ptime * - info.stream_info[i].param->setting.frm_per_pkt, - last_update, - - indent, - good_number(packets, stat.tx.pkt), - good_number(bytes, stat.tx.bytes), - good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 40), - good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat.tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), - good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat.tx.bytes + stat.tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), - - indent, - stat.tx.loss, - stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss), - stat.tx.dup, - stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss), - stat.tx.reorder, - stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss), - - indent, indent, - stat.tx.loss_period.min / 1000.0, - stat.tx.loss_period.mean / 1000.0, - stat.tx.loss_period.max / 1000.0, - stat.tx.loss_period.last / 1000.0, - pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0, - indent, - stat.tx.jitter.min / 1000.0, - stat.tx.jitter.mean / 1000.0, - stat.tx.jitter.max / 1000.0, - stat.tx.jitter.last / 1000.0, - pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0, - "" - ); - - if (len < 1 || len > end-p) { - *p = '\0'; - return; - } - - p += len; - *p++ = '\n'; - *p = '\0'; - - len = pj_ansi_snprintf(p, end-p, - "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f", - indent, - stat.rtt.min / 1000.0, - stat.rtt.mean / 1000.0, - stat.rtt.max / 1000.0, - stat.rtt.last / 1000.0, - pj_math_stat_get_stddev(&stat.rtt) / 1000.0 - ); - if (len < 1 || len > end-p) { - *p = '\0'; - return; - } - - p += len; - *p++ = '\n'; - *p = '\0'; - -#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) -# define SAMPLES_TO_USEC(usec, samples, clock_rate) \ - do { \ - if (samples <= 4294) \ - usec = samples * 1000000 / clock_rate; \ - else { \ - usec = samples * 1000 / clock_rate; \ - usec *= 1000; \ - } \ - } while(0) - -# define PRINT_VOIP_MTC_VAL(s, v) \ - if (v == 127) \ - sprintf(s, "(na)"); \ - else \ - sprintf(s, "%d", v) - -# define VALIDATE_PRINT_BUF() \ - if (len < 1 || len > end-p) { *p = '\0'; return; } \ - p += len; *p++ = '\n'; *p = '\0' - - - do { - char loss[16], dup[16]; - char jitter[80]; - char toh[80]; - char plc[16], jba[16], jbr[16]; - char signal_lvl[16], noise_lvl[16], rerl[16]; - char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16]; - pjmedia_rtcp_xr_stat xr_stat; - unsigned clock_rate; - - if (pjmedia_session_get_stream_stat_xr(session, i, &xr_stat) != - PJ_SUCCESS) - { - break; - } - - clock_rate = info.stream_info[i].fmt.clock_rate; - - len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent); - VALIDATE_PRINT_BUF(); - - /* Statistics Summary */ - len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent); - VALIDATE_PRINT_BUF(); - - if (xr_stat.rx.stat_sum.l) - sprintf(loss, "%d", xr_stat.rx.stat_sum.lost); - else - sprintf(loss, "(na)"); - - if (xr_stat.rx.stat_sum.d) - sprintf(dup, "%d", xr_stat.rx.stat_sum.dup); - else - sprintf(dup, "(na)"); - - if (xr_stat.rx.stat_sum.j) { - unsigned jmin, jmax, jmean, jdev; - - SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min, - clock_rate); - SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max, - clock_rate); - SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean, - clock_rate); - SAMPLES_TO_USEC(jdev, - pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter), - clock_rate); - sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", - jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); - } else - sprintf(jitter, "(report not available)"); - - if (xr_stat.rx.stat_sum.t) { - sprintf(toh, "%11d %11d %11d %11d", - xr_stat.rx.stat_sum.toh.min, - xr_stat.rx.stat_sum.toh.mean, - xr_stat.rx.stat_sum.toh.max, - pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); - } else - sprintf(toh, "(report not available)"); - - if (xr_stat.rx.stat_sum.update.sec == 0) - strcpy(last_update, "never"); - else { - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update); - sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", - now.sec / 3600, - (now.sec % 3600) / 60, - now.sec % 60, - now.msec); - } - - len = pj_ansi_snprintf(p, end-p, - "%s RX last update: %s\n" - "%s begin seq=%d, end seq=%d\n" - "%s pkt loss=%s, dup=%s\n" - "%s (msec) min avg max dev\n" - "%s jitter : %s\n" - "%s toh : %s", - indent, last_update, - indent, - xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq, - indent, loss, dup, - indent, - indent, jitter, - indent, toh - ); - VALIDATE_PRINT_BUF(); - - if (xr_stat.tx.stat_sum.l) - sprintf(loss, "%d", xr_stat.tx.stat_sum.lost); - else - sprintf(loss, "(na)"); - - if (xr_stat.tx.stat_sum.d) - sprintf(dup, "%d", xr_stat.tx.stat_sum.dup); - else - sprintf(dup, "(na)"); - - if (xr_stat.tx.stat_sum.j) { - unsigned jmin, jmax, jmean, jdev; - - SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min, - clock_rate); - SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max, - clock_rate); - SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean, - clock_rate); - SAMPLES_TO_USEC(jdev, - pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter), - clock_rate); - sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", - jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); - } else - sprintf(jitter, "(report not available)"); - - if (xr_stat.tx.stat_sum.t) { - sprintf(toh, "%11d %11d %11d %11d", - xr_stat.tx.stat_sum.toh.min, - xr_stat.tx.stat_sum.toh.mean, - xr_stat.tx.stat_sum.toh.max, - pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); - } else - sprintf(toh, "(report not available)"); - - if (xr_stat.tx.stat_sum.update.sec == 0) - strcpy(last_update, "never"); - else { - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update); - sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", - now.sec / 3600, - (now.sec % 3600) / 60, - now.sec % 60, - now.msec); - } - - len = pj_ansi_snprintf(p, end-p, - "%s TX last update: %s\n" - "%s begin seq=%d, end seq=%d\n" - "%s pkt loss=%s, dup=%s\n" - "%s (msec) min avg max dev\n" - "%s jitter : %s\n" - "%s toh : %s", - indent, last_update, - indent, - xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq, - indent, loss, dup, - indent, - indent, jitter, - indent, toh - ); - VALIDATE_PRINT_BUF(); - - - /* VoIP Metrics */ - len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent); - VALIDATE_PRINT_BUF(); - - PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl); - PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl); - PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl); - PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor); - PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor); - PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq); - PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq); - - switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) { - case PJMEDIA_RTCP_XR_PLC_DIS: - sprintf(plc, "DISABLED"); - break; - case PJMEDIA_RTCP_XR_PLC_ENH: - sprintf(plc, "ENHANCED"); - break; - case PJMEDIA_RTCP_XR_PLC_STD: - sprintf(plc, "STANDARD"); - break; - case PJMEDIA_RTCP_XR_PLC_UNK: - default: - sprintf(plc, "UNKNOWN"); - break; - } - - switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) { - case PJMEDIA_RTCP_XR_JB_FIXED: - sprintf(jba, "FIXED"); - break; - case PJMEDIA_RTCP_XR_JB_ADAPTIVE: - sprintf(jba, "ADAPTIVE"); - break; - default: - sprintf(jba, "UNKNOWN"); - break; - } - - sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F); - - if (xr_stat.rx.voip_mtc.update.sec == 0) - strcpy(last_update, "never"); - else { - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update); - sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", - now.sec / 3600, - (now.sec % 3600) / 60, - now.sec % 60, - now.msec); - } - - len = pj_ansi_snprintf(p, end-p, - "%s RX last update: %s\n" - "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" - "%s burst : density=%d (%.2f%%), duration=%d%s\n" - "%s gap : density=%d (%.2f%%), duration=%d%s\n" - "%s delay : round trip=%d%s, end system=%d%s\n" - "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n" - "%s quality : R factor=%s, ext R factor=%s\n" - "%s MOS LQ=%s, MOS CQ=%s\n" - "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" - "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s", - indent, - last_update, - /* packets */ - indent, - xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256, - xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256, - /* burst */ - indent, - xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256, - xr_stat.rx.voip_mtc.burst_dur, "ms", - /* gap */ - indent, - xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256, - xr_stat.rx.voip_mtc.gap_dur, "ms", - /* delay */ - indent, - xr_stat.rx.voip_mtc.rnd_trip_delay, "ms", - xr_stat.rx.voip_mtc.end_sys_delay, "ms", - /* level */ - indent, - signal_lvl, "dB", - noise_lvl, "dB", - rerl, "", - /* quality */ - indent, - r_factor, ext_r_factor, - indent, - mos_lq, mos_cq, - /* config */ - indent, - plc, jba, jbr, xr_stat.rx.voip_mtc.gmin, - /* JB delay */ - indent, - xr_stat.rx.voip_mtc.jb_nom, "ms", - xr_stat.rx.voip_mtc.jb_max, "ms", - xr_stat.rx.voip_mtc.jb_abs_max, "ms" - ); - VALIDATE_PRINT_BUF(); - - PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl); - PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl); - PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl); - PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor); - PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor); - PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq); - PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq); - - switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) { - case PJMEDIA_RTCP_XR_PLC_DIS: - sprintf(plc, "DISABLED"); - break; - case PJMEDIA_RTCP_XR_PLC_ENH: - sprintf(plc, "ENHANCED"); - break; - case PJMEDIA_RTCP_XR_PLC_STD: - sprintf(plc, "STANDARD"); - break; - case PJMEDIA_RTCP_XR_PLC_UNK: - default: - sprintf(plc, "unknown"); - break; - } - - switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) { - case PJMEDIA_RTCP_XR_JB_FIXED: - sprintf(jba, "FIXED"); - break; - case PJMEDIA_RTCP_XR_JB_ADAPTIVE: - sprintf(jba, "ADAPTIVE"); - break; - default: - sprintf(jba, "unknown"); - break; - } - - sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F); - - if (xr_stat.tx.voip_mtc.update.sec == 0) - strcpy(last_update, "never"); - else { - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update); - sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", - now.sec / 3600, - (now.sec % 3600) / 60, - now.sec % 60, - now.msec); - } - - len = pj_ansi_snprintf(p, end-p, - "%s TX last update: %s\n" - "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" - "%s burst : density=%d (%.2f%%), duration=%d%s\n" - "%s gap : density=%d (%.2f%%), duration=%d%s\n" - "%s delay : round trip=%d%s, end system=%d%s\n" - "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n" - "%s quality : R factor=%s, ext R factor=%s\n" - "%s MOS LQ=%s, MOS CQ=%s\n" - "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" - "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s", - indent, - last_update, - /* pakcets */ - indent, - xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256, - xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256, - /* burst */ - indent, - xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256, - xr_stat.tx.voip_mtc.burst_dur, "ms", - /* gap */ - indent, - xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256, - xr_stat.tx.voip_mtc.gap_dur, "ms", - /* delay */ - indent, - xr_stat.tx.voip_mtc.rnd_trip_delay, "ms", - xr_stat.tx.voip_mtc.end_sys_delay, "ms", - /* level */ - indent, - signal_lvl, "dB", - noise_lvl, "dB", - rerl, "", - /* quality */ - indent, - r_factor, ext_r_factor, - indent, - mos_lq, mos_cq, - /* config */ - indent, - plc, jba, jbr, xr_stat.tx.voip_mtc.gmin, - /* JB delay */ - indent, - xr_stat.tx.voip_mtc.jb_nom, "ms", - xr_stat.tx.voip_mtc.jb_max, "ms", - xr_stat.tx.voip_mtc.jb_abs_max, "ms" - ); - VALIDATE_PRINT_BUF(); - - - /* RTT delay (by receiver side) */ - len = pj_ansi_snprintf(p, end-p, - "%s RTT (from recv) min avg max last dev", - indent); - VALIDATE_PRINT_BUF(); - len = pj_ansi_snprintf(p, end-p, - "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f", - indent, - xr_stat.rtt.min / 1000.0, - xr_stat.rtt.mean / 1000.0, - xr_stat.rtt.max / 1000.0, - xr_stat.rtt.last / 1000.0, - pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0 - ); - VALIDATE_PRINT_BUF(); - } while(0); -#endif - - } -} - - -/* Print call info */ -void print_call(const char *title, - int call_id, - char *buf, pj_size_t size) -{ - int len; - pjsip_inv_session *inv = pjsua_var.calls[call_id].inv; - pjsip_dialog *dlg = inv->dlg; - char userinfo[128]; - - /* Dump invite sesion info. */ - - len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); - if (len < 0) - pj_ansi_strcpy(userinfo, "<--uri too long-->"); - else - userinfo[len] = '\0'; - - len = pj_ansi_snprintf(buf, size, "%s[%s] %s", - title, - pjsip_inv_state_name(inv->state), - userinfo); - if (len < 1 || len >= (int)size) { - pj_ansi_strcpy(buf, "<--uri too long-->"); - len = 18; - } else - buf[len] = '\0'; -} - - -/* - * Dump call and media statistics to string. - */ -PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id, - pj_bool_t with_media, - char *buffer, - unsigned maxlen, - const char *indent) -{ - pjsua_call *call; - pjsip_dialog *dlg; - pj_time_val duration, res_delay, con_delay; - char tmp[128]; - char *p, *end; - pj_status_t status; - int len; - pjmedia_transport_info tp_info; - - PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, - PJ_EINVAL); - - status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg); - if (status != PJ_SUCCESS) - return status; - - *buffer = '\0'; - p = buffer; - end = buffer + maxlen; - len = 0; - - print_call(indent, call_id, tmp, sizeof(tmp)); - - len = pj_ansi_strlen(tmp); - pj_ansi_strcpy(buffer, tmp); - - p += len; - *p++ = '\r'; - *p++ = '\n'; - - /* Calculate call duration */ - if (call->conn_time.sec != 0) { - pj_gettimeofday(&duration); - PJ_TIME_VAL_SUB(duration, call->conn_time); - con_delay = call->conn_time; - PJ_TIME_VAL_SUB(con_delay, call->start_time); - } else { - duration.sec = duration.msec = 0; - con_delay.sec = con_delay.msec = 0; - } - - /* Calculate first response delay */ - if (call->res_time.sec != 0) { - res_delay = call->res_time; - PJ_TIME_VAL_SUB(res_delay, call->start_time); - } else { - res_delay.sec = res_delay.msec = 0; - } - - /* Print duration */ - len = pj_ansi_snprintf(p, end-p, - "%s Call time: %02dh:%02dm:%02ds, " - "1st res in %d ms, conn in %dms", - indent, - (int)(duration.sec / 3600), - (int)((duration.sec % 3600)/60), - (int)(duration.sec % 60), - (int)PJ_TIME_VAL_MSEC(res_delay), - (int)PJ_TIME_VAL_MSEC(con_delay)); - - if (len > 0 && len < end-p) { - p += len; - *p++ = '\n'; - *p = '\0'; - } - - /* Get and ICE SRTP status */ - 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; - - len = pj_ansi_snprintf(p, end-p, - "%s SRTP status: %s Crypto-suite: %s", - indent, - (srtp_info->active?"Active":"Not active"), - srtp_info->tx_policy.name.ptr); - if (len > 0 && len < end-p) { - p += len; - *p++ = '\n'; - *p = '\0'; - } - } else if (tp_info.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) { - const pjmedia_ice_transport_info *ii; - - ii = (const pjmedia_ice_transport_info*) - tp_info.spc_info[i].buffer; - - len = pj_ansi_snprintf(p, end-p, - "%s ICE role: %s, state: %s, comp_cnt: %u", - indent, - pj_ice_sess_role_name(ii->role), - pj_ice_strans_state_name(ii->sess_state), - ii->comp_cnt); - if (len > 0 && len < end-p) { - p += len; - *p++ = '\n'; - *p = '\0'; - } - } - } - } - - /* Dump session statistics */ - if (with_media && call->session) - dump_media_session(indent, p, end-p, call); - - pjsip_dlg_dec_lock(dlg); - - return PJ_SUCCESS; -} - /* Proto */ static pj_status_t perform_lock_codec(pjsua_call *call); @@ -3075,10 +2379,9 @@ static pj_status_t perform_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_media *ref_m; - pjmedia_sdp_media *m; - unsigned i, codec_cnt = 0; + unsigned i; pj_bool_t rem_can_update; + pj_bool_t need_lock_codec = PJ_FALSE; pjsip_tx_data *tdata; pj_status_t status; @@ -3110,14 +2413,6 @@ static pj_status_t perform_lock_codec(pjsua_call *call) if (local_sdp->origin.version > call->lock_codec.sdp_ver) return PJMEDIA_SDP_EINVER; - /* Verify if media is deactivated */ - if (call->media_st == PJSUA_CALL_MEDIA_NONE || - call->media_st == PJSUA_CALL_MEDIA_ERROR || - call->media_dir == PJMEDIA_DIR_NONE) - { - return PJ_EINVALIDOP; - } - 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 @@ -3125,35 +2420,54 @@ static pj_status_t perform_lock_codec(pjsua_call *call) * just directly update the offer without looking-up the answer. */ new_sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, local_sdp); - m = new_sdp->media[call->audio_idx]; - ref_m = local_sdp->media[call->audio_idx]; - pj_assert(ref_m->desc.port); - codec_cnt = 0; - i = 0; - while (i < m->desc.fmt_count) { - pjmedia_sdp_attr *a; - pj_str_t *fmt = &m->desc.fmt[i]; - if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) { - ++i; + for (i = 0; i < call->med_cnt; ++i) { + unsigned j = 0, codec_cnt = 0; + const pjmedia_sdp_media *ref_m; + pjmedia_sdp_media *m; + 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; } - /* Remove format */ - a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); - if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); - a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "fmtp", fmt); - if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); - pj_array_erase(m->desc.fmt, sizeof(m->desc.fmt[0]), - m->desc.fmt_count, i); - --m->desc.fmt_count; + ref_m = local_sdp->media[i]; + m = new_sdp->media[i]; + + /* Verify that media must be active. */ + pj_assert(ref_m->desc.port); + + while (j < m->desc.fmt_count) { + pjmedia_sdp_attr *a; + pj_str_t *fmt = &m->desc.fmt[j]; + + if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) { + ++j; + continue; + } + + /* Remove format */ + a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); + if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); + a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "fmtp", fmt); + if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); + pj_array_erase(m->desc.fmt, sizeof(m->desc.fmt[0]), + m->desc.fmt_count, j); + --m->desc.fmt_count; + } + + 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 (ref_m->desc.fmt_count == m->desc.fmt_count) + if (!need_lock_codec) return PJ_SUCCESS; /* Send UPDATE or re-INVITE */ @@ -3201,10 +2515,10 @@ static pj_status_t lock_codec(pjsua_call *call) { pjsip_inv_session *inv = call->inv; const pjmedia_sdp_session *local_sdp, *remote_sdp; - const pjmedia_sdp_media *rem_m, *loc_m; - unsigned codec_cnt=0, i; pj_time_val delay = {0, 0}; const pj_str_t st_update = {"UPDATE", 6}; + unsigned i; + pj_bool_t has_mult_fmt = PJ_FALSE; pj_status_t status; /* Stop lock codec timer, if it is active */ @@ -3219,14 +2533,6 @@ static pj_status_t lock_codec(pjsua_call *call) return PJ_SUCCESS; } - /* Skip this if the media is inactive or error */ - if (call->media_st == PJSUA_CALL_MEDIA_NONE || - call->media_st == PJSUA_CALL_MEDIA_ERROR || - call->media_dir == PJMEDIA_DIR_NONE) - { - return PJ_SUCCESS; - } - /* Delay this when the SDP negotiation done in call state EARLY and * remote does not support UPDATE method. */ @@ -3245,28 +2551,50 @@ static pj_status_t lock_codec(pjsua_call *call) if (status != PJ_SUCCESS) return status; - PJ_ASSERT_RETURN(call->audio_idx>=0 && - call->audio_idx < (int)remote_sdp->media_count, - PJ_EINVALIDOP); + /* 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; - rem_m = remote_sdp->media[call->audio_idx]; - loc_m = local_sdp->media[call->audio_idx]; + /* 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; + } - /* Verify that media must be active. */ - pj_assert(loc_m->desc.port && rem_m->desc.port); + /* Remote may answer with less media lines. */ + if (i >= remote_sdp->media_count) + continue; - /* Count the formats in the answer. */ - if (rem_m->desc.fmt_count==1) { - codec_cnt = 1; - } else { - for (i=0; i<rem_m->desc.fmt_count && codec_cnt <= 1; ++i) { - if (!is_non_av_fmt(rem_m, &rem_m->desc.fmt[i])) - ++codec_cnt; - } + rem_m = remote_sdp->media[i]; + loc_m = local_sdp->media[i]; + + /* Verify that media must be active. */ + pj_assert(loc_m->desc.port && rem_m->desc.port); + + /* Count the formats in the answer. */ + if (rem_m->desc.fmt_count==1) { + codec_cnt = 1; + } else { + unsigned j; + for (j=0; j<rem_m->desc.fmt_count && codec_cnt <= 1; ++j) { + if (!is_non_av_fmt(rem_m, &rem_m->desc.fmt[j])) + ++codec_cnt; + } + } + + if (codec_cnt > 1) { + has_mult_fmt = PJ_TRUE; + break; + } } - if (codec_cnt <= 1) { - /* Answer contains single codec. */ - call->lock_codec.retry_cnt = 0; + + /* Each media in the answer already contains single codec. */ + if (!has_mult_fmt) { + call->lock_codec.retry_cnt = 0; return PJ_SUCCESS; } @@ -3538,7 +2866,8 @@ static void call_disconnect( pjsip_inv_session *inv, return; /* Add SDP in 488 status */ - if (call && call->med_tp && tdata->msg->type==PJSIP_RESPONSE_MSG && +#if DISABLED_FOR_TICKET_1185 + if (call && call->tp && tdata->msg->type==PJSIP_RESPONSE_MSG && code==PJSIP_SC_NOT_ACCEPTABLE_HERE) { pjmedia_sdp_session *local_sdp; @@ -3553,6 +2882,7 @@ static void call_disconnect( pjsip_inv_session *inv, &tdata->msg->body); } } +#endif pjsip_inv_send_msg(inv, tdata); } @@ -3660,7 +2990,7 @@ static pj_status_t modify_sdp_of_call_hold(pjsua_call *call, pj_pool_t *pool, pjmedia_sdp_session *sdp) { - pjmedia_sdp_media *m; + unsigned mi; /* Call-hold is done by set the media direction to 'sendonly' * (PJMEDIA_DIR_ENCODING), except when current media direction is @@ -3668,54 +2998,56 @@ static pj_status_t modify_sdp_of_call_hold(pjsua_call *call, * (See RFC 3264 Section 8.4 and RFC 4317 Section 3.1) */ /* http://trac.pjsip.org/repos/ticket/880 - if (call->media_dir != PJMEDIA_DIR_ENCODING) { + if (call->dir != PJMEDIA_DIR_ENCODING) { */ /* https://trac.pjsip.org/repos/ticket/1142: * configuration to use c=0.0.0.0 for call hold. */ - m = sdp->media[call->audio_idx]; - - if (call->call_hold_type == PJSUA_CALL_HOLD_TYPE_RFC2543) { - pjmedia_sdp_conn *conn; - pjmedia_sdp_attr *attr; - - /* Get SDP media connection line */ - conn = m->conn; - if (!conn) - conn = sdp->conn; + for (mi=0; mi<sdp->media_count; ++mi) { + pjmedia_sdp_media *m = sdp->media[mi]; - /* Modify address */ - conn->addr = pj_str("0.0.0.0"); + if (call->call_hold_type == PJSUA_CALL_HOLD_TYPE_RFC2543) { + pjmedia_sdp_conn *conn; + pjmedia_sdp_attr *attr; - /* Remove existing directions attributes */ - pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); - pjmedia_sdp_media_remove_all_attr(m, "sendonly"); - pjmedia_sdp_media_remove_all_attr(m, "recvonly"); - pjmedia_sdp_media_remove_all_attr(m, "inactive"); + /* Get SDP media connection line */ + conn = m->conn; + if (!conn) + conn = sdp->conn; - /* Add inactive attribute */ - attr = pjmedia_sdp_attr_create(pool, "inactive", NULL); - pjmedia_sdp_media_add_attr(m, attr); + /* Modify address */ + conn->addr = pj_str("0.0.0.0"); + /* Remove existing directions attributes */ + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); + pjmedia_sdp_media_remove_all_attr(m, "inactive"); - } else { - pjmedia_sdp_attr *attr; - - /* Remove existing directions attributes */ - pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); - pjmedia_sdp_media_remove_all_attr(m, "sendonly"); - pjmedia_sdp_media_remove_all_attr(m, "recvonly"); - pjmedia_sdp_media_remove_all_attr(m, "inactive"); - - if (call->media_dir & PJMEDIA_DIR_ENCODING) { - /* Add sendonly attribute */ - attr = pjmedia_sdp_attr_create(pool, "sendonly", NULL); - pjmedia_sdp_media_add_attr(m, attr); - } else { /* Add inactive attribute */ attr = pjmedia_sdp_attr_create(pool, "inactive", NULL); pjmedia_sdp_media_add_attr(m, attr); + + + } else { + pjmedia_sdp_attr *attr; + + /* Remove existing directions attributes */ + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); + pjmedia_sdp_media_remove_all_attr(m, "inactive"); + + if (call->media[mi].dir & PJMEDIA_DIR_ENCODING) { + /* Add sendonly attribute */ + attr = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + pjmedia_sdp_media_add_attr(m, attr); + } else { + /* Add inactive attribute */ + attr = pjmedia_sdp_attr_create(pool, "inactive", NULL); + pjmedia_sdp_media_add_attr(m, attr); + } } } @@ -3757,20 +3089,14 @@ static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_session *offer) { pjsua_call *call; - pjmedia_sdp_conn *conn = NULL; pjmedia_sdp_session *answer; + unsigned i; pj_status_t status; PJSUA_LOCK(); call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; - if (call->audio_idx < (int)offer->media_count) - conn = offer->media[call->audio_idx]->conn; - - if (!conn) - conn = offer->conn; - /* Supply candidate answer */ PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer", call->index)); @@ -3784,12 +3110,36 @@ static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, return; } + /* Validate media count in the generated answer */ + pj_assert(answer->media_count == offer->media_count); + /* Check if offer's conn address is zero */ - if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 || - pj_strcmp2(&conn->addr, "0")==0) - { - /* Modify address */ - answer->conn->addr = pj_str("0.0.0.0"); + for (i = 0; i < answer->media_count; ++i) { + pjmedia_sdp_conn *conn; + + conn = offer->media[i]->conn; + if (!conn) + conn = offer->conn; + + if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 || + pj_strcmp2(&conn->addr, "0")==0) + { + pjmedia_sdp_conn *a_conn = answer->media[i]->conn; + + /* Modify answer address */ + if (a_conn) { + a_conn->addr = pj_str("0.0.0.0"); + } else if (answer->conn == NULL || + pj_strcmp2(&answer->conn->addr, "0.0.0.0") != 0) + { + a_conn = PJ_POOL_ZALLOC_T(call->inv->pool_prov, + pjmedia_sdp_conn); + a_conn->net_type = pj_str("IN"); + a_conn->addr_type = pj_str("IP4"); + a_conn->addr = pj_str("0.0.0.0"); + answer->media[i]->conn = a_conn; + } + } } /* Check if call is on-hold */ diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 362ed0cd..d19bb744 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -67,6 +67,10 @@ static void init_data() pj_list_init(&pjsua_var.outbound_proxy); pjsua_config_default(&pjsua_var.ua_cfg); + + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + pjsua_vid_win_reset(i); + } } @@ -103,10 +107,8 @@ PJ_DEF(void) pjsua_config_default(pjsua_config *cfg) cfg->stun_ignore_failure = PJ_TRUE; cfg->force_lr = PJ_TRUE; cfg->enable_unsolicited_mwi = PJ_TRUE; -#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) cfg->use_srtp = PJSUA_DEFAULT_USE_SRTP; cfg->srtp_secure_signaling = PJSUA_DEFAULT_SRTP_SECURE_SIGNALING; -#endif cfg->hangup_forked_call = PJ_TRUE; cfg->use_timer = PJSUA_SIP_TIMER_OPTIONAL; @@ -177,11 +179,13 @@ PJ_DEF(void) pjsua_acc_config_default(pjsua_acc_config *cfg) cfg->timer_setting = pjsua_var.ua_cfg.timer_setting; cfg->ka_interval = 15; cfg->ka_data = pj_str("\r\n"); -#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) + cfg->max_audio_cnt = 1; + cfg->vid_cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; + cfg->vid_rend_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV; + pjsua_transport_config_default(&cfg->rtp_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; -#endif cfg->reg_retry_interval = PJSUA_REG_RETRY_INTERVAL; cfg->contact_rewrite_method = PJSUA_CONTACT_REWRITE_METHOD; cfg->use_rfc5626 = PJ_TRUE; @@ -225,7 +229,6 @@ PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg) cfg->turn_conn_type = PJ_TURN_TP_UDP; } - /***************************************************************************** * This is a very simple PJSIP module, whose sole purpose is to display * incoming and outgoing messages to log. This module will have priority @@ -380,6 +383,7 @@ static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata) } /* Get media socket info, make sure transport is ready */ +#if DISABLED_FOR_TICKET_1185 if (pjsua_var.calls[0].med_tp) { pjmedia_transport_info_init(&tpinfo); pjmedia_transport_get_info(pjsua_var.calls[0].med_tp, &tpinfo); @@ -391,8 +395,9 @@ static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata) pjsip_create_sdp_body(tdata->pool, sdp, &tdata->msg->body); } } +#endif - /* Send response statelessly */ + /* Send response */ pjsip_get_response_addr(tdata->pool, rdata, &res_addr); status = pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, tdata, NULL, NULL); if (status != PJ_SUCCESS) @@ -637,6 +642,10 @@ PJ_DEF(pj_status_t) pjsua_create(void) pjsua_var.cap_dev = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; pjsua_var.play_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; + /* Set default video device ID */ + pjsua_var.vcap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; + pjsua_var.vrdr_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV; + /* Init caching pool. */ pj_caching_pool_init(&pjsua_var.cp, NULL, 0); @@ -661,6 +670,7 @@ PJ_DEF(pj_status_t) pjsua_create(void) &pjsua_var.endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pjsua_set_state(PJSUA_STATE_CREATED); return PJ_SUCCESS; } @@ -929,6 +939,8 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, PJ_LOG(3,(THIS_FILE, "pjsua version %s for %s initialized", pj_get_version(), pj_get_sys_info()->info.ptr)); + pjsua_set_state(PJSUA_STATE_INIT); + return PJ_SUCCESS; on_error: @@ -1286,6 +1298,12 @@ PJ_DEF(pj_status_t) pjsua_destroy(void) { int i; /* Must be signed */ + if (pjsua_var.state > PJSUA_STATE_NULL && + pjsua_var.state < PJSUA_STATE_CLOSING) + { + pjsua_set_state(PJSUA_STATE_CLOSING); + } + /* Signal threads to quit: */ pjsua_var.thread_quit_flag = 1; @@ -1448,6 +1466,8 @@ PJ_DEF(pj_status_t) pjsua_destroy(void) pjsua_var.pool = NULL; pj_caching_pool_destroy(&pjsua_var.cp); + pjsua_set_state(PJSUA_STATE_NULL); + PJ_LOG(4,(THIS_FILE, "PJSUA destroyed...")); /* End logging */ @@ -1467,6 +1487,28 @@ PJ_DEF(pj_status_t) pjsua_destroy(void) return PJ_SUCCESS; } +void pjsua_set_state(pjsua_state new_state) +{ + const char *state_name[] = { + "NULL", + "CREATED", + "INIT", + "STARTING", + "RUNNING", + "CLOSING" + }; + pjsua_state old_state = pjsua_var.state; + + pjsua_var.state = new_state; + PJ_LOG(4,(THIS_FILE, "PJSUA state changed: %s --> %s", + state_name[old_state], state_name[new_state])); +} + +/* Get state */ +PJ_DEF(pjsua_state) pjsua_get_state(void) +{ + return pjsua_var.state; +} /** * Application is recommended to call this function after all initialization @@ -1479,6 +1521,8 @@ PJ_DEF(pj_status_t) pjsua_start(void) { pj_status_t status; + pjsua_set_state(PJSUA_STATE_STARTING); + status = pjsua_call_subsys_start(); if (status != PJ_SUCCESS) return status; @@ -1491,6 +1535,8 @@ PJ_DEF(pj_status_t) pjsua_start(void) if (status != PJ_SUCCESS) return status; + pjsua_set_state(PJSUA_STATE_RUNNING); + return PJ_SUCCESS; } @@ -2574,6 +2620,7 @@ PJ_DEF(void) pjsua_dump(pj_bool_t detail) pjmedia_endpt_dump(pjsua_get_pjmedia_endpt()); PJ_LOG(3,(THIS_FILE, "Dumping media transports:")); +#if DISABLED_FOR_TICKET_1185 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { pjsua_call *call = &pjsua_var.calls[i]; pjmedia_transport_info tpinfo; @@ -2590,6 +2637,7 @@ PJ_DEF(void) pjsua_dump(pj_bool_t detail) pj_sockaddr_print(&tpinfo.sock_info.rtp_addr_name, addr_buf, sizeof(addr_buf), 3))); } +#endif pjsip_tsx_layer_dump(detail); pjsip_ua_dump(detail); diff --git a/pjsip/src/pjsua-lib/pjsua_dump.c b/pjsip/src/pjsua-lib/pjsua_dump.c new file mode 100644 index 00000000..f94e036f --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_dump.c @@ -0,0 +1,944 @@ +/* $Id$ */ +/* + * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + +const char *good_number(char *buf, pj_int32_t val) +{ + if (val < 1000) { + pj_ansi_sprintf(buf, "%d", val); + } else if (val < 1000000) { + pj_ansi_sprintf(buf, "%d.%dK", + val / 1000, + (val % 1000) / 100); + } else { + pj_ansi_sprintf(buf, "%d.%02dM", + val / 1000000, + (val % 1000000) / 10000); + } + + return buf; +} + +static unsigned dump_media_stat(const char *indent, + char *buf, unsigned maxlen, + const pjmedia_rtcp_stat *stat, + const char *rx_info, const char *tx_info) +{ + char last_update[64]; + char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32]; + pj_time_val media_duration, now; + char *p = buf, *end = buf+maxlen; + int len; + + if (stat->rx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, stat->rx.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + pj_gettimeofday(&media_duration); + PJ_TIME_VAL_SUB(media_duration, stat->start); + if (PJ_TIME_VAL_MSEC(media_duration) == 0) + media_duration.msec = 1; + + len = pj_ansi_snprintf(p, end-p, + "%s RX %s last update:%s\n" + "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n" + "%s pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n" + "%s (msec) min avg max last dev\n" + "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n" + "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n" +#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 + "%s raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n" +#endif +#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 + "%s IPDV : %7.3f %7.3f %7.3f %7.3f %7.3f\n" +#endif + "%s", + indent, + rx_info? rx_info : "", + last_update, + + indent, + good_number(packets, stat->rx.pkt), + good_number(bytes, stat->rx.bytes), + good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40), + good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), + good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->rx.bytes + stat->rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), + indent, + stat->rx.loss, + (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0), + stat->rx.discard, + (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0), + stat->rx.dup, + (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0), + stat->rx.reorder, + (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0), + indent, indent, + stat->rx.loss_period.min / 1000.0, + stat->rx.loss_period.mean / 1000.0, + stat->rx.loss_period.max / 1000.0, + stat->rx.loss_period.last / 1000.0, + pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0, + indent, + stat->rx.jitter.min / 1000.0, + stat->rx.jitter.mean / 1000.0, + stat->rx.jitter.max / 1000.0, + stat->rx.jitter.last / 1000.0, + pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0, +#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 + indent, + stat->rx_raw_jitter.min / 1000.0, + stat->rx_raw_jitter.mean / 1000.0, + stat->rx_raw_jitter.max / 1000.0, + stat->rx_raw_jitter.last / 1000.0, + pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0, +#endif +#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 + indent, + stat->rx_ipdv.min / 1000.0, + stat->rx_ipdv.mean / 1000.0, + stat->rx_ipdv.max / 1000.0, + stat->rx_ipdv.last / 1000.0, + pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0, +#endif + "" + ); + + if (len < 1 || len > end-p) { + *p = '\0'; + return (p-buf); + } + p += len; + + if (stat->tx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, stat->tx.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s TX %s last update:%s\n" + "%s total %spkt %sB (%sB +IP hdr) @avg %sbps/%sbps\n" + "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n" + "%s (msec) min avg max last dev \n" + "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n" + "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n", + indent, + tx_info, + last_update, + + indent, + good_number(packets, stat->tx.pkt), + good_number(bytes, stat->tx.bytes), + good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40), + good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), + good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->tx.bytes + stat->tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), + + indent, + stat->tx.loss, + (stat->tx.loss? stat->tx.loss * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0), + stat->tx.dup, + (stat->tx.dup? stat->tx.dup * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0), + stat->tx.reorder, + (stat->tx.reorder? stat->tx.reorder * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0), + + indent, indent, + stat->tx.loss_period.min / 1000.0, + stat->tx.loss_period.mean / 1000.0, + stat->tx.loss_period.max / 1000.0, + stat->tx.loss_period.last / 1000.0, + pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0, + indent, + stat->tx.jitter.min / 1000.0, + stat->tx.jitter.mean / 1000.0, + stat->tx.jitter.max / 1000.0, + stat->tx.jitter.last / 1000.0, + pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0 + ); + + if (len < 1 || len > end-p) { + *p = '\0'; + return (p-buf); + } + p += len; + + len = pj_ansi_snprintf(p, end-p, + "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f\n", + indent, + stat->rtt.min / 1000.0, + stat->rtt.mean / 1000.0, + stat->rtt.max / 1000.0, + stat->rtt.last / 1000.0, + pj_math_stat_get_stddev(&stat->rtt) / 1000.0 + ); + if (len < 1 || len > end-p) { + *p = '\0'; + return (p-buf); + } + p += len; + + return (p-buf); +} + + +/* Dump media session */ +static void dump_media_session(const char *indent, + char *buf, unsigned maxlen, + pjsua_call *call) +{ + unsigned i; + char *p = buf, *end = buf+maxlen; + int len; + + for (i=0; i<call->med_cnt; ++i) { + pjsua_call_media *call_med = &call->media[i]; + pjmedia_rtcp_stat stat; + pj_bool_t has_stat; + pjmedia_transport_info tp_info; + char rem_addr_buf[80]; + char codec_info[32] = {'0'}; + char rx_info[80] = {'\0'}; + char tx_info[80] = {'\0'}; + const char *rem_addr; + const char *dir_str; + const char *media_type_str; + + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + media_type_str = "audio"; + break; + case PJMEDIA_TYPE_VIDEO: + media_type_str = "video"; + break; + case PJMEDIA_TYPE_APPLICATION: + media_type_str = "application"; + break; + default: + media_type_str = "unknown"; + break; + } + + /* Check if the stream is deactivated */ + if (call_med->tp == NULL || + (!call_med->strm.a.stream && !call_med->strm.v.stream)) + { + len = pj_ansi_snprintf(p, end-p, + "%s #%d %s deactivated\n", + indent, i, media_type_str); + if (len < 1 || len > end-p) { + *p = '\0'; + return; + } + + p += len; + continue; + } + + pjmedia_transport_info_init(&tp_info); + pjmedia_transport_get_info(call_med->tp, &tp_info); + + // rem_addr will contain actual address of RTP originator, instead of + // remote RTP address specified by stream which is fetched from the SDP. + // Please note that we are assuming only one stream per call. + //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr, + // rem_addr_buf, sizeof(rem_addr_buf), 3); + if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) { + rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf, + sizeof(rem_addr_buf), 3); + } else { + pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-"); + rem_addr = rem_addr_buf; + } + + if (call_med->dir == PJMEDIA_DIR_NONE) { + /* To handle when the stream that is currently being paused + * (http://trac.pjsip.org/repos/ticket/1079) + */ + dir_str = "inactive"; + } else if (call_med->dir == PJMEDIA_DIR_ENCODING) + dir_str = "sendonly"; + else if (call_med->dir == PJMEDIA_DIR_DECODING) + dir_str = "recvonly"; + else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING) + dir_str = "sendrecv"; + else + dir_str = "inactive"; + + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + pjmedia_stream *stream = call_med->strm.a.stream; + pjmedia_stream_info info; + + pjmedia_stream_get_stat(stream, &stat); + has_stat = PJ_TRUE; + + pjmedia_stream_get_info(stream, &info); + pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz", + (int)info.fmt.encoding_name.slen, + info.fmt.encoding_name.ptr, + info.fmt.clock_rate / 1000); + pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,", + info.fmt.pt); + pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,", + info.tx_pt, + info.param->setting.frm_per_pkt* + info.param->info.frm_ptime); + } else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + pjmedia_vid_stream *stream = call_med->strm.v.stream; + pjmedia_vid_stream_info info; + + pjmedia_vid_stream_get_stat(stream, &stat); + has_stat = PJ_TRUE; + + pjmedia_vid_stream_get_info(stream, &info); + pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s", + (int)info.codec_info.encoding_name.slen, + info.codec_info.encoding_name.ptr); + if (call_med->dir & PJMEDIA_DIR_DECODING) { + pjmedia_video_format_detail *vfd; + vfd = pjmedia_format_get_video_format_detail( + &info.codec_param->dec_fmt, PJ_TRUE); + pj_ansi_snprintf(rx_info, sizeof(rx_info), + "pt=%d, size=%dx%d, fps=%.2f,", + info.rx_pt, + vfd->size.w, vfd->size.h, + vfd->fps.num*1.0/vfd->fps.denum); + } + if (call_med->dir & PJMEDIA_DIR_ENCODING) { + pjmedia_video_format_detail *vfd; + vfd = pjmedia_format_get_video_format_detail( + &info.codec_param->enc_fmt, PJ_TRUE); + pj_ansi_snprintf(tx_info, sizeof(tx_info), + "pt=%d, size=%dx%d, fps=%.2f,", + info.tx_pt, + vfd->size.w, vfd->size.h, + vfd->fps.num*1.0/vfd->fps.denum); + } + } else { + has_stat = PJ_FALSE; + } + + len = pj_ansi_snprintf(p, end-p, + "%s #%d %s%s, %s, peer=%s\n", + indent, + call_med->idx, + media_type_str, + codec_info, + dir_str, + rem_addr); + if (len < 1 || len > end-p) { + *p = '\0'; + return; + } + p += len; + + /* Get and ICE SRTP status */ + if (call_med->tp) { + pjmedia_transport_info tp_info; + + pjmedia_transport_info_init(&tp_info); + pjmedia_transport_get_info(call_med->tp, &tp_info); + if (tp_info.specific_info_cnt > 0) { + unsigned j; + for (j = 0; j < tp_info.specific_info_cnt; ++j) { + if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP) + { + pjmedia_srtp_info *srtp_info = + (pjmedia_srtp_info*) tp_info.spc_info[j].buffer; + + len = pj_ansi_snprintf(p, end-p, + " %s SRTP status: %s Crypto-suite: %s", + indent, + (srtp_info->active?"Active":"Not active"), + srtp_info->tx_policy.name.ptr); + if (len > 0 && len < end-p) { + p += len; + *p++ = '\n'; + *p = '\0'; + } + } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) { + const pjmedia_ice_transport_info *ii; + + ii = (const pjmedia_ice_transport_info*) + tp_info.spc_info[j].buffer; + + len = pj_ansi_snprintf(p, end-p, + " %s ICE role: %s, state: %s, comp_cnt: %u", + indent, + pj_ice_sess_role_name(ii->role), + pj_ice_strans_state_name(ii->sess_state), + ii->comp_cnt); + if (len > 0 && len < end-p) { + p += len; + *p++ = '\n'; + *p = '\0'; + } + } + } + } + } + + + if (has_stat) { + len = dump_media_stat(indent, p, end-p, &stat, + rx_info, tx_info); + p += len; + } + +#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) +# define SAMPLES_TO_USEC(usec, samples, clock_rate) \ + do { \ + if (samples <= 4294) \ + usec = samples * 1000000 / clock_rate; \ + else { \ + usec = samples * 1000 / clock_rate; \ + usec *= 1000; \ + } \ + } while(0) + +# define PRINT_VOIP_MTC_VAL(s, v) \ + if (v == 127) \ + sprintf(s, "(na)"); \ + else \ + sprintf(s, "%d", v) + +# define VALIDATE_PRINT_BUF() \ + if (len < 1 || len > end-p) { *p = '\0'; return; } \ + p += len; *p++ = '\n'; *p = '\0' + + + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + pjmedia_stream_info info; + char last_update[64]; + char loss[16], dup[16]; + char jitter[80]; + char toh[80]; + char plc[16], jba[16], jbr[16]; + char signal_lvl[16], noise_lvl[16], rerl[16]; + char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16]; + pjmedia_rtcp_xr_stat xr_stat; + unsigned clock_rate; + pj_time_val now; + + if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream, + &xr_stat) != PJ_SUCCESS) + { + continue; + } + + if (pjmedia_stream_get_info(call_med->strm.a.stream, &info) + != PJ_SUCCESS) + { + continue; + } + + clock_rate = info.fmt.clock_rate; + pj_gettimeofday(&now); + + len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent); + VALIDATE_PRINT_BUF(); + + /* Statistics Summary */ + len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent); + VALIDATE_PRINT_BUF(); + + if (xr_stat.rx.stat_sum.l) + sprintf(loss, "%d", xr_stat.rx.stat_sum.lost); + else + sprintf(loss, "(na)"); + + if (xr_stat.rx.stat_sum.d) + sprintf(dup, "%d", xr_stat.rx.stat_sum.dup); + else + sprintf(dup, "(na)"); + + if (xr_stat.rx.stat_sum.j) { + unsigned jmin, jmax, jmean, jdev; + + SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min, + clock_rate); + SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max, + clock_rate); + SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean, + clock_rate); + SAMPLES_TO_USEC(jdev, + pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter), + clock_rate); + sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", + jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); + } else + sprintf(jitter, "(report not available)"); + + if (xr_stat.rx.stat_sum.t) { + sprintf(toh, "%11d %11d %11d %11d", + xr_stat.rx.stat_sum.toh.min, + xr_stat.rx.stat_sum.toh.mean, + xr_stat.rx.stat_sum.toh.max, + pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); + } else + sprintf(toh, "(report not available)"); + + if (xr_stat.rx.stat_sum.update.sec == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s RX last update: %s\n" + "%s begin seq=%d, end seq=%d\n" + "%s pkt loss=%s, dup=%s\n" + "%s (msec) min avg max dev\n" + "%s jitter : %s\n" + "%s toh : %s", + indent, last_update, + indent, + xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq, + indent, loss, dup, + indent, + indent, jitter, + indent, toh + ); + VALIDATE_PRINT_BUF(); + + if (xr_stat.tx.stat_sum.l) + sprintf(loss, "%d", xr_stat.tx.stat_sum.lost); + else + sprintf(loss, "(na)"); + + if (xr_stat.tx.stat_sum.d) + sprintf(dup, "%d", xr_stat.tx.stat_sum.dup); + else + sprintf(dup, "(na)"); + + if (xr_stat.tx.stat_sum.j) { + unsigned jmin, jmax, jmean, jdev; + + SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min, + clock_rate); + SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max, + clock_rate); + SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean, + clock_rate); + SAMPLES_TO_USEC(jdev, + pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter), + clock_rate); + sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", + jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); + } else + sprintf(jitter, "(report not available)"); + + if (xr_stat.tx.stat_sum.t) { + sprintf(toh, "%11d %11d %11d %11d", + xr_stat.tx.stat_sum.toh.min, + xr_stat.tx.stat_sum.toh.mean, + xr_stat.tx.stat_sum.toh.max, + pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); + } else + sprintf(toh, "(report not available)"); + + if (xr_stat.tx.stat_sum.update.sec == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s TX last update: %s\n" + "%s begin seq=%d, end seq=%d\n" + "%s pkt loss=%s, dup=%s\n" + "%s (msec) min avg max dev\n" + "%s jitter : %s\n" + "%s toh : %s", + indent, last_update, + indent, + xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq, + indent, loss, dup, + indent, + indent, jitter, + indent, toh + ); + VALIDATE_PRINT_BUF(); + + + /* VoIP Metrics */ + len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent); + VALIDATE_PRINT_BUF(); + + PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl); + PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl); + PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl); + PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor); + PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor); + PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq); + PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq); + + switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) { + case PJMEDIA_RTCP_XR_PLC_DIS: + sprintf(plc, "DISABLED"); + break; + case PJMEDIA_RTCP_XR_PLC_ENH: + sprintf(plc, "ENHANCED"); + break; + case PJMEDIA_RTCP_XR_PLC_STD: + sprintf(plc, "STANDARD"); + break; + case PJMEDIA_RTCP_XR_PLC_UNK: + default: + sprintf(plc, "UNKNOWN"); + break; + } + + switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) { + case PJMEDIA_RTCP_XR_JB_FIXED: + sprintf(jba, "FIXED"); + break; + case PJMEDIA_RTCP_XR_JB_ADAPTIVE: + sprintf(jba, "ADAPTIVE"); + break; + default: + sprintf(jba, "UNKNOWN"); + break; + } + + sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F); + + if (xr_stat.rx.voip_mtc.update.sec == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s RX last update: %s\n" + "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" + "%s burst : density=%d (%.2f%%), duration=%d%s\n" + "%s gap : density=%d (%.2f%%), duration=%d%s\n" + "%s delay : round trip=%d%s, end system=%d%s\n" + "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n" + "%s quality : R factor=%s, ext R factor=%s\n" + "%s MOS LQ=%s, MOS CQ=%s\n" + "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" + "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s", + indent, + last_update, + /* packets */ + indent, + xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256, + xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256, + /* burst */ + indent, + xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256, + xr_stat.rx.voip_mtc.burst_dur, "ms", + /* gap */ + indent, + xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256, + xr_stat.rx.voip_mtc.gap_dur, "ms", + /* delay */ + indent, + xr_stat.rx.voip_mtc.rnd_trip_delay, "ms", + xr_stat.rx.voip_mtc.end_sys_delay, "ms", + /* level */ + indent, + signal_lvl, "dB", + noise_lvl, "dB", + rerl, "", + /* quality */ + indent, + r_factor, ext_r_factor, + indent, + mos_lq, mos_cq, + /* config */ + indent, + plc, jba, jbr, xr_stat.rx.voip_mtc.gmin, + /* JB delay */ + indent, + xr_stat.rx.voip_mtc.jb_nom, "ms", + xr_stat.rx.voip_mtc.jb_max, "ms", + xr_stat.rx.voip_mtc.jb_abs_max, "ms" + ); + VALIDATE_PRINT_BUF(); + + PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl); + PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl); + PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl); + PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor); + PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor); + PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq); + PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq); + + switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) { + case PJMEDIA_RTCP_XR_PLC_DIS: + sprintf(plc, "DISABLED"); + break; + case PJMEDIA_RTCP_XR_PLC_ENH: + sprintf(plc, "ENHANCED"); + break; + case PJMEDIA_RTCP_XR_PLC_STD: + sprintf(plc, "STANDARD"); + break; + case PJMEDIA_RTCP_XR_PLC_UNK: + default: + sprintf(plc, "unknown"); + break; + } + + switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) { + case PJMEDIA_RTCP_XR_JB_FIXED: + sprintf(jba, "FIXED"); + break; + case PJMEDIA_RTCP_XR_JB_ADAPTIVE: + sprintf(jba, "ADAPTIVE"); + break; + default: + sprintf(jba, "unknown"); + break; + } + + sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F); + + if (xr_stat.tx.voip_mtc.update.sec == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s TX last update: %s\n" + "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" + "%s burst : density=%d (%.2f%%), duration=%d%s\n" + "%s gap : density=%d (%.2f%%), duration=%d%s\n" + "%s delay : round trip=%d%s, end system=%d%s\n" + "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n" + "%s quality : R factor=%s, ext R factor=%s\n" + "%s MOS LQ=%s, MOS CQ=%s\n" + "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" + "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s", + indent, + last_update, + /* pakcets */ + indent, + xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256, + xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256, + /* burst */ + indent, + xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256, + xr_stat.tx.voip_mtc.burst_dur, "ms", + /* gap */ + indent, + xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256, + xr_stat.tx.voip_mtc.gap_dur, "ms", + /* delay */ + indent, + xr_stat.tx.voip_mtc.rnd_trip_delay, "ms", + xr_stat.tx.voip_mtc.end_sys_delay, "ms", + /* level */ + indent, + signal_lvl, "dB", + noise_lvl, "dB", + rerl, "", + /* quality */ + indent, + r_factor, ext_r_factor, + indent, + mos_lq, mos_cq, + /* config */ + indent, + plc, jba, jbr, xr_stat.tx.voip_mtc.gmin, + /* JB delay */ + indent, + xr_stat.tx.voip_mtc.jb_nom, "ms", + xr_stat.tx.voip_mtc.jb_max, "ms", + xr_stat.tx.voip_mtc.jb_abs_max, "ms" + ); + VALIDATE_PRINT_BUF(); + + + /* RTT delay (by receiver side) */ + len = pj_ansi_snprintf(p, end-p, + "%s RTT (from recv) min avg max last dev", + indent); + VALIDATE_PRINT_BUF(); + len = pj_ansi_snprintf(p, end-p, + "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f", + indent, + xr_stat.rtt.min / 1000.0, + xr_stat.rtt.mean / 1000.0, + xr_stat.rtt.max / 1000.0, + xr_stat.rtt.last / 1000.0, + pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0 + ); + VALIDATE_PRINT_BUF(); + } /* if audio */; +#endif + + } +} + + +/* Print call info */ +void print_call(const char *title, + int call_id, + char *buf, pj_size_t size) +{ + int len; + pjsip_inv_session *inv = pjsua_var.calls[call_id].inv; + pjsip_dialog *dlg = inv->dlg; + char userinfo[128]; + + /* Dump invite sesion info. */ + + len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); + if (len < 0) + pj_ansi_strcpy(userinfo, "<--uri too long-->"); + else + userinfo[len] = '\0'; + + len = pj_ansi_snprintf(buf, size, "%s[%s] %s", + title, + pjsip_inv_state_name(inv->state), + userinfo); + if (len < 1 || len >= (int)size) { + pj_ansi_strcpy(buf, "<--uri too long-->"); + len = 18; + } else + buf[len] = '\0'; +} + + +/* + * Dump call and media statistics to string. + */ +PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id, + pj_bool_t with_media, + char *buffer, + unsigned maxlen, + const char *indent) +{ + pjsua_call *call; + pjsip_dialog *dlg; + pj_time_val duration, res_delay, con_delay; + char tmp[128]; + char *p, *end; + pj_status_t status; + int len; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + return status; + + *buffer = '\0'; + p = buffer; + end = buffer + maxlen; + len = 0; + + print_call(indent, call_id, tmp, sizeof(tmp)); + + len = pj_ansi_strlen(tmp); + pj_ansi_strcpy(buffer, tmp); + + p += len; + *p++ = '\r'; + *p++ = '\n'; + + /* Calculate call duration */ + if (call->conn_time.sec != 0) { + pj_gettimeofday(&duration); + PJ_TIME_VAL_SUB(duration, call->conn_time); + con_delay = call->conn_time; + PJ_TIME_VAL_SUB(con_delay, call->start_time); + } else { + duration.sec = duration.msec = 0; + con_delay.sec = con_delay.msec = 0; + } + + /* Calculate first response delay */ + if (call->res_time.sec != 0) { + res_delay = call->res_time; + PJ_TIME_VAL_SUB(res_delay, call->start_time); + } else { + res_delay.sec = res_delay.msec = 0; + } + + /* Print duration */ + len = pj_ansi_snprintf(p, end-p, + "%s Call time: %02dh:%02dm:%02ds, " + "1st res in %d ms, conn in %dms", + indent, + (int)(duration.sec / 3600), + (int)((duration.sec % 3600)/60), + (int)(duration.sec % 60), + (int)PJ_TIME_VAL_MSEC(res_delay), + (int)PJ_TIME_VAL_MSEC(con_delay)); + + if (len > 0 && len < end-p) { + p += len; + *p++ = '\n'; + *p = '\0'; + } + + /* Dump session statistics */ + if (with_media && pjsua_call_has_media(call_id)) + dump_media_session(indent, p, end-p, call); + + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 2eced3d9..94e81566 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -58,6 +58,7 @@ static void pjsua_media_config_dup(pj_pool_t *pool, pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred); } + /** * Init media subsystems. */ @@ -65,6 +66,7 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) { pj_str_t codec_id = {NULL, 0}; unsigned opt; + pjmedia_audio_codec_config codec_cfg; pj_status_t status; /* To suppress warning about unused var when all codecs are disabled */ @@ -111,86 +113,13 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) return status; } - /* Register all codecs */ - -#if PJMEDIA_HAS_SPEEX_CODEC - /* Register speex. */ - status = pjmedia_codec_speex_init(pjsua_var.med_endpt, - 0, - pjsua_var.media_cfg.quality, - -1); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing Speex codec", - status); - return status; - } - - /* Set speex/16000 to higher priority*/ - codec_id = pj_str("speex/16000"); - pjmedia_codec_mgr_set_codec_priority( - pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), - &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2); - - /* Set speex/8000 to next higher priority*/ - codec_id = pj_str("speex/8000"); - pjmedia_codec_mgr_set_codec_priority( - pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), - &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1); - - - -#endif /* PJMEDIA_HAS_SPEEX_CODEC */ - -#if PJMEDIA_HAS_ILBC_CODEC - /* Register iLBC. */ - status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt, - pjsua_var.media_cfg.ilbc_mode); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing iLBC codec", - status); - return status; - } -#endif /* PJMEDIA_HAS_ILBC_CODEC */ - -#if PJMEDIA_HAS_GSM_CODEC - /* Register GSM */ - status = pjmedia_codec_gsm_init(pjsua_var.med_endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing GSM codec", - status); - return status; - } -#endif /* PJMEDIA_HAS_GSM_CODEC */ - -#if PJMEDIA_HAS_G711_CODEC - /* Register PCMA and PCMU */ - status = pjmedia_codec_g711_init(pjsua_var.med_endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing G711 codec", - status); - return status; - } -#endif /* PJMEDIA_HAS_G711_CODEC */ - -#if PJMEDIA_HAS_G722_CODEC - status = pjmedia_codec_g722_init( pjsua_var.med_endpt ); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing G722 codec", - status); - return status; - } -#endif /* PJMEDIA_HAS_G722_CODEC */ - -#if PJMEDIA_HAS_INTEL_IPP - /* Register IPP codecs */ - status = pjmedia_codec_ipp_init(pjsua_var.med_endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing IPP codecs", - status); - return status; - } - -#endif /* PJMEDIA_HAS_INTEL_IPP */ + /* + * Register all codecs + */ + pjmedia_audio_codec_config_default(&codec_cfg); + codec_cfg.speex.quality = pjsua_var.media_cfg.quality; + codec_cfg.speex.complexity = -1; + codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode; #if PJMEDIA_HAS_PASSTHROUGH_CODECS /* Register passthrough codecs */ @@ -198,7 +127,6 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) unsigned aud_idx; unsigned ext_fmt_cnt = 0; pjmedia_format ext_fmts[32]; - pjmedia_codec_passthrough_setting setting; /* List extended formats supported by audio devices */ for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) { @@ -235,36 +163,31 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) } /* Init the passthrough codec with supported formats only */ - setting.fmt_cnt = ext_fmt_cnt; - setting.fmts = ext_fmts; - setting.ilbc_mode = cfg->ilbc_mode; - status = pjmedia_codec_passthrough_init2(pjsua_var.med_endpt, &setting); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing passthrough codecs", - status); - return status; - } + 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; } #endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */ -#if PJMEDIA_HAS_G7221_CODEC - /* Register G722.1 codecs */ - status = pjmedia_codec_g7221_init(pjsua_var.med_endpt); + /* Register all codecs */ + status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt, + &codec_cfg); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing G722.1 codec", - status); + PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs")); return status; } -#endif /* PJMEDIA_HAS_G7221_CODEC */ -#if PJMEDIA_HAS_L16_CODEC - /* Register L16 family codecs, but disable all */ - status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing L16 codecs", - status); - return status; - } + /* Set speex/16000 to higher priority*/ + codec_id = pj_str("speex/16000"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2); + + /* Set speex/8000 to next higher priority*/ + codec_id = pj_str("speex/8000"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1); /* Disable ALL L16 codecs */ codec_id = pj_str("L16"); @@ -272,8 +195,6 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), &codec_id, PJMEDIA_CODEC_PRIO_DISABLED); -#endif /* PJMEDIA_HAS_L16_CODEC */ - /* Save additional conference bridge parameters for future * reference. @@ -296,7 +217,6 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) opt |= PJMEDIA_CONF_USE_LINEAR; } - /* Init conference bridge. */ status = pjmedia_conf_create(pjsua_var.pool, pjsua_var.media_cfg.max_media_ports, @@ -334,18 +254,214 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) } #endif + /* Video */ +#if PJMEDIA_HAS_VIDEO + status = pjsua_vid_subsys_init(); + if (status != PJ_SUCCESS) + return status; +#endif + return PJ_SUCCESS; } -/* +/* Check if sound device is idle. */ +static void check_snd_dev_idle() +{ + unsigned call_cnt; + + /* Get the call count, we shouldn't close the sound device when there is + * any calls active. + */ + call_cnt = pjsua_call_get_count(); + + /* When this function is called from pjsua_media_channel_deinit() upon + * disconnecting call, actually the call count hasn't been updated/ + * decreased. So we put additional check here, if there is only one + * call and it's in DISCONNECTED state, there is actually no active + * call. + */ + if (call_cnt == 1) { + pjsua_call_id call_id; + pj_status_t status; + + status = pjsua_enum_calls(&call_id, &call_cnt); + if (status == PJ_SUCCESS && call_cnt > 0 && + !pjsua_call_is_active(call_id)) + { + call_cnt = 0; + } + } + + /* Activate sound device auto-close timer if sound device is idle. + * It is idle when there is no port connection in the bridge and + * there is no active call. + * + * Note: this block is now valid if no snd dev is used because of #1299 + */ + if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL || + pjsua_var.no_snd) && + pjsua_var.snd_idle_timer.id == PJ_FALSE && + pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 && + call_cnt == 0 && + pjsua_var.media_cfg.snd_auto_close_time >= 0) + { + pj_time_val delay; + + delay.msec = 0; + delay.sec = pjsua_var.media_cfg.snd_auto_close_time; + + pjsua_var.snd_idle_timer.id = PJ_TRUE; + pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer, + &delay); + } +} + + +/* Timer callback to close sound device */ +static void close_snd_timer_cb( pj_timer_heap_t *th, + pj_timer_entry *entry) +{ + PJ_UNUSED_ARG(th); + + PJSUA_LOCK(); + if (entry->id) { + PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds", + pjsua_var.media_cfg.snd_auto_close_time)); + + entry->id = PJ_FALSE; + + close_snd_dev(); + } + PJSUA_UNLOCK(); +} + + +/* + * Start pjsua media subsystem. + */ +pj_status_t pjsua_media_subsys_start(void) +{ + pj_status_t status; + +#if DISABLED_FOR_TICKET_1185 + /* Create media for calls, if none is specified */ + if (pjsua_var.calls[0].media[0].tp == NULL) { + pjsua_transport_config transport_cfg; + + /* Create default transport config */ + pjsua_transport_config_default(&transport_cfg); + transport_cfg.port = DEFAULT_RTP_PORT; + + status = pjsua_media_transports_create(&transport_cfg); + if (status != PJ_SUCCESS) + return status; + } +#endif + + pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL, + &close_snd_timer_cb); + + /* Video */ +#if PJMEDIA_HAS_VIDEO + status = pjsua_vid_subsys_start(); + if (status != PJ_SUCCESS) + return status; +#endif + + /* Perform NAT detection */ + status = pjsua_detect_nat_type(); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed")); + } + + return PJ_SUCCESS; +} + + +/* + * Destroy pjsua media subsystem. + */ +pj_status_t pjsua_media_subsys_destroy(void) +{ + unsigned i; + + PJ_LOG(4,(THIS_FILE, "Shutting down media..")); + + close_snd_dev(); + + if (pjsua_var.mconf) { + pjmedia_conf_destroy(pjsua_var.mconf); + pjsua_var.mconf = NULL; + } + + if (pjsua_var.null_port) { + pjmedia_port_destroy(pjsua_var.null_port); + pjsua_var.null_port = NULL; + } + + /* Destroy file players */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) { + if (pjsua_var.player[i].port) { + pjmedia_port_destroy(pjsua_var.player[i].port); + pjsua_var.player[i].port = NULL; + } + } + + /* Destroy file recorders */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) { + if (pjsua_var.recorder[i].port) { + pjmedia_port_destroy(pjsua_var.recorder[i].port); + pjsua_var.recorder[i].port = NULL; + } + } + + /* Close media transports */ + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + unsigned strm_idx; + pjsua_call *call = &pjsua_var.calls[i]; + for (strm_idx=0; strm_idx<call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + if (call_med->tp_st != PJSUA_MED_TP_IDLE) { + pjsua_media_channel_deinit(i); + } + if (call_med->tp && call_med->tp_auto_del) { + pjmedia_transport_close(call_med->tp); + } + call_med->tp = NULL; + } + } + + /* Destroy media endpoint. */ + if (pjsua_var.med_endpt) { + +# if PJMEDIA_HAS_VIDEO + pjsua_vid_subsys_destroy(); +# endif + + pjmedia_endpt_destroy(pjsua_var.med_endpt); + pjsua_var.med_endpt = NULL; + + /* Deinitialize sound subsystem */ + // Not necessary, as pjmedia_snd_deinit() should have been called + // in pjmedia_endpt_destroy(). + //pjmedia_snd_deinit(); + } + + /* Reset RTP port */ + next_rtp_port = 0; + + return PJ_SUCCESS; +} + +/* * 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, pjmedia_sock_info *skinfo) { - enum { + enum { RTP_RETRY = 100 }; int i; @@ -365,6 +481,9 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, if (next_rtp_port == 0) next_rtp_port = (pj_uint16_t)cfg->port; + if (next_rtp_port == 0) + next_rtp_port = (pj_uint16_t)40000; + for (i=0; i<2; ++i) sock[i] = PJ_INVALID_SOCKET; @@ -389,15 +508,15 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, } /* Apply QoS to RTP socket, if specified */ - status = pj_sock_apply_qos2(sock[0], cfg->qos_type, - &cfg->qos_params, + status = pj_sock_apply_qos2(sock[0], cfg->qos_type, + &cfg->qos_params, 2, THIS_FILE, "RTP socket"); /* Bind RTP socket */ - status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr), + status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr), next_rtp_port); if (status != PJ_SUCCESS) { - pj_sock_close(sock[0]); + pj_sock_close(sock[0]); sock[0] = PJ_INVALID_SOCKET; continue; } @@ -411,18 +530,18 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, } /* Apply QoS to RTCP socket, if specified */ - status = pj_sock_apply_qos2(sock[1], cfg->qos_type, - &cfg->qos_params, + status = pj_sock_apply_qos2(sock[1], cfg->qos_type, + &cfg->qos_params, 2, THIS_FILE, "RTCP socket"); /* Bind RTCP socket */ - status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr), + status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr), (pj_uint16_t)(next_rtp_port+1)); if (status != PJ_SUCCESS) { - pj_sock_close(sock[0]); + pj_sock_close(sock[0]); sock[0] = PJ_INVALID_SOCKET; - pj_sock_close(sock[1]); + pj_sock_close(sock[1]); sock[1] = PJ_INVALID_SOCKET; continue; } @@ -435,7 +554,7 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, char ip_addr[32]; pj_str_t stun_srv; - pj_ansi_strcpy(ip_addr, + pj_ansi_strcpy(ip_addr, pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr)); stun_srv = pj_str(ip_addr); @@ -449,23 +568,23 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, } #if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT - if (pj_ntohs(mapped_addr[1].sin_port) == + if (pj_ntohs(mapped_addr[1].sin_port) == pj_ntohs(mapped_addr[0].sin_port)+1) { /* Success! */ break; } - pj_sock_close(sock[0]); + pj_sock_close(sock[0]); sock[0] = PJ_INVALID_SOCKET; - pj_sock_close(sock[1]); + pj_sock_close(sock[1]); sock[1] = PJ_INVALID_SOCKET; #else - if (pj_ntohs(mapped_addr[1].sin_port) != + if (pj_ntohs(mapped_addr[1].sin_port) != pj_ntohs(mapped_addr[0].sin_port)+1) { - PJ_LOG(4,(THIS_FILE, + 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), @@ -514,18 +633,18 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, } if (sock[0] == PJ_INVALID_SOCKET) { - PJ_LOG(1,(THIS_FILE, + PJ_LOG(1,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination")); goto on_error; } skinfo->rtp_sock = sock[0]; - pj_memcpy(&skinfo->rtp_addr_name, + pj_memcpy(&skinfo->rtp_addr_name, &mapped_addr[0], sizeof(pj_sockaddr_in)); skinfo->rtcp_sock = sock[1]; - pj_memcpy(&skinfo->rtcp_addr_name, + pj_memcpy(&skinfo->rtcp_addr_name, &mapped_addr[1], sizeof(pj_sockaddr_in)); PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s", @@ -546,294 +665,106 @@ on_error: return status; } -/* Check if sound device is idle. */ -static void check_snd_dev_idle() -{ - unsigned call_cnt; - - /* Get the call count, we shouldn't close the sound device when there is - * any calls active. - */ - call_cnt = pjsua_call_get_count(); - - /* When this function is called from pjsua_media_channel_deinit() upon - * disconnecting call, actually the call count hasn't been updated/ - * decreased. So we put additional check here, if there is only one - * call and it's in DISCONNECTED state, there is actually no active - * call. - */ - if (call_cnt == 1) { - pjsua_call_id call_id; - pj_status_t status; - - status = pjsua_enum_calls(&call_id, &call_cnt); - if (status == PJ_SUCCESS && call_cnt > 0 && - !pjsua_call_is_active(call_id)) - { - call_cnt = 0; - } - } - - /* Activate sound device auto-close timer if sound device is idle. - * It is idle when there is no port connection in the bridge and - * there is no active call. - */ - if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) && - pjsua_var.snd_idle_timer.id == PJ_FALSE && - pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 && - call_cnt == 0 && - pjsua_var.media_cfg.snd_auto_close_time >= 0) - { - pj_time_val delay; - - delay.msec = 0; - delay.sec = pjsua_var.media_cfg.snd_auto_close_time; - - pjsua_var.snd_idle_timer.id = PJ_TRUE; - pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer, - &delay); - } -} - - -/* Timer callback to close sound device */ -static void close_snd_timer_cb( pj_timer_heap_t *th, - pj_timer_entry *entry) -{ - PJ_UNUSED_ARG(th); - - PJSUA_LOCK(); - if (entry->id) { - PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds", - pjsua_var.media_cfg.snd_auto_close_time)); - - entry->id = PJ_FALSE; - - close_snd_dev(); - } - PJSUA_UNLOCK(); -} - - -/* - * Start pjsua media subsystem. - */ -pj_status_t pjsua_media_subsys_start(void) +/* Create normal UDP media transports */ +static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg, + pjsua_call_media *call_med) { + pjmedia_sock_info skinfo; pj_status_t status; - /* Create media for calls, if none is specified */ - if (pjsua_var.calls[0].med_tp == NULL) { - pjsua_transport_config transport_cfg; - - /* Create default transport config */ - pjsua_transport_config_default(&transport_cfg); - transport_cfg.port = DEFAULT_RTP_PORT; - - status = pjsua_media_transports_create(&transport_cfg); - if (status != PJ_SUCCESS) - return status; - } - - pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL, - &close_snd_timer_cb); - - /* Perform NAT detection */ - pjsua_detect_nat_type(); - - return PJ_SUCCESS; -} - - -/* - * Destroy pjsua media subsystem. - */ -pj_status_t pjsua_media_subsys_destroy(void) -{ - unsigned i; - - PJ_LOG(4,(THIS_FILE, "Shutting down media..")); - - close_snd_dev(); - - if (pjsua_var.mconf) { - pjmedia_conf_destroy(pjsua_var.mconf); - pjsua_var.mconf = NULL; - } - - if (pjsua_var.null_port) { - pjmedia_port_destroy(pjsua_var.null_port); - pjsua_var.null_port = NULL; - } - - /* Destroy file players */ - for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) { - if (pjsua_var.player[i].port) { - pjmedia_port_destroy(pjsua_var.player[i].port); - pjsua_var.player[i].port = NULL; - } - } - - /* Destroy file recorders */ - for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) { - if (pjsua_var.recorder[i].port) { - pjmedia_port_destroy(pjsua_var.recorder[i].port); - pjsua_var.recorder[i].port = NULL; - } + status = create_rtp_rtcp_sock(cfg, &skinfo); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket", + status); + goto on_error; } - /* Close media transports */ - for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) { - pjsua_media_channel_deinit(i); - } - if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) { - pjmedia_transport_close(pjsua_var.calls[i].med_tp); - } - pjsua_var.calls[i].med_tp = NULL; + status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL, + &skinfo, 0, &call_med->tp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media transport", + status); + goto on_error; } - /* Destroy media endpoint. */ - if (pjsua_var.med_endpt) { - - /* Shutdown all codecs: */ -# if PJMEDIA_HAS_SPEEX_CODEC - pjmedia_codec_speex_deinit(); -# endif /* PJMEDIA_HAS_SPEEX_CODEC */ - -# if PJMEDIA_HAS_GSM_CODEC - pjmedia_codec_gsm_deinit(); -# endif /* PJMEDIA_HAS_GSM_CODEC */ - -# if PJMEDIA_HAS_G711_CODEC - pjmedia_codec_g711_deinit(); -# endif /* PJMEDIA_HAS_G711_CODEC */ - -# if PJMEDIA_HAS_G722_CODEC - pjmedia_codec_g722_deinit(); -# endif /* PJMEDIA_HAS_G722_CODEC */ - -# if PJMEDIA_HAS_INTEL_IPP - pjmedia_codec_ipp_deinit(); -# endif /* PJMEDIA_HAS_INTEL_IPP */ + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING, + pjsua_var.media_cfg.tx_drop_pct); -# if PJMEDIA_HAS_PASSTHROUGH_CODECS - pjmedia_codec_passthrough_deinit(); -# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */ + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING, + pjsua_var.media_cfg.rx_drop_pct); -# if PJMEDIA_HAS_G7221_CODEC - pjmedia_codec_g7221_deinit(); -# endif /* PJMEDIA_HAS_G7221_CODEC */ - -# if PJMEDIA_HAS_L16_CODEC - pjmedia_codec_l16_deinit(); -# endif /* PJMEDIA_HAS_L16_CODEC */ - - pjmedia_endpt_destroy(pjsua_var.med_endpt); - pjsua_var.med_endpt = NULL; - - /* Deinitialize sound subsystem */ - // Not necessary, as pjmedia_snd_deinit() should have been called - // in pjmedia_endpt_destroy(). - //pjmedia_snd_deinit(); - } + return PJ_SUCCESS; - /* Reset RTP port */ - next_rtp_port = 0; +on_error: + if (call_med->tp) + pjmedia_transport_close(call_med->tp); - return PJ_SUCCESS; + return status; } - +#if DISABLED_FOR_TICKET_1185 /* Create normal UDP media transports */ static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg) { unsigned i; - pjmedia_sock_info skinfo; pj_status_t status; - /* Create each media transport */ - for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; - status = create_rtp_rtcp_sock(cfg, &skinfo); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket", - status); - goto on_error; - } + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; - status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL, - &skinfo, 0, - &pjsua_var.calls[i].med_tp); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create media transport", - status); - goto on_error; + status = create_udp_media_transport(cfg, &call_med->tp); + if (status != PJ_SUCCESS) + goto on_error; } - - pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp, - PJMEDIA_DIR_ENCODING, - pjsua_var.media_cfg.tx_drop_pct); - - pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp, - PJMEDIA_DIR_DECODING, - pjsua_var.media_cfg.rx_drop_pct); - } return PJ_SUCCESS; on_error: - for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - if (pjsua_var.calls[i].med_tp != NULL) { - pjmedia_transport_close(pjsua_var.calls[i].med_tp); - pjsua_var.calls[i].med_tp = NULL; + for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + if (call_med->tp) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + } } } - return status; } - +#endif /* This callback is called when ICE negotiation completes */ static void on_ice_complete(pjmedia_transport *tp, pj_ice_strans_op op, pj_status_t result) { - unsigned id; - pj_bool_t found = PJ_FALSE; - - /* Find call which has this media transport */ + pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data; - PJSUA_LOCK(); - - for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) { - if (pjsua_var.calls[id].med_tp == tp || - pjsua_var.calls[id].med_orig == tp) - { - found = PJ_TRUE; - break; - } - } - - PJSUA_UNLOCK(); - - if (!found) + if (!call_med) return; switch (op) { case PJ_ICE_STRANS_OP_INIT: - pjsua_var.calls[id].med_tp_ready = result; + call_med->tp_ready = result; break; case PJ_ICE_STRANS_OP_NEGOTIATION: if (result != PJ_SUCCESS) { - pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR; - pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE; + call_med->state = PJSUA_CALL_MEDIA_ERROR; + call_med->dir = PJMEDIA_DIR_NONE; - if (pjsua_var.ua_cfg.cb.on_call_media_state) { - pjsua_var.ua_cfg.cb.on_call_media_state(id); + 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 { + } else if (call_med->call) { /* Send UPDATE if default transport address is different than * what was advertised (ticket #881) */ @@ -853,36 +784,38 @@ static void on_ice_complete(pjmedia_transport *tp, if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING && pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name, - &pjsua_var.calls[id].med_rtp_addr)) + &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 = pjsua_var.calls[id].inv->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", id, + "call %d, sending %s", call_med->call->index, (use_update ? "UPDATE" : "re-INVITE"))); if (use_update) - pjsua_call_update(id, 0, NULL); + pjsua_call_update(call_med->call->index, 0, NULL); else - pjsua_call_reinvite(id, 0, NULL); + pjsua_call_reinvite(call_med->call->index, 0, NULL); } } 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", id)); + "ICE keep alive failure for transport %d:%d", + call_med->call->index, call_med->idx)); } if (pjsua_var.ua_cfg.cb.on_ice_transport_error) { + pjsua_call_id id = call_med->call->index; (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result, NULL); } @@ -918,11 +851,15 @@ static pj_status_t parse_host_port(const pj_str_t *host_port, } /* Create ICE media transports (when ice is enabled) */ -static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg) +static pj_status_t create_ice_media_transport( + const pjsua_transport_config *cfg, + pjsua_call_media *call_med) { char stunip[PJ_INET6_ADDRSTRLEN]; pj_ice_strans_cfg ice_cfg; - unsigned i; + pjmedia_ice_cb ice_cb; + char name[32]; + unsigned comp_cnt; pj_status_t status; /* Make sure STUN server resolution has completed */ @@ -979,68 +916,97 @@ static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg) sizeof(cfg->qos_params)); } - /* Create each media transport */ - for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - pjmedia_ice_cb ice_cb; - char name[32]; - unsigned comp_cnt; - - pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb)); - ice_cb.on_ice_complete = &on_ice_complete; - pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i); - pjsua_var.calls[i].med_tp_ready = PJ_EPENDING; - - comp_cnt = 1; - if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp) - ++comp_cnt; - - status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt, - &ice_cfg, &ice_cb, - &pjsua_var.calls[i].med_tp); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create ICE media transport", - status); - goto on_error; - } + 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; - /* Wait until transport is initialized, or time out */ - PJSUA_UNLOCK(); - while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) { - pjsua_handle_events(100); - } - PJSUA_LOCK(); - if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing ICE media transport", - pjsua_var.calls[i].med_tp_ready); - status = pjsua_var.calls[i].med_tp_ready; - goto on_error; - } + comp_cnt = 1; + if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp) + ++comp_cnt; - pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp, - PJMEDIA_DIR_ENCODING, - pjsua_var.media_cfg.tx_drop_pct); + status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt, + &ice_cfg, &ice_cb, 0, call_med, + &call_med->tp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create ICE media transport", + status); + goto on_error; + } - pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp, - PJMEDIA_DIR_DECODING, - pjsua_var.media_cfg.rx_drop_pct); + /* Wait until transport is initialized, or time out */ + PJSUA_UNLOCK(); + while (call_med->tp_ready == PJ_EPENDING) { + pjsua_handle_events(100); + } + PJSUA_LOCK(); + if (call_med->tp_ready != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing ICE media transport", + call_med->tp_ready); + status = call_med->tp_ready; + goto on_error; } + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING, + pjsua_var.media_cfg.tx_drop_pct); + + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING, + pjsua_var.media_cfg.rx_drop_pct); + return PJ_SUCCESS; on_error: - for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - if (pjsua_var.calls[i].med_tp != NULL) { - pjmedia_transport_close(pjsua_var.calls[i].med_tp); - pjsua_var.calls[i].med_tp = NULL; - } + if (call_med->tp != NULL) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; } return status; } +#if DISABLED_FOR_TICKET_1185 +/* Create ICE media transports (when ice is enabled) */ +static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg) +{ + unsigned i; + pj_status_t status; + + for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + status = create_ice_media_transport(cfg, call_med); + if (status != PJ_SUCCESS) + goto on_error; + } + } + + return PJ_SUCCESS; + +on_error: + for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + if (call_med->tp) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + } + } + } + return status; +} +#endif +#if DISABLED_FOR_TICKET_1185 /* - * Create UDP media transports for all the calls. This function creates + * Create media transports for all the calls. This function creates * one UDP media transport for each call. */ PJ_DEF(pj_status_t) pjsua_media_transports_create( @@ -1058,12 +1024,17 @@ PJ_DEF(pj_status_t) pjsua_media_transports_create( /* Delete existing media transports */ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - if (pjsua_var.calls[i].med_tp != NULL && - pjsua_var.calls[i].med_tp_auto_del) - { - pjmedia_transport_close(pjsua_var.calls[i].med_tp); - pjsua_var.calls[i].med_tp = NULL; - pjsua_var.calls[i].med_orig = NULL; + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + if (call_med->tp && call_med->tp_auto_del) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + call_med->tp_orig = NULL; + } } } @@ -1079,7 +1050,14 @@ PJ_DEF(pj_status_t) pjsua_media_transports_create( /* Set media transport auto_delete to True */ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE; + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + call_med->tp_auto_del = PJ_TRUE; + } } PJSUA_UNLOCK(); @@ -1100,184 +1078,421 @@ PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[], /* Assign the media transports */ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - if (pjsua_var.calls[i].med_tp != NULL && - pjsua_var.calls[i].med_tp_auto_del) - { - pjmedia_transport_close(pjsua_var.calls[i].med_tp); + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + if (call_med->tp && call_med->tp_auto_del) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + call_med->tp_orig = NULL; + } } - pjsua_var.calls[i].med_tp = tp[i].transport; - pjsua_var.calls[i].med_tp_auto_del = auto_delete; + PJ_TODO(remove_pjsua_media_transports_attach); + + call->media[0].tp = tp[i].transport; + call->media[0].tp_auto_del = auto_delete; } return PJ_SUCCESS; } +#endif - -static int find_audio_index(const pjmedia_sdp_session *sdp, - pj_bool_t prefer_srtp) +/* Go through the list of media in the SDP, find acceptable media, and + * sort them based on the "quality" of the media, and store the indexes + * in the specified array. Media with the best quality will be listed + * first in the array. The quality factors considered currently is + * encryption. + */ +static void sort_media(const pjmedia_sdp_session *sdp, + const pj_str_t *type, + pjmedia_srtp_use use_srtp, + pj_uint8_t midx[], + unsigned *p_count) { unsigned i; - int audio_idx = -1; + unsigned count = 0; + int score[PJSUA_MAX_CALL_MEDIA]; - for (i=0; i<sdp->media_count; ++i) { - const pjmedia_sdp_media *m = sdp->media[i]; + pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA); - /* Skip if media is not audio */ - if (pj_stricmp2(&m->desc.media, "audio") != 0) - continue; + *p_count = 0; + for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i) + score[i] = 1; - /* Skip if media is disabled */ - if (m->desc.port == 0) - continue; + /* Score each media */ + for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) { + const pjmedia_sdp_media *m = sdp->media[i]; + const pjmedia_sdp_conn *c; - /* Skip if transport is not supported */ - if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 && - pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0) - { + /* Skip different media */ + if (pj_stricmp(&m->desc.media, type) != 0) { + score[count++] = -22000; continue; } - if (audio_idx == -1) { - audio_idx = i; - } else { - /* We've found multiple candidates. This could happen - * e.g. when remote is offering both RTP/SAVP and RTP/AVP, - * or when remote for some reason offers two audio. - */ + c = m->conn? m->conn : sdp->conn; - if (prefer_srtp && - pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) - { - /* Prefer RTP/SAVP when our media transport is SRTP */ - audio_idx = i; + /* Supported transports */ + if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) { + switch (use_srtp) { + case PJMEDIA_SRTP_MANDATORY: + case PJMEDIA_SRTP_OPTIONAL: + ++score[i]; + break; + case PJMEDIA_SRTP_DISABLED: + --score[i]; break; - } else if (!prefer_srtp && - pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) - { - /* Prefer RTP/AVP when our media transport is NOT SRTP */ - audio_idx = i; } + } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) { + switch (use_srtp) { + case PJMEDIA_SRTP_MANDATORY: + --score[i]; + break; + case PJMEDIA_SRTP_OPTIONAL: + /* No change in score */ + break; + case PJMEDIA_SRTP_DISABLED: + ++score[i]; + break; + } + } else { + score[i] -= 10; } + + /* Is media disabled? */ + if (m->desc.port == 0) + score[i] -= 10; + + /* Is media inactive? */ + if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) || + pj_strcmp2(&c->addr, "0.0.0.0") == 0) + { + //score[i] -= 10; + score[i] -= 1; + } + + ++count; } - return audio_idx; + /* Created sorted list based on quality */ + for (i=0; i<count; ++i) { + unsigned j; + int best = 0; + + for (j=1; j<count; ++j) { + if (score[j] > score[best]) + best = j; + } + /* Don't put media with negative score, that media is unacceptable + * for us. + */ + if (score[best] >= 0) { + midx[*p_count] = (pj_uint8_t)best; + (*p_count)++; + } + + score[best] = -22000; + + } } +/* Callback to receive media events */ +static pj_status_t call_media_on_event(pjmedia_event_subscription *esub, + pjmedia_event *event) +{ + pjsua_call_media *call_med = (pjsua_call_media*)esub->user_data; + pjsua_call *call = call_med->call; -pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, - pjsip_role_e role, - int security_level, - pj_pool_t *tmp_pool, - const pjmedia_sdp_session *rem_sdp, - int *sip_err_code) + if (pjsua_var.ua_cfg.cb.on_call_media_event && call) { + ++event->proc_cnt; + (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index, + call_med->idx, event); + } + + return PJ_SUCCESS; +} + +/* Initialize the media line */ +pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, + pjmedia_type type, + const pjsua_transport_config *tcfg, + int security_level, + int *sip_err_code) { - pjsua_call *call = &pjsua_var.calls[call_id]; + pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; pj_status_t status; -#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) - pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; - pjmedia_srtp_setting srtp_opt; - pjmedia_transport *srtp = NULL; -#endif + /* + * Note: this function may be called when the media already exists + * (e.g. in reinvites, updates, etc.) + */ + call_med->type = type; - PJ_UNUSED_ARG(role); + /* Create the media transport for initial call. This is blocking for now */ + if (call_med->tp == NULL) { + if (pjsua_var.media_cfg.enable_ice) { + status = create_ice_media_transport(tcfg, call_med); + } else { + status = create_udp_media_transport(tcfg, call_med); + } - /* Return error if media transport has not been created yet - * (e.g. application is starting) - */ - if (call->med_tp == NULL) { - if (sip_err_code) - *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR; - return PJ_EBUSY; + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport")); + return status; + } + + call_med->tp_st = PJSUA_MED_TP_IDLE; + + /* While in initial call, set default video devices */ + if (type == PJMEDIA_TYPE_VIDEO) { + call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev; + call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev; + if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info); + call_med->strm.v.rdr_dev = info.id; + } + if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info); + call_med->strm.v.cap_dev = info.id; + } + } + } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) { + /* Media is being reenabled. */ + call_med->tp_st = PJSUA_MED_TP_INIT; } #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) - /* This function may be called when SRTP transport already exists + /* This function may be called when SRTP transport already exists * (e.g: in re-invite, update), don't need to destroy/re-create. */ - if (!call->med_orig || call->med_tp == call->med_orig) { + if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) { + pjmedia_srtp_setting srtp_opt; + pjmedia_transport *srtp = NULL; /* Check if SRTP requires secure signaling */ if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) { if (security_level < acc->cfg.srtp_secure_signaling) { if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; - return PJSIP_ESESSIONINSECURE; + status = PJSIP_ESESSIONINSECURE; + goto on_error; } } /* Always create SRTP adapter */ pjmedia_srtp_setting_default(&srtp_opt); - srtp_opt.close_member_tp = PJ_FALSE; - /* If media session has been ever established, let's use remote's + srtp_opt.close_member_tp = PJ_TRUE; + /* If media session has been ever established, let's use remote's * preference in SRTP usage policy, especially when it is stricter. */ - if (call->rem_srtp_use > acc->cfg.use_srtp) - srtp_opt.use = call->rem_srtp_use; + if (call_med->rem_srtp_use > acc->cfg.use_srtp) + srtp_opt.use = call_med->rem_srtp_use; else srtp_opt.use = acc->cfg.use_srtp; - status = pjmedia_transport_srtp_create(pjsua_var.med_endpt, - call->med_tp, + status = pjmedia_transport_srtp_create(pjsua_var.med_endpt, + call_med->tp, &srtp_opt, &srtp); if (status != PJ_SUCCESS) { if (sip_err_code) *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR; - return status; + goto on_error; } /* Set SRTP as current media transport */ - call->med_orig = call->med_tp; - call->med_tp = srtp; + call_med->tp_orig = call_med->tp; + call_med->tp = srtp; } #else - call->med_orig = call->med_tp; + call->tp_orig = call->tp; PJ_UNUSED_ARG(security_level); #endif - /* Find out which media line in SDP that we support. If we are offerer, - * audio will be initialized at index 0 in SDP. + pjmedia_event_subscription_init(&call_med->esub_rend, &call_media_on_event, + call_med); + pjmedia_event_subscription_init(&call_med->esub_cap, &call_media_on_event, + call_med); + + return PJ_SUCCESS; + +on_error: + if (call_med->tp) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + } + return status; +} + +pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, + pjsip_role_e role, + int security_level, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *rem_sdp, + int *sip_err_code) +{ + const pj_str_t STR_AUDIO = { "audio", 5 }; + const pj_str_t STR_VIDEO = { "video", 5 }; + pjsua_call *call = &pjsua_var.calls[call_id]; + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA]; + unsigned maudcnt = PJ_ARRAY_SIZE(maudidx); + pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA]; + unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx); + pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA]; + unsigned mi; + pj_status_t status; + + PJ_UNUSED_ARG(role); + + /* + * Note: this function may be called when the media already exists + * (e.g. in reinvites, updates, etc). */ - if (rem_sdp == NULL) { - call->audio_idx = 0; - } - /* Otherwise find out the candidate audio media line in SDP */ - else { - pj_bool_t srtp_active; -#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) - srtp_active = acc->cfg.use_srtp; -#else - srtp_active = PJ_FALSE; + if (pjsua_get_state() != PJSUA_STATE_RUNNING) + return PJ_EBUSY; + +#if DISABLED_FOR_TICKET_1185 + /* Return error if media transport has not been created yet + * (e.g. application is starting) + */ + for (i=0; i<call->med_cnt; ++i) { + if (call->media[i].tp == NULL) { + return PJ_EBUSY; + } + } #endif - /* Media count must have been checked */ - pj_assert(rem_sdp->media_count != 0); + if (rem_sdp) { + sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp, + maudidx, &maudcnt); + if (maudcnt > acc->cfg.max_audio_cnt) + maudcnt = acc->cfg.max_audio_cnt; + + if (maudcnt==0) { + /* Expecting audio in the offer */ + if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + pjsua_media_channel_deinit(call_id); + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE); + } + + sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp, + mvididx, &mvidcnt); + if (mvidcnt > acc->cfg.max_video_cnt) + mvidcnt = acc->cfg.max_video_cnt; + + /* Update media count only when remote add any media, this media count + * must never decrease. + */ + if (call->med_cnt < rem_sdp->media_count) + call->med_cnt = PJ_MIN(rem_sdp->media_count, PJSUA_MAX_CALL_MEDIA); - call->audio_idx = find_audio_index(rem_sdp, srtp_active); + } else { + maudcnt = acc->cfg.max_audio_cnt; + for (mi=0; mi<maudcnt; ++mi) { + maudidx[mi] = (pj_uint8_t)mi; + media_types[mi] = PJMEDIA_TYPE_AUDIO; + } + mvidcnt = acc->cfg.max_video_cnt; + for (mi=0; mi<mvidcnt; ++mi) { + media_types[maudcnt + mi] = PJMEDIA_TYPE_VIDEO; + } + + call->med_cnt = maudcnt + mvidcnt; } - /* Reject offer if we couldn't find a good m=audio line in offer */ - if (call->audio_idx < 0) { + if (call->med_cnt == 0) { + /* Expecting at least one media */ if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE; pjsua_media_channel_deinit(call_id); return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE); } - PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d", + /* Initialize each media line */ + for (mi=0; mi < call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + pj_bool_t enabled = PJ_FALSE; + pjmedia_type media_type = PJMEDIA_TYPE_NONE; + + if (rem_sdp) { + if (mi >= rem_sdp->media_count) { + /* Media has been removed in remote re-offer */ + media_type = call_med->type; + } else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_AUDIO)) { + media_type = PJMEDIA_TYPE_AUDIO; + if (pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) { + enabled = PJ_TRUE; + } + } + else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_VIDEO)) { + media_type = PJMEDIA_TYPE_VIDEO; + if (pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) { + enabled = PJ_TRUE; + } + } + + } else { + enabled = PJ_TRUE; + media_type = media_types[mi]; + } + + if (enabled) { + status = pjsua_call_media_init(call_med, media_type, + &acc->cfg.rtp_cfg, + security_level, sip_err_code); + if (status != PJ_SUCCESS) { + pjsua_media_channel_deinit(call_id); + return status; + } + } else { + /* By convention, the media is disabled if transport is NULL + * or transport state is PJSUA_MED_TP_DISABLED. + */ + if (call_med->tp) { + // Don't close transport here, as SDP negotiation has not been + // done and stream may be still active. + //pjmedia_transport_close(call_med->tp); + //call_med->tp = NULL; + pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT || + call_med->tp_st == PJSUA_MED_TP_RUNNING); + call_med->tp_st = PJSUA_MED_TP_DISABLED; + } + + /* Put media type just for info */ + call_med->type = media_type; + } + } + + call->audio_idx = maudidx[0]; + + PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d", call->audio_idx, call->index)); - /* Create the media transport */ - status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0, - rem_sdp, call->audio_idx); - if (status != PJ_SUCCESS) { - if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; - pjsua_media_channel_deinit(call_id); - return status; + /* Tell the media transport of a new offer/answer session */ + for (mi=0; mi < call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + + /* Note: tp may be NULL if this media line is disabled */ + if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) { + status = pjmedia_transport_media_create(call_med->tp, + tmp_pool, 0, + rem_sdp, mi); + if (status != PJ_SUCCESS) { + if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; + pjsua_media_channel_deinit(call_id); + return status; + } + + call_med->tp_st = PJSUA_MED_TP_INIT; + } } - call->med_tp_st = PJSUA_MED_TP_INIT; return PJ_SUCCESS; } @@ -1285,106 +1500,183 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, pj_pool_t *pool, const pjmedia_sdp_session *rem_sdp, pjmedia_sdp_session **p_sdp, - int *sip_status_code) + int *sip_err_code) { - enum { MAX_MEDIA = 1 }; + enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA }; pjmedia_sdp_session *sdp; - pjmedia_transport_info tpinfo; + pj_sockaddr origin; pjsua_call *call = &pjsua_var.calls[call_id]; pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL; + unsigned mi; pj_status_t status; - /* Return error if media transport has not been created yet - * (e.g. application is starting) - */ - if (call->med_tp == NULL) { + if (pjsua_get_state() != PJSUA_STATE_RUNNING) return PJ_EBUSY; - } - - if (rem_sdp && rem_sdp->media_count != 0) { - pj_bool_t srtp_active; -#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) - srtp_active = pjsua_var.acc[call->acc_id].cfg.use_srtp; -#else - srtp_active = PJ_FALSE; -#endif + if (rem_sdp) { + /* If this is a re-offer, let's re-initialize media as remote may + * add or remove media + */ + if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) { + status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS, + call->secure_level, pool, + rem_sdp, sip_err_code); + if (status != PJ_SUCCESS) + return status; + } - call->audio_idx = find_audio_index(rem_sdp, srtp_active); - if (call->audio_idx == -1) { - /* No audio in the offer. We can't accept this */ - PJ_LOG(4,(THIS_FILE, - "Unable to accept SDP offer without audio for call %d", - call_id)); - return PJMEDIA_SDP_EINMEDIA; +#if 0 + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA]; + unsigned maudcnt = PJ_ARRAY_SIZE(maudidx); + + sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp, + maudidx, &maudcnt); + + if (maudcnt==0) { + /* Expecting audio in the offer */ + if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + pjsua_media_channel_deinit(call_id); + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE); } - } - /* Media index must have been determined before */ - pj_assert(call->audio_idx != -1); + call->audio_idx = maudidx[0]; +#endif + } else { + /* Audio is first in our offer, by convention */ + // The audio_idx should not be changed here, as this function may be + // called in generating re-offer and the current active audio index + // can be anywhere. + //call->audio_idx = 0; + } +#if 0 + // Since r3512, old-style hold should have got transport, created by + // pjsua_media_channel_init() in initial offer/answer or remote reoffer. /* Create media if it's not created. This could happen when call is - * currently on-hold + * currently on-hold (with the old style hold) */ - if (call->med_tp_st == PJSUA_MED_TP_IDLE) { + if (call->media[call->audio_idx].tp == NULL) { pjsip_role_e role; role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC); status = pjsua_media_channel_init(call_id, role, call->secure_level, - pool, rem_sdp, sip_status_code); + pool, rem_sdp, sip_err_code); if (status != PJ_SUCCESS) return status; } +#endif /* Get SDP negotiator state */ if (call->inv && call->inv->neg) sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg); - /* Get media socket info */ - pjmedia_transport_info_init(&tpinfo); - pjmedia_transport_get_info(call->med_tp, &tpinfo); + /* Get one address to use in the origin field */ + pj_bzero(&origin, sizeof(origin)); + for (mi=0; mi<call->med_cnt; ++mi) { + pjmedia_transport_info tpinfo; - /* Create SDP */ - status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA, - &tpinfo.sock_info, &sdp); - if (status != PJ_SUCCESS) { - if (sip_status_code) *sip_status_code = 500; - return status; + if (call->media[mi].tp == NULL) + continue; + + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call->media[mi].tp, &tpinfo); + pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name); + break; } - /* If we're answering or updating the session with a new offer, - * and the selected media is not the first media - * in SDP, then fill in the unselected media with with zero port. - * Otherwise we'll crash in transport_encode_sdp() because the media - * lines are not aligned between offer and answer. - */ - if (call->audio_idx != 0 && - (rem_sdp || sdp_neg_state==PJMEDIA_SDP_NEG_STATE_DONE)) - { - unsigned i; - const pjmedia_sdp_session *ref_sdp = rem_sdp; + /* Create the base (blank) SDP */ + status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL, + &origin, &sdp); + if (status != PJ_SUCCESS) + return status; + + /* Process each media line */ + for (mi=0; mi<call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + pjmedia_sdp_media *m = NULL; + pjmedia_transport_info tpinfo; - if (!ref_sdp) { - /* We are updating session with a new offer */ - status = pjmedia_sdp_neg_get_active_local(call->inv->neg, - &ref_sdp); - pj_assert(status == PJ_SUCCESS); + if (rem_sdp && mi >= rem_sdp->media_count) { + /* Remote might have removed some media lines. */ + break; } - for (i=0; i<ref_sdp->media_count; ++i) { - const pjmedia_sdp_media *ref_m = ref_sdp->media[i]; - pjmedia_sdp_media *m; + if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED) + { + /* + * This media is disabled. Just create a valid SDP with zero + * port. + */ + 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: + m->desc.media = pj_str("audio"); + m->desc.fmt[0] = pj_str("0"); + break; + case PJMEDIA_TYPE_VIDEO: + m->desc.media = pj_str("video"); + m->desc.fmt[0] = pj_str("31"); + break; + default: + if (rem_sdp && mi < rem_sdp->media_count) { + pj_strdup(pool, &m->desc.media, + &rem_sdp->media[mi]->desc.media); + pj_strdup(pool, &m->desc.fmt[0], + &rem_sdp->media[mi]->desc.fmt[0]); + } else { + pj_assert(!"Invalid call_med media type"); + return PJ_EBUG; + } + } + + sdp->media[sdp->media_count++] = m; + continue; + } - if ((int)i == call->audio_idx) - continue; + /* Get transport address info */ + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); - m = pjmedia_sdp_media_clone_deactivate(pool, ref_m); - if (i==sdp->media_count) - sdp->media[sdp->media_count++] = m; - else { - pj_array_insert(sdp->media, sizeof(sdp->media[0]), - sdp->media_count, i, &m); - ++sdp->media_count; - } + /* Ask pjmedia endpoint to create SDP media line */ + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &m); + break; + case PJMEDIA_TYPE_VIDEO: + status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &m); + break; + default: + pj_assert(!"Invalid call_med media type"); + return PJ_EBUG; + } + + if (status != PJ_SUCCESS) + return status; + + sdp->media[sdp->media_count++] = m; + + /* Give to transport */ + status = pjmedia_transport_encode_sdp(call_med->tp, pool, + sdp, rem_sdp, mi); + if (status != PJ_SUCCESS) { + if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; + return status; + } + + /* 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); } } @@ -1412,15 +1704,8 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, } - /* Give the SDP to media transport */ - status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp, - call->audio_idx); - if (status != PJ_SUCCESS) { - if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE; - return status; - } -#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) +#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) /* Check if SRTP is in optional mode and configured to use duplicated * media, i.e: secured and unsecured version, in the SDP offer. */ @@ -1433,13 +1718,13 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, for (i = 0; i < sdp->media_count; ++i) { pjmedia_sdp_media *m = sdp->media[i]; - /* Check if this media is unsecured but has SDP "crypto" + /* Check if this media is unsecured but has SDP "crypto" * attribute. */ if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 && pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL) { - if (i == (unsigned)call->audio_idx && + if (i == (unsigned)call->audio_idx && sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE) { /* This is a session update, and peer has chosen the @@ -1462,7 +1747,7 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, /* Insert the new media before the unsecured media */ if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) { - pj_array_insert(sdp->media, sizeof(new_m), + pj_array_insert(sdp->media, sizeof(new_m), sdp->media_count, i, &new_m); ++sdp->media_count; ++i; @@ -1473,10 +1758,6 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, } #endif - /* Update currently advertised RTP source address */ - pj_memcpy(&call->med_rtp_addr, &tpinfo.sock_info.rtp_addr_name, - sizeof(pj_sockaddr)); - *p_sdp = sdp; return PJ_SUCCESS; } @@ -1485,61 +1766,83 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, static void stop_media_session(pjsua_call_id call_id) { pjsua_call *call = &pjsua_var.calls[call_id]; + unsigned mi; - if (call->conf_slot != PJSUA_INVALID_ID) { - if (pjsua_var.mconf) { - pjsua_conf_remove_port(call->conf_slot); - } - call->conf_slot = PJSUA_INVALID_ID; - } + for (mi=0; mi<call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; - if (call->session) { - pjmedia_rtcp_stat stat; + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + pjmedia_stream *strm = call_med->strm.a.stream; + pjmedia_rtcp_stat stat; - if ((call->media_dir & PJMEDIA_DIR_ENCODING) && - (pjmedia_session_get_stream_stat(call->session, 0, &stat) - == PJ_SUCCESS)) - { - /* Save RTP timestamp & sequence, so when media session is - * restarted, those values will be restored as the initial - * RTP timestamp & sequence of the new media session. So in - * the same call session, RTP timestamp and sequence are - * guaranteed to be contigue. - */ - call->rtp_tx_seq_ts_set = 1 | (1 << 1); - call->rtp_tx_seq = stat.rtp_tx_last_seq; - call->rtp_tx_ts = stat.rtp_tx_last_ts; - } + if (strm) { + if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) { + if (pjsua_var.mconf) { + pjsua_conf_remove_port(call_med->strm.a.conf_slot); + } + call_med->strm.a.conf_slot = PJSUA_INVALID_ID; + } - if (pjsua_var.ua_cfg.cb.on_stream_destroyed) { - pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0); - } + if ((call_med->dir & PJMEDIA_DIR_ENCODING) && + (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS)) + { + /* Save RTP timestamp & sequence, so when media session is + * restarted, those values will be restored as the initial + * RTP timestamp & sequence of the new media session. So in + * the same call session, RTP timestamp and sequence are + * guaranteed to be contigue. + */ + call_med->rtp_tx_seq_ts_set = 1 | (1 << 1); + call_med->rtp_tx_seq = stat.rtp_tx_last_seq; + call_med->rtp_tx_ts = stat.rtp_tx_last_ts; + } + + if (pjsua_var.ua_cfg.cb.on_stream_destroyed) { + pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi); + } - pjmedia_session_destroy(call->session); - call->session = NULL; + pjmedia_stream_destroy(strm); + call_med->strm.a.stream = NULL; + } + } - PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed", - call_id)); +#if PJMEDIA_HAS_VIDEO + else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + stop_video_stream(call_med); + } +#endif + PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed", + call_id, mi)); + call_med->state = PJSUA_CALL_MEDIA_NONE; } - - call->media_st = PJSUA_CALL_MEDIA_NONE; } pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id) { pjsua_call *call = &pjsua_var.calls[call_id]; + unsigned mi; stop_media_session(call_id); - if (call->med_tp_st != PJSUA_MED_TP_IDLE) { - pjmedia_transport_media_stop(call->med_tp); - call->med_tp_st = PJSUA_MED_TP_IDLE; - } + for (mi=0; mi<call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; - if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) { - pjmedia_transport_close(call->med_tp); - call->med_tp = call->med_orig; + if (call_med->tp_st != PJSUA_MED_TP_IDLE) { + pjmedia_transport_media_stop(call_med->tp); + call_med->tp_st = PJSUA_MED_TP_IDLE; + } + + //if (call_med->tp_orig && call_med->tp && + // call_med->tp != call_med->tp_orig) + //{ + // pjmedia_transport_close(call_med->tp); + // call_med->tp = call_med->tp_orig; + //} + if (call_med->tp) { + pjmedia_transport_close(call_med->tp); + call_med->tp = call_med->tp_orig = NULL; + } } check_snd_dev_idle(); @@ -1569,93 +1872,45 @@ static void dtmf_callback(pjmedia_stream *strm, void *user_data, } -pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, - const pjmedia_sdp_session *local_sdp, - const pjmedia_sdp_session *remote_sdp) +static pj_status_t audio_channel_update(pjsua_call_media *call_med, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *remote_sdp) { - int prev_media_st = 0; - pjsua_call *call = &pjsua_var.calls[call_id]; - pjmedia_session_info sess_info; - pjmedia_stream_info *si = NULL; + pjsua_call *call = call_med->call; + pjmedia_stream_info the_si, *si = &the_si; pjmedia_port *media_port; + unsigned strm_idx = call_med->idx; pj_status_t status; - - if (!pjsua_var.med_endpt) { - /* We're being shutdown */ - return PJ_EBUSY; - } - - /* Destroy existing media session, if any. */ - prev_media_st = call->media_st; - stop_media_session(call->index); - - /* Create media session info based on SDP parameters. - */ - status = pjmedia_session_info_from_sdp( call->inv->pool_prov, - pjsua_var.med_endpt, - PJMEDIA_MAX_SDP_MEDIA, &sess_info, - local_sdp, remote_sdp); + + status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, + local_sdp, remote_sdp, strm_idx); if (status != PJ_SUCCESS) return status; - /* Update audio index from the negotiated SDP */ - call->audio_idx = find_audio_index(local_sdp, PJ_TRUE); - - /* Find which session is audio */ - PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG); - PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG); - si = &sess_info.stream_info[call->audio_idx]; - - /* Reset session info with only one media stream */ - sess_info.stream_cnt = 1; - if (si != &sess_info.stream_info[0]) { - pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info)); - si = &sess_info.stream_info[0]; - } - /* Check if no media is active */ - if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE) - { + if (si->dir == PJMEDIA_DIR_NONE) { /* Call media state */ - call->media_st = PJSUA_CALL_MEDIA_NONE; + call_med->state = PJSUA_CALL_MEDIA_NONE; /* Call media direction */ - call->media_dir = PJMEDIA_DIR_NONE; - - /* Don't stop transport because we need to transmit keep-alives, and - * also to prevent restarting ICE negotiation. See - * http://trac.pjsip.org/repos/ticket/1094 - */ -#if 0 - /* Shutdown transport's session */ - pjmedia_transport_media_stop(call->med_tp); - call->med_tp_st = PJSUA_MED_TP_IDLE; - - /* No need because we need keepalive? */ - - /* Close upper entry of transport stack */ - if (call->med_orig && (call->med_tp != call->med_orig)) { - pjmedia_transport_close(call->med_tp); - call->med_tp = call->med_orig; - } -#endif + call_med->dir = PJMEDIA_DIR_NONE; } else { pjmedia_transport_info tp_info; /* Start/restart media transport */ - status = pjmedia_transport_media_start(call->med_tp, - call->inv->pool_prov, - local_sdp, remote_sdp, - call->audio_idx); + status = pjmedia_transport_media_start(call_med->tp, + tmp_pool, local_sdp, + remote_sdp, strm_idx); if (status != PJ_SUCCESS) return status; - call->med_tp_st = PJSUA_MED_TP_RUNNING; + call_med->tp_st = PJSUA_MED_TP_RUNNING; /* Get remote SRTP usage policy */ pjmedia_transport_info_init(&tp_info); - pjmedia_transport_get_info(call->med_tp, &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) { @@ -1664,7 +1919,7 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, pjmedia_srtp_info *srtp_info = (pjmedia_srtp_info*) tp_info.spc_info[i].buffer; - call->rem_srtp_use = srtp_info->peer_use; + call_med->rem_srtp_use = srtp_info->peer_use; break; } } @@ -1693,16 +1948,16 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, si->jb_max = pjsua_var.media_cfg.jb_max; /* Set SSRC */ - si->ssrc = call->ssrc; + si->ssrc = call_med->ssrc; /* Set RTP timestamp & sequence, normally these value are intialized * automatically when stream session created, but for some cases (e.g: * call reinvite, call update) timestamp and sequence need to be kept * contigue. */ - si->rtp_ts = call->rtp_tx_ts; - si->rtp_seq = call->rtp_tx_seq; - si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set; + si->rtp_ts = call_med->rtp_tx_ts; + si->rtp_seq = call_med->rtp_tx_seq; + si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set; #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 /* Enable/disable stream keep-alive and NAT hole punch. */ @@ -1710,9 +1965,15 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, #endif /* Create session based on session info. */ - status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info, - &call->med_tp, - call, &call->session ); + status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si, + call_med->tp, NULL, + &call_med->strm.a.stream); + if (status != PJ_SUCCESS) { + return status; + } + + /* Start stream */ + status = pjmedia_stream_start(call_med->strm.a.stream); if (status != PJ_SUCCESS) { return status; } @@ -1721,23 +1982,24 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, * callback to the session. */ if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { - pjmedia_session_set_dtmf_callback(call->session, 0, - &dtmf_callback, - (void*)(long)(call->index)); + pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream, + &dtmf_callback, + (void*)(long)(call->index)); } /* Get the port interface of the first stream in the session. * We need the port interface to add to the conference bridge. */ - pjmedia_session_get_port(call->session, 0, &media_port); + pjmedia_stream_get_port(call_med->strm.a.stream, &media_port); /* Notify application about stream creation. * Note: application may modify media_port to point to different * media port */ if (pjsua_var.ua_cfg.cb.on_stream_created) { - pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session, - 0, &media_port); + pjsua_var.ua_cfg.cb.on_stream_created(call->index, + call_med->strm.a.stream, + strm_idx, &media_port); } /* @@ -1758,66 +2020,132 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, call->inv->pool_prov, media_port, &port_name, - (unsigned*)&call->conf_slot); + (unsigned*) + &call_med->strm.a.conf_slot); if (status != PJ_SUCCESS) { return status; } } /* Call media direction */ - call->media_dir = si->dir; + call_med->dir = si->dir; /* Call media state */ if (call->local_hold) - call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD; - else if (call->media_dir == PJMEDIA_DIR_DECODING) - call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD; + call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD; + else if (call_med->dir == PJMEDIA_DIR_DECODING) + call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD; else - call->media_st = PJSUA_CALL_MEDIA_ACTIVE; + call_med->state = PJSUA_CALL_MEDIA_ACTIVE; } /* Print info. */ { char info[80]; int info_len = 0; - unsigned i; + int len; + const char *dir; - for (i=0; i<sess_info.stream_cnt; ++i) { - int len; - const char *dir; - pjmedia_stream_info *strm_info = &sess_info.stream_info[i]; - - switch (strm_info->dir) { - case PJMEDIA_DIR_NONE: - dir = "inactive"; - break; - case PJMEDIA_DIR_ENCODING: - dir = "sendonly"; - break; - case PJMEDIA_DIR_DECODING: - dir = "recvonly"; - break; - case PJMEDIA_DIR_ENCODING_DECODING: - dir = "sendrecv"; - break; - default: - dir = "unknown"; - break; - } - len = pj_ansi_sprintf( info+info_len, - ", stream #%d: %.*s (%s)", i, - (int)strm_info->fmt.encoding_name.slen, - strm_info->fmt.encoding_name.ptr, - dir); - if (len > 0) - info_len += len; + switch (si->dir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", strm_idx, + (int)si->fmt.encoding_name.slen, + si->fmt.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; PJ_LOG(4,(THIS_FILE,"Media updates%s", info)); } return PJ_SUCCESS; } +pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *remote_sdp) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + pj_pool_t *tmp_pool = call->inv->pool_prov; + unsigned mi; + pj_bool_t got_media = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; + + if (pjsua_get_state() != PJSUA_STATE_RUNNING) + return PJ_EBUSY; + + /* Destroy existing media session, if any. */ + stop_media_session(call->index); + + /* Reset audio_idx first */ + call->audio_idx = -1; + + /* Process each media stream */ + for (mi=0; mi < call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + + if (mi >= local_sdp->media_count || + mi >= remote_sdp->media_count) + { + /* This may happen when remote removed any SDP media lines in + * its re-offer. + */ + continue; +#if 0 + /* Something is wrong */ + PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: " + "invalid media index %d in SDP", call_id, mi)); + return PJMEDIA_SDP_EINSDP; +#endif + } + + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + status = audio_channel_update(call_med, tmp_pool, + local_sdp, remote_sdp); + if (call->audio_idx==-1 && status==PJ_SUCCESS && + call_med->strm.a.stream) + { + call->audio_idx = mi; + } + break; +#if PJMEDIA_HAS_VIDEO + case PJMEDIA_TYPE_VIDEO: + status = video_channel_update(call_med, tmp_pool, + local_sdp, remote_sdp); + break; +#endif + default: + status = PJMEDIA_EINVALIMEDIATYPE; + break; + } + + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d", + call_id, mi)); + } else { + got_media = PJ_TRUE; + } + } + + return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA); +} + /* * Get maxinum number of conference ports. */ @@ -1968,9 +2296,10 @@ PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source, port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0) { need_reopen = (peer_info.format.id != port0_info.format.id || - peer_info.format.bitrate != port0_info.format.bitrate || + peer_info.format.det.aud.avg_bps != + port0_info.format.det.aud.avg_bps || peer_info.clock_rate != port0_info.clock_rate || - peer_info.channel_count != port0_info.channel_count); + peer_info.channel_count!=port0_info.channel_count); } if (need_reopen) { @@ -1985,7 +2314,8 @@ PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source, peer_info.samples_per_frame, peer_info.bits_per_sample); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error opening sound device", status); + pjsua_perror(THIS_FILE, "Error opening sound device", + status); return status; } @@ -1998,17 +2328,28 @@ PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source, param.options = 0; status = open_snd_dev(¶m); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error opening sound device", status); + pjsua_perror(THIS_FILE, "Error opening sound device", + status); return status; } } else { /* Null-audio */ - status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev); + status = pjsua_set_snd_dev(pjsua_var.cap_dev, + pjsua_var.play_dev); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error opening sound device", status); + pjsua_perror(THIS_FILE, "Error opening sound device", + status); return status; } } + } else if (pjsua_var.no_snd) { + if (!pjsua_var.snd_is_on) { + pjsua_var.snd_is_on = PJ_TRUE; + /* Notify app */ + if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1); + } + } } } else { @@ -2025,8 +2366,13 @@ PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source, pjsua_perror(THIS_FILE, "Error opening sound device", status); return status; } + } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) { + pjsua_var.snd_is_on = PJ_TRUE; + /* Notify app */ + if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1); + } } - } return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0); @@ -2142,7 +2488,7 @@ PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename, status = pjmedia_wav_player_port_create( pool, path, pjsua_var.mconf_cfg.samples_per_frame * - 1000 / pjsua_var.media_cfg.channel_count / + 1000 / pjsua_var.media_cfg.channel_count / pjsua_var.media_cfg.clock_rate, options, 0, &port); if (status != PJ_SUCCESS) { @@ -2683,6 +3029,11 @@ static pj_status_t open_snd_dev(pjmedia_snd_port_param *param) /* Close existing sound port */ close_snd_dev(); + /* Notify app */ + if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1); + } + /* Create memory pool for sound device. */ pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000); PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM); @@ -2708,7 +3059,7 @@ static pj_status_t open_snd_dev(pjmedia_snd_port_param *param) */ if (!pjsua_var.is_mswitch && param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM && - conf_port->info.clock_rate != param->base.clock_rate) + PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate) { pjmedia_port *resample_port; unsigned resample_opt = 0; @@ -2744,14 +3095,17 @@ static pj_status_t open_snd_dev(pjmedia_snd_port_param *param) * derived from the sound device setting, so update the setting. */ if (pjsua_var.is_mswitch) { - pj_memcpy(&conf_port->info.format, ¶m->base.ext_fmt, - sizeof(conf_port->info.format)); - conf_port->info.clock_rate = param->base.clock_rate; - conf_port->info.samples_per_frame = param->base.samples_per_frame; - conf_port->info.channel_count = param->base.channel_count; - conf_port->info.bits_per_sample = 16; + pj_memcpy(&conf_port->info.fmt, ¶m->base.ext_fmt, + sizeof(conf_port->info.fmt)); + conf_port->info.fmt.det.aud.clock_rate = param->base.clock_rate; + conf_port->info.fmt.det.aud.frame_time_usec = param->base.samples_per_frame* + 1000000 / + param->base.clock_rate; + conf_port->info.fmt.det.aud.channel_count = param->base.channel_count; + conf_port->info.fmt.det.aud.bits_per_sample = 16; } + /* Connect sound port to the bridge */ status = pjmedia_snd_port_connect(pjsua_var.snd_port, conf_port ); @@ -2816,6 +3170,11 @@ static pj_status_t open_snd_dev(pjmedia_snd_port_param *param) /* Close existing sound device */ static void close_snd_dev(void) { + /* Notify app */ + if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0); + } + /* Close sound device */ if (pjsua_var.snd_port) { pjmedia_aud_dev_info cap_info, play_info; @@ -2849,6 +3208,7 @@ static void close_snd_dev(void) if (pjsua_var.snd_pool) pj_pool_release(pjsua_var.snd_pool); pjsua_var.snd_pool = NULL; + pjsua_var.snd_is_on = PJ_FALSE; } @@ -2912,6 +3272,7 @@ PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev, } pjsua_var.no_snd = PJ_FALSE; + pjsua_var.snd_is_on = PJ_TRUE; return PJ_SUCCESS; } @@ -2947,6 +3308,11 @@ PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void) /* Close existing sound device */ close_snd_dev(); + /* Notify app */ + if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1); + } + /* Create memory pool for sound device. */ pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000); PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM); @@ -2976,6 +3342,7 @@ PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void) pjsua_var.play_dev = NULL_SND_DEV_ID; pjsua_var.no_snd = PJ_FALSE; + pjsua_var.snd_is_on = PJ_TRUE; return PJ_SUCCESS; } @@ -3121,6 +3488,8 @@ PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[], if (count > *p_count) count = *p_count; for (i=0; i<count; ++i) { + pj_bzero(&id[i], sizeof(pjsua_codec_info)); + pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_)); id[i].codec_id = pj_str(id[i].buf_); id[i].priority = (pj_uint8_t) prio[i]; @@ -3174,7 +3543,7 @@ PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id, return status; if (count != 1) - return PJ_ENOTFOUND; + return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND); status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param); return status; @@ -3211,3 +3580,5 @@ PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id, status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param); return status; } + + diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c new file mode 100644 index 00000000..afb564e5 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_vid.c @@ -0,0 +1,1648 @@ +/* $Id$ */ +/* + * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + +#define THIS_FILE "pjsua_vid.c" + +#if PJSUA_HAS_VIDEO + +static void free_vid_win(pjsua_vid_win_id wid); + +/***************************************************************************** + * pjsua video subsystem. + */ +pj_status_t pjsua_vid_subsys_init(void) +{ + unsigned i; + pj_status_t status; + + status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA video format manager")); + return status; + } + + status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA converter manager")); + return status; + } + + status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA video codec manager")); + return status; + } + + status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA video subsystem")); + return status; + } + +#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC + status = pjmedia_codec_ffmpeg_init(NULL, &pjsua_var.cp.factory); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error initializing ffmpeg library")); + return status; + } +#endif + + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + if (pjsua_var.win[i].pool == NULL) { + pjsua_var.win[i].pool = pjsua_pool_create("win%p", 512, 512); + if (pjsua_var.win[i].pool == NULL) + return PJ_ENOMEM; + } + } + + return PJ_SUCCESS; +} + +pj_status_t pjsua_vid_subsys_start(void) +{ + return PJ_SUCCESS; +} + +pj_status_t pjsua_vid_subsys_destroy(void) +{ + unsigned i; + + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + if (pjsua_var.win[i].pool) { + free_vid_win(i); + pj_pool_release(pjsua_var.win[i].pool); + pjsua_var.win[i].pool = NULL; + } + } + + pjmedia_vid_dev_subsys_shutdown(); + +#if PJMEDIA_HAS_FFMPEG_CODEC + pjmedia_codec_ffmpeg_deinit(); +#endif + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Devices. + */ + +/* + * Get the number of video devices installed in the system. + */ +PJ_DEF(unsigned) pjsua_vid_dev_count(void) +{ + return pjmedia_vid_dev_count(); +} + +/* + * Retrieve the video device info for the specified device index. + */ +PJ_DEF(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id, + pjmedia_vid_dev_info *vdi) +{ + return pjmedia_vid_dev_get_info(id, vdi); +} + +/* + * Enum all video devices installed in the system. + */ +PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[], + unsigned *count) +{ + unsigned i, dev_count; + + dev_count = pjmedia_vid_dev_count(); + + if (dev_count > *count) dev_count = *count; + + for (i=0; i<dev_count; ++i) { + pj_status_t status; + + status = pjmedia_vid_dev_get_info(i, &info[i]); + if (status != PJ_SUCCESS) + return status; + } + + *count = dev_count; + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Codecs. + */ + +/* + * Enum all supported video codecs in the system. + */ +PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[], + unsigned *p_count ) +{ + pjmedia_vid_codec_info info[32]; + unsigned i, j, count, prio[32]; + pj_status_t status; + + count = PJ_ARRAY_SIZE(info); + status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio); + if (status != PJ_SUCCESS) { + *p_count = 0; + return status; + } + + for (i=0, j=0; i<count && j<*p_count; ++i) { + if (info[i].has_rtp_pack) { + pj_bzero(&id[j], sizeof(pjsua_codec_info)); + + pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_)); + id[j].codec_id = pj_str(id[j].buf_); + id[j].priority = (pj_uint8_t) prio[i]; + + if (id[j].codec_id.slen < sizeof(id[j].buf_)) { + id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1; + pj_strncpy(&id[j].desc, &info[i].encoding_desc, + sizeof(id[j].buf_) - id[j].codec_id.slen - 1); + } + + ++j; + } + } + + *p_count = j; + + return PJ_SUCCESS; +} + + +/* + * Change video codec priority. + */ +PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id, + pj_uint8_t priority ) +{ + const pj_str_t all = { NULL, 0 }; + + if (codec_id->slen==1 && *codec_id->ptr=='*') + codec_id = &all; + + return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id, + priority); +} + + +/* + * Get video codec parameters. + */ +PJ_DEF(pj_status_t) pjsua_vid_codec_get_param( + const pj_str_t *codec_id, + pjmedia_vid_codec_param *param) +{ + const pj_str_t all = { NULL, 0 }; + const pjmedia_vid_codec_info *info; + unsigned count = 1; + pj_status_t status; + + if (codec_id->slen==1 && *codec_id->ptr=='*') + codec_id = &all; + + status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id, + &count, &info, NULL); + if (status != PJ_SUCCESS) + return status; + + if (count != 1) + return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND); + + status = pjmedia_vid_codec_mgr_get_default_param(NULL, info, param); + return status; +} + + +/* + * Set video codec parameters. + */ +PJ_DEF(pj_status_t) pjsua_vid_codec_set_param( + const pj_str_t *codec_id, + const pjmedia_vid_codec_param *param) +{ + const pjmedia_vid_codec_info *info[2]; + unsigned count = 2; + pj_status_t status; + + status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id, + &count, info, NULL); + if (status != PJ_SUCCESS) + return status; + + /* Codec ID should be specific */ + if (count > 1) { + pj_assert(!"Codec ID is not specific"); + return PJ_ETOOMANY; + } + + status = pjmedia_vid_codec_mgr_set_default_param(NULL, pjsua_var.pool, + info[0], param); + return status; +} + + +/***************************************************************************** + * Preview + */ + +/* + * Get the preview window handle associated with the capture device, if any. + */ +PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id) +{ + pjsua_vid_win_id wid = PJSUA_INVALID_ID; + unsigned i; + + PJSUA_LOCK(); + + /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */ + if (id == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(id, &info); + id = info.id; + } + + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + pjsua_vid_win *w = &pjsua_var.win[i]; + if (w->type == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) { + wid = i; + break; + } + } + PJSUA_UNLOCK(); + + return wid; +} + + +/* Allocate and initialize pjsua video window: + * - If the type is preview, video capture, tee, and render + * will be instantiated. + * - If the type is stream, only renderer will be created. + */ +static pj_status_t create_vid_win(pjsua_vid_win_type type, + const pjmedia_format *fmt, + pjmedia_vid_dev_index rend_id, + pjmedia_vid_dev_index cap_id, + pj_bool_t show, + pjsua_vid_win_id *id) +{ + pjsua_vid_win_id wid = PJSUA_INVALID_ID; + pjsua_vid_win *w = NULL; + pjmedia_vid_port_param vp_param; + pjmedia_format fmt_; + pj_status_t status; + unsigned i; + + /* If type is preview, check if it exists already */ + if (type == PJSUA_WND_TYPE_PREVIEW) { + wid = pjsua_vid_preview_get_win(cap_id); + if (wid != PJSUA_INVALID_ID) { + /* Yes, it exists */ + + /* Show window if requested */ + if (show) { + pjmedia_vid_dev_stream *rdr; + pj_bool_t hide = PJ_FALSE; + + rdr = pjmedia_vid_port_get_stream(pjsua_var.win[wid].vp_rend); + pj_assert(rdr); + status = pjmedia_vid_dev_stream_set_cap( + rdr, + PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, + &hide); + } + + /* Done */ + *id = wid; + return PJ_SUCCESS; + } + } + + /* Allocate window */ + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + w = &pjsua_var.win[i]; + if (w->type == PJSUA_WND_TYPE_NONE) { + wid = i; + w->type = type; + break; + } + } + if (i == PJSUA_MAX_VID_WINS) + return PJ_ETOOMANY; + + /* Initialize window */ + pjmedia_vid_port_param_default(&vp_param); + + if (w->type == PJSUA_WND_TYPE_PREVIEW) { + status = pjmedia_vid_dev_default_param(w->pool, cap_id, + &vp_param.vidparam); + if (status != PJ_SUCCESS) + goto on_error; + + /* Normalize capture ID, in case it was set to + * PJMEDIA_VID_DEFAULT_CAPTURE_DEV + */ + cap_id = vp_param.vidparam.cap_id; + + /* Assign preview capture device ID */ + w->preview_cap_id = cap_id; + + /* Create capture video port */ + vp_param.active = PJ_TRUE; + vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE; + if (fmt) + vp_param.vidparam.fmt = *fmt; + + status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_cap); + if (status != PJ_SUCCESS) + goto on_error; + + /* Update format info */ + fmt_ = vp_param.vidparam.fmt; + fmt = &fmt_; + + /* Create video tee */ + status = pjmedia_vid_tee_create(w->pool, fmt, 2, &w->tee); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Create renderer video port */ + status = pjmedia_vid_dev_default_param(w->pool, rend_id, + &vp_param.vidparam); + if (status != PJ_SUCCESS) + goto on_error; + + vp_param.active = (w->type == PJSUA_WND_TYPE_STREAM); + vp_param.vidparam.dir = PJMEDIA_DIR_RENDER; + vp_param.vidparam.fmt = *fmt; + vp_param.vidparam.disp_size = fmt->det.vid.size; + vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE; + vp_param.vidparam.window_hide = !show; + + status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend); + if (status != PJ_SUCCESS) + goto on_error; + + /* For preview window, connect capturer & renderer (via tee) */ + if (w->type == PJSUA_WND_TYPE_PREVIEW) { + pjmedia_port *rend_port; + + status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE); + if (status != PJ_SUCCESS) + goto on_error; + + rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend); + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, rend_port); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Done */ + *id = wid; + + return PJ_SUCCESS; + +on_error: + free_vid_win(wid); + return status; +} + + +static void free_vid_win(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w = &pjsua_var.win[wid]; + + if (w->vp_cap) { + pjmedia_vid_port_stop(w->vp_cap); + pjmedia_vid_port_disconnect(w->vp_cap); + pjmedia_vid_port_destroy(w->vp_cap); + } + if (w->vp_rend) { + pjmedia_vid_port_stop(w->vp_rend); + pjmedia_vid_port_destroy(w->vp_rend); + } + if (w->tee) { + pjmedia_port_destroy(w->tee); + } + pjsua_vid_win_reset(wid); +} + + +static void inc_vid_win(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w; + + pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS); + + w = &pjsua_var.win[wid]; + pj_assert(w->type != PJSUA_WND_TYPE_NONE); + ++w->ref_cnt; +} + +static void dec_vid_win(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w; + + pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS); + + w = &pjsua_var.win[wid]; + pj_assert(w->type != PJSUA_WND_TYPE_NONE); + if (--w->ref_cnt == 0) + free_vid_win(wid); +} + + +/* Internal function: update video channel after SDP negotiation */ +pj_status_t video_channel_update(pjsua_call_media *call_med, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *remote_sdp) +{ + pjsua_call *call = call_med->call; + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pjmedia_vid_stream_info the_si, *si = &the_si; + pjmedia_port *media_port; + unsigned strm_idx = call_med->idx; + pj_status_t status; + + status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, + local_sdp, remote_sdp, strm_idx); + if (status != PJ_SUCCESS) + return status; + + /* Check if no media is active */ + if (si->dir == PJMEDIA_DIR_NONE) { + /* Call media state */ + call_med->state = PJSUA_CALL_MEDIA_NONE; + + /* Call media direction */ + call_med->dir = PJMEDIA_DIR_NONE; + + } else { + pjmedia_transport_info tp_info; + + /* Start/restart media transport */ + status = pjmedia_transport_media_start(call_med->tp, + tmp_pool, local_sdp, + remote_sdp, strm_idx); + if (status != PJ_SUCCESS) + return status; + + call_med->tp_st = PJSUA_MED_TP_RUNNING; + + /* 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; + } + } + } + + /* Optionally, application may modify other stream settings here + * (such as jitter buffer parameters, codec ptime, etc.) + */ + si->jb_init = pjsua_var.media_cfg.jb_init; + si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre; + si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre; + si->jb_max = pjsua_var.media_cfg.jb_max; + + /* Set SSRC */ + si->ssrc = call_med->ssrc; + + /* Set RTP timestamp & sequence, normally these value are intialized + * automatically when stream session created, but for some cases (e.g: + * call reinvite, call update) timestamp and sequence need to be kept + * contigue. + */ + si->rtp_ts = call_med->rtp_tx_ts; + si->rtp_seq = call_med->rtp_tx_seq; + si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set; + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + /* Enable/disable stream keep-alive and NAT hole punch. */ + si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka; +#endif + + /* Try to get shared format ID between the capture device and + * the encoder to avoid format conversion in the capture device. + */ + if (si->dir & PJMEDIA_DIR_ENCODING) { + pjmedia_vid_dev_info dev_info; + pjmedia_vid_codec_info *codec_info = &si->codec_info; + unsigned i, j; + + status = pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, + &dev_info); + if (status != PJ_SUCCESS) + return status; + + /* Find matched format ID */ + for (i = 0; i < codec_info->dec_fmt_id_cnt; ++i) { + for (j = 0; j < dev_info.fmt_cnt; ++j) { + if (codec_info->dec_fmt_id[i] == + (pjmedia_format_id)dev_info.fmt[j].id) + { + /* Apply the matched format ID to the codec */ + si->codec_param->dec_fmt.id = + codec_info->dec_fmt_id[i]; + + /* Force outer loop to break */ + i = codec_info->dec_fmt_id_cnt; + break; + } + } + } + } + + /* Create session based on session info. */ + status = pjmedia_vid_stream_create(pjsua_var.med_endpt, NULL, si, + call_med->tp, NULL, + &call_med->strm.v.stream); + if (status != PJ_SUCCESS) + return status; + + /* Start stream */ + status = pjmedia_vid_stream_start(call_med->strm.v.stream); + if (status != PJ_SUCCESS) + return status; + + /* Setup decoding direction */ + if (si->dir & PJMEDIA_DIR_DECODING) + { + pjsua_vid_win_id wid; + pjsua_vid_win *w; + + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_DECODING, + &media_port); + if (status != PJ_SUCCESS) + return status; + + /* Create stream video window */ + status = create_vid_win(PJSUA_WND_TYPE_STREAM, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + //acc->cfg.vid_rend_dev, + PJSUA_INVALID_ID, + acc->cfg.vid_in_auto_show, + &wid); + if (status != PJ_SUCCESS) + return status; + + w = &pjsua_var.win[wid]; + + /* Register to video events */ + pjmedia_event_subscribe( + pjmedia_vid_port_get_event_publisher(w->vp_rend), + &call_med->esub_rend); + + /* Connect renderer to stream */ + status = pjmedia_vid_port_connect(w->vp_rend, media_port, + PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + + /* Start renderer */ + status = pjmedia_vid_port_start(w->vp_rend); + if (status != PJ_SUCCESS) + return status; + + /* Done */ + inc_vid_win(wid); + call_med->strm.v.rdr_win_id = wid; + } + + /* Setup encoding direction */ + if (si->dir & PJMEDIA_DIR_ENCODING && !call->local_hold) + { + pjsua_vid_win *w; + pjsua_vid_win_id wid; + + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, + &media_port); + if (status != PJ_SUCCESS) + return status; + + /* Create preview video window */ + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + call_med->strm.v.cap_dev, + //acc->cfg.vid_rend_dev, + //acc->cfg.vid_cap_dev, + PJ_FALSE, + &wid); + if (status != PJ_SUCCESS) + return status; + + w = &pjsua_var.win[wid]; + + pjmedia_event_subscribe( + pjmedia_vid_port_get_event_publisher(w->vp_cap), + &call_med->esub_cap); + + /* Connect stream to capturer (via video window tee) */ + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port); + if (status != PJ_SUCCESS) + return status; + + /* Start renderer */ + status = pjmedia_vid_port_start(w->vp_rend); + if (status != PJ_SUCCESS) + return status; + + /* Start capturer */ + status = pjmedia_vid_port_start(w->vp_cap); + if (status != PJ_SUCCESS) + return status; + + /* Done */ + inc_vid_win(wid); + call_med->strm.v.cap_win_id = wid; + } + + /* Call media direction */ + call_med->dir = si->dir; + + /* Call media state */ + if (call->local_hold) + call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD; + else if (call_med->dir == PJMEDIA_DIR_DECODING) + call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD; + else + call_med->state = PJSUA_CALL_MEDIA_ACTIVE; + } + + /* Print info. */ + { + char info[80]; + int info_len = 0; + int len; + const char *dir; + + switch (si->dir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; + } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", strm_idx, + (int)si->codec_info.encoding_name.slen, + si->codec_info.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; + PJ_LOG(4,(THIS_FILE,"Media updates%s", info)); + } + + if (!acc->cfg.vid_out_auto_transmit && call_med->strm.v.stream) { + status = pjmedia_vid_stream_pause(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + if (status != PJ_SUCCESS) + return status; + } + + return PJ_SUCCESS; +} + + +/* Internal function to stop video stream */ +void stop_video_stream(pjsua_call_media *call_med) +{ + pjmedia_vid_stream *strm = call_med->strm.v.stream; + pjmedia_rtcp_stat stat; + + pj_assert(call_med->type == PJMEDIA_TYPE_VIDEO); + + if (!strm) + return; + + /* Unsubscribe events */ + pjmedia_event_unsubscribe(&call_med->esub_rend); + pjmedia_event_unsubscribe(&call_med->esub_cap); + + if (call_med->dir & PJMEDIA_DIR_ENCODING && + call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) + { + pjmedia_port *media_port; + pjsua_vid_win *w = + &pjsua_var.win[call_med->strm.v.cap_win_id]; + + pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, + &media_port); + pj_assert(media_port); + + pjmedia_vid_port_stop(w->vp_cap); + pjmedia_vid_tee_remove_dst_port(w->tee, media_port); + pjmedia_vid_port_start(w->vp_cap); + + dec_vid_win(call_med->strm.v.cap_win_id); + } + + if (call_med->dir & PJMEDIA_DIR_DECODING && + call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) + { + dec_vid_win(call_med->strm.v.rdr_win_id); + } + + if ((call_med->dir & PJMEDIA_DIR_ENCODING) && + (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS)) + { + /* Save RTP timestamp & sequence, so when media session is + * restarted, those values will be restored as the initial + * RTP timestamp & sequence of the new media session. So in + * the same call session, RTP timestamp and sequence are + * guaranteed to be contigue. + */ + call_med->rtp_tx_seq_ts_set = 1 | (1 << 1); + call_med->rtp_tx_seq = stat.rtp_tx_last_seq; + call_med->rtp_tx_ts = stat.rtp_tx_last_ts; + } + + pjmedia_vid_stream_destroy(strm); + call_med->strm.v.stream = NULL; +} + + +/* + * Start video preview window for the specified capture device. + */ +PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id, + pjsua_vid_preview_param *prm) +{ + pjsua_vid_win_id wid; + pjsua_vid_win *w; + pjmedia_vid_dev_index rend_id; + pj_status_t status; + + PJSUA_LOCK(); + + if (prm) { + rend_id = prm->rend_id; + } else { + rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV; + } + + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, NULL, rend_id, id, + PJ_TRUE, &wid); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + w = &pjsua_var.win[wid]; + + /* Start capturer */ + status = pjmedia_vid_port_start(w->vp_rend); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + /* Start renderer */ + status = pjmedia_vid_port_start(w->vp_cap); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + inc_vid_win(wid); + + PJSUA_UNLOCK(); + return PJ_SUCCESS; +} + +/* + * Stop video preview. + */ +PJ_DEF(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id) +{ + pjsua_vid_win_id wid = PJSUA_INVALID_ID; + + PJSUA_LOCK(); + wid = pjsua_vid_preview_get_win(id); + if (wid == PJSUA_INVALID_ID) { + PJSUA_UNLOCK(); + return PJ_ENOTFOUND; + } + + dec_vid_win(wid); + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Window + */ + + +/* + * Enumerates all video windows. + */ +PJ_DEF(pj_status_t) pjsua_vid_enum_wins( pjsua_vid_win_id wids[], + unsigned *count) +{ + unsigned i, cnt; + + cnt = 0; + + for (i=0; i<PJSUA_MAX_VID_WINS && cnt <*count; ++i) { + pjsua_vid_win *w = &pjsua_var.win[i]; + if (w->type != PJSUA_WND_TYPE_NONE) + wids[cnt++] = i; + } + + *count = cnt; + + return PJ_SUCCESS; +} + + +/* + * Get window info. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_get_info( pjsua_vid_win_id wid, + pjsua_vid_win_info *wi) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pjmedia_vid_dev_param vparam; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && wi, PJ_EINVAL); + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + if (w->vp_rend == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_get_param(s, &vparam); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + wi->show = !vparam.window_hide; + wi->pos = vparam.window_pos; + wi->size = vparam.disp_size; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + +/* + * Show or hide window. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_set_show( pjsua_vid_win_id wid, + pj_bool_t show) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pj_bool_t hide; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL); + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + if (w->vp_rend == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + hide = !show; + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, &hide); + + PJSUA_UNLOCK(); + + return status; +} + +/* + * Set video window position. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_set_pos( pjsua_vid_win_id wid, + const pjmedia_coord *pos) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && pos, PJ_EINVAL); + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + if (w->vp_rend == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, pos); + + PJSUA_UNLOCK(); + + return status; +} + +/* + * Resize window. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_set_size( pjsua_vid_win_id wid, + const pjmedia_rect_size *size) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && size, PJ_EINVAL); + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + if (w->vp_rend == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE, size); + + PJSUA_UNLOCK(); + + return status; +} + + +static void call_get_vid_strm_info(pjsua_call *call, + int *first_active, + int *first_inactive, + unsigned *active_cnt, + unsigned *cnt) +{ + unsigned i, var_cnt = 0; + + if (first_active && ++var_cnt) + *first_active = -1; + if (first_inactive && ++var_cnt) + *first_inactive = -1; + if (active_cnt && ++var_cnt) + *active_cnt = 0; + if (cnt && ++var_cnt) + *cnt = 0; + + for (i = 0; i < call->med_cnt && var_cnt; ++i) { + if (call->media[i].type == PJMEDIA_TYPE_VIDEO) { + if (call->media[i].dir != PJMEDIA_DIR_NONE) + { + if (first_active && *first_active == -1) { + *first_active = i; + --var_cnt; + } + if (active_cnt) + ++(*active_cnt); + } else if (first_inactive && *first_inactive == -1) { + *first_inactive = i; + --var_cnt; + } + if (cnt) + ++(*cnt); + } + } +} + + +/* Send SDP reoffer. */ +static pj_status_t call_reoffer_sdp(pjsua_call_id call_id, + const pjmedia_sdp_session *sdp) +{ + pjsua_call *call; + pjsip_tx_data *tdata; + pjsip_dialog *dlg; + pj_status_t status; + + status = acquire_call("call_reoffer_sdp()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + return status; + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); + pjsip_dlg_dec_lock(dlg); + return PJSIP_ESESSIONSTATE; + } + + /* Create re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + pjsip_dlg_dec_lock(dlg); + return status; + } + + /* Send the request */ + status = pjsip_inv_send_msg( call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + pjsip_dlg_dec_lock(dlg); + return status; + } + + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + +/* Add a new video stream into a call */ +static pj_status_t call_add_video(pjsua_call *call, + pjmedia_vid_dev_index cap_dev, + pjmedia_dir dir) +{ + pj_pool_t *pool = call->inv->pool_prov; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + pjsua_call_media *call_med; + pjmedia_sdp_session *sdp; + pjmedia_sdp_media *sdp_m; + pjmedia_transport_info tpinfo; + unsigned active_cnt; + pj_status_t status; + + /* Verify media slot availability */ + if (call->med_cnt == PJSUA_MAX_CALL_MEDIA) + return PJ_ETOOMANY; + + call_get_vid_strm_info(call, NULL, NULL, &active_cnt, NULL); + if (active_cnt == acc_cfg->max_video_cnt) + return PJ_ETOOMANY; + + /* Get active local SDP */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp); + if (status != PJ_SUCCESS) + return status; + + /* Initialize call media */ + call_med = &call->media[call->med_cnt++]; + + status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, + &acc_cfg->rtp_cfg, call->secure_level, + NULL); + if (status != PJ_SUCCESS) + goto on_error; + + /* Override default capture device setting */ + call_med->strm.v.cap_dev = cap_dev; + + /* Init transport media */ + status = pjmedia_transport_media_create(call_med->tp, pool, 0, + NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + + call_med->tp_st = PJSUA_MED_TP_INIT; + + /* Get transport address info */ + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + + /* Create SDP media line */ + status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &sdp_m); + if (status != PJ_SUCCESS) + goto on_error; + + sdp->media[sdp->media_count++] = sdp_m; + + /* Update media direction, if it is not 'sendrecv' */ + if (dir != PJMEDIA_DIR_ENCODING_DECODING) { + pjmedia_sdp_attr *a; + + /* Remove sendrecv direction attribute, if any */ + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv"); + + if (dir == PJMEDIA_DIR_ENCODING) + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + else if (dir == PJMEDIA_DIR_DECODING) + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + else + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + + pjmedia_sdp_media_add_attr(sdp_m, a); + } + + /* 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; + + status = call_reoffer_sdp(call->index, sdp); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + if (call_med->tp) { + pjmedia_transport_close(call_med->tp); + call_med->tp = call_med->tp_orig = NULL; + } + + return status; +} + + +/* Modify a video stream from a call, i.e: update direction, + * remove/disable. + */ +static pj_status_t call_modify_video(pjsua_call *call, + int med_idx, + pjmedia_dir dir, + pj_bool_t remove) +{ + pjsua_call_media *call_med; + pjmedia_sdp_session *sdp; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify if the stream media type is video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO) + return PJ_EINVAL; + + /* Verify if the stream dir is not changed */ + if ((!remove && call_med->dir == dir) || + ( remove && (call_med->tp_st == PJSUA_MED_TP_DISABLED || + call_med->tp == NULL))) + { + return PJ_SUCCESS; + } + + /* Get active local SDP */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp); + if (status != PJ_SUCCESS) + return status; + + pj_assert(med_idx < (int)sdp->media_count); + + if (!remove) { + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + pj_pool_t *pool = call->inv->pool_prov; + pjmedia_sdp_media *sdp_m; + + status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, + &acc_cfg->rtp_cfg, call->secure_level, + NULL); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init transport media */ + if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) { + status = pjmedia_transport_media_create(call_med->tp, pool, 0, + NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + } + + sdp_m = sdp->media[med_idx]; + + /* Create new SDP media line if the stream is disabled */ + if (sdp->media[med_idx]->desc.port == 0) { + pjmedia_transport_info tpinfo; + + /* Get transport address info */ + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + + status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &sdp_m); + if (status != PJ_SUCCESS) + goto on_error; + } + + { + pjmedia_sdp_attr *a; + + /* Remove any direction attributes */ + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "recvonly"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "inactive"); + + /* Update media direction */ + if (dir == PJMEDIA_DIR_ENCODING_DECODING) + a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL); + else if (dir == PJMEDIA_DIR_ENCODING) + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + else if (dir == PJMEDIA_DIR_DECODING) + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + else + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + + pjmedia_sdp_media_add_attr(sdp_m, a); + } + + 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; + +on_error: + if (status != PJ_SUCCESS) { + return status; + } + + } else { + + pj_pool_t *pool = call->inv->pool_prov; + + /* Mark media transport to disabled */ + // Don't close this here, as SDP negotiation has not been + // done and stream may be still active. + call_med->tp_st = PJSUA_MED_TP_DISABLED; + + /* Deactivate the stream */ + pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]); + + } + + status = call_reoffer_sdp(call->index, sdp); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + + +/* Change capture device of a video stream in a call */ +static pj_status_t call_change_cap_dev(pjsua_call *call, + int med_idx, + pjmedia_vid_dev_index cap_dev) +{ + pjsua_call_media *call_med; + pjmedia_vid_dev_info info; + pjsua_vid_win *w, *new_w = NULL; + pjsua_vid_win_id wid, new_wid; + pjmedia_port *media_port; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify if the stream media type is video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO) + return PJ_EINVAL; + + /* Verify the capture device */ + status = pjmedia_vid_dev_get_info(cap_dev, &info); + if (status != PJ_SUCCESS || info.dir != PJMEDIA_DIR_CAPTURE) + return PJ_EINVAL; + + /* The specified capture device is being used already */ + if (call_med->strm.v.cap_dev == cap_dev) + return PJ_SUCCESS; + + /* == Apply the new capture device == */ + + wid = call_med->strm.v.cap_win_id; + w = &pjsua_var.win[wid]; + pj_assert(w->type == PJSUA_WND_TYPE_PREVIEW && w->vp_cap); + + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, &media_port); + if (status != PJ_SUCCESS) + return status; + + pjmedia_event_unsubscribe(&call_med->esub_cap); + + /* = Detach stream port from the old capture device = */ + status = pjmedia_vid_port_disconnect(w->vp_cap); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_vid_tee_remove_dst_port(w->tee, media_port); + if (status != PJ_SUCCESS) { + /* Connect back the old capturer */ + pjmedia_vid_port_connect(w->vp_cap, media_port, PJ_FALSE); + return status; + } + + /* = Attach stream port to the new capture device = */ + + /* Create preview video window */ + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + cap_dev, + PJ_FALSE, + &new_wid); + if (status != PJ_SUCCESS) + goto on_error; + + inc_vid_win(new_wid); + new_w = &pjsua_var.win[new_wid]; + + /* Connect stream to capturer (via video window tee) */ + status = pjmedia_vid_tee_add_dst_port2(new_w->tee, 0, media_port); + if (status != PJ_SUCCESS) + goto on_error; + + /* Connect capturer to tee */ + status = pjmedia_vid_port_connect(new_w->vp_cap, new_w->tee, PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + + pjmedia_event_subscribe( + pjmedia_vid_port_get_event_publisher(w->vp_rend), + &call_med->esub_cap); + + /* Start renderer */ + status = pjmedia_vid_port_start(new_w->vp_rend); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start capturer */ + status = pjmedia_vid_port_start(new_w->vp_cap); + if (status != PJ_SUCCESS) + goto on_error; + + /* Finally */ + call_med->strm.v.cap_dev = cap_dev; + call_med->strm.v.cap_win_id = new_wid; + dec_vid_win(wid); + + return PJ_SUCCESS; + +on_error: + if (new_w) { + /* Disconnect media port from the new capturer */ + pjmedia_vid_tee_remove_dst_port(new_w->tee, media_port); + /* Release the new capturer */ + dec_vid_win(new_wid); + } + + /* Revert back to the old capturer */ + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + + return status; +} + + +/* Start/stop transmitting video stream in a call */ +static pj_status_t call_set_tx_video(pjsua_call *call, + int med_idx, + pj_bool_t enable) +{ + pjsua_call_media *call_med; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify if the stream is transmitting video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO || + (enable && (call_med->dir & PJMEDIA_DIR_ENCODING) == 0)) + { + return PJ_EINVAL; + } + + if (enable) { + /* Start stream in encoding direction */ + status = pjmedia_vid_stream_resume(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + } else { + /* Pause stream in encoding direction */ + status = pjmedia_vid_stream_pause( call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + } + + return status; +} + + +/* + * Start, stop, and/or manipulate video transmission for the specified call. + */ +PJ_DEF(pj_status_t) pjsua_call_set_vid_strm ( + pjsua_call_id call_id, + pjsua_call_vid_strm_op op, + const pjsua_call_vid_strm_op_param *param) +{ + pjsua_call *call; + pjsua_call_vid_strm_op_param param_; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (param) { + param_ = *param; + } else { + param_.med_idx = -1; + param_.cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; + param_.dir = PJMEDIA_DIR_ENCODING_DECODING; + } + + /* If set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with + * account default video capture device. + */ + if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + param_.cap_dev = acc_cfg->vid_cap_dev; + + /* If the account default video capture device is + * PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with + * global default video capture device. + */ + if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(param_.cap_dev, &info); + pj_assert(info.dir == PJMEDIA_DIR_CAPTURE); + param_.cap_dev = info.id; + } + } + + switch (op) { + case PJSUA_CALL_VID_STRM_ADD: + status = call_add_video(call, param_.cap_dev, param_.dir); + break; + case PJSUA_CALL_VID_STRM_REMOVE: + status = call_modify_video(call, param_.med_idx, PJMEDIA_DIR_NONE, + PJ_TRUE); + break; + case PJSUA_CALL_VID_STRM_CHANGE_DIR: + status = call_modify_video(call, param_.med_idx, param_.dir, PJ_FALSE); + break; + case PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV: + status = call_change_cap_dev(call, param_.med_idx, param_.cap_dev); + break; + case PJSUA_CALL_VID_STRM_START_TRANSMIT: + status = call_set_tx_video(call, param_.med_idx, PJ_TRUE); + break; + case PJSUA_CALL_VID_STRM_STOP_TRANSMIT: + status = call_set_tx_video(call, param_.med_idx, PJ_FALSE); + break; + default: + status = PJ_EINVALIDOP; + break; + } + + PJSUA_UNLOCK(); + + return status; +} + + +/* + * Get the media stream index of the default video stream in the call. + */ +PJ_DEF(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id) +{ + pjsua_call *call; + int first_active, first_inactive; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + call = &pjsua_var.calls[call_id]; + call_get_vid_strm_info(call, &first_active, &first_inactive, NULL, NULL); + PJSUA_UNLOCK(); + + if (first_active == -1) + return first_inactive; + + return first_active; +} + + +#endif /* PJSUA_HAS_VIDEO */ + |