Index: /pjproject/trunk/pjmedia/include/pjmedia/stereo.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/stereo.h	(revision 1901)
+++ /pjproject/trunk/pjmedia/include/pjmedia/stereo.h	(revision 1901)
@@ -0,0 +1,206 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 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 
+ */
+#ifndef __PJMEDIA_STEREO_H__
+#define __PJMEDIA_STEREO_H__
+
+/**
+ * @file stereo.h
+ * @brief Monochannel and multichannel converter.
+ */
+
+#include <pjmedia/errno.h>
+#include <pjmedia/port.h>
+#include <pjmedia/types.h>
+#include <pj/assert.h>
+
+
+/**
+ * @defgroup PJMEDIA_STEREO Monochannel and multichannel audio frame converter
+ * @ingroup PJMEDIA_MISC
+ * @brief Interface for converting monochannel audio frame to multichannel
+ * audio frame and vice versa.
+ * @{
+ *
+ */
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Multichannel to monochannel conversion mixes samples from all channels
+ * into the monochannel.
+ */
+#define PJMEDIA_STEREO_MIX  PJ_TRUE
+
+
+
+/**
+ * Multichannel to monochannel conversion, it has two operation mode specified
+ * by param options, @see pjmedia_stereo_options. This function can work safely
+ * using the same buffer (in place conversion).
+ *
+ * @param mono		    Output buffer to store the mono frame extracted 
+ *			    from the multichannels frame.
+ * @param multi		    Input frame containing multichannels audio.
+ * @param channel_count	    Number of channels in the input frame.
+ * @param samples_per_frame Number of samples in the input frame.
+ * @param mix		    If the value is PJ_TRUE then the input channels 
+ *			    will be mixed to produce output frame, otherwise
+ *			    only frame from channel_src will be copied to the
+ *			    output frame.
+ * @param channel_src	    When mixing is disabled, the mono output frame
+ *			    will be copied from this channel number.
+ *
+ * @return		    PJ_SUCCESS on success;
+ */
+PJ_INLINE(pj_status_t) pjmedia_convert_channel_nto1(pj_int16_t mono[],
+						    const pj_int16_t multi[],
+						    unsigned channel_count,
+						    unsigned samples_per_frame,
+						    pj_bool_t mix,
+						    unsigned channel_src)
+{
+    unsigned i;
+
+    PJ_ASSERT_RETURN(mono && multi && channel_count && samples_per_frame &&
+		     channel_src < channel_count,  PJ_EINVAL);
+
+    if (mix==PJ_FALSE) {
+	for (i = channel_src; i < samples_per_frame; i += channel_count) {
+	    *mono = multi[i];
+	    ++mono;
+	}
+    } else {
+	unsigned j;
+	for (i = 0; i < samples_per_frame; i += channel_count) {
+	    int tmp = 0;
+	    for(j = 0; j < channel_count; ++j)
+		tmp += multi[i+j];
+
+	    if (tmp > 32767) tmp = 32767;
+	    else if (tmp < -32768) tmp = -32768;
+	    *mono = (pj_int16_t) tmp;
+	    ++mono;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Monochannel to multichannel conversion, it will just duplicate the samples
+ * from monochannel frame to all channels in the multichannel frame. 
+ * This function can work safely using the same buffer (in place conversion)
+ * as long as the buffer is big enough for the multichannel samples.
+ *
+ * @param multi		    Output buffer to store the multichannels frame 
+ *			    mixed from the mono frame.
+ * @param mono		    The input monochannel audio frame.
+ * @param channel_count	    Desired number of channels in the output frame.
+ * @param samples_per_frame Number of samples in the input frame.
+ * @param options	    Options for conversion, currently must be zero.
+ *
+ * @return		    PJ_SUCCESS on success;
+ */
+PJ_INLINE(pj_status_t) pjmedia_convert_channel_1ton(pj_int16_t multi[],
+						    const pj_int16_t mono[],
+						    unsigned channel_count,
+						    unsigned samples_per_frame,
+						    unsigned options)
+{
+    const pj_int16_t *src;
+
+    PJ_ASSERT_RETURN(mono && multi && channel_count && samples_per_frame, 
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
+
+    PJ_UNUSED_ARG(options);
+
+    src = mono + samples_per_frame - 1;
+    samples_per_frame *= channel_count;
+    while (samples_per_frame) {
+	unsigned i;
+	for (i=1; i<=channel_count; ++i)
+	    multi[samples_per_frame-i] = *src;
+	samples_per_frame -= channel_count;
+	--src;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/** 
+ * Options for channel converter port. The @pjmedia_stereo_options is also
+ * valid for this port options.
+ */
+enum pjmedia_stereo_port_options
+{
+    /**
+     * Specifies whether this port should not destroy downstream port when 
+     * this port is destroyed.
+     */
+    PJMEDIA_STEREO_DONT_DESTROY_DN  = 4
+};
+
+
+/**
+ * Create a mono-multi channel converter port. This creates a converter session,
+ * which will adjust the samples of audio frame to a different channel count
+ * when the port's get_frame() and put_frame() is called.
+ *
+ * When the port's get_frame() is called, this port will get a frame from 
+ * the downstream port and convert the frame to the target channel count before
+ * returning it to the caller.
+ *
+ * When the port's put_frame() is called, this port will convert the frame
+ * to the downstream port's channel count before giving the frame to the 
+ * downstream port.
+ *
+ * @param pool			Pool to allocate the structure and buffers.
+ * @param dn_port		The downstream port, which channel count is to
+ *				be converted to the target channel count.
+ * @param channel_count		This port channel count.
+ * @param options		Bitmask flags from #pjmedia_stereo_port_options
+ *				and also application may add PJMEDIA_STEREO_MIX
+ *				to mix channels.
+ *				When this flag is zero, the default behavior
+ *				is to use simple N-to-1 channel converter and 
+ *				to destroy downstream port when this port is 
+ *				destroyed.
+ * @param p_port		Pointer to receive the stereo port instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_stereo_port_create( pj_pool_t *pool,
+						 pjmedia_port *dn_port,
+						 unsigned channel_count,
+						 unsigned options,
+						 pjmedia_port **p_port );
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif	/* __PJMEDIA_STEREO_H__ */
+
Index: /pjproject/trunk/pjmedia/src/pjmedia/stereo_port.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/stereo_port.c	(revision 1901)
+++ /pjproject/trunk/pjmedia/src/pjmedia/stereo_port.c	(revision 1901)
@@ -0,0 +1,207 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 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 
+ */
+
+#include <pjmedia/stereo.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define SIGNATURE		PJMEDIA_PORT_SIGNATURE('S','T','R','O')
+
+
+struct stereo_port
+{
+    pjmedia_port	 base;
+    pjmedia_port	*dn_port;
+    unsigned		 options;
+    pj_int16_t		*put_buf;
+    pj_int16_t		*get_buf;
+};
+
+
+
+static pj_status_t stereo_put_frame(pjmedia_port *this_port,
+				    const pjmedia_frame *frame);
+static pj_status_t stereo_get_frame(pjmedia_port *this_port, 
+				    pjmedia_frame *frame);
+static pj_status_t stereo_destroy(pjmedia_port *this_port);
+
+
+
+PJ_DEF(pj_status_t) pjmedia_stereo_port_create( pj_pool_t *pool,
+						pjmedia_port *dn_port,
+						unsigned channel_count,
+						unsigned options,
+						pjmedia_port **p_port )
+{
+    const pj_str_t name = pj_str("stereo");
+    struct stereo_port *sport;
+    unsigned samples_per_frame;
+
+    /* Validate arguments. */
+    PJ_ASSERT_RETURN(pool && dn_port && channel_count && p_port, PJ_EINVAL);
+
+    /* Only supports 16bit samples per frame */
+    PJ_ASSERT_RETURN(dn_port->info.bits_per_sample == 16, PJMEDIA_ENCBITS);
+
+    /* Validate channel counts */
+    PJ_ASSERT_RETURN(((dn_port->info.channel_count>1 && channel_count==1) ||
+		      (dn_port->info.channel_count==1 && channel_count>1)),
+		      PJ_EINVAL);
+
+    /* Create and initialize port. */
+    sport = PJ_POOL_ZALLOC_T(pool, struct stereo_port);
+    PJ_ASSERT_RETURN(sport != NULL, PJ_ENOMEM);
+
+    samples_per_frame = dn_port->info.samples_per_frame * channel_count /
+			dn_port->info.channel_count;
+
+    pjmedia_port_info_init(&sport->base.info, &name, SIGNATURE, 
+			   dn_port->info.clock_rate,
+			   channel_count, 
+			   dn_port->info.bits_per_sample, 
+			   samples_per_frame);
+
+    sport->dn_port = dn_port;
+    sport->options = options;
+
+    /* We always need buffer for put_frame */
+    sport->put_buf = (pj_int16_t*)
+		     pj_pool_alloc(pool, dn_port->info.bytes_per_frame);
+
+    /* See if we need buffer for get_frame */
+    if (dn_port->info.channel_count > channel_count) {
+	sport->get_buf = (pj_int16_t*)
+			 pj_pool_alloc(pool, dn_port->info.bytes_per_frame);
+    }
+
+    /* Media port interface */
+    sport->base.get_frame = &stereo_get_frame;
+    sport->base.put_frame = &stereo_put_frame;
+    sport->base.on_destroy = &stereo_destroy;
+
+
+    /* Done */
+    *p_port = &sport->base;
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t stereo_put_frame(pjmedia_port *this_port,
+				    const pjmedia_frame *frame)
+{
+    struct stereo_port *sport = (struct stereo_port*) this_port;
+    pjmedia_frame tmp_frame;
+
+    /* Return if we don't have downstream port. */
+    if (sport->dn_port == NULL) {
+	return PJ_SUCCESS;
+    }
+
+    if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+	tmp_frame.buf = sport->put_buf;
+	if (sport->dn_port->info.channel_count == 1) {
+	    pjmedia_convert_channel_nto1(tmp_frame.buf, frame->buf,
+					 sport->base.info.channel_count, 
+					 sport->base.info.samples_per_frame, 
+					 (sport->options & PJMEDIA_STEREO_MIX),
+					 0);
+	} else {
+	    pjmedia_convert_channel_1ton(tmp_frame.buf, frame->buf,
+					 sport->dn_port->info.channel_count, 
+					 sport->base.info.samples_per_frame,
+					 sport->options);
+	}
+	tmp_frame.size = sport->dn_port->info.bytes_per_frame;
+    } else {
+	tmp_frame.buf = frame->buf;
+	tmp_frame.size = frame->size;
+    }
+
+    tmp_frame.type = frame->type;
+    tmp_frame.timestamp.u64 = frame->timestamp.u64;
+
+    return pjmedia_port_put_frame( sport->dn_port, &tmp_frame );
+}
+
+
+
+static pj_status_t stereo_get_frame(pjmedia_port *this_port, 
+				    pjmedia_frame *frame)
+{
+    struct stereo_port *sport = (struct stereo_port*) this_port;
+    pjmedia_frame tmp_frame;
+    pj_status_t status;
+
+    /* Return silence if we don't have downstream port */
+    if (sport->dn_port == NULL) {
+	pj_bzero(frame->buf, frame->size);
+	return PJ_SUCCESS;
+    }
+
+    tmp_frame.buf = sport->get_buf? sport->get_buf : frame->buf;
+    tmp_frame.size = sport->dn_port->info.bytes_per_frame;
+    tmp_frame.timestamp.u64 = frame->timestamp.u64;
+    tmp_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+    status = pjmedia_port_get_frame( sport->dn_port, &tmp_frame);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    if (tmp_frame.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+	frame->type = tmp_frame.type;
+	frame->timestamp = tmp_frame.timestamp;
+	frame->size = tmp_frame.size;
+	if (tmp_frame.size && tmp_frame.buf == sport->get_buf)
+	    pj_memcpy(frame->buf, tmp_frame.buf, tmp_frame.size);
+	return PJ_SUCCESS;
+    }
+
+    if (sport->base.info.channel_count == 1) {
+	pjmedia_convert_channel_nto1(frame->buf, tmp_frame.buf,
+				     sport->dn_port->info.channel_count, 
+				     sport->dn_port->info.samples_per_frame, 
+				     (sport->options & PJMEDIA_STEREO_MIX), 0);
+    } else {
+	pjmedia_convert_channel_1ton(frame->buf, tmp_frame.buf,
+				     sport->base.info.channel_count, 
+				     sport->dn_port->info.samples_per_frame,
+				     sport->options);
+    }
+
+    frame->size = sport->base.info.bytes_per_frame;
+    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_status_t stereo_destroy(pjmedia_port *this_port)
+{
+    struct stereo_port *sport = (struct stereo_port*) this_port;
+
+    if ((sport->options & PJMEDIA_STEREO_DONT_DESTROY_DN)==0) {
+	pjmedia_port_destroy(sport->dn_port);
+	sport->dn_port = NULL;
+    }
+
+    return PJ_SUCCESS;
+}
+
Index: /pjproject/trunk/pjsip-apps/src/samples/stereotest.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/samples/stereotest.c	(revision 1901)
+++ /pjproject/trunk/pjsip-apps/src/samples/stereotest.c	(revision 1901)
@@ -0,0 +1,334 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2008 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 
+ */
+
+/**
+ * \page page_pjmedia_samples_stereo_c Samples: Using Stereo Port
+ *
+ * This example demonstrates how to use @ref PJMEDIA_STEREO_PORT to
+ * change the channel count of the media streams.
+ *
+ * This file is pjsip-apps/src/samples/stereotest.c
+ *
+ * \includelineno stereotest.c
+ */
+
+#include <pjmedia.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+#define REC_CLOCK_RATE	    16000
+#define PTIME		    10
+
+#define MODE_PLAY	    1
+#define MODE_RECORD	    2
+
+
+/* For logging purpose. */
+#define THIS_FILE   "stereotest.c"
+
+
+static const char *desc = 
+" FILE		    						    \n"
+"		    						    \n"
+"  stereotest.c	    						    \n"
+"		    						    \n"
+" PURPOSE	    						    \n"
+"		    						    \n"
+"  Demonstrate how use stereo port to play a WAV file to sound	    \n"
+"  device or record to a WAV file from sound device with different  \n"
+"  channel count.    						    \n"
+"		    						    \n"
+" USAGE		    						    \n"
+"		    						    \n"
+"  stereotest [options]	WAV					    \n"
+"		    						    \n"
+"  Options:							    \n"
+"  -m, --mode=N          Operation mode: 1 = playing, 2 = recording.\n"
+"  -C, --rec-ch-cnt=N    Number of channel for recording file.	    \n"
+"  -c, --snd-ch-cnt=N    Number of channel for opening sound device.\n"
+"		    						    \n";
+
+int main(int argc, char *argv[])
+{
+    pj_caching_pool cp;
+    pjmedia_endpt *med_endpt;
+    pj_pool_t *pool;
+
+    pjmedia_port *file_port = NULL;
+    pjmedia_port *stereo_port = NULL;
+    pjmedia_snd_port *snd_port = NULL;
+
+    int dev_id = -1;
+    char tmp[10];
+    pj_status_t status;
+
+    char *wav_file = NULL;
+    unsigned mode = 0;
+    unsigned rec_ch_cnt = 1;
+    unsigned snd_ch_cnt = 2;
+
+    enum {
+	OPT_MODE	= 'm',
+	OPT_REC_CHANNEL = 'C',
+	OPT_SND_CHANNEL = 'c',
+    };
+
+    struct pj_getopt_option long_options[] = {
+	{ "mode",	    1, 0, OPT_MODE },
+	{ "rec-ch-cnt",	    1, 0, OPT_REC_CHANNEL },
+	{ "snd-ch-cnt",	    1, 0, OPT_SND_CHANNEL },
+	{ NULL, 0, 0, 0 },
+    };
+
+    int c;
+    int option_index;
+
+    /* Must init PJLIB first: */
+    status = pj_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Parse arguments */
+    pj_optind = 0;
+    while((c=pj_getopt_long(argc,argv, "m:C:c:", long_options, &option_index))!=-1) {
+
+	switch (c) {
+	case OPT_MODE:
+	    if (mode) {
+		app_perror(THIS_FILE, "Cannot record and play at once!", 
+			   PJ_EINVAL);
+		return 1;
+	    }
+	    mode = atoi(pj_optarg);
+	    break;
+
+	case OPT_REC_CHANNEL:
+	    rec_ch_cnt = atoi(pj_optarg);
+	    break;
+
+	case OPT_SND_CHANNEL:
+	    snd_ch_cnt = atoi(pj_optarg);
+	    break;
+
+	default:
+	    printf("Invalid options %s\n", argv[pj_optind]);
+	    puts(desc);
+	    return 1;
+	}
+
+    }
+
+    wav_file = argv[pj_optind];
+
+    /* Verify arguments. */
+    if (!wav_file) {
+	app_perror(THIS_FILE, "WAV file not specified!", PJ_EINVAL);
+	puts(desc);
+	return 1;
+    }
+    if (!snd_ch_cnt || !rec_ch_cnt || rec_ch_cnt > 6) {
+	app_perror(THIS_FILE, "Invalid or too many channel count!", PJ_EINVAL);
+	puts(desc);
+	return 1;
+    }
+    if (mode != MODE_RECORD && mode != MODE_PLAY) {
+	app_perror(THIS_FILE, "Invalid operation mode!", PJ_EINVAL);
+	puts(desc);
+	return 1;
+    }
+
+    /* 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 our file player */
+    pool = pj_pool_create( &cp.factory,	    /* pool factory	    */
+			   "app",	    /* pool name.	    */
+			   4000,	    /* init size	    */
+			   4000,	    /* increment size	    */
+			   NULL		    /* callback on error    */
+			   );
+
+    if (mode == MODE_PLAY) {
+	/* Create WAVE file player port. */
+	status = pjmedia_wav_player_port_create( pool, wav_file, PTIME, 0,
+						 0, &file_port);
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to open file", status);
+	    return 1;
+	}
+
+	/* Create sound player port. */
+	status = pjmedia_snd_port_create_player( 
+		     pool,				/* pool		      */
+		     dev_id,				/* device id.	      */
+		     file_port->info.clock_rate,	/* clock rate.	      */
+		     snd_ch_cnt,			/* # of channels.     */
+		     snd_ch_cnt * PTIME *		/* samples per frame. */
+		     file_port->info.clock_rate / 1000,
+		     file_port->info.bits_per_sample,   /* bits per sample.   */
+		     0,					/* options	      */
+		     &snd_port				/* returned port      */
+		     );
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to open sound device", status);
+	    return 1;
+	}
+
+	if (snd_ch_cnt != file_port->info.channel_count) {
+	    status = pjmedia_stereo_port_create( pool,
+						 file_port,
+						 snd_ch_cnt,
+						 0,
+						 &stereo_port);
+	    if (status != PJ_SUCCESS) {
+		app_perror(THIS_FILE, "Unable to create stereo port", status);
+		return 1;
+	    }
+
+	    status = pjmedia_snd_port_connect(snd_port, stereo_port);
+	} else {
+	    status = pjmedia_snd_port_connect(snd_port, file_port);
+	}
+
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to connect sound port", status);
+	    return 1;
+	}
+
+    } else {
+	/* Create WAVE file writer port. */
+	status = pjmedia_wav_writer_port_create(pool, wav_file,
+						REC_CLOCK_RATE,
+						rec_ch_cnt,
+						rec_ch_cnt * PTIME * 
+						REC_CLOCK_RATE / 1000,
+						NBITS,
+						0, 0, 
+						&file_port);
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to open file", status);
+	    return 1;
+	}
+
+	/* Create sound player port. */
+	status = pjmedia_snd_port_create_rec( 
+			 pool,			    /* pool		    */
+			 dev_id,		    /* device id.	    */
+			 REC_CLOCK_RATE,	    /* clock rate.	    */
+			 snd_ch_cnt,		    /* # of channels.	    */
+			 snd_ch_cnt * PTIME * 
+			 REC_CLOCK_RATE / 1000,	    /* samples per frame.   */
+			 NBITS,			    /* bits per sample.	    */
+			 0,			    /* options		    */
+			 &snd_port		    /* returned port	    */
+		     );
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to open sound device", status);
+	    return 1;
+	}
+
+	if (rec_ch_cnt != snd_ch_cnt) {
+	    status = pjmedia_stereo_port_create( pool,
+						 file_port,
+						 snd_ch_cnt,
+						 0,
+						 &stereo_port);
+	    if (status != PJ_SUCCESS) {
+		app_perror(THIS_FILE, "Unable to create stereo port", status);
+		return 1;
+	    }
+
+	    status = pjmedia_snd_port_connect(snd_port, stereo_port);
+	} else {
+	    status = pjmedia_snd_port_connect(snd_port, file_port);
+	}
+
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to connect sound port", status);
+	    return 1;
+	}
+    }
+
+    /* Dump memory usage */
+    dump_pool_usage(THIS_FILE, &cp);
+
+    /* 
+     * File should be playing and looping now, using sound device's thread. 
+     */
+
+
+    /* Sleep to allow log messages to flush */
+    pj_thread_sleep(100);
+
+    printf("Mode = %s\n", (mode == MODE_PLAY? "playing" : "recording") );
+    printf("File  port channel count = %d\n", file_port->info.channel_count);
+    printf("Sound port channel count = %d\n", 
+	   pjmedia_snd_port_get_port(snd_port)->info.channel_count);
+    puts("");
+    puts("Press <ENTER> to stop and quit");
+
+    fgets(tmp, sizeof(tmp), stdin);
+
+    
+    /* Start deinitialization: */
+
+
+    /* Destroy sound device */
+    status = pjmedia_snd_port_destroy( snd_port );
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+    /* Destroy stereo port and file_port. 
+     * Stereo port will destroy all downstream ports (e.g. the file port)
+     */
+    status = pjmedia_port_destroy( stereo_port? stereo_port : file_port);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+    /* Release application pool */
+    pj_pool_release( pool );
+
+    /* Destroy media endpoint. */
+    pjmedia_endpt_destroy( med_endpt );
+
+    /* Destroy pool factory */
+    pj_caching_pool_destroy( &cp );
+
+    /* Shutdown PJLIB */
+    pj_shutdown();
+
+
+    /* Done. */
+    return 0;
+
+}
+
+
+
