Index: pjproject/trunk/pjmedia/include/pjmedia/sound.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/sound.h	(revision 351)
+++ pjproject/trunk/pjmedia/include/pjmedia/sound.h	(revision 352)
@@ -112,5 +112,43 @@
 
 /**
- * Create a new audio stream for audio capture purpose.
+ * Create sound stream for both capturing audio and audio playback,  from the 
+ * same device. This is the recommended way to create simultaneous recorder 
+ * and player streams, because it should work on backends that does not allow
+ * a device to be opened more than once.
+ *
+ * @param rec_id	    Device index for recorder/capture stream, or
+ *			    -1 to use the first capable device.
+ * @param play_id	    Device index for playback stream, or -1 to use 
+ *			    the first capable device.
+ * @param clock_rate	    Sound device's clock rate to set.
+ * @param channel_count	    Set number of channels, 1 for mono, or 2 for
+ *			    stereo. The channel count determines the format
+ *			    of the frame.
+ * @param samples_per_frame Number of samples per frame.
+ * @param bits_per_sample   Set the number of bits per sample. The normal 
+ *			    value for this parameter is 16 bits per sample.
+ * @param rec_cb	    Callback to handle captured audio samples.
+ * @param play_cb	    Callback to be called when the sound player needs
+ *			    more audio samples to play.
+ * @param user_data	    User data to be associated with the stream.
+ * @param p_snd_strm	    Pointer to receive the stream instance.
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_snd_open(int rec_id,
+				      int play_id,
+				      unsigned clock_rate,
+				      unsigned channel_count,
+				      unsigned samples_per_frame,
+				      unsigned bits_per_sample,
+				      pjmedia_snd_rec_cb rec_cb,
+				      pjmedia_snd_play_cb play_cb,
+				      void *user_data,
+				      pjmedia_snd_stream **p_snd_strm);
+
+
+/**
+ * Create a unidirectional audio stream for capturing audio samples from
+ * the sound device.
  *
  * @param index		    Device index, or -1 to let the library choose the 
@@ -129,5 +167,5 @@
  * @return		    PJ_SUCCESS on success.
  */
-PJ_DECL(pj_status_t) pjmedia_snd_open_recorder( int index,
+PJ_DECL(pj_status_t) pjmedia_snd_open_rec( int index,
 					   unsigned clock_rate,
 					   unsigned channel_count,
@@ -139,5 +177,6 @@
 
 /**
- * Create a new audio stream for playing audio samples.
+ * Create a unidirectional audio stream for playing audio samples to the
+ * sound device.
  *
  * @param index		    Device index, or -1 to let the library choose the 
@@ -166,4 +205,5 @@
 					 pjmedia_snd_stream **p_snd_strm );
 
+
 /**
  * Start the stream.
Index: pjproject/trunk/pjmedia/include/pjmedia/sound_port.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/sound_port.h	(revision 351)
+++ pjproject/trunk/pjmedia/include/pjmedia/sound_port.h	(revision 352)
@@ -42,7 +42,40 @@
 typedef struct pjmedia_snd_port pjmedia_snd_port;
 
-/**
- * Create sound device port for capturing audio streams from the sound device
- * with the specified parameters.
+
+/**
+ * Create bidirectional sound port for both capturing and playback of
+ * audio samples.
+ *
+ * @param pool		    Pool to allocate sound port structure.
+ * @param rec_id	    Device index for recorder/capture stream, or
+ *			    -1 to use the first capable device.
+ * @param play_id	    Device index for playback stream, or -1 to use 
+ *			    the first capable device.
+ * @param clock_rate	    Sound device's clock rate to set.
+ * @param channel_count	    Set number of channels, 1 for mono, or 2 for
+ *			    stereo. The channel count determines the format
+ *			    of the frame.
+ * @param samples_per_frame Number of samples per frame.
+ * @param bits_per_sample   Set the number of bits per sample. The normal 
+ *			    value for this parameter is 16 bits per sample.
+ * @param options	    Options flag, currently must be zero.
+ * @param p_port	    Pointer to receive the sound device port instance.
+ *
+ * @return		    PJ_SUCCESS on success, or the appropriate error
+ *			    code.
+ */
+PJ_DECL(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool,
+					      int rec_id,
+					      int play_id,
+					      unsigned clock_rate,
+					      unsigned channel_count,
+					      unsigned samples_per_frame,
+					      unsigned bits_per_sample,
+					      unsigned options,
+					      pjmedia_snd_port **p_port);
+
+/**
+ * Create unidirectional sound device port for capturing audio streams from 
+ * the sound device with the specified parameters.
  *
  * @param pool		    Pool to allocate sound port structure.
@@ -72,6 +105,6 @@
 					      
 /**
- * Create sound device port for playing audio streams with the specified 
- * parameters.
+ * Create unidirectional sound device port for playing audio streams with the 
+ * specified parameters.
  *
  * @param pool		    Pool to allocate sound port structure.
@@ -113,7 +146,18 @@
 
 /**
- * Connect a port to the sound device port. If the sound device port is a
+ * Retrieve the sound stream associated by this sound device port.
+ *
+ * @param snd_port	    The sound device port.
+ *
+ * @return		    The sound stream instance.
+ */
+PJ_DECL(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
+						pjmedia_snd_port *snd_port);
+
+
+/**
+ * Connect a port to the sound device port. If the sound device port has a
  * sound recorder device, then this will start periodic function call to
- * the port's put_frame() function. If the sound device is a sound player
+ * the port's put_frame() function. If the sound device has a sound player
  * device, then this will start periodic function call to the port's
  * get_frame() function.
Index: pjproject/trunk/pjmedia/include/pjmedia/types.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/types.h	(revision 351)
+++ pjproject/trunk/pjmedia/include/pjmedia/types.h	(revision 352)
@@ -64,4 +64,21 @@
 };
 
+/* Alternate names for media direction: */
+
+/**
+ * Direction is capturing audio frames.
+ */
+#define PJMEDIA_DIR_CAPTURE	PJMEDIA_DIR_ENCODING
+
+/**
+ * Direction is playback of audio frames.
+ */
+#define PJMEDIA_DIR_PLAYBACK	PJMEDIA_DIR_DECODING
+
+/**
+ * Direction is both capture and playback.
+ */
+#define PJMEDIA_DIR_CAPTURE_PLAYBACK	PJMEDIA_DIR_ENCODING_DECODING
+
 
 /**
Index: pjproject/trunk/pjmedia/src/pjmedia/conference.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/conference.c	(revision 351)
+++ pjproject/trunk/pjmedia/src/pjmedia/conference.c	(revision 352)
@@ -159,6 +159,5 @@
     unsigned		  port_cnt;	/**< Current number of ports.	    */
     unsigned		  connect_cnt;	/**< Total number of connections    */
-    pjmedia_snd_port	 *snd_rec;	/**< Sound recorder stream.	    */
-    pjmedia_snd_port	 *snd_player;	/**< Sound player stream.	    */
+    pjmedia_snd_port	 *snd_dev_port;	/**< Sound device port.		    */
     pjmedia_port	 *master_port;	/**< Port zero's port.		    */
     pj_mutex_t		 *mutex;	/**< Conference mutex.		    */
@@ -343,38 +342,31 @@
 
 
-    /* Create sound devices: */
-
-    /* Create recorder only if mic is not disabled. */
-    if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0 &&
-	(conf->options & PJMEDIA_CONF_NO_MIC) == 0) 
-    {
-	status = pjmedia_snd_port_create_rec( pool, -1, conf->clock_rate, 
+    /* Create sound device port: */
+
+    if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) {
+
+	/*
+	 * If capture is disabled then create player only port.
+	 * Otherwise create bidirectional sound device port.
+	 */
+	if (conf->options & PJMEDIA_CONF_NO_MIC)  {
+	    status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate,
+						    conf->channel_count,
+						    conf->samples_per_frame,
+						    conf->bits_per_sample, 
+						    0,	/* options */
+						    &conf->snd_dev_port);
+
+	} else {
+	    status = pjmedia_snd_port_create( pool, -1, -1, conf->clock_rate, 
 					      conf->channel_count, 
 					      conf->samples_per_frame,
 					      conf->bits_per_sample,
 					      0,    /* Options */
-					      &conf->snd_rec);
-	if (status != PJ_SUCCESS) {
-	    conf->snd_rec = NULL;
+					      &conf->snd_dev_port);
+	}
+
+	if (status != PJ_SUCCESS)
 	    return status;
-	}
-    }
-
-    /* Create player device */
-    if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) {
-	status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate, 
-						conf->channel_count,
-						conf->samples_per_frame,
-						conf->bits_per_sample, 
-						0,	/* options */
-						&conf->snd_player);
-	if (status != PJ_SUCCESS) {
-	    if (conf->snd_rec) {
-		pjmedia_snd_port_destroy(conf->snd_rec);
-		conf->snd_rec = NULL;
-	    }
-	    conf->snd_player = NULL;
-	    return status;
-	}
     }
 
@@ -434,5 +426,5 @@
     conf->master_port->info.encoding_name = pj_str("pcm");
     conf->master_port->info.has_info = 1;
-    conf->master_port->info.name = pj_str("master port");
+    conf->master_port->info.name = pj_str("sound-dev");
     conf->master_port->info.need_info = 0;
     conf->master_port->info.pt = 0xFF;
@@ -465,6 +457,6 @@
      * master port.
      */
-    if (conf->snd_player) {
-	status = pjmedia_snd_port_connect( conf->snd_player, 
+    if (conf->snd_dev_port) {
+	status = pjmedia_snd_port_connect( conf->snd_dev_port, 
 					   conf->master_port );
 	if (status != PJ_SUCCESS) {
@@ -474,13 +466,4 @@
     }
 
-    if (conf->snd_rec) {
-	status = pjmedia_snd_port_connect( conf->snd_rec,
-					   conf->master_port);
-	if (status != PJ_SUCCESS) {
-	    pjmedia_conf_destroy(conf);
-	    return status;
-	}
-    }
-
 
     /* Done */
@@ -520,12 +503,8 @@
     PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL);
 
-    /* Destroy sound devices. */
-    if (conf->snd_rec) {
-	pjmedia_snd_port_destroy(conf->snd_rec);
-	conf->snd_rec = NULL;
-    }
-    if (conf->snd_player) {
-	pjmedia_snd_port_destroy(conf->snd_player);
-	conf->snd_player = NULL;
+    /* Destroy sound device port. */
+    if (conf->snd_dev_port) {
+	pjmedia_snd_port_destroy(conf->snd_dev_port);
+	conf->snd_dev_port = NULL;
     }
 
Index: pjproject/trunk/pjmedia/src/pjmedia/nullsound.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/nullsound.c	(revision 351)
+++ pjproject/trunk/pjmedia/src/pjmedia/nullsound.c	(revision 352)
@@ -48,5 +48,5 @@
 }
 
-PJ_DEF(pj_status_t) pjmedia_snd_open_recorder( int index,
+PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
 					  unsigned clock_rate,
 					  unsigned channel_count,
@@ -92,4 +92,31 @@
 }
 
+PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
+				      int play_id,
+				      unsigned clock_rate,
+				      unsigned channel_count,
+				      unsigned samples_per_frame,
+				      unsigned bits_per_sample,
+				      pjmedia_snd_rec_cb rec_cb,
+				      pjmedia_snd_play_cb play_cb,
+				      void *user_data,
+				      pjmedia_snd_stream **p_snd_strm)
+{
+    PJ_UNUSED_ARG(rec_id);
+    PJ_UNUSED_ARG(play_id);
+    PJ_UNUSED_ARG(clock_rate);
+    PJ_UNUSED_ARG(channel_count);
+    PJ_UNUSED_ARG(samples_per_frame);
+    PJ_UNUSED_ARG(bits_per_sample);
+    PJ_UNUSED_ARG(rec_cb);
+    PJ_UNUSED_ARG(play_cb);
+    PJ_UNUSED_ARG(user_data);
+
+    *p_snd_strm = (void*)1;
+
+    return PJ_SUCCESS;
+}
+
+
 PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
 {
Index: pjproject/trunk/pjmedia/src/pjmedia/pasound.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/pasound.c	(revision 351)
+++ pjproject/trunk/pjmedia/src/pjmedia/pasound.c	(revision 352)
@@ -33,24 +33,33 @@
 } snd_mgr;
 
+/* 
+ * Sound stream descriptor.
+ * This struct may be used for both unidirectional or bidirectional sound
+ * streams.
+ */
 struct pjmedia_snd_stream
 {
-    pj_pool_t	    *pool;
-    pj_str_t	     name;
-    PaStream	    *stream;
-    int		     dev_index;
-    int		     bytes_per_sample;
-    pj_uint32_t	     samples_per_sec;
-    int		     channel_count;
-    pj_uint32_t	     timestamp;
-    pj_uint32_t	     underflow;
-    pj_uint32_t	     overflow;
-    void	    *user_data;
-    pjmedia_snd_rec_cb    rec_cb;
-    pjmedia_snd_play_cb   play_cb;
-    pj_bool_t	     quit_flag;
-    pj_bool_t	     thread_has_exited;
-    pj_bool_t	     thread_initialized;
-    pj_thread_desc   thread_desc;
-    pj_thread_t	    *thread;
+    pj_pool_t		*pool;
+    pj_str_t		 name;
+    pjmedia_dir		 dir;
+    int			 bytes_per_sample;
+    pj_uint32_t		 samples_per_sec;
+    int			 channel_count;
+
+    PaStream		*stream;
+    void		*user_data;
+    pjmedia_snd_rec_cb   rec_cb;
+    pjmedia_snd_play_cb  play_cb;
+
+    pj_uint32_t		 timestamp;
+    pj_uint32_t		 underflow;
+    pj_uint32_t		 overflow;
+
+    pj_bool_t		 quit_flag;
+
+    pj_bool_t		 thread_exited;
+    pj_bool_t		 thread_initialized;
+    pj_thread_desc	 thread_desc;
+    pj_thread_t		*thread;
 };
 
@@ -73,5 +82,6 @@
 
     if (stream->thread_initialized == 0) {
-	status = pj_thread_register("pa_rec", stream->thread_desc, &stream->thread);
+	status = pj_thread_register("pa_rec", stream->thread_desc, 
+				    &stream->thread);
 	stream->thread_initialized = 1;
 	PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
@@ -93,5 +103,5 @@
 
 on_break:
-    stream->thread_has_exited = 1;
+    stream->thread_exited = 1;
     return paAbort;
 }
@@ -116,5 +126,6 @@
 
     if (stream->thread_initialized == 0) {
-	status = pj_thread_register("pa_rec", stream->thread_desc, &stream->thread);
+	status = pj_thread_register("portaudio", stream->thread_desc,
+				    &stream->thread);
 	stream->thread_initialized = 1;
 	PJ_LOG(5,(THIS_FILE, "Player thread started"));
@@ -135,6 +146,26 @@
 
 on_break:
-    stream->thread_has_exited = 1;
+    stream->thread_exited = 1;
     return paAbort;
+}
+
+
+static int PaRecorderPlayerCallback( const void *input, 
+				     void *output,
+				     unsigned long frameCount,
+				     const PaStreamCallbackTimeInfo* timeInfo,
+				     PaStreamCallbackFlags statusFlags,
+				     void *userData )
+{
+    int rc;
+
+    rc = PaRecorderCallback(input, output, frameCount, timeInfo,
+			    statusFlags, userData);
+    if (rc != paContinue)
+	return rc;
+
+    rc = PaPlayerCallback(input, output, frameCount, timeInfo,
+			  statusFlags, userData);
+    return rc;
 }
 
@@ -195,5 +226,5 @@
  * Open stream.
  */
-PJ_DEF(pj_status_t) pjmedia_snd_open_recorder( int index,
+PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
 					  unsigned clock_rate,
 					  unsigned channel_count,
@@ -216,5 +247,5 @@
 	for (index=0; index<count; ++index) {
 	    paDevInfo = Pa_GetDeviceInfo(index);
-	    if (paDevInfo->maxInputChannels > 0)
+	    if (paDevInfo->maxInputChannels >= (int)channel_count)
 		break;
 	}
@@ -246,7 +277,7 @@
     stream = pj_pool_zalloc(pool, sizeof(*stream));
     stream->pool = pool;
-    stream->name = pj_str("snd-rec");
+    pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
+    stream->dir = PJMEDIA_DIR_CAPTURE;
     stream->user_data = user_data;
-    stream->dev_index = index;
     stream->samples_per_sec = samples_per_frame;
     stream->bytes_per_sample = bits_per_sample / 8;
@@ -305,5 +336,5 @@
 	for (index=0; index<count; ++index) {
 	    paDevInfo = Pa_GetDeviceInfo(index);
-	    if (paDevInfo->maxOutputChannels > 0)
+	    if (paDevInfo->maxOutputChannels >= (int)channel_count)
 		break;
 	}
@@ -335,10 +366,10 @@
     stream = pj_pool_calloc(pool, 1, sizeof(*stream));
     stream->pool = pool;
-    stream->name = pj_str("player");
+    pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
+    stream->dir = stream->dir = PJMEDIA_DIR_PLAYBACK;
     stream->user_data = user_data;
     stream->samples_per_sec = samples_per_frame;
     stream->bytes_per_sample = bits_per_sample / 8;
     stream->channel_count = channel_count;
-    stream->dev_index = index;
     stream->play_cb = play_cb;
 
@@ -375,4 +406,129 @@
 
 /*
+ * Open both player and recorder.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
+				      int play_id,
+				      unsigned clock_rate,
+				      unsigned channel_count,
+				      unsigned samples_per_frame,
+				      unsigned bits_per_sample,
+				      pjmedia_snd_rec_cb rec_cb,
+				      pjmedia_snd_play_cb play_cb,
+				      void *user_data,
+				      pjmedia_snd_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_snd_stream *stream;
+    PaStreamParameters inputParam;
+    PaStreamParameters outputParam;
+    int sampleFormat;
+    const PaDeviceInfo *paRecDevInfo = NULL;
+    const PaDeviceInfo *paPlayDevInfo = NULL;
+    unsigned paFrames;
+    PaError err;
+
+    if (rec_id == -1) {
+	int count = Pa_GetDeviceCount();
+	for (rec_id=0; rec_id<count; ++rec_id) {
+	    paRecDevInfo = Pa_GetDeviceInfo(rec_id);
+	    if (paRecDevInfo->maxInputChannels >= (int)channel_count)
+		break;
+	}
+	if (rec_id == count) {
+	    /* No such device. */
+	    return PJMEDIA_ENOSNDREC;
+	}
+    } else {
+	paRecDevInfo = Pa_GetDeviceInfo(rec_id);
+	if (!paRecDevInfo) {
+	    /* Assumed it is "No such device" error. */
+	    return PJMEDIA_ESNDINDEVID;
+	}
+    }
+
+    if (play_id == -1) {
+	int count = Pa_GetDeviceCount();
+	for (play_id=0; play_id<count; ++play_id) {
+	    paPlayDevInfo = Pa_GetDeviceInfo(play_id);
+	    if (paPlayDevInfo->maxOutputChannels >= (int)channel_count)
+		break;
+	}
+	if (play_id == count) {
+	    /* No such device. */
+	    return PJMEDIA_ENOSNDPLAY;
+	}
+    } else {
+	paPlayDevInfo = Pa_GetDeviceInfo(play_id);
+	if (!paPlayDevInfo) {
+	    /* Assumed it is "No such device" error. */
+	    return PJMEDIA_ESNDINDEVID;
+	}
+    }
+
+    if (bits_per_sample == 8)
+	sampleFormat = paUInt8;
+    else if (bits_per_sample == 16)
+	sampleFormat = paInt16;
+    else if (bits_per_sample == 32)
+	sampleFormat = paInt32;
+    else
+	return PJMEDIA_ESNDINSAMPLEFMT;
+    
+    pool = pj_pool_create( snd_mgr.factory, "sndstream", 1024, 1024, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    stream = pj_pool_zalloc(pool, sizeof(*stream));
+    stream->pool = pool;
+    pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
+    stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    stream->user_data = user_data;
+    stream->samples_per_sec = samples_per_frame;
+    stream->bytes_per_sample = bits_per_sample / 8;
+    stream->channel_count = channel_count;
+    stream->rec_cb = rec_cb;
+    stream->play_cb = play_cb;
+
+    pj_memset(&inputParam, 0, sizeof(inputParam));
+    inputParam.device = rec_id;
+    inputParam.channelCount = channel_count;
+    inputParam.hostApiSpecificStreamInfo = NULL;
+    inputParam.sampleFormat = sampleFormat;
+    inputParam.suggestedLatency = paRecDevInfo->defaultLowInputLatency;
+
+    pj_memset(&outputParam, 0, sizeof(outputParam));
+    outputParam.device = play_id;
+    outputParam.channelCount = channel_count;
+    outputParam.hostApiSpecificStreamInfo = NULL;
+    outputParam.sampleFormat = sampleFormat;
+    outputParam.suggestedLatency = paPlayDevInfo->defaultLowInputLatency;
+
+    /* Frames in PortAudio is number of samples in a single channel */
+    paFrames = samples_per_frame / channel_count;
+
+    err = Pa_OpenStream( &stream->stream, &inputParam, &outputParam,
+			 clock_rate, paFrames, 
+			 paClipOff, &PaRecorderPlayerCallback, stream );
+    if (err != paNoError) {
+	pj_pool_release(pool);
+	return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
+    }
+
+    PJ_LOG(5,(THIS_FILE, "%s opening device %s/%s for recording and playback, "
+			 "sample rate=%d, channel count=%d, "
+			 "%d bits per sample, %d samples per buffer",
+			 (err==0 ? "Success" : "Error"),
+			 paRecDevInfo->name, paPlayDevInfo->name,
+			 clock_rate, channel_count,
+			 bits_per_sample, samples_per_frame));
+
+    *p_snd_strm = stream;
+
+
+    return PJ_SUCCESS;
+}
+
+/*
  * Start stream.
  */
@@ -398,5 +554,5 @@
 
     stream->quit_flag = 1;
-    for (i=0; !stream->thread_has_exited && i<100; ++i)
+    for (i=0; !stream->thread_exited && i<100; ++i)
 	pj_thread_sleep(10);
 
@@ -418,16 +574,13 @@
 {
     int i, err;
-    const PaDeviceInfo *paDevInfo;
 
     stream->quit_flag = 1;
-    for (i=0; !stream->thread_has_exited && i<100; ++i)
-	pj_thread_sleep(10);
-
-    pj_thread_sleep(1);
-
-    paDevInfo = Pa_GetDeviceInfo(stream->dev_index);
-
-    PJ_LOG(5,(THIS_FILE, "Closing %s: %lu underflow, %lu overflow",
-			 paDevInfo->name,
+    for (i=0; !stream->thread_exited && i<100; ++i) {
+	pj_thread_sleep(1);
+    }
+
+    PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow",
+			 (int)stream->name.slen,
+			 stream->name.ptr,
 			 stream->underflow, stream->overflow));
 
Index: pjproject/trunk/pjmedia/src/pjmedia/sound_port.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/sound_port.c	(revision 351)
+++ pjproject/trunk/pjmedia/src/pjmedia/sound_port.c	(revision 352)
@@ -24,5 +24,6 @@
 struct pjmedia_snd_port
 {
-    int			 snd_index;
+    int			 rec_id;
+    int			 play_id;
     pjmedia_snd_stream	*snd_stream;
     pjmedia_dir		 dir;
@@ -124,15 +125,16 @@
 
     /* Open sound stream. */
-    if (snd_port->dir == PJMEDIA_DIR_ENCODING) {
-	status = pjmedia_snd_open_recorder( snd_port->snd_index, 
-					    snd_port->clock_rate,
-					    snd_port->channel_count,
-					    snd_port->samples_per_frame,
-					    snd_port->bits_per_sample,
-					    &rec_cb,
-					    snd_port,
-					    &snd_port->snd_stream);
-    } else {
-	status = pjmedia_snd_open_player( snd_port->snd_index, 
+    if (snd_port->dir == PJMEDIA_DIR_CAPTURE) {
+	status = pjmedia_snd_open_rec( snd_port->rec_id, 
+				       snd_port->clock_rate,
+				       snd_port->channel_count,
+				       snd_port->samples_per_frame,
+				       snd_port->bits_per_sample,
+				       &rec_cb,
+				       snd_port,
+				       &snd_port->snd_stream);
+
+    } else if (snd_port->dir == PJMEDIA_DIR_PLAYBACK) {
+	status = pjmedia_snd_open_player( snd_port->play_id, 
 					  snd_port->clock_rate,
 					  snd_port->channel_count,
@@ -142,4 +144,19 @@
 					  snd_port,
 					  &snd_port->snd_stream);
+
+    } else if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+	status = pjmedia_snd_open( snd_port->rec_id, 
+				   snd_port->play_id,
+				   snd_port->clock_rate,
+				   snd_port->channel_count,
+				   snd_port->samples_per_frame,
+				   snd_port->bits_per_sample,
+				   &rec_cb,
+				   &play_cb,
+				   snd_port,
+				   &snd_port->snd_stream);
+    } else {
+	pj_assert(!"Invalid dir");
+	status = PJ_EBUG;
     }
 
@@ -177,8 +194,47 @@
 
 /*
+ * Create bidirectional port.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool,
+					     int rec_id,
+					     int play_id,
+					     unsigned clock_rate,
+					     unsigned channel_count,
+					     unsigned samples_per_frame,
+					     unsigned bits_per_sample,
+					     unsigned options,
+					     pjmedia_snd_port **p_port)
+{
+    pjmedia_snd_port *snd_port;
+
+    PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
+    PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
+
+    snd_port = pj_pool_zalloc(pool, sizeof(pjmedia_snd_port));
+    PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
+
+    snd_port->rec_id = rec_id;
+    snd_port->play_id = play_id;
+    snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    snd_port->clock_rate = clock_rate;
+    snd_port->channel_count = channel_count;
+    snd_port->samples_per_frame = samples_per_frame;
+    snd_port->bits_per_sample = bits_per_sample;
+
+    *p_port = snd_port;
+
+    /* Start sound device immediately.
+     * If there's no port connected, the sound callback will return
+     * empty signal.
+     */
+    return start_sound_device( snd_port );
+
+}
+
+/*
  * Create sound recorder port.
  */
 PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool,
-						 int index,
+						 int dev_id,
 						 unsigned clock_rate,
 						 unsigned channel_count,
@@ -196,6 +252,6 @@
     PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
 
-    snd_port->snd_index = index;
-    snd_port->dir = PJMEDIA_DIR_ENCODING;
+    snd_port->rec_id = dev_id;
+    snd_port->dir = PJMEDIA_DIR_CAPTURE;
     snd_port->clock_rate = clock_rate;
     snd_port->channel_count = channel_count;
@@ -217,5 +273,5 @@
  */
 PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool,
-						    int index,
+						    int dev_id,
 						    unsigned clock_rate,
 						    unsigned channel_count,
@@ -233,6 +289,6 @@
     PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
 
-    snd_port->snd_index = index;
-    snd_port->dir = PJMEDIA_DIR_DECODING;
+    snd_port->play_id = dev_id;
+    snd_port->dir = PJMEDIA_DIR_PLAYBACK;
     snd_port->clock_rate = clock_rate;
     snd_port->channel_count = channel_count;
@@ -262,4 +318,15 @@
 
 /*
+ * Retrieve the sound stream associated by this sound device port.
+ */
+PJ_DEF(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
+						pjmedia_snd_port *snd_port)
+{
+    PJ_ASSERT_RETURN(snd_port, NULL);
+    return snd_port->snd_stream;
+}
+
+
+/*
  * Connect a port.
  */
Index: pjproject/trunk/pjsip-apps/src/samples/confsample.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/confsample.c	(revision 351)
+++ pjproject/trunk/pjsip-apps/src/samples/confsample.c	(revision 352)
@@ -93,10 +93,5 @@
 {
     puts("");
-    puts("Usage: confsample [file1.wav] [file2.wav] ...");
-    puts("");
-    puts("where:");
-    puts("  fileN.WAV are optional WAV files to be connected to the conference");
-    puts("  bridge. The WAV files MUST have single channel (mono) and 16 bit PCM");
-    puts("  samples. They can have arbitrary/different sampling rate.");
+    puts(desc);
 }
 
