pjsip logo pjsip.org
Open source SIP stack and media stack for presence, im/instant messaging, and multimedia communication

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

Samples: Remote Streaming

This example mainly demonstrates how to stream media file to remote peer using RTP.

This file is pjsip-apps/src/samples/streamutil.c

00001 /* $Id: streamutil.c 2039 2008-06-20 22:44:47Z bennylp $ */
00002 /* 
00003  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
00004  *
00005  * This program is free software; you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License as published by
00007  * the Free Software Foundation; either version 2 of the License, or
00008  * (at your option) any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program; if not, write to the Free Software
00017  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
00018  */
00019 
00020 
00032 #include <pjlib.h>
00033 #include <pjlib-util.h>
00034 #include <pjmedia.h>
00035 #include <pjmedia-codec.h>
00036 #include <pjmedia/transport_srtp.h>
00037 
00038 #include <stdlib.h>     /* atoi() */
00039 #include <stdio.h>
00040 
00041 #include "util.h"
00042 
00043 
00044 static const char *desc = 
00045  " streamutil                                                           \n"
00046  "                                                                      \n"
00047  " PURPOSE:                                                             \n"
00048  "  Demonstrate how to use pjmedia stream component to transmit/receive \n"
00049  "  RTP packets to/from sound device.                                   \n"
00050  "\n"
00051  "\n"
00052  " USAGE:                                                               \n"
00053  "  streamutil [options]                                                \n"
00054  "\n"
00055  "\n"
00056  " Options:\n"
00057  "  --codec=CODEC         Set the codec name.                           \n"
00058  "  --local-port=PORT     Set local RTP port (default=4000)             \n"
00059  "  --remote=IP:PORT      Set the remote peer. If this option is set,   \n"
00060  "                        the program will transmit RTP audio to the    \n"
00061  "                        specified address. (default: recv only)       \n"
00062  "  --play-file=WAV       Send audio from the WAV file instead of from  \n"
00063  "                        the sound device.                             \n"
00064  "  --record-file=WAV     Record incoming audio to WAV file instead of  \n"
00065  "                        playing it to sound device.                   \n"
00066  "  --send-recv           Set stream direction to bidirectional.        \n"
00067  "  --send-only           Set stream direction to send only             \n"
00068  "  --recv-only           Set stream direction to recv only (default)   \n"
00069 
00070 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00071  "  --use-srtp[=NAME]     Enable SRTP with crypto suite NAME            \n"
00072  "                        e.g: AES_CM_128_HMAC_SHA1_80 (default),       \n"
00073  "                             AES_CM_128_HMAC_SHA1_32                  \n"
00074  "                        Use this option along with the TX & RX keys,  \n"
00075  "                        formated of 60 hex digits (e.g: E148DA..)      \n"
00076  "  --srtp-tx-key         SRTP key for transmiting                      \n"
00077  "  --srtp-rx-key         SRTP key for receiving                        \n"
00078 #endif
00079 
00080  "\n"
00081 ;
00082 
00083 
00084 
00085 
00086 #define THIS_FILE       "stream.c"
00087 
00088 
00089 
00090 /* Prototype */
00091 static void print_stream_stat(pjmedia_stream *stream);
00092 
00093 /* Prototype for LIBSRTP utility in file datatypes.c */
00094 int hex_string_to_octet_string(char *raw, char *hex, int len);
00095 
00096 /* 
00097  * Register all codecs. 
00098  */
00099 static pj_status_t init_codecs(pjmedia_endpt *med_endpt)
00100 {
00101     pj_status_t status;
00102 
00103     /* To suppress warning about unused var when all codecs are disabled */
00104     PJ_UNUSED_ARG(status);
00105 
00106 #if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
00107     status = pjmedia_codec_g711_init(med_endpt);
00108     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00109 #endif
00110 
00111 #if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC!=0
00112     status = pjmedia_codec_gsm_init(med_endpt);
00113     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00114 #endif
00115 
00116 #if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0
00117     status = pjmedia_codec_speex_init(med_endpt, 0, -1, -1);
00118     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00119 #endif
00120 
00121 #if defined(PJMEDIA_HAS_G722_CODEC) && PJMEDIA_HAS_G722_CODEC!=0
00122     status = pjmedia_codec_g722_init(med_endpt);
00123     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00124 #endif
00125 
00126 #if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0
00127     status = pjmedia_codec_l16_init(med_endpt, 0);
00128     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
00129 #endif
00130 
00131     return PJ_SUCCESS;
00132 }
00133 
00134 
00135 /* 
00136  * Create stream based on the codec, dir, remote address, etc. 
00137  */
00138 static pj_status_t create_stream( pj_pool_t *pool,
00139                                   pjmedia_endpt *med_endpt,
00140                                   const pjmedia_codec_info *codec_info,
00141                                   pjmedia_dir dir,
00142                                   pj_uint16_t local_port,
00143                                   const pj_sockaddr_in *rem_addr,
00144 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00145                                   pj_bool_t use_srtp,
00146                                   const pj_str_t *crypto_suite,
00147                                   const pj_str_t *srtp_tx_key,
00148                                   const pj_str_t *srtp_rx_key,
00149 #endif
00150                                   pjmedia_stream **p_stream )
00151 {
00152     pjmedia_stream_info info;
00153     pjmedia_transport *transport = NULL;
00154     pj_status_t status;
00155 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00156     pjmedia_transport *srtp_tp = NULL;
00157 #endif
00158 
00159 
00160     /* Reset stream info. */
00161     pj_bzero(&info, sizeof(info));
00162 
00163 
00164     /* Initialize stream info formats */
00165     info.type = PJMEDIA_TYPE_AUDIO;
00166     info.dir = dir;
00167     pj_memcpy(&info.fmt, codec_info, sizeof(pjmedia_codec_info));
00168     info.tx_pt = codec_info->pt;
00169     info.ssrc = pj_rand();
00170     
00171 #if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
00172     /* Set default RTCP XR enabled/disabled */
00173     info.rtcp_xr_enabled = PJ_TRUE;
00174 #endif
00175 
00176     /* Copy remote address */
00177     pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));
00178 
00179     /* If remote address is not set, set to an arbitrary address
00180      * (otherwise stream will assert).
00181      */
00182     if (info.rem_addr.addr.sa_family == 0) {
00183         const pj_str_t addr = pj_str("127.0.0.1");
00184         pj_sockaddr_in_init(&info.rem_addr.ipv4, &addr, 0);
00185     }
00186 
00187     /* Create media transport */
00188     status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,
00189                                           0, &transport);
00190     if (status != PJ_SUCCESS)
00191         return status;
00192 
00193 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00194     /* Check if SRTP enabled */
00195     if (use_srtp) {
00196         pjmedia_srtp_crypto tx_plc, rx_plc;
00197 
00198         status = pjmedia_transport_srtp_create(med_endpt, transport, 
00199                                                NULL, &srtp_tp);
00200         if (status != PJ_SUCCESS)
00201             return status;
00202 
00203         pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
00204         pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));
00205 
00206         tx_plc.key = *srtp_tx_key;
00207         tx_plc.name = *crypto_suite;
00208         rx_plc.key = *srtp_rx_key;
00209         rx_plc.name = *crypto_suite;
00210         
00211         status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
00212         if (status != PJ_SUCCESS)
00213             return status;
00214 
00215         transport = srtp_tp;
00216     }
00217 #endif
00218 
00219     /* Now that the stream info is initialized, we can create the 
00220      * stream.
00221      */
00222 
00223     status = pjmedia_stream_create( med_endpt, pool, &info, 
00224                                     transport, 
00225                                     NULL, p_stream);
00226 
00227     if (status != PJ_SUCCESS) {
00228         app_perror(THIS_FILE, "Error creating stream", status);
00229         pjmedia_transport_close(transport);
00230         return status;
00231     }
00232 
00233 
00234     return PJ_SUCCESS;
00235 }
00236 
00237 
00238 /*
00239  * usage()
00240  */
00241 static void usage()
00242 {
00243     puts(desc);
00244 }
00245 
00246 /*
00247  * main()
00248  */
00249 int main(int argc, char *argv[])
00250 {
00251     pj_caching_pool cp;
00252     pjmedia_endpt *med_endpt;
00253     pj_pool_t *pool;
00254     pjmedia_port *rec_file_port = NULL, *play_file_port = NULL;
00255     pjmedia_master_port *master_port = NULL;
00256     pjmedia_snd_port *snd_port = NULL;
00257     pjmedia_stream *stream = NULL;
00258     pjmedia_port *stream_port;
00259     char tmp[10];
00260     pj_status_t status; 
00261 
00262 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00263     /* SRTP variables */
00264     pj_bool_t use_srtp = PJ_FALSE;
00265     char tmp_tx_key[64];
00266     char tmp_rx_key[64];
00267     pj_str_t  srtp_tx_key = {NULL, 0};
00268     pj_str_t  srtp_rx_key = {NULL, 0};
00269     pj_str_t  srtp_crypto_suite = {NULL, 0};
00270     int tmp_key_len;
00271 #endif
00272 
00273     /* Default values */
00274     const pjmedia_codec_info *codec_info;
00275     pjmedia_dir dir = PJMEDIA_DIR_DECODING;
00276     pj_sockaddr_in remote_addr;
00277     pj_uint16_t local_port = 4000;
00278     char *codec_id = NULL;
00279     char *rec_file = NULL;
00280     char *play_file = NULL;
00281 
00282     enum {
00283         OPT_CODEC       = 'c',
00284         OPT_LOCAL_PORT  = 'p',
00285         OPT_REMOTE      = 'r',
00286         OPT_PLAY_FILE   = 'w',
00287         OPT_RECORD_FILE = 'R',
00288         OPT_SEND_RECV   = 'b',
00289         OPT_SEND_ONLY   = 's',
00290         OPT_RECV_ONLY   = 'i',
00291 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00292         OPT_USE_SRTP    = 'S',
00293 #endif
00294         OPT_SRTP_TX_KEY = 'x',
00295         OPT_SRTP_RX_KEY = 'y',
00296         OPT_HELP        = 'h',
00297     };
00298 
00299     struct pj_getopt_option long_options[] = {
00300         { "codec",          1, 0, OPT_CODEC },
00301         { "local-port",     1, 0, OPT_LOCAL_PORT },
00302         { "remote",         1, 0, OPT_REMOTE },
00303         { "play-file",      1, 0, OPT_PLAY_FILE },
00304         { "record-file",    1, 0, OPT_RECORD_FILE },
00305         { "send-recv",      0, 0, OPT_SEND_RECV },
00306         { "send-only",      0, 0, OPT_SEND_ONLY },
00307         { "recv-only",      0, 0, OPT_RECV_ONLY },
00308 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00309         { "use-srtp",       2, 0, OPT_USE_SRTP },
00310         { "srtp-tx-key",    1, 0, OPT_SRTP_TX_KEY },
00311         { "srtp-rx-key",    1, 0, OPT_SRTP_RX_KEY },
00312 #endif
00313         { "help",           0, 0, OPT_HELP },
00314         { NULL, 0, 0, 0 },
00315     };
00316 
00317     int c;
00318     int option_index;
00319 
00320 
00321     pj_bzero(&remote_addr, sizeof(remote_addr));
00322 
00323 
00324     /* init PJLIB : */
00325     status = pj_init();
00326     PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00327 
00328 
00329     /* Parse arguments */
00330     pj_optind = 0;
00331     while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1) {
00332 
00333         switch (c) {
00334         case OPT_CODEC:
00335             codec_id = pj_optarg;
00336             break;
00337 
00338         case OPT_LOCAL_PORT:
00339             local_port = (pj_uint16_t) atoi(pj_optarg);
00340             if (local_port < 1) {
00341                 printf("Error: invalid local port %s\n", pj_optarg);
00342                 return 1;
00343             }
00344             break;
00345 
00346         case OPT_REMOTE:
00347             {
00348                 pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
00349                 pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));
00350 
00351                 status = pj_sockaddr_in_init(&remote_addr, &ip, port);
00352                 if (status != PJ_SUCCESS) {
00353                     app_perror(THIS_FILE, "Invalid remote address", status);
00354                     return 1;
00355                 }
00356             }
00357             break;
00358 
00359         case OPT_PLAY_FILE:
00360             play_file = pj_optarg;
00361             break;
00362 
00363         case OPT_RECORD_FILE:
00364             rec_file = pj_optarg;
00365             break;
00366 
00367         case OPT_SEND_RECV:
00368             dir = PJMEDIA_DIR_ENCODING_DECODING;
00369             break;
00370 
00371         case OPT_SEND_ONLY:
00372             dir = PJMEDIA_DIR_ENCODING;
00373             break;
00374 
00375         case OPT_RECV_ONLY:
00376             dir = PJMEDIA_DIR_DECODING;
00377             break;
00378 
00379 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00380         case OPT_USE_SRTP:
00381             use_srtp = PJ_TRUE;
00382             if (pj_optarg) {
00383                 pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg));
00384             } else {
00385                 srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
00386             }
00387             break;
00388 
00389         case OPT_SRTP_TX_KEY:
00390             tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg, strlen(pj_optarg));
00391             pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);
00392             break;
00393 
00394         case OPT_SRTP_RX_KEY:
00395             tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg, strlen(pj_optarg));
00396             pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);
00397             break;
00398 #endif
00399 
00400         case OPT_HELP:
00401             usage();
00402             return 1;
00403 
00404         default:
00405             printf("Invalid options %s\n", argv[pj_optind]);
00406             return 1;
00407         }
00408 
00409     }
00410 
00411 
00412     /* Verify arguments. */
00413     if (dir & PJMEDIA_DIR_ENCODING) {
00414         if (remote_addr.sin_addr.s_addr == 0) {
00415             printf("Error: remote address must be set\n");
00416             return 1;
00417         }
00418     }
00419 
00420     if (play_file != NULL && dir != PJMEDIA_DIR_ENCODING) {
00421         printf("Direction is set to --send-only because of --play-file\n");
00422         dir = PJMEDIA_DIR_ENCODING;
00423     }
00424 
00425 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00426     /* SRTP validation */
00427     if (use_srtp) {
00428         if (!srtp_tx_key.slen || !srtp_rx_key.slen)
00429         {
00430             printf("Error: Key for each SRTP stream direction must be set\n");
00431             return 1;
00432         }
00433     }
00434 #endif
00435 
00436     /* Must create a pool factory before we can allocate any memory. */
00437     pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
00438 
00439     /* 
00440      * Initialize media endpoint.
00441      * This will implicitly initialize PJMEDIA too.
00442      */
00443     status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
00444     PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00445 
00446     /* Create memory pool for application purpose */
00447     pool = pj_pool_create( &cp.factory,     /* pool factory         */
00448                            "app",           /* pool name.           */
00449                            4000,            /* init size            */
00450                            4000,            /* increment size       */
00451                            NULL             /* callback on error    */
00452                            );
00453 
00454 
00455     /* Register all supported codecs */
00456     status = init_codecs(med_endpt);
00457     PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00458 
00459 
00460     /* Find which codec to use. */
00461     if (codec_id) {
00462         unsigned count = 1;
00463         pj_str_t str_codec_id = pj_str(codec_id);
00464         pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt);
00465         status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr,
00466                                                       &str_codec_id, &count,
00467                                                       &codec_info, NULL);
00468         if (status != PJ_SUCCESS) {
00469             printf("Error: unable to find codec %s\n", codec_id);
00470             return 1;
00471         }
00472     } else {
00473         /* Default to pcmu */
00474         pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt),
00475                                           0, &codec_info);
00476     }
00477 
00478     /* Create stream based on program arguments */
00479     status = create_stream(pool, med_endpt, codec_info, dir, local_port, 
00480                            &remote_addr, 
00481 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00482                            use_srtp, &srtp_crypto_suite, 
00483                            &srtp_tx_key, &srtp_rx_key,
00484 #endif
00485                            &stream);
00486     if (status != PJ_SUCCESS)
00487         goto on_exit;
00488 
00489 
00490     /* Get the port interface of the stream */
00491     status = pjmedia_stream_get_port( stream, &stream_port);
00492     PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00493 
00494 
00495     if (play_file) {
00496         unsigned wav_ptime;
00497 
00498         wav_ptime = stream_port->info.samples_per_frame * 1000 /
00499                     stream_port->info.clock_rate;
00500         status = pjmedia_wav_player_port_create(pool, play_file, wav_ptime,
00501                                                 0, -1, &play_file_port);
00502         if (status != PJ_SUCCESS) {
00503             app_perror(THIS_FILE, "Unable to use file", status);
00504             goto on_exit;
00505         }
00506 
00507         status = pjmedia_master_port_create(pool, play_file_port, stream_port,
00508                                             0, &master_port);
00509         if (status != PJ_SUCCESS) {
00510             app_perror(THIS_FILE, "Unable to create master port", status);
00511             goto on_exit;
00512         }
00513 
00514         status = pjmedia_master_port_start(master_port);
00515         if (status != PJ_SUCCESS) {
00516             app_perror(THIS_FILE, "Error starting master port", status);
00517             goto on_exit;
00518         }
00519 
00520         printf("Playing from WAV file %s..\n", play_file);
00521 
00522     } else if (rec_file) {
00523 
00524         status = pjmedia_wav_writer_port_create(pool, rec_file,
00525                                                 stream_port->info.clock_rate,
00526                                                 stream_port->info.channel_count,
00527                                                 stream_port->info.samples_per_frame,
00528                                                 stream_port->info.bits_per_sample,
00529                                                 0, 0, &rec_file_port);
00530         if (status != PJ_SUCCESS) {
00531             app_perror(THIS_FILE, "Unable to use file", status);
00532             goto on_exit;
00533         }
00534 
00535         status = pjmedia_master_port_create(pool, stream_port, rec_file_port, 
00536                                             0, &master_port);
00537         if (status != PJ_SUCCESS) {
00538             app_perror(THIS_FILE, "Unable to create master port", status);
00539             goto on_exit;
00540         }
00541 
00542         status = pjmedia_master_port_start(master_port);
00543         if (status != PJ_SUCCESS) {
00544             app_perror(THIS_FILE, "Error starting master port", status);
00545             goto on_exit;
00546         }
00547 
00548         printf("Recording to WAV file %s..\n", rec_file);
00549         
00550     } else {
00551 
00552         /* Create sound device port. */
00553         if (dir == PJMEDIA_DIR_ENCODING_DECODING)
00554             status = pjmedia_snd_port_create(pool, -1, -1, 
00555                                         stream_port->info.clock_rate,
00556                                         stream_port->info.channel_count,
00557                                         stream_port->info.samples_per_frame,
00558                                         stream_port->info.bits_per_sample,
00559                                         0, &snd_port);
00560         else if (dir == PJMEDIA_DIR_ENCODING)
00561             status = pjmedia_snd_port_create_rec(pool, -1, 
00562                                         stream_port->info.clock_rate,
00563                                         stream_port->info.channel_count,
00564                                         stream_port->info.samples_per_frame,
00565                                         stream_port->info.bits_per_sample,
00566                                         0, &snd_port);
00567         else
00568             status = pjmedia_snd_port_create_player(pool, -1, 
00569                                         stream_port->info.clock_rate,
00570                                         stream_port->info.channel_count,
00571                                         stream_port->info.samples_per_frame,
00572                                         stream_port->info.bits_per_sample,
00573                                         0, &snd_port);
00574 
00575 
00576         if (status != PJ_SUCCESS) {
00577             app_perror(THIS_FILE, "Unable to create sound port", status);
00578             goto on_exit;
00579         }
00580 
00581         /* Connect sound port to stream */
00582         status = pjmedia_snd_port_connect( snd_port, stream_port );
00583         PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00584 
00585     }
00586 
00587     /* Start streaming */
00588     pjmedia_stream_start(stream);
00589 
00590 
00591     /* Done */
00592 
00593     if (dir == PJMEDIA_DIR_DECODING)
00594         printf("Stream is active, dir is recv-only, local port is %d\n",
00595                local_port);
00596     else if (dir == PJMEDIA_DIR_ENCODING)
00597         printf("Stream is active, dir is send-only, sending to %s:%d\n",
00598                pj_inet_ntoa(remote_addr.sin_addr),
00599                pj_ntohs(remote_addr.sin_port));
00600     else
00601         printf("Stream is active, send/recv, local port is %d, "
00602                "sending to %s:%d\n",
00603                local_port,
00604                pj_inet_ntoa(remote_addr.sin_addr),
00605                pj_ntohs(remote_addr.sin_port));
00606 
00607 
00608     for (;;) {
00609 
00610         puts("");
00611         puts("Commands:");
00612         puts("  s     Display media statistics");
00613         puts("  q     Quit");
00614         puts("");
00615 
00616         printf("Command: "); fflush(stdout);
00617 
00618         fgets(tmp, sizeof(tmp), stdin);
00619 
00620         if (tmp[0] == 's')
00621             print_stream_stat(stream);
00622         else if (tmp[0] == 'q')
00623             break;
00624 
00625     }
00626 
00627 
00628 
00629     /* Start deinitialization: */
00630 on_exit:
00631 
00632     /* Destroy sound device */
00633     if (snd_port) {
00634         pjmedia_snd_port_destroy( snd_port );
00635         PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00636     }
00637 
00638     /* If there is master port, then we just need to destroy master port
00639      * (it will recursively destroy upstream and downstream ports, which
00640      * in this case are file_port and stream_port).
00641      */
00642     if (master_port) {
00643         pjmedia_master_port_destroy(master_port, PJ_TRUE);
00644         play_file_port = NULL;
00645         stream = NULL;
00646     }
00647 
00648     /* Destroy stream */
00649     if (stream) {
00650         pjmedia_transport *tp;
00651 
00652         tp = pjmedia_stream_get_transport(stream);
00653         pjmedia_stream_destroy(stream);
00654         
00655         pjmedia_transport_close(tp);
00656     }
00657 
00658     /* Destroy file ports */
00659     if (play_file_port)
00660         pjmedia_port_destroy( play_file_port );
00661     if (rec_file_port)
00662         pjmedia_port_destroy( rec_file_port );
00663 
00664 
00665     /* Release application pool */
00666     pj_pool_release( pool );
00667 
00668     /* Destroy media endpoint. */
00669     pjmedia_endpt_destroy( med_endpt );
00670 
00671     /* Destroy pool factory */
00672     pj_caching_pool_destroy( &cp );
00673 
00674     /* Shutdown PJLIB */
00675     pj_shutdown();
00676 
00677 
00678     return (status == PJ_SUCCESS) ? 0 : 1;
00679 }
00680 
00681 
00682 
00683 
00684 static const char *good_number(char *buf, pj_int32_t val)
00685 {
00686     if (val < 1000) {
00687         pj_ansi_sprintf(buf, "%d", val);
00688     } else if (val < 1000000) {
00689         pj_ansi_sprintf(buf, "%d.%dK", 
00690                         val / 1000,
00691                         (val % 1000) / 100);
00692     } else {
00693         pj_ansi_sprintf(buf, "%d.%02dM", 
00694                         val / 1000000,
00695                         (val % 1000000) / 10000);
00696     }
00697 
00698     return buf;
00699 }
00700 
00701 
00702 #define SAMPLES_TO_USEC(usec, samples, clock_rate) \
00703     do { \
00704         if (samples <= 4294) \
00705             usec = samples * 1000000 / clock_rate; \
00706         else { \
00707             usec = samples * 1000 / clock_rate; \
00708             usec *= 1000; \
00709         } \
00710     } while(0)
00711 
00712 #define PRINT_VOIP_MTC_VAL(s, v) \
00713     if (v == 127) \
00714         sprintf(s, "(na)"); \
00715     else \
00716         sprintf(s, "%d", v)
00717 
00718 
00719 /*
00720  * Print stream statistics
00721  */
00722 static void print_stream_stat(pjmedia_stream *stream)
00723 {
00724     char duration[80], last_update[80];
00725     char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
00726     pjmedia_port *port;
00727     pjmedia_rtcp_stat stat;
00728     pj_time_val now;
00729 
00730 
00731     pj_gettimeofday(&now);
00732     pjmedia_stream_get_stat(stream, &stat);
00733     pjmedia_stream_get_port(stream, &port);
00734 
00735     puts("Stream statistics:");
00736 
00737     /* Print duration */
00738     PJ_TIME_VAL_SUB(now, stat.start);
00739     sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld",
00740             now.sec / 3600,
00741             (now.sec % 3600) / 60,
00742             (now.sec % 60),
00743             now.msec);
00744 
00745 
00746     printf(" Info: audio %.*s@%dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
00747         (int)port->info.encoding_name.slen,
00748         port->info.encoding_name.ptr,
00749         port->info.clock_rate,
00750         port->info.samples_per_frame * 1000 / port->info.clock_rate,
00751         good_number(bps, port->info.bytes_per_frame * port->info.clock_rate /
00752                     port->info.samples_per_frame),
00753         good_number(ipbps, (port->info.bytes_per_frame+32) * 
00754                             port->info.clock_rate / port->info.clock_rate));
00755 
00756     if (stat.rx.update_cnt == 0)
00757         strcpy(last_update, "never");
00758     else {
00759         pj_gettimeofday(&now);
00760         PJ_TIME_VAL_SUB(now, stat.rx.update);
00761         sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
00762                 now.sec / 3600,
00763                 (now.sec % 3600) / 60,
00764                 now.sec % 60,
00765                 now.msec);
00766     }
00767 
00768     printf(" RX stat last update: %s\n"
00769            "    total %s packets %sB received (%sB +IP hdr)%s\n"
00770            "    pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
00771            "          (msec)    min     avg     max     last    dev\n"
00772            "    loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
00773            "    jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
00774            last_update,
00775            good_number(packets, stat.rx.pkt),
00776            good_number(bytes, stat.rx.bytes),
00777            good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
00778            "",
00779            stat.rx.loss,
00780            stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss),
00781            stat.rx.dup, 
00782            stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss),
00783            stat.rx.reorder, 
00784            stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss),
00785            "",
00786            stat.rx.loss_period.min / 1000.0, 
00787            stat.rx.loss_period.mean / 1000.0, 
00788            stat.rx.loss_period.max / 1000.0,
00789            stat.rx.loss_period.last / 1000.0,
00790            pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0,
00791            "",
00792            stat.rx.jitter.min / 1000.0,
00793            stat.rx.jitter.mean / 1000.0,
00794            stat.rx.jitter.max / 1000.0,
00795            stat.rx.jitter.last / 1000.0,
00796            pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0,
00797            ""
00798            );
00799 
00800 
00801     if (stat.tx.update_cnt == 0)
00802         strcpy(last_update, "never");
00803     else {
00804         pj_gettimeofday(&now);
00805         PJ_TIME_VAL_SUB(now, stat.tx.update);
00806         sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
00807                 now.sec / 3600,
00808                 (now.sec % 3600) / 60,
00809                 now.sec % 60,
00810                 now.msec);
00811     }
00812 
00813     printf(" TX stat last update: %s\n"
00814            "    total %s packets %sB sent (%sB +IP hdr)%s\n"
00815            "    pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
00816            "          (msec)    min     avg     max     last    dev\n"
00817            "    loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
00818            "    jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
00819            last_update,
00820            good_number(packets, stat.tx.pkt),
00821            good_number(bytes, stat.tx.bytes),
00822            good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
00823            "",
00824            stat.tx.loss,
00825            stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss),
00826            stat.tx.dup, 
00827            stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss),
00828            stat.tx.reorder, 
00829            stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss),
00830            "",
00831            stat.tx.loss_period.min / 1000.0, 
00832            stat.tx.loss_period.mean / 1000.0, 
00833            stat.tx.loss_period.max / 1000.0,
00834            stat.tx.loss_period.last / 1000.0,
00835            pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0,
00836            "",
00837            stat.tx.jitter.min / 1000.0,
00838            stat.tx.jitter.mean / 1000.0,
00839            stat.tx.jitter.max / 1000.0,
00840            stat.tx.jitter.last / 1000.0,
00841            pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0,
00842            ""
00843            );
00844 
00845 
00846     printf(" RTT delay     : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n", 
00847            stat.rtt.min / 1000.0,
00848            stat.rtt.mean / 1000.0,
00849            stat.rtt.max / 1000.0,
00850            stat.rtt.last / 1000.0,
00851            pj_math_stat_get_stddev(&stat.rtt) / 1000.0,
00852            ""
00853            );
00854 
00855 #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
00856     /* RTCP XR Reports */
00857     do {
00858         char loss[16], dup[16];
00859         char jitter[80];
00860         char toh[80];
00861         char plc[16], jba[16], jbr[16];
00862         char signal_lvl[16], noise_lvl[16], rerl[16];
00863         char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
00864         pjmedia_rtcp_xr_stat xr_stat;
00865 
00866         if (pjmedia_stream_get_stat_xr(stream, &xr_stat) != PJ_SUCCESS)
00867             break;
00868 
00869         puts("\nExtended reports:");
00870 
00871         /* Statistics Summary */
00872         puts(" Statistics Summary");
00873 
00874         if (xr_stat.rx.stat_sum.l)
00875             sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
00876         else
00877             sprintf(loss, "(na)");
00878 
00879         if (xr_stat.rx.stat_sum.d)
00880             sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
00881         else
00882             sprintf(dup, "(na)");
00883 
00884         if (xr_stat.rx.stat_sum.j) {
00885             unsigned jmin, jmax, jmean, jdev;
00886 
00887             SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min, 
00888                             port->info.clock_rate);
00889             SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max, 
00890                             port->info.clock_rate);
00891             SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean, 
00892                             port->info.clock_rate);
00893             SAMPLES_TO_USEC(jdev, 
00894                            pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
00895                            port->info.clock_rate);
00896             sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", 
00897                     jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
00898         } else
00899             sprintf(jitter, "(report not available)");
00900 
00901         if (xr_stat.rx.stat_sum.t) {
00902             sprintf(toh, "%11d %11d %11d %11d", 
00903                     xr_stat.rx.stat_sum.toh.min,
00904                     xr_stat.rx.stat_sum.toh.mean,
00905                     xr_stat.rx.stat_sum.toh.max,
00906                     pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
00907         } else
00908             sprintf(toh, "(report not available)");
00909 
00910         if (xr_stat.rx.stat_sum.update.sec == 0)
00911             strcpy(last_update, "never");
00912         else {
00913             pj_gettimeofday(&now);
00914             PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
00915             sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
00916                     now.sec / 3600,
00917                     (now.sec % 3600) / 60,
00918                     now.sec % 60,
00919                     now.msec);
00920         }
00921 
00922         printf(" RX last update: %s\n"
00923                "    begin seq=%d, end seq=%d%s\n"
00924                "    pkt loss=%s, dup=%s%s\n"
00925                "          (msec)    min     avg     max     dev\n"
00926                "    jitter     : %s\n"
00927                "    toh        : %s\n",
00928                last_update,
00929                xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
00930                "",
00931                loss, dup,
00932                "",
00933                jitter,
00934                toh
00935                );
00936 
00937         if (xr_stat.tx.stat_sum.l)
00938             sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
00939         else
00940             sprintf(loss, "(na)");
00941 
00942         if (xr_stat.tx.stat_sum.d)
00943             sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
00944         else
00945             sprintf(dup, "(na)");
00946 
00947         if (xr_stat.tx.stat_sum.j) {
00948             unsigned jmin, jmax, jmean, jdev;
00949 
00950             SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min, 
00951                             port->info.clock_rate);
00952             SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max, 
00953                             port->info.clock_rate);
00954             SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean, 
00955                             port->info.clock_rate);
00956             SAMPLES_TO_USEC(jdev, 
00957                            pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
00958                            port->info.clock_rate);
00959             sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", 
00960                     jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
00961         } else
00962             sprintf(jitter, "(report not available)");
00963 
00964         if (xr_stat.tx.stat_sum.t) {
00965             sprintf(toh, "%11d %11d %11d %11d", 
00966                     xr_stat.tx.stat_sum.toh.min,
00967                     xr_stat.tx.stat_sum.toh.mean,
00968                     xr_stat.tx.stat_sum.toh.max,
00969                     pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
00970         } else
00971             sprintf(toh,    "(report not available)");
00972 
00973         if (xr_stat.tx.stat_sum.update.sec == 0)
00974             strcpy(last_update, "never");
00975         else {
00976             pj_gettimeofday(&now);
00977             PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
00978             sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
00979                     now.sec / 3600,
00980                     (now.sec % 3600) / 60,
00981                     now.sec % 60,
00982                     now.msec);
00983         }
00984 
00985         printf(" TX last update: %s\n"
00986                "    begin seq=%d, end seq=%d%s\n"
00987                "    pkt loss=%s, dup=%s%s\n"
00988                "          (msec)    min     avg     max     dev\n"
00989                "    jitter     : %s\n"
00990                "    toh        : %s\n",
00991                last_update,
00992                xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,