blob: ee357b3dd1dd4adcdfa11c7571a3077cb3b4a993 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21
22/**
23 * \page page_pjmedia_samples_streamutil_c Samples: Remote Streaming
24 *
25 * This example mainly demonstrates how to stream media file to remote
26 * peer using RTP.
27 *
28 * This file is pjsip-apps/src/samples/streamutil.c
29 *
30 * \includelineno streamutil.c
31 */
32
33#include <pjlib.h>
34#include <pjlib-util.h>
35#include <pjmedia.h>
36#include <pjmedia-codec.h>
37#include <pjmedia/transport_srtp.h>
38
39#include <stdlib.h> /* atoi() */
40#include <stdio.h>
41
42#include "util.h"
43
44
45static const char *desc =
46 " streamutil \n"
47 " \n"
48 " PURPOSE: \n"
49 " Demonstrate how to use pjmedia stream component to transmit/receive \n"
50 " RTP packets to/from sound device. \n"
51 "\n"
52 "\n"
53 " USAGE: \n"
54 " streamutil [options] \n"
55 "\n"
56 "\n"
57 " Options:\n"
58 " --codec=CODEC Set the codec name. \n"
59 " --local-port=PORT Set local RTP port (default=4000) \n"
60 " --remote=IP:PORT Set the remote peer. If this option is set, \n"
61 " the program will transmit RTP audio to the \n"
62 " specified address. (default: recv only) \n"
63 " --play-file=WAV Send audio from the WAV file instead of from \n"
64 " the sound device. \n"
65 " --record-file=WAV Record incoming audio to WAV file instead of \n"
66 " playing it to sound device. \n"
67 " --send-recv Set stream direction to bidirectional. \n"
68 " --send-only Set stream direction to send only \n"
69 " --recv-only Set stream direction to recv only (default) \n"
70
71#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
72 " --use-srtp[=NAME] Enable SRTP with crypto suite NAME \n"
73 " e.g: AES_CM_128_HMAC_SHA1_80 (default), \n"
74 " AES_CM_128_HMAC_SHA1_32 \n"
75 " Use this option along with the TX & RX keys, \n"
76 " formated of 60 hex digits (e.g: E148DA..) \n"
77 " --srtp-tx-key SRTP key for transmiting \n"
78 " --srtp-rx-key SRTP key for receiving \n"
79#endif
80
81 "\n"
82;
83
84
85
86
87#define THIS_FILE "stream.c"
88
89
90
91/* Prototype */
92static void print_stream_stat(pjmedia_stream *stream,
93 const pjmedia_codec_param *codec_param);
94
95/* Prototype for LIBSRTP utility in file datatypes.c */
96int hex_string_to_octet_string(char *raw, char *hex, int len);
97
98/*
99 * Register all codecs.
100 */
101static pj_status_t init_codecs(pjmedia_endpt *med_endpt)
102{
103 return pjmedia_codec_register_audio_codecs(med_endpt, NULL);
104}
105
106
107/*
108 * Create stream based on the codec, dir, remote address, etc.
109 */
110static pj_status_t create_stream( pj_pool_t *pool,
111 pjmedia_endpt *med_endpt,
112 const pjmedia_codec_info *codec_info,
113 pjmedia_dir dir,
114 pj_uint16_t local_port,
115 const pj_sockaddr_in *rem_addr,
116#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
117 pj_bool_t use_srtp,
118 const pj_str_t *crypto_suite,
119 const pj_str_t *srtp_tx_key,
120 const pj_str_t *srtp_rx_key,
121#endif
122 pjmedia_stream **p_stream )
123{
124 pjmedia_stream_info info;
125 pjmedia_transport *transport = NULL;
126 pj_status_t status;
127#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
128 pjmedia_transport *srtp_tp = NULL;
129#endif
130
131
132 /* Reset stream info. */
133 pj_bzero(&info, sizeof(info));
134
135
136 /* Initialize stream info formats */
137 info.type = PJMEDIA_TYPE_AUDIO;
138 info.dir = dir;
139 pj_memcpy(&info.fmt, codec_info, sizeof(pjmedia_codec_info));
140 info.tx_pt = codec_info->pt;
141 info.ssrc = pj_rand();
142
143#if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
144 /* Set default RTCP XR enabled/disabled */
145 info.rtcp_xr_enabled = PJ_TRUE;
146#endif
147
148 /* Copy remote address */
149 pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));
150
151 /* If remote address is not set, set to an arbitrary address
152 * (otherwise stream will assert).
153 */
154 if (info.rem_addr.addr.sa_family == 0) {
155 const pj_str_t addr = pj_str("127.0.0.1");
156 pj_sockaddr_in_init(&info.rem_addr.ipv4, &addr, 0);
157 }
158
159 /* Create media transport */
160 status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,
161 0, &transport);
162 if (status != PJ_SUCCESS)
163 return status;
164
165#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
166 /* Check if SRTP enabled */
167 if (use_srtp) {
168 pjmedia_srtp_crypto tx_plc, rx_plc;
169
170 status = pjmedia_transport_srtp_create(med_endpt, transport,
171 NULL, &srtp_tp);
172 if (status != PJ_SUCCESS)
173 return status;
174
175 pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
176 pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));
177
178 tx_plc.key = *srtp_tx_key;
179 tx_plc.name = *crypto_suite;
180 rx_plc.key = *srtp_rx_key;
181 rx_plc.name = *crypto_suite;
182
183 status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
184 if (status != PJ_SUCCESS)
185 return status;
186
187 transport = srtp_tp;
188 }
189#endif
190
191 /* Now that the stream info is initialized, we can create the
192 * stream.
193 */
194
195 status = pjmedia_stream_create( med_endpt, pool, &info,
196 transport,
197 NULL, p_stream);
198
199 if (status != PJ_SUCCESS) {
200 app_perror(THIS_FILE, "Error creating stream", status);
201 pjmedia_transport_close(transport);
202 return status;
203 }
204
205
206 return PJ_SUCCESS;
207}
208
209
210/*
211 * usage()
212 */
213static void usage()
214{
215 puts(desc);
216}
217
218/*
219 * main()
220 */
221int main(int argc, char *argv[])
222{
223 pj_caching_pool cp;
224 pjmedia_endpt *med_endpt;
225 pj_pool_t *pool;
226 pjmedia_port *rec_file_port = NULL, *play_file_port = NULL;
227 pjmedia_master_port *master_port = NULL;
228 pjmedia_snd_port *snd_port = NULL;
229 pjmedia_stream *stream = NULL;
230 pjmedia_port *stream_port;
231 char tmp[10];
232 pj_status_t status;
233
234#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
235 /* SRTP variables */
236 pj_bool_t use_srtp = PJ_FALSE;
237 char tmp_tx_key[64];
238 char tmp_rx_key[64];
239 pj_str_t srtp_tx_key = {NULL, 0};
240 pj_str_t srtp_rx_key = {NULL, 0};
241 pj_str_t srtp_crypto_suite = {NULL, 0};
242 int tmp_key_len;
243#endif
244
245 /* Default values */
246 const pjmedia_codec_info *codec_info;
247 pjmedia_codec_param codec_param;
248 pjmedia_dir dir = PJMEDIA_DIR_DECODING;
249 pj_sockaddr_in remote_addr;
250 pj_uint16_t local_port = 4000;
251 char *codec_id = NULL;
252 char *rec_file = NULL;
253 char *play_file = NULL;
254
255 enum {
256 OPT_CODEC = 'c',
257 OPT_LOCAL_PORT = 'p',
258 OPT_REMOTE = 'r',
259 OPT_PLAY_FILE = 'w',
260 OPT_RECORD_FILE = 'R',
261 OPT_SEND_RECV = 'b',
262 OPT_SEND_ONLY = 's',
263 OPT_RECV_ONLY = 'i',
264#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
265 OPT_USE_SRTP = 'S',
266#endif
267 OPT_SRTP_TX_KEY = 'x',
268 OPT_SRTP_RX_KEY = 'y',
269 OPT_HELP = 'h',
270 };
271
272 struct pj_getopt_option long_options[] = {
273 { "codec", 1, 0, OPT_CODEC },
274 { "local-port", 1, 0, OPT_LOCAL_PORT },
275 { "remote", 1, 0, OPT_REMOTE },
276 { "play-file", 1, 0, OPT_PLAY_FILE },
277 { "record-file", 1, 0, OPT_RECORD_FILE },
278 { "send-recv", 0, 0, OPT_SEND_RECV },
279 { "send-only", 0, 0, OPT_SEND_ONLY },
280 { "recv-only", 0, 0, OPT_RECV_ONLY },
281#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
282 { "use-srtp", 2, 0, OPT_USE_SRTP },
283 { "srtp-tx-key", 1, 0, OPT_SRTP_TX_KEY },
284 { "srtp-rx-key", 1, 0, OPT_SRTP_RX_KEY },
285#endif
286 { "help", 0, 0, OPT_HELP },
287 { NULL, 0, 0, 0 },
288 };
289
290 int c;
291 int option_index;
292
293
294 pj_bzero(&remote_addr, sizeof(remote_addr));
295
296
297 /* init PJLIB : */
298 status = pj_init();
299 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
300
301
302 /* Parse arguments */
303 pj_optind = 0;
304 while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1) {
305
306 switch (c) {
307 case OPT_CODEC:
308 codec_id = pj_optarg;
309 break;
310
311 case OPT_LOCAL_PORT:
312 local_port = (pj_uint16_t) atoi(pj_optarg);
313 if (local_port < 1) {
314 printf("Error: invalid local port %s\n", pj_optarg);
315 return 1;
316 }
317 break;
318
319 case OPT_REMOTE:
320 {
321 pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
322 pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));
323
324 status = pj_sockaddr_in_init(&remote_addr, &ip, port);
325 if (status != PJ_SUCCESS) {
326 app_perror(THIS_FILE, "Invalid remote address", status);
327 return 1;
328 }
329 }
330 break;
331
332 case OPT_PLAY_FILE:
333 play_file = pj_optarg;
334 break;
335
336 case OPT_RECORD_FILE:
337 rec_file = pj_optarg;
338 break;
339
340 case OPT_SEND_RECV:
341 dir = PJMEDIA_DIR_ENCODING_DECODING;
342 break;
343
344 case OPT_SEND_ONLY:
345 dir = PJMEDIA_DIR_ENCODING;
346 break;
347
348 case OPT_RECV_ONLY:
349 dir = PJMEDIA_DIR_DECODING;
350 break;
351
352#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
353 case OPT_USE_SRTP:
354 use_srtp = PJ_TRUE;
355 if (pj_optarg) {
356 pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg));
357 } else {
358 srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
359 }
360 break;
361
362 case OPT_SRTP_TX_KEY:
363 tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg,
364 (int)strlen(pj_optarg));
365 pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);
366 break;
367
368 case OPT_SRTP_RX_KEY:
369 tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg,
370 (int)strlen(pj_optarg));
371 pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);
372 break;
373#endif
374
375 case OPT_HELP:
376 usage();
377 return 1;
378
379 default:
380 printf("Invalid options %s\n", argv[pj_optind]);
381 return 1;
382 }
383
384 }
385
386
387 /* Verify arguments. */
388 if (dir & PJMEDIA_DIR_ENCODING) {
389 if (remote_addr.sin_addr.s_addr == 0) {
390 printf("Error: remote address must be set\n");
391 return 1;
392 }
393 }
394
395 if (play_file != NULL && dir != PJMEDIA_DIR_ENCODING) {
396 printf("Direction is set to --send-only because of --play-file\n");
397 dir = PJMEDIA_DIR_ENCODING;
398 }
399
400#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
401 /* SRTP validation */
402 if (use_srtp) {
403 if (!srtp_tx_key.slen || !srtp_rx_key.slen)
404 {
405 printf("Error: Key for each SRTP stream direction must be set\n");
406 return 1;
407 }
408 }
409#endif
410
411 /* Must create a pool factory before we can allocate any memory. */
412 pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
413
414 /*
415 * Initialize media endpoint.
416 * This will implicitly initialize PJMEDIA too.
417 */
418 status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
419 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
420
421 /* Create memory pool for application purpose */
422 pool = pj_pool_create( &cp.factory, /* pool factory */
423 "app", /* pool name. */
424 4000, /* init size */
425 4000, /* increment size */
426 NULL /* callback on error */
427 );
428
429
430 /* Register all supported codecs */
431 status = init_codecs(med_endpt);
432 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
433
434
435 /* Find which codec to use. */
436 if (codec_id) {
437 unsigned count = 1;
438 pj_str_t str_codec_id = pj_str(codec_id);
439 pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt);
440 status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr,
441 &str_codec_id, &count,
442 &codec_info, NULL);
443 if (status != PJ_SUCCESS) {
444 printf("Error: unable to find codec %s\n", codec_id);
445 return 1;
446 }
447 } else {
448 /* Default to pcmu */
449 pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt),
450 0, &codec_info);
451 }
452
453 /* Create stream based on program arguments */
454 status = create_stream(pool, med_endpt, codec_info, dir, local_port,
455 &remote_addr,
456#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
457 use_srtp, &srtp_crypto_suite,
458 &srtp_tx_key, &srtp_rx_key,
459#endif
460 &stream);
461 if (status != PJ_SUCCESS)
462 goto on_exit;
463
464 /* Get codec default param for info */
465 status = pjmedia_codec_mgr_get_default_param(
466 pjmedia_endpt_get_codec_mgr(med_endpt),
467 codec_info,
468 &codec_param);
469 /* Should be ok, as create_stream() above succeeded */
470 pj_assert(status == PJ_SUCCESS);
471
472 /* Get the port interface of the stream */
473 status = pjmedia_stream_get_port( stream, &stream_port);
474 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
475
476
477 if (play_file) {
478 unsigned wav_ptime;
479
480 wav_ptime = PJMEDIA_PIA_PTIME(&stream_port->info);
481 status = pjmedia_wav_player_port_create(pool, play_file, wav_ptime,
482 0, -1, &play_file_port);
483 if (status != PJ_SUCCESS) {
484 app_perror(THIS_FILE, "Unable to use file", status);
485 goto on_exit;
486 }
487
488 status = pjmedia_master_port_create(pool, play_file_port, stream_port,
489 0, &master_port);
490 if (status != PJ_SUCCESS) {
491 app_perror(THIS_FILE, "Unable to create master port", status);
492 goto on_exit;
493 }
494
495 status = pjmedia_master_port_start(master_port);
496 if (status != PJ_SUCCESS) {
497 app_perror(THIS_FILE, "Error starting master port", status);
498 goto on_exit;
499 }
500
501 printf("Playing from WAV file %s..\n", play_file);
502
503 } else if (rec_file) {
504
505 status = pjmedia_wav_writer_port_create(pool, rec_file,
506 PJMEDIA_PIA_SRATE(&stream_port->info),
507 PJMEDIA_PIA_CCNT(&stream_port->info),
508 PJMEDIA_PIA_SPF(&stream_port->info),
509 PJMEDIA_PIA_BITS(&stream_port->info),
510 0, 0, &rec_file_port);
511 if (status != PJ_SUCCESS) {
512 app_perror(THIS_FILE, "Unable to use file", status);
513 goto on_exit;
514 }
515
516 status = pjmedia_master_port_create(pool, stream_port, rec_file_port,
517 0, &master_port);
518 if (status != PJ_SUCCESS) {
519 app_perror(THIS_FILE, "Unable to create master port", status);
520 goto on_exit;
521 }
522
523 status = pjmedia_master_port_start(master_port);
524 if (status != PJ_SUCCESS) {
525 app_perror(THIS_FILE, "Error starting master port", status);
526 goto on_exit;
527 }
528
529 printf("Recording to WAV file %s..\n", rec_file);
530
531 } else {
532
533 /* Create sound device port. */
534 if (dir == PJMEDIA_DIR_ENCODING_DECODING)
535 status = pjmedia_snd_port_create(pool, -1, -1,
536 PJMEDIA_PIA_SRATE(&stream_port->info),
537 PJMEDIA_PIA_CCNT(&stream_port->info),
538 PJMEDIA_PIA_SPF(&stream_port->info),
539 PJMEDIA_PIA_BITS(&stream_port->info),
540 0, &snd_port);
541 else if (dir == PJMEDIA_DIR_ENCODING)
542 status = pjmedia_snd_port_create_rec(pool, -1,
543 PJMEDIA_PIA_SRATE(&stream_port->info),
544 PJMEDIA_PIA_CCNT(&stream_port->info),
545 PJMEDIA_PIA_SPF(&stream_port->info),
546 PJMEDIA_PIA_BITS(&stream_port->info),
547 0, &snd_port);
548 else
549 status = pjmedia_snd_port_create_player(pool, -1,
550 PJMEDIA_PIA_SRATE(&stream_port->info),
551 PJMEDIA_PIA_CCNT(&stream_port->info),
552 PJMEDIA_PIA_SPF(&stream_port->info),
553 PJMEDIA_PIA_BITS(&stream_port->info),
554 0, &snd_port);
555
556
557 if (status != PJ_SUCCESS) {
558 app_perror(THIS_FILE, "Unable to create sound port", status);
559 goto on_exit;
560 }
561
562 /* Connect sound port to stream */
563 status = pjmedia_snd_port_connect( snd_port, stream_port );
564 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
565
566 }
567
568 /* Start streaming */
569 pjmedia_stream_start(stream);
570
571
572 /* Done */
573
574 if (dir == PJMEDIA_DIR_DECODING)
575 printf("Stream is active, dir is recv-only, local port is %d\n",
576 local_port);
577 else if (dir == PJMEDIA_DIR_ENCODING)
578 printf("Stream is active, dir is send-only, sending to %s:%d\n",
579 pj_inet_ntoa(remote_addr.sin_addr),
580 pj_ntohs(remote_addr.sin_port));
581 else
582 printf("Stream is active, send/recv, local port is %d, "
583 "sending to %s:%d\n",
584 local_port,
585 pj_inet_ntoa(remote_addr.sin_addr),
586 pj_ntohs(remote_addr.sin_port));
587
588
589 for (;;) {
590
591 puts("");
592 puts("Commands:");
593 puts(" s Display media statistics");
594 puts(" q Quit");
595 puts("");
596
597 printf("Command: "); fflush(stdout);
598
599 if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
600 puts("EOF while reading stdin, will quit now..");
601 break;
602 }
603
604 if (tmp[0] == 's')
605 print_stream_stat(stream, &codec_param);
606 else if (tmp[0] == 'q')
607 break;
608
609 }
610
611
612
613 /* Start deinitialization: */
614on_exit:
615
616 /* Destroy sound device */
617 if (snd_port) {
618 pjmedia_snd_port_destroy( snd_port );
619 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
620 }
621
622 /* If there is master port, then we just need to destroy master port
623 * (it will recursively destroy upstream and downstream ports, which
624 * in this case are file_port and stream_port).
625 */
626 if (master_port) {
627 pjmedia_master_port_destroy(master_port, PJ_TRUE);
628 play_file_port = NULL;
629 stream = NULL;
630 }
631
632 /* Destroy stream */
633 if (stream) {
634 pjmedia_transport *tp;
635
636 tp = pjmedia_stream_get_transport(stream);
637 pjmedia_stream_destroy(stream);
638
639 pjmedia_transport_close(tp);
640 }
641
642 /* Destroy file ports */
643 if (play_file_port)
644 pjmedia_port_destroy( play_file_port );
645 if (rec_file_port)
646 pjmedia_port_destroy( rec_file_port );
647
648
649 /* Release application pool */
650 pj_pool_release( pool );
651
652 /* Destroy media endpoint. */
653 pjmedia_endpt_destroy( med_endpt );
654
655 /* Destroy pool factory */
656 pj_caching_pool_destroy( &cp );
657
658 /* Shutdown PJLIB */
659 pj_shutdown();
660
661
662 return (status == PJ_SUCCESS) ? 0 : 1;
663}
664
665
666
667
668static const char *good_number(char *buf, pj_int32_t val)
669{
670 if (val < 1000) {
671 pj_ansi_sprintf(buf, "%d", val);
672 } else if (val < 1000000) {
673 pj_ansi_sprintf(buf, "%d.%dK",
674 val / 1000,
675 (val % 1000) / 100);
676 } else {
677 pj_ansi_sprintf(buf, "%d.%02dM",
678 val / 1000000,
679 (val % 1000000) / 10000);
680 }
681
682 return buf;
683}
684
685
686#define SAMPLES_TO_USEC(usec, samples, clock_rate) \
687 do { \
688 if (samples <= 4294) \
689 usec = samples * 1000000 / clock_rate; \
690 else { \
691 usec = samples * 1000 / clock_rate; \
692 usec *= 1000; \
693 } \
694 } while(0)
695
696#define PRINT_VOIP_MTC_VAL(s, v) \
697 if (v == 127) \
698 sprintf(s, "(na)"); \
699 else \
700 sprintf(s, "%d", v)
701
702
703/*
704 * Print stream statistics
705 */
706static void print_stream_stat(pjmedia_stream *stream,
707 const pjmedia_codec_param *codec_param)
708{
709 char duration[80], last_update[80];
710 char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
711 pjmedia_port *port;
712 pjmedia_rtcp_stat stat;
713 pj_time_val now;
714
715
716 pj_gettimeofday(&now);
717 pjmedia_stream_get_stat(stream, &stat);
718 pjmedia_stream_get_port(stream, &port);
719
720 puts("Stream statistics:");
721
722 /* Print duration */
723 PJ_TIME_VAL_SUB(now, stat.start);
724 sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld",
725 now.sec / 3600,
726 (now.sec % 3600) / 60,
727 (now.sec % 60),
728 now.msec);
729
730
731 printf(" Info: audio %dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
732 PJMEDIA_PIA_SRATE(&port->info),
733 PJMEDIA_PIA_PTIME(&port->info),
734 good_number(bps, (codec_param->info.avg_bps+7)/8),
735 good_number(ipbps, ((codec_param->info.avg_bps+7)/8) +
736 (40 * 1000 /
737 codec_param->setting.frm_per_pkt /
738 codec_param->info.frm_ptime)));
739
740 if (stat.rx.update_cnt == 0)
741 strcpy(last_update, "never");
742 else {
743 pj_gettimeofday(&now);
744 PJ_TIME_VAL_SUB(now, stat.rx.update);
745 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
746 now.sec / 3600,
747 (now.sec % 3600) / 60,
748 now.sec % 60,
749 now.msec);
750 }
751
752 printf(" RX stat last update: %s\n"
753 " total %s packets %sB received (%sB +IP hdr)%s\n"
754 " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
755 " (msec) min avg max last dev\n"
756 " loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
757 " jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
758 last_update,
759 good_number(packets, stat.rx.pkt),
760 good_number(bytes, stat.rx.bytes),
761 good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
762 "",
763 stat.rx.loss,
764 stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss),
765 stat.rx.dup,
766 stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss),
767 stat.rx.reorder,
768 stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss),
769 "",
770 stat.rx.loss_period.min / 1000.0,
771 stat.rx.loss_period.mean / 1000.0,
772 stat.rx.loss_period.max / 1000.0,
773 stat.rx.loss_period.last / 1000.0,
774 pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0,
775 "",
776 stat.rx.jitter.min / 1000.0,
777 stat.rx.jitter.mean / 1000.0,
778 stat.rx.jitter.max / 1000.0,
779 stat.rx.jitter.last / 1000.0,
780 pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0,
781 ""
782 );
783
784
785 if (stat.tx.update_cnt == 0)
786 strcpy(last_update, "never");
787 else {
788 pj_gettimeofday(&now);
789 PJ_TIME_VAL_SUB(now, stat.tx.update);
790 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
791 now.sec / 3600,
792 (now.sec % 3600) / 60,
793 now.sec % 60,
794 now.msec);
795 }
796
797 printf(" TX stat last update: %s\n"
798 " total %s packets %sB sent (%sB +IP hdr)%s\n"
799 " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
800 " (msec) min avg max last dev\n"
801 " loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
802 " jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
803 last_update,
804 good_number(packets, stat.tx.pkt),
805 good_number(bytes, stat.tx.bytes),
806 good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
807 "",
808 stat.tx.loss,
809 stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss),
810 stat.tx.dup,
811 stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss),
812 stat.tx.reorder,
813 stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss),
814 "",
815 stat.tx.loss_period.min / 1000.0,
816 stat.tx.loss_period.mean / 1000.0,
817 stat.tx.loss_period.max / 1000.0,
818 stat.tx.loss_period.last / 1000.0,
819 pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0,
820 "",
821 stat.tx.jitter.min / 1000.0,
822 stat.tx.jitter.mean / 1000.0,
823 stat.tx.jitter.max / 1000.0,
824 stat.tx.jitter.last / 1000.0,
825 pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0,
826 ""
827 );
828
829
830 printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
831 stat.rtt.min / 1000.0,
832 stat.rtt.mean / 1000.0,
833 stat.rtt.max / 1000.0,
834 stat.rtt.last / 1000.0,
835 pj_math_stat_get_stddev(&stat.rtt) / 1000.0,
836 ""
837 );
838
839#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
840 /* RTCP XR Reports */
841 do {
842 char loss[16], dup[16];
843 char jitter[80];
844 char toh[80];
845 char plc[16], jba[16], jbr[16];
846 char signal_lvl[16], noise_lvl[16], rerl[16];
847 char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
848 pjmedia_rtcp_xr_stat xr_stat;
849
850 if (pjmedia_stream_get_stat_xr(stream, &xr_stat) != PJ_SUCCESS)
851 break;
852
853 puts("\nExtended reports:");
854
855 /* Statistics Summary */
856 puts(" Statistics Summary");
857
858 if (xr_stat.rx.stat_sum.l)
859 sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
860 else
861 sprintf(loss, "(na)");
862
863 if (xr_stat.rx.stat_sum.d)
864 sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
865 else
866 sprintf(dup, "(na)");
867
868 if (xr_stat.rx.stat_sum.j) {
869 unsigned jmin, jmax, jmean, jdev;
870
871 SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
872 port->info.fmt.det.aud.clock_rate);
873 SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
874 port->info.fmt.det.aud.clock_rate);
875 SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
876 port->info.fmt.det.aud.clock_rate);
877 SAMPLES_TO_USEC(jdev,
878 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
879 port->info.fmt.det.aud.clock_rate);
880 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
881 jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
882 } else
883 sprintf(jitter, "(report not available)");
884
885 if (xr_stat.rx.stat_sum.t) {
886 sprintf(toh, "%11d %11d %11d %11d",
887 xr_stat.rx.stat_sum.toh.min,
888 xr_stat.rx.stat_sum.toh.mean,
889 xr_stat.rx.stat_sum.toh.max,
890 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
891 } else
892 sprintf(toh, "(report not available)");
893
894 if (xr_stat.rx.stat_sum.update.sec == 0)
895 strcpy(last_update, "never");
896 else {
897 pj_gettimeofday(&now);
898 PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
899 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
900 now.sec / 3600,
901 (now.sec % 3600) / 60,
902 now.sec % 60,
903 now.msec);
904 }
905
906 printf(" RX last update: %s\n"
907 " begin seq=%d, end seq=%d%s\n"
908 " pkt loss=%s, dup=%s%s\n"
909 " (msec) min avg max dev\n"
910 " jitter : %s\n"
911 " toh : %s\n",
912 last_update,
913 xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
914 "",
915 loss, dup,
916 "",
917 jitter,
918 toh
919 );
920
921 if (xr_stat.tx.stat_sum.l)
922 sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
923 else
924 sprintf(loss, "(na)");
925
926 if (xr_stat.tx.stat_sum.d)
927 sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
928 else
929 sprintf(dup, "(na)");
930
931 if (xr_stat.tx.stat_sum.j) {
932 unsigned jmin, jmax, jmean, jdev;
933
934 SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
935 port->info.fmt.det.aud.clock_rate);
936 SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
937 port->info.fmt.det.aud.clock_rate);
938 SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
939 port->info.fmt.det.aud.clock_rate);
940 SAMPLES_TO_USEC(jdev,
941 pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
942 port->info.fmt.det.aud.clock_rate);
943 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
944 jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
945 } else
946 sprintf(jitter, "(report not available)");
947
948 if (xr_stat.tx.stat_sum.t) {
949 sprintf(toh, "%11d %11d %11d %11d",
950 xr_stat.tx.stat_sum.toh.min,
951 xr_stat.tx.stat_sum.toh.mean,
952 xr_stat.tx.stat_sum.toh.max,
953 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
954 } else
955 sprintf(toh, "(report not available)");
956
957 if (xr_stat.tx.stat_sum.update.sec == 0)
958 strcpy(last_update, "never");
959 else {
960 pj_gettimeofday(&now);
961 PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
962 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
963 now.sec / 3600,
964 (now.sec % 3600) / 60,
965 now.sec % 60,
966 now.msec);
967 }
968
969 printf(" TX last update: %s\n"
970 " begin seq=%d, end seq=%d%s\n"
971 " pkt loss=%s, dup=%s%s\n"
972 " (msec) min avg max dev\n"
973 " jitter : %s\n"
974 " toh : %s\n",
975 last_update,
976 xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
977 "",
978 loss, dup,
979 "",
980 jitter,
981 toh
982 );
983
984 /* VoIP Metrics */
985 puts(" VoIP Metrics");
986
987 PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
988 PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
989 PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
990 PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
991 PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
992 PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
993 PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
994
995 switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
996 case PJMEDIA_RTCP_XR_PLC_DIS:
997 sprintf(plc, "DISABLED");
998 break;
999 case PJMEDIA_RTCP_XR_PLC_ENH:
1000 sprintf(plc, "ENHANCED");
1001 break;
1002 case PJMEDIA_RTCP_XR_PLC_STD:
1003 sprintf(plc, "STANDARD");
1004 break;
1005 case PJMEDIA_RTCP_XR_PLC_UNK:
1006 default:
1007 sprintf(plc, "UNKNOWN");
1008 break;
1009 }
1010
1011 switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
1012 case PJMEDIA_RTCP_XR_JB_FIXED:
1013 sprintf(jba, "FIXED");
1014 break;
1015 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
1016 sprintf(jba, "ADAPTIVE");
1017 break;
1018 default:
1019 sprintf(jba, "UNKNOWN");
1020 break;
1021 }
1022
1023 sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
1024
1025 if (xr_stat.rx.voip_mtc.update.sec == 0)
1026 strcpy(last_update, "never");
1027 else {
1028 pj_gettimeofday(&now);
1029 PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
1030 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
1031 now.sec / 3600,
1032 (now.sec % 3600) / 60,
1033 now.sec % 60,
1034 now.msec);
1035 }
1036
1037 printf(" RX last update: %s\n"
1038 " packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
1039 " burst : density=%d (%.2f%%), duration=%d%s\n"
1040 " gap : density=%d (%.2f%%), duration=%d%s\n"
1041 " delay : round trip=%d%s, end system=%d%s\n"
1042 " level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
1043 " quality : R factor=%s, ext R factor=%s\n"
1044 " MOS LQ=%s, MOS CQ=%s\n"
1045 " config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
1046 " JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
1047 last_update,
1048 /* pakcets */
1049 xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
1050 xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
1051 /* burst */
1052 xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
1053 xr_stat.rx.voip_mtc.burst_dur, "ms",
1054 /* gap */
1055 xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
1056 xr_stat.rx.voip_mtc.gap_dur, "ms",
1057 /* delay */
1058 xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
1059 xr_stat.rx.voip_mtc.end_sys_delay, "ms",
1060 /* level */
1061 signal_lvl, "dB",
1062 noise_lvl, "dB",
1063 rerl, "",
1064 /* quality */
1065 r_factor, ext_r_factor, mos_lq, mos_cq,
1066 /* config */
1067 plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
1068 /* JB delay */
1069 xr_stat.rx.voip_mtc.jb_nom, "ms",
1070 xr_stat.rx.voip_mtc.jb_max, "ms",
1071 xr_stat.rx.voip_mtc.jb_abs_max, "ms"
1072 );
1073
1074 PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
1075 PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
1076 PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
1077 PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
1078 PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
1079 PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
1080 PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
1081
1082 switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
1083 case PJMEDIA_RTCP_XR_PLC_DIS:
1084 sprintf(plc, "DISABLED");
1085 break;
1086 case PJMEDIA_RTCP_XR_PLC_ENH:
1087 sprintf(plc, "ENHANCED");
1088 break;
1089 case PJMEDIA_RTCP_XR_PLC_STD:
1090 sprintf(plc, "STANDARD");
1091 break;
1092 case PJMEDIA_RTCP_XR_PLC_UNK:
1093 default:
1094 sprintf(plc, "unknown");
1095 break;
1096 }
1097
1098 switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
1099 case PJMEDIA_RTCP_XR_JB_FIXED:
1100 sprintf(jba, "FIXED");
1101 break;
1102 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
1103 sprintf(jba, "ADAPTIVE");
1104 break;
1105 default:
1106 sprintf(jba, "unknown");
1107 break;
1108 }
1109
1110 sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
1111
1112 if (xr_stat.tx.voip_mtc.update.sec == 0)
1113 strcpy(last_update, "never");
1114 else {
1115 pj_gettimeofday(&now);
1116 PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
1117 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
1118 now.sec / 3600,
1119 (now.sec % 3600) / 60,
1120 now.sec % 60,
1121 now.msec);
1122 }
1123
1124 printf(" TX last update: %s\n"
1125 " packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
1126 " burst : density=%d (%.2f%%), duration=%d%s\n"
1127 " gap : density=%d (%.2f%%), duration=%d%s\n"
1128 " delay : round trip=%d%s, end system=%d%s\n"
1129 " level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
1130 " quality : R factor=%s, ext R factor=%s\n"
1131 " MOS LQ=%s, MOS CQ=%s\n"
1132 " config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
1133 " JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
1134 last_update,
1135 /* pakcets */
1136 xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
1137 xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
1138 /* burst */
1139 xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
1140 xr_stat.tx.voip_mtc.burst_dur, "ms",
1141 /* gap */
1142 xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
1143 xr_stat.tx.voip_mtc.gap_dur, "ms",
1144 /* delay */
1145 xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
1146 xr_stat.tx.voip_mtc.end_sys_delay, "ms",
1147 /* level */
1148 signal_lvl, "dB",
1149 noise_lvl, "dB",
1150 rerl, "",
1151 /* quality */
1152 r_factor, ext_r_factor, mos_lq, mos_cq,
1153 /* config */
1154 plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
1155 /* JB delay */
1156 xr_stat.tx.voip_mtc.jb_nom, "ms",
1157 xr_stat.tx.voip_mtc.jb_max, "ms",
1158 xr_stat.tx.voip_mtc.jb_abs_max, "ms"
1159 );
1160
1161
1162 /* RTT delay (by receiver side) */
1163 printf(" (msec) min avg max last dev\n");
1164 printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
1165 xr_stat.rtt.min / 1000.0,
1166 xr_stat.rtt.mean / 1000.0,
1167 xr_stat.rtt.max / 1000.0,
1168 xr_stat.rtt.last / 1000.0,
1169 pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0,
1170 ""
1171 );
1172 } while (0);
1173#endif /* PJMEDIA_HAS_RTCP_XR */
1174
1175}
1176