blob: 7279e512c8283e0548da0a2b249fbe26c4afbe3d [file] [log] [blame]
Benny Prijono9f468d12011-07-07 07:46:33 +00001/* $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
22const 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
39static 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 (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"
Nanang Izzuddin46977e72011-10-25 13:17:19 +0000151 "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
Benny Prijono9f468d12011-07-07 07:46:33 +0000152 "%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 + stat->tx.loss) : 0),
170 stat->tx.dup,
171 (stat->tx.dup? stat->tx.dup * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
172 stat->tx.reorder,
173 (stat->tx.reorder? stat->tx.reorder * 100.0 / (stat->tx.pkt + stat->tx.loss) : 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 (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 (p-buf);
207 }
208 p += len;
209
210 return (p-buf);
211}
212
213
214/* Dump media session */
215static void dump_media_session(const char *indent,
216 char *buf, unsigned maxlen,
217 pjsua_call *call)
218{
219 unsigned i;
220 char *p = buf, *end = buf+maxlen;
221 int len;
222
223 for (i=0; i<call->med_cnt; ++i) {
224 pjsua_call_media *call_med = &call->media[i];
225 pjmedia_rtcp_stat stat;
226 pj_bool_t has_stat;
227 pjmedia_transport_info tp_info;
228 char rem_addr_buf[80];
229 char codec_info[32] = {'0'};
230 char rx_info[80] = {'\0'};
231 char tx_info[80] = {'\0'};
232 const char *rem_addr;
233 const char *dir_str;
234 const char *media_type_str;
235
236 switch (call_med->type) {
237 case PJMEDIA_TYPE_AUDIO:
238 media_type_str = "audio";
239 break;
240 case PJMEDIA_TYPE_VIDEO:
241 media_type_str = "video";
242 break;
243 case PJMEDIA_TYPE_APPLICATION:
244 media_type_str = "application";
245 break;
246 default:
247 media_type_str = "unknown";
248 break;
249 }
250
251 /* Check if the stream is deactivated */
252 if (call_med->tp == NULL ||
253 (!call_med->strm.a.stream && !call_med->strm.v.stream))
254 {
255 len = pj_ansi_snprintf(p, end-p,
256 "%s #%d %s deactivated\n",
257 indent, i, media_type_str);
258 if (len < 1 || len > end-p) {
259 *p = '\0';
260 return;
261 }
262
263 p += len;
264 continue;
265 }
266
267 pjmedia_transport_info_init(&tp_info);
268 pjmedia_transport_get_info(call_med->tp, &tp_info);
269
270 // rem_addr will contain actual address of RTP originator, instead of
271 // remote RTP address specified by stream which is fetched from the SDP.
272 // Please note that we are assuming only one stream per call.
273 //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr,
274 // rem_addr_buf, sizeof(rem_addr_buf), 3);
275 if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) {
276 rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf,
277 sizeof(rem_addr_buf), 3);
278 } else {
279 pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-");
280 rem_addr = rem_addr_buf;
281 }
282
283 if (call_med->dir == PJMEDIA_DIR_NONE) {
284 /* To handle when the stream that is currently being paused
285 * (http://trac.pjsip.org/repos/ticket/1079)
286 */
287 dir_str = "inactive";
288 } else if (call_med->dir == PJMEDIA_DIR_ENCODING)
289 dir_str = "sendonly";
290 else if (call_med->dir == PJMEDIA_DIR_DECODING)
291 dir_str = "recvonly";
292 else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING)
293 dir_str = "sendrecv";
294 else
295 dir_str = "inactive";
296
297 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
298 pjmedia_stream *stream = call_med->strm.a.stream;
299 pjmedia_stream_info info;
300
301 pjmedia_stream_get_stat(stream, &stat);
302 has_stat = PJ_TRUE;
303
304 pjmedia_stream_get_info(stream, &info);
305 pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz",
306 (int)info.fmt.encoding_name.slen,
307 info.fmt.encoding_name.ptr,
308 info.fmt.clock_rate / 1000);
309 pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,",
Nanang Izzuddin16653df2011-10-23 06:59:48 +0000310 info.rx_pt);
Benny Prijono9f468d12011-07-07 07:46:33 +0000311 pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,",
312 info.tx_pt,
313 info.param->setting.frm_per_pkt*
314 info.param->info.frm_ptime);
Nanang Izzuddin63b3c132011-07-19 11:11:07 +0000315
316#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono9f468d12011-07-07 07:46:33 +0000317 } else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
318 pjmedia_vid_stream *stream = call_med->strm.v.stream;
319 pjmedia_vid_stream_info info;
320
321 pjmedia_vid_stream_get_stat(stream, &stat);
322 has_stat = PJ_TRUE;
323
324 pjmedia_vid_stream_get_info(stream, &info);
325 pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s",
326 (int)info.codec_info.encoding_name.slen,
327 info.codec_info.encoding_name.ptr);
328 if (call_med->dir & PJMEDIA_DIR_DECODING) {
329 pjmedia_video_format_detail *vfd;
330 vfd = pjmedia_format_get_video_format_detail(
331 &info.codec_param->dec_fmt, PJ_TRUE);
332 pj_ansi_snprintf(rx_info, sizeof(rx_info),
333 "pt=%d, size=%dx%d, fps=%.2f,",
334 info.rx_pt,
335 vfd->size.w, vfd->size.h,
336 vfd->fps.num*1.0/vfd->fps.denum);
337 }
338 if (call_med->dir & PJMEDIA_DIR_ENCODING) {
339 pjmedia_video_format_detail *vfd;
340 vfd = pjmedia_format_get_video_format_detail(
341 &info.codec_param->enc_fmt, PJ_TRUE);
342 pj_ansi_snprintf(tx_info, sizeof(tx_info),
343 "pt=%d, size=%dx%d, fps=%.2f,",
344 info.tx_pt,
345 vfd->size.w, vfd->size.h,
346 vfd->fps.num*1.0/vfd->fps.denum);
347 }
Nanang Izzuddin63b3c132011-07-19 11:11:07 +0000348#endif /* PJMEDIA_HAS_VIDEO */
349
Benny Prijono9f468d12011-07-07 07:46:33 +0000350 } else {
351 has_stat = PJ_FALSE;
352 }
353
354 len = pj_ansi_snprintf(p, end-p,
355 "%s #%d %s%s, %s, peer=%s\n",
356 indent,
357 call_med->idx,
358 media_type_str,
359 codec_info,
360 dir_str,
361 rem_addr);
362 if (len < 1 || len > end-p) {
363 *p = '\0';
364 return;
365 }
366 p += len;
367
368 /* Get and ICE SRTP status */
369 if (call_med->tp) {
370 pjmedia_transport_info tp_info;
371
372 pjmedia_transport_info_init(&tp_info);
373 pjmedia_transport_get_info(call_med->tp, &tp_info);
374 if (tp_info.specific_info_cnt > 0) {
375 unsigned j;
376 for (j = 0; j < tp_info.specific_info_cnt; ++j) {
377 if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
378 {
379 pjmedia_srtp_info *srtp_info =
380 (pjmedia_srtp_info*) tp_info.spc_info[j].buffer;
381
382 len = pj_ansi_snprintf(p, end-p,
383 " %s SRTP status: %s Crypto-suite: %s",
384 indent,
385 (srtp_info->active?"Active":"Not active"),
386 srtp_info->tx_policy.name.ptr);
387 if (len > 0 && len < end-p) {
388 p += len;
389 *p++ = '\n';
390 *p = '\0';
391 }
392 } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
393 const pjmedia_ice_transport_info *ii;
Benny Prijono20eb7272011-10-28 04:10:00 +0000394 unsigned jj;
Benny Prijono9f468d12011-07-07 07:46:33 +0000395
396 ii = (const pjmedia_ice_transport_info*)
397 tp_info.spc_info[j].buffer;
398
399 len = pj_ansi_snprintf(p, end-p,
400 " %s ICE role: %s, state: %s, comp_cnt: %u",
401 indent,
402 pj_ice_sess_role_name(ii->role),
403 pj_ice_strans_state_name(ii->sess_state),
404 ii->comp_cnt);
405 if (len > 0 && len < end-p) {
406 p += len;
407 *p++ = '\n';
408 *p = '\0';
409 }
Benny Prijono20eb7272011-10-28 04:10:00 +0000410
411 for (jj=0; ii->sess_state==PJ_ICE_STRANS_STATE_RUNNING && jj<2; ++jj) {
412 const char *type1 = pj_ice_get_cand_type_name(ii->comp[jj].lcand_type);
413 const char *type2 = pj_ice_get_cand_type_name(ii->comp[jj].rcand_type);
414 char addr1[PJ_INET6_ADDRSTRLEN+10];
415 char addr2[PJ_INET6_ADDRSTRLEN+10];
416 const char *comp_name[2] = {"rtp ", "rtcp"};
417
418 if (pj_sockaddr_has_addr(&ii->comp[jj].lcand_addr))
419 pj_sockaddr_print(&ii->comp[jj].lcand_addr, addr1, sizeof(addr1), 3);
420 else
421 strcpy(addr1, "0.0.0.0:0");
422 if (pj_sockaddr_has_addr(&ii->comp[jj].rcand_addr))
423 pj_sockaddr_print(&ii->comp[jj].rcand_addr, addr2, sizeof(addr2), 3);
424 else
425 strcpy(addr2, "0.0.0.0:0");
426 len = pj_ansi_snprintf(p, end-p,
427 " %s [%d]: L:%s (%c) --> R:%s (%c)\n",
428 indent, jj,
429 addr1, type1[0],
430 addr2, type2[0]);
431 if (len > 0 && len < end-p) {
432 p += len;
433 *p = '\0';
434 }
435 }
Benny Prijono9f468d12011-07-07 07:46:33 +0000436 }
437 }
438 }
439 }
440
441
442 if (has_stat) {
443 len = dump_media_stat(indent, p, end-p, &stat,
444 rx_info, tx_info);
445 p += len;
446 }
447
448#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
449# define SAMPLES_TO_USEC(usec, samples, clock_rate) \
450 do { \
451 if (samples <= 4294) \
452 usec = samples * 1000000 / clock_rate; \
453 else { \
454 usec = samples * 1000 / clock_rate; \
455 usec *= 1000; \
456 } \
457 } while(0)
458
459# define PRINT_VOIP_MTC_VAL(s, v) \
460 if (v == 127) \
461 sprintf(s, "(na)"); \
462 else \
463 sprintf(s, "%d", v)
464
465# define VALIDATE_PRINT_BUF() \
466 if (len < 1 || len > end-p) { *p = '\0'; return; } \
467 p += len; *p++ = '\n'; *p = '\0'
468
469
470 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
471 pjmedia_stream_info info;
472 char last_update[64];
473 char loss[16], dup[16];
474 char jitter[80];
475 char toh[80];
476 char plc[16], jba[16], jbr[16];
477 char signal_lvl[16], noise_lvl[16], rerl[16];
478 char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
479 pjmedia_rtcp_xr_stat xr_stat;
480 unsigned clock_rate;
481 pj_time_val now;
482
483 if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
484 &xr_stat) != PJ_SUCCESS)
485 {
486 continue;
487 }
488
489 if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
490 != PJ_SUCCESS)
491 {
492 continue;
493 }
494
495 clock_rate = info.fmt.clock_rate;
496 pj_gettimeofday(&now);
497
498 len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent);
499 VALIDATE_PRINT_BUF();
500
501 /* Statistics Summary */
502 len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent);
503 VALIDATE_PRINT_BUF();
504
505 if (xr_stat.rx.stat_sum.l)
506 sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
507 else
508 sprintf(loss, "(na)");
509
510 if (xr_stat.rx.stat_sum.d)
511 sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
512 else
513 sprintf(dup, "(na)");
514
515 if (xr_stat.rx.stat_sum.j) {
516 unsigned jmin, jmax, jmean, jdev;
517
518 SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
519 clock_rate);
520 SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
521 clock_rate);
522 SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
523 clock_rate);
524 SAMPLES_TO_USEC(jdev,
525 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
526 clock_rate);
527 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
528 jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
529 } else
530 sprintf(jitter, "(report not available)");
531
532 if (xr_stat.rx.stat_sum.t) {
533 sprintf(toh, "%11d %11d %11d %11d",
534 xr_stat.rx.stat_sum.toh.min,
535 xr_stat.rx.stat_sum.toh.mean,
536 xr_stat.rx.stat_sum.toh.max,
537 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
538 } else
539 sprintf(toh, "(report not available)");
540
541 if (xr_stat.rx.stat_sum.update.sec == 0)
542 strcpy(last_update, "never");
543 else {
544 pj_gettimeofday(&now);
545 PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
546 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
547 now.sec / 3600,
548 (now.sec % 3600) / 60,
549 now.sec % 60,
550 now.msec);
551 }
552
553 len = pj_ansi_snprintf(p, end-p,
554 "%s RX last update: %s\n"
555 "%s begin seq=%d, end seq=%d\n"
556 "%s pkt loss=%s, dup=%s\n"
557 "%s (msec) min avg max dev\n"
558 "%s jitter : %s\n"
559 "%s toh : %s",
560 indent, last_update,
561 indent,
562 xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
563 indent, loss, dup,
564 indent,
565 indent, jitter,
566 indent, toh
567 );
568 VALIDATE_PRINT_BUF();
569
570 if (xr_stat.tx.stat_sum.l)
571 sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
572 else
573 sprintf(loss, "(na)");
574
575 if (xr_stat.tx.stat_sum.d)
576 sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
577 else
578 sprintf(dup, "(na)");
579
580 if (xr_stat.tx.stat_sum.j) {
581 unsigned jmin, jmax, jmean, jdev;
582
583 SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
584 clock_rate);
585 SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
586 clock_rate);
587 SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
588 clock_rate);
589 SAMPLES_TO_USEC(jdev,
590 pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
591 clock_rate);
592 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
593 jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
594 } else
595 sprintf(jitter, "(report not available)");
596
597 if (xr_stat.tx.stat_sum.t) {
598 sprintf(toh, "%11d %11d %11d %11d",
599 xr_stat.tx.stat_sum.toh.min,
600 xr_stat.tx.stat_sum.toh.mean,
601 xr_stat.tx.stat_sum.toh.max,
602 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
603 } else
604 sprintf(toh, "(report not available)");
605
606 if (xr_stat.tx.stat_sum.update.sec == 0)
607 strcpy(last_update, "never");
608 else {
609 pj_gettimeofday(&now);
610 PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
611 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
612 now.sec / 3600,
613 (now.sec % 3600) / 60,
614 now.sec % 60,
615 now.msec);
616 }
617
618 len = pj_ansi_snprintf(p, end-p,
619 "%s TX last update: %s\n"
620 "%s begin seq=%d, end seq=%d\n"
621 "%s pkt loss=%s, dup=%s\n"
622 "%s (msec) min avg max dev\n"
623 "%s jitter : %s\n"
624 "%s toh : %s",
625 indent, last_update,
626 indent,
627 xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
628 indent, loss, dup,
629 indent,
630 indent, jitter,
631 indent, toh
632 );
633 VALIDATE_PRINT_BUF();
634
635
636 /* VoIP Metrics */
637 len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent);
638 VALIDATE_PRINT_BUF();
639
640 PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
641 PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
642 PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
643 PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
644 PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
645 PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
646 PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
647
648 switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
649 case PJMEDIA_RTCP_XR_PLC_DIS:
650 sprintf(plc, "DISABLED");
651 break;
652 case PJMEDIA_RTCP_XR_PLC_ENH:
653 sprintf(plc, "ENHANCED");
654 break;
655 case PJMEDIA_RTCP_XR_PLC_STD:
656 sprintf(plc, "STANDARD");
657 break;
658 case PJMEDIA_RTCP_XR_PLC_UNK:
659 default:
660 sprintf(plc, "UNKNOWN");
661 break;
662 }
663
664 switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
665 case PJMEDIA_RTCP_XR_JB_FIXED:
666 sprintf(jba, "FIXED");
667 break;
668 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
669 sprintf(jba, "ADAPTIVE");
670 break;
671 default:
672 sprintf(jba, "UNKNOWN");
673 break;
674 }
675
676 sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
677
678 if (xr_stat.rx.voip_mtc.update.sec == 0)
679 strcpy(last_update, "never");
680 else {
681 pj_gettimeofday(&now);
682 PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
683 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
684 now.sec / 3600,
685 (now.sec % 3600) / 60,
686 now.sec % 60,
687 now.msec);
688 }
689
690 len = pj_ansi_snprintf(p, end-p,
691 "%s RX last update: %s\n"
692 "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
693 "%s burst : density=%d (%.2f%%), duration=%d%s\n"
694 "%s gap : density=%d (%.2f%%), duration=%d%s\n"
695 "%s delay : round trip=%d%s, end system=%d%s\n"
696 "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
697 "%s quality : R factor=%s, ext R factor=%s\n"
698 "%s MOS LQ=%s, MOS CQ=%s\n"
699 "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
700 "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
701 indent,
702 last_update,
703 /* packets */
704 indent,
705 xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
706 xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
707 /* burst */
708 indent,
709 xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
710 xr_stat.rx.voip_mtc.burst_dur, "ms",
711 /* gap */
712 indent,
713 xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
714 xr_stat.rx.voip_mtc.gap_dur, "ms",
715 /* delay */
716 indent,
717 xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
718 xr_stat.rx.voip_mtc.end_sys_delay, "ms",
719 /* level */
720 indent,
721 signal_lvl, "dB",
722 noise_lvl, "dB",
723 rerl, "",
724 /* quality */
725 indent,
726 r_factor, ext_r_factor,
727 indent,
728 mos_lq, mos_cq,
729 /* config */
730 indent,
731 plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
732 /* JB delay */
733 indent,
734 xr_stat.rx.voip_mtc.jb_nom, "ms",
735 xr_stat.rx.voip_mtc.jb_max, "ms",
736 xr_stat.rx.voip_mtc.jb_abs_max, "ms"
737 );
738 VALIDATE_PRINT_BUF();
739
740 PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
741 PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
742 PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
743 PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
744 PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
745 PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
746 PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
747
748 switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
749 case PJMEDIA_RTCP_XR_PLC_DIS:
750 sprintf(plc, "DISABLED");
751 break;
752 case PJMEDIA_RTCP_XR_PLC_ENH:
753 sprintf(plc, "ENHANCED");
754 break;
755 case PJMEDIA_RTCP_XR_PLC_STD:
756 sprintf(plc, "STANDARD");
757 break;
758 case PJMEDIA_RTCP_XR_PLC_UNK:
759 default:
760 sprintf(plc, "unknown");
761 break;
762 }
763
764 switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
765 case PJMEDIA_RTCP_XR_JB_FIXED:
766 sprintf(jba, "FIXED");
767 break;
768 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
769 sprintf(jba, "ADAPTIVE");
770 break;
771 default:
772 sprintf(jba, "unknown");
773 break;
774 }
775
776 sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
777
778 if (xr_stat.tx.voip_mtc.update.sec == 0)
779 strcpy(last_update, "never");
780 else {
781 pj_gettimeofday(&now);
782 PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
783 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
784 now.sec / 3600,
785 (now.sec % 3600) / 60,
786 now.sec % 60,
787 now.msec);
788 }
789
790 len = pj_ansi_snprintf(p, end-p,
791 "%s TX last update: %s\n"
792 "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
793 "%s burst : density=%d (%.2f%%), duration=%d%s\n"
794 "%s gap : density=%d (%.2f%%), duration=%d%s\n"
795 "%s delay : round trip=%d%s, end system=%d%s\n"
796 "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
797 "%s quality : R factor=%s, ext R factor=%s\n"
798 "%s MOS LQ=%s, MOS CQ=%s\n"
799 "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
800 "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
801 indent,
802 last_update,
803 /* pakcets */
804 indent,
805 xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
806 xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
807 /* burst */
808 indent,
809 xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
810 xr_stat.tx.voip_mtc.burst_dur, "ms",
811 /* gap */
812 indent,
813 xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
814 xr_stat.tx.voip_mtc.gap_dur, "ms",
815 /* delay */
816 indent,
817 xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
818 xr_stat.tx.voip_mtc.end_sys_delay, "ms",
819 /* level */
820 indent,
821 signal_lvl, "dB",
822 noise_lvl, "dB",
823 rerl, "",
824 /* quality */
825 indent,
826 r_factor, ext_r_factor,
827 indent,
828 mos_lq, mos_cq,
829 /* config */
830 indent,
831 plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
832 /* JB delay */
833 indent,
834 xr_stat.tx.voip_mtc.jb_nom, "ms",
835 xr_stat.tx.voip_mtc.jb_max, "ms",
836 xr_stat.tx.voip_mtc.jb_abs_max, "ms"
837 );
838 VALIDATE_PRINT_BUF();
839
840
841 /* RTT delay (by receiver side) */
842 len = pj_ansi_snprintf(p, end-p,
843 "%s RTT (from recv) min avg max last dev",
844 indent);
845 VALIDATE_PRINT_BUF();
846 len = pj_ansi_snprintf(p, end-p,
847 "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f",
848 indent,
849 xr_stat.rtt.min / 1000.0,
850 xr_stat.rtt.mean / 1000.0,
851 xr_stat.rtt.max / 1000.0,
852 xr_stat.rtt.last / 1000.0,
853 pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
854 );
855 VALIDATE_PRINT_BUF();
856 } /* if audio */;
857#endif
858
859 }
860}
861
862
863/* Print call info */
864void print_call(const char *title,
865 int call_id,
866 char *buf, pj_size_t size)
867{
868 int len;
869 pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
870 pjsip_dialog *dlg = inv->dlg;
871 char userinfo[128];
872
873 /* Dump invite sesion info. */
874
875 len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
876 if (len < 0)
877 pj_ansi_strcpy(userinfo, "<--uri too long-->");
878 else
879 userinfo[len] = '\0';
880
881 len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
882 title,
883 pjsip_inv_state_name(inv->state),
884 userinfo);
885 if (len < 1 || len >= (int)size) {
886 pj_ansi_strcpy(buf, "<--uri too long-->");
887 len = 18;
888 } else
889 buf[len] = '\0';
890}
891
892
893/*
894 * Dump call and media statistics to string.
895 */
896PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id,
897 pj_bool_t with_media,
898 char *buffer,
899 unsigned maxlen,
900 const char *indent)
901{
902 pjsua_call *call;
903 pjsip_dialog *dlg;
904 pj_time_val duration, res_delay, con_delay;
905 char tmp[128];
906 char *p, *end;
907 pj_status_t status;
908 int len;
909
910 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
911 PJ_EINVAL);
912
913 status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
914 if (status != PJ_SUCCESS)
915 return status;
916
917 *buffer = '\0';
918 p = buffer;
919 end = buffer + maxlen;
920 len = 0;
921
922 print_call(indent, call_id, tmp, sizeof(tmp));
923
924 len = pj_ansi_strlen(tmp);
925 pj_ansi_strcpy(buffer, tmp);
926
927 p += len;
928 *p++ = '\r';
929 *p++ = '\n';
930
931 /* Calculate call duration */
932 if (call->conn_time.sec != 0) {
933 pj_gettimeofday(&duration);
934 PJ_TIME_VAL_SUB(duration, call->conn_time);
935 con_delay = call->conn_time;
936 PJ_TIME_VAL_SUB(con_delay, call->start_time);
937 } else {
938 duration.sec = duration.msec = 0;
939 con_delay.sec = con_delay.msec = 0;
940 }
941
942 /* Calculate first response delay */
943 if (call->res_time.sec != 0) {
944 res_delay = call->res_time;
945 PJ_TIME_VAL_SUB(res_delay, call->start_time);
946 } else {
947 res_delay.sec = res_delay.msec = 0;
948 }
949
950 /* Print duration */
951 len = pj_ansi_snprintf(p, end-p,
952 "%s Call time: %02dh:%02dm:%02ds, "
953 "1st res in %d ms, conn in %dms",
954 indent,
955 (int)(duration.sec / 3600),
956 (int)((duration.sec % 3600)/60),
957 (int)(duration.sec % 60),
958 (int)PJ_TIME_VAL_MSEC(res_delay),
959 (int)PJ_TIME_VAL_MSEC(con_delay));
960
961 if (len > 0 && len < end-p) {
962 p += len;
963 *p++ = '\n';
964 *p = '\0';
965 }
966
967 /* Dump session statistics */
968 if (with_media && pjsua_call_has_media(call_id))
969 dump_media_session(indent, p, end-p, call);
970
971 pjsip_dlg_dec_lock(dlg);
972
973 return PJ_SUCCESS;
974}
975