Add support for ICE/STUN/TURN in res_rtp_asterisk and chan_sip.
[asterisk/asterisk.git] / res / pjproject / pjsip / src / pjsua-lib / pjsua_dump.c
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
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
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 (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 + 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 */
215 static 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,",
310                              info.rx_pt);
311             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);
315
316 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
317         } 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             }
348 #endif /* PJMEDIA_HAS_VIDEO */
349
350         } 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;
394                         unsigned jj;
395
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                         }
410
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
417                             if (pj_sockaddr_has_addr(&ii->comp[jj].lcand_addr))
418                                 pj_sockaddr_print(&ii->comp[jj].lcand_addr, addr1, sizeof(addr1), 3);
419                             else
420                                 strcpy(addr1, "0.0.0.0:0");
421                             if (pj_sockaddr_has_addr(&ii->comp[jj].rcand_addr))
422                                 pj_sockaddr_print(&ii->comp[jj].rcand_addr, addr2, sizeof(addr2), 3);
423                             else
424                                 strcpy(addr2, "0.0.0.0:0");
425                             len = pj_ansi_snprintf(p, end-p,
426                                                    "   %s     [%d]: L:%s (%c) --> R:%s (%c)\n",
427                                                    indent, jj,
428                                                    addr1, type1[0],
429                                                    addr2, type2[0]);
430                             if (len > 0 && len < end-p) {
431                                 p += len;
432                                 *p = '\0';
433                             }
434                         }
435                     }
436                 }
437             }
438         }
439
440
441         if (has_stat) {
442             len = dump_media_stat(indent, p, end-p, &stat,
443                                   rx_info, tx_info);
444             p += len;
445         }
446
447 #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
448 #   define SAMPLES_TO_USEC(usec, samples, clock_rate) \
449         do { \
450             if (samples <= 4294) \
451                 usec = samples * 1000000 / clock_rate; \
452             else { \
453                 usec = samples * 1000 / clock_rate; \
454                 usec *= 1000; \
455             } \
456         } while(0)
457
458 #   define PRINT_VOIP_MTC_VAL(s, v) \
459         if (v == 127) \
460             sprintf(s, "(na)"); \
461         else \
462             sprintf(s, "%d", v)
463
464 #   define VALIDATE_PRINT_BUF() \
465         if (len < 1 || len > end-p) { *p = '\0'; return; } \
466         p += len; *p++ = '\n'; *p = '\0'
467
468
469         if (call_med->type == PJMEDIA_TYPE_AUDIO) {
470             pjmedia_stream_info info;
471             char last_update[64];
472             char loss[16], dup[16];
473             char jitter[80];
474             char toh[80];
475             char plc[16], jba[16], jbr[16];
476             char signal_lvl[16], noise_lvl[16], rerl[16];
477             char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
478             pjmedia_rtcp_xr_stat xr_stat;
479             unsigned clock_rate;
480             pj_time_val now;
481
482             if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
483                                            &xr_stat) != PJ_SUCCESS)
484             {
485                 continue;
486             }
487
488             if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
489                     != PJ_SUCCESS)
490             {
491                 continue;
492             }
493
494             clock_rate = info.fmt.clock_rate;
495             pj_gettimeofday(&now);
496
497             len = pj_ansi_snprintf(p, end-p, "\n%s  Extended reports:", indent);
498             VALIDATE_PRINT_BUF();
499
500             /* Statistics Summary */
501             len = pj_ansi_snprintf(p, end-p, "%s   Statistics Summary", indent);
502             VALIDATE_PRINT_BUF();
503
504             if (xr_stat.rx.stat_sum.l)
505                 sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
506             else
507                 sprintf(loss, "(na)");
508
509             if (xr_stat.rx.stat_sum.d)
510                 sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
511             else
512                 sprintf(dup, "(na)");
513
514             if (xr_stat.rx.stat_sum.j) {
515                 unsigned jmin, jmax, jmean, jdev;
516
517                 SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
518                                 clock_rate);
519                 SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
520                                 clock_rate);
521                 SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
522                                 clock_rate);
523                 SAMPLES_TO_USEC(jdev,
524                                pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
525                                clock_rate);
526                 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
527                         jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
528             } else
529                 sprintf(jitter, "(report not available)");
530
531             if (xr_stat.rx.stat_sum.t) {
532                 sprintf(toh, "%11d %11d %11d %11d",
533                         xr_stat.rx.stat_sum.toh.min,
534                         xr_stat.rx.stat_sum.toh.mean,
535                         xr_stat.rx.stat_sum.toh.max,
536                         pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
537             } else
538                 sprintf(toh, "(report not available)");
539
540             if (xr_stat.rx.stat_sum.update.sec == 0)
541                 strcpy(last_update, "never");
542             else {
543                 pj_gettimeofday(&now);
544                 PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
545                 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
546                         now.sec / 3600,
547                         (now.sec % 3600) / 60,
548                         now.sec % 60,
549                         now.msec);
550             }
551
552             len = pj_ansi_snprintf(p, end-p,
553                     "%s     RX last update: %s\n"
554                     "%s        begin seq=%d, end seq=%d\n"
555                     "%s        pkt loss=%s, dup=%s\n"
556                     "%s              (msec)    min     avg     max     dev\n"
557                     "%s        jitter     : %s\n"
558                     "%s        toh        : %s",
559                     indent, last_update,
560                     indent,
561                     xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
562                     indent, loss, dup,
563                     indent,
564                     indent, jitter,
565                     indent, toh
566                     );
567             VALIDATE_PRINT_BUF();
568
569             if (xr_stat.tx.stat_sum.l)
570                 sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
571             else
572                 sprintf(loss, "(na)");
573
574             if (xr_stat.tx.stat_sum.d)
575                 sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
576             else
577                 sprintf(dup, "(na)");
578
579             if (xr_stat.tx.stat_sum.j) {
580                 unsigned jmin, jmax, jmean, jdev;
581
582                 SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
583                                 clock_rate);
584                 SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
585                                 clock_rate);
586                 SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
587                                 clock_rate);
588                 SAMPLES_TO_USEC(jdev,
589                                pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
590                                clock_rate);
591                 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
592                         jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
593             } else
594                 sprintf(jitter, "(report not available)");
595
596             if (xr_stat.tx.stat_sum.t) {
597                 sprintf(toh, "%11d %11d %11d %11d",
598                         xr_stat.tx.stat_sum.toh.min,
599                         xr_stat.tx.stat_sum.toh.mean,
600                         xr_stat.tx.stat_sum.toh.max,
601                         pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
602             } else
603                 sprintf(toh,    "(report not available)");
604
605             if (xr_stat.tx.stat_sum.update.sec == 0)
606                 strcpy(last_update, "never");
607             else {
608                 pj_gettimeofday(&now);
609                 PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
610                 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
611                         now.sec / 3600,
612                         (now.sec % 3600) / 60,
613                         now.sec % 60,
614                         now.msec);
615             }
616
617             len = pj_ansi_snprintf(p, end-p,
618                     "%s     TX last update: %s\n"
619                     "%s        begin seq=%d, end seq=%d\n"
620                     "%s        pkt loss=%s, dup=%s\n"
621                     "%s              (msec)    min     avg     max     dev\n"
622                     "%s        jitter     : %s\n"
623                     "%s        toh        : %s",
624                     indent, last_update,
625                     indent,
626                     xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
627                     indent, loss, dup,
628                     indent,
629                     indent, jitter,
630                     indent, toh
631                     );
632             VALIDATE_PRINT_BUF();
633
634
635             /* VoIP Metrics */
636             len = pj_ansi_snprintf(p, end-p, "%s   VoIP Metrics", indent);
637             VALIDATE_PRINT_BUF();
638
639             PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
640             PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
641             PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
642             PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
643             PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
644             PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
645             PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
646
647             switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
648                 case PJMEDIA_RTCP_XR_PLC_DIS:
649                     sprintf(plc, "DISABLED");
650                     break;
651                 case PJMEDIA_RTCP_XR_PLC_ENH:
652                     sprintf(plc, "ENHANCED");
653                     break;
654                 case PJMEDIA_RTCP_XR_PLC_STD:
655                     sprintf(plc, "STANDARD");
656                     break;
657                 case PJMEDIA_RTCP_XR_PLC_UNK:
658                 default:
659                     sprintf(plc, "UNKNOWN");
660                     break;
661             }
662
663             switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
664                 case PJMEDIA_RTCP_XR_JB_FIXED:
665                     sprintf(jba, "FIXED");
666                     break;
667                 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
668                     sprintf(jba, "ADAPTIVE");
669                     break;
670                 default:
671                     sprintf(jba, "UNKNOWN");
672                     break;
673             }
674
675             sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
676
677             if (xr_stat.rx.voip_mtc.update.sec == 0)
678                 strcpy(last_update, "never");
679             else {
680                 pj_gettimeofday(&now);
681                 PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
682                 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
683                         now.sec / 3600,
684                         (now.sec % 3600) / 60,
685                         now.sec % 60,
686                         now.msec);
687             }
688
689             len = pj_ansi_snprintf(p, end-p,
690                     "%s     RX last update: %s\n"
691                     "%s        packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
692                     "%s        burst      : density=%d (%.2f%%), duration=%d%s\n"
693                     "%s        gap        : density=%d (%.2f%%), duration=%d%s\n"
694                     "%s        delay      : round trip=%d%s, end system=%d%s\n"
695                     "%s        level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
696                     "%s        quality    : R factor=%s, ext R factor=%s\n"
697                     "%s                     MOS LQ=%s, MOS CQ=%s\n"
698                     "%s        config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
699                     "%s        JB delay   : cur=%d%s, max=%d%s, abs max=%d%s",
700                     indent,
701                     last_update,
702                     /* packets */
703                     indent,
704                     xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
705                     xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
706                     /* burst */
707                     indent,
708                     xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
709                     xr_stat.rx.voip_mtc.burst_dur, "ms",
710                     /* gap */
711                     indent,
712                     xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
713                     xr_stat.rx.voip_mtc.gap_dur, "ms",
714                     /* delay */
715                     indent,
716                     xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
717                     xr_stat.rx.voip_mtc.end_sys_delay, "ms",
718                     /* level */
719                     indent,
720                     signal_lvl, "dB",
721                     noise_lvl, "dB",
722                     rerl, "",
723                     /* quality */
724                     indent,
725                     r_factor, ext_r_factor,
726                     indent,
727                     mos_lq, mos_cq,
728                     /* config */
729                     indent,
730                     plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
731                     /* JB delay */
732                     indent,
733                     xr_stat.rx.voip_mtc.jb_nom, "ms",
734                     xr_stat.rx.voip_mtc.jb_max, "ms",
735                     xr_stat.rx.voip_mtc.jb_abs_max, "ms"
736                     );
737             VALIDATE_PRINT_BUF();
738
739             PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
740             PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
741             PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
742             PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
743             PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
744             PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
745             PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
746
747             switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
748                 case PJMEDIA_RTCP_XR_PLC_DIS:
749                     sprintf(plc, "DISABLED");
750                     break;
751                 case PJMEDIA_RTCP_XR_PLC_ENH:
752                     sprintf(plc, "ENHANCED");
753                     break;
754                 case PJMEDIA_RTCP_XR_PLC_STD:
755                     sprintf(plc, "STANDARD");
756                     break;
757                 case PJMEDIA_RTCP_XR_PLC_UNK:
758                 default:
759                     sprintf(plc, "unknown");
760                     break;
761             }
762
763             switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
764                 case PJMEDIA_RTCP_XR_JB_FIXED:
765                     sprintf(jba, "FIXED");
766                     break;
767                 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
768                     sprintf(jba, "ADAPTIVE");
769                     break;
770                 default:
771                     sprintf(jba, "unknown");
772                     break;
773             }
774
775             sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
776
777             if (xr_stat.tx.voip_mtc.update.sec == 0)
778                 strcpy(last_update, "never");
779             else {
780                 pj_gettimeofday(&now);
781                 PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
782                 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
783                         now.sec / 3600,
784                         (now.sec % 3600) / 60,
785                         now.sec % 60,
786                         now.msec);
787             }
788
789             len = pj_ansi_snprintf(p, end-p,
790                     "%s     TX last update: %s\n"
791                     "%s        packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
792                     "%s        burst      : density=%d (%.2f%%), duration=%d%s\n"
793                     "%s        gap        : density=%d (%.2f%%), duration=%d%s\n"
794                     "%s        delay      : round trip=%d%s, end system=%d%s\n"
795                     "%s        level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
796                     "%s        quality    : R factor=%s, ext R factor=%s\n"
797                     "%s                     MOS LQ=%s, MOS CQ=%s\n"
798                     "%s        config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
799                     "%s        JB delay   : cur=%d%s, max=%d%s, abs max=%d%s",
800                     indent,
801                     last_update,
802                     /* pakcets */
803                     indent,
804                     xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
805                     xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
806                     /* burst */
807                     indent,
808                     xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
809                     xr_stat.tx.voip_mtc.burst_dur, "ms",
810                     /* gap */
811                     indent,
812                     xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
813                     xr_stat.tx.voip_mtc.gap_dur, "ms",
814                     /* delay */
815                     indent,
816                     xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
817                     xr_stat.tx.voip_mtc.end_sys_delay, "ms",
818                     /* level */
819                     indent,
820                     signal_lvl, "dB",
821                     noise_lvl, "dB",
822                     rerl, "",
823                     /* quality */
824                     indent,
825                     r_factor, ext_r_factor,
826                     indent,
827                     mos_lq, mos_cq,
828                     /* config */
829                     indent,
830                     plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
831                     /* JB delay */
832                     indent,
833                     xr_stat.tx.voip_mtc.jb_nom, "ms",
834                     xr_stat.tx.voip_mtc.jb_max, "ms",
835                     xr_stat.tx.voip_mtc.jb_abs_max, "ms"
836                     );
837             VALIDATE_PRINT_BUF();
838
839
840             /* RTT delay (by receiver side) */
841             len = pj_ansi_snprintf(p, end-p,
842                     "%s   RTT (from recv)      min     avg     max     last    dev",
843                     indent);
844             VALIDATE_PRINT_BUF();
845             len = pj_ansi_snprintf(p, end-p,
846                     "%s     RTT msec      : %7.3f %7.3f %7.3f %7.3f %7.3f",
847                     indent,
848                     xr_stat.rtt.min / 1000.0,
849                     xr_stat.rtt.mean / 1000.0,
850                     xr_stat.rtt.max / 1000.0,
851                     xr_stat.rtt.last / 1000.0,
852                     pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
853                    );
854             VALIDATE_PRINT_BUF();
855         } /* if audio */;
856 #endif
857
858     }
859 }
860
861
862 /* Print call info */
863 void print_call(const char *title,
864                 int call_id,
865                 char *buf, pj_size_t size)
866 {
867     int len;
868     pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
869     pjsip_dialog *dlg = inv->dlg;
870     char userinfo[128];
871
872     /* Dump invite sesion info. */
873
874     len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
875     if (len < 0)
876         pj_ansi_strcpy(userinfo, "<--uri too long-->");
877     else
878         userinfo[len] = '\0';
879
880     len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
881                            title,
882                            pjsip_inv_state_name(inv->state),
883                            userinfo);
884     if (len < 1 || len >= (int)size) {
885         pj_ansi_strcpy(buf, "<--uri too long-->");
886         len = 18;
887     } else
888         buf[len] = '\0';
889 }
890
891
892 /*
893  * Dump call and media statistics to string.
894  */
895 PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id,
896                                      pj_bool_t with_media,
897                                      char *buffer,
898                                      unsigned maxlen,
899                                      const char *indent)
900 {
901     pjsua_call *call;
902     pjsip_dialog *dlg;
903     pj_time_val duration, res_delay, con_delay;
904     char tmp[128];
905     char *p, *end;
906     pj_status_t status;
907     int len;
908
909     PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
910                      PJ_EINVAL);
911
912     status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
913     if (status != PJ_SUCCESS)
914         return status;
915
916     *buffer = '\0';
917     p = buffer;
918     end = buffer + maxlen;
919     len = 0;
920
921     print_call(indent, call_id, tmp, sizeof(tmp));
922
923     len = pj_ansi_strlen(tmp);
924     pj_ansi_strcpy(buffer, tmp);
925
926     p += len;
927     *p++ = '\r';
928     *p++ = '\n';
929
930     /* Calculate call duration */
931     if (call->conn_time.sec != 0) {
932         pj_gettimeofday(&duration);
933         PJ_TIME_VAL_SUB(duration, call->conn_time);
934         con_delay = call->conn_time;
935         PJ_TIME_VAL_SUB(con_delay, call->start_time);
936     } else {
937         duration.sec = duration.msec = 0;
938         con_delay.sec = con_delay.msec = 0;
939     }
940
941     /* Calculate first response delay */
942     if (call->res_time.sec != 0) {
943         res_delay = call->res_time;
944         PJ_TIME_VAL_SUB(res_delay, call->start_time);
945     } else {
946         res_delay.sec = res_delay.msec = 0;
947     }
948
949     /* Print duration */
950     len = pj_ansi_snprintf(p, end-p,
951                            "%s  Call time: %02dh:%02dm:%02ds, "
952                            "1st res in %d ms, conn in %dms",
953                            indent,
954                            (int)(duration.sec / 3600),
955                            (int)((duration.sec % 3600)/60),
956                            (int)(duration.sec % 60),
957                            (int)PJ_TIME_VAL_MSEC(res_delay),
958                            (int)PJ_TIME_VAL_MSEC(con_delay));
959
960     if (len > 0 && len < end-p) {
961         p += len;
962         *p++ = '\n';
963         *p = '\0';
964     }
965
966     /* Dump session statistics */
967     if (with_media)
968         dump_media_session(indent, p, end-p, call);
969
970     pjsip_dlg_dec_lock(dlg);
971
972     return PJ_SUCCESS;
973 }
974