Beispiel #1
0
static int print_cand_to_xml(char buffer[], unsigned maxlen,
                      const pj_ice_sess_cand *cand)
{
    char ipaddr[PJ_INET6_ADDRSTRLEN];
    char *p = buffer;
    int printed;

    PRINT("<candidate><foundation>%.*s</foundation> <comp_id>%u</comp_id> <transport>UDP</transport> <prio>%u</prio> <ip>%s</ip> <port>%u</port>",
          (int)cand->foundation.slen,
          cand->foundation.ptr,
          (unsigned)cand->comp_id,
          cand->prio,
          pj_sockaddr_print(&cand->addr, ipaddr,
                            sizeof(ipaddr), 0),
          (unsigned)pj_sockaddr_get_port(&cand->addr));

    PRINT("<type>%s</type> </candidate> \n",
          pj_ice_get_cand_type_name(cand->type));

    if (p == buffer+maxlen)
        return -PJ_ETOOSMALL;

    *p = '\0';

    return (int)(p-buffer);
}
Beispiel #2
0
/* Utility to create a=candidate SDP attribute */
static int print_cand(char buffer[], unsigned maxlen,
		      const pj_ice_sess_cand *cand)
{
    char ipaddr[PJ_INET6_ADDRSTRLEN];
    char *p = buffer;
    int printed;

    PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
	  (int)cand->foundation.slen,
	  cand->foundation.ptr,
	  (unsigned)cand->comp_id,
	  cand->prio,
	  pj_sockaddr_print(&cand->addr, ipaddr, 
			    sizeof(ipaddr), 0),
	  (unsigned)pj_sockaddr_get_port(&cand->addr));

    PRINT("%s\n",
	  pj_ice_get_cand_type_name(cand->type),
	  0, 0, 0, 0, 0);

    if (p == buffer+maxlen)
	return -PJ_ETOOSMALL;

    *p = '\0';

    return p-buffer;
}
Beispiel #3
0
/* Create SDP candidate attribute */
static int print_sdp_cand_attr(char *buffer, int max_len,
			       const pj_ice_sess_cand *cand)
{
    char ipaddr[PJ_INET6_ADDRSTRLEN+2];
    int len, len2;

    len = pj_ansi_snprintf( buffer, max_len,
			    "%.*s %u UDP %u %s %u typ ",
			    (int)cand->foundation.slen,
			    cand->foundation.ptr,
			    (unsigned)cand->comp_id,
			    cand->prio,
			    pj_sockaddr_print(&cand->addr, ipaddr, 
					      sizeof(ipaddr), 0),
			    (unsigned)pj_sockaddr_get_port(&cand->addr));
    if (len < 1 || len >= max_len)
	return -1;

    switch (cand->type) {
    case PJ_ICE_CAND_TYPE_HOST:
	len2 = pj_ansi_snprintf(buffer+len, max_len-len, "host");
	break;
    case PJ_ICE_CAND_TYPE_SRFLX:
    case PJ_ICE_CAND_TYPE_RELAYED:
    case PJ_ICE_CAND_TYPE_PRFLX:
	len2 = pj_ansi_snprintf(buffer+len, max_len-len,
			        "%s raddr %s rport %d",
				pj_ice_get_cand_type_name(cand->type),
				pj_sockaddr_print(&cand->rel_addr, ipaddr,
						  sizeof(ipaddr), 0),
				(int)pj_sockaddr_get_port(&cand->rel_addr));
	break;
    default:
	pj_assert(!"Invalid candidate type");
	len2 = -1;
	break;
    }
    if (len2 < 1 || len2 >= max_len)
	return -1;

    return len+len2;
}
Beispiel #4
0
static int krx_ice_candidate_to_string(char* out, int nbytes, pj_ice_sess_cand* cand) {

  if(!out) { return -1; }
  if(!nbytes) { return 2; } 
  if(!cand) { return -3; } 

  char ipaddr[PJ_INET6_ADDRSTRLEN];
  #if 0
  /* @todo(roxlu): make sure we don't overflow the out buffer in krx_ice_candidate_to_string() */
  sprintf(out,
          "a=candidate:%.*s %u UDP %u %s %u type %s\n",
          (int)cand->foundation.slen, cand->foundation.ptr,            /* foundation */
          (unsigned)cand->comp_id,                                     /* component id */
          cand->prio,                                                  /* priority */
        "address",
        //        pj_sockaddr_print(&cand->addr, ipaddr, sizeof(ipaddr), 0),   /* socket address */
        // (unsigned)pj_sockaddr_get_port(&cand->addr),
          pj_ice_get_cand_type_name(cand->type)
  );
  #endif
          
  return nbytes;
}
Beispiel #5
0
/* 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.rx_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);

#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
	} 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);
	    }
#endif /* PJMEDIA_HAS_VIDEO */

	} 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;
			unsigned jj;

			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';
			}

			for (jj=0; ii->sess_state==PJ_ICE_STRANS_STATE_RUNNING && jj<2; ++jj) {
			    const char *type1 = pj_ice_get_cand_type_name(ii->comp[jj].lcand_type);
			    const char *type2 = pj_ice_get_cand_type_name(ii->comp[jj].rcand_type);
			    char addr1[PJ_INET6_ADDRSTRLEN+10];
			    char addr2[PJ_INET6_ADDRSTRLEN+10];

			    if (pj_sockaddr_has_addr(&ii->comp[jj].lcand_addr))
				pj_sockaddr_print(&ii->comp[jj].lcand_addr, addr1, sizeof(addr1), 3);
			    else
				strcpy(addr1, "0.0.0.0:0");
			    if (pj_sockaddr_has_addr(&ii->comp[jj].rcand_addr))
				pj_sockaddr_print(&ii->comp[jj].rcand_addr, addr2, sizeof(addr2), 3);
			    else
				strcpy(addr2, "0.0.0.0:0");
			    len = pj_ansi_snprintf(p, end-p,
			                           "   %s     [%d]: L:%s (%c) --> R:%s (%c)\n",
			                           indent, jj,
			                           addr1, type1[0],
			                           addr2, type2[0]);
			    if (len > 0 && len < end-p) {
				p += len;
				*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

    }