1
/* $Id: streamutil.c 3664 2011-07-19 03:42:28Z nanang $ */
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)
103
return pjmedia_codec_register_audio_codecs(med_endpt, NULL);
108
* Create stream based on the codec, dir, remote address, etc.
110
static pj_status_t create_stream( pj_pool_t *pool,
111
pjmedia_endpt *med_endpt,
112
const pjmedia_codec_info *codec_info,
114
pj_uint16_t local_port,
115
const pj_sockaddr_in *rem_addr,
116
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
118
const pj_str_t *crypto_suite,
119
const pj_str_t *srtp_tx_key,
120
const pj_str_t *srtp_rx_key,
122
pjmedia_stream **p_stream )
124
pjmedia_stream_info info;
125
pjmedia_transport *transport = NULL;
127
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
128
pjmedia_transport *srtp_tp = NULL;
132
/* Reset stream info. */
133
pj_bzero(&info, sizeof(info));
136
/* Initialize stream info formats */
137
info.type = PJMEDIA_TYPE_AUDIO;
139
pj_memcpy(&info.fmt, codec_info, sizeof(pjmedia_codec_info));
140
info.tx_pt = codec_info->pt;
141
info.ssrc = pj_rand();
143
#if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
144
/* Set default RTCP XR enabled/disabled */
145
info.rtcp_xr_enabled = PJ_TRUE;
148
/* Copy remote address */
149
pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));
151
/* If remote address is not set, set to an arbitrary address
152
* (otherwise stream will assert).
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);
159
/* Create media transport */
160
status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,
162
if (status != PJ_SUCCESS)
165
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
166
/* Check if SRTP enabled */
168
pjmedia_srtp_crypto tx_plc, rx_plc;
170
status = pjmedia_transport_srtp_create(med_endpt, transport,
172
if (status != PJ_SUCCESS)
175
pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
176
pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));
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;
183
status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
184
if (status != PJ_SUCCESS)
191
/* Now that the stream info is initialized, we can create the
195
status = pjmedia_stream_create( med_endpt, pool, &info,
199
if (status != PJ_SUCCESS) {
200
app_perror(THIS_FILE, "Error creating stream", status);
201
pjmedia_transport_close(transport);
221
int main(int argc, char *argv[])
224
pjmedia_endpt *med_endpt;
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;
234
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
236
pj_bool_t use_srtp = PJ_FALSE;
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};
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;
257
OPT_LOCAL_PORT = 'p',
260
OPT_RECORD_FILE = 'R',
264
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
267
OPT_SRTP_TX_KEY = 'x',
268
OPT_SRTP_RX_KEY = 'y',
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 },
286
{ "help", 0, 0, OPT_HELP },
294
pj_bzero(&remote_addr, sizeof(remote_addr));
299
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
302
/* Parse arguments */
304
while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1) {
308
codec_id = pj_optarg;
312
local_port = (pj_uint16_t) atoi(pj_optarg);
313
if (local_port < 1) {
314
printf("Error: invalid local port %s\n", pj_optarg);
321
pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
322
pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));
324
status = pj_sockaddr_in_init(&remote_addr, &ip, port);
325
if (status != PJ_SUCCESS) {
326
app_perror(THIS_FILE, "Invalid remote address", status);
333
play_file = pj_optarg;
336
case OPT_RECORD_FILE:
337
rec_file = pj_optarg;
341
dir = PJMEDIA_DIR_ENCODING_DECODING;
345
dir = PJMEDIA_DIR_ENCODING;
349
dir = PJMEDIA_DIR_DECODING;
352
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
356
pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg));
358
srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
362
case OPT_SRTP_TX_KEY:
363
tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg, strlen(pj_optarg));
364
pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);
367
case OPT_SRTP_RX_KEY:
368
tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg, strlen(pj_optarg));
369
pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);
378
printf("Invalid options %s\n", argv[pj_optind]);
385
/* Verify arguments. */
386
if (dir & PJMEDIA_DIR_ENCODING) {
387
if (remote_addr.sin_addr.s_addr == 0) {
388
printf("Error: remote address must be set\n");
393
if (play_file != NULL && dir != PJMEDIA_DIR_ENCODING) {
394
printf("Direction is set to --send-only because of --play-file\n");
395
dir = PJMEDIA_DIR_ENCODING;
398
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
399
/* SRTP validation */
401
if (!srtp_tx_key.slen || !srtp_rx_key.slen)
403
printf("Error: Key for each SRTP stream direction must be set\n");
409
/* Must create a pool factory before we can allocate any memory. */
410
pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
413
* Initialize media endpoint.
414
* This will implicitly initialize PJMEDIA too.
416
status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
417
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
419
/* Create memory pool for application purpose */
420
pool = pj_pool_create( &cp.factory, /* pool factory */
421
"app", /* pool name. */
422
4000, /* init size */
423
4000, /* increment size */
424
NULL /* callback on error */
428
/* Register all supported codecs */
429
status = init_codecs(med_endpt);
430
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
433
/* Find which codec to use. */
436
pj_str_t str_codec_id = pj_str(codec_id);
437
pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt);
438
status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr,
439
&str_codec_id, &count,
441
if (status != PJ_SUCCESS) {
442
printf("Error: unable to find codec %s\n", codec_id);
446
/* Default to pcmu */
447
pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt),
451
/* Create stream based on program arguments */
452
status = create_stream(pool, med_endpt, codec_info, dir, local_port,
454
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
455
use_srtp, &srtp_crypto_suite,
456
&srtp_tx_key, &srtp_rx_key,
459
if (status != PJ_SUCCESS)
462
/* Get codec default param for info */
463
status = pjmedia_codec_mgr_get_default_param(
464
pjmedia_endpt_get_codec_mgr(med_endpt),
467
/* Should be ok, as create_stream() above succeeded */
468
pj_assert(status == PJ_SUCCESS);
470
/* Get the port interface of the stream */
471
status = pjmedia_stream_get_port( stream, &stream_port);
472
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
478
wav_ptime = PJMEDIA_PIA_PTIME(&stream_port->info);
479
status = pjmedia_wav_player_port_create(pool, play_file, wav_ptime,
480
0, -1, &play_file_port);
481
if (status != PJ_SUCCESS) {
482
app_perror(THIS_FILE, "Unable to use file", status);
486
status = pjmedia_master_port_create(pool, play_file_port, stream_port,
488
if (status != PJ_SUCCESS) {
489
app_perror(THIS_FILE, "Unable to create master port", status);
493
status = pjmedia_master_port_start(master_port);
494
if (status != PJ_SUCCESS) {
495
app_perror(THIS_FILE, "Error starting master port", status);
499
printf("Playing from WAV file %s..\n", play_file);
501
} else if (rec_file) {
503
status = pjmedia_wav_writer_port_create(pool, rec_file,
504
PJMEDIA_PIA_SRATE(&stream_port->info),
505
PJMEDIA_PIA_CCNT(&stream_port->info),
506
PJMEDIA_PIA_SPF(&stream_port->info),
507
PJMEDIA_PIA_BITS(&stream_port->info),
508
0, 0, &rec_file_port);
509
if (status != PJ_SUCCESS) {
510
app_perror(THIS_FILE, "Unable to use file", status);
514
status = pjmedia_master_port_create(pool, stream_port, rec_file_port,
516
if (status != PJ_SUCCESS) {
517
app_perror(THIS_FILE, "Unable to create master port", status);
521
status = pjmedia_master_port_start(master_port);
522
if (status != PJ_SUCCESS) {
523
app_perror(THIS_FILE, "Error starting master port", status);
527
printf("Recording to WAV file %s..\n", rec_file);
531
/* Create sound device port. */
532
if (dir == PJMEDIA_DIR_ENCODING_DECODING)
533
status = pjmedia_snd_port_create(pool, -1, -1,
534
PJMEDIA_PIA_SRATE(&stream_port->info),
535
PJMEDIA_PIA_CCNT(&stream_port->info),
536
PJMEDIA_PIA_SPF(&stream_port->info),
537
PJMEDIA_PIA_BITS(&stream_port->info),
539
else if (dir == PJMEDIA_DIR_ENCODING)
540
status = pjmedia_snd_port_create_rec(pool, -1,
541
PJMEDIA_PIA_SRATE(&stream_port->info),
542
PJMEDIA_PIA_CCNT(&stream_port->info),
543
PJMEDIA_PIA_SPF(&stream_port->info),
544
PJMEDIA_PIA_BITS(&stream_port->info),
547
status = pjmedia_snd_port_create_player(pool, -1,
548
PJMEDIA_PIA_SRATE(&stream_port->info),
549
PJMEDIA_PIA_CCNT(&stream_port->info),
550
PJMEDIA_PIA_SPF(&stream_port->info),
551
PJMEDIA_PIA_BITS(&stream_port->info),
555
if (status != PJ_SUCCESS) {
556
app_perror(THIS_FILE, "Unable to create sound port", status);
560
/* Connect sound port to stream */
561
status = pjmedia_snd_port_connect( snd_port, stream_port );
562
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
566
/* Start streaming */
567
pjmedia_stream_start(stream);
572
if (dir == PJMEDIA_DIR_DECODING)
573
printf("Stream is active, dir is recv-only, local port is %d\n",
575
else if (dir == PJMEDIA_DIR_ENCODING)
576
printf("Stream is active, dir is send-only, sending to %s:%d\n",
577
pj_inet_ntoa(remote_addr.sin_addr),
578
pj_ntohs(remote_addr.sin_port));
580
printf("Stream is active, send/recv, local port is %d, "
581
"sending to %s:%d\n",
583
pj_inet_ntoa(remote_addr.sin_addr),
584
pj_ntohs(remote_addr.sin_port));
591
puts(" s Display media statistics");
595
printf("Command: "); fflush(stdout);
597
if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
598
puts("EOF while reading stdin, will quit now..");
603
print_stream_stat(stream, &codec_param);
604
else if (tmp[0] == 'q')
611
/* Start deinitialization: */
614
/* Destroy sound device */
616
pjmedia_snd_port_destroy( snd_port );
617
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
620
/* If there is master port, then we just need to destroy master port
621
* (it will recursively destroy upstream and downstream ports, which
622
* in this case are file_port and stream_port).
625
pjmedia_master_port_destroy(master_port, PJ_TRUE);
626
play_file_port = NULL;
632
pjmedia_transport *tp;
634
tp = pjmedia_stream_get_transport(stream);
635
pjmedia_stream_destroy(stream);
637
pjmedia_transport_close(tp);
640
/* Destroy file ports */
642
pjmedia_port_destroy( play_file_port );
644
pjmedia_port_destroy( rec_file_port );
647
/* Release application pool */
648
pj_pool_release( pool );
650
/* Destroy media endpoint. */
651
pjmedia_endpt_destroy( med_endpt );
653
/* Destroy pool factory */
654
pj_caching_pool_destroy( &cp );
660
return (status == PJ_SUCCESS) ? 0 : 1;
666
static const char *good_number(char *buf, pj_int32_t val)
669
pj_ansi_sprintf(buf, "%d", val);
670
} else if (val < 1000000) {
671
pj_ansi_sprintf(buf, "%d.%dK",
675
pj_ansi_sprintf(buf, "%d.%02dM",
677
(val % 1000000) / 10000);
684
#define SAMPLES_TO_USEC(usec, samples, clock_rate) \
686
if (samples <= 4294) \
687
usec = samples * 1000000 / clock_rate; \
689
usec = samples * 1000 / clock_rate; \
694
#define PRINT_VOIP_MTC_VAL(s, v) \
696
sprintf(s, "(na)"); \
702
* Print stream statistics
704
static void print_stream_stat(pjmedia_stream *stream,
705
const pjmedia_codec_param *codec_param)
707
char duration[80], last_update[80];
708
char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
710
pjmedia_rtcp_stat stat;
714
pj_gettimeofday(&now);
715
pjmedia_stream_get_stat(stream, &stat);
716
pjmedia_stream_get_port(stream, &port);
718
puts("Stream statistics:");
721
PJ_TIME_VAL_SUB(now, stat.start);
722
sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld",
724
(now.sec % 3600) / 60,
729
printf(" Info: audio %dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
730
PJMEDIA_PIA_SRATE(&port->info),
731
PJMEDIA_PIA_PTIME(&port->info),
732
good_number(bps, (codec_param->info.avg_bps+7)/8),
733
good_number(ipbps, ((codec_param->info.avg_bps+7)/8) +
735
codec_param->setting.frm_per_pkt /
736
codec_param->info.frm_ptime)));
738
if (stat.rx.update_cnt == 0)
739
strcpy(last_update, "never");
741
pj_gettimeofday(&now);
742
PJ_TIME_VAL_SUB(now, stat.rx.update);
743
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
745
(now.sec % 3600) / 60,
750
printf(" RX stat last update: %s\n"
751
" total %s packets %sB received (%sB +IP hdr)%s\n"
752
" pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
753
" (msec) min avg max last dev\n"
754
" loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
755
" jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
757
good_number(packets, stat.rx.pkt),
758
good_number(bytes, stat.rx.bytes),
759
good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
762
stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss),
764
stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss),
766
stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss),
768
stat.rx.loss_period.min / 1000.0,
769
stat.rx.loss_period.mean / 1000.0,
770
stat.rx.loss_period.max / 1000.0,
771
stat.rx.loss_period.last / 1000.0,
772
pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0,
774
stat.rx.jitter.min / 1000.0,
775
stat.rx.jitter.mean / 1000.0,
776
stat.rx.jitter.max / 1000.0,
777
stat.rx.jitter.last / 1000.0,
778
pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0,
783
if (stat.tx.update_cnt == 0)
784
strcpy(last_update, "never");
786
pj_gettimeofday(&now);
787
PJ_TIME_VAL_SUB(now, stat.tx.update);
788
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
790
(now.sec % 3600) / 60,
795
printf(" TX stat last update: %s\n"
796
" total %s packets %sB sent (%sB +IP hdr)%s\n"
797
" pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
798
" (msec) min avg max last dev\n"
799
" loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
800
" jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
802
good_number(packets, stat.tx.pkt),
803
good_number(bytes, stat.tx.bytes),
804
good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
807
stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss),
809
stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss),
811
stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss),
813
stat.tx.loss_period.min / 1000.0,
814
stat.tx.loss_period.mean / 1000.0,
815
stat.tx.loss_period.max / 1000.0,
816
stat.tx.loss_period.last / 1000.0,
817
pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0,
819
stat.tx.jitter.min / 1000.0,
820
stat.tx.jitter.mean / 1000.0,
821
stat.tx.jitter.max / 1000.0,
822
stat.tx.jitter.last / 1000.0,
823
pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0,
828
printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
829
stat.rtt.min / 1000.0,
830
stat.rtt.mean / 1000.0,
831
stat.rtt.max / 1000.0,
832
stat.rtt.last / 1000.0,
833
pj_math_stat_get_stddev(&stat.rtt) / 1000.0,
837
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
838
/* RTCP XR Reports */
840
char loss[16], dup[16];
843
char plc[16], jba[16], jbr[16];
844
char signal_lvl[16], noise_lvl[16], rerl[16];
845
char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
846
pjmedia_rtcp_xr_stat xr_stat;
848
if (pjmedia_stream_get_stat_xr(stream, &xr_stat) != PJ_SUCCESS)
851
puts("\nExtended reports:");
853
/* Statistics Summary */
854
puts(" Statistics Summary");
856
if (xr_stat.rx.stat_sum.l)
857
sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
859
sprintf(loss, "(na)");
861
if (xr_stat.rx.stat_sum.d)
862
sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
864
sprintf(dup, "(na)");
866
if (xr_stat.rx.stat_sum.j) {
867
unsigned jmin, jmax, jmean, jdev;
869
SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
870
port->info.fmt.det.aud.clock_rate);
871
SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
872
port->info.fmt.det.aud.clock_rate);
873
SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
874
port->info.fmt.det.aud.clock_rate);
875
SAMPLES_TO_USEC(jdev,
876
pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
877
port->info.fmt.det.aud.clock_rate);
878
sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
879
jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
881
sprintf(jitter, "(report not available)");
883
if (xr_stat.rx.stat_sum.t) {
884
sprintf(toh, "%11d %11d %11d %11d",
885
xr_stat.rx.stat_sum.toh.min,
886
xr_stat.rx.stat_sum.toh.mean,
887
xr_stat.rx.stat_sum.toh.max,
888
pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
890
sprintf(toh, "(report not available)");
892
if (xr_stat.rx.stat_sum.update.sec == 0)
893
strcpy(last_update, "never");
895
pj_gettimeofday(&now);
896
PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
897
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
899
(now.sec % 3600) / 60,
904
printf(" RX last update: %s\n"
905
" begin seq=%d, end seq=%d%s\n"
906
" pkt loss=%s, dup=%s%s\n"
907
" (msec) min avg max dev\n"
911
xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
919
if (xr_stat.tx.stat_sum.l)
920
sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
922
sprintf(loss, "(na)");
924
if (xr_stat.tx.stat_sum.d)
925
sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
927
sprintf(dup, "(na)");
929
if (xr_stat.tx.stat_sum.j) {
930
unsigned jmin, jmax, jmean, jdev;
932
SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
933
port->info.fmt.det.aud.clock_rate);
934
SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
935
port->info.fmt.det.aud.clock_rate);
936
SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
937
port->info.fmt.det.aud.clock_rate);
938
SAMPLES_TO_USEC(jdev,
939
pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
940
port->info.fmt.det.aud.clock_rate);
941
sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
942
jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
944
sprintf(jitter, "(report not available)");
946
if (xr_stat.tx.stat_sum.t) {
947
sprintf(toh, "%11d %11d %11d %11d",
948
xr_stat.tx.stat_sum.toh.min,
949
xr_stat.tx.stat_sum.toh.mean,
950
xr_stat.tx.stat_sum.toh.max,
951
pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
953
sprintf(toh, "(report not available)");
955
if (xr_stat.tx.stat_sum.update.sec == 0)
956
strcpy(last_update, "never");
958
pj_gettimeofday(&now);
959
PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
960
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
962
(now.sec % 3600) / 60,
967
printf(" TX last update: %s\n"
968
" begin seq=%d, end seq=%d%s\n"
969
" pkt loss=%s, dup=%s%s\n"
970
" (msec) min avg max dev\n"
974
xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
983
puts(" VoIP Metrics");
985
PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
986
PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
987
PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
988
PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
989
PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
990
PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
991
PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
993
switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
994
case PJMEDIA_RTCP_XR_PLC_DIS:
995
sprintf(plc, "DISABLED");
997
case PJMEDIA_RTCP_XR_PLC_ENH:
998
sprintf(plc, "ENHANCED");
1000
case PJMEDIA_RTCP_XR_PLC_STD:
1001
sprintf(plc, "STANDARD");
1003
case PJMEDIA_RTCP_XR_PLC_UNK:
1005
sprintf(plc, "UNKNOWN");
1009
switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
1010
case PJMEDIA_RTCP_XR_JB_FIXED:
1011
sprintf(jba, "FIXED");
1013
case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
1014
sprintf(jba, "ADAPTIVE");
1017
sprintf(jba, "UNKNOWN");
1021
sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
1023
if (xr_stat.rx.voip_mtc.update.sec == 0)
1024
strcpy(last_update, "never");
1026
pj_gettimeofday(&now);
1027
PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
1028
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
1030
(now.sec % 3600) / 60,
1035
printf(" RX last update: %s\n"
1036
" packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
1037
" burst : density=%d (%.2f%%), duration=%d%s\n"
1038
" gap : density=%d (%.2f%%), duration=%d%s\n"
1039
" delay : round trip=%d%s, end system=%d%s\n"
1040
" level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
1041
" quality : R factor=%s, ext R factor=%s\n"
1042
" MOS LQ=%s, MOS CQ=%s\n"
1043
" config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
1044
" JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
1047
xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
1048
xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
1050
xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
1051
xr_stat.rx.voip_mtc.burst_dur, "ms",
1053
xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
1054
xr_stat.rx.voip_mtc.gap_dur, "ms",
1056
xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
1057
xr_stat.rx.voip_mtc.end_sys_delay, "ms",
1063
r_factor, ext_r_factor, mos_lq, mos_cq,
1065
plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
1067
xr_stat.rx.voip_mtc.jb_nom, "ms",
1068
xr_stat.rx.voip_mtc.jb_max, "ms",
1069
xr_stat.rx.voip_mtc.jb_abs_max, "ms"
1072
PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
1073
PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
1074
PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
1075
PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
1076
PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
1077
PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
1078
PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
1080
switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
1081
case PJMEDIA_RTCP_XR_PLC_DIS:
1082
sprintf(plc, "DISABLED");
1084
case PJMEDIA_RTCP_XR_PLC_ENH:
1085
sprintf(plc, "ENHANCED");
1087
case PJMEDIA_RTCP_XR_PLC_STD:
1088
sprintf(plc, "STANDARD");
1090
case PJMEDIA_RTCP_XR_PLC_UNK:
1092
sprintf(plc, "unknown");
1096
switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
1097
case PJMEDIA_RTCP_XR_JB_FIXED:
1098
sprintf(jba, "FIXED");
1100
case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
1101
sprintf(jba, "ADAPTIVE");
1104
sprintf(jba, "unknown");
1108
sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
1110
if (xr_stat.tx.voip_mtc.update.sec == 0)
1111
strcpy(last_update, "never");
1113
pj_gettimeofday(&now);
1114
PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
1115
sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
1117
(now.sec % 3600) / 60,
1122
printf(" TX last update: %s\n"
1123
" packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
1124
" burst : density=%d (%.2f%%), duration=%d%s\n"
1125
" gap : density=%d (%.2f%%), duration=%d%s\n"
1126
" delay : round trip=%d%s, end system=%d%s\n"
1127
" level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
1128
" quality : R factor=%s, ext R factor=%s\n"
1129
" MOS LQ=%s, MOS CQ=%s\n"
1130
" config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
1131
" JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
1134
xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
1135
xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
1137
xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
1138
xr_stat.tx.voip_mtc.burst_dur, "ms",
1140
xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
1141
xr_stat.tx.voip_mtc.gap_dur, "ms",
1143
xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
1144
xr_stat.tx.voip_mtc.end_sys_delay, "ms",
1150
r_factor, ext_r_factor, mos_lq, mos_cq,
1152
plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
1154
xr_stat.tx.voip_mtc.jb_nom, "ms",
1155
xr_stat.tx.voip_mtc.jb_max, "ms",
1156
xr_stat.tx.voip_mtc.jb_abs_max, "ms"
1160
/* RTT delay (by receiver side) */
1161
printf(" (msec) min avg max last dev\n");
1162
printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
1163
xr_stat.rtt.min / 1000.0,
1164
xr_stat.rtt.mean / 1000.0,
1165
xr_stat.rtt.max / 1000.0,
1166
xr_stat.rtt.last / 1000.0,
1167
pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0,
1171
#endif /* PJMEDIA_HAS_RTCP_XR */