1 /* $Id$ */
2 /*
3  * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com)
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 #include <pjsua-lib/pjsua.h>
20 #include <pjsua-lib/pjsua_internal.h>
21 
good_number(char * buf,pj_int32_t val)22 const char *good_number(char *buf, pj_int32_t val)
23 {
24     if (val < 1000) {
25 	pj_ansi_sprintf(buf, "%d", val);
26     } else if (val < 1000000) {
27 	pj_ansi_sprintf(buf, "%d.%dK",
28 			val / 1000,
29 			(val % 1000) / 100);
30     } else {
31 	pj_ansi_sprintf(buf, "%d.%02dM",
32 			val / 1000000,
33 			(val % 1000000) / 10000);
34     }
35 
36     return buf;
37 }
38 
dump_media_stat(const char * indent,char * buf,unsigned maxlen,const pjmedia_rtcp_stat * stat,const char * rx_info,const char * tx_info)39 static unsigned dump_media_stat(const char *indent,
40 				char *buf, unsigned maxlen,
41 				const pjmedia_rtcp_stat *stat,
42 				const char *rx_info, const char *tx_info)
43 {
44     char last_update[64];
45     char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32];
46     pj_time_val media_duration, now;
47     char *p = buf, *end = buf+maxlen;
48     int len;
49 
50     if (stat->rx.update_cnt == 0)
51 	strcpy(last_update, "never");
52     else {
53 	pj_gettimeofday(&now);
54 	PJ_TIME_VAL_SUB(now, stat->rx.update);
55 	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
56 		now.sec / 3600,
57 		(now.sec % 3600) / 60,
58 		now.sec % 60,
59 		now.msec);
60     }
61 
62     pj_gettimeofday(&media_duration);
63     PJ_TIME_VAL_SUB(media_duration, stat->start);
64     if (PJ_TIME_VAL_MSEC(media_duration) == 0)
65 	media_duration.msec = 1;
66 
67     len = pj_ansi_snprintf(p, end-p,
68 	   "%s     RX %s last update:%s\n"
69 	   "%s        total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
70 	   "%s        pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n"
71 	   "%s              (msec)    min     avg     max     last    dev\n"
72 	   "%s        loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
73 	   "%s        jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
74 #if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
75 	   "%s        raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
76 #endif
77 #if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
78 	   "%s        IPDV       : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
79 #endif
80 	   "%s",
81 	   indent,
82 	   rx_info? rx_info : "",
83 	   last_update,
84 
85 	   indent,
86 	   good_number(packets, stat->rx.pkt),
87 	   good_number(bytes, stat->rx.bytes),
88 	   good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40),
89 	   good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
90 	   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))),
91 	   indent,
92 	   stat->rx.loss,
93 	   (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
94 	   stat->rx.discard,
95 	   (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
96 	   stat->rx.dup,
97 	   (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
98 	   stat->rx.reorder,
99 	   (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
100 	   indent, indent,
101 	   stat->rx.loss_period.min / 1000.0,
102 	   stat->rx.loss_period.mean / 1000.0,
103 	   stat->rx.loss_period.max / 1000.0,
104 	   stat->rx.loss_period.last / 1000.0,
105 	   pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0,
106 	   indent,
107 	   stat->rx.jitter.min / 1000.0,
108 	   stat->rx.jitter.mean / 1000.0,
109 	   stat->rx.jitter.max / 1000.0,
110 	   stat->rx.jitter.last / 1000.0,
111 	   pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0,
112 #if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
113 	   indent,
114 	   stat->rx_raw_jitter.min / 1000.0,
115 	   stat->rx_raw_jitter.mean / 1000.0,
116 	   stat->rx_raw_jitter.max / 1000.0,
117 	   stat->rx_raw_jitter.last / 1000.0,
118 	   pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0,
119 #endif
120 #if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
121 	   indent,
122 	   stat->rx_ipdv.min / 1000.0,
123 	   stat->rx_ipdv.mean / 1000.0,
124 	   stat->rx_ipdv.max / 1000.0,
125 	   stat->rx_ipdv.last / 1000.0,
126 	   pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0,
127 #endif
128 	   ""
129 	   );
130 
131     if (len < 1 || len >= end-p) {
132 	*p = '\0';
133 	return (unsigned)(p-buf);
134     }
135     p += len;
136 
137     if (stat->tx.update_cnt == 0)
138 	strcpy(last_update, "never");
139     else {
140 	pj_gettimeofday(&now);
141 	PJ_TIME_VAL_SUB(now, stat->tx.update);
142 	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
143 		now.sec / 3600,
144 		(now.sec % 3600) / 60,
145 		now.sec % 60,
146 		now.msec);
147     }
148 
149     len = pj_ansi_snprintf(p, end-p,
150 	   "%s     TX %s last update:%s\n"
151 	   "%s        total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
152 	   "%s        pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
153 	   "%s              (msec)    min     avg     max     last    dev \n"
154 	   "%s        loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
155 	   "%s        jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
156 	   indent,
157 	   tx_info,
158 	   last_update,
159 
160 	   indent,
161 	   good_number(packets, stat->tx.pkt),
162 	   good_number(bytes, stat->tx.bytes),
163 	   good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40),
164 	   good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
165 	   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))),
166 
167 	   indent,
168 	   stat->tx.loss,
169 	   (stat->tx.loss? stat->tx.loss * 100.0 / stat->tx.pkt : 0),
170 	   stat->tx.dup,
171 	   (stat->tx.dup? stat->tx.dup * 100.0 / stat->tx.pkt : 0),
172 	   stat->tx.reorder,
173 	   (stat->tx.reorder? stat->tx.reorder * 100.0 / stat->tx.pkt : 0),
174 
175 	   indent, indent,
176 	   stat->tx.loss_period.min / 1000.0,
177 	   stat->tx.loss_period.mean / 1000.0,
178 	   stat->tx.loss_period.max / 1000.0,
179 	   stat->tx.loss_period.last / 1000.0,
180 	   pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0,
181 	   indent,
182 	   stat->tx.jitter.min / 1000.0,
183 	   stat->tx.jitter.mean / 1000.0,
184 	   stat->tx.jitter.max / 1000.0,
185 	   stat->tx.jitter.last / 1000.0,
186 	   pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0
187 	   );
188 
189     if (len < 1 || len >= end-p) {
190 	*p = '\0';
191 	return (unsigned)(p-buf);
192     }
193     p += len;
194 
195     len = pj_ansi_snprintf(p, end-p,
196 	   "%s     RTT msec      : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
197 	   indent,
198 	   stat->rtt.min / 1000.0,
199 	   stat->rtt.mean / 1000.0,
200 	   stat->rtt.max / 1000.0,
201 	   stat->rtt.last / 1000.0,
202 	   pj_math_stat_get_stddev(&stat->rtt) / 1000.0
203 	   );
204     if (len < 1 || len >= end-p) {
205 	*p = '\0';
206 	return (unsigned)(p-buf);
207     }
208     p += len;
209 
210     return (unsigned)(p-buf);
211 }
212 
213 
214 /* Dump media session */
215 #if PJSUA_MEDIA_HAS_PJMEDIA || \
216     (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO && \
217      PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT)
218 
dump_media_session(const char * indent,char * buf,unsigned maxlen,pjsua_call * call)219 static void dump_media_session(const char *indent,
220 			       char *buf, unsigned maxlen,
221 			       pjsua_call *call)
222 {
223     unsigned i;
224     char *p = buf, *end = buf+maxlen;
225     int len;
226 
227     for (i=0; i<call->med_cnt; ++i) {
228 	pjsua_call_media *call_med = &call->media[i];
229 	pjmedia_rtcp_stat stat;
230 	pj_bool_t has_stat;
231 	pjmedia_transport_info tp_info;
232 	char rem_addr_buf[80];
233 	char codec_info[32] = {'0'};
234 	char rx_info[80] = {'\0'};
235 	char tx_info[80] = {'\0'};
236 	const char *rem_addr;
237 	const char *dir_str;
238 	const char *media_type_str;
239 
240 	switch (call_med->type) {
241 	case PJMEDIA_TYPE_AUDIO:
242 	    media_type_str = "audio";
243 	    break;
244 	case PJMEDIA_TYPE_VIDEO:
245 	    media_type_str = "video";
246 	    break;
247 	case PJMEDIA_TYPE_APPLICATION:
248 	    media_type_str = "application";
249 	    break;
250 	default:
251 	    media_type_str = "unknown";
252 	    break;
253 	}
254 
255 	/* Check if the stream is deactivated */
256 	if (call_med->tp == NULL ||
257 	    (!call_med->strm.a.stream && !call_med->strm.v.stream))
258 	{
259 	    len = pj_ansi_snprintf(p, end-p,
260 		      "%s  #%d %s deactivated\n",
261 		      indent, i, media_type_str);
262 	    if (len < 1 || len >= end-p) {
263 		*p = '\0';
264 		return;
265 	    }
266 
267 	    p += len;
268 	    continue;
269 	}
270 
271 	pjmedia_transport_info_init(&tp_info);
272 	pjmedia_transport_get_info(call_med->tp, &tp_info);
273 
274 	// rem_addr will contain actual address of RTP originator, instead of
275 	// remote RTP address specified by stream which is fetched from the SDP.
276 	// Please note that we are assuming only one stream per call.
277 	//rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr,
278 	//			     rem_addr_buf, sizeof(rem_addr_buf), 3);
279 	if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) {
280 	    rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf,
281 					 sizeof(rem_addr_buf), 3);
282 	} else {
283 	    pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-");
284 	    rem_addr = rem_addr_buf;
285 	}
286 
287 	if (call_med->dir == PJMEDIA_DIR_NONE) {
288 	    /* To handle when the stream that is currently being paused
289 	     * (http://trac.pjsip.org/repos/ticket/1079)
290 	     */
291 	    dir_str = "inactive";
292 	} else if (call_med->dir == PJMEDIA_DIR_ENCODING)
293 	    dir_str = "sendonly";
294 	else if (call_med->dir == PJMEDIA_DIR_DECODING)
295 	    dir_str = "recvonly";
296 	else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING)
297 	    dir_str = "sendrecv";
298 	else
299 	    dir_str = "inactive";
300 
301 	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
302 	    pjmedia_stream *stream = call_med->strm.a.stream;
303 	    pjmedia_stream_info info;
304 
305 	    pjmedia_stream_get_stat(stream, &stat);
306 	    has_stat = PJ_TRUE;
307 
308 	    pjmedia_stream_get_info(stream, &info);
309 	    pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz",
310 			     (int)info.fmt.encoding_name.slen,
311 			     info.fmt.encoding_name.ptr,
312 			     info.fmt.clock_rate / 1000);
313 	    pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,",
314 			     info.rx_pt);
315 	    pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,",
316 			     info.tx_pt,
317 			     info.param->setting.frm_per_pkt*
318 			     info.param->info.frm_ptime);
319 
320 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
321 	} else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
322 	    pjmedia_vid_stream *stream = call_med->strm.v.stream;
323 	    pjmedia_vid_stream_info info;
324 
325 	    pjmedia_vid_stream_get_stat(stream, &stat);
326 	    has_stat = PJ_TRUE;
327 
328 	    pjmedia_vid_stream_get_info(stream, &info);
329 	    pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s",
330 	                     (int)info.codec_info.encoding_name.slen,
331 			     info.codec_info.encoding_name.ptr);
332 	    if (call_med->dir & PJMEDIA_DIR_DECODING) {
333 		pjmedia_video_format_detail *vfd;
334 		vfd = pjmedia_format_get_video_format_detail(
335 					&info.codec_param->dec_fmt, PJ_TRUE);
336 		pj_ansi_snprintf(rx_info, sizeof(rx_info),
337 				 "pt=%d, size=%dx%d, fps=%.2f,",
338 				 info.rx_pt,
339 				 vfd->size.w, vfd->size.h,
340 				 vfd->fps.num*1.0/vfd->fps.denum);
341 	    }
342 	    if (call_med->dir & PJMEDIA_DIR_ENCODING) {
343 		pjmedia_video_format_detail *vfd;
344 		vfd = pjmedia_format_get_video_format_detail(
345 					&info.codec_param->enc_fmt, PJ_TRUE);
346 		pj_ansi_snprintf(tx_info, sizeof(tx_info),
347 				 "pt=%d, size=%dx%d, fps=%.2f,",
348 				 info.tx_pt,
349 				 vfd->size.w, vfd->size.h,
350 				 vfd->fps.num*1.0/vfd->fps.denum);
351 	    }
352 #endif /* PJMEDIA_HAS_VIDEO */
353 
354 	} else {
355 	    has_stat = PJ_FALSE;
356 	}
357 
358 	len = pj_ansi_snprintf(p, end-p,
359 		  "%s  #%d %s%s, %s, peer=%s\n",
360 		  indent,
361 		  call_med->idx,
362 		  media_type_str,
363 		  codec_info,
364 		  dir_str,
365 		  rem_addr);
366 	if (len < 1 || len >= end-p) {
367 	    *p = '\0';
368 	    return;
369 	}
370 	p += len;
371 
372 	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
373 	    if (pjsua_snd_is_active()) {
374 	    	pjmedia_echo_stat ec_stat;
375 	    	pj_status_t status;
376 
377 	    	status = pjsua_get_ec_stat(&ec_stat);
378 	    	if (status == PJ_SUCCESS) {
379 		    len = pj_ansi_snprintf(p, end-p, "   %s  EC stat: %.*s\n",
380 		    			   indent,
381 		    			   (int)ec_stat.stat_info.slen,
382 		    			   ec_stat.stat_info.ptr);
383 		    if (len < 1 || len >= end-p) {
384 	    	    	*p = '\0';
385 	    	    	return;
386 		    }
387 		    p += len;
388 	    	}
389 	    }
390 	}
391 
392 	/* Get and ICE SRTP status */
393 	if (call_med->tp) {
394 	    if (tp_info.specific_info_cnt > 0) {
395 		unsigned j;
396 		for (j = 0; j < tp_info.specific_info_cnt; ++j) {
397 		    if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
398 		    {
399 			pjmedia_srtp_info *srtp_info =
400 				    (pjmedia_srtp_info*) tp_info.spc_info[j].buffer;
401 			const char *policy_name = srtp_info->tx_policy.name.ptr;
402 
403 			if (!policy_name)
404 			    policy_name = "";
405 
406 			len = pj_ansi_snprintf(p, end-p,
407 					       "   %s  SRTP status: %s Crypto-suite: %s",
408 					       indent,
409 					       (srtp_info->active?"Active":"Not active"),
410 					       policy_name);
411 			if (len > 0 && len < end-p) {
412 			    p += len;
413 			    *p++ = '\n';
414 			    *p = '\0';
415 			}
416 		    } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
417 			const pjmedia_ice_transport_info *ii;
418 			unsigned jj;
419 
420 			ii = (const pjmedia_ice_transport_info*)
421 			     tp_info.spc_info[j].buffer;
422 
423 			len = pj_ansi_snprintf(p, end-p,
424 					       "   %s  ICE role: %s, state: %s, comp_cnt: %u",
425 					       indent,
426 					       pj_ice_sess_role_name(ii->role),
427 					       pj_ice_strans_state_name(ii->sess_state),
428 					       ii->comp_cnt);
429 			if (len > 0 && len < end-p) {
430 			    p += len;
431 			    *p++ = '\n';
432 			    *p = '\0';
433 			}
434 
435 			for (jj=0; ii->sess_state==PJ_ICE_STRANS_STATE_RUNNING && jj<2; ++jj) {
436 			    const char *type1 = pj_ice_get_cand_type_name(ii->comp[jj].lcand_type);
437 			    const char *type2 = pj_ice_get_cand_type_name(ii->comp[jj].rcand_type);
438 			    char addr1[PJ_INET6_ADDRSTRLEN+10];
439 			    char addr2[PJ_INET6_ADDRSTRLEN+10];
440 
441 			    if (pj_sockaddr_has_addr(&ii->comp[jj].lcand_addr))
442 				pj_sockaddr_print(&ii->comp[jj].lcand_addr, addr1, sizeof(addr1), 3);
443 			    else
444 				strcpy(addr1, "0.0.0.0:0");
445 			    if (pj_sockaddr_has_addr(&ii->comp[jj].rcand_addr))
446 				pj_sockaddr_print(&ii->comp[jj].rcand_addr, addr2, sizeof(addr2), 3);
447 			    else
448 				strcpy(addr2, "0.0.0.0:0");
449 			    len = pj_ansi_snprintf(p, end-p,
450 			                           "   %s     [%d]: L:%s (%c) --> R:%s (%c)\n",
451 			                           indent, jj,
452 			                           addr1, type1[0],
453 			                           addr2, type2[0]);
454 			    if (len > 0 && len < end-p) {
455 				p += len;
456 				*p = '\0';
457 			    }
458 			}
459 		    }
460 		}
461 	    }
462 	}
463 
464 
465 	if (has_stat) {
466 	    len = dump_media_stat(indent, p, (unsigned)(end-p), &stat,
467 				  rx_info, tx_info);
468 	    p += len;
469 	}
470 
471 #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
472 #   define SAMPLES_TO_USEC(usec, samples, clock_rate) \
473 	do { \
474 	    if (samples <= 4294) \
475 		usec = samples * 1000000 / clock_rate; \
476 	    else { \
477 		usec = samples * 1000 / clock_rate; \
478 		usec *= 1000; \
479 	    } \
480 	} while(0)
481 
482 #   define PRINT_VOIP_MTC_VAL(s, v) \
483 	if (v == 127) \
484 	    sprintf(s, "(na)"); \
485 	else \
486 	    sprintf(s, "%d", v)
487 
488 #   define VALIDATE_PRINT_BUF() \
489 	if (len < 1 || len >= end-p) { *p = '\0'; return; } \
490 	p += len; *p++ = '\n'; *p = '\0'
491 
492 
493 	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
494 	    pjmedia_stream_info info;
495 	    char last_update[64];
496 	    char loss[16], dup[16];
497 	    char jitter[80];
498 	    char toh[80];
499 	    char plc[16], jba[16], jbr[16];
500 	    char signal_lvl[16], noise_lvl[16], rerl[16];
501 	    char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
502 	    pjmedia_rtcp_xr_stat xr_stat;
503 	    unsigned clock_rate;
504 	    pj_time_val now;
505 
506 	    if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
507 	                                   &xr_stat) != PJ_SUCCESS)
508 	    {
509 		continue;
510 	    }
511 
512 	    if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
513 		    != PJ_SUCCESS)
514 	    {
515 		continue;
516 	    }
517 
518 	    clock_rate = info.fmt.clock_rate;
519 	    pj_gettimeofday(&now);
520 
521 	    len = pj_ansi_snprintf(p, end-p, "\n%s  Extended reports:", indent);
522 	    VALIDATE_PRINT_BUF();
523 
524 	    /* Statistics Summary */
525 	    len = pj_ansi_snprintf(p, end-p, "%s   Statistics Summary", indent);
526 	    VALIDATE_PRINT_BUF();
527 
528 	    if (xr_stat.rx.stat_sum.l)
529 		sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
530 	    else
531 		sprintf(loss, "(na)");
532 
533 	    if (xr_stat.rx.stat_sum.d)
534 		sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
535 	    else
536 		sprintf(dup, "(na)");
537 
538 	    if (xr_stat.rx.stat_sum.j) {
539 		unsigned jmin, jmax, jmean, jdev;
540 
541 		SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
542 				clock_rate);
543 		SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
544 				clock_rate);
545 		SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
546 				clock_rate);
547 		SAMPLES_TO_USEC(jdev,
548 			       pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
549 			       clock_rate);
550 		sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
551 			jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
552 	    } else
553 		sprintf(jitter, "(report not available)");
554 
555 	    if (xr_stat.rx.stat_sum.t) {
556 		sprintf(toh, "%11d %11d %11d %11d",
557 			xr_stat.rx.stat_sum.toh.min,
558 			xr_stat.rx.stat_sum.toh.mean,
559 			xr_stat.rx.stat_sum.toh.max,
560 			pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
561 	    } else
562 		sprintf(toh, "(report not available)");
563 
564 	    if (xr_stat.rx.stat_sum.update.sec == 0)
565 		strcpy(last_update, "never");
566 	    else {
567 		pj_gettimeofday(&now);
568 		PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
569 		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
570 			now.sec / 3600,
571 			(now.sec % 3600) / 60,
572 			now.sec % 60,
573 			now.msec);
574 	    }
575 
576 	    len = pj_ansi_snprintf(p, end-p,
577 		    "%s     RX last update: %s\n"
578 		    "%s        begin seq=%d, end seq=%d\n"
579 		    "%s        pkt loss=%s, dup=%s\n"
580 		    "%s              (msec)    min     avg     max     dev\n"
581 		    "%s        jitter     : %s\n"
582 		    "%s        toh        : %s",
583 		    indent, last_update,
584 		    indent,
585 		    xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
586 		    indent, loss, dup,
587 		    indent,
588 		    indent, jitter,
589 		    indent, toh
590 		    );
591 	    VALIDATE_PRINT_BUF();
592 
593 	    if (xr_stat.tx.stat_sum.l)
594 		sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
595 	    else
596 		sprintf(loss, "(na)");
597 
598 	    if (xr_stat.tx.stat_sum.d)
599 		sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
600 	    else
601 		sprintf(dup, "(na)");
602 
603 	    if (xr_stat.tx.stat_sum.j) {
604 		unsigned jmin, jmax, jmean, jdev;
605 
606 		SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
607 				clock_rate);
608 		SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
609 				clock_rate);
610 		SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
611 				clock_rate);
612 		SAMPLES_TO_USEC(jdev,
613 			       pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
614 			       clock_rate);
615 		sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
616 			jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
617 	    } else
618 		sprintf(jitter, "(report not available)");
619 
620 	    if (xr_stat.tx.stat_sum.t) {
621 		sprintf(toh, "%11d %11d %11d %11d",
622 			xr_stat.tx.stat_sum.toh.min,
623 			xr_stat.tx.stat_sum.toh.mean,
624 			xr_stat.tx.stat_sum.toh.max,
625 			pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
626 	    } else
627 		sprintf(toh,    "(report not available)");
628 
629 	    if (xr_stat.tx.stat_sum.update.sec == 0)
630 		strcpy(last_update, "never");
631 	    else {
632 		pj_gettimeofday(&now);
633 		PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
634 		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
635 			now.sec / 3600,
636 			(now.sec % 3600) / 60,
637 			now.sec % 60,
638 			now.msec);
639 	    }
640 
641 	    len = pj_ansi_snprintf(p, end-p,
642 		    "%s     TX last update: %s\n"
643 		    "%s        begin seq=%d, end seq=%d\n"
644 		    "%s        pkt loss=%s, dup=%s\n"
645 		    "%s              (msec)    min     avg     max     dev\n"
646 		    "%s        jitter     : %s\n"
647 		    "%s        toh        : %s",
648 		    indent, last_update,
649 		    indent,
650 		    xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
651 		    indent, loss, dup,
652 		    indent,
653 		    indent, jitter,
654 		    indent, toh
655 		    );
656 	    VALIDATE_PRINT_BUF();
657 
658 
659 	    /* VoIP Metrics */
660 	    len = pj_ansi_snprintf(p, end-p, "%s   VoIP Metrics", indent);
661 	    VALIDATE_PRINT_BUF();
662 
663 	    PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
664 	    PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
665 	    PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
666 	    PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
667 	    PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
668 	    PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
669 	    PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
670 
671 	    switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
672 		case PJMEDIA_RTCP_XR_PLC_DIS:
673 		    sprintf(plc, "DISABLED");
674 		    break;
675 		case PJMEDIA_RTCP_XR_PLC_ENH:
676 		    sprintf(plc, "ENHANCED");
677 		    break;
678 		case PJMEDIA_RTCP_XR_PLC_STD:
679 		    sprintf(plc, "STANDARD");
680 		    break;
681 		case PJMEDIA_RTCP_XR_PLC_UNK:
682 		default:
683 		    sprintf(plc, "UNKNOWN");
684 		    break;
685 	    }
686 
687 	    switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
688 		case PJMEDIA_RTCP_XR_JB_FIXED:
689 		    sprintf(jba, "FIXED");
690 		    break;
691 		case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
692 		    sprintf(jba, "ADAPTIVE");
693 		    break;
694 		default:
695 		    sprintf(jba, "UNKNOWN");
696 		    break;
697 	    }
698 
699 	    sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
700 
701 	    if (xr_stat.rx.voip_mtc.update.sec == 0)
702 		strcpy(last_update, "never");
703 	    else {
704 		pj_gettimeofday(&now);
705 		PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
706 		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
707 			now.sec / 3600,
708 			(now.sec % 3600) / 60,
709 			now.sec % 60,
710 			now.msec);
711 	    }
712 
713 	    len = pj_ansi_snprintf(p, end-p,
714 		    "%s     RX last update: %s\n"
715 		    "%s        packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
716 		    "%s        burst      : density=%d (%.2f%%), duration=%d%s\n"
717 		    "%s        gap        : density=%d (%.2f%%), duration=%d%s\n"
718 		    "%s        delay      : round trip=%d%s, end system=%d%s\n"
719 		    "%s        level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
720 		    "%s        quality    : R factor=%s, ext R factor=%s\n"
721 		    "%s                     MOS LQ=%s, MOS CQ=%s\n"
722 		    "%s        config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
723 		    "%s        JB delay   : cur=%d%s, max=%d%s, abs max=%d%s",
724 		    indent,
725 		    last_update,
726 		    /* packets */
727 		    indent,
728 		    xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
729 		    xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
730 		    /* burst */
731 		    indent,
732 		    xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
733 		    xr_stat.rx.voip_mtc.burst_dur, "ms",
734 		    /* gap */
735 		    indent,
736 		    xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
737 		    xr_stat.rx.voip_mtc.gap_dur, "ms",
738 		    /* delay */
739 		    indent,
740 		    xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
741 		    xr_stat.rx.voip_mtc.end_sys_delay, "ms",
742 		    /* level */
743 		    indent,
744 		    signal_lvl, "dB",
745 		    noise_lvl, "dB",
746 		    rerl, "",
747 		    /* quality */
748 		    indent,
749 		    r_factor, ext_r_factor,
750 		    indent,
751 		    mos_lq, mos_cq,
752 		    /* config */
753 		    indent,
754 		    plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
755 		    /* JB delay */
756 		    indent,
757 		    xr_stat.rx.voip_mtc.jb_nom, "ms",
758 		    xr_stat.rx.voip_mtc.jb_max, "ms",
759 		    xr_stat.rx.voip_mtc.jb_abs_max, "ms"
760 		    );
761 	    VALIDATE_PRINT_BUF();
762 
763 	    PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
764 	    PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
765 	    PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
766 	    PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
767 	    PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
768 	    PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
769 	    PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
770 
771 	    switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
772 		case PJMEDIA_RTCP_XR_PLC_DIS:
773 		    sprintf(plc, "DISABLED");
774 		    break;
775 		case PJMEDIA_RTCP_XR_PLC_ENH:
776 		    sprintf(plc, "ENHANCED");
777 		    break;
778 		case PJMEDIA_RTCP_XR_PLC_STD:
779 		    sprintf(plc, "STANDARD");
780 		    break;
781 		case PJMEDIA_RTCP_XR_PLC_UNK:
782 		default:
783 		    sprintf(plc, "unknown");
784 		    break;
785 	    }
786 
787 	    switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
788 		case PJMEDIA_RTCP_XR_JB_FIXED:
789 		    sprintf(jba, "FIXED");
790 		    break;
791 		case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
792 		    sprintf(jba, "ADAPTIVE");
793 		    break;
794 		default:
795 		    sprintf(jba, "unknown");
796 		    break;
797 	    }
798 
799 	    sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
800 
801 	    if (xr_stat.tx.voip_mtc.update.sec == 0)
802 		strcpy(last_update, "never");
803 	    else {
804 		pj_gettimeofday(&now);
805 		PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
806 		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
807 			now.sec / 3600,
808 			(now.sec % 3600) / 60,
809 			now.sec % 60,
810 			now.msec);
811 	    }
812 
813 	    len = pj_ansi_snprintf(p, end-p,
814 		    "%s     TX last update: %s\n"
815 		    "%s        packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
816 		    "%s        burst      : density=%d (%.2f%%), duration=%d%s\n"
817 		    "%s        gap        : density=%d (%.2f%%), duration=%d%s\n"
818 		    "%s        delay      : round trip=%d%s, end system=%d%s\n"
819 		    "%s        level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
820 		    "%s        quality    : R factor=%s, ext R factor=%s\n"
821 		    "%s                     MOS LQ=%s, MOS CQ=%s\n"
822 		    "%s        config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
823 		    "%s        JB delay   : cur=%d%s, max=%d%s, abs max=%d%s",
824 		    indent,
825 		    last_update,
826 		    /* pakcets */
827 		    indent,
828 		    xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
829 		    xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
830 		    /* burst */
831 		    indent,
832 		    xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
833 		    xr_stat.tx.voip_mtc.burst_dur, "ms",
834 		    /* gap */
835 		    indent,
836 		    xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
837 		    xr_stat.tx.voip_mtc.gap_dur, "ms",
838 		    /* delay */
839 		    indent,
840 		    xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
841 		    xr_stat.tx.voip_mtc.end_sys_delay, "ms",
842 		    /* level */
843 		    indent,
844 		    signal_lvl, "dB",
845 		    noise_lvl, "dB",
846 		    rerl, "",
847 		    /* quality */
848 		    indent,
849 		    r_factor, ext_r_factor,
850 		    indent,
851 		    mos_lq, mos_cq,
852 		    /* config */
853 		    indent,
854 		    plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
855 		    /* JB delay */
856 		    indent,
857 		    xr_stat.tx.voip_mtc.jb_nom, "ms",
858 		    xr_stat.tx.voip_mtc.jb_max, "ms",
859 		    xr_stat.tx.voip_mtc.jb_abs_max, "ms"
860 		    );
861 	    VALIDATE_PRINT_BUF();
862 
863 
864 	    /* RTT delay (by receiver side) */
865 	    len = pj_ansi_snprintf(p, end-p,
866 		    "%s   RTT (from recv)      min     avg     max     last    dev",
867 		    indent);
868 	    VALIDATE_PRINT_BUF();
869 	    len = pj_ansi_snprintf(p, end-p,
870 		    "%s     RTT msec      : %7.3f %7.3f %7.3f %7.3f %7.3f",
871 		    indent,
872 		    xr_stat.rtt.min / 1000.0,
873 		    xr_stat.rtt.mean / 1000.0,
874 		    xr_stat.rtt.max / 1000.0,
875 		    xr_stat.rtt.last / 1000.0,
876 		    pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
877 		   );
878 	    VALIDATE_PRINT_BUF();
879 	} /* if audio */;
880 #endif
881 
882     }
883 }
884 
885 #else	/* PJSUA_MEDIA_HAS_PJMEDIA ||
886 	   (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO &&
887 	    PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT) */
888 
dump_media_session(const char * indent,char * buf,unsigned maxlen,pjsua_call * call)889 static void dump_media_session(const char *indent,
890 			       char *buf, unsigned maxlen,
891 			       pjsua_call *call)
892 {
893     PJ_UNUSED_ARG(indent);
894     PJ_UNUSED_ARG(buf);
895     PJ_UNUSED_ARG(maxlen);
896     PJ_UNUSED_ARG(call);
897 }
898 
899 #endif	/* PJSUA_MEDIA_HAS_PJMEDIA ||
900 	   (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO &&
901 	    PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT) */
902 
903 
904 /* Print call info */
print_call(const char * title,int call_id,char * buf,pj_size_t size)905 void print_call(const char *title,
906 	        int call_id,
907 	        char *buf, pj_size_t size)
908 {
909     int len;
910     pjsua_call *call = &pjsua_var.calls[call_id];
911     pjsip_inv_session *inv = call->inv;
912     pjsip_dialog *dlg;
913     char userinfo[PJSIP_MAX_URL_SIZE];
914 
915     /* Dump invite sesion info. */
916 
917     dlg = (inv? inv->dlg: call->async_call.dlg);
918     len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
919     if (len < 0)
920 	pj_ansi_strcpy(userinfo, "<--uri too long-->");
921     else
922 	userinfo[len] = '\0';
923 
924     len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
925 			   title,
926                            pjsip_inv_state_name((call->hanging_up || !inv)?
927                                                 PJSIP_INV_STATE_DISCONNECTED:
928                                                 inv->state),
929 			   userinfo);
930     if (len < 1 || len >= (int)size) {
931 	pj_ansi_strcpy(buf, "<--uri too long-->");
932 	len = 18;
933     } else
934 	buf[len] = '\0';
935 }
936 
937 
938 /*
939  * Dump call and media statistics to string.
940  */
pjsua_call_dump(pjsua_call_id call_id,pj_bool_t with_media,char * buffer,unsigned maxlen,const char * indent)941 PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id,
942 				     pj_bool_t with_media,
943 				     char *buffer,
944 				     unsigned maxlen,
945 				     const char *indent)
946 {
947     pjsua_call *call;
948     pjsip_dialog *dlg;
949     pj_time_val duration, res_delay, con_delay;
950     char tmp[128];
951     char *p, *end;
952     pj_status_t status;
953     int len;
954 
955     PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
956 		     PJ_EINVAL);
957 
958     status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
959     if (status != PJ_SUCCESS)
960 	return status;
961 
962     *buffer = '\0';
963     p = buffer;
964     end = buffer + maxlen;
965     len = 0;
966 
967     print_call(indent, call_id, tmp, sizeof(tmp));
968 
969     len = (int)pj_ansi_strlen(tmp);
970     pj_ansi_strcpy(buffer, tmp);
971 
972     p += len;
973     *p++ = '\r';
974     *p++ = '\n';
975 
976     /* Calculate call duration */
977     if (call->conn_time.sec != 0) {
978 	pj_gettimeofday(&duration);
979 	PJ_TIME_VAL_SUB(duration, call->conn_time);
980 	con_delay = call->conn_time;
981 	PJ_TIME_VAL_SUB(con_delay, call->start_time);
982     } else {
983 	duration.sec = duration.msec = 0;
984 	con_delay.sec = con_delay.msec = 0;
985     }
986 
987     /* Calculate first response delay */
988     if (call->res_time.sec != 0) {
989 	res_delay = call->res_time;
990 	PJ_TIME_VAL_SUB(res_delay, call->start_time);
991     } else {
992 	res_delay.sec = res_delay.msec = 0;
993     }
994 
995     /* Print duration */
996     len = pj_ansi_snprintf(p, end-p,
997 		           "%s  Call time: %02dh:%02dm:%02ds, "
998 		           "1st res in %d ms, conn in %dms",
999 			   indent,
1000 		           (int)(duration.sec / 3600),
1001 		           (int)((duration.sec % 3600)/60),
1002 		           (int)(duration.sec % 60),
1003 		           (int)PJ_TIME_VAL_MSEC(res_delay),
1004 		           (int)PJ_TIME_VAL_MSEC(con_delay));
1005 
1006     if (len > 0 && len < end-p) {
1007 	p += len;
1008 	*p++ = '\n';
1009 	*p = '\0';
1010     }
1011 
1012     /* Dump session statistics */
1013     if (with_media)
1014 	dump_media_session(indent, p, (unsigned)(end-p), call);
1015 
1016     pjsip_dlg_dec_lock(dlg);
1017 
1018     return PJ_SUCCESS;
1019 }
1020 
1021