1
/* $Id: streamutil.c 3816 2011-10-14 04:15:15Z bennylp $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
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.
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.
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
23
* \page page_pjmedia_samples_streamutil_c Samples: Remote Streaming
25
* This example mainly demonstrates how to stream media file to remote
28
* This file is pjsip-apps/src/samples/streamutil.c
30
* \includelineno streamutil.c
34
#include <pjlib-util.h>
36
#include <pjmedia-codec.h>
37
#include <pjmedia/transport_srtp.h>
39
#include <stdlib.h> /* atoi() */
45
static const char *desc =
49
" Demonstrate how to use pjmedia stream component to transmit/receive \n"
50
" RTP packets to/from sound device. \n"
54
" streamutil [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"
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"
87
#define THIS_FILE "stream.c"
92
static void print_stream_stat(pjmedia_stream *stream,
93
const pjmedia_codec_param *codec_param);
95
/* Prototype for LIBSRTP utility in file datatypes.c */
96
int hex_string_to_octet_string(char *raw, char *hex, int len);
99
* Register all codecs.
101
static pj_status_t init_codecs(pjmedia_endpt *med_endpt)
105
/* To suppress warning about unused var when all codecs are disabled */
106
PJ_UNUSED_ARG(status);
108
#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
109
status = pjmedia_codec_g711_init(med_endpt);
110
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
113
#if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC!=0
114
status = pjmedia_codec_gsm_init(med_endpt);
115
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
118
#if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0
119
status = pjmedia_codec_speex_init(med_endpt, 0, -1, -1);
120
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
123
#if defined(PJMEDIA_HAS_G722_CODEC) && PJMEDIA_HAS_G722_CODEC!=0
124
status = pjmedia_codec_g722_init(med_endpt);
125
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
128
#if defined(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC) && PJMEDIA_HAS_OPENCORE_AMRNB_CODEC!=0
129
status = pjmedia_codec_opencore_amrnb_init(med_endpt);
130
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
133
#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0
134
status = pjmedia_codec_l16_init(med_endpt, 0);
135
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
143
* Create stream based on the codec, dir, remote address, etc.
145
static pj_status_t create_stream( pj_pool_t *pool,
146
pjmedia_endpt *med_endpt,
147
const pjmedia_codec_info *codec_info,
149
pj_uint16_t local_port,
150
const pj_sockaddr_in *rem_addr,
151
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
153
const pj_str_t *crypto_suite,
154
const pj_str_t *srtp_tx_key,
155
const pj_str_t *srtp_rx_key,
157
pjmedia_stream **p_stream )
159
pjmedia_stream_info info;
160
pjmedia_transport *transport = NULL;
162
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
163
pjmedia_transport *srtp_tp = NULL;
167
/* Reset stream info. */
168
pj_bzero(&info, sizeof(info));
171
/* Initialize stream info formats */
172
info.type = PJMEDIA_TYPE_AUDIO;
174
pj_memcpy(&info.fmt, codec_info, sizeof(pjmedia_codec_info));
175
info.tx_pt = codec_info->pt;
176
info.ssrc = pj_rand();
178
#if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
179
/* Set default RTCP XR enabled/disabled */
180
info.rtcp_xr_enabled = PJ_TRUE;
183
/* Copy remote address */
184
pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));
186
/* If remote address is not set, set to an arbitrary address
187
* (otherwise stream will assert).
189
if (info.rem_addr.addr.sa_family == 0) {
190
const pj_str_t addr = pj_str("127.0.0.1");
191
pj_sockaddr_in_init(&info.rem_addr.ipv4, &addr, 0);
194
/* Create media transport */
195
status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,
197
if (status != PJ_SUCCESS)
200
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
201
/* Check if SRTP enabled */
203
pjmedia_srtp_crypto tx_plc, rx_plc;
205
status = pjmedia_transport_srtp_create(med_endpt, transport,
207
if (status != PJ_SUCCESS)
210
pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
211
pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));
213
tx_plc.key = *srtp_tx_key;
214
tx_plc.name = *crypto_suite;
215
rx_plc.key = *srtp_rx_key;
216
rx_plc.name = *crypto_suite;
218
status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
219
if (status != PJ_SUCCESS)
226
/* Now that the stream info is initialized, we can create the
230
status = pjmedia_stream_create( med_endpt, pool, &info,
234
if (status != PJ_SUCCESS) {
235
app_perror(THIS_FILE, "Error creating stream", status);
236
pjmedia_transport_close(transport);
256
int main(int argc, char *argv[])
259
pjmedia_endpt *med_endpt;
261
pjmedia_port *rec_file_port = NULL, *play_file_port = NULL;
262
pjmedia_master_port *master_port = NULL;
263
pjmedia_snd_port *snd_port = NULL;
264
pjmedia_stream *stream = NULL;
265
pjmedia_port *stream_port;
269
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
271
pj_bool_t use_srtp = PJ_FALSE;
274
pj_str_t srtp_tx_key = {NULL, 0};
275
pj_str_t srtp_rx_key = {NULL, 0};
276
pj_str_t srtp_crypto_suite = {NULL, 0};
281
const pjmedia_codec_info *codec_info;
282
pjmedia_codec_param codec_param;
283
pjmedia_dir dir = PJMEDIA_DIR_DECODING;
284
pj_sockaddr_in remote_addr;
285
pj_uint16_t local_port = 4000;
286
char *codec_id = NULL;
287
char *rec_file = NULL;
288
char *play_file = NULL;
292
OPT_LOCAL_PORT = 'p',
295
OPT_RECORD_FILE = 'R',
299
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
302
OPT_SRTP_TX_KEY = 'x',
303
OPT_SRTP_RX_KEY = 'y',
307
struct pj_getopt_option long_options[] = {
308
{ "codec", 1, 0, OPT_CODEC },
309
{ "local-port", 1, 0, OPT_LOCAL_PORT },
310
{ "remote", 1, 0, OPT_REMOTE },
311
{ "play-file", 1, 0, OPT_PLAY_FILE },
312
{ "record-file", 1, 0, OPT_RECORD_FILE },
313
{ "send-recv", 0, 0, OPT_SEND_RECV },
314
{ "send-only", 0, 0, OPT_SEND_ONLY },
315
{ "recv-only", 0, 0, OPT_RECV_ONLY },
316
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
317
{ "use-srtp", 2, 0, OPT_USE_SRTP },
318
{ "srtp-tx-key", 1, 0, OPT_SRTP_TX_KEY },
319
{ "srtp-rx-key", 1, 0, OPT_SRTP_RX_KEY },
321
{ "help", 0, 0, OPT_HELP },
329
pj_bzero(&remote_addr, sizeof(remote_addr));
334
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
337
/* Parse arguments */
339
while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1) {
343
codec_id = pj_optarg;
347
local_port = (pj_uint16_t) atoi(pj_optarg);
348
if (local_port < 1) {
349
printf("Error: invalid local port %s\n", pj_optarg);
356
pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
357
pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));
359
status = pj_sockaddr_in_init(&remote_addr, &ip, port);
360
if (status != PJ_SUCCESS) {
361
app_perror(THIS_FILE, "Invalid remote address", status);
368
play_file = pj_optarg;
371
case OPT_RECORD_FILE:
372
rec_file = pj_optarg;
376
dir = PJMEDIA_DIR_ENCODING_DECODING;
380
dir = PJMEDIA_DIR_ENCODING;
384
dir = PJMEDIA_DIR_DECODING;
387
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
391
pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg));
393
srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
397
case OPT_SRTP_TX_KEY:
398
tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg, strlen(pj_optarg));
399
pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);
402
case OPT_SRTP_RX_KEY:
403
tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg, strlen(pj_optarg));
404
pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);
413
printf("Invalid options %s\n", argv[pj_optind]);
420
/* Verify arguments. */
421
if (dir & PJMEDIA_DIR_ENCODING) {
422
if (remote_addr.sin_addr.s_addr == 0) {
423
printf("Error: remote address must be set\n");
428
if (play_file != NULL && dir != PJMEDIA_DIR_ENCODING) {
429
printf("Direction is set to --send-only because of --play-file\n");
430
dir = PJMEDIA_DIR_ENCODING;
433
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
434
/* SRTP validation */
436
if (!srtp_tx_key.slen || !srtp_rx_key.slen)
438
printf("Error: Key for each SRTP stream direction must be set\n");
444
/* Must create a pool factory before we can allocate any memory. */
445
pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
448
* Initialize media endpoint.
449
* This will implicitly initialize PJMEDIA too.
451
status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
452
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
454
/* Create memory pool for application purpose */
455
pool = pj_pool_create( &cp.factory, /* pool factory */
456
"app", /* pool name. */
457
4000, /* init size */
458
4000, /* increment size */
459
NULL /* callback on error */
463
/* Register all supported codecs */
464
status = init_codecs(med_endpt);
465
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
468
/* Find which codec to use. */
471
pj_str_t str_codec_id = pj_str(codec_id);
472
pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt);
473
status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr,
474
&str_codec_id, &count,
476
if (status != PJ_SUCCESS) {
477
printf("Error: unable to find codec %s\n", codec_id);
481
/* Default to pcmu */
482
pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt),
486
/* Create stream based on program arguments */
487
status = create_stream(pool, med_endpt, codec_info, dir, local_port,
489
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
490
use_srtp, &srtp_crypto_suite,
491
&srtp_tx_key, &srtp_rx_key,
494
if (status != PJ_SUCCESS)
497
/* Get codec default param for info */
498
status = pjmedia_codec_mgr_get_default_param(
499
pjmedia_endpt_get_codec_mgr(med_endpt),
502
/* Should be ok, as create_stream() above succeeded */
503
pj_assert(status == PJ_SUCCESS);
505
/* Get the port interface of the stream */
506
status = pjmedia_stream_get_port( stream, &stream_port);
507
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
513
wav_ptime = stream_port->info.samples_per_frame * 1000 /
514
stream_port->info.clock_rate;
515
status = pjmedia_wav_player_port_create(pool, play_file, wav_ptime,
516
0, -1, &play_file_port);
517
if (status != PJ_SUCCESS) {
518
app_perror(THIS_FILE, "Unable to use file", status);
522
status = pjmedia_master_port_create(pool, play_file_port, stream_port,
524
if (status != PJ_SUCCESS) {
525
app_perror(THIS_FILE, "Unable to create master port", status);
529
status = pjmedia_master_port_start(master_port);
530
if (status != PJ_SUCCESS) {
531
app_perror(THIS_FILE, "Error starting master port", status);
535
printf("Playing from WAV file %s..\n", play_file);
537
} else if (rec_file) {
539
status = pjmedia_wav_writer_port_create(pool, rec_file,
540
stream_port->info.clock_rate,
541
stream_port->info.channel_count,
542
stream_port->info.samples_per_frame,
543
stream_port->info.bits_per_sample,
544
0, 0, &rec_file_port);
545
if (status != PJ_SUCCESS) {
546
app_perror(THIS_FILE, "Unable to use file", status);
550
status = pjmedia_master_port_create(pool, stream_port, rec_file_port,
552
if (status != PJ_SUCCESS) {
553
app_perror(THIS_FILE, "Unable to create master port", status);
557
status = pjmedia_master_port_start(master_port);
558
if (status != PJ_SUCCESS) {
559
app_perror(THIS_FILE, "Error starting master port", status);
563
printf("Recording to WAV file %s..\n", rec_file);
567
/* Create sound device port. */
568
if (dir == PJMEDIA_DIR_ENCODING_DECODING)
569
status = pjmedia_snd_port_create(pool, -1, -1,
570
stream_port->info.clock_rate,
571
stream_port->info.channel_count,
572
stream_port->info.samples_per_frame,
573
stream_port->info.bits_per_sample,
575
else if (dir == PJMEDIA_DIR_ENCODING)
576
status = pjmedia_snd_port_create_rec(pool, -1,
577
stream_port->info.clock_rate,
578
stream_port->info.channel_count,
579
stream_port->info.samples_per_frame,
580
stream_port->info.bits_per_sample,
583
status = pjmedia_snd_port_create_player(pool, -1,
584
stream_port->info.clock_rate,
585
stream_port->info.channel_count,
586
stream_port->info.samples_per_frame,
587
stream_port->info.bits_per_sample,
591
if (status != PJ_SUCCESS) {
592
app_perror(THIS_FILE, "Unable to create sound port", status);
596
/* Connect sound port to stream */
597
status = pjmedia_snd_port_connect( snd_port, stream_port );
598
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
602
/* Start streaming */
603
pjmedia_stream_start(stream);
608
if (dir == PJMEDIA_DIR_DECODING)
609
printf("Stream is active, dir is recv-only, local port is %d\n",
611
else if (dir == PJMEDIA_DIR_ENCODING)
612
printf("Stream is active, dir is send-only, sending to %s:%d\n",
613
pj_inet_ntoa(remote_addr.sin_addr),
614
pj_ntohs(remote_addr.sin_port));
616
printf("Stream is active, send/recv, local port is %d, "
617
"sending to %s:%d\n",
619
pj_inet_ntoa(remote_addr.sin_addr),
620
pj_ntohs(remote_addr.sin_port));
627
puts(" s Display media statistics");
631
printf("Command: "); fflush(stdout);
633
if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
634
puts("EOF while reading stdin, will quit now..");
639
print_stream_stat(stream, &codec_param);
640
else if (tmp[0] == 'q')
647
/* Start deinitialization: */
650
/* Destroy sound device */
652
pjmedia_snd_port_destroy( snd_port );
653
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
656
/* If there is master port, then we just need to destroy master port
657
* (it will recursively destroy upstream and downstream ports, which
658
* in this case are file_port and stream_port).
661
pjmedia_master_port_destroy(master_port, PJ_TRUE);
662
play_file_port = NULL;
668
pjmedia_transport *tp;
670
tp = pjmedia_stream_get_transport(stream);
671
pjmedia_stream_destroy(stream);
673
pjmedia_transport_close(tp);
676
/* Destroy file ports */
678
pjmedia_port_destroy( play_file_port );
680
pjmedia_port_destroy( rec_file_port );
683
/* Release application pool */
684
pj_pool_release( pool );
686
/* Destroy media endpoint. */
687
pjmedia_endpt_destroy( med_endpt );
689
/* Destroy pool factory */
690
pj_caching_pool_destroy( &cp );
696
return (status == PJ_SUCCESS) ? 0 : 1;
702
static const char *good_number(char *buf, pj_int32_t val)
705
pj_ansi_sprintf(buf, "%d", val);
706
} else if (val < 1000000) {
707
pj_ansi_sprintf(buf, "%d.%dK",
711
pj_ansi_sprintf(buf, "%d.%02dM",
713
(val % 1000000) / 10000);
720
#define SAMPLES_TO_USEC(usec, samples, clock_rate) \
722
if (samples <= 4294) \
723
usec = samples * 1000000 / clock_rate; \
725
usec = samples * 1000 / clock_rate; \
730
#define PRINT_VOIP_MTC_VAL(s, v) \
732
sprintf(s, "(na)"); \
738
* Print stream statistics
740
static void print_stream_stat(pjmedia_stream *stream,
741
const pjmedia_codec_param *codec_param)
743
char duration[80], last_update[80];
744
char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
746
pjmedia_rtcp_stat stat;
750
pj_gettimeofday(&now);
751
pjmedia_stream_get_stat(stream, &stat);
752
pjmedia_stream_get_port(stream, &port);
754
puts("Stream statistics:");
757
PJ_TIME_VAL_SUB(now, stat.start);
758
sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld",
760
(now.sec % 3600) / 60,
765
printf(" Info: audio %.*s@%dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
766
(int)port->info.encoding_name.slen,
767
port->info.encoding_name.ptr,
768
port->info.clock_rate,
769
port->info.samples_per_frame * 1000 / port->info.clock_rate,
770
good_number(bps, (codec_param->info.avg_bps+7)/8),
771
good_number(ipbps, ((codec_param->info.avg_bps+7)/8) +
773
codec_param->setting.frm_per_pkt /
774
codec_param->info.frm_ptime)));
776
if (stat.rx.update_cnt == 0)
777
strcpy(last_update, "never");
779
pj_gettimeofday(&now);
780
PJ_TIME_VAL_SUB(now, stat.rx.update);
781
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
783
(now.sec % 3600) / 60,
788
printf(" RX stat last update: %s\n"
789
" total %s packets %sB received (%sB +IP hdr)%s\n"
790
" pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
791
" (msec) min avg max last dev\n"
792
" loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
793
" jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
795
good_number(packets, stat.rx.pkt),
796
good_number(bytes, stat.rx.bytes),
797
good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
800
stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss),
802
stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss),
804
stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss),
806
stat.rx.loss_period.min / 1000.0,
807
stat.rx.loss_period.mean / 1000.0,
808
stat.rx.loss_period.max / 1000.0,
809
stat.rx.loss_period.last / 1000.0,
810
pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0,
812
stat.rx.jitter.min / 1000.0,
813
stat.rx.jitter.mean / 1000.0,
814
stat.rx.jitter.max / 1000.0,
815
stat.rx.jitter.last / 1000.0,
816
pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0,
821
if (stat.tx.update_cnt == 0)
822
strcpy(last_update, "never");
824
pj_gettimeofday(&now);
825
PJ_TIME_VAL_SUB(now, stat.tx.update);
826
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
828
(now.sec % 3600) / 60,
833
printf(" TX stat last update: %s\n"
834
" total %s packets %sB sent (%sB +IP hdr)%s\n"
835
" pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
836
" (msec) min avg max last dev\n"
837
" loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
838
" jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
840
good_number(packets, stat.tx.pkt),
841
good_number(bytes, stat.tx.bytes),
842
good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
845
stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss),
847
stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss),
849
stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss),
851
stat.tx.loss_period.min / 1000.0,
852
stat.tx.loss_period.mean / 1000.0,
853
stat.tx.loss_period.max / 1000.0,
854
stat.tx.loss_period.last / 1000.0,
855
pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0,
857
stat.tx.jitter.min / 1000.0,
858
stat.tx.jitter.mean / 1000.0,
859
stat.tx.jitter.max / 1000.0,
860
stat.tx.jitter.last / 1000.0,
861
pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0,
866
printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
867
stat.rtt.min / 1000.0,
868
stat.rtt.mean / 1000.0,
869
stat.rtt.max / 1000.0,
870
stat.rtt.last / 1000.0,
871
pj_math_stat_get_stddev(&stat.rtt) / 1000.0,
875
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
876
/* RTCP XR Reports */
878
char loss[16], dup[16];
881
char plc[16], jba[16], jbr[16];
882
char signal_lvl[16], noise_lvl[16], rerl[16];
883
char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
884
pjmedia_rtcp_xr_stat xr_stat;
886
if (pjmedia_stream_get_stat_xr(stream, &xr_stat) != PJ_SUCCESS)
889
puts("\nExtended reports:");
891
/* Statistics Summary */
892
puts(" Statistics Summary");
894
if (xr_stat.rx.stat_sum.l)
895
sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
897
sprintf(loss, "(na)");
899
if (xr_stat.rx.stat_sum.d)
900
sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
902
sprintf(dup, "(na)");
904
if (xr_stat.rx.stat_sum.j) {
905
unsigned jmin, jmax, jmean, jdev;
907
SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
908
port->info.clock_rate);
909
SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
910
port->info.clock_rate);
911
SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
912
port->info.clock_rate);
913
SAMPLES_TO_USEC(jdev,
914
pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
915
port->info.clock_rate);
916
sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
917
jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
919
sprintf(jitter, "(report not available)");
921
if (xr_stat.rx.stat_sum.t) {
922
sprintf(toh, "%11d %11d %11d %11d",
923
xr_stat.rx.stat_sum.toh.min,
924
xr_stat.rx.stat_sum.toh.mean,
925
xr_stat.rx.stat_sum.toh.max,
926
pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
928
sprintf(toh, "(report not available)");
930
if (xr_stat.rx.stat_sum.update.sec == 0)
931
strcpy(last_update, "never");
933
pj_gettimeofday(&now);
934
PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
935
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
937
(now.sec % 3600) / 60,
942
printf(" RX last update: %s\n"
943
" begin seq=%d, end seq=%d%s\n"
944
" pkt loss=%s, dup=%s%s\n"
945
" (msec) min avg max dev\n"
949
xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
957
if (xr_stat.tx.stat_sum.l)
958
sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
960
sprintf(loss, "(na)");
962
if (xr_stat.tx.stat_sum.d)
963
sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
965
sprintf(dup, "(na)");
967
if (xr_stat.tx.stat_sum.j) {
968
unsigned jmin, jmax, jmean, jdev;
970
SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
971
port->info.clock_rate);
972
SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
973
port->info.clock_rate);
974
SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
975
port->info.clock_rate);
976
SAMPLES_TO_USEC(jdev,
977
pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
978
port->info.clock_rate);
979
sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
980
jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
982
sprintf(jitter, "(report not available)");
984
if (xr_stat.tx.stat_sum.t) {
985
sprintf(toh, "%11d %11d %11d %11d",
986
xr_stat.tx.stat_sum.toh.min,
987
xr_stat.tx.stat_sum.toh.mean,
988
xr_stat.tx.stat_sum.toh.max,
989
pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
991
sprintf(toh, "(report not available)");
993
if (xr_stat.tx.stat_sum.update.sec == 0)
994
strcpy(last_update, "never");
996
pj_gettimeofday(&now);
997
PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
998
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
1000
(now.sec % 3600) / 60,
1005
printf(" TX last update: %s\n"
1006
" begin seq=%d, end seq=%d%s\n"
1007
" pkt loss=%s, dup=%s%s\n"
1008
" (msec) min avg max dev\n"
1012
xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
1021
puts(" VoIP Metrics");
1023
PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
1024
PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
1025
PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
1026
PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
1027
PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
1028
PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
1029
PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
1031
switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
1032
case PJMEDIA_RTCP_XR_PLC_DIS:
1033
sprintf(plc, "DISABLED");
1035
case PJMEDIA_RTCP_XR_PLC_ENH:
1036
sprintf(plc, "ENHANCED");
1038
case PJMEDIA_RTCP_XR_PLC_STD:
1039
sprintf(plc, "STANDARD");
1041
case PJMEDIA_RTCP_XR_PLC_UNK:
1043
sprintf(plc, "UNKNOWN");
1047
switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
1048
case PJMEDIA_RTCP_XR_JB_FIXED:
1049
sprintf(jba, "FIXED");
1051
case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
1052
sprintf(jba, "ADAPTIVE");
1055
sprintf(jba, "UNKNOWN");
1059
sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
1061
if (xr_stat.rx.voip_mtc.update.sec == 0)
1062
strcpy(last_update, "never");
1064
pj_gettimeofday(&now);
1065
PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
1066
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
1068
(now.sec % 3600) / 60,
1073
printf(" RX last update: %s\n"
1074
" packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
1075
" burst : density=%d (%.2f%%), duration=%d%s\n"
1076
" gap : density=%d (%.2f%%), duration=%d%s\n"
1077
" delay : round trip=%d%s, end system=%d%s\n"
1078
" level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
1079
" quality : R factor=%s, ext R factor=%s\n"
1080
" MOS LQ=%s, MOS CQ=%s\n"
1081
" config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
1082
" JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
1085
xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
1086
xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
1088
xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
1089
xr_stat.rx.voip_mtc.burst_dur, "ms",
1091
xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
1092
xr_stat.rx.voip_mtc.gap_dur, "ms",
1094
xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
1095
xr_stat.rx.voip_mtc.end_sys_delay, "ms",
1101
r_factor, ext_r_factor, mos_lq, mos_cq,
1103
plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
1105
xr_stat.rx.voip_mtc.jb_nom, "ms",
1106
xr_stat.rx.voip_mtc.jb_max, "ms",
1107
xr_stat.rx.voip_mtc.jb_abs_max, "ms"
1110
PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
1111
PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
1112
PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
1113
PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
1114
PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
1115
PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
1116
PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
1118
switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
1119
case PJMEDIA_RTCP_XR_PLC_DIS:
1120
sprintf(plc, "DISABLED");
1122
case PJMEDIA_RTCP_XR_PLC_ENH:
1123
sprintf(plc, "ENHANCED");
1125
case PJMEDIA_RTCP_XR_PLC_STD:
1126
sprintf(plc, "STANDARD");
1128
case PJMEDIA_RTCP_XR_PLC_UNK:
1130
sprintf(plc, "unknown");
1134
switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
1135
case PJMEDIA_RTCP_XR_JB_FIXED:
1136
sprintf(jba, "FIXED");
1138
case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
1139
sprintf(jba, "ADAPTIVE");
1142
sprintf(jba, "unknown");
1146
sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
1148
if (xr_stat.tx.voip_mtc.update.sec == 0)
1149
strcpy(last_update, "never");
1151
pj_gettimeofday(&now);
1152
PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
1153
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
1155
(now.sec % 3600) / 60,
1160
printf(" TX last update: %s\n"
1161
" packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
1162
" burst : density=%d (%.2f%%), duration=%d%s\n"
1163
" gap : density=%d (%.2f%%), duration=%d%s\n"
1164
" delay : round trip=%d%s, end system=%d%s\n"
1165
" level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
1166
" quality : R factor=%s, ext R factor=%s\n"
1167
" MOS LQ=%s, MOS CQ=%s\n"
1168
" config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
1169
" JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
1172
xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
1173
xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
1175
xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
1176
xr_stat.tx.voip_mtc.burst_dur, "ms",
1178
xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
1179
xr_stat.tx.voip_mtc.gap_dur, "ms",
1181
xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
1182
xr_stat.tx.voip_mtc.end_sys_delay, "ms",
1188
r_factor, ext_r_factor, mos_lq, mos_cq,
1190
plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
1192
xr_stat.tx.voip_mtc.jb_nom, "ms",
1193
xr_stat.tx.voip_mtc.jb_max, "ms",
1194
xr_stat.tx.voip_mtc.jb_abs_max, "ms"
1198
/* RTT delay (by receiver side) */
1199
printf(" (msec) min avg max last dev\n");
1200
printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
1201
xr_stat.rtt.min / 1000.0,
1202
xr_stat.rtt.mean / 1000.0,
1203
xr_stat.rtt.max / 1000.0,
1204
xr_stat.rtt.last / 1000.0,
1205
pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0,
1209
#endif /* PJMEDIA_HAS_RTCP_XR */