This example mainly demonstrates how to stream media file to remote peer using RTP.
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00033 #include <pjlib.h>
00034 #include <pjlib-util.h>
00035 #include <pjmedia.h>
00036 #include <pjmedia-codec.h>
00037 #include <pjmedia/transport_srtp.h>
00038
00039 #include <stdlib.h>
00040 #include <stdio.h>
00041
00042 #include "util.h"
00043
00044
00045 static const char *desc =
00046 " streamutil \n"
00047 " \n"
00048 " PURPOSE: \n"
00049 " Demonstrate how to use pjmedia stream component to transmit/receive \n"
00050 " RTP packets to/from sound device. \n"
00051 "\n"
00052 "\n"
00053 " USAGE: \n"
00054 " streamutil [options] \n"
00055 "\n"
00056 "\n"
00057 " Options:\n"
00058 " --codec=CODEC Set the codec name. \n"
00059 " --local-port=PORT Set local RTP port (default=4000) \n"
00060 " --remote=IP:PORT Set the remote peer. If this option is set, \n"
00061 " the program will transmit RTP audio to the \n"
00062 " specified address. (default: recv only) \n"
00063 " --play-file=WAV Send audio from the WAV file instead of from \n"
00064 " the sound device. \n"
00065 " --record-file=WAV Record incoming audio to WAV file instead of \n"
00066 " playing it to sound device. \n"
00067 " --send-recv Set stream direction to bidirectional. \n"
00068 " --send-only Set stream direction to send only \n"
00069 " --recv-only Set stream direction to recv only (default) \n"
00070
00071 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00072 " --use-srtp[=NAME] Enable SRTP with crypto suite NAME \n"
00073 " e.g: AES_CM_128_HMAC_SHA1_80 (default), \n"
00074 " AES_CM_128_HMAC_SHA1_32 \n"
00075 " Use this option along with the TX & RX keys, \n"
00076 " formated of 60 hex digits (e.g: E148DA..) \n"
00077 " --srtp-tx-key SRTP key for transmiting \n"
00078 " --srtp-rx-key SRTP key for receiving \n"
00079 #endif
00080
00081 "\n"
00082 ;
00083
00084
00085
00086
00087 #define THIS_FILE "stream.c"
00088
00089
00090
00091
00092 static void print_stream_stat(pjmedia_stream *stream,
00093 const pjmedia_codec_param *codec_param);
00094
00095
00096 int hex_string_to_octet_string(char *raw, char *hex, int len);
00097
00098
00099
00100
00101 static pj_status_t init_codecs(pjmedia_endpt *med_endpt)
00102 {
00103 return pjmedia_codec_register_audio_codecs(med_endpt, NULL);
00104 }
00105
00106
00107
00108
00109
00110 static pj_status_t create_stream( pj_pool_t *pool,
00111 pjmedia_endpt *med_endpt,
00112 const pjmedia_codec_info *codec_info,
00113 pjmedia_dir dir,
00114 pj_uint16_t local_port,
00115 const pj_sockaddr_in *rem_addr,
00116 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00117 pj_bool_t use_srtp,
00118 const pj_str_t *crypto_suite,
00119 const pj_str_t *srtp_tx_key,
00120 const pj_str_t *srtp_rx_key,
00121 #endif
00122 pjmedia_stream **p_stream )
00123 {
00124 pjmedia_stream_info info;
00125 pjmedia_transport *transport = NULL;
00126 pj_status_t status;
00127 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00128 pjmedia_transport *srtp_tp = NULL;
00129 #endif
00130
00131
00132
00133 pj_bzero(&info, sizeof(info));
00134
00135
00136
00137 info.type = PJMEDIA_TYPE_AUDIO;
00138 info.dir = dir;
00139 pj_memcpy(&info.fmt, codec_info, sizeof(pjmedia_codec_info));
00140 info.tx_pt = codec_info->pt;
00141 info.ssrc = pj_rand();
00142
00143 #if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
00144
00145 info.rtcp_xr_enabled = PJ_TRUE;
00146 #endif
00147
00148
00149 pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));
00150
00151
00152
00153
00154 if (info.rem_addr.addr.sa_family == 0) {
00155 const pj_str_t addr = pj_str("127.0.0.1");
00156 pj_sockaddr_in_init(&info.rem_addr.ipv4, &addr, 0);
00157 }
00158
00159
00160 status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,
00161 0, &transport);
00162 if (status != PJ_SUCCESS)
00163 return status;
00164
00165 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00166
00167 if (use_srtp) {
00168 pjmedia_srtp_crypto tx_plc, rx_plc;
00169
00170 status = pjmedia_transport_srtp_create(med_endpt, transport,
00171 NULL, &srtp_tp);
00172 if (status != PJ_SUCCESS)
00173 return status;
00174
00175 pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
00176 pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));
00177
00178 tx_plc.key = *srtp_tx_key;
00179 tx_plc.name = *crypto_suite;
00180 rx_plc.key = *srtp_rx_key;
00181 rx_plc.name = *crypto_suite;
00182
00183 status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
00184 if (status != PJ_SUCCESS)
00185 return status;
00186
00187 transport = srtp_tp;
00188 }
00189 #endif
00190
00191
00192
00193
00194
00195 status = pjmedia_stream_create( med_endpt, pool, &info,
00196 transport,
00197 NULL, p_stream);
00198
00199 if (status != PJ_SUCCESS) {
00200 app_perror(THIS_FILE, "Error creating stream", status);
00201 pjmedia_transport_close(transport);
00202 return status;
00203 }
00204
00205
00206 return PJ_SUCCESS;
00207 }
00208
00209
00210
00211
00212
00213 static void usage()
00214 {
00215 puts(desc);
00216 }
00217
00218
00219
00220
00221 int main(int argc, char *argv[])
00222 {
00223 pj_caching_pool cp;
00224 pjmedia_endpt *med_endpt;
00225 pj_pool_t *pool;
00226 pjmedia_port *rec_file_port = NULL, *play_file_port = NULL;
00227 pjmedia_master_port *master_port = NULL;
00228 pjmedia_snd_port *snd_port = NULL;
00229 pjmedia_stream *stream = NULL;
00230 pjmedia_port *stream_port;
00231 char tmp[10];
00232 pj_status_t status;
00233
00234 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00235
00236 pj_bool_t use_srtp = PJ_FALSE;
00237 char tmp_tx_key[64];
00238 char tmp_rx_key[64];
00239 pj_str_t srtp_tx_key = {NULL, 0};
00240 pj_str_t srtp_rx_key = {NULL, 0};
00241 pj_str_t srtp_crypto_suite = {NULL, 0};
00242 int tmp_key_len;
00243 #endif
00244
00245
00246 const pjmedia_codec_info *codec_info;
00247 pjmedia_codec_param codec_param;
00248 pjmedia_dir dir = PJMEDIA_DIR_DECODING;
00249 pj_sockaddr_in remote_addr;
00250 pj_uint16_t local_port = 4000;
00251 char *codec_id = NULL;
00252 char *rec_file = NULL;
00253 char *play_file = NULL;
00254
00255 enum {
00256 OPT_CODEC = 'c',
00257 OPT_LOCAL_PORT = 'p',
00258 OPT_REMOTE = 'r',
00259 OPT_PLAY_FILE = 'w',
00260 OPT_RECORD_FILE = 'R',
00261 OPT_SEND_RECV = 'b',
00262 OPT_SEND_ONLY = 's',
00263 OPT_RECV_ONLY = 'i',
00264 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00265 OPT_USE_SRTP = 'S',
00266 #endif
00267 OPT_SRTP_TX_KEY = 'x',
00268 OPT_SRTP_RX_KEY = 'y',
00269 OPT_HELP = 'h',
00270 };
00271
00272 struct pj_getopt_option long_options[] = {
00273 { "codec", 1, 0, OPT_CODEC },
00274 { "local-port", 1, 0, OPT_LOCAL_PORT },
00275 { "remote", 1, 0, OPT_REMOTE },
00276 { "play-file", 1, 0, OPT_PLAY_FILE },
00277 { "record-file", 1, 0, OPT_RECORD_FILE },
00278 { "send-recv", 0, 0, OPT_SEND_RECV },
00279 { "send-only", 0, 0, OPT_SEND_ONLY },
00280 { "recv-only", 0, 0, OPT_RECV_ONLY },
00281 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00282 { "use-srtp", 2, 0, OPT_USE_SRTP },
00283 { "srtp-tx-key", 1, 0, OPT_SRTP_TX_KEY },
00284 { "srtp-rx-key", 1, 0, OPT_SRTP_RX_KEY },
00285 #endif
00286 { "help", 0, 0, OPT_HELP },
00287 { NULL, 0, 0, 0 },
00288 };
00289
00290 int c;
00291 int option_index;
00292
00293
00294 pj_bzero(&remote_addr, sizeof(remote_addr));
00295
00296
00297
00298 status = pj_init();
00299 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00300
00301
00302
00303 pj_optind = 0;
00304 while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1) {
00305
00306 switch (c) {
00307 case OPT_CODEC:
00308 codec_id = pj_optarg;
00309 break;
00310
00311 case OPT_LOCAL_PORT:
00312 local_port = (pj_uint16_t) atoi(pj_optarg);
00313 if (local_port < 1) {
00314 printf("Error: invalid local port %s\n", pj_optarg);
00315 return 1;
00316 }
00317 break;
00318
00319 case OPT_REMOTE:
00320 {
00321 pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
00322 pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));
00323
00324 status = pj_sockaddr_in_init(&remote_addr, &ip, port);
00325 if (status != PJ_SUCCESS) {
00326 app_perror(THIS_FILE, "Invalid remote address", status);
00327 return 1;
00328 }
00329 }
00330 break;
00331
00332 case OPT_PLAY_FILE:
00333 play_file = pj_optarg;
00334 break;
00335
00336 case OPT_RECORD_FILE:
00337 rec_file = pj_optarg;
00338 break;
00339
00340 case OPT_SEND_RECV:
00341 dir = PJMEDIA_DIR_ENCODING_DECODING;
00342 break;
00343
00344 case OPT_SEND_ONLY:
00345 dir = PJMEDIA_DIR_ENCODING;
00346 break;
00347
00348 case OPT_RECV_ONLY:
00349 dir = PJMEDIA_DIR_DECODING;
00350 break;
00351
00352 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00353 case OPT_USE_SRTP:
00354 use_srtp = PJ_TRUE;
00355 if (pj_optarg) {
00356 pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg));
00357 } else {
00358 srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
00359 }
00360 break;
00361
00362 case OPT_SRTP_TX_KEY:
00363 tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg, strlen(pj_optarg));
00364 pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);
00365 break;
00366
00367 case OPT_SRTP_RX_KEY:
00368 tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg, strlen(pj_optarg));
00369 pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);
00370 break;
00371 #endif
00372
00373 case OPT_HELP:
00374 usage();
00375 return 1;
00376
00377 default:
00378 printf("Invalid options %s\n", argv[pj_optind]);
00379 return 1;
00380 }
00381
00382 }
00383
00384
00385
00386 if (dir & PJMEDIA_DIR_ENCODING) {
00387 if (remote_addr.sin_addr.s_addr == 0) {
00388 printf("Error: remote address must be set\n");
00389 return 1;
00390 }
00391 }
00392
00393 if (play_file != NULL && dir != PJMEDIA_DIR_ENCODING) {
00394 printf("Direction is set to --send-only because of --play-file\n");
00395 dir = PJMEDIA_DIR_ENCODING;
00396 }
00397
00398 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00399
00400 if (use_srtp) {
00401 if (!srtp_tx_key.slen || !srtp_rx_key.slen)
00402 {
00403 printf("Error: Key for each SRTP stream direction must be set\n");
00404 return 1;
00405 }
00406 }
00407 #endif
00408
00409
00410 pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
00411
00412
00413
00414
00415
00416 status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
00417 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00418
00419
00420 pool = pj_pool_create( &cp.factory,
00421 "app",
00422 4000,
00423 4000,
00424 NULL
00425 );
00426
00427
00428
00429 status = init_codecs(med_endpt);
00430 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00431
00432
00433
00434 if (codec_id) {
00435 unsigned count = 1;
00436 pj_str_t str_codec_id = pj_str(codec_id);
00437 pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt);
00438 status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr,
00439 &str_codec_id, &count,
00440 &codec_info, NULL);
00441 if (status != PJ_SUCCESS) {
00442 printf("Error: unable to find codec %s\n", codec_id);
00443 return 1;
00444 }
00445 } else {
00446
00447 pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt),
00448 0, &codec_info);
00449 }
00450
00451
00452 status = create_stream(pool, med_endpt, codec_info, dir, local_port,
00453 &remote_addr,
00454 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00455 use_srtp, &srtp_crypto_suite,
00456 &srtp_tx_key, &srtp_rx_key,
00457 #endif
00458 &stream);
00459 if (status != PJ_SUCCESS)
00460 goto on_exit;
00461
00462
00463 status = pjmedia_codec_mgr_get_default_param(
00464 pjmedia_endpt_get_codec_mgr(med_endpt),
00465 codec_info,
00466 &codec_param);
00467
00468 pj_assert(status == PJ_SUCCESS);
00469
00470
00471 status = pjmedia_stream_get_port( stream, &stream_port);
00472 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00473
00474
00475 if (play_file) {
00476 unsigned wav_ptime;
00477
00478 wav_ptime = PJMEDIA_PIA_PTIME(&stream_port->info);
00479 status = pjmedia_wav_player_port_create(pool, play_file, wav_ptime,
00480 0, -1, &play_file_port);
00481 if (status != PJ_SUCCESS) {
00482 app_perror(THIS_FILE, "Unable to use file", status);
00483 goto on_exit;
00484 }
00485
00486 status = pjmedia_master_port_create(pool, play_file_port, stream_port,
00487 0, &master_port);
00488 if (status != PJ_SUCCESS) {
00489 app_perror(THIS_FILE, "Unable to create master port", status);
00490 goto on_exit;
00491 }
00492
00493 status = pjmedia_master_port_start(master_port);
00494 if (status != PJ_SUCCESS) {
00495 app_perror(THIS_FILE, "Error starting master port", status);
00496 goto on_exit;
00497 }
00498
00499 printf("Playing from WAV file %s..\n", play_file);
00500
00501 } else if (rec_file) {
00502
00503 status = pjmedia_wav_writer_port_create(pool, rec_file,
00504 PJMEDIA_PIA_SRATE(&stream_port->info),
00505 PJMEDIA_PIA_CCNT(&stream_port->info),
00506 PJMEDIA_PIA_SPF(&stream_port->info),
00507 PJMEDIA_PIA_BITS(&stream_port->info),
00508 0, 0, &rec_file_port);
00509 if (status != PJ_SUCCESS) {
00510 app_perror(THIS_FILE, "Unable to use file", status);
00511 goto on_exit;
00512 }
00513
00514 status = pjmedia_master_port_create(pool, stream_port, rec_file_port,
00515 0, &master_port);
00516 if (status != PJ_SUCCESS) {
00517 app_perror(THIS_FILE, "Unable to create master port", status);
00518 goto on_exit;
00519 }
00520
00521 status = pjmedia_master_port_start(master_port);
00522 if (status != PJ_SUCCESS) {
00523 app_perror(THIS_FILE, "Error starting master port", status);
00524 goto on_exit;
00525 }
00526
00527 printf("Recording to WAV file %s..\n", rec_file);
00528
00529 } else {
00530
00531
00532 if (dir == PJMEDIA_DIR_ENCODING_DECODING)
00533 status = pjmedia_snd_port_create(pool, -1, -1,
00534 PJMEDIA_PIA_SRATE(&stream_port->info),
00535 PJMEDIA_PIA_CCNT(&stream_port->info),
00536 PJMEDIA_PIA_SPF(&stream_port->info),
00537 PJMEDIA_PIA_BITS(&stream_port->info),
00538 0, &snd_port);
00539 else if (dir == PJMEDIA_DIR_ENCODING)
00540 status = pjmedia_snd_port_create_rec(pool, -1,
00541 PJMEDIA_PIA_SRATE(&stream_port->info),
00542 PJMEDIA_PIA_CCNT(&stream_port->info),
00543 PJMEDIA_PIA_SPF(&stream_port->info),
00544 PJMEDIA_PIA_BITS(&stream_port->info),
00545 0, &snd_port);
00546 else
00547 status = pjmedia_snd_port_create_player(pool, -1,
00548 PJMEDIA_PIA_SRATE(&stream_port->info),
00549 PJMEDIA_PIA_CCNT(&stream_port->info),
00550 PJMEDIA_PIA_SPF(&stream_port->info),
00551 PJMEDIA_PIA_BITS(&stream_port->info),
00552 0, &snd_port);
00553
00554
00555 if (status != PJ_SUCCESS) {
00556 app_perror(THIS_FILE, "Unable to create sound port", status);
00557 goto on_exit;
00558 }
00559
00560
00561 status = pjmedia_snd_port_connect( snd_port, stream_port );
00562 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00563
00564 }
00565
00566
00567 pjmedia_stream_start(stream);
00568
00569
00570
00571
00572 if (dir == PJMEDIA_DIR_DECODING)
00573 printf("Stream is active, dir is recv-only, local port is %d\n",
00574 local_port);
00575 else if (dir == PJMEDIA_DIR_ENCODING)
00576 printf("Stream is active, dir is send-only, sending to %s:%d\n",
00577 pj_inet_ntoa(remote_addr.sin_addr),
00578 pj_ntohs(remote_addr.sin_port));
00579 else
00580 printf("Stream is active, send/recv, local port is %d, "
00581 "sending to %s:%d\n",
00582 local_port,
00583 pj_inet_ntoa(remote_addr.sin_addr),
00584 pj_ntohs(remote_addr.sin_port));
00585
00586
00587 for (;;) {
00588
00589 puts("");
00590 puts("Commands:");
00591 puts(" s Display media statistics");
00592 puts(" q Quit");
00593 puts("");
00594
00595 printf("Command: "); fflush(stdout);
00596
00597 if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
00598 puts("EOF while reading stdin, will quit now..");
00599 break;
00600 }
00601
00602 if (tmp[0] == 's')
00603 print_stream_stat(stream, &codec_param);
00604 else if (tmp[0] == 'q')
00605 break;
00606
00607 }
00608
00609
00610
00611
00612 on_exit:
00613
00614
00615 if (snd_port) {
00616 pjmedia_snd_port_destroy( snd_port );
00617 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00618 }
00619
00620
00621
00622
00623
00624 if (master_port) {
00625 pjmedia_master_port_destroy(master_port, PJ_TRUE);
00626 play_file_port = NULL;
00627 stream = NULL;
00628 }
00629
00630
00631 if (stream) {
00632 pjmedia_transport *tp;
00633
00634 tp = pjmedia_stream_get_transport(stream);
00635 pjmedia_stream_destroy(stream);
00636
00637 pjmedia_transport_close(tp);
00638 }
00639
00640
00641 if (play_file_port)
00642 pjmedia_port_destroy( play_file_port );
00643 if (rec_file_port)
00644 pjmedia_port_destroy( rec_file_port );
00645
00646
00647
00648 pj_pool_release( pool );
00649
00650
00651 pjmedia_endpt_destroy( med_endpt );
00652
00653
00654 pj_caching_pool_destroy( &cp );
00655
00656
00657 pj_shutdown();
00658
00659
00660 return (status == PJ_SUCCESS) ? 0 : 1;
00661 }
00662
00663
00664
00665
00666 static const char *good_number(char *buf, pj_int32_t val)
00667 {
00668 if (val < 1000) {
00669 pj_ansi_sprintf(buf, "%d", val);
00670 } else if (val < 1000000) {
00671 pj_ansi_sprintf(buf, "%d.%dK",
00672 val / 1000,
00673 (val % 1000) / 100);
00674 } else {
00675 pj_ansi_sprintf(buf, "%d.%02dM",
00676 val / 1000000,
00677 (val % 1000000) / 10000);
00678 }
00679
00680 return buf;
00681 }
00682
00683
00684 #define SAMPLES_TO_USEC(usec, samples, clock_rate) \
00685 do { \
00686 if (samples <= 4294) \
00687 usec = samples * 1000000 / clock_rate; \
00688 else { \
00689 usec = samples * 1000 / clock_rate; \
00690 usec *= 1000; \
00691 } \
00692 } while(0)
00693
00694 #define PRINT_VOIP_MTC_VAL(s, v) \
00695 if (v == 127) \
00696 sprintf(s, "(na)"); \
00697 else \
00698 sprintf(s, "%d", v)
00699
00700
00701
00702
00703
00704 static void print_stream_stat(pjmedia_stream *stream,
00705 const pjmedia_codec_param *codec_param)
00706 {
00707 char duration[80], last_update[80];
00708 char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
00709 pjmedia_port *port;
00710 pjmedia_rtcp_stat stat;
00711 pj_time_val now;
00712
00713
00714 pj_gettimeofday(&now);
00715 pjmedia_stream_get_stat(stream, &stat);
00716 pjmedia_stream_get_port(stream, &port);
00717
00718 puts("Stream statistics:");
00719
00720
00721 PJ_TIME_VAL_SUB(now, stat.start);
00722 sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld",
00723 now.sec / 3600,
00724 (now.sec % 3600) / 60,
00725 (now.sec % 60),
00726 now.msec);
00727
00728
00729 printf(" Info: audio %dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
00730 PJMEDIA_PIA_SRATE(&port->info),
00731 PJMEDIA_PIA_PTIME(&port->info),
00732 good_number(bps, (codec_param->info.avg_bps+7)/8),
00733 good_number(ipbps, ((codec_param->info.avg_bps+7)/8) +
00734 (40 * 1000 /
00735 codec_param->setting.frm_per_pkt /
00736 codec_param->info.frm_ptime)));
00737
00738 if (stat.rx.update_cnt == 0)
00739 strcpy(last_update, "never");
00740 else {
00741 pj_gettimeofday(&now);
00742 PJ_TIME_VAL_SUB(now, stat.rx.update);
00743 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
00744 now.sec / 3600,
00745 (now.sec % 3600) / 60,
00746 now.sec % 60,
00747 now.msec);
00748 }
00749
00750 printf(" RX stat last update: %s\n"
00751 " total %s packets %sB received (%sB +IP hdr)%s\n"
00752 " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
00753 " (msec) min avg max last dev\n"
00754 " loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
00755 " jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
00756 last_update,
00757 good_number(packets, stat.rx.pkt),
00758 good_number(bytes, stat.rx.bytes),
00759 good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
00760 "",
00761 stat.rx.loss,
00762 stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss),
00763 stat.rx.dup,
00764 stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss),
00765 stat.rx.reorder,
00766 stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss),
00767 "",
00768 stat.rx.loss_period.min / 1000.0,
00769 stat.rx.loss_period.mean / 1000.0,
00770 stat.rx.loss_period.max / 1000.0,
00771 stat.rx.loss_period.last / 1000.0,
00772 pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0,
00773 "",
00774 stat.rx.jitter.min / 1000.0,
00775 stat.rx.jitter.mean / 1000.0,
00776 stat.rx.jitter.max / 1000.0,
00777 stat.rx.jitter.last / 1000.0,
00778 pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0,
00779 ""
00780 );
00781
00782
00783 if (stat.tx.update_cnt == 0)
00784 strcpy(last_update, "never");
00785 else {
00786 pj_gettimeofday(&now);
00787 PJ_TIME_VAL_SUB(now, stat.tx.update);
00788 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
00789 now.sec / 3600,
00790 (now.sec % 3600) / 60,
00791 now.sec % 60,
00792 now.msec);
00793 }
00794
00795 printf(" TX stat last update: %s\n"
00796 " total %s packets %sB sent (%sB +IP hdr)%s\n"
00797 " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
00798 " (msec) min avg max last dev\n"
00799 " loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
00800 " jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
00801 last_update,
00802 good_number(packets, stat.tx.pkt),
00803 good_number(bytes, stat.tx.bytes),
00804 good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
00805 "",
00806 stat.tx.loss,
00807 stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss),
00808 stat.tx.dup,
00809 stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss),
00810 stat.tx.reorder,
00811 stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss),
00812 "",
00813 stat.tx.loss_period.min / 1000.0,
00814 stat.tx.loss_period.mean / 1000.0,
00815 stat.tx.loss_period.max / 1000.0,
00816 stat.tx.loss_period.last / 1000.0,
00817 pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0,
00818 "",
00819 stat.tx.jitter.min / 1000.0,
00820 stat.tx.jitter.mean / 1000.0,
00821 stat.tx.jitter.max / 1000.0,
00822 stat.tx.jitter.last / 1000.0,
00823 pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0,
00824 ""
00825 );
00826
00827
00828 printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
00829 stat.rtt.min / 1000.0,
00830 stat.rtt.mean / 1000.0,
00831 stat.rtt.max / 1000.0,
00832 stat.rtt.last / 1000.0,
00833 pj_math_stat_get_stddev(&stat.rtt) / 1000.0,
00834 ""
00835 );
00836
00837 #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
00838
00839 do {
00840 char loss[16], dup[16];
00841 char jitter[80];
00842 char toh[80];
00843 char plc[16], jba[16], jbr[16];
00844 char signal_lvl[16], noise_lvl[16], rerl[16];
00845 char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
00846 pjmedia_rtcp_xr_stat xr_stat;
00847
00848 if (pjmedia_stream_get_stat_xr(stream, &xr_stat) != PJ_SUCCESS)
00849 break;
00850
00851 puts("\nExtended reports:");
00852
00853
00854 puts(" Statistics Summary");
00855
00856 if (xr_stat.rx.stat_sum.l)
00857 sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
00858 else
00859 sprintf(loss, "(na)");
00860
00861 if (xr_stat.rx.stat_sum.d)
00862 sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
00863 else
00864 sprintf(dup, "(na)");
00865
00866 if (xr_stat.rx.stat_sum.j) {
00867 unsigned jmin, jmax, jmean, jdev;
00868
00869 SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
00870 port->info.fmt.det.aud.clock_rate);
00871 SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
00872 port->info.fmt.det.aud.clock_rate);
00873 SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
00874 port->info.fmt.det.aud.clock_rate);
00875 SAMPLES_TO_USEC(jdev,
00876 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
00877 port->info.fmt.det.aud.clock_rate);
00878 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
00879 jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
00880 } else
00881 sprintf(jitter, "(report not available)");
00882
00883 if (xr_stat.rx.stat_sum.t) {
00884 sprintf(toh, "%11d %11d %11d %11d",
00885 xr_stat.rx.stat_sum.toh.min,
00886 xr_stat.rx.stat_sum.toh.mean,
00887 xr_stat.rx.stat_sum.toh.max,
00888 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
00889 } else
00890 sprintf(toh, "(report not available)");
00891
00892 if (xr_stat.rx.stat_sum.update.sec == 0)
00893 strcpy(last_update, "never");
00894 else {
00895 pj_gettimeofday(&now);
00896 PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
00897 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
00898 now.sec / 3600,
00899 (now.sec % 3600) / 60,
00900 now.sec % 60,
00901 now.msec);
00902 }
00903
00904 printf(" RX last update: %s\n"
00905 " begin seq=%d, end seq=%d%s\n"
00906 " pkt loss=%s, dup=%s%s\n"
00907 " (msec) min avg max dev\n"
00908 " jitter : %s\n"
00909 " toh : %s\n",
00910 last_update,
00911 xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
00912 "",
00913 loss, dup,
00914 "",
00915 jitter,
00916 toh
00917 );
00918
00919 if (xr_stat.tx.stat_sum.l)
00920 sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
00921 else
00922 sprintf(loss, "(na)");
00923
00924 if (xr_stat.tx.stat_sum.d)
00925 sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
00926 else
00927 sprintf(dup, "(na)");
00928
00929 if (xr_stat.tx.stat_sum.j) {
00930 unsigned jmin, jmax, jmean, jdev;
00931
00932 SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
00933 port->info.fmt.det.aud.clock_rate);
00934 SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
00935 port->info.fmt.det.aud.clock_rate);
00936 SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
00937 port->info.fmt.det.aud.clock_rate);
00938 SAMPLES_TO_USEC(jdev,
00939 pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
00940 port->info.fmt.det.aud.clock_rate);
00941 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
00942 jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
00943 } else
00944 sprintf(jitter, "(report not available)");
00945
00946 if (xr_stat.tx.stat_sum.t) {
00947 sprintf(toh, "%11d %11d %11d %11d",
00948 xr_stat.tx.stat_sum.toh.min,
00949 xr_stat.tx.stat_sum.toh.mean,
00950 xr_stat.tx.stat_sum.toh.max,
00951 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
00952 } else
00953 sprintf(toh, "(report not available)");
00954
00955 if (xr_stat.tx.stat_sum.update.sec == 0)
00956 strcpy(last_update, "never");
00957 else {
00958 pj_gettimeofday(&now);
00959 PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
00960 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
00961 now.sec / 3600,
00962 (now.sec % 3600) / 60,
00963 now.sec % 60,
00964 now.msec);
00965 }
00966
00967 printf(" TX last update: %s\n"
00968 " begin seq=%d, end seq=%d%s\n"
00969 " pkt loss=%s, dup=%s%s\n"
00970 " (msec) min avg max dev\n"
00971 " jitter : %s\n"
00972 " toh : %s\n",
00973 last_update,
00974 xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
00975 "",
00976 loss, dup,
00977 "",
00978 jitter,
00979 toh
00980 );
00981
00982
00983 puts(" VoIP Metrics");
00984
00985 PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
00986 PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
00987 PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
00988 PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
00989 PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
00990 PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
00991 PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
00992
00993 switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
00994 case PJMEDIA_RTCP_XR_PLC_DIS:
00995 sprintf(plc, "DISABLED");
00996 break;
00997 case PJMEDIA_RTCP_XR_PLC_ENH:
00998 sprintf(plc, "ENHANCED");
00999 break;
01000 case PJMEDIA_RTCP_XR_PLC_STD:
01001 sprintf(plc, "STANDARD");
01002 break;
01003 case PJMEDIA_RTCP_XR_PLC_UNK:
01004 default:
01005 sprintf(plc, "UNKNOWN");
01006 break;
01007 }
01008
01009 switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
01010 case PJMEDIA_RTCP_XR_JB_FIXED:
01011 sprintf(jba, "FIXED");
01012 break;
01013 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
01014 sprintf(jba, "ADAPTIVE");
01015 break;
01016 default:
01017 sprintf(jba, "UNKNOWN");
01018 break;
01019 }
01020
01021 sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
01022
01023 if (xr_stat.rx.voip_mtc.update.sec == 0)
01024 strcpy(last_update, "never");
01025 else {
01026 pj_gettimeofday(&now);
01027 PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
01028 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
01029 now.sec / 3600,
01030 (now.sec % 3600) / 60,
01031 now.sec % 60,
01032 now.msec);
01033 }
01034
01035 printf(" RX last update: %s\n"
01036 " packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
01037 " burst : density=%d (%.2f%%), duration=%d%s\n"
01038 " gap : density=%d (%.2f%%), duration=%d%s\n"
01039 " delay : round trip=%d%s, end system=%d%s\n"
01040 " level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
01041 " quality : R factor=%s, ext R factor=%s\n"
01042 " MOS LQ=%s, MOS CQ=%s\n"
01043 " config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
01044 " JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
01045 last_update,
01046
01047 xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
01048 xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
01049
01050 xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
01051 xr_stat.rx.voip_mtc.burst_dur, "ms",
01052
01053 xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
01054 xr_stat.rx.voip_mtc.gap_dur, "ms",
01055
01056 xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
01057 xr_stat.rx.voip_mtc.end_sys_delay, "ms",
01058
01059 signal_lvl, "dB",
01060 noise_lvl, "dB",
01061 rerl, "",
01062
01063 r_factor, ext_r_factor, mos_lq, mos_cq,
01064
01065 plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
01066
01067 xr_stat.rx.voip_mtc.jb_nom, "ms",
01068 xr_stat.rx.voip_mtc.jb_max, "ms",
01069 xr_stat.rx.voip_mtc.jb_abs_max, "ms"
01070 );
01071
01072 PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
01073 PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
01074 PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
01075 PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
01076 PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
01077 PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
01078 PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
01079
01080 switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
01081 case PJMEDIA_RTCP_XR_PLC_DIS:
01082 sprintf(plc, "DISABLED");
01083 break;
01084 case PJMEDIA_RTCP_XR_PLC_ENH:
01085 sprintf(plc, "ENHANCED");
01086 break;
01087 case PJMEDIA_RTCP_XR_PLC_STD:
01088 sprintf(plc, "STANDARD");
01089 break;
01090 case PJMEDIA_RTCP_XR_PLC_UNK:
01091 default:
01092 sprintf(plc, "unknown");
01093 break;
01094 }
01095
01096 switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
01097 case PJMEDIA_RTCP_XR_JB_FIXED:
01098 sprintf(jba, "FIXED");
01099 break;
01100 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
01101 sprintf(jba, "ADAPTIVE");
01102 break;
01103 default:
01104 sprintf(jba, "unknown");
01105 break;
01106 }
01107
01108 sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
01109
01110 if (xr_stat.tx.voip_mtc.update.sec == 0)
01111 strcpy(last_update, "never");
01112 else {
01113 pj_gettimeofday(&now);
01114 PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
01115 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
01116 now.sec / 3600,
01117 (now.sec % 3600) / 60,
01118 now.sec % 60,
01119 now.msec);
01120 }
01121
01122 printf(" TX last update: %s\n"
01123 " packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
01124 " burst : density=%d (%.2f%%), duration=%d%s\n"
01125 " gap : density=%d (%.2f%%), duration=%d%s\n"
01126 " delay : round trip=%d%s, end system=%d%s\n"
01127 " level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
01128 " quality : R factor=%s, ext R factor=%s\n"
01129 " MOS LQ=%s, MOS CQ=%s\n"
01130 " config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
01131 " JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
01132 last_update,
01133
01134 xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
01135 xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
01136
01137 xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
01138 xr_stat.tx.voip_mtc.burst_dur, "ms",
01139
01140 xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
01141 xr_stat.tx.voip_mtc.gap_dur, "ms",
01142
01143 xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
01144 xr_stat.tx.voip_mtc.end_sys_delay, "ms",
01145
01146 signal_lvl, "dB",
01147 noise_lvl, "dB",
01148 rerl, "",
01149
01150 r_factor, ext_r_factor, mos_lq, mos_cq,
01151
01152 plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
01153
01154 xr_stat.tx.voip_mtc.jb_nom, "ms",
01155 xr_stat.tx.voip_mtc.jb_max, "ms",
01156 xr_stat.tx.voip_mtc.jb_abs_max, "ms"
01157 );
01158
01159
01160
01161 printf(" (msec) min avg max last dev\n");
01162 printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
01163 xr_stat.rtt.min / 1000.0,
01164 xr_stat.rtt.mean / 1000.0,
01165 xr_stat.rtt.max / 1000.0,
01166 xr_stat.rtt.last / 1000.0,
01167 pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0,
01168 ""
01169 );
01170 } while (0);
01171 #endif
01172
01173 }
01174