Added WAVE writer and resample port, and also found out why audio quality is poor with DirectSound

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@358 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index 7cf6012..54a4f59 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -64,9 +64,9 @@
 #
 export PJMEDIA_SRCDIR = ../src/pjmedia
 export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
-			codec.o conference.o endpoint.o errno.o file_port.o \
-			g711.o jbuf.o null_port.o port.o resample.o rtcp.o \
-			rtp.o sdp.o sdp_cmp.o sdp_neg.o session.o silencedet.o \
+			codec.o conference.o endpoint.o errno.o file_player.o \
+			g711.o jbuf.o null_port.o port.o resample.o resample_port.o \
+			rtcp.o rtp.o sdp.o sdp_cmp.o sdp_neg.o session.o silencedet.o \
 			sound_port.o stream.o $(SOUND_OBJS) $(NULLSOUND_OBJS)
 
 export PJMEDIA_CFLAGS += $(_CFLAGS)
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp
index a84395a..43d09f7 100644
--- a/pjmedia/build/pjmedia.dsp
+++ b/pjmedia/build/pjmedia.dsp
@@ -65,7 +65,7 @@
 # PROP Intermediate_Dir ".\output\pjmedia-i386-win32-vc6-debug"

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c

-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../src/pjmedia/portaudio" /D "_DEBUG" /D "PA_NO_ASIO" /D "PA_NO_WMME" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c

+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../src/pjmedia/portaudio" /D "_DEBUG" /D "PA_NO_ASIO" /D "PA_NO_DS" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "_DEBUG"

 # ADD RSC /l 0x409 /d "_DEBUG"

@@ -108,7 +108,11 @@
 # End Source File

 # Begin Source File

 

-SOURCE=..\src\pjmedia\file_port.c

+SOURCE=..\src\pjmedia\file_player.c

+# End Source File

+# Begin Source File

+

+SOURCE=..\src\pjmedia\file_writer.c

 # End Source File

 # Begin Source File

 

@@ -140,6 +144,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\src\pjmedia\resample_port.c

+# End Source File

+# Begin Source File

+

 SOURCE=..\src\pjmedia\rtcp.c

 # End Source File

 # Begin Source File

@@ -174,6 +182,10 @@
 

 SOURCE=..\src\pjmedia\stream.c

 # End Source File

+# Begin Source File

+

+SOURCE=..\src\pjmedia\wave.c

+# End Source File

 # End Group

 # Begin Group "Header Files"

 

diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h
index 8cdc0b7..34264b6 100644
--- a/pjmedia/include/pjmedia.h
+++ b/pjmedia/include/pjmedia.h
@@ -34,6 +34,7 @@
 #include <pjmedia/jbuf.h>
 #include <pjmedia/null_port.h>
 #include <pjmedia/port.h>
+#include <pjmedia/resample.h>
 #include <pjmedia/rtcp.h>
 #include <pjmedia/rtp.h>
 #include <pjmedia/sdp.h>
diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h
index c81693a..8e11104 100644
--- a/pjmedia/include/pjmedia/config.h
+++ b/pjmedia/include/pjmedia/config.h
@@ -68,5 +68,13 @@
 #endif
 
 
+/**
+ * Default file player/writer buffer size.
+ */
+#ifndef PJMEDIA_FILE_PORT_BUFSIZE
+#   define PJMEDIA_FILE_PORT_BUFSIZE    4000
+#endif
+
+
 #endif	/* __PJMEDIA_CONFIG_H__ */
 
diff --git a/pjmedia/include/pjmedia/errno.h b/pjmedia/include/pjmedia/errno.h
index 67c4d01..59e665d 100644
--- a/pjmedia/include/pjmedia/errno.h
+++ b/pjmedia/include/pjmedia/errno.h
@@ -440,6 +440,11 @@
  * Wave file too short.
  */
 #define PJMEDIA_EWAVETOOSHORT	    (PJMEDIA_ERRNO_START+182)    /* 220182 */
+/**
+ * @hideinitializer
+ * Sound frame is too large for file buffer.
+ */
+#define PJMEDIA_EFRMFILETOOBIG	    (PJMEDIA_ERRNO_START+183)    /* 220183 */
 
 
 /************************************************************
diff --git a/pjmedia/include/pjmedia/file_port.h b/pjmedia/include/pjmedia/file_port.h
index 75482c7..363f94e 100644
--- a/pjmedia/include/pjmedia/file_port.h
+++ b/pjmedia/include/pjmedia/file_port.h
@@ -26,11 +26,23 @@
 #include <pjmedia/port.h>
 
 
+
 PJ_BEGIN_DECL
 
 
 /**
- * Create file player port.
+ * Create a media port to play streams from a WAV file.
+ *
+ * @param pool		Pool to create memory buffers for this port.
+ * @param filename	File name to open.
+ * @param flags		Port creation flags.
+ * @param buf_size	Buffer size to be allocated. If the value is zero or
+ *			negative, the port will use default buffer size (which
+ *			is about 4KB).
+ * @param user_data	User data to be associated with the file player port.
+ * @param p_port	Pointer to receive the file port instance.
+ *
+ * @return		PJ_SUCCESS on success.
  */
 PJ_DECL(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
 						      const char *filename,
@@ -41,6 +53,36 @@
 
 
 
+/**
+ * Create a media port to record streams to a WAV file. Note that the port
+ * must be closed properly (with #pjmedia_port_destroy()) so that the WAV
+ * header can be filled with correct values (such as the file length).
+ *
+ * @param pool		Pool to create memory buffers for this port.
+ * @param filename	File name.
+ * @param flags		Port creation flags.
+ * @param buf_size	Buffer size to be allocated. If the value is zero or
+ *			negative, the port will use default buffer size (which
+ *			is about 4KB).
+ * @param user_data	User data to be associated with the file writer port.
+ * @param p_port	Pointer to receive the file port instance.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_file_writer_port_create( pj_pool_t *pool,
+						      const char *filename,
+						      unsigned sampling_rate,
+						      unsigned channel_count,
+						      unsigned samples_per_frame,
+						      unsigned bits_per_sample,
+						      unsigned flags,
+						      pj_ssize_t buff_size,
+						      void *user_data,
+						      pjmedia_port **p_port );
+
+
+
+
 PJ_END_DECL
 
 
diff --git a/pjmedia/include/pjmedia/resample.h b/pjmedia/include/pjmedia/resample.h
index 63b3351..1def54c 100644
--- a/pjmedia/include/pjmedia/resample.h
+++ b/pjmedia/include/pjmedia/resample.h
@@ -25,10 +25,25 @@
  * @file reample.h
  * @brief Sample rate converter.
  */
-#include "pjmedia/types.h"
+#include <pjmedia/types.h>
+#include <pjmedia/port.h>
+
 
 PJ_BEGIN_DECL
 
+/*
+ * This file declares two types of API:
+ *
+ * Application can use #pjmedia_resample_create() and #pjmedia_resample_run()
+ * to convert a frame from source rate to destination rate. The inpuit frame 
+ * must have a constant length.
+ *
+ * Alternatively, application can create a resampling port with
+ * #pjmedia_resample_port_create() and connect the port to other ports to
+ * change the sampling rate of the samples.
+ */
+
+
 /**
  * Opaque resample session.
  */
@@ -59,7 +74,9 @@
 
 
 /**
- * Resample a frame.
+ * Use the resample session to resample a frame. The frame must have the
+ * same size and settings as the resample session, or otherwise the
+ * behavior is undefined.
  *
  * @param resample		The resample session.
  * @param input			Buffer containing the input samples.
@@ -70,6 +87,55 @@
 				    pj_int16_t *output );
 
 
+/**
+ * Get the input frame size of a resample session.
+ *
+ * @param resample		The resample session.
+ *
+ * @return			The frame size, in number of samples.
+ */
+PJ_DECL(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample);
+
+
+/**
+ * Create a resample port. This creates a bidirectional resample session,
+ * which will resample frames when the port's get_frame() and put_frame()
+ * is called.
+ *
+ * When the resample port's get_frame() is called, this port will get
+ * a frame from the downstream port and resample the frame to the upstream
+ * port's clock rate before returning it to the caller.
+ *
+ * When the resample port's put_frame() is called, this port will resample
+ * the frame to the downstream's port clock rate before giving the frame
+ * to the downstream port.
+ *
+ * @param pool			Pool to allocate the structure and buffers.
+ * @param high_quality		If true, then high quality conversion will be
+ *				used, at the expense of more CPU and memory,
+ *				because temporary buffer needs to be created.
+ * @param large_filter		If true, large filter size will be used.
+ * @param downstream_rate	The sampling rate of the downstream port.
+ * @param upstream_rate		The sampling rate of the upstream port.
+ * @param channel_count		The number of channels. This argument is only
+ *				used for the port information. It does not
+ *				change the behavior of the resample port.
+ * @param samples_per_frame	Number of samples per frame from the downstream
+ *				port.
+ * @param p_port		Pointer to receive the resample port instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool,
+						   pj_bool_t high_quality,
+						   pj_bool_t large_filter,
+						   unsigned downstream_rate,
+						   unsigned upstream_rate,
+						   unsigned channel_count,
+						   unsigned samples_per_frame,
+						   pjmedia_port **p_port );
+
+
 PJ_END_DECL
 
 #endif	/* __PJMEDIA_RESAMPLE_H__ */
diff --git a/pjmedia/include/pjmedia/wave.h b/pjmedia/include/pjmedia/wave.h
index cd4c3e6..201f976 100644
--- a/pjmedia/include/pjmedia/wave.h
+++ b/pjmedia/include/pjmedia/wave.h
@@ -32,6 +32,7 @@
 #define PJMEDIA_RIFF_TAG	('F'<<24|'F'<<16|'I'<<8|'R')
 #define PJMEDIA_WAVE_TAG	('E'<<24|'V'<<16|'A'<<8|'W')
 #define PJMEDIA_FMT_TAG		(' '<<24|'t'<<16|'m'<<8|'f')
+#define PJMEDIA_DATA_TAG	('a'<<24|'t'<<16|'a'<<8|'d')
 
 
 /**
@@ -69,6 +70,32 @@
 typedef struct pjmedia_wave_hdr pjmedia_wave_hdr;
 
 
+/**
+ * On big-endian hosts, this function swaps the byte order of the values
+ * in the WAVE header fields. On little-endian hosts, this function does 
+ * nothing.
+ *
+ * Application SHOULD call this function after reading the WAVE header
+ * chunks from a file.
+ *
+ * @param hdr	    The WAVE header.
+ */
+PJ_DECL(void) pjmedia_wave_hdr_file_to_host( pjmedia_wave_hdr *hdr );
+
+
+/**
+ * On big-endian hosts, this function swaps the byte order of the values
+ * in the WAVE header fields. On little-endian hosts, this function does 
+ * nothing.
+ *
+ * Application SHOULD call this function before writing the WAVE header
+ * to a file.
+ *
+ * @param hdr	    The WAVE header.
+ */
+PJ_DECL(void) pjmedia_wave_hdr_host_to_file( pjmedia_wave_hdr *hdr );
+
+
 PJ_END_DECL
 
 #endif	/* __PJMEDIA_WAVE_H__ */
diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c
index fad562c..dfbe567 100644
--- a/pjmedia/src/pjmedia/conference.c
+++ b/pjmedia/src/pjmedia/conference.c
@@ -28,15 +28,29 @@
 #include <pj/pool.h>
 #include <pj/string.h>
 
+/* CONF_DEBUG enables detailed operation of the conference bridge.
+ * Beware that it prints large amounts of logs (several lines per frame).
+ */
 //#define CONF_DEBUG
 #ifdef CONF_DEBUG
 #   include <stdio.h>
-#   define TRACE_(x)   {printf x; fflush(stdout); }
+#   define TRACE_(x)   PJ_LOG(5,x)
 #else
 #   define TRACE_(x)
 #endif
 
 
+/* REC_FILE macro enables recording of the samples written to the sound
+ * device. The file contains RAW PCM data with no header, and has the
+ * same settings (clock rate etc) as the conference bridge.
+ * This should only be enabled when debugging audio quality *only*.
+ */
+//#define REC_FILE    "confrec.pcm"
+#ifdef REC_FILE
+static FILE *fhnd_rec;
+#endif
+
+
 #define THIS_FILE	"conference.c"
 #define RX_BUF_COUNT	8
 
@@ -966,6 +980,10 @@
 
     pj_assert(count == conf->samples_per_frame);
 
+    TRACE_((THIS_FILE, "read_port %.*s: count=%d", 
+		       (int)cport->name.slen, cport->name.ptr,
+		       count));
+
     /* If port's samples per frame and sampling rate matches conference
      * bridge's settings, get the frame directly from the port.
      */
@@ -976,6 +994,10 @@
 	f.buf = frame;
 	f.size = count * BYTES_PER_SAMPLE;
 
+	TRACE_((THIS_FILE, "  get_frame %.*s: count=%d", 
+		   (int)cport->name.slen, cport->name.ptr,
+		   count));
+
 	status = (cport->port->get_frame)(cport->port, &f);
 
 	*type = f.type;
@@ -997,6 +1019,9 @@
 	    f.buf = cport->rx_buf + cport->rx_buf_count;
 	    f.size = cport->samples_per_frame * BYTES_PER_SAMPLE;
 
+	    TRACE_((THIS_FILE, "  get_frame, count=%d", 
+		       cport->samples_per_frame));
+
 	    status = pjmedia_port_get_frame(cport->port, &f);
 
 	    if (status != PJ_SUCCESS) {
@@ -1005,12 +1030,16 @@
 	    }
 
 	    if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+		TRACE_((THIS_FILE, "  get_frame returned non-audio"));
 		zero_samples( cport->rx_buf + cport->rx_buf_count,
 			      cport->samples_per_frame);
 	    }
 
 	    cport->rx_buf_count += cport->samples_per_frame;
 
+	    TRACE_((THIS_FILE, "  rx buffer size is now %d",
+		    cport->rx_buf_count));
+
 	    pj_assert(cport->rx_buf_count <= cport->rx_buf_cap);
 	}
 
@@ -1022,6 +1051,9 @@
 	    
 	    unsigned src_count;
 
+	    TRACE_((THIS_FILE, "  resample, input count=%d", 
+		    pjmedia_resample_get_input_size(cport->rx_resample)));
+
 	    pjmedia_resample_run( cport->rx_resample,cport->rx_buf, frame);
 
 	    src_count = (unsigned)(count * 1.0 * cport->clock_rate / 
@@ -1032,6 +1064,9 @@
 			     cport->rx_buf_count);
 	    }
 
+	    TRACE_((THIS_FILE, "  rx buffer size is now %d",
+		    cport->rx_buf_count));
+
 	} else {
 
 	    copy_samples(frame, cport->rx_buf, count);
@@ -1067,8 +1102,9 @@
 	frame.buf = NULL;
 	frame.size = 0;
 
-	if (cport->port && cport->port->put_frame)
+	if (cport->port && cport->port->put_frame) {
 	    pjmedia_port_put_frame(cport->port, &frame);
+	}
 
 	cport->tx_level = 0;
 	return PJ_SUCCESS;
@@ -1111,11 +1147,15 @@
 	    buf[j] = (pj_int16_t) itemp;
 	}
 
-    } else {
+    } else if (cport->sources) {
 	/* No need to adjust signal level. */
 	for (j=0; j<conf->samples_per_frame; ++j) {
 	    buf[j] = unsigned2pcm(cport->mix_buf[j] / cport->sources);
 	}
+    } else {
+	// Not necessarry. Buffer has been zeroed before.
+	// zero_samples(buf, conf->samples_per_frame);
+	pj_assert(buf[0] == 0);
     }
 
     /* Calculate TX level if we need to do so. 
@@ -1154,6 +1194,10 @@
 	    frame.size = conf->samples_per_frame * BYTES_PER_SAMPLE;
 	    frame.timestamp.u64 = timestamp;
 
+	    TRACE_((THIS_FILE, "put_frame %.*s, count=%d", 
+			       (int)cport->name.slen, cport->name.ptr,
+			       frame.size / BYTES_PER_SAMPLE));
+
 	    return pjmedia_port_put_frame(cport->port, &frame);
 	} else
 	    return PJ_SUCCESS;
@@ -1185,6 +1229,10 @@
 	
 	pj_status_t status;
 
+	TRACE_((THIS_FILE, "write_port %.*s: count=%d", 
+			   (int)cport->name.slen, cport->name.ptr,
+			   cport->samples_per_frame));
+
 	if (cport->port) {
 	    pjmedia_frame frame;
 
@@ -1193,6 +1241,10 @@
 	    frame.size = cport->samples_per_frame * BYTES_PER_SAMPLE;
 	    frame.timestamp.u64 = timestamp;
 
+	    TRACE_((THIS_FILE, "put_frame %.*s, count=%d", 
+			       (int)cport->name.slen, cport->name.ptr,
+			       frame.size / BYTES_PER_SAMPLE));
+
 	    status = pjmedia_port_put_frame(cport->port, &frame);
 
 	} else
@@ -1205,6 +1257,9 @@
 			 cport->tx_buf_count);
 	}
 
+	TRACE_((THIS_FILE, " tx_buf count now is %d", 
+			   cport->tx_buf_count));
+
 	return status;
     }
 
@@ -1221,6 +1276,8 @@
     pjmedia_conf *conf = this_port->user_data;
     unsigned ci, cj, i, j;
     
+    TRACE_((THIS_FILE, "- clock -"));
+
     /* Check that correct size is specified. */
     pj_assert(frame->size == conf->samples_per_frame *
 			     conf->bits_per_sample / 8);
@@ -1228,8 +1285,6 @@
     /* Must lock mutex (must we??) */
     pj_mutex_lock(conf->mutex);
 
-    TRACE_(("p"));
-
     /* Zero all port's temporary buffers. */
     for (i=0, ci=0; i<conf->max_ports && ci < conf->port_cnt; ++i) {
 	struct conf_port *conf_port = conf->ports[i];
@@ -1244,8 +1299,7 @@
 	conf_port->sources = 0;
 	mix_buf = conf_port->mix_buf;
 
-	for (j=0; j<conf->samples_per_frame; ++j)
-	    mix_buf[j] = 0;
+	for (j=0; j<conf->samples_per_frame; ++j) mix_buf[j] = 0;
     }
 
     /* Get frames from all ports, and "mix" the signal 
@@ -1421,6 +1475,9 @@
 
     /* Return sound playback frame. */
     if (conf->ports[0]->sources) {
+	TRACE_((THIS_FILE, "write to audio, count=%d", 
+			   conf->samples_per_frame));
+
 	copy_samples( frame->buf, (pj_int16_t*)conf->ports[0]->mix_buf, 
 		      conf->samples_per_frame);
     } else {
@@ -1432,6 +1489,13 @@
 
     pj_mutex_unlock(conf->mutex);
 
+#ifdef REC_FILE
+    if (fhnd_rec == NULL)
+	fhnd_rec = fopen(REC_FILE, "wb");
+    if (fhnd_rec)
+	fwrite(frame->buf, frame->size, 1, fhnd_rec);
+#endif
+
     return PJ_SUCCESS;
 }
 
@@ -1448,8 +1512,6 @@
     pj_int16_t *target_snd_buf;
     unsigned i;
 
-    TRACE_(("r"));
-
     /* Check for correct size. */
     PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame *
 				     conf->bits_per_sample / 8,
diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c
index 86c78d5..e5759c1 100644
--- a/pjmedia/src/pjmedia/errno.c
+++ b/pjmedia/src/pjmedia/errno.c
@@ -122,6 +122,7 @@
     { PJMEDIA_ENOTVALIDWAVE,	    "Not a valid WAVE file" },
     { PJMEDIA_EWAVEUNSUPP,	    "Unsupported WAVE file format" },
     { PJMEDIA_EWAVETOOSHORT,	    "WAVE file too short" },
+    { PJMEDIA_EFRMFILETOOBIG,	    "Sound frame too large for file buffer"},
 
     /* Sound device errors: */
     { PJMEDIA_ENOSNDREC,	    "No suitable sound capture device" },
diff --git a/pjmedia/src/pjmedia/file_port.c b/pjmedia/src/pjmedia/file_player.c
similarity index 80%
rename from pjmedia/src/pjmedia/file_port.c
rename to pjmedia/src/pjmedia/file_player.c
index f1b2e81..ca5a3e1 100644
--- a/pjmedia/src/pjmedia/file_port.c
+++ b/pjmedia/src/pjmedia/file_player.c
@@ -30,13 +30,10 @@
 #define THIS_FILE   "file_port.c"
 
 
-#ifndef PJMEDIA_FILE_PORT_BUFSIZE
-#   define PJMEDIA_FILE_PORT_BUFSIZE    4000
-#endif
+#define SIGNATURE	    ('F'<<24|'P'<<16|'L'<<8|'Y')
+#define BYTES_PER_SAMPLE    2
 
 
-#define SIGNATURE	('F'<<24|'I'<<16|'L'<<8|'E')
-
 #if 1
 #   define TRACE_(x)	PJ_LOG(4,x)
 #else
@@ -44,37 +41,14 @@
 #endif
 
 #if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
-    PJ_INLINE(pj_int16_t) swap16(pj_int16_t val)
-    {
-	pj_uint8_t *p = (pj_uint8_t*)&val;
-	pj_uint8_t tmp = *p;
-	*p = *(p+1);
-	*(p+1) = tmp;
-	return val;
-    }
-    PJ_INLINE(pj_int32_t) swap32(pj_int32_t val)
-    {
-	pj_uint8_t *p = (pj_uint8_t*)&val;
-	pj_uint8_t tmp = *p;
-	*p = *(p+3);
-	*(p+3) = tmp;
-	tmp = *(p+1);
-	*(p+1) = *(p+2);
-	*(p+2) = tmp;
-	return val;
-    }
-#   define SWAP16(val16)	swap16(val16)
-#   define SWAP32(val32)	swap32(val32)
     static void samples_to_host(pj_int16_t *samples, unsigned count)
     {
 	unsigned i;
 	for (i=0; i<count; ++i) {
-	    samples[i] = SWAP16(samples[i]);
+	    samples[i] = pj_swap16(samples[i]);
 	}
     }
 #else
-#   define SWAP16(val16)	(val16)
-#   define SWAP32(val32)	(val32)
 #   define samples_to_host(samples,count)
 #endif
 
@@ -170,43 +144,13 @@
     }
 
     /* Convert samples to host rep */
-    samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/2);
+    samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/BYTES_PER_SAMPLE);
 
     return PJ_SUCCESS;
 }
 
 
 /*
- * Change the endianness of WAVE header fields.
- */
-void pjmedia_wave_hdr_swap_bytes( pjmedia_wave_hdr *hdr )
-{
-    hdr->riff_hdr.riff		    = SWAP32(hdr->riff_hdr.riff);
-    hdr->riff_hdr.file_len	    = SWAP32(hdr->riff_hdr.file_len);
-    hdr->riff_hdr.wave		    = SWAP32(hdr->riff_hdr.wave);
-    
-    hdr->fmt_hdr.fmt		    = SWAP32(hdr->fmt_hdr.fmt);
-    hdr->fmt_hdr.len		    = SWAP32(hdr->fmt_hdr.len);
-    hdr->fmt_hdr.fmt_tag	    = SWAP16(hdr->fmt_hdr.fmt_tag);
-    hdr->fmt_hdr.nchan		    = SWAP16(hdr->fmt_hdr.nchan);
-    hdr->fmt_hdr.sample_rate	    = SWAP32(hdr->fmt_hdr.sample_rate);
-    hdr->fmt_hdr.bytes_per_sec	    = SWAP32(hdr->fmt_hdr.bytes_per_sec);
-    hdr->fmt_hdr.block_align	    = SWAP16(hdr->fmt_hdr.block_align);
-    hdr->fmt_hdr.bits_per_sample    = SWAP16(hdr->fmt_hdr.bits_per_sample);
-    
-    hdr->data_hdr.data		    = SWAP32(hdr->data_hdr.data);
-    hdr->data_hdr.len		    = SWAP32(hdr->data_hdr.len);
-}
-
-
-#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
-#   define normalize_wave_hdr(hdr)  pjmedia_wave_hdr_swap_bytes(hdr)
-#else
-#   define normalize_wave_hdr(hdr)
-#endif
-
-
-/*
  * Create WAVE player port.
  */
 PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
@@ -223,7 +167,6 @@
 
 
     PJ_UNUSED_ARG(flags);
-    PJ_UNUSED_ARG(buff_size);
 
     /* Check arguments. */
     PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
@@ -265,8 +208,10 @@
 	return PJMEDIA_ENOTVALIDWAVE;
     }
 
-    /* Normalize WAVE header fields value (only used in big-endian hosts) */
-    normalize_wave_hdr(&wave_hdr);
+    /* Normalize WAVE header fields values from little-endian to host
+     * byte order.
+     */
+    pjmedia_wave_hdr_file_to_host(&wave_hdr);
     
     /* Validate WAVE file. */
     if (wave_hdr.riff_hdr.riff != PJMEDIA_RIFF_TAG ||
@@ -291,7 +236,7 @@
     }
 
     /* Block align must be 2*nchannels */
-    if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*2) {
+    if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*BYTES_PER_SAMPLE) {
 	pj_file_close(fport->fd);
 	return PJMEDIA_EWAVEUNSUPP;
     }
@@ -326,7 +271,8 @@
 
     /* Create file buffer.
      */
-    fport->bufsize = PJMEDIA_FILE_PORT_BUFSIZE;
+    if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE;
+    fport->bufsize = buff_size;
 
 
     /* Create buffer. */
@@ -354,11 +300,12 @@
 
 
     PJ_LOG(4,(THIS_FILE, 
-	      "File port '%.*s' created: clock=%dKHz, bufsize=%uKB, "
+	      "File player '%.*s' created: samp.rate=%d, ch=%d, bufsize=%uKB, "
 	      "filesize=%luKB",
 	      (int)fport->base.info.name.slen,
 	      fport->base.info.name.ptr,
-	      fport->base.info.sample_rate/1000,
+	      fport->base.info.sample_rate,
+	      fport->base.info.channel_count,
 	      fport->bufsize / 1000,
 	      (unsigned long)(fport->fsize / 1000)));
 
@@ -389,8 +336,9 @@
 
     pj_assert(fport->base.info.signature == SIGNATURE);
 
-    frame_size = fport->base.info.bytes_per_frame;
-    pj_assert(frame->size == frame_size);
+    //frame_size = fport->base.info.bytes_per_frame;
+    //pj_assert(frame->size == frame_size);
+    frame_size = frame->size;
 
     /* Copy frame from buffer. */
     frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
diff --git a/pjmedia/src/pjmedia/file_reader.c b/pjmedia/src/pjmedia/file_reader.c
deleted file mode 100644
index f1b2e81..0000000
--- a/pjmedia/src/pjmedia/file_reader.c
+++ /dev/null
@@ -1,448 +0,0 @@
-/* $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 
- */
-#include <pjmedia/file_port.h>
-#include <pjmedia/errno.h>
-#include <pjmedia/wave.h>
-#include <pj/assert.h>
-#include <pj/file_access.h>
-#include <pj/file_io.h>
-#include <pj/log.h>
-#include <pj/pool.h>
-#include <pj/string.h>
-
-
-#define THIS_FILE   "file_port.c"
-
-
-#ifndef PJMEDIA_FILE_PORT_BUFSIZE
-#   define PJMEDIA_FILE_PORT_BUFSIZE    4000
-#endif
-
-
-#define SIGNATURE	('F'<<24|'I'<<16|'L'<<8|'E')
-
-#if 1
-#   define TRACE_(x)	PJ_LOG(4,x)
-#else
-#   define TRACE_(x)
-#endif
-
-#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
-    PJ_INLINE(pj_int16_t) swap16(pj_int16_t val)
-    {
-	pj_uint8_t *p = (pj_uint8_t*)&val;
-	pj_uint8_t tmp = *p;
-	*p = *(p+1);
-	*(p+1) = tmp;
-	return val;
-    }
-    PJ_INLINE(pj_int32_t) swap32(pj_int32_t val)
-    {
-	pj_uint8_t *p = (pj_uint8_t*)&val;
-	pj_uint8_t tmp = *p;
-	*p = *(p+3);
-	*(p+3) = tmp;
-	tmp = *(p+1);
-	*(p+1) = *(p+2);
-	*(p+2) = tmp;
-	return val;
-    }
-#   define SWAP16(val16)	swap16(val16)
-#   define SWAP32(val32)	swap32(val32)
-    static void samples_to_host(pj_int16_t *samples, unsigned count)
-    {
-	unsigned i;
-	for (i=0; i<count; ++i) {
-	    samples[i] = SWAP16(samples[i]);
-	}
-    }
-#else
-#   define SWAP16(val16)	(val16)
-#   define SWAP32(val32)	(val32)
-#   define samples_to_host(samples,count)
-#endif
-
-struct file_port
-{
-    pjmedia_port     base;
-    pj_size_t	     bufsize;
-    char	    *buf;
-    char	    *readpos;
-
-    pj_off_t	     fsize;
-    pj_off_t	     fpos;
-    pj_oshandle_t    fd;
-
-};
-
-
-static pj_status_t file_put_frame(pjmedia_port *this_port, 
-				  const pjmedia_frame *frame);
-static pj_status_t file_get_frame(pjmedia_port *this_port, 
-				  pjmedia_frame *frame);
-static pj_status_t file_on_destroy(pjmedia_port *this_port);
-
-static struct file_port *create_file_port(pj_pool_t *pool)
-{
-    struct file_port *port;
-
-    port = pj_pool_zalloc(pool, sizeof(struct file_port));
-    if (!port)
-	return NULL;
-
-    port->base.info.name = pj_str("file");
-    port->base.info.signature = SIGNATURE;
-    port->base.info.type = PJMEDIA_TYPE_AUDIO;
-    port->base.info.has_info = PJ_TRUE;
-    port->base.info.need_info = PJ_FALSE;
-    port->base.info.pt = 0xFF;
-    port->base.info.encoding_name = pj_str("pcm");
-
-    port->base.put_frame = &file_put_frame;
-    port->base.get_frame = &file_get_frame;
-    port->base.on_destroy = &file_on_destroy;
-
-
-    /* Put in default values.
-     * These will be overriden once the file is read.
-     */
-    port->base.info.sample_rate = 8000;
-    port->base.info.bits_per_sample = 16;
-    port->base.info.samples_per_frame = 160;
-    port->base.info.bytes_per_frame = 320;
-
-    return port;
-}
-
-/*
- * Fill buffer.
- */
-static pj_status_t fill_buffer(struct file_port *fport)
-{
-    pj_ssize_t size_left = fport->bufsize;
-    unsigned size_to_read;
-    pj_ssize_t size;
-    pj_status_t status;
-
-    while (size_left > 0) {
-
-	/* Calculate how many bytes to read in this run. */
-	size = size_to_read = size_left;
-	status = pj_file_read(fport->fd, 
-			      &fport->buf[fport->bufsize-size_left], 
-			      &size);
-	if (status != PJ_SUCCESS)
-	    return status;
-	if (size < 0) {
-	    /* Should return more appropriate error code here.. */
-	    return PJ_ECANCELLED;
-	}
-
-	size_left -= size;
-	fport->fpos += size;
-
-	/* If size is less than size_to_read, it indicates that we've
-	 * encountered EOF. Rewind the file.
-	 */
-	if (size < (pj_ssize_t)size_to_read) {
-	    PJ_LOG(5,(THIS_FILE, "File port %.*s EOF, rewinding..",
-		      (int)fport->base.info.name.slen,
-		      fport->base.info.name.ptr));
-	    fport->fpos = sizeof(struct pjmedia_wave_hdr);
-	    pj_file_setpos( fport->fd, fport->fpos, PJ_SEEK_SET);
-	}
-    }
-
-    /* Convert samples to host rep */
-    samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/2);
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Change the endianness of WAVE header fields.
- */
-void pjmedia_wave_hdr_swap_bytes( pjmedia_wave_hdr *hdr )
-{
-    hdr->riff_hdr.riff		    = SWAP32(hdr->riff_hdr.riff);
-    hdr->riff_hdr.file_len	    = SWAP32(hdr->riff_hdr.file_len);
-    hdr->riff_hdr.wave		    = SWAP32(hdr->riff_hdr.wave);
-    
-    hdr->fmt_hdr.fmt		    = SWAP32(hdr->fmt_hdr.fmt);
-    hdr->fmt_hdr.len		    = SWAP32(hdr->fmt_hdr.len);
-    hdr->fmt_hdr.fmt_tag	    = SWAP16(hdr->fmt_hdr.fmt_tag);
-    hdr->fmt_hdr.nchan		    = SWAP16(hdr->fmt_hdr.nchan);
-    hdr->fmt_hdr.sample_rate	    = SWAP32(hdr->fmt_hdr.sample_rate);
-    hdr->fmt_hdr.bytes_per_sec	    = SWAP32(hdr->fmt_hdr.bytes_per_sec);
-    hdr->fmt_hdr.block_align	    = SWAP16(hdr->fmt_hdr.block_align);
-    hdr->fmt_hdr.bits_per_sample    = SWAP16(hdr->fmt_hdr.bits_per_sample);
-    
-    hdr->data_hdr.data		    = SWAP32(hdr->data_hdr.data);
-    hdr->data_hdr.len		    = SWAP32(hdr->data_hdr.len);
-}
-
-
-#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
-#   define normalize_wave_hdr(hdr)  pjmedia_wave_hdr_swap_bytes(hdr)
-#else
-#   define normalize_wave_hdr(hdr)
-#endif
-
-
-/*
- * Create WAVE player port.
- */
-PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
-						     const char *filename,
-						     unsigned flags,
-						     pj_ssize_t buff_size,
-						     void *user_data,
-						     pjmedia_port **p_port )
-{
-    pjmedia_wave_hdr wave_hdr;
-    pj_ssize_t size_read;
-    struct file_port *fport;
-    pj_status_t status;
-
-
-    PJ_UNUSED_ARG(flags);
-    PJ_UNUSED_ARG(buff_size);
-
-    /* Check arguments. */
-    PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
-
-    /* Check the file really exists. */
-    if (!pj_file_exists(filename)) {
-	return PJ_ENOTFOUND;
-    }
-
-    /* Create fport instance. */
-    fport = create_file_port(pool);
-    if (!fport) {
-	return PJ_ENOMEM;
-    }
-
-
-    /* Get the file size. */
-    fport->fsize = pj_file_size(filename);
-
-    /* Size must be more than WAVE header size */
-    if (fport->fsize <= sizeof(pjmedia_wave_hdr)) {
-	return PJMEDIA_ENOTVALIDWAVE;
-    }
-
-    /* Open file. */
-    status = pj_file_open( pool, filename, PJ_O_RDONLY, &fport->fd);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    /* Read the WAVE header. */
-    size_read = sizeof(wave_hdr);
-    status = pj_file_read( fport->fd, &wave_hdr, &size_read);
-    if (status != PJ_SUCCESS) {
-	pj_file_close(fport->fd);
-	return status;
-    }
-    if (size_read != sizeof(wave_hdr)) {
-	pj_file_close(fport->fd);
-	return PJMEDIA_ENOTVALIDWAVE;
-    }
-
-    /* Normalize WAVE header fields value (only used in big-endian hosts) */
-    normalize_wave_hdr(&wave_hdr);
-    
-    /* Validate WAVE file. */
-    if (wave_hdr.riff_hdr.riff != PJMEDIA_RIFF_TAG ||
-	wave_hdr.riff_hdr.wave != PJMEDIA_WAVE_TAG ||
-	wave_hdr.fmt_hdr.fmt != PJMEDIA_FMT_TAG)
-    {
-	pj_file_close(fport->fd);
-	TRACE_((THIS_FILE, 
-		"actual value|expected riff=%x|%x, wave=%x|%x fmt=%x|%x",
-		wave_hdr.riff_hdr.riff, PJMEDIA_RIFF_TAG,
-		wave_hdr.riff_hdr.wave, PJMEDIA_WAVE_TAG,
-		wave_hdr.fmt_hdr.fmt, PJMEDIA_FMT_TAG));
-	return PJMEDIA_ENOTVALIDWAVE;
-    }
-
-    /* Must be PCM with 16bits per sample */
-    if (wave_hdr.fmt_hdr.fmt_tag != 1 ||
-	wave_hdr.fmt_hdr.bits_per_sample != 16)
-    {
-	pj_file_close(fport->fd);
-	return PJMEDIA_EWAVEUNSUPP;
-    }
-
-    /* Block align must be 2*nchannels */
-    if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*2) {
-	pj_file_close(fport->fd);
-	return PJMEDIA_EWAVEUNSUPP;
-    }
-
-    /* Validate length. */
-    if (wave_hdr.data_hdr.len != fport->fsize-sizeof(pjmedia_wave_hdr)) {
-	pj_file_close(fport->fd);
-	return PJMEDIA_EWAVEUNSUPP;
-    }
-    if (wave_hdr.data_hdr.len < 400) {
-	pj_file_close(fport->fd);
-	return PJMEDIA_EWAVETOOSHORT;
-    }
-
-    /* It seems like we have a valid WAVE file. */
-
-    /* Initialize */
-    fport->base.user_data = user_data;
-
-    /* Update port info. */
-    fport->base.info.channel_count = wave_hdr.fmt_hdr.nchan;
-    fport->base.info.sample_rate = wave_hdr.fmt_hdr.sample_rate;
-    fport->base.info.bits_per_sample = wave_hdr.fmt_hdr.bits_per_sample;
-    fport->base.info.samples_per_frame = fport->base.info.sample_rate *
-					 wave_hdr.fmt_hdr.nchan *
-					 20 / 1000;
-    fport->base.info.bytes_per_frame = 
-	fport->base.info.samples_per_frame * 
-	fport->base.info.bits_per_sample / 8;
-
-    pj_strdup2(pool, &fport->base.info.name, filename);
-
-    /* Create file buffer.
-     */
-    fport->bufsize = PJMEDIA_FILE_PORT_BUFSIZE;
-
-
-    /* Create buffer. */
-    fport->buf = pj_pool_alloc(pool, fport->bufsize);
-    if (!fport->buf) {
-	pj_file_close(fport->fd);
-	return PJ_ENOMEM;
-    }
-
-    fport->readpos = fport->buf;
-
-    /* Set initial position of the file. */
-    fport->fpos = sizeof(struct pjmedia_wave_hdr);
-
-    /* Fill up the buffer. */
-    status = fill_buffer(fport);
-    if (status != PJ_SUCCESS) {
-	pj_file_close(fport->fd);
-	return status;
-    }
-
-    /* Done. */
-
-    *p_port = &fport->base;
-
-
-    PJ_LOG(4,(THIS_FILE, 
-	      "File port '%.*s' created: clock=%dKHz, bufsize=%uKB, "
-	      "filesize=%luKB",
-	      (int)fport->base.info.name.slen,
-	      fport->base.info.name.ptr,
-	      fport->base.info.sample_rate/1000,
-	      fport->bufsize / 1000,
-	      (unsigned long)(fport->fsize / 1000)));
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Put frame to file.
- */
-static pj_status_t file_put_frame(pjmedia_port *this_port, 
-				  const pjmedia_frame *frame)
-{
-    PJ_UNUSED_ARG(this_port);
-    PJ_UNUSED_ARG(frame);
-    return PJ_EINVALIDOP;
-}
-
-/*
- * Get frame from file.
- */
-static pj_status_t file_get_frame(pjmedia_port *this_port, 
-				  pjmedia_frame *frame)
-{
-    struct file_port *fport = (struct file_port*)this_port;
-    unsigned frame_size;
-    pj_status_t status;
-
-    pj_assert(fport->base.info.signature == SIGNATURE);
-
-    frame_size = fport->base.info.bytes_per_frame;
-    pj_assert(frame->size == frame_size);
-
-    /* Copy frame from buffer. */
-    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
-    frame->size = frame_size;
-    frame->timestamp.u64 = 0;
-
-    if (fport->readpos + frame_size <= fport->buf + fport->bufsize) {
-
-	/* Read contiguous buffer. */
-	pj_memcpy(frame->buf, fport->readpos, frame_size);
-
-	/* Fill up the buffer if all has been read. */
-	fport->readpos += frame_size;
-	if (fport->readpos == fport->buf + fport->bufsize) {
-	    fport->readpos = fport->buf;
-
-	    status = fill_buffer(fport);
-	    if (status != PJ_SUCCESS)
-		return status;
-	}
-    } else {
-	unsigned endread;
-
-	/* Split read.
-	 * First stage: read until end of buffer. 
-	 */
-	endread = (fport->buf+fport->bufsize) - fport->readpos;
-	pj_memcpy(frame->buf, fport->readpos, endread);
-
-	/* Second stage: fill up buffer, and read from the start of buffer. */
-	status = fill_buffer(fport);
-	if (status != PJ_SUCCESS) {
-	    pj_memset(((char*)frame->buf)+endread, 0, frame_size-endread);
-	    return status;
-	}
-
-	pj_memcpy(((char*)frame->buf)+endread, fport->buf, frame_size-endread);
-	fport->readpos = fport->buf + (frame_size - endread);
-    }
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Destroy port.
- */
-static pj_status_t file_on_destroy(pjmedia_port *this_port)
-{
-    struct file_port *fport = (struct file_port*) this_port;
-
-    pj_assert(this_port->info.signature == SIGNATURE);
-
-    pj_file_close(fport->fd);
-    return PJ_SUCCESS;
-}
diff --git a/pjmedia/src/pjmedia/file_writer.c b/pjmedia/src/pjmedia/file_writer.c
new file mode 100644
index 0000000..8471ba4
--- /dev/null
+++ b/pjmedia/src/pjmedia/file_writer.c
@@ -0,0 +1,306 @@
+/* $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 
+ */
+#include <pjmedia/file_port.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/wave.h>
+#include <pj/assert.h>
+#include <pj/file_access.h>
+#include <pj/file_io.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE	    "file_writer.c"
+#define SIGNATURE	    ('F'<<24|'W'<<16|'R'<<8|'T')
+#define BYTES_PER_SAMPLE    2
+
+
+struct file_port
+{
+    pjmedia_port     base;
+    pj_size_t	     bufsize;
+    char	    *buf;
+    char	    *writepos;
+
+    pj_oshandle_t    fd;
+};
+
+static pj_status_t file_put_frame(pjmedia_port *this_port, 
+				  const pjmedia_frame *frame);
+static pj_status_t file_get_frame(pjmedia_port *this_port, 
+				  pjmedia_frame *frame);
+static pj_status_t file_on_destroy(pjmedia_port *this_port);
+
+
+/*
+ * Create file writer port.
+ */
+PJ_DEF(pj_status_t) pjmedia_file_writer_port_create( pj_pool_t *pool,
+						     const char *filename,
+						     unsigned sampling_rate,
+						     unsigned channel_count,
+						     unsigned samples_per_frame,
+						     unsigned bits_per_sample,
+						     unsigned flags,
+						     pj_ssize_t buff_size,
+						     void *user_data,
+						     pjmedia_port **p_port )
+{
+    struct file_port *fport;
+    pjmedia_wave_hdr wave_hdr;
+    pj_ssize_t size;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(flags);
+    PJ_UNUSED_ARG(user_data);
+
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
+
+    /* Only supports 16bits per sample for now.
+     * See flush_buffer().
+     */
+    PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+    /* Create file port instance. */
+    fport = pj_pool_zalloc(pool, sizeof(struct file_port));
+    PJ_ASSERT_RETURN(fport != NULL, PJ_ENOMEM);
+
+    /* Initialize port info. */
+    fport->base.info.bits_per_sample = bits_per_sample;
+    fport->base.info.bytes_per_frame = samples_per_frame * bits_per_sample *
+				       channel_count / 8;
+    fport->base.info.channel_count = channel_count;
+    fport->base.info.encoding_name = pj_str("pom");
+    fport->base.info.has_info = 1;
+    pj_strdup2(pool, &fport->base.info.name, filename);
+    fport->base.info.need_info = 0;
+    fport->base.info.pt = 0xFF;
+    fport->base.info.sample_rate = sampling_rate;
+    fport->base.info.samples_per_frame = samples_per_frame;
+    fport->base.info.signature = SIGNATURE;
+    fport->base.info.type = PJMEDIA_TYPE_AUDIO;
+    
+    fport->base.get_frame = &file_get_frame;
+    fport->base.put_frame = &file_put_frame;
+    fport->base.on_destroy = &file_on_destroy;
+
+
+    /* Open file in write and read mode.
+     * We need the read mode because we'll modify the WAVE header once
+     * the recording has completed.
+     */
+    status = pj_file_open(pool, filename, PJ_O_WRONLY, &fport->fd);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Initialize WAVE header */
+    pj_memset(&wave_hdr, 0, sizeof(pjmedia_wave_hdr));
+    wave_hdr.riff_hdr.riff = PJMEDIA_RIFF_TAG;
+    wave_hdr.riff_hdr.file_len = 0; /* will be filled later */
+    wave_hdr.riff_hdr.wave = PJMEDIA_WAVE_TAG;
+
+    wave_hdr.fmt_hdr.fmt = PJMEDIA_FMT_TAG;
+    wave_hdr.fmt_hdr.len = 16;
+    wave_hdr.fmt_hdr.fmt_tag = 1;
+    wave_hdr.fmt_hdr.nchan = (pj_int16_t)channel_count;
+    wave_hdr.fmt_hdr.sample_rate = sampling_rate;
+    wave_hdr.fmt_hdr.bytes_per_sec = sampling_rate * channel_count * 
+				     bits_per_sample / 8;
+    wave_hdr.fmt_hdr.block_align = (pj_int16_t) (channel_count * 
+						 bits_per_sample / 8);
+    wave_hdr.fmt_hdr.bits_per_sample = (pj_int16_t)bits_per_sample;
+
+    wave_hdr.data_hdr.data = PJMEDIA_DATA_TAG;
+    wave_hdr.data_hdr.len = 0;	    /* will be filled later */
+
+
+    /* Convert WAVE header from host byte order to little endian
+     * before writing the header.
+     */
+    pjmedia_wave_hdr_host_to_file(&wave_hdr);
+
+
+    /* Write WAVE header */
+    size = sizeof(pjmedia_wave_hdr);
+    status = pj_file_write(fport->fd, &wave_hdr, &size);
+    if (status != PJ_SUCCESS) {
+	pj_file_close(fport->fd);
+	return status;
+    }
+
+    /* Set buffer size. */
+    if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE;
+    fport->bufsize = buff_size;
+
+    /* Check that buffer size is greater than bytes per frame */
+    pj_assert(fport->bufsize >= fport->base.info.bytes_per_frame);
+
+
+    /* Allocate buffer and set initial write position */
+    fport->buf = pj_pool_alloc(pool, fport->bufsize);
+    if (fport->buf == NULL) {
+	pj_file_close(fport->fd);
+	return PJ_ENOMEM;
+    }
+    fport->writepos = fport->buf;
+
+    /* Done. */
+    *p_port = &fport->base;
+
+    PJ_LOG(4,(THIS_FILE, 
+	      "File writer '%.*s' created: samp.rate=%d, bufsize=%uKB",
+	      (int)fport->base.info.name.slen,
+	      fport->base.info.name.ptr,
+	      fport->base.info.sample_rate,
+	      fport->bufsize / 1000));
+
+
+    return PJ_SUCCESS;
+}
+
+
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+    static void swap_samples(pj_int16_t *samples, unsigned count)
+    {
+	unsigned i;
+	for (i=0; i<count; ++i) {
+	    samples[i] = pj_swap16(samples[i]);
+	}
+    }
+#else
+#   define swap_samples(samples,count)
+#endif
+
+/*
+ * Flush the contents of the buffer to the file.
+ */
+static pj_status_t flush_buffer(struct file_port *fport)
+{
+    pj_ssize_t bytes = fport->writepos - fport->buf;
+    pj_status_t status;
+
+    /* Convert samples to little endian */
+    swap_samples((pj_int16_t*)fport->buf, bytes/BYTES_PER_SAMPLE);
+
+    /* Write to file. */
+    status = pj_file_write(fport->fd, fport->buf, &bytes);
+
+    /* Reset writepos */
+    fport->writepos = fport->buf;
+
+    return status;
+}
+
+/*
+ * Put a frame into the buffer. When the buffer is full, flush the buffer
+ * to the file.
+ */
+static pj_status_t file_put_frame(pjmedia_port *this_port, 
+				  const pjmedia_frame *frame)
+{
+    struct file_port *fport = (struct file_port *)this_port;
+
+    /* Flush buffer if we don't have enough room for the frame. */
+    if (fport->writepos + frame->size > fport->buf + fport->bufsize) {
+	pj_status_t status;
+	status = flush_buffer(fport);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    /* Check if frame is not too large. */
+    PJ_ASSERT_RETURN(fport->writepos+frame->size <= fport->buf+fport->bufsize,
+		     PJMEDIA_EFRMFILETOOBIG);
+
+    /* Copy frame to buffer. */
+    pj_memcpy(fport->writepos, frame->buf, frame->size);
+    fport->writepos += frame->size;
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Get frame, basicy is a no-op operation.
+ */
+static pj_status_t file_get_frame(pjmedia_port *this_port, 
+				  pjmedia_frame *frame)
+{
+    PJ_UNUSED_ARG(this_port);
+    PJ_UNUSED_ARG(frame);
+    return PJ_EINVALIDOP;
+}
+
+/*
+ * Close the port, modify file header with updated file length.
+ */
+static pj_status_t file_on_destroy(pjmedia_port *this_port)
+{
+    enum { FILE_LEN_POS = 4, DATA_LEN_POS = 40 };
+    struct file_port *fport = (struct file_port *)this_port;
+    pj_off_t file_size;
+    pj_ssize_t bytes;
+    pj_uint32_t wave_file_len;
+    pj_uint32_t wave_data_len;
+    pj_status_t status;
+
+    /* Flush remaining buffers. */
+    if (fport->writepos != fport->buf) 
+	flush_buffer(fport);
+
+    /* Get file size. */
+    status = pj_file_getpos(fport->fd, &file_size);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    /* Calculate wave fields */
+    wave_file_len = (pj_uint32_t)(file_size - 8);
+    wave_data_len = (pj_uint32_t)(file_size - sizeof(pjmedia_wave_hdr));
+
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+    wave_file_len = pj_swap32(wave_file_len);
+    wave_data_len = pj_swap32(wave_data_len);
+#endif
+
+    /* Seek to the file_len field. */
+    status = pj_file_setpos(fport->fd, FILE_LEN_POS, PJ_SEEK_SET);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    /* Write file_len */
+    bytes = sizeof(wave_file_len);
+    status = pj_file_write(fport->fd, &wave_file_len, &bytes);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    /* Seek to data_len field. */
+    status = pj_file_setpos(fport->fd, DATA_LEN_POS, PJ_SEEK_SET);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    /* Write file_len */
+    bytes = sizeof(wave_data_len);
+    status = pj_file_write(fport->fd, &wave_data_len, &bytes);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    /* Close file */
+    status = pj_file_close(fport->fd);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    /* Done. */
+    return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/pasound.c b/pjmedia/src/pjmedia/pasound.c
index 44bac29..769a1c1 100644
--- a/pjmedia/src/pjmedia/pasound.c
+++ b/pjmedia/src/pjmedia/pasound.c
@@ -131,9 +131,9 @@
 	PJ_LOG(5,(THIS_FILE, "Player thread started"));
     }
 
-    if (statusFlags & paInputUnderflow)
+    if (statusFlags & paOutputUnderflow)
 	++stream->underflow;
-    if (statusFlags & paInputOverflow)
+    if (statusFlags & paOutputOverflow)
 	++stream->overflow;
 
     stream->timestamp += frameCount;
@@ -239,6 +239,7 @@
     PaStreamParameters inputParam;
     int sampleFormat;
     const PaDeviceInfo *paDevInfo = NULL;
+    const PaHostApiInfo *paHostApiInfo = NULL;
     unsigned paFrames;
     PaError err;
 
@@ -291,6 +292,8 @@
     inputParam.sampleFormat = sampleFormat;
     inputParam.suggestedLatency = paDevInfo->defaultLowInputLatency;
 
+    paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
+
     /* Frames in PortAudio is number of samples in a single channel */
     paFrames = samples_per_frame / channel_count;
 
@@ -302,11 +305,12 @@
 	return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
     }
 
-    PJ_LOG(5,(THIS_FILE, "%s opening device %s for recording, sample rate=%d, "
-			 "channel count=%d, "
-			 "%d bits per sample, %d samples per buffer",
+    PJ_LOG(5,(THIS_FILE, "%s opening device %s (%s) for recording, sample "
+			 "rate=%d, ch=%d, "
+			 "bits=%d, %d samples per frame",
 			 (err==0 ? "Success" : "Error"),
-			 paDevInfo->name, clock_rate, channel_count,
+			 paDevInfo->name, paHostApiInfo->name,
+			 clock_rate, channel_count,
 			 bits_per_sample, samples_per_frame));
 
     *p_snd_strm = stream;
@@ -328,6 +332,7 @@
     PaStreamParameters outputParam;
     int sampleFormat;
     const PaDeviceInfo *paDevInfo = NULL;
+    const PaHostApiInfo *paHostApiInfo = NULL;
     unsigned paFrames;
     PaError err;
 
@@ -380,6 +385,8 @@
     outputParam.sampleFormat = sampleFormat;
     outputParam.suggestedLatency = paDevInfo->defaultLowInputLatency;
 
+    paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
+
     /* Frames in PortAudio is number of samples in a single channel */
     paFrames = samples_per_frame / channel_count;
 
@@ -391,11 +398,12 @@
 	return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
     }
 
-    PJ_LOG(5,(THIS_FILE, "%s opening device %s for playing, sample rate=%d, "
-			 "channel count=%d, "
-			 "%d bits per sample, %d samples per frame",
+    PJ_LOG(5,(THIS_FILE, "%s opening device %s(%s) for playing, sample rate=%d"
+			 ", ch=%d, "
+			 "bits=%d, %d samples per frame",
 			 (err==0 ? "Success" : "Error"),
-			 paDevInfo->name, clock_rate, channel_count,
+			 paDevInfo->name, paHostApiInfo->name, 
+			 clock_rate, channel_count,
 		 	 bits_per_sample, samples_per_frame));
 
     *p_snd_strm = stream;
@@ -425,6 +433,8 @@
     int sampleFormat;
     const PaDeviceInfo *paRecDevInfo = NULL;
     const PaDeviceInfo *paPlayDevInfo = NULL;
+    const PaHostApiInfo *paRecHostApiInfo = NULL;
+    const PaHostApiInfo *paPlayHostApiInfo = NULL;
     unsigned paFrames;
     PaError err;
 
@@ -497,12 +507,16 @@
     inputParam.sampleFormat = sampleFormat;
     inputParam.suggestedLatency = paRecDevInfo->defaultLowInputLatency;
 
+    paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
+
     pj_memset(&outputParam, 0, sizeof(outputParam));
     outputParam.device = play_id;
     outputParam.channelCount = channel_count;
     outputParam.hostApiSpecificStreamInfo = NULL;
     outputParam.sampleFormat = sampleFormat;
-    outputParam.suggestedLatency = paPlayDevInfo->defaultLowInputLatency;
+    outputParam.suggestedLatency = paPlayDevInfo->defaultLowOutputLatency;
+
+    paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
 
     /* Frames in PortAudio is number of samples in a single channel */
     paFrames = samples_per_frame / channel_count;
@@ -515,11 +529,12 @@
 	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",
+    PJ_LOG(5,(THIS_FILE, "%s opening device %s(%s)/%s(%s) for recording and "
+			 "playback, sample rate=%d, ch=%d, "
+			 "bits=%d, %d samples per frame",
 			 (err==0 ? "Success" : "Error"),
-			 paRecDevInfo->name, paPlayDevInfo->name,
+			 paRecDevInfo->name, paRecHostApiInfo->name,
+			 paPlayDevInfo->name, paPlayHostApiInfo->name,
 			 clock_rate, channel_count,
 			 bits_per_sample, samples_per_frame));
 
diff --git a/pjmedia/src/pjmedia/port.c b/pjmedia/src/pjmedia/port.c
index d283b97..50a008e 100644
--- a/pjmedia/src/pjmedia/port.c
+++ b/pjmedia/src/pjmedia/port.c
@@ -35,6 +35,7 @@
 
     PJ_ASSERT_RETURN(pool && upstream_port && downstream_port, PJ_EINVAL);
 
+#if 0
     /* They both MUST have the same media type. */
     PJ_ASSERT_RETURN(upstream_port->info.type ==
 		     downstream_port->info.type, PJMEDIA_ENCTYPE);
@@ -57,17 +58,22 @@
     PJ_ASSERT_RETURN(upstream_port->info.bytes_per_frame ==
 		     downstream_port->info.bytes_per_frame, 
 		     PJMEDIA_ENCBYTES);
+#endif
 
     /* Create mutual attachment. */
-    status = upstream_port->on_downstream_connect( pool, upstream_port,
-						   downstream_port );
-    if (status != PJ_SUCCESS)
-	return status;
+    if (upstream_port->on_downstream_connect) {
+	status = upstream_port->on_downstream_connect( pool, upstream_port,
+						       downstream_port );
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
 
-    status = downstream_port->on_upstream_connect( pool, downstream_port,
-						   upstream_port );
-    if (status != PJ_SUCCESS)
-	return status;
+    if (downstream_port->on_upstream_connect) {
+	status = downstream_port->on_upstream_connect( pool, downstream_port,
+						       upstream_port );
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
 
     /* Save the attachment. */
     upstream_port->downstream_port = downstream_port;
diff --git a/pjmedia/src/pjmedia/resample.c b/pjmedia/src/pjmedia/resample.c
index 9045a02..95cf44c 100644
--- a/pjmedia/src/pjmedia/resample.c
+++ b/pjmedia/src/pjmedia/resample.c
@@ -498,7 +498,7 @@
      * to yield the same performance.
      */
     if (rate_out < rate_in) {
-	high_quality = 0;
+	//high_quality = 0;
     }
 
 #if !defined(PJMEDIA_HAS_LARGE_FILTER) || PJMEDIA_HAS_LARGE_FILTER==0
@@ -579,8 +579,7 @@
          *     0    xoff  2*xoff       size+2*xoff 
 	 */
 	dst_buf = resample->buffer + resample->xoff*2;
-	for (i=0; i<resample->frame_size; ++i)
-	    dst_buf[i] = input[i];
+	for (i=0; i<resample->frame_size; ++i) dst_buf[i] = input[i];
 	    
 	if (resample->factor >= 1) {
 
@@ -632,3 +631,10 @@
 	SrcLinear( input, output, resample->factor, resample->frame_size);
     }
 }
+
+PJ_DEF(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample)
+{
+    PJ_ASSERT_RETURN(resample != NULL, 0);
+    return resample->frame_size;
+}
+
diff --git a/pjmedia/src/pjmedia/resample_port.c b/pjmedia/src/pjmedia/resample_port.c
new file mode 100644
index 0000000..d8ec708
--- /dev/null
+++ b/pjmedia/src/pjmedia/resample_port.c
@@ -0,0 +1,185 @@
+/* $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 
+ */
+#include <pjmedia/resample.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define BYTES_PER_SAMPLE	2
+
+struct resample_port
+{
+    pjmedia_port	 base;
+    pjmedia_resample	*resample_get;
+    pjmedia_resample	*resample_put;
+    pj_int16_t		*get_buf;
+    pj_int16_t		*put_buf;
+    unsigned		 downstream_frame_size;
+    unsigned		 upstream_frame_size;
+};
+
+
+
+static pj_status_t resample_put_frame(pjmedia_port *this_port,
+				      const pjmedia_frame *frame);
+static pj_status_t resample_get_frame(pjmedia_port *this_port, 
+				      pjmedia_frame *frame);
+
+
+
+PJ_DEF(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool,
+						  pj_bool_t high_quality,
+						  pj_bool_t large_filter,
+						  unsigned downstream_rate,
+						  unsigned upstream_rate,
+						  unsigned channel_count,
+						  unsigned samples_per_frame,
+						  pjmedia_port **p_port )
+{
+    struct resample_port *rport;
+    unsigned upstream_samples_per_frame;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
+
+    upstream_samples_per_frame = (unsigned)(samples_per_frame * 1.0 *
+					    upstream_rate / downstream_rate);
+
+    /* Create and initialize port. */
+    rport = pj_pool_zalloc(pool, sizeof(struct resample_port));
+    PJ_ASSERT_RETURN(rport != NULL, PJ_ENOMEM);
+
+    rport->base.info.bits_per_sample = 16;
+    rport->base.info.bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
+    rport->base.info.channel_count = channel_count;
+    rport->base.info.encoding_name = pj_str("pcm");
+    rport->base.info.has_info = 1;
+    rport->base.info.name = pj_str("resample");
+    rport->base.info.need_info = 0;
+    rport->base.info.pt = 0xFF;
+    rport->base.info.sample_rate = upstream_rate;
+    rport->base.info.samples_per_frame = upstream_samples_per_frame;
+    rport->base.info.signature = 0;
+    rport->base.info.type = PJMEDIA_TYPE_AUDIO;
+
+    rport->downstream_frame_size = samples_per_frame;
+    rport->upstream_frame_size = upstream_samples_per_frame;
+
+    /* Create buffers. 
+     * We need separate buffer for get_frame() and put_frame() since
+     * both functions may run simultaneously.
+     */
+    rport->get_buf = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE);
+    PJ_ASSERT_RETURN(rport->get_buf, PJ_ENOMEM);
+
+    rport->put_buf = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE);
+    PJ_ASSERT_RETURN(rport->put_buf, PJ_ENOMEM);
+
+
+    /* Create "get_frame" resample */
+    status = pjmedia_resample_create( pool, high_quality, large_filter,
+				      downstream_rate, upstream_rate,
+				      samples_per_frame, &rport->resample_get);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Create "put_frame" resample */
+    status = pjmedia_resample_create( pool, high_quality, large_filter,
+				      upstream_rate, downstream_rate,
+				      upstream_samples_per_frame,
+				      &rport->resample_put);
+
+    /* Set get_frame and put_frame interface */
+    rport->base.get_frame = &resample_get_frame;
+    rport->base.put_frame = &resample_put_frame;
+
+
+    /* Done */
+    *p_port = &rport->base;
+
+    return PJ_SUCCESS;
+}
+
+
+
+static pj_status_t resample_put_frame(pjmedia_port *this_port,
+				      const pjmedia_frame *frame)
+{
+    struct resample_port *rport = (struct resample_port*) this_port;
+    pjmedia_frame downstream_frame;
+
+    /* Return if we don't have downstream port. */
+    if (this_port->downstream_port == NULL) {
+	return PJ_SUCCESS;
+    }
+
+    if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+	pjmedia_resample_run( rport->resample_put, frame->buf, rport->put_buf);
+
+	downstream_frame.buf = rport->put_buf;
+	downstream_frame.size = rport->downstream_frame_size * 
+				BYTES_PER_SAMPLE;
+    } else {
+	downstream_frame.buf = frame->buf;
+	downstream_frame.size = frame->size;
+    }
+
+    downstream_frame.type = frame->type;
+    downstream_frame.timestamp.u64 = frame->timestamp.u64;
+
+    return pjmedia_port_put_frame( this_port->downstream_port, 
+				   &downstream_frame );
+}
+
+
+
+static pj_status_t resample_get_frame(pjmedia_port *this_port, 
+				      pjmedia_frame *frame)
+{
+    struct resample_port *rport = (struct resample_port*) this_port;
+    pjmedia_frame downstream_frame;
+    pj_status_t status;
+
+    /* Return silence if we don't have downstream port */
+    if (this_port->downstream_port == NULL) {
+	pj_memset(frame->buf, frame->size, 0);
+	return PJ_SUCCESS;
+    }
+
+    downstream_frame.buf = rport->get_buf;
+    downstream_frame.size = rport->downstream_frame_size * BYTES_PER_SAMPLE;
+    downstream_frame.timestamp.u64 = frame->timestamp.u64;
+    downstream_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+    status = pjmedia_port_get_frame( this_port->downstream_port,
+				     &downstream_frame);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pjmedia_resample_run( rport->resample_get, rport->get_buf, frame->buf);
+
+    frame->size = rport->upstream_frame_size * BYTES_PER_SAMPLE;
+    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+    return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c
index aca74e1..b4e6684 100644
--- a/pjmedia/src/pjmedia/sound_port.c
+++ b/pjmedia/src/pjmedia/sound_port.c
@@ -70,6 +70,9 @@
     if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
 	goto no_frame;
 
+    /* Must supply the required samples */
+    pj_assert(frame.size == size);
+
     return PJ_SUCCESS;
 
 no_frame:
diff --git a/pjmedia/src/pjmedia/wave.c b/pjmedia/src/pjmedia/wave.c
new file mode 100644
index 0000000..29aafd5
--- /dev/null
+++ b/pjmedia/src/pjmedia/wave.c
@@ -0,0 +1,57 @@
+/* $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 
+ */
+#include <pjmedia/wave.h>
+
+/*
+ * Change the endianness of WAVE header fields.
+ */
+static void wave_hdr_swap_bytes( pjmedia_wave_hdr *hdr )
+{
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+    hdr->riff_hdr.riff		    = pj_swap32(hdr->riff_hdr.riff);
+    hdr->riff_hdr.file_len	    = pj_swap32(hdr->riff_hdr.file_len);
+    hdr->riff_hdr.wave		    = pj_swap32(hdr->riff_hdr.wave);
+    
+    hdr->fmt_hdr.fmt		    = pj_swap32(hdr->fmt_hdr.fmt);
+    hdr->fmt_hdr.len		    = pj_swap32(hdr->fmt_hdr.len);
+    hdr->fmt_hdr.fmt_tag	    = pj_swap16(hdr->fmt_hdr.fmt_tag);
+    hdr->fmt_hdr.nchan		    = pj_swap16(hdr->fmt_hdr.nchan);
+    hdr->fmt_hdr.sample_rate	    = pj_swap32(hdr->fmt_hdr.sample_rate);
+    hdr->fmt_hdr.bytes_per_sec	    = pj_swap32(hdr->fmt_hdr.bytes_per_sec);
+    hdr->fmt_hdr.block_align	    = pj_swap16(hdr->fmt_hdr.block_align);
+    hdr->fmt_hdr.bits_per_sample    = pj_swap16(hdr->fmt_hdr.bits_per_sample);
+    
+    hdr->data_hdr.data		    = pj_swap32(hdr->data_hdr.data);
+    hdr->data_hdr.len		    = pj_swap32(hdr->data_hdr.len);
+#else
+    PJ_UNUSED_ARG(hdr);
+#endif
+}
+
+
+PJ_DEF(void) pjmedia_wave_hdr_file_to_host( pjmedia_wave_hdr *hdr )
+{
+    wave_hdr_swap_bytes(hdr);
+}
+
+PJ_DEF(void) pjmedia_wave_hdr_host_to_file( pjmedia_wave_hdr *hdr )
+{
+    wave_hdr_swap_bytes(hdr);
+}
+