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