Added stream.c example to demonstrate how to stream audio from both sound device or WAV file

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@407 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip-apps/build/Samples-vc.mak b/pjsip-apps/build/Samples-vc.mak
index 43ed926..40ee2e9 100644
--- a/pjsip-apps/build/Samples-vc.mak
+++ b/pjsip-apps/build/Samples-vc.mak
@@ -37,10 +37,10 @@
 BINDIR = ..\bin\samples
 
 
-SAMPLES = $(BINDIR)\simpleua.exe $(BINDIR)\playfile.exe $(BINDIR)\playsine.exe \
+SAMPLES = $(BINDIR)\simpleua.exe $(BINDIR)\playfile.exe $(BINDIR)\playsine.exe\
 	  $(BINDIR)\confsample.exe $(BINDIR)\sndinfo.exe \
 	  $(BINDIR)\level.exe $(BINDIR)\recfile.exe  \
-	  $(BINDIR)\resampleplay.exe $(BINDIR)\siprtp.exe
+	  $(BINDIR)\resampleplay.exe $(BINDIR)\siprtp.exe $(BINDIR)\stream.exe
 
 
 all: $(OBJDIR) $(SAMPLES)
diff --git a/pjsip-apps/build/Samples.mak b/pjsip-apps/build/Samples.mak
index 749c939..d048ec5 100644
--- a/pjsip-apps/build/Samples.mak
+++ b/pjsip-apps/build/Samples.mak
@@ -39,7 +39,7 @@
 BINDIR := ../bin/samples
 
 SAMPLES := simpleua playfile playsine confsample sndinfo level recfile resampleplay \
-	   siprtp
+	   siprtp stream
 
 EXES := $(foreach file, $(SAMPLES), $(BINDIR)/$(file)-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(HOST_EXE))
 
diff --git a/pjsip-apps/build/samples.dsp b/pjsip-apps/build/samples.dsp
index be8b213..96289e7 100644
--- a/pjsip-apps/build/samples.dsp
+++ b/pjsip-apps/build/samples.dsp
@@ -120,6 +120,10 @@
 
 SOURCE=..\src\samples\sndinfo.c
 # End Source File
+# Begin Source File
+
+SOURCE=..\src\samples\stream.c
+# End Source File
 # End Group
 # Begin Group "Header Files"
 
@@ -137,5 +141,9 @@
 
 SOURCE=".\Samples-vc.mak"
 # End Source File
+# Begin Source File
+
+SOURCE=.\Samples.mak
+# End Source File
 # End Target
 # End Project
diff --git a/pjsip-apps/src/samples/debug.c b/pjsip-apps/src/samples/debug.c
index 259a7de..b6c1005 100644
--- a/pjsip-apps/src/samples/debug.c
+++ b/pjsip-apps/src/samples/debug.c
@@ -27,5 +27,5 @@
  * E.g.:
  *  #include "playfile.c"
  */
-#include "siprtp.c"
+#include "stream.c"
 
diff --git a/pjsip-apps/src/samples/playfile.c b/pjsip-apps/src/samples/playfile.c
index 215ff41..1ae9a6c 100644
--- a/pjsip-apps/src/samples/playfile.c
+++ b/pjsip-apps/src/samples/playfile.c
@@ -176,3 +176,4 @@
     /* Done. */
     return 0;
 }
+
diff --git a/pjsip-apps/src/samples/siprtp.c b/pjsip-apps/src/samples/siprtp.c
index 91cc93d..ca7f07d 100644
--- a/pjsip-apps/src/samples/siprtp.c
+++ b/pjsip-apps/src/samples/siprtp.c
@@ -18,6 +18,43 @@
  */
 
 
+
+
+/* Usage */
+static const char *USAGE = 
+" PURPOSE:								    \n"
+"   This program establishes SIP INVITE session and media, and calculate    \n"
+"   the media quality (packet lost, jitter, rtt, etc.). Unlike normal	    \n"
+"   pjmedia applications, this program bypasses all pjmedia stream	    \n"
+"   framework and transmit encoded RTP packets manually using own thread.   \n"
+"\n"
+" USAGE:\n"
+"   siprtp [options]        => to start in server mode\n"
+"   siprtp [options] URL    => to start in client mode\n"
+"\n"
+" Program options:\n"
+"   --count=N,        -c    Set number of calls to create (default:1) \n"
+"\n"
+" Address and ports options:\n"
+"   --local-port=PORT,-p    Set local SIP port (default: 5060)\n"
+"   --rtp-port=PORT,  -r    Set start of RTP port (default: 4000)\n"
+"   --ip-addr=IP,     -i    Set local IP address to use (otherwise it will\n"
+"                           try to determine local IP address from hostname)\n"
+"\n"
+" Logging Options:\n"
+"   --log-level=N,    -l    Set log verbosity level (default=5)\n"
+"   --app-log-level=N       Set app screen log verbosity (default=3)\n"
+"   --log-file=FILE         Write log to file FILE\n"
+"\n"
+" Codec Options:\n"
+"   --a-pt=PT               Set audio payload type to PT (default=0)\n"
+"   --a-name=NAME           Set audio codec name to NAME (default=pcmu)\n"
+"   --a-clock=RATE          Set audio codec rate to RATE Hz (default=8000Hz)\n"
+"   --a-bitrate=BPS         Set audio codec bitrate to BPS (default=64000bps)\n"
+"   --a-ptime=MS            Set audio frame time to MS msec (default=20ms)\n"
+;
+
+
 /* Include all headers. */
 #include <pjsip.h>
 #include <pjmedia.h>
@@ -725,35 +762,6 @@
 }
 
 
-/* Usage */
-static const char *USAGE = 
-"Usage:\n"
-"   siprtp [options]        => to start in server mode\n"
-"   siprtp [options] URL    => to start in client mode\n"
-"\n"
-"Program options:\n"
-"   --count=N,        -c    Set number of calls to create (default:1) \n"
-"\n"
-"Address and ports options:\n"
-"   --local-port=PORT,-p    Set local SIP port (default: 5060)\n"
-"   --rtp-port=PORT,  -r    Set start of RTP port (default: 4000)\n"
-"   --ip-addr=IP,     -i    Set local IP address to use (otherwise it will\n"
-"                           try to determine local IP address from hostname)\n"
-"\n"
-"Logging Options:\n"
-"   --log-level=N,    -l    Set log verbosity level (default=5)\n"
-"   --app-log-level=N       Set app screen log verbosity (default=3)\n"
-"   --log-file=FILE         Write log to file FILE\n"
-"\n"
-"Codec Options:\n"
-"   --a-pt=PT               Set audio payload type to PT (default=0)\n"
-"   --a-name=NAME           Set audio codec name to NAME (default=pcmu)\n"
-"   --a-clock=RATE          Set audio codec rate to RATE Hz (default=8000 Hz)\n"
-"   --a-bitrate=BPS         Set audio codec bitrate to BPS (default=64000 bps)\n"
-"   --a-ptime=MS            Set audio frame time to MS msec (default=20 msec)\n"
-;
-
-
 /* Init application options */
 static pj_status_t init_options(int argc, char *argv[])
 {
diff --git a/pjsip-apps/src/samples/stream.c b/pjsip-apps/src/samples/stream.c
new file mode 100644
index 0000000..8503c1b
--- /dev/null
+++ b/pjsip-apps/src/samples/stream.c
@@ -0,0 +1,681 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+
+
+static const char *desc = 
+ " stream.c								\n"
+ "									\n"
+ " PURPOSE:								\n"
+ "  Demonstrate how to use pjmedia stream component to transmit/receive \n"
+ "  RTP packets to/from sound device.		    			\n"
+ "\n"
+ "\n"
+ " USAGE:								\n"
+ "  stream [options]                                                    \n"
+ "\n"
+ "\n"
+ " Options:\n"
+ "  --codec=CODEC         Set the codec name. Valid codec names are:	\n"
+ "                        pcma, pcmu, gsm, speexnb, speexwb, speexuwb.	\n"
+ "                        (default: pcma).				\n"
+ "  --local-port=PORT     Set local RTP port (default=4000)		\n"
+ "  --remote=IP:PORT      Set the remote peer. If this option is set,	\n"
+ "                        the program will transmit RTP audio to the	\n"
+ "                        specified address. (default: recv only)	\n"
+ "  --play-file=WAV       Send audio from the WAV file instead of from	\n"
+ "                        the sound device.				\n"
+// "  --record-file=WAV     Record incoming audio to WAV file instead of	\n"
+// "                        playing it to sound device.			\n"
+ "  --send-recv           Set stream direction to bidirectional.        \n"
+ "  --send-only           Set stream direction to send only		\n"
+ "  --recv-only           Set stream direction to recv only (default)   \n"
+ "\n"
+;
+
+
+
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+
+#include <stdlib.h>	/* atoi() */
+#include <stdio.h>
+
+#include "util.h"
+
+
+#define THIS_FILE	"stream.c"
+
+
+struct codec
+{
+    char    *name;
+    char    *encoding_name;
+    int	     pt;
+    int	     clock_rate;
+} codec[] = 
+{
+    { "pcma",	  "pcma",  PJMEDIA_RTP_PT_PCMA, 8000 },
+    { "pcmu",	  "pcmu",  PJMEDIA_RTP_PT_PCMU, 8000 },
+    { "gsm",	  "gsm",   PJMEDIA_RTP_PT_GSM, 8000 },
+    { "speexnb",  "speex", 120, 8000 },
+    { "speexwb",  "speex", 121, 16000 },
+    { "speexuwb", "speex", 122, 32000 },
+};
+
+
+/* Prototype */
+static void print_stream_stat(pjmedia_stream *stream);
+
+
+/* 
+ * Register all codecs. 
+ */
+static pj_status_t init_codecs(pjmedia_endpt *med_endpt)
+{
+    pj_status_t status;
+
+    status = pjmedia_codec_g711_init(med_endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    status = pjmedia_codec_gsm_init(med_endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    status = pjmedia_codec_speex_init(med_endpt, 0, -1, -1);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    return PJ_SUCCESS;
+}
+
+
+/* 
+ * Create stream based on the codec, dir, remote address, etc. 
+ */
+static pj_status_t create_stream( pj_pool_t *pool,
+				  pjmedia_endpt *med_endpt,
+				  unsigned codec_index,
+				  pjmedia_dir dir,
+				  pj_uint16_t local_port,
+				  const pj_sockaddr_in *rem_addr,
+				  pjmedia_stream **p_stream )
+{
+    pjmedia_stream_info info;
+    pj_status_t status;
+
+
+    /* Reset stream info. */
+    pj_memset(&info, 0, sizeof(info));
+
+
+    /* Initialize stream info formats */
+    info.type = PJMEDIA_TYPE_AUDIO;
+    info.dir = dir;
+    info.fmt.encoding_name = pj_str(codec[codec_index].encoding_name);
+    info.fmt.type = PJMEDIA_TYPE_AUDIO;
+    info.fmt.sample_rate = codec[codec_index].clock_rate;
+    info.fmt.pt = codec[codec_index].pt;
+    info.tx_pt = codec[codec_index].pt;
+    info.ssrc = pj_rand();
+    
+
+    /* Copy remote address */
+    pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));
+
+
+    /* Create RTP socket */
+    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 
+			    &info.sock_info.rtp_sock);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Bind RTP socket to local port */
+    info.sock_info.rtp_addr_name.sin_family = PJ_AF_INET;
+    info.sock_info.rtp_addr_name.sin_port = pj_htons(local_port);
+
+    status = pj_sock_bind(info.sock_info.rtp_sock, 
+			  &info.sock_info.rtp_addr_name,
+			  sizeof(pj_sockaddr_in));
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to bind RTP socket", status);
+	pj_sock_close(info.sock_info.rtp_sock);
+	return status;
+    }
+
+
+    /* Create RTCP socket */
+    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0,
+			    &info.sock_info.rtcp_sock);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Bind RTP socket to local port + 1 */
+    ++local_port;
+    info.sock_info.rtcp_addr_name.sin_family = PJ_AF_INET;
+    info.sock_info.rtcp_addr_name.sin_port = pj_htons(local_port);
+
+    status = pj_sock_bind(info.sock_info.rtcp_sock, 
+			  &info.sock_info.rtcp_addr_name,
+			  sizeof(pj_sockaddr_in));
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to bind RTCP socket", status);
+	pj_sock_close(info.sock_info.rtp_sock);
+	pj_sock_close(info.sock_info.rtcp_sock);
+	return status;
+    }
+
+
+    /* Now that the stream info is initialized, we can create the 
+     * stream.
+     */
+
+    status = pjmedia_stream_create( med_endpt, pool, &info, NULL, p_stream);
+
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Error creating stream", status);
+	pj_sock_close(info.sock_info.rtp_sock);
+	pj_sock_close(info.sock_info.rtcp_sock);
+	return status;
+    }
+
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+    pj_caching_pool cp;
+    pjmedia_endpt *med_endpt;
+    pj_pool_t *pool;
+    pjmedia_port *rec_file_port = NULL, *play_file_port = NULL;
+    pjmedia_master_port *master_port = NULL;
+    pjmedia_snd_port *snd_port = NULL;
+    pjmedia_stream *stream = NULL;
+    pjmedia_port *stream_port;
+    char tmp[10];
+    pj_status_t status;
+
+
+    /* Default values */
+    int codec_index = 0;
+    pjmedia_dir dir = PJMEDIA_DIR_DECODING;
+    pj_sockaddr_in remote_addr;
+    pj_uint16_t local_port = 4000;
+    char *rec_file = NULL;
+    char *play_file = NULL;
+
+    enum {
+	OPT_CODEC	= 'c',
+	OPT_LOCAL_PORT	= 'p',
+	OPT_REMOTE	= 'r',
+	OPT_PLAY_FILE	= 'w',
+	OPT_RECORD_FILE	= 'R',
+	OPT_SEND_RECV	= 'b',
+	OPT_SEND_ONLY	= 's',
+	OPT_RECV_ONLY	= 'i',
+    };
+
+    struct pj_getopt_option long_options[] = {
+	{ "codec",	    1, 0, OPT_CODEC },
+	{ "local-port",	    1, 0, OPT_LOCAL_PORT },
+	{ "remote",	    1, 0, OPT_REMOTE },
+	{ "play-file",	    1, 0, OPT_PLAY_FILE },
+	{ "record-file",    1, 0, OPT_RECORD_FILE },
+	{ "send-recv",      0, 0, OPT_SEND_RECV },
+	{ "send-only",      0, 0, OPT_SEND_ONLY },
+	{ "recv-only",      0, 0, OPT_RECV_ONLY },
+	{ NULL, 0, 0, 0 },
+    };
+
+    int c;
+    int option_index;
+
+
+    pj_memset(&remote_addr, 0, sizeof(remote_addr));
+
+
+    /* init PJLIB : */
+    status = pj_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+    /* Parse arguments */
+    pj_optind = 0;
+    while((c=pj_getopt_long(argc,argv, "", long_options, &option_index))!=-1) {
+
+	switch (c) {
+	case OPT_CODEC:
+	    {
+		unsigned i;
+		for (i=0; i<PJ_ARRAY_SIZE(codec); ++i) {
+		    if (pj_ansi_stricmp(pj_optarg, codec[i].name)==0) {
+			break;
+		    }
+		}
+
+		if (i == PJ_ARRAY_SIZE(codec)) {
+		    printf("Error: unknown codec %s\n", pj_optarg);
+		    return 1;
+		}
+
+		codec_index = i;
+	    }
+	    break;
+
+	case OPT_LOCAL_PORT:
+	    local_port = (pj_uint16_t) atoi(pj_optarg);
+	    if (local_port < 1) {
+		printf("Error: invalid local port %s\n", pj_optarg);
+		return 1;
+	    }
+	    break;
+
+	case OPT_REMOTE:
+	    {
+		pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
+		pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));
+
+		status = pj_sockaddr_in_init(&remote_addr, &ip, port);
+		if (status != PJ_SUCCESS) {
+		    app_perror(THIS_FILE, "Invalid remote address", status);
+		    return 1;
+		}
+	    }
+	    break;
+
+	case OPT_PLAY_FILE:
+	    play_file = pj_optarg;
+	    break;
+
+	case OPT_RECORD_FILE:
+	    rec_file = pj_optarg;
+	    break;
+
+	case OPT_SEND_RECV:
+	    dir = PJMEDIA_DIR_ENCODING_DECODING;
+	    break;
+
+	case OPT_SEND_ONLY:
+	    dir = PJMEDIA_DIR_ENCODING;
+	    break;
+
+	case OPT_RECV_ONLY:
+	    dir = PJMEDIA_DIR_DECODING;
+	    break;
+
+	default:
+	    printf("Invalid options %s\n", argv[pj_optind]);
+	    return 1;
+	}
+
+    }
+
+
+    /* Verify arguments. */
+    if (dir & PJMEDIA_DIR_ENCODING) {
+	if (remote_addr.sin_addr.s_addr == 0) {
+	    printf("Error: remote address must be set\n");
+	    return 1;
+	}
+    }
+
+    if (play_file != NULL && dir != PJMEDIA_DIR_ENCODING) {
+	printf("Direction is set to --send-only because of --play-file\n");
+	dir = PJMEDIA_DIR_ENCODING;
+    }
+
+
+    /* Must create a pool factory before we can allocate any memory. */
+    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+    /* 
+     * Initialize media endpoint.
+     * This will implicitly initialize PJMEDIA too.
+     */
+    status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Create memory pool for application purpose */
+    pool = pj_pool_create( &cp.factory,	    /* pool factory	    */
+			   "app",	    /* pool name.	    */
+			   4000,	    /* init size	    */
+			   4000,	    /* increment size	    */
+			   NULL		    /* callback on error    */
+			   );
+
+
+    /* Register all supported codecs */
+    status = init_codecs(med_endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+    /* Create stream based on program arguments */
+    status = create_stream(pool, med_endpt, codec_index, dir, local_port, 
+			   &remote_addr, &stream);
+    if (status != PJ_SUCCESS)
+	goto on_exit;
+
+
+    /* Get the port interface of the stream */
+    status = pjmedia_stream_get_port( stream, &stream_port);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+    if (play_file) {
+
+	status = pjmedia_file_player_port_create(pool, play_file, 0,
+						 -1, NULL, &play_file_port);
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to use file", status);
+	    goto on_exit;
+	}
+
+	status = pjmedia_master_port_create(pool, play_file_port, stream_port,
+					    0, &master_port);
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to create master port", status);
+	    goto on_exit;
+	}
+
+	status = pjmedia_master_port_start(master_port);
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Error starting master port", status);
+	    goto on_exit;
+	}
+
+
+    } else {
+
+	/* Create sound device port. */
+	if (dir == PJMEDIA_DIR_ENCODING_DECODING)
+	    status = pjmedia_snd_port_create(pool, -1, -1, 
+					stream_port->info.sample_rate,
+					stream_port->info.channel_count,
+					stream_port->info.samples_per_frame,
+					stream_port->info.bits_per_sample,
+					0, &snd_port);
+	else if (dir == PJMEDIA_DIR_ENCODING)
+	    status = pjmedia_snd_port_create_rec(pool, -1, 
+					stream_port->info.sample_rate,
+					stream_port->info.channel_count,
+					stream_port->info.samples_per_frame,
+					stream_port->info.bits_per_sample,
+					0, &snd_port);
+	else
+	    status = pjmedia_snd_port_create_player(pool, -1, 
+					stream_port->info.sample_rate,
+					stream_port->info.channel_count,
+					stream_port->info.samples_per_frame,
+					stream_port->info.bits_per_sample,
+					0, &snd_port);
+
+
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to create sound port", status);
+	    goto on_exit;
+	}
+
+	/* Connect sound port to stream */
+	status = pjmedia_snd_port_connect( snd_port, stream_port );
+	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    }
+
+
+    /* Done */
+
+    if (dir == PJMEDIA_DIR_DECODING)
+	printf("Stream is active, dir is recv-only, local port is %d\n",
+	       local_port);
+    else if (dir == PJMEDIA_DIR_ENCODING)
+	printf("Stream is active, dir is send-only, sending to %s:%d\n",
+	       pj_inet_ntoa(remote_addr.sin_addr),
+	       pj_ntohs(remote_addr.sin_port));
+    else
+	printf("Stream is active, send/recv, local port is %d, "
+	       "sending to %s:%d\n",
+	       local_port,
+	       pj_inet_ntoa(remote_addr.sin_addr),
+	       pj_ntohs(remote_addr.sin_port));
+
+
+    for (;;) {
+
+	puts("");
+	puts("Commands:");
+	puts("  s     Display media statistics");
+	puts("  q     Quit");
+	puts("");
+
+	printf("Command: "); fflush(stdout);
+
+	fgets(tmp, sizeof(tmp), stdin);
+
+	if (tmp[0] == 's')
+	    print_stream_stat(stream);
+	else if (tmp[0] == 'q')
+	    break;
+
+    }
+
+
+
+    /* Start deinitialization: */
+on_exit:
+
+    /* Destroy sound device */
+    if (snd_port) {
+	pjmedia_snd_port_destroy( snd_port );
+	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+    }
+
+    /* If there is master port, then we just need to destroy master port
+     * (it will recursively destroy upstream and downstream ports, which
+     * in this case are file_port and stream_port).
+     */
+    if (master_port) {
+	pjmedia_master_port_destroy(master_port);
+	play_file_port = NULL;
+	stream = NULL;
+    }
+
+    /* Destroy stream */
+    if (stream) {
+	pjmedia_stream_destroy(stream);
+    }
+
+    /* Destroy file ports */
+    if (play_file_port)
+	pjmedia_port_destroy( play_file_port );
+    if (rec_file_port)
+	pjmedia_port_destroy( rec_file_port );
+
+
+    /* Release application pool */
+    pj_pool_release( pool );
+
+    /* Destroy media endpoint. */
+    pjmedia_endpt_destroy( med_endpt );
+
+    /* Destroy pool factory */
+    pj_caching_pool_destroy( &cp );
+
+
+    return (status == PJ_SUCCESS) ? 0 : 1;
+}
+
+
+
+
+static const char *good_number(char *buf, pj_int32_t val)
+{
+    if (val < 1000) {
+	pj_ansi_sprintf(buf, "%d", val);
+    } else if (val < 1000000) {
+	pj_ansi_sprintf(buf, "%d.%dK", 
+			val / 1000,
+			(val % 1000) / 100);
+    } else {
+	pj_ansi_sprintf(buf, "%d.%02dM", 
+			val / 1000000,
+			(val % 1000000) / 10000);
+    }
+
+    return buf;
+}
+
+
+
+/*
+ * Print stream statistics
+ */
+static void print_stream_stat(pjmedia_stream *stream)
+{
+    char duration[80], last_update[80];
+    char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
+    pjmedia_port *port;
+    pjmedia_rtcp_stat stat;
+    pj_time_val now;
+
+
+    pj_gettimeofday(&now);
+    pjmedia_stream_get_stat(stream, &stat);
+    pjmedia_stream_get_port(stream, &port);
+
+    puts("Stream statistics:");
+
+    /* Print duration */
+    PJ_TIME_VAL_SUB(now, stat.start);
+    sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld",
+	    now.sec / 3600,
+	    (now.sec % 3600) / 60,
+	    (now.sec % 60),
+	    now.msec);
+
+
+    printf(" Info: audio %.*s@%dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
+   	(int)port->info.encoding_name.slen,
+	port->info.encoding_name.ptr,
+	port->info.sample_rate,
+	port->info.samples_per_frame * 1000 / port->info.sample_rate,
+	good_number(bps, port->info.bytes_per_frame * port->info.sample_rate /
+		    port->info.sample_rate),
+	good_number(ipbps, (port->info.bytes_per_frame+32) * 
+			    port->info.sample_rate / port->info.sample_rate));
+
+    if (stat.rx.update_cnt == 0)
+	strcpy(last_update, "never");
+    else {
+	pj_gettimeofday(&now);
+	PJ_TIME_VAL_SUB(now, stat.rx.update);
+	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+		now.sec / 3600,
+		(now.sec % 3600) / 60,
+		now.sec % 60,
+		now.msec);
+    }
+
+    printf(" RX stat last update: %s\n"
+	   "    total %s packets %sB received (%sB +IP hdr)%s\n"
+	   "    pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
+	   "          (msec)    min     avg     max     last\n"
+	   "    loss period: %7.3f %7.3f %7.3f %7.3f%s\n"
+	   "    jitter     : %7.3f %7.3f %7.3f %7.3f%s\n",
+	   last_update,
+	   good_number(packets, stat.rx.pkt),
+	   good_number(bytes, stat.rx.bytes),
+	   good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
+	   "",
+	   stat.rx.loss,
+	   stat.rx.loss * 100.0 / stat.rx.pkt,
+	   stat.rx.dup, 
+	   stat.rx.dup * 100.0 / stat.rx.pkt,
+	   stat.rx.reorder, 
+	   stat.rx.reorder * 100.0 / stat.rx.pkt,
+	   "",
+	   stat.rx.loss_period.min / 1000.0, 
+	   stat.rx.loss_period.avg / 1000.0, 
+	   stat.rx.loss_period.max / 1000.0,
+	   stat.rx.loss_period.last / 1000.0,
+	   "",
+	   stat.rx.jitter.min / 1000.0,
+	   stat.rx.jitter.avg / 1000.0,
+	   stat.rx.jitter.max / 1000.0,
+	   stat.rx.jitter.last / 1000.0,
+	   ""
+	   );
+
+
+    if (stat.tx.update_cnt == 0)
+	strcpy(last_update, "never");
+    else {
+	pj_gettimeofday(&now);
+	PJ_TIME_VAL_SUB(now, stat.tx.update);
+	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+		now.sec / 3600,
+		(now.sec % 3600) / 60,
+		now.sec % 60,
+		now.msec);
+    }
+
+    printf(" TX stat last update: %s\n"
+	   "    total %s packets %sB sent (%sB +IP hdr)%s\n"
+	   "    pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
+	   "          (msec)    min     avg     max     last\n"
+	   "    loss period: %7.3f %7.3f %7.3f %7.3f%s\n"
+	   "    jitter     : %7.3f %7.3f %7.3f %7.3f%s\n",
+	   last_update,
+	   good_number(packets, stat.tx.pkt),
+	   good_number(bytes, stat.tx.bytes),
+	   good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
+	   "",
+	   stat.tx.loss,
+	   stat.tx.loss * 100.0 / stat.tx.pkt,
+	   stat.tx.dup, 
+	   stat.tx.dup * 100.0 / stat.tx.pkt,
+	   stat.tx.reorder, 
+	   stat.tx.reorder * 100.0 / stat.tx.pkt,
+	   "",
+	   stat.tx.loss_period.min / 1000.0, 
+	   stat.tx.loss_period.avg / 1000.0, 
+	   stat.tx.loss_period.max / 1000.0,
+	   stat.tx.loss_period.last / 1000.0,
+	   "",
+	   stat.tx.jitter.min / 1000.0,
+	   stat.tx.jitter.avg / 1000.0,
+	   stat.tx.jitter.max / 1000.0,
+	   stat.tx.jitter.last / 1000.0,
+	   ""
+	   );
+
+
+    printf(" RTT delay     : %7.3f %7.3f %7.3f %7.3f%s\n", 
+	   stat.rtt.min / 1000.0,
+	   stat.rtt.avg / 1000.0,
+	   stat.rtt.max / 1000.0,
+	   stat.rtt.last / 1000.0,
+	   ""
+	   );
+
+}
+
diff --git a/pjsip-apps/src/samples/util.h b/pjsip-apps/src/samples/util.h
index 2e2a32c..02ff702 100644
--- a/pjsip-apps/src/samples/util.h
+++ b/pjsip-apps/src/samples/util.h
@@ -36,14 +36,14 @@
  * This utility function parses the command line and look for
  * common sound options.
  */
-static pj_status_t get_snd_options( const char *app_name,
-				    int argc, 
-				    char *argv[],
-				    int *dev_id,
-				    int *clock_rate,
-				    int *channel_count,
-				    int *samples_per_frame,
-				    int *bits_per_sample)
+pj_status_t get_snd_options(const char *app_name,
+			    int argc, 
+			    char *argv[],
+			    int *dev_id,
+			    int *clock_rate,
+			    int *channel_count,
+			    int *samples_per_frame,
+			    int *bits_per_sample)
 {
     struct pj_getopt_option long_options[] = {
 	{ "dev",	1, 0, 'd' },
@@ -133,7 +133,7 @@
 
 
 /* Dump memory pool usage. */
-static void dump_pool_usage( const char *app_name, pj_caching_pool *cp )
+void dump_pool_usage( const char *app_name, pj_caching_pool *cp )
 {
 #if !defined(PJ_HAS_POOL_ALT_API) || PJ_HAS_POOL_ALT_API==0
     pj_pool_t   *p;