Index: pjproject/trunk/Makefile
===================================================================
--- pjproject/trunk/Makefile	(revision 2505)
+++ pjproject/trunk/Makefile	(revision 2506)
@@ -34,4 +34,5 @@
 	pjnath/lib/libpjnath-$(TARGET_NAME).a \
 	pjmedia/lib/libpjmedia-$(TARGET_NAME).a \
+	pjmedia/lib/libpjmedia-audiodev-$(TARGET_NAME).a \
 	pjmedia/lib/libpjmedia-codec-$(TARGET_NAME).a \
        	pjsip/lib/libpjsip-$(TARGET_NAME).a \
Index: pjproject/trunk/build.mak.in
===================================================================
--- pjproject/trunk/build.mak.in	(revision 2505)
+++ pjproject/trunk/build.mak.in	(revision 2506)
@@ -61,4 +61,5 @@
 	-lpjmedia-codec-$(TARGET_NAME)\
 	-lpjmedia-$(TARGET_NAME)\
+	-lpjmedia-audiodev-$(TARGET_NAME)\
 	-lpjnath-$(TARGET_NAME)\
 	-lpjlib-util-$(TARGET_NAME)\
Index: pjproject/trunk/build.symbian/bld.inf
===================================================================
--- pjproject/trunk/build.symbian/bld.inf	(revision 2505)
+++ pjproject/trunk/build.symbian/bld.inf	(revision 2506)
@@ -1,5 +1,2 @@
-#define SND_USE_NULL	0
-#define SND_USE_APS	0
-
 prj_platforms
 winscw
@@ -24,13 +21,8 @@
 libgsmcodec.mmp
 libspeexcodec.mmp
+libpassthroughcodec.mmp
 
-/* Sound device impl */
-#if SND_USE_NULL
-	null_audio.mmp
-#elif SND_USE_APS
-	symbian_audio_aps.mmp
-#else
-	symbian_audio.mmp
-#endif
+/* Audio device. */
+pjmedia_audiodev.mmp
 
 /* Applications */
Index: pjproject/trunk/build.symbian/libpassthroughcodec.mmp
===================================================================
--- pjproject/trunk/build.symbian/libpassthroughcodec.mmp	(revision 2506)
+++ pjproject/trunk/build.symbian/libpassthroughcodec.mmp	(revision 2506)
@@ -0,0 +1,26 @@
+TARGET		libpassthroughcodec.lib
+TARGETTYPE	lib
+
+MACRO		HAVE_CONFIG_H
+MACRO		PJ_M_I386=1
+MACRO		PJ_SYMBIAN=1
+
+//
+// GCCE optimization setting
+//
+OPTION		GCCE -O2 -fno-unit-at-a-time
+
+//
+// Passthrough codecs wrapper for pjmedia-codec
+//
+SOURCEPATH 	..\pjmedia\src\pjmedia-codec
+SOURCE		passthrough.c
+
+//
+// Header files
+//
+SYSTEMINCLUDE	..\pjmedia\include
+SYSTEMINCLUDE	..\pjlib\include 
+
+SYSTEMINCLUDE	\epoc32\include 
+SYSTEMINCLUDE	\epoc32\include\libc
Index: pjproject/trunk/build.symbian/pjlib.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjlib.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjlib.mmp	(revision 2506)
@@ -19,8 +19,4 @@
 
 SOURCEPATH	..\pjlib\src\pj
-
-OPTION		CW -lang c++
-OPTION		ARMCC --cpp
-OPTION		GCC	-x c++
 
 MACRO		PJ_M_I386=1
Index: pjproject/trunk/build.symbian/pjlib_test.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjlib_test.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjlib_test.mmp	(revision 2506)
@@ -15,8 +15,4 @@
 STATICLIBRARY	pjlib.lib 
 #endif
-
-OPTION		CW -lang c++
-OPTION		ARMCC --cpp
-OPTION		GCC	-x c++
 
 // Test files
Index: pjproject/trunk/build.symbian/pjlib_util.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjlib_util.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjlib_util.mmp	(revision 2506)
@@ -22,8 +22,4 @@
 MACRO		PJ_M_I386=1
 MACRO		PJ_SYMBIAN=1
-
-OPTION		CW -lang c++
-OPTION		ARMCC --cpp
-OPTION		GCC	-x c++
 
 //
Index: pjproject/trunk/build.symbian/pjmedia.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjmedia.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjmedia.mmp	(revision 2506)
@@ -41,4 +41,5 @@
 SOURCE		codec.c
 SOURCE		conference.c
+SOURCE		conf_switch.c
 SOURCE		echo_common.c
 SOURCE		echo_port.c
Index: pjproject/trunk/build.symbian/pjmedia_audiodev.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjmedia_audiodev.mmp	(revision 2506)
+++ pjproject/trunk/build.symbian/pjmedia_audiodev.mmp	(revision 2506)
@@ -0,0 +1,30 @@
+TARGET 		pjmedia_audiodev.lib
+TARGETTYPE 	lib
+
+SOURCEPATH	..\pjmedia\src\pjmedia-audiodev
+
+//
+// GCCE optimization setting
+//
+//OPTION		GCCE -O2 -fno-unit-at-a-time
+
+MACRO		PJ_M_I386=1
+MACRO		PJ_SYMBIAN=1
+
+SOURCE		audiodev.c
+SOURCE		errno.c
+SOURCE		symb_aps_dev.cpp
+SOURCE		symb_mda_dev.cpp
+
+SYSTEMINCLUDE	..\pjmedia\include
+SYSTEMINCLUDE	..\pjlib\include 
+SYSTEMINCLUDE	..\pjlib-util\include 
+
+SYSTEMINCLUDE	\epoc32\include
+SYSTEMINCLUDE	\epoc32\include\libc
+SYSTEMINCLUDE   \epoc32\include\mmf\server 
+SYSTEMINCLUDE   \epoc32\include\mmf\common 
+SYSTEMINCLUDE   \epoc32\include\mda\common 
+SYSTEMINCLUDE	\epoc32\include\mmf\plugin 
+
+
Index: pjproject/trunk/build.symbian/pjnath.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjnath.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjnath.mmp	(revision 2506)
@@ -22,8 +22,4 @@
 MACRO		PJ_M_I386=1
 MACRO		PJ_SYMBIAN=1
-
-OPTION		CW -lang c++
-OPTION		ARMCC --cpp
-OPTION		GCC	-x c++
 
 //
Index: pjproject/trunk/build.symbian/pjsdp.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjsdp.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjsdp.mmp	(revision 2506)
@@ -20,8 +20,4 @@
 SOURCEPATH	..\pjmedia\src\pjmedia
 
-OPTION		CW -lang c++
-OPTION		ARMCC --cpp
-OPTION		GCC	-x c++
-
 MACRO		PJ_M_I386=1
 MACRO		PJ_SYMBIAN=1
Index: pjproject/trunk/build.symbian/pjsip.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjsip.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjsip.mmp	(revision 2506)
@@ -25,8 +25,4 @@
 MACRO		PJ_M_I386=1
 MACRO		PJ_SYMBIAN=1
-
-OPTION		CW -lang c++
-OPTION		ARMCC --cpp
-OPTION		GCC	-x c++
 
 // PJSIP-CORE files
Index: pjproject/trunk/build.symbian/pjsip_simple.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjsip_simple.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjsip_simple.mmp	(revision 2506)
@@ -26,9 +26,4 @@
 MACRO		PJ_SYMBIAN=1
 
-OPTION		CW -lang c++
-OPTION		ARMCC --cpp
-OPTION		GCC	-x c++
-
-
 // PJSIP-SIMPLE files
 
Index: pjproject/trunk/build.symbian/pjstun_client.mmp
===================================================================
--- pjproject/trunk/build.symbian/pjstun_client.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/pjstun_client.mmp	(revision 2506)
@@ -7,8 +7,4 @@
 MACRO		PJ_M_I386=1
 MACRO		PJ_SYMBIAN=1
-
-OPTION		CW -lang c++
-OPTION		ARMCC --cpp
-OPTION		GCC	-x c++
 
 // PJSTUN-CLIENT files
Index: pjproject/trunk/build.symbian/symbian_audio.mmp
===================================================================
--- pjproject/trunk/build.symbian/symbian_audio.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/symbian_audio.mmp	(revision 2506)
@@ -25,8 +25,4 @@
 
 OPTION		CW -lang c++
-
-//
-// GCCE optimization setting
-//
 OPTION		GCCE -O2 -fno-unit-at-a-time
 
@@ -34,5 +30,7 @@
 MACRO		PJ_SYMBIAN=1
 
+SOURCE		nullsound.c
 SOURCE		symbian_sound.cpp
+SOURCE		symbian_sound_aps.cpp
 
 SYSTEMINCLUDE	..\pjlib\include
@@ -41,4 +39,7 @@
 SYSTEMINCLUDE	\epoc32\include
 SYSTEMINCLUDE	\epoc32\include\libc
+SYSTEMINCLUDE   \epoc32\include\mmf\server 
+SYSTEMINCLUDE   \epoc32\include\mmf\common 
+SYSTEMINCLUDE   \epoc32\include\mda\common 
 
 SYSTEMINCLUDE	\epoc32\include\mmf\plugin 
Index: pjproject/trunk/build.symbian/symbian_audio_aps.mmp
===================================================================
--- pjproject/trunk/build.symbian/symbian_audio_aps.mmp	(revision 2505)
+++ 	(revision )
@@ -1,20 +1,0 @@
-TARGET 					symbian_audio_aps.lib
-TARGETTYPE 				lib
-
-OPTION                                  CW -lang c++ 
-OPTION                                  GCCE -O2 -fno-unit-at-a-time 
-
-MACRO                                   PJ_M_I386=1
-MACRO					PJ_SYMBIAN=1 
-
-SYSTEMINCLUDE                           ..\pjmedia\include 
-SYSTEMINCLUDE                           ..\pjlib\include 
-
-SOURCEPATH                              ..\pjmedia\src\pjmedia 
-SOURCE                                  symbian_sound_aps.cpp 
-
-SYSTEMINCLUDE                           \epoc32\include\mmf\server 
-SYSTEMINCLUDE                           \epoc32\include\mmf\common 
-SYSTEMINCLUDE                           \epoc32\include\mda\common 
-SYSTEMINCLUDE                           \epoc32\include\libc 
-SYSTEMINCLUDE                           \epoc32\include 
Index: pjproject/trunk/build.symbian/symbian_ua.mmp
===================================================================
--- pjproject/trunk/build.symbian/symbian_ua.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/symbian_ua.mmp	(revision 2506)
@@ -1,63 +1,70 @@
-#define SND_USE_NULL	0
-#define SND_USE_APS	0
+#define SND_HAS_APS	0
+#define SND_HAS_VAS	0
+#define SND_HAS_MDA	1
 
-TARGET 		symbian_ua.exe
-TARGETTYPE 	exe
-UID		0x0 0xA000000D
+TARGET 			symbian_ua.exe
+TARGETTYPE 		exe
+UID			0x0 0x200235D3
 
-SOURCEPATH	..\pjsip-apps\src\symbian_ua
+SOURCEPATH		..\pjsip-apps\src\symbian_ua
 
-MACRO		PJ_M_I386=1
-MACRO		PJ_SYMBIAN=1
+MACRO			PJ_M_I386=1
+MACRO			PJ_SYMBIAN=1
 
 // Source files
 
-SOURCE	ua.cpp
-SOURCE	main_symbian.cpp
+SOURCE			ua.cpp
+SOURCE			main_symbian.cpp
 
-DOCUMENT ua.h
+DOCUMENT 		ua.h
 
-START RESOURCE  symbian_ua_reg.rss
+START RESOURCE  	symbian_ua_reg.rss
 	TARGETPATH      \private\10003a3f\apps
 END
 
-SYSTEMINCLUDE	..\pjlib\include
-SYSTEMINCLUDE	..\pjlib-util\include
-SYSTEMINCLUDE	..\pjnath\include
-SYSTEMINCLUDE	..\pjmedia\include
-SYSTEMINCLUDE	..\pjsip\include
+SYSTEMINCLUDE		..\pjlib\include
+SYSTEMINCLUDE		..\pjlib-util\include
+SYSTEMINCLUDE		..\pjnath\include
+SYSTEMINCLUDE		..\pjmedia\include
+SYSTEMINCLUDE		..\pjsip\include
 
-SYSTEMINCLUDE	\epoc32\include
-SYSTEMINCLUDE	\epoc32\include\libc
+SYSTEMINCLUDE		\epoc32\include
+SYSTEMINCLUDE		\epoc32\include\libc
 
-STATICLIBRARY	pjsua_lib.lib pjsip_ua.lib
-STATICLIBRARY	pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
-STATICLIBRARY	pjnath.lib pjlib_util.lib pjlib.lib
-STATICLIBRARY	libsrtp.lib
-STATICLIBRARY	libgsmcodec.lib libspeexcodec.lib
+STATICLIBRARY		pjsua_lib.lib pjsip_ua.lib
+STATICLIBRARY		pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
+STATICLIBRARY		pjnath.lib pjlib_util.lib pjlib.lib
+STATICLIBRARY		libsrtp.lib
+STATICLIBRARY		libgsmcodec.lib libspeexcodec.lib
+STATICLIBRARY		libpassthroughcodec.lib
+STATICLIBRARY		pjmedia_audiodev.lib 
 
-#if SND_USE_NULL
-    STATICLIBRARY	null_audio.lib
-    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
-#elif SND_USE_APS
-    STATICLIBRARY	symbian_audio_aps.lib
-    LIBRARY		APSSession2.lib
-    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
-    MACRO		PJMEDIA_SYM_SND_USE_APS=1
-#else
-    STATICLIBRARY	symbian_audio.lib
-    LIBRARY 		mediaclientaudiostream.lib
-    LIBRARY 		mediaclientaudioinputstream.lib
-    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
+#if SND_HAS_APS
+	LIBRARY		APSSession2.lib
 #endif
 
+#if SND_HAS_VAS
+//	LIBRARY		
+#endif
+
+#if SND_HAS_MDA
+	LIBRARY 	mediaclientaudiostream.lib
+	LIBRARY 	mediaclientaudioinputstream.lib
+#endif
+
+#if SND_HAS_APS || SND_HAS_VAS
+	CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
+#else
+	CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
+#endif
+	
 #ifdef WINSCW
 	STATICLIBRARY   eexe.lib ecrt0.lib
 #endif
 
-LIBRARY		esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib 
+LIBRARY			esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib 
 
 // The default 8KB seems to be insufficient with all bells and
 // whistles turned on
-EPOCSTACKSIZE	12288
+EPOCSTACKSIZE		12288
 
Index: pjproject/trunk/build.symbian/symbian_ua.pkg
===================================================================
--- pjproject/trunk/build.symbian/symbian_ua.pkg	(revision 2506)
+++ pjproject/trunk/build.symbian/symbian_ua.pkg	(revision 2506)
@@ -0,0 +1,18 @@
+; symbian_ua.pkg
+
+; Languages
+&EN
+
+; Header
+#{"symbian_ua"},(0x200235D3), 0, 1, 1
+
+; Platform compatibility
+[0x101F7961], *, *, *,{"Series60ProductID"}
+
+; vendor
+%{"PJSIP"}
+:"PJSIP"
+
+; Target
+"$(EPOCROOT)Epoc32\release\$(PLATFORM)\$(TARGET)\symbian_ua.exe"-"!:\sys\bin\symbian_ua.exe"
+"$(EPOCROOT)Epoc32\data\z\private\10003a3f\apps\symbian_ua_reg.rSC"-"!:\private\10003a3f\import\apps\symbian_ua_reg.rSC"
Index: pjproject/trunk/build.symbian/symbian_ua_udeb.pkg
===================================================================
--- pjproject/trunk/build.symbian/symbian_ua_udeb.pkg	(revision 2505)
+++ pjproject/trunk/build.symbian/symbian_ua_udeb.pkg	(revision 2506)
@@ -1,3 +1,6 @@
-; symbian_ua.pkg
+; symbian_ua_udeb.pkg
+
+; for Carbide development, please use symbian_ua.pkg for comfortness
+; (epoc32 path, platform, and target are set automatically using env vars)
 
 ; Languages
@@ -5,5 +8,5 @@
 
 ; Header
-#{"symbian_ua"},(0xA000000D), 0, 1, 1
+#{"symbian_ua"},(0x200235D3), 0, 1, 1
 
 ; Platform compatibility
@@ -15,4 +18,4 @@
 
 ; Target
-"C:\Symbian\9.1\S60_3rd_MR\epoc32\release\gcce\UDEB\symbian_ua.exe"-"!:\sys\bin\symbian_ua.exe"
-"C:\Symbian\9.1\S60_3rd_MR\epoc32\data\z\private\10003a3f\apps\symbian_ua_reg.rSC"-"!:\private\10003a3f\import\apps\symbian_ua_reg.rSC"
+"C:\Symbian\9.2\S60_3rd_FP1\epoc32\release\gcce\UDEB\symbian_ua.exe"-"!:\sys\bin\symbian_ua.exe"
+"C:\Symbian\9.2\S60_3rd_FP1\epoc32\data\z\private\10003a3f\apps\symbian_ua_reg.rSC"-"!:\private\10003a3f\import\apps\symbian_ua_reg.rSC"
Index: pjproject/trunk/build.symbian/symbian_ua_urel.pkg
===================================================================
--- pjproject/trunk/build.symbian/symbian_ua_urel.pkg	(revision 2505)
+++ pjproject/trunk/build.symbian/symbian_ua_urel.pkg	(revision 2506)
@@ -1,3 +1,6 @@
 ; symbian_ua.pkg
+
+; for Carbide development, please use symbian_ua.pkg for comfortness
+; (epoc32 path, platform, and target are set automatically using env vars)
 
 ; Languages
@@ -5,5 +8,5 @@
 
 ; Header
-#{"symbian_ua"},(0xA000000D), 0, 1, 1
+#{"symbian_ua"},(0x200235D3), 0, 1, 1
 
 ; Platform compatibility
Index: pjproject/trunk/build.symbian/symsndtest.mmp
===================================================================
--- pjproject/trunk/build.symbian/symsndtest.mmp	(revision 2505)
+++ pjproject/trunk/build.symbian/symsndtest.mmp	(revision 2506)
@@ -1,52 +1,44 @@
-#define SND_USE_NULL	0
-#define SND_USE_APS	0
+#define SND_USE_APS	1
+#define SND_USE_VAS	0
 
-TARGET 		symsndtest.exe
-TARGETTYPE 	exe
-UID		0x0 0xA000000E
+TARGET 			symsndtest.exe
+TARGETTYPE 		exe
+UID			0x0 0xA000000E
 
-SOURCEPATH	..\pjsip-apps\src\symsndtest
+SOURCEPATH		..\pjsip-apps\src\symsndtest
 
-MACRO		PJ_M_I386=1
-MACRO		PJ_SYMBIAN=1
+MACRO			PJ_M_I386=1
+MACRO			PJ_SYMBIAN=1
 
 // Test files
 
-SOURCE	app_main.cpp
-SOURCE	main_symbian.cpp
+SOURCE			app_main.cpp
+SOURCE			main_symbian.cpp
 
-START RESOURCE  symsndtest_reg.rss
+START RESOURCE  	symsndtest_reg.rss
 	TARGETPATH	\private\10003a3f\apps
 END
 
-SYSTEMINCLUDE	..\pjlib\include
-SYSTEMINCLUDE	..\pjmedia\include
+SYSTEMINCLUDE		..\pjlib\include
+SYSTEMINCLUDE		..\pjmedia\include
 
-SYSTEMINCLUDE	\epoc32\include
-SYSTEMINCLUDE	\epoc32\include\libc
+SYSTEMINCLUDE		\epoc32\include
+SYSTEMINCLUDE		\epoc32\include\libc
 
-LIBRARY		charconv.lib euser.lib estlib.lib
-LIBRARY		esock.lib insock.lib
-STATICLIBRARY	pjlib.lib pjmedia.lib
+LIBRARY			charconv.lib euser.lib estlib.lib
+LIBRARY			esock.lib insock.lib
+STATICLIBRARY		pjlib.lib pjmedia.lib
+STATICLIBRARY		symbian_audio.lib
 
-#if SND_USE_NULL
-    STATICLIBRARY	null_audio.lib
-    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
-#elif SND_USE_APS
-    SOURCEPATH                              ..\pjmedia\src\pjmedia 
-    SOURCE                                  symbian_sound_aps.cpp 
-
-    SYSTEMINCLUDE                           \epoc32\include\mmf\server 
-    SYSTEMINCLUDE                           \epoc32\include\mmf\common 
-    SYSTEMINCLUDE                           \epoc32\include\mda\common 
-
-    //STATICLIBRARY	symbian_audio_aps.lib
-    LIBRARY		APSSession2.lib
-    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
+#if SND_USE_APS
+	LIBRARY		APSSession2.lib
+	CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
+#elif SND_USE_VAS
+//	LIBRARY		
+	CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
 #else
-    STATICLIBRARY	symbian_audio.lib
-    LIBRARY 		mediaclientaudiostream.lib
-    LIBRARY 		mediaclientaudioinputstream.lib
-    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
+	LIBRARY 	mediaclientaudiostream.lib
+	LIBRARY 	mediaclientaudioinputstream.lib
+	CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment
 #endif
 
Index: pjproject/trunk/pjlib/include/pj/compat/os_symbian.h
===================================================================
--- pjproject/trunk/pjlib/include/pj/compat/os_symbian.h	(revision 2505)
+++ pjproject/trunk/pjlib/include/pj/compat/os_symbian.h	(revision 2506)
@@ -115,5 +115,5 @@
  * Override features.
  */
-#define PJ_HAS_FLOATING_POINT	    1
+#define PJ_HAS_FLOATING_POINT	    0
 #define PJ_HAS_MALLOC               0
 #define PJ_HAS_SEMAPHORE	    1
Index: pjproject/trunk/pjlib/include/pj/config_site_sample.h
===================================================================
--- pjproject/trunk/pjlib/include/pj/config_site_sample.h	(revision 2505)
+++ pjproject/trunk/pjlib/include/pj/config_site_sample.h	(revision 2506)
@@ -1,11 +1,21 @@
-
-
-//#define PJ_CONFIG_MINIMAL_SIZE
-//#define PJ_CONFIG_MAXIMUM_SPEED
-
-
-/*
- * This file (config_site_sample.h) contains various configuration
- * settings that I use for certain settings. 
+/*
+ * This file contains several sample settings especially for Windows
+ * Mobile and Symbian targets. You can include this file in your
+ * <pj/config_site.h> file.
+ *
+ * The Windows Mobile and Symbian settings will be activated
+ * automatically if you include this file.
+ *
+ * In addition, you may specify one of these macros (before including
+ * this file) to activate additional settings:
+ *
+ * #define PJ_CONFIG_NOKIA_APS_DIRECT
+ *   Use this macro to activate the APS-Direct feature. Please see
+ *   http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct for more 
+ *   info.
+ *
+ * #define PJ_CONFIG_WIN32_WMME_DIRECT
+ *   Configuration to activate "APS-Direct" media mode on Windows or
+ *   Windows Mobile, useful for testing purposes only.
  */
 
@@ -15,24 +25,61 @@
  */
 #if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
-#   define PJ_HAS_FLOATING_POINT	0
-
-#   define PJMEDIA_HAS_G711_PLC		0
-//#   define PJMEDIA_HAS_SMALL_FILTER	1
-//#   define PJMEDIA_HAS_LARGE_FILTER	0
-#   define PJMEDIA_HAS_L16_CODEC	0
-/*#   define PJMEDIA_HAS_GSM_CODEC	0*/
-/*#   define PJMEDIA_HAS_ILBC_CODEC	0*/
-/*#   define PJMEDIA_HAS_SPEEX_CODEC	0*/
-#   define PJMEDIA_HAS_SPEEX_AEC	0
-#   undef PJMEDIA_RESAMPLE_IMP
-#   define PJMEDIA_RESAMPLE_IMP		PJMEDIA_RESAMPLE_LIBRESAMPLE
-#   define PJMEDIA_WSOLA_IMP		PJMEDIA_WSOLA_IMP_WSOLA_LITE
-
-    /* Speex default quality settings */
-#   define PJSUA_DEFAULT_CODEC_QUALITY	5
-#   define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY	5
-
-#   define PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER	0
-#endif
+
+    /*
+     * PJLIB settings.
+     */
+
+    /* Disable floating point support */
+    #define PJ_HAS_FLOATING_POINT		0
+
+    /*
+     * PJMEDIA settings
+     */
+
+    /* Select codecs to disable */
+    #define PJMEDIA_HAS_L16_CODEC		0
+    #define PJMEDIA_HAS_ILBC_CODEC		0
+
+    /* We probably need more buffers on WM, so increase the limit */
+    #define PJMEDIA_SOUND_BUFFER_COUNT		32
+
+    /* Fine tune Speex's default settings for best performance/quality */
+    #define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY	5
+
+    /* For CPU reason, disable speex AEC and use the echo suppressor. */
+    #define PJMEDIA_HAS_SPEEX_AEC		0
+
+    /* Shouldn't use resampling for performance reason too. */
+    #define PJMEDIA_RESAMPLE_IMP		PJMEDIA_RESAMPLE_NONE
+
+    /* Use the lighter WSOLA implementation */
+    #define PJMEDIA_WSOLA_IMP			PJMEDIA_WSOLA_IMP_WSOLA_LITE
+
+    /*
+     * PJSIP settings.
+     */
+
+    /* Set maximum number of dialog/transaction/calls to minimum to reduce
+     * memory usage 
+     */
+    #define PJSIP_MAX_TSX_COUNT 		31
+    #define PJSIP_MAX_DIALOG_COUNT 		31
+    #define PJSUA_MAX_CALLS			4
+
+    /*
+     * PJSUA settings
+     */
+
+    /* Default codec (Speex) quality */
+    #define PJSUA_DEFAULT_CODEC_QUALITY		5
+
+    /* Set maximum number of objects to minimum to reduce memory usage */
+    #define PJSUA_MAX_ACC			4
+    #define PJSUA_MAX_PLAYERS			4
+    #define PJSUA_MAX_RECORDERS			4
+    #define PJSUA_MAX_CONF_PORTS		(PJSUA_MAX_CALLS+2*PJSUA_MAX_PLAYERS)
+    #define PJSUA_MAX_BUDDIES			32
+
+#endif	/* PJ_WIN32_WINCE */
 
 
@@ -42,35 +89,13 @@
 #if defined(PJ_SYMBIAN) && PJ_SYMBIAN!=0
 
-    /* We don't want to use float, for now */
-#   undef PJ_HAS_FLOATING_POINT
-#   define PJ_HAS_FLOATING_POINT	0
-
-#   define PJMEDIA_SOUND_IMPLEMENTATION PJMEDIA_SOUND_NULL_SOUND
+    /*
+     * PJLIB settings.
+     */
+
+    /* Disable floating point support */
+    #define PJ_HAS_FLOATING_POINT		0
 
     /* Misc PJLIB setting */
-#   define PJ_MAXPATH			80
-
-	/* SRTP has not been ported to Symbian yet */
-#   define PJMEDIA_HAS_SRTP			1
-
-    /* Disable these */
-#   define PJMEDIA_RESAMPLE_IMP		PJMEDIA_RESAMPLE_NONE
-#   define PJMEDIA_HAS_SPEEX_AEC	0
-#   define PJMEDIA_WSOLA_IMP		PJMEDIA_WSOLA_IMP_WSOLA_LITE
-
-    /* Disable all codecs but G.711 and GSM, for now */
-#   define PJMEDIA_HAS_GSM_CODEC	1
-#   define PJMEDIA_HAS_L16_CODEC	0
-#   define PJMEDIA_HAS_ILBC_CODEC	0
-#   define PJMEDIA_HAS_SPEEX_CODEC	1
-#   define PJMEDIA_HAS_G722_CODEC	0
-
-    /* Need larger sound buffers */
-#   define PJMEDIA_SOUND_BUFFER_COUNT	16
-
-    /* Disable safe module access */
-#   define PJSIP_SAFE_MODULE		0
-
-#   define PJSIP_MAX_PKT_LEN		2000
+    #define PJ_MAXPATH				80
 
     /* This is important for Symbian. Symbian lacks vsnprintf(), so
@@ -79,42 +104,141 @@
      * to log the packet.
      */
-#   define PJ_LOG_MAX_SIZE		(PJSIP_MAX_PKT_LEN+500)
-
-    /* Since we don't have threads, log buffer can use static buffer */
-#   define PJ_LOG_USE_STACK_BUFFER	0
-
-	/* Disable check stack since it increases footprint */
-#   undef PJ_OS_HAS_CHECK_STACK
-#   define PJ_OS_HAS_CHECK_STACK	0
+    #define PJ_LOG_MAX_SIZE			(PJSIP_MAX_PKT_LEN+500)
+
+    /* Since we don't have threads, log buffer can use static buffer
+     * rather than stack
+     */
+    #define PJ_LOG_USE_STACK_BUFFER		0
+
+    /* Disable check stack since it increases footprint */
+    #define PJ_OS_HAS_CHECK_STACK		0
+
+
+    /*
+     * PJMEDIA settings
+     */
+
+    /* Disable non-Symbian audio devices */
+    #define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO	0
+    #define PJMEDIA_AUDIO_DEV_HAS_WMME		0
+
+    /* Select codecs to disable */
+    #define PJMEDIA_HAS_L16_CODEC		0
+    #define PJMEDIA_HAS_ILBC_CODEC		0
+    #define PJMEDIA_HAS_G722_CODEC		0
+
+    /* Fine tune Speex's default settings for best performance/quality */
+    #define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY	5
+
+    /* For CPU reason, disable speex AEC and use the echo suppressor. */
+    #define PJMEDIA_HAS_SPEEX_AEC		0
+
+    /* Shouldn't use resampling for performance reason too. */
+    #define PJMEDIA_RESAMPLE_IMP		PJMEDIA_RESAMPLE_NONE
+
+    /* Use the lighter WSOLA implementation */
+    #define PJMEDIA_WSOLA_IMP			PJMEDIA_WSOLA_IMP_WSOLA_LITE
+
+    /* We probably need more buffers especially if MDA audio backend 
+     * is used, so increase the limit 
+     */
+    #define PJMEDIA_SOUND_BUFFER_COUNT		32
+
+    /*
+     * PJSIP settings.
+     */
+
+    /* Disable safe module access, since we don't use multithreading */
+    #define PJSIP_SAFE_MODULE			0
+
+    /* Increase allowable packet size, just in case */
+    #define PJSIP_MAX_PKT_LEN			2000
 
     /* Symbian has problem with too many large blocks */
-#   define PJSIP_POOL_LEN_ENDPT		1000
-#   define PJSIP_POOL_INC_ENDPT		1000
-#   define PJSIP_POOL_RDATA_LEN		2000
-#   define PJSIP_POOL_RDATA_INC		2000
-#   define PJSIP_POOL_LEN_TDATA		2000
-#   define PJSIP_POOL_INC_TDATA		512
-#   define PJSIP_POOL_LEN_UA		2000
-#   define PJSIP_POOL_INC_UA		1000
-#   define PJSIP_POOL_TSX_LAYER_LEN	256
-#   define PJSIP_POOL_TSX_LAYER_INC	256
-#   define PJSIP_POOL_TSX_LEN		512
-#   define PJSIP_POOL_TSX_INC		128
+    #define PJSIP_POOL_LEN_ENDPT		1000
+    #define PJSIP_POOL_INC_ENDPT		1000
+    #define PJSIP_POOL_RDATA_LEN		2000
+    #define PJSIP_POOL_RDATA_INC		2000
+    #define PJSIP_POOL_LEN_TDATA		2000
+    #define PJSIP_POOL_INC_TDATA		512
+    #define PJSIP_POOL_LEN_UA			2000
+    #define PJSIP_POOL_INC_UA			1000
+    #define PJSIP_POOL_TSX_LAYER_LEN		256
+    #define PJSIP_POOL_TSX_LAYER_INC		256
+    #define PJSIP_POOL_TSX_LEN			512
+    #define PJSIP_POOL_TSX_INC			128
+
+    /*
+     * PJSUA settings.
+     */
+
+    /* Default codec quality */
+    #define PJSUA_DEFAULT_CODEC_QUALITY		5
 
     /* Set maximum number of dialog/transaction/calls to minimum */
-#   define PJSIP_MAX_TSX_COUNT 		31
-#   define PJSIP_MAX_DIALOG_COUNT 	31
-#   define PJSUA_MAX_CALLS		4
+    #define PJSIP_MAX_TSX_COUNT 		31
+    #define PJSIP_MAX_DIALOG_COUNT 		31
+    #define PJSUA_MAX_CALLS			4
 
     /* Other pjsua settings */
-#   define PJSUA_MAX_ACC		4
-#   define PJSUA_MAX_PLAYERS		4
-#   define PJSUA_MAX_RECORDERS		4
-#   define PJSUA_MAX_CONF_PORTS		(PJSUA_MAX_CALLS+2*PJSUA_MAX_PLAYERS)
-#   define PJSUA_MAX_BUDDIES		32
-
-    /* Speex default quality settings */
-#   define PJSUA_DEFAULT_CODEC_QUALITY	5
-#   define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY	5
+    #define PJSUA_MAX_ACC			4
+    #define PJSUA_MAX_PLAYERS			4
+    #define PJSUA_MAX_RECORDERS			4
+    #define PJSUA_MAX_CONF_PORTS		(PJSUA_MAX_CALLS+2*PJSUA_MAX_PLAYERS)
+    #define PJSUA_MAX_BUDDIES			32
+#endif
+
+
+/*
+ * Additional configuration to activate APS-Direct feature for
+ * Nokia S60 target
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct
+ */
+#ifdef PJ_CONFIG_NOKIA_APS_DIRECT
+
+    /* MUST use switchboard rather than the conference bridge */
+    #define PJMEDIA_CONF_USE_SWITCH_BOARD	1
+
+    /* Enable APS sound device backend and disable MDA */
+    #define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA	0
+    #define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS	1
+
+    /* Enable passthrough codec framework */
+    #define PJMEDIA_HAS_PASSTHROUGH_CODECS	1
+
+    /* And selectively enable which codecs are supported by the handset */
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC	1
+
+#endif
+
+
+/*
+ * Configuration to activate "APS-Direct" media mode on Windows,
+ * useful for testing purposes only.
+ */
+#ifdef PJ_CONFIG_WIN32_WMME_DIRECT
+
+    /* MUST use switchboard rather than the conference bridge */
+    #define PJMEDIA_CONF_USE_SWITCH_BOARD	1
+
+    /* Only WMME supports the "direct" feature */
+    #define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO	0
+    #define PJMEDIA_AUDIO_DEV_HAS_WMME		1
+
+    /* Enable passthrough codec framework */
+    #define PJMEDIA_HAS_PASSTHROUGH_CODECS	1
+
+    /* Only PCMA and PCMU are supported by WMME-direct */
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR	0
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729	0
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC	0
+
 #endif
 
Index: pjproject/trunk/pjlib/include/pj/errno.h
===================================================================
--- pjproject/trunk/pjlib/include/pj/errno.h	(revision 2505)
+++ pjproject/trunk/pjlib/include/pj/errno.h	(revision 2506)
@@ -376,4 +376,5 @@
  *  - PJLIB_UTIL_ERRNO_START	(PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*3)
  *  - PJNATH_ERRNO_START	(PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*4)
+ *  - PJMEDIA_AUDIODEV_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*5)
  */
 
Index: pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- pjproject/trunk/pjmedia/build/Makefile	(revision 2505)
+++ pjproject/trunk/pjmedia/build/Makefile	(revision 2506)
@@ -19,6 +19,6 @@
 export PJMEDIA_CODEC_LIB:=../lib/libpjmedia-codec-$(TARGET_NAME)$(LIBEXT)
 export PJSDP_LIB:=../lib/libpjsdp-$(TARGET_NAME)$(LIBEXT)
+export PJMEDIA_AUDIODEV_LIB:=../lib/libpjmedia-audiodev-$(TARGET_NAME)$(LIBEXT)
 
-NULLSOUND_OBJS := nullsound.o
 
 ###############################################################################
@@ -35,4 +35,5 @@
 		   $(HOST_CXXFLAGS) $(CXXFLAGS)
 export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \
+		   $(subst /,$(HOST_PSEP),$(PJMEDIA_AUDIODEV_LIB)) \
 		   $(subst /,$(HOST_PSEP),$(PJMEDIA_CODEC_LIB)) \
 		   $(subst /,$(HOST_PSEP),$(PJLIB_LIB)) \
@@ -50,19 +51,28 @@
 export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
 			alaw_ulaw.o alaw_ulaw_table.o clock_thread.o codec.o \
-			conference.o delaybuf.o echo_common.o echo_port.o \
-			echo_suppress.o endpoint.o errno.o \
+			conference.o conf_switch.o delaybuf.o echo_common.o \
+			echo_port.o echo_suppress.o endpoint.o errno.o \
 			g711.o jbuf.o master_port.o mem_capture.o mem_player.o \
 			null_port.o plc_common.o port.o splitcomb.o \
 			resample_resample.o resample_libsamplerate.o \
 			resample_port.o rtcp.o rtcp_xr.o rtp.o \
-			sdp.o sdp_cmp.o sdp_neg.o \
-			session.o silencedet.o sound_port.o stereo_port.o \
+			sdp.o sdp_cmp.o sdp_neg.o session.o silencedet.o \
+			sound_legacy.o sound_port.o stereo_port.o \
 			stream.o tonegen.o transport_adapter_sample.o \
 			transport_ice.o transport_loop.o \
 			transport_srtp.o transport_udp.o \
 			wav_player.o wav_playlist.o wav_writer.o wave.o \
-			wsola.o $(SOUND_OBJS) $(NULLSOUND_OBJS)
+			wsola.o
 
 export PJMEDIA_CFLAGS += $(_CFLAGS)
+
+
+###############################################################################
+# Defines for building PJMEDIA-AUDIODEV library
+#
+export PJMEDIA_AUDIODEV_SRCDIR = ../src/pjmedia-audiodev
+export PJMEDIA_AUDIODEV_OBJS +=  audiodev.o audiotest.o errno.o legacy_dev.o pa_dev.o \
+				 wmme_dev.o
+export PJMEDIA_AUDIODEV_CFLAGS += $(_CFLAGS)
 
 
@@ -107,5 +117,5 @@
 # $(TARGET) is defined in os-$(OS_NAME).mak file in current directory.
 #
-TARGETS := pjmedia pjmedia-codec pjsdp pjmedia-test
+TARGETS := pjmedia pjmedia-audiodev pjmedia-codec pjsdp pjmedia-test
 
 all: $(TARGETS)
@@ -122,5 +132,5 @@
 distclean: realclean
 
-.PHONY: dep depend pjmedia pjmedia-codec pjmedia-test clean realclean distclean
+.PHONY: dep depend pjmedia pjmedia-codec pjmedia-audiodev pjmedia-test clean realclean distclean
 
 pjmedia:
@@ -129,4 +139,7 @@
 pjmedia-codec:
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $(PJMEDIA_CODEC_LIB)
+
+pjmedia-audiodev:
+	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $(PJMEDIA_AUDIODEV_LIB)
 
 pjsdp:
@@ -153,4 +166,5 @@
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
+	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $@
 	$(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
@@ -158,4 +172,5 @@
 realclean:
 	$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-$(TARGET_NAME).depend),$(HOST_RMR))
+	$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-audiodev-$(TARGET_NAME).depend),$(HOST_RMR))
 	$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-codec-$(TARGET_NAME).depend),$(HOST_RMR))
 	$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-test-$(TARGET_NAME).depend),$(HOST_RMR))
@@ -163,4 +178,5 @@
 	
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
+	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia-audiodev $@
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
@@ -169,4 +185,5 @@
 depend:
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
+	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia-audiodev $@
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
 	$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
Index: pjproject/trunk/pjmedia/build/os-auto.mak.in
===================================================================
--- pjproject/trunk/pjmedia/build/os-auto.mak.in	(revision 2505)
+++ pjproject/trunk/pjmedia/build/os-auto.mak.in	(revision 2506)
@@ -79,14 +79,14 @@
 #
 ifneq ($(findstring pa,$(AC_PJMEDIA_SND)),)
-export CFLAGS += -I$(THIRD_PARTY)/build/portaudio -I$(THIRD_PARTY)/portaudio/include -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_PORTAUDIO_SOUND
-export SOUND_OBJS = pasound.o
+export CFLAGS += -I$(THIRD_PARTY)/build/portaudio -I$(THIRD_PARTY)/portaudio/include -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=1
 endif
 
 #
-# Win32 DirectSound
+# Windows specific
 #
-ifeq ($(AC_PJMEDIA_SND),ds)
-export SOUND_OBJS = dsound.o
-export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_WIN32_DIRECT_SOUND
+ifneq ($(findstring win32,$(AC_PJMEDIA_SND)),)
+export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_WMME=1
+else
+export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_WMME=0
 endif
 
@@ -95,5 +95,6 @@
 #
 ifeq ($(AC_PJMEDIA_SND),null)
-export SOUND_OBJS = nullsound.o
+# ***** Error ******
+# This will not work either with the new Audiodev
 export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_NULL_SOUND
 endif
@@ -103,6 +104,5 @@
 #
 ifeq ($(AC_PJMEDIA_SND),external)
-export SOUND_OBJS = 
-export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_EXTERNAL
+export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=0 -DPJMEDIA_AUDIO_DEV_HAS_WMME=0
 endif
 
Index: pjproject/trunk/pjmedia/build/pjmedia.dsp
===================================================================
--- pjproject/trunk/pjmedia/build/pjmedia.dsp	(revision 2505)
+++ pjproject/trunk/pjmedia/build/pjmedia.dsp	(revision 2506)
@@ -110,4 +110,8 @@
 # Begin Source File
 
+SOURCE=..\src\pjmedia\conf_switch.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\pjmedia\conference.c
 # End Source File
@@ -118,8 +122,4 @@
 # Begin Source File
 
-SOURCE=..\src\pjmedia\dsound.c
-# End Source File
-# Begin Source File
-
 SOURCE=..\src\pjmedia\echo_common.c
 # End Source File
@@ -174,12 +174,4 @@
 # Begin Source File
 
-SOURCE=..\src\pjmedia\nullsound.c
-# End Source File
-# Begin Source File
-
-SOURCE=..\src\pjmedia\pasound.c
-# End Source File
-# Begin Source File
-
 SOURCE=..\src\pjmedia\plc_common.c
 # End Source File
@@ -238,4 +230,8 @@
 # Begin Source File
 
+SOURCE=..\src\pjmedia\sound_legacy.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\pjmedia\sound_port.c
 # End Source File
@@ -291,8 +287,4 @@
 
 SOURCE=..\src\pjmedia\wave.c
-# End Source File
-# Begin Source File
-
-SOURCE=..\src\pjmedia\wmme_sound.c
 # End Source File
 # Begin Source File
@@ -307,4 +299,8 @@
 
 SOURCE=..\include\pjmedia\alaw_ulaw.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\include\pjmedia\audio_dev.h
 # End Source File
 # Begin Source File
Index: pjproject/trunk/pjmedia/build/pjmedia.vcproj
===================================================================
--- pjproject/trunk/pjmedia/build/pjmedia.vcproj	(revision 2505)
+++ pjproject/trunk/pjmedia/build/pjmedia.vcproj	(revision 2506)
@@ -435,4 +435,8 @@
 			</File>
 			<File
+				RelativePath="..\src\pjmedia\conf_switch.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\pjmedia\conference.c"
 				>
@@ -461,26 +465,4 @@
 			</File>
 			<File
-				RelativePath="..\src\pjmedia\dsound.c"
-				>
-				<FileConfiguration
-					Name="Release|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Debug|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-			</File>
-			<File
 				RelativePath="..\src\pjmedia\echo_common.c"
 				>
@@ -747,48 +729,4 @@
 			</File>
 			<File
-				RelativePath="..\src\pjmedia\nullsound.c"
-				>
-				<FileConfiguration
-					Name="Release|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Debug|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-			</File>
-			<File
-				RelativePath="..\src\pjmedia\pasound.c"
-				>
-				<FileConfiguration
-					Name="Release|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Debug|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-			</File>
-			<File
 				RelativePath="..\src\pjmedia\plc_common.c"
 				>
@@ -1027,4 +965,8 @@
 			</File>
 			<File
+				RelativePath="..\src\pjmedia\sound_legacy.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\pjmedia\sound_port.c"
 				>
@@ -1243,8 +1185,4 @@
 					/>
 				</FileConfiguration>
-			</File>
-			<File
-				RelativePath="..\src\pjmedia\wmme_sound.c"
-				>
 			</File>
 			<File
Index: pjproject/trunk/pjmedia/build/pjmedia_audiodev.dsp
===================================================================
--- pjproject/trunk/pjmedia/build/pjmedia_audiodev.dsp	(revision 2506)
+++ pjproject/trunk/pjmedia/build/pjmedia_audiodev.dsp	(revision 2506)
@@ -0,0 +1,150 @@
+# Microsoft Developer Studio Project File - Name="pjmedia_audiodev" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Static Library" 0x0104
+
+CFG=pjmedia_audiodev - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE 
+!MESSAGE NMAKE /f "pjmedia_audiodev.mak".
+!MESSAGE 
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE 
+!MESSAGE NMAKE /f "pjmedia_audiodev.mak" CFG="pjmedia_audiodev - Win32 Debug"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "pjmedia_audiodev - Win32 Release" (based on "Win32 (x86) Static Library")
+!MESSAGE "pjmedia_audiodev - Win32 Debug" (based on "Win32 (x86) Static Library")
+!MESSAGE 
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "pjmedia_audiodev - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-release"
+# PROP Intermediate_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-release"
+# PROP Target_Dir ""
+F90=df.exe
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjnath/include" /I "../../third_party/portaudio/include" /I "../../third_party/speex/include" /I "../../third_party/build/srtp" /I "../../third_party/srtp/crypto/include" /I "../../third_party/srtp/include" /I "../.." /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /c
+# SUBTRACT CPP /YX
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo /out:"../lib/pjmedia-audiodev-i386-win32-vc6-release.lib"
+
+!ELSEIF  "$(CFG)" == "pjmedia_audiodev - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir ".\output\pjmedia_audiodev-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir ".\output\pjmedia_audiodev-i386-win32-vc6-debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-debug"
+# PROP Intermediate_Dir ".\output\pjmedia-audiodev-i386-win32-vc6-debug"
+# PROP Target_Dir ""
+F90=df.exe
+# 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 "../../pjnath/include" /I "../../third_party/portaudio/include" /I "../../third_party/speex/include" /I "../../third_party/build/srtp" /I "../../third_party/srtp/crypto/include" /I "../../third_party/srtp/include" /I "../.." /D "_DEBUG" /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"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo /out:"../lib/pjmedia-audiodev-i386-win32-vc6-debug.lib"
+
+!ENDIF 
+
+# Begin Target
+
+# Name "pjmedia_audiodev - Win32 Release"
+# Name "pjmedia_audiodev - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\audiodev.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\audiotest.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\errno.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\legacy_dev.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\pa_dev.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\symb_aps_dev.cpp"
+# PROP Exclude_From_Build 1
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\symb_mda_dev.cpp"
+# PROP Exclude_From_Build 1
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjmedia-audiodev\wmme_dev.c"
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\audiodev.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\audiodev_imp.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\audiotest.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\config.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\errno.h"
+# End Source File
+# End Group
+# End Target
+# End Project
Index: pjproject/trunk/pjmedia/build/pjmedia_audiodev.vcproj
===================================================================
--- pjproject/trunk/pjmedia/build/pjmedia_audiodev.vcproj	(revision 2506)
+++ pjproject/trunk/pjmedia/build/pjmedia_audiodev.vcproj	(revision 2506)
@@ -0,0 +1,386 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="pjmedia_audiodev"
+	ProjectGUID="{4281CA5E-1D48-45D4-A991-2718A454B4BA}"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory=".\output\pjmedia-audiodev-i386-win32-vc8-debug"
+			IntermediateDirectory=".\output\pjmedia-audiodev-i386-win32-vc8-debug"
+			ConfigurationType="4"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjnath/include,../../third_party/portaudio/include,../../third_party/speex/include,../../third_party/build/srtp,../../third_party/srtp/crypto/include,../../third_party/srtp/include,../.."
+				PreprocessorDefinitions="_DEBUG;WIN32;_LIB;PJ_WIN32=1;PJ_M_I386=1"
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="1"
+				PrecompiledHeaderFile=".\output\pjmedia-audiodev-i386-win32-vc8-debug/pjmedia_audiodev.pch"
+				AssemblerListingLocation=".\output\pjmedia-audiodev-i386-win32-vc8-debug/"
+				ObjectFile=".\output\pjmedia-audiodev-i386-win32-vc8-debug/"
+				ProgramDataBaseFileName=".\output\pjmedia-audiodev-i386-win32-vc8-debug/"
+				BrowseInformation="1"
+				WarningLevel="4"
+				SuppressStartupBanner="true"
+				DebugInformationFormat="4"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="_DEBUG"
+				Culture="1033"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLibrarianTool"
+				OutputFile="../lib/pjmedia-audiodev-i386-win32-vc8-debug.lib"
+				SuppressStartupBanner="true"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+				SuppressStartupBanner="true"
+				OutputFile=".\output\pjmedia-audiodev-i386-win32-vc8-debug/pjmedia_audiodev.bsc"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory=".\output\pjmedia-audiodev-i386-win32-vc8-release"
+			IntermediateDirectory=".\output\pjmedia-audiodev-i386-win32-vc8-release"
+			ConfigurationType="4"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				InlineFunctionExpansion="1"
+				AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjnath/include,../../third_party/portaudio/include,../../third_party/speex/include,../../third_party/build/srtp,../../third_party/srtp/crypto/include,../../third_party/srtp/include,../.."
+				PreprocessorDefinitions="NDEBUG;WIN32;_LIB;PJ_WIN32=1;PJ_M_I386=1"
+				StringPooling="true"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				PrecompiledHeaderFile=".\output\pjmedia-audiodev-i386-win32-vc8-release/pjmedia_audiodev.pch"
+				AssemblerListingLocation=".\output\pjmedia-audiodev-i386-win32-vc8-release/"
+				ObjectFile=".\output\pjmedia-audiodev-i386-win32-vc8-release/"
+				ProgramDataBaseFileName=".\output\pjmedia-audiodev-i386-win32-vc8-release/"
+				BrowseInformation="1"
+				WarningLevel="4"
+				SuppressStartupBanner="true"
+				DebugInformationFormat="3"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="NDEBUG"
+				Culture="1033"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLibrarianTool"
+				OutputFile="../lib/pjmedia-audiodev-i386-win32-vc8-release.lib"
+				SuppressStartupBanner="true"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+				SuppressStartupBanner="true"
+				OutputFile=".\output\pjmedia-audiodev-i386-win32-vc8-release/pjmedia_audiodev.bsc"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Source Files"
+			Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+			>
+			<File
+				RelativePath="..\src\pjmedia-audiodev\audiodev.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjmedia-audiodev\audiotest.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjmedia-audiodev\errno.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjmedia-audiodev\legacy_dev.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjmedia-audiodev\pa_dev.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjmedia-audiodev\symb_aps_dev.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					ExcludedFromBuild="true"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					ExcludedFromBuild="true"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjmedia-audiodev\symb_mda_dev.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					ExcludedFromBuild="true"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					ExcludedFromBuild="true"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjmedia-audiodev\wmme_dev.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+		</Filter>
+		<Filter
+			Name="Header Files"
+			Filter="h;hpp;hxx;hm;inl"
+			>
+			<File
+				RelativePath="..\include\pjmedia-audiodev\audiodev.h"
+				>
+			</File>
+			<File
+				RelativePath="..\include\pjmedia-audiodev\audiodev_imp.h"
+				>
+			</File>
+			<File
+				RelativePath="..\include\pjmedia-audiodev\audiotest.h"
+				>
+			</File>
+			<File
+				RelativePath="..\include\pjmedia-audiodev\config.h"
+				>
+			</File>
+			<File
+				RelativePath="..\include\pjmedia-audiodev\errno.h"
+				>
+			</File>
+		</Filter>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>
Index: pjproject/trunk/pjmedia/build/pjmedia_codec.dsp
===================================================================
--- pjproject/trunk/pjmedia/build/pjmedia_codec.dsp	(revision 2505)
+++ pjproject/trunk/pjmedia/build/pjmedia_codec.dsp	(revision 2506)
@@ -148,4 +148,8 @@
 # Begin Source File
 
+SOURCE="..\src\pjmedia-codec\passthrough.c"
+# End Source File
+# Begin Source File
+
 SOURCE="..\src\pjmedia-codec\speex_codec.c"
 
@@ -190,4 +194,8 @@
 
 SOURCE="..\include\pjmedia-codec\l16.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-codec\passthrough.h"
 # End Source File
 # Begin Source File
Index: pjproject/trunk/pjmedia/build/pjmedia_codec.vcproj
===================================================================
--- pjproject/trunk/pjmedia/build/pjmedia_codec.vcproj	(revision 2505)
+++ pjproject/trunk/pjmedia/build/pjmedia_codec.vcproj	(revision 2506)
@@ -399,4 +399,8 @@
 			</File>
 			<File
+				RelativePath="..\src\pjmedia-codec\passthrough.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\pjmedia-codec\speex_codec.c"
 				>
Index: pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiodev.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiodev.h	(revision 2506)
+++ pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiodev.h	(revision 2506)
@@ -0,0 +1,667 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#ifndef __PJMEDIA_AUDIODEV_AUDIODEV_H__
+#define __PJMEDIA_AUDIODEV_AUDIODEV_H__
+
+/**
+ * @file audiodev.h
+ * @brief Audio device API.
+ */
+#include <pjmedia-audiodev/config.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/types.h>
+#include <pj/pool.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup s2_audio_device_reference Audio Device API Reference
+ * @ingroup audio_device_api
+ * @brief API Reference
+ * @{
+ */
+
+/** 
+ * Type for device index.
+ */
+typedef pj_int32_t pjmedia_aud_dev_index;
+
+/**
+ * Device index constants.
+ */
+enum
+{
+    /** 
+     * Constant to denote default capture device 
+     */
+    PJMEDIA_AUD_DEFAULT_CAPTURE_DEV = -1,
+
+    /** 
+     * Constant to denote default playback device 
+     */
+    PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV = -2,
+
+    /**
+     * Constant to denote invalid device index.
+     */
+    PJMEDIA_AUD_INVALID_DEV = -3
+};
+
+
+/**
+ * This enumeration identifies various audio device capabilities. These audio
+ * capabilities indicates what features are supported by the underlying
+ * audio device implementation.
+ *
+ * Applications get these capabilities in the #pjmedia_aud_dev_info structure.
+ *
+ * Application can also set the specific features/capabilities when opening
+ * the audio stream by setting the \a flags member of #pjmedia_aud_param
+ * structure.
+ *
+ * Once audio stream is running, application can also retrieve or set some
+ * specific audio capability, by using #pjmedia_aud_stream_get_cap() and
+ * #pjmedia_aud_stream_set_cap() and specifying the desired capability. The
+ * value of the capability is specified as pointer, and application needs to
+ * supply the pointer with the correct value, according to the documentation
+ * of each of the capability.
+ */
+typedef enum pjmedia_aud_dev_cap
+{
+    /** 
+     * Support for audio formats other than PCM. The value of this capability
+     * is represented by #pjmedia_format structure.
+     */
+    PJMEDIA_AUD_DEV_CAP_EXT_FORMAT = 1,
+
+    /** 
+     * Support for audio input latency control or query. The value of this 
+     * capability is an unsigned integer containing milliseconds value of
+     * the latency.
+     */
+    PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY = 2,
+
+    /** 
+     * Support for audio output latency control or query. The value of this 
+     * capability is an unsigned integer containing milliseconds value of
+     * the latency.
+     */
+    PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY = 4,
+
+    /** 
+     * Support for setting/retrieving the audio input device volume level.
+     * The value of this capability is an unsigned integer representing 
+     * the input audio volume setting in percent.
+     */
+    PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING = 8,
+
+    /** 
+     * Support for setting/retrieving the audio output device volume level.
+     * The value of this capability is an unsigned integer representing 
+     * the output audio volume setting in percent.
+     */
+    PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING = 16,
+
+    /** 
+     * Support for monitoring the current audio input signal volume. 
+     * The value of this capability is an unsigned integer representing 
+     * the audio volume in percent.
+     */
+    PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER = 32,
+
+    /** 
+     * Support for monitoring the current audio output signal volume. 
+     * The value of this capability is an unsigned integer representing 
+     * the audio volume in percent.
+     */
+    PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER = 64,
+
+    /** 
+     * Support for audio input routing. The value of this capability is an 
+     * integer containing #pjmedia_aud_dev_route enumeration.
+     */
+    PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE = 128,
+
+    /** 
+     * Support for audio output routing (e.g. loudspeaker vs earpiece). The
+     * value of this capability is an integer containing #pjmedia_aud_dev_route
+     * enumeration.
+     */
+    PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE = 256,
+
+    /** 
+     * The audio device has echo cancellation feature. The value of this
+     * capability is a pj_bool_t containing boolean PJ_TRUE or PJ_FALSE.
+     */
+    PJMEDIA_AUD_DEV_CAP_EC = 512,
+
+    /** 
+     * The audio device supports setting echo cancellation fail length. The
+     * value of this capability is an unsigned integer representing the
+     * echo tail in milliseconds.
+     */
+    PJMEDIA_AUD_DEV_CAP_EC_TAIL = 1024,
+
+    /** 
+     * The audio device has voice activity detection feature. The value
+     * of this capability is a pj_bool_t containing boolean PJ_TRUE or 
+     * PJ_FALSE.
+     */
+    PJMEDIA_AUD_DEV_CAP_VAD = 2048,
+
+    /** 
+     * The audio device has comfort noise generation feature. The value
+     * of this capability is a pj_bool_t containing boolean PJ_TRUE or 
+     * PJ_FALSE.
+     */
+    PJMEDIA_AUD_DEV_CAP_CNG = 4096,
+
+    /** 
+     * The audio device has packet loss concealment feature. The value
+     * of this capability is a pj_bool_t containing boolean PJ_TRUE or 
+     * PJ_FALSE.
+     */
+    PJMEDIA_AUD_DEV_CAP_PLC = 8192,
+    
+    /**
+     * End of capability
+     */
+    PJMEDIA_AUD_DEV_CAP_MAX = 16384
+
+} pjmedia_aud_dev_cap;
+
+
+/**
+ * This enumeration describes audio routing setting.
+ */
+typedef enum pjmedia_aud_dev_route
+{
+    /** Default route. */
+    PJMEDIA_AUD_DEV_ROUTE_DEFAULT = 0,
+
+    /** Route to loudspeaker */
+    PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER = 1,
+
+    /** Route to earpiece */
+    PJMEDIA_AUD_DEV_ROUTE_EARPIECE = 2
+
+} pjmedia_aud_dev_route;
+
+
+/**
+ * Device information structure returned by #pjmedia_aud_dev_get_info().
+ */
+typedef struct pjmedia_aud_dev_info
+{
+    /** 
+     * The device name 
+     */
+    char name[64];
+
+    /** 
+     * Maximum number of input channels supported by this device. If the
+     * value is zero, the device does not support input operation (i.e.
+     * it is a playback only device). 
+     */
+    unsigned input_count;
+
+    /** 
+     * Maximum number of output channels supported by this device. If the
+     * value is zero, the device does not support output operation (i.e. 
+     * it is an input only device).
+     */
+    unsigned output_count;
+
+    /** 
+     * Default sampling rate.
+     */
+    unsigned default_samples_per_sec;
+
+    /** 
+     * The underlying driver name 
+     */
+    char driver[32];
+
+    /** 
+     * Device capabilities, as bitmask combination of #pjmedia_aud_dev_cap.
+     */
+    unsigned caps;
+
+    /** 
+     * Supported audio device routes, as bitmask combination of 
+     * #pjmedia_aud_dev_route. The value may be zero if the device
+     * does not support audio routing.
+     */
+    unsigned routes;
+
+    /** 
+     * Number of audio formats supported by this device. The value may be
+     * zero if the device does not support non-PCM format.
+     */
+    unsigned ext_fmt_cnt;
+
+    /** 
+     * Array of supported extended audio formats 
+     */
+    pjmedia_format ext_fmt[8];
+
+
+} pjmedia_aud_dev_info;
+
+
+/** 
+ * This callback is called by player stream when it needs additional data
+ * to be played by the device. Application must fill in the whole of output 
+ * buffer with audio samples.
+ *
+ * The frame argument contains the following values:
+ *  - timestamp	    Playback timestamp, in samples.
+ *  - buf	    Buffer to be filled out by application.
+ *  - size	    The size requested in bytes, which will be equal to
+ *		    the size of one whole packet.
+ *
+ * @param user_data User data associated with the stream.
+ * @param frame	    Audio frame, which buffer is to be filled in by
+ *		    the application.
+ *
+ * @return	    Returning non-PJ_SUCCESS will cause the audio stream
+ *		    to stop
+ */
+typedef pj_status_t (*pjmedia_aud_play_cb)(void *user_data,
+					   pjmedia_frame *frame);
+
+/**
+ * This callback is called by recorder stream when it has captured the whole
+ * packet worth of audio samples.
+ *
+ * @param user_data User data associated with the stream.
+ * @param frame	    Captured frame.
+ *
+ * @return	    Returning non-PJ_SUCCESS will cause the audio stream
+ *		    to stop
+ */
+typedef pj_status_t (*pjmedia_aud_rec_cb)(void *user_data,
+					  pjmedia_frame *frame);
+
+/**
+ * This structure specifies the parameters to open the audio stream.
+ */
+typedef struct pjmedia_aud_param
+{
+    /**
+     * The audio direction. This setting is mandatory.
+     */
+    pjmedia_dir dir;
+
+    /**
+     * The audio recorder device ID. This setting is mandatory if the audio
+     * direction includes input/capture direction.
+     */
+    pjmedia_aud_dev_index rec_id;
+
+    /**
+     * The audio playback device ID. This setting is mandatory if the audio
+     * direction includes output/playback direction.
+     */
+    pjmedia_aud_dev_index play_id;
+
+    /** 
+     * Clock rate/sampling rate. This setting is mandatory. 
+     */
+    unsigned clock_rate;
+
+    /** 
+     * Number of channels. This setting is mandatory. 
+     */
+    unsigned channel_count;
+
+    /** 
+     * Number of samples per frame. This setting is mandatory. 
+     */
+    unsigned samples_per_frame;
+
+    /** 
+     * Number of bits per sample. This setting is mandatory. 
+     */
+    unsigned bits_per_sample;
+
+    /** 
+     * This flags specifies which of the optional settings are valid in this
+     * structure. The flags is bitmask combination of pjmedia_aud_dev_cap.
+     */
+    unsigned flags;
+
+    /** 
+     * Set the audio format. This setting is optional, and will only be used
+     * if PJMEDIA_AUD_DEV_CAP_EXT_FORMAT is set in the flags.
+     */
+    pjmedia_format ext_fmt;
+
+    /**
+     * Input latency, in milliseconds. This setting is optional, and will 
+     * only be used if PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY is set in the flags.
+     */
+    unsigned input_latency_ms;
+
+    /**
+     * Input latency, in milliseconds. This setting is optional, and will 
+     * only be used if PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY is set in the flags.
+     */
+    unsigned output_latency_ms;
+
+    /**
+     * Input volume setting, in percent. This setting is optional, and will 
+     * only be used if PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING is set in 
+     * the flags.
+     */
+    unsigned input_vol;
+
+    /**
+     * Output volume setting, in percent. This setting is optional, and will 
+     * only be used if PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING is set in 
+     * the flags.
+     */
+    unsigned output_vol;
+
+    /** 
+     * Set the audio input route. This setting is optional, and will only be
+     * used if PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE is set in the flags.
+     */
+    pjmedia_aud_dev_route input_route;
+
+    /** 
+     * Set the audio output route. This setting is optional, and will only be
+     * used if PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE is set in the flags.
+     */
+    pjmedia_aud_dev_route output_route;
+
+    /**
+     * Enable/disable echo canceller, if the device supports it. This setting
+     * is optional, and will only be used if PJMEDIA_AUD_DEV_CAP_EC is set in
+     * the flags.
+     */
+    pj_bool_t ec_enabled;
+
+    /**
+     * Set echo canceller tail length in milliseconds, if the device supports
+     * it. This setting is optional, and will only be used if
+     * PJMEDIA_AUD_DEV_CAP_EC_TAIL is set in the flags.
+     */
+    unsigned ec_tail_ms;
+
+    /** 
+     * Enable/disable PLC. This setting is optional, and will only be used
+     * if PJMEDIA_AUD_DEV_CAP_PLC is set in the flags.
+     */
+    pj_bool_t plc_enabled;
+
+    /** 
+     * Enable/disable CNG. This setting is optional, and will only be used
+     * if PJMEDIA_AUD_DEV_CAP_CNG is set in the flags.
+     */
+    pj_bool_t cng_enabled;
+
+} pjmedia_aud_param;
+
+
+/** Forward declaration for pjmedia_aud_stream */
+typedef struct pjmedia_aud_stream pjmedia_aud_stream;
+
+/** Forward declaration for audio device factory */
+typedef struct pjmedia_aud_dev_factory pjmedia_aud_dev_factory;
+
+/**
+ * Get string info for the specified capability.
+ *
+ * @param cap		The capability ID.
+ * @param p_desc	Optional pointer which will be filled with longer 
+ *			description about the capability.
+ *
+ * @return		Capability name.
+ */
+PJ_DECL(const char*) pjmedia_aud_dev_cap_name(pjmedia_aud_dev_cap cap,
+					      const char **p_desc);
+
+
+/**
+ * Set a capability field value in #pjmedia_aud_param structure. This will
+ * also set the flags field for the specified capability in the structure.
+ *
+ * @param param		The structure.
+ * @param cap		The audio capability which value is to be set.
+ * @param value		Pointer to value. Please see the type of value to
+ *			be supplied in the pjmedia_aud_dev_cap documentation.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_param_set_cap(pjmedia_aud_param *param,
+					       pjmedia_aud_dev_cap cap,
+					       const void *pval);
+
+
+/**
+ * Get a capability field value from #pjmedia_aud_param structure. This
+ * function will return PJMEDIA_EAUD_INVCAP error if the flag for that
+ * capability is not set in the flags field in the structure.
+ *
+ * @param param		The structure.
+ * @param cap		The audio capability which value is to be retrieved.
+ * @param value		Pointer to value. Please see the type of value to
+ *			be supplied in the pjmedia_aud_dev_cap documentation.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_param_get_cap(const pjmedia_aud_param *param,
+					       pjmedia_aud_dev_cap cap,
+					       void *pval);
+
+/**
+ * Initialize the audio subsystem. This will register all supported audio 
+ * device factories to the audio subsystem. This function may be called
+ * more than once, but each call to this function must have the
+ * corresponding #pjmedia_aud_subsys_shutdown() call.
+ *
+ * @param pf		The pool factory.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf);
+
+
+/**
+ * Get the pool factory registered to the audio subsystem.
+ *
+ * @return		The pool factory.
+ */
+PJ_DECL(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void);
+
+
+/**
+ * Shutdown the audio subsystem. This will destroy all audio device factories
+ * registered in the audio subsystem. Note that currently opened audio streams
+ * may or may not be closed, depending on the implementation of the audio
+ * device factories.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_subsys_shutdown(void);
+
+
+/**
+ * Get the number of sound devices installed in the system.
+ *
+ * @return		The number of sound devices installed in the system.
+ */
+PJ_DECL(unsigned) pjmedia_aud_dev_count(void);
+
+
+/**
+ * Get device information.
+ *
+ * @param id		The audio device ID.
+ * @param info		The device information which will be filled in by this
+ *			function once it returns successfully.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_dev_get_info(pjmedia_aud_dev_index id,
+					      pjmedia_aud_dev_info *info);
+
+
+/**
+ * Lookup device index based on the driver and device name.
+ *
+ * @param drv_name	The driver name.
+ * @param dev_name	The device name.
+ * @param id		Pointer to store the returned device ID.
+ *
+ * @return		PJ_SUCCESS if the device can be found.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_dev_lookup(const char *drv_name,
+					    const char *dev_name,
+					    pjmedia_aud_dev_index *id);
+
+
+/**
+ * Initialize the audio device parameters with default values for the
+ * specified device.
+ *
+ * @param id		The audio device ID.
+ * @param param		The audio device parameters which will be initialized
+ *			by this function once it returns successfully.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_dev_default_param(pjmedia_aud_dev_index id,
+						   pjmedia_aud_param *param);
+
+
+/**
+ * Open audio stream object using the specified parameters.
+ *
+ * @param param		Sound device parameters to be used for the stream.
+ * @param rec_cb	Callback to be called on every input frame captured.
+ * @param play_cb	Callback to be called everytime the sound device needs
+ *			audio frames to be played back.
+ * @param user_data	Arbitrary user data, which will be given back in the
+ *			callbacks.
+ * @param p_strm	Pointer to receive the audio stream.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *param,
+					       pjmedia_aud_rec_cb rec_cb,
+					       pjmedia_aud_play_cb play_cb,
+					       void *user_data,
+					       pjmedia_aud_stream **p_strm);
+
+/**
+ * Get the running parameters for the specified audio stream.
+ *
+ * @param strm		The audio stream.
+ * @param param		Audio stream parameters to be filled in by this 
+ *			function once it returns successfully.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_get_param(pjmedia_aud_stream *strm,
+						  pjmedia_aud_param *param);
+
+/**
+ * Get the value of a specific capability of the audio stream.
+ *
+ * @param strm		The audio stream.
+ * @param cap		The audio capability which value is to be retrieved.
+ * @param value		Pointer to value to be filled in by this function 
+ *			once it returns successfully.  Please see the type 
+ *			of value to be supplied in the pjmedia_aud_dev_cap
+ *			documentation.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_get_cap(pjmedia_aud_stream *strm,
+						pjmedia_aud_dev_cap cap,
+						void *value);
+
+/**
+ * Set the value of a specific capability of the audio stream.
+ *
+ * @param strm		The audio stream.
+ * @param cap		The audio capability which value is to be set.
+ * @param value		Pointer to value. Please see the type of value to
+ *			be supplied in the pjmedia_aud_dev_cap documentation.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_set_cap(pjmedia_aud_stream *strm,
+						pjmedia_aud_dev_cap cap,
+						const void *value);
+
+/**
+ * Start the stream.
+ *
+ * @param strm		The audio stream.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_start(pjmedia_aud_stream *strm);
+
+/**
+ * Stop the stream.
+ *
+ * @param strm		The audio stream.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_stop(pjmedia_aud_stream *strm);
+
+/**
+ * Destroy the stream.
+ *
+ * @param strm		The audio stream.
+ *
+ * @return		PJ_SUCCESS on successful operation or the appropriate
+ *			error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm);
+
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif	/* __PJMEDIA_AUDIODEV_AUDIODEV_H__ */
+
Index: pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiodev_imp.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiodev_imp.h	(revision 2506)
+++ pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiodev_imp.h	(revision 2506)
@@ -0,0 +1,181 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#ifndef __AUDIODEV_IMP_H__
+#define __AUDIODEV_IMP_H__
+
+#include <pjmedia-audiodev/audiodev.h>
+
+/**
+ * @defgroup s8_audio_device_implementors_api Audio Device Implementors API
+ * @ingroup audio_device_api
+ * @brief API for audio device implementors
+ * @{
+ */
+
+/**
+ * Sound device factory operations.
+ */
+typedef struct pjmedia_aud_dev_factory_op
+{
+    /**
+     * Initialize the audio device factory.
+     *
+     * @param f		The audio device factory.
+     */
+    pj_status_t (*init)(pjmedia_aud_dev_factory *f);
+
+    /**
+     * Close this audio device factory and release all resources back to the
+     * operating system.
+     *
+     * @param f		The audio device factory.
+     */
+    pj_status_t (*destroy)(pjmedia_aud_dev_factory *f);
+
+    /**
+     * Get the number of audio devices installed in the system.
+     *
+     * @param f		The audio device factory.
+     */
+    unsigned (*get_dev_count)(pjmedia_aud_dev_factory *f);
+
+    /**
+     * Get the audio device information and capabilities.
+     *
+     * @param f		The audio device factory.
+     * @param index	Device index.
+     * @param info	The audio device information structure which will be
+     *			initialized by this function once it returns 
+     *			successfully.
+     */
+    pj_status_t	(*get_dev_info)(pjmedia_aud_dev_factory *f, 
+				unsigned index,
+				pjmedia_aud_dev_info *info);
+
+    /**
+     * Initialize the specified audio device parameter with the default
+     * values for the specified device.
+     *
+     * @param f		The audio device factory.
+     * @param index	Device index.
+     * @param param	The audio device parameter.
+     */
+    pj_status_t (*default_param)(pjmedia_aud_dev_factory *f,
+				 unsigned index,
+				 pjmedia_aud_param *param);
+
+    /**
+     * Open the audio device and create audio stream. See
+     * #pjmedia_aud_stream_create()
+     */
+    pj_status_t (*create_stream)(pjmedia_aud_dev_factory *f,
+				 const pjmedia_aud_param *param,
+				 pjmedia_aud_rec_cb rec_cb,
+				 pjmedia_aud_play_cb play_cb,
+				 void *user_data,
+				 pjmedia_aud_stream **p_aud_strm);
+
+} pjmedia_aud_dev_factory_op;
+
+
+/**
+ * This structure describes an audio device factory. 
+ */
+struct pjmedia_aud_dev_factory
+{
+    /** Internal data to be initialized by audio subsystem. */
+    struct {
+	/** Driver index */
+	unsigned drv_idx;
+    } sys;
+
+    /** Operations */
+    pjmedia_aud_dev_factory_op *op;
+};
+
+
+/**
+ * Sound stream operations.
+ */
+typedef struct pjmedia_aud_stream_op
+{
+    /**
+     * See #pjmedia_aud_stream_get_param()
+     */
+    pj_status_t (*get_param)(pjmedia_aud_stream *strm,
+			     pjmedia_aud_param *param);
+
+    /**
+     * See #pjmedia_aud_stream_get_cap()
+     */
+    pj_status_t (*get_cap)(pjmedia_aud_stream *strm,
+			   pjmedia_aud_dev_cap cap,
+			   void *value);
+
+    /**
+     * See #pjmedia_aud_stream_set_cap()
+     */
+    pj_status_t (*set_cap)(pjmedia_aud_stream *strm,
+			   pjmedia_aud_dev_cap cap,
+			   const void *value);
+
+    /**
+     * See #pjmedia_aud_stream_start()
+     */
+    pj_status_t (*start)(pjmedia_aud_stream *strm);
+
+    /**
+     * See #pjmedia_aud_stream_stop().
+     */
+    pj_status_t (*stop)(pjmedia_aud_stream *strm);
+
+    /**
+     * See #pjmedia_aud_stream_destroy().
+     */
+    pj_status_t (*destroy)(pjmedia_aud_stream *strm);
+
+} pjmedia_aud_stream_op;
+
+
+/**
+ * This structure describes the audio device stream.
+ */
+struct pjmedia_aud_stream
+{
+    /** Internal data to be initialized by audio subsystem */
+    struct {
+	/** Driver index */
+	unsigned drv_idx;
+    } sys;
+
+    /** Operations */
+    pjmedia_aud_stream_op *op;
+};
+
+
+
+
+/**
+ * @}
+ */
+
+
+
+#endif /* __AUDIODEV_IMP_H__ */
Index: pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiotest.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiotest.h	(revision 2506)
+++ pjproject/trunk/pjmedia/include/pjmedia-audiodev/audiotest.h	(revision 2506)
@@ -0,0 +1,116 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#ifndef __PJMEDIA_AUDIODEV_AUDIOTEST_H__
+#define __PJMEDIA_AUDIODEV_AUDIOTEST_H__
+
+/**
+ * @file audiotest.h
+ * @brief Audio test utility.
+ */
+#include <pjmedia-audiodev/audiodev.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup s30_audio_test_utility Audio tests utility.
+ * @ingroup audio_device_api
+ * @brief Audio test utility.
+ * @{
+ */
+
+/**
+ * Statistic for each direction.
+ */
+typedef struct pjmedia_aud_test_stat
+{
+    /**
+     * Number of frames processed during the test.
+     */
+    unsigned frame_cnt;
+
+    /** 
+     * Minimum inter-frame arrival time, in milliseconds 
+     */
+    unsigned min_interval;
+
+    /** 
+     * Maximum inter-frame arrival time, in milliseconds 
+     */
+    unsigned max_interval;
+
+    /** 
+     * Average inter-frame arrival time, in milliseconds 
+     */
+    unsigned avg_interval;
+
+    /** 
+     * Standard deviation of inter-frame arrival time, in milliseconds 
+     */
+    unsigned dev_interval;
+
+    /** 
+     * Maximum number of frame burst 
+     */
+    unsigned max_burst;
+
+} pjmedia_aud_test_stat;
+
+
+/**
+ * Test results.
+ */
+typedef struct pjmedia_aud_test_results
+{
+    /**
+     * Recording statistic.
+     */
+    pjmedia_aud_test_stat rec;
+
+    /**
+     * Playback statistic.
+     */
+    pjmedia_aud_test_stat play;
+
+    /** 
+     * Clock drifts per second, in samples. Positive number indicates rec
+     * device is running faster than playback device.
+     */
+    pj_int32_t rec_drift_per_sec;
+
+} pjmedia_aud_test_results;
+
+
+/**
+ * Perform audio device testing.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_test(const pjmedia_aud_param *param,
+				      pjmedia_aud_test_results *result);
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif	/* __PJMEDIA_AUDIODEV_AUDIOTEST_H__ */
+
+
Index: pjproject/trunk/pjmedia/include/pjmedia-audiodev/config.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-audiodev/config.h	(revision 2506)
+++ pjproject/trunk/pjmedia/include/pjmedia-audiodev/config.h	(revision 2506)
@@ -0,0 +1,368 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#ifndef __PJMEDIA_AUDIODEV_CONFIG_H__
+#define __PJMEDIA_AUDIODEV_CONFIG_H__
+
+/**
+ * @file audiodev.h
+ * @brief Audio device API.
+ */
+#include <pjmedia/types.h>
+#include <pj/pool.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup audio_device_api Audio Device API
+ * @brief PJMEDIA audio device abstraction API.
+ */
+
+/**
+ * @defgroup s1_audio_device_config Compile time configurations
+ * @ingroup audio_device_api
+ * @brief Compile time configurations
+ * @{
+ */
+
+/**
+ * This setting controls whether PortAudio support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+#   define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO	1
+#endif
+
+
+/**
+ * This setting controls whether WMME support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_WMME
+#   define PJMEDIA_AUDIO_DEV_HAS_WMME		1
+#endif
+
+
+/**
+ * This setting controls whether Symbian APS support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+#   define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS	0
+#endif
+
+
+/**
+ * This setting controls whether Symbian audio (using built-in multimedia 
+ * framework) support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+#   define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA	PJ_SYMBIAN
+#endif
+
+
+/**
+ * This setting controls whether the Audio Device API should support
+ * device implementation that is based on the old sound device API
+ * (sound.h). 
+ *
+ * Enable this API if:
+ *  - you have implemented your own sound device using the old sound
+ *    device API (sound.h), and
+ *  - you wish to be able to use your sound device implementation
+ *    using the new Audio Device API.
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * info.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE
+#   define PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE	0
+#endif
+
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif	/* __PJMEDIA_AUDIODEV_CONFIG_H__ */
+
+/*
+ --------------------- DOCUMENTATION FOLLOWS ---------------------------
+ */
+
+/**
+ * @addtogroup audio_device_api Audio Device API
+ * @{
+
+PJMEDIA Audio Device API is a cross-platform audio API appropriate for use with
+VoIP applications and many other types of audio streaming applications. 
+
+The API abstracts many different audio API's on various platforms, such as:
+ - PortAudio back-end for Win32, Windows Mobile, Linux, Unix, dan MacOS X.
+ - native WMME audio for Win32 and Windows Mobile devices
+ - native Symbian audio streaming/multimedia framework (MMF) implementation
+ - native Nokia Audio Proxy Server (APS) implementation
+ - null-audio implementation
+ - and more to be implemented in the future
+
+The Audio Device API/library is an evolution from PJMEDIA @ref PJMED_SND and 
+contains many enhancements:
+
+ - Forward compatibility:
+\n
+   The new API has been designed to be extensible, it will support new API's as 
+   well as new features that may be introduced in the future without breaking 
+   compatibility with applications that use this API as well as compatibility 
+   with existing device implementations. 
+
+ - Device capabilities:
+\n
+   At the heart of the API is device capabilities management, where all possible
+   audio capabilities of audio devices should be able to be handled in a generic
+   manner. With this framework, new capabilities that may be discovered in the 
+   future can be handled in manner without breaking existing applications. 
+
+ - Built-in features:
+\n
+   The device capabilities framework enables applications to use and control 
+   audio features built-in in the device, such as:
+    - echo cancellation, 
+    - built-in codecs, 
+    - audio routing (e.g. to earpiece or loudspeaker),
+    - volume control,
+    - etc.
+
+ - Codec support:
+\n
+   Some audio devices such as Nokia/Symbian Audio Proxy Server (APS) and Nokia 
+   VoIP Audio Services (VAS) support built-in hardware audio codecs (e.g. G.729,
+   iLBC, and AMR), and application can use the sound device in encoded mode to
+   make use of these hardware codecs. 
+
+ - Multiple backends:
+\n
+   The new API supports multiple audio backends (called factories or drivers in 
+   the code) to be active simultaneously, and audio backends may be added or 
+   removed during run-time. 
+
+
+@section using Overview on using the API
+
+@subsection getting_started Getting started
+
+ -# <b>Configure the application's project settings</b>.\n
+    Add the following 
+    include:
+    \code
+    #include <pjmedia_audiodev.h>\endcode\n
+    And add <b>pjmedia-audiodev</b> library to your application link 
+    specifications.\n
+ -# <b>Compile time settings</b>.\n
+    Use the compile time settings to enable or
+    disable specific audio drivers. For more information, please see
+    \ref s1_audio_device_config.
+ -# <b>API initialization and cleaning up</b>.\n
+    Before anything else, application must initialize the API by calling:
+    \code
+    pjmedia_aud_subsys_init(pf);\endcode\n
+    And add this in the application cleanup sequence
+    \code
+    pjmedia_aud_subsys_shutdown();\endcode
+
+@subsection devices Working with devices
+
+ -# The following code prints the list of audio devices detected
+    in the system.
+    \code
+    int dev_count;
+    pjmedia_aud_dev_index dev_idx;
+    pj_status_t status;
+
+    dev_count = pjmedia_aud_dev_count();
+    printf("Got %d audio devices\n", dev_count);
+
+    for (dev_idx=0; dev_idx<dev_count; ++i) {
+	pjmedia_aud_dev_info info;
+
+	status = pjmedia_aud_dev_get_info(dev_idx, &info);
+	printf("%d. %s (in=%d, out=%d)\n",
+	       dev_idx, info.name, 
+	       info.input_count, info.output_count);
+    }
+    \endcode\n
+ -# Info: The #PJMEDIA_AUD_DEFAULT_CAPTURE_DEV and #PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV
+    constants are used to denote default capture and playback devices
+    respectively.
+ -# Info: You may save the device and driver's name in your application
+    setting, for example to specify the prefered devices to be
+    used by your application. You can then retrieve the device index
+    for the device by calling:
+    \code
+	const char *drv_name = "WMME";
+	const char *dev_name = "Wave mapper";
+	pjmedia_aud_dev_index dev_idx;
+
+        status = pjmedia_aud_dev_lookup(drv_name, dev_name, &dev_idx);
+	if (status==PJ_SUCCESS)
+	    printf("Device index is %d\n", dev_idx);
+    \endcode
+
+@subsection caps Device capabilities
+
+Capabilities are encoded as #pjmedia_aud_dev_cap enumeration. Please see
+#pjmedia_aud_dev_cap enumeration for more information.
+
+ -# The following snippet prints the capabilities supported by the device:
+    \code
+    pjmedia_aud_dev_info info;
+    pj_status_t status;
+
+    status = pjmedia_aud_dev_get_info(PJMEDIA_AUD_DEFAULT_CAPTURE_DEV, &info);
+    if (status == PJ_SUCCESS) {
+	unsigned i;
+	// Enumerate capability bits
+	printf("Device capabilities: ");
+	for (i=0; i<32; ++i) {
+	    if (info.caps & (1 << i))
+		printf("%s ", pjmedia_aud_dev_cap_name(1 << i, NULL));
+	}
+    }
+    \endcode\n
+ -# Info: You can set the device settings when opening audio stream by setting
+    the flags and the appropriate setting in #pjmedia_aud_param when calling
+    #pjmedia_aud_stream_create()\n
+ -# Info: Once the audio stream is running, you can retrieve or change the stream 
+    setting by specifying the capability in #pjmedia_aud_stream_get_cap()
+    and #pjmedia_aud_stream_set_cap() respectively.
+
+
+@subsection creating_stream Creating audio streams
+
+The audio stream enables audio streaming to capture device, playback device,
+or both.
+
+ -# It is recommended to initialize the #pjmedia_aud_param with its default
+    values before using it:
+    \code
+    pjmedia_aud_param param;
+    pjmedia_aud_dev_index dev_idx;
+    pj_status_t status;
+
+    dev_idx = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+    status = pjmedia_aud_dev_default_param(dev_idx, &param);
+    \endcode\n
+ -# Configure the mandatory parameters:
+    \code
+    param.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    param.rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+    param.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+    param.clock_rate = 8000;
+    param.channel_count = 1;
+    param.samples_per_frame = 160;
+    param.bits_per_sample = 16;
+    \endcode\n
+ -# If you want the audio stream to use the device's built-in codec, specify
+    the codec in the #pjmedia_aud_param. You must make sure that the codec
+    is supported by the device, by looking at its supported format list in
+    the #pjmedia_aud_dev_info.\n
+    The snippet below sets the audio stream to use G.711 ULAW encoding:
+    \code
+    unsigned i;
+
+    // Make sure Ulaw is supported
+    if ((info.caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) == 0)
+	error("Device does not support extended formats");
+    for (i = 0; i < info.ext_fmt_cnt; ++i) {
+	if (info.ext_fmt[i].id == PJMEDIA_FORMAT_ULAW)
+	    break;
+    }
+    if (i == info.ext_fmt_cnt)
+	error("Device does not support Ulaw format");
+
+    // Set Ulaw format
+    param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+    param.ext_fmt.id = PJMEDIA_FORMAT_ULAW;
+    param.ext_fmt.bitrate = 64000;
+    param.ext_fmt.vad = PJ_FALSE;
+    \endcode\n
+ -# Note that if non-PCM format is configured on the audio stream, the
+    capture and/or playback functions (#pjmedia_aud_rec_cb and 
+    #pjmedia_aud_play_cb respectively) will report the audio frame as
+    #pjmedia_frame_ext structure instead of the #pjmedia_frame.
+ -# Optionally configure other device's capabilities. The following snippet
+    shows how to enable echo cancellation on the device (note that this
+    snippet may not be necessary since the setting may have been enabled 
+    when calling #pjmedia_aud_dev_default_param() above):
+    \code
+    if (info.caps & PJMEDIA_AUD_DEV_CAP_EC) {
+	param.flags |= PJMEDIA_AUD_DEV_CAP_EC;
+	param.ec_enabled = PJ_TRUE;
+    }
+    \endcode
+ -# Open the audio stream, specifying the capture and/or playback callback
+    functions:
+    \code
+       pjmedia_aud_stream *stream;
+
+       status = pjmedia_aud_stream_create(&param, &rec_cb, &play_cb, 
+                                          user_data, &stream);
+    \endcode
+
+@subsection working_with_stream Working with audio streams
+
+ -# To start the audio stream:
+    \code
+	status = pjmedia_aud_stream_start(stream);
+    \endcode\n
+    To stop the stream:
+    \code
+	status = pjmedia_aud_stream_stop(stream);
+    \endcode\n
+    And to destroy the stream:
+    \code
+	status = pjmedia_aud_stream_destroy(stream);
+    \endcode\n
+ -# Info: The following shows how to retrieve the capability value of the
+    stream (in this case, the current output volume setting).
+    \code
+    // Volume setting is an unsigned integer showing the level in percent.
+    unsigned vol;
+    status = pjmedia_aud_stream_get_cap(stream, 
+					PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+					&vol);
+    \endcode
+ -# Info: And following shows how to modify the capability value of the
+    stream (in this case, the current output volume setting).
+    \code
+    // Volume setting is an unsigned integer showing the level in percent.
+    unsigned vol = 50;
+    status = pjmedia_aud_stream_set_cap(stream, 
+					PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+					&vol);
+    \endcode
+
+
+*/
+
+
+/**
+ * @}
+ */
+
Index: pjproject/trunk/pjmedia/include/pjmedia-audiodev/errno.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-audiodev/errno.h	(revision 2506)
+++ pjproject/trunk/pjmedia/include/pjmedia-audiodev/errno.h	(revision 2506)
@@ -0,0 +1,198 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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_AUDIODEV_AUDIODEV_ERRNO_H__
+#define __PJMEDIA_AUDIODEV_AUDIODEV_ERRNO_H__
+
+/**
+ * @file errno.h Error Codes
+ * @brief Audiodev specific error codes.
+ */
+
+#include <pjmedia-audiodev/config.h>
+#include <pj/errno.h>
+
+/**
+ * @defgroup error_codes Error Codes
+ * @ingroup audio_device_api
+ * @brief Audio devive library specific error codes.
+ * @{
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Start of error code relative to PJ_ERRNO_START_USER.
+ * This value is 420000.
+ */
+#define PJMEDIA_AUDIODEV_ERRNO_START \
+	    (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*5)
+#define PJMEDIA_AUDIODEV_ERRNO_END   \
+	    (PJMEDIA_AUDIODEV_ERRNO_START + PJ_ERRNO_SPACE_SIZE - 1)
+
+
+/**
+ * Mapping from PortAudio error codes to pjmedia error space.
+ */
+#define PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START \
+	    (PJMEDIA_AUDIODEV_ERRNO_END-10000)
+#define PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_END   \
+	    (PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START + 10000 -1)
+/**
+ * Convert PortAudio error code to PJLIB error code.
+ * PortAudio error code range: 0 >= err >= -10000
+ */
+#define PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) \
+	    ((int)PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START-err)
+
+/**
+ * Mapping from Windows multimedia WaveIn error codes.
+ */
+#define PJMEDIA_AUDIODEV_WMME_IN_ERROR_START	\
+	    (PJMEDIA_AUDIODEV_ERRNO_START + 30000)
+#define PJMEDIA_AUDIODEV_WMME_IN_ERROR_END	\
+	    (PJMEDIA_AUDIODEV_WMME_IN_ERROR_START + 1000 - 1)
+/**
+ * Convert WaveIn operation error codes to PJLIB error space.
+ */
+#define PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(err) \
+	    ((int)PJMEDIA_AUDIODEV_WMME_IN_ERROR_START+err)
+
+
+/**
+ * Mapping from Windows multimedia WaveOut error codes.
+ */
+#define PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START	\
+	    (PJMEDIA_AUDIODEV_WMME_IN_ERROR_END + 1000)
+#define PJMEDIA_AUDIODEV_WMME_OUT_ERROR_END	\
+	    (PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START + 1000)
+/**
+ * Convert WaveOut operation error codes to PJLIB error space.
+ */
+#define PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(err) \
+	    ((int)PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START+err)
+
+
+/************************************************************
+ * Audio Device API error codes
+ ***********************************************************/
+/**
+ * @hideinitializer
+ * General/unknown error.
+ */
+#define PJMEDIA_EAUD_ERR	(PJMEDIA_AUDIODEV_ERRNO_START+1) /* 420001 */
+
+/**
+ * @hideinitializer
+ * Unknown error from audio driver
+ */
+#define PJMEDIA_EAUD_SYSERR	(PJMEDIA_AUDIODEV_ERRNO_START+2) /* 420002 */
+
+/**
+ * @hideinitializer
+ * Audio subsystem not initialized
+ */
+#define PJMEDIA_EAUD_INIT	(PJMEDIA_AUDIODEV_ERRNO_START+3) /* 420003 */
+
+/**
+ * @hideinitializer
+ * Invalid audio device
+ */
+#define PJMEDIA_EAUD_INVDEV	(PJMEDIA_AUDIODEV_ERRNO_START+4) /* 420004 */
+
+/**
+ * @hideinitializer
+ * Found no devices
+ */
+#define PJMEDIA_EAUD_NODEV	(PJMEDIA_AUDIODEV_ERRNO_START+5) /* 420005 */
+
+/**
+ * @hideinitializer
+ * Unable to find default device
+ */
+#define PJMEDIA_EAUD_NODEFDEV	(PJMEDIA_AUDIODEV_ERRNO_START+6) /* 420006 */
+
+/**
+ * @hideinitializer
+ * Device not ready
+ */
+#define PJMEDIA_EAUD_NOTREADY	(PJMEDIA_AUDIODEV_ERRNO_START+7) /* 420007 */
+
+/**
+ * @hideinitializer
+ * The audio capability is invalid or not supported
+ */
+#define PJMEDIA_EAUD_INVCAP	(PJMEDIA_AUDIODEV_ERRNO_START+8) /* 420008 */
+
+/**
+ * @hideinitializer
+ * The operation is invalid or not supported
+ */
+#define PJMEDIA_EAUD_INVOP	(PJMEDIA_AUDIODEV_ERRNO_START+9) /* 420009 */
+
+/**
+ * @hideinitializer
+ * Bad or invalid audio device format
+ */
+#define PJMEDIA_EAUD_BADFORMAT	(PJMEDIA_AUDIODEV_ERRNO_START+10) /* 4200010 */
+
+/**
+ * @hideinitializer
+ * Invalid audio device sample format
+ */
+#define PJMEDIA_EAUD_SAMPFORMAT	(PJMEDIA_AUDIODEV_ERRNO_START+11) /* 4200011 */
+
+/**
+ * @hideinitializer
+ * Bad latency setting
+ */
+#define PJMEDIA_EAUD_BADLATENCY	(PJMEDIA_AUDIODEV_ERRNO_START+12) /* 4200012 */
+
+
+
+
+
+/**
+ * Get error message for the specified error code. Note that this
+ * function is only able to decode PJMEDIA Audiodev specific error code.
+ * Application should use pj_strerror(), which should be able to
+ * decode all error codes belonging to all subsystems (e.g. pjlib,
+ * pjmedia, pjsip, etc).
+ *
+ * @param status    The error code.
+ * @param buffer    The buffer where to put the error message.
+ * @param bufsize   Size of the buffer.
+ *
+ * @return	    The error message as NULL terminated string,
+ *                  wrapped with pj_str_t.
+ */
+PJ_DECL(pj_str_t) pjmedia_audiodev_strerror(pj_status_t status, char *buffer,
+					    pj_size_t bufsize);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif	/* __PJMEDIA_AUDIODEV_AUDIODEV_ERRNO_H__ */
+
Index: pjproject/trunk/pjmedia/include/pjmedia-codec.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-codec.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia-codec.h	(revision 2506)
@@ -32,4 +32,5 @@
 #include <pjmedia-codec/g722.h>
 #include <pjmedia-codec/ipp_codecs.h>
+#include <pjmedia-codec/passthrough.h>
 
 
Index: pjproject/trunk/pjmedia/include/pjmedia-codec/amr_helper.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-codec/amr_helper.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia-codec/amr_helper.h	(revision 2506)
@@ -633,5 +633,4 @@
  * @return	    AMR mode.
  */
-
 PJ_INLINE(pj_int8_t) pjmedia_codec_amr_get_mode(unsigned bitrate)
 {
@@ -679,4 +678,33 @@
 
 /**
+ * Get AMR mode based on frame length.
+ *
+ * @param amrnb	    Set to PJ_TRUE for AMR-NB domain or PJ_FALSE for AMR-WB.
+ * @param frame_len The frame length.
+ *
+ * @return	    AMR mode.
+ */
+
+PJ_INLINE(pj_int8_t) pjmedia_codec_amr_get_mode2(pj_bool_t amrnb,
+						 unsigned frame_len)
+{
+    int i;
+
+    if (amrnb) {
+	for (i = 0; i < 9; ++i)
+	    if (frame_len == pjmedia_codec_amrnb_framelen[i])
+		return (pj_int8_t)i;
+    } else {
+	for (i = 0; i < 10; ++i) {
+	    if (frame_len == pjmedia_codec_amrwb_framelen[i])
+		return (pj_int8_t)i;
+	}
+    }
+    
+    pj_assert(!"Invalid AMR frame length");
+    return -1;
+}
+
+/**
  * Prepare a frame before pass it to decoder. This function will do:
  * - reorder AMR bitstream from descending sensitivity order into 
@@ -795,5 +823,4 @@
 	out_info->mode = FT_;
 	out->size = 5;
-	PJ_ASSERT_RETURN(out->size <= in->size, PJMEDIA_CODEC_EFRMINLEN);
 
 	pj_bzero(out->buf, out->size);
Index: pjproject/trunk/pjmedia/include/pjmedia-codec/config.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-codec/config.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia-codec/config.h	(revision 2506)
@@ -195,4 +195,34 @@
 #endif
 
+/**
+ * Enable Passthrough codecs.
+ *
+ * Default: 0
+ */
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODECS
+#   define PJMEDIA_HAS_PASSTHROUGH_CODECS	0
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+#   define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR	1
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+#   define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729	1
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+#   define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC	1
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU
+#   define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU	1
+#   undef PJMEDIA_HAS_G711_CODEC
+#endif
+
+#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA
+#   define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA	1
+#   undef PJMEDIA_HAS_G711_CODEC
+#endif
 
 #endif	/* __PJMEDIA_CODEC_CONFIG_H__ */
Index: pjproject/trunk/pjmedia/include/pjmedia-codec/passthrough.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-codec/passthrough.h	(revision 2506)
+++ pjproject/trunk/pjmedia/include/pjmedia-codec/passthrough.h	(revision 2506)
@@ -0,0 +1,78 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#ifndef __PJMEDIA_CODECS_PASSTHROUGH_H__
+#define __PJMEDIA_CODECS_PASSTHROUGH_H__
+
+/**
+ * @file pjmedia-codec/passthrough.h
+ * @brief Passthrough codecs.
+ */
+
+#include <pjmedia-codec/types.h>
+
+/**
+ * @defgroup PJMED_PASSTHROUGH_CODEC Passthrough Codecs
+ * @ingroup PJMEDIA_CODEC
+ * @brief Implementation of passthrough codecs
+ * @{
+ *
+ * This section describes functions to register and register passthrough 
+ * codecs factory to the codec manager. After the codec factory has been 
+ * registered, application can use @ref PJMEDIA_CODEC API to manipulate 
+ * the codec. This codec factory contains various codecs, e.g: G.729, iLBC,
+ * AMR, and G.711.
+ *
+ * Passthrough codecs are codecs wrapper that does not perform encoding 
+ * or decoding, it just pack and parse encoded audio data from/into RTP 
+ * payload. This will accomodate pjmedia ports which work with encoded
+ * audio data, e.g: encoded audio files, sound device with capability
+ * of playing/recording encoded audio data.
+ */
+
+PJ_BEGIN_DECL
+
+/**
+ * Initialize and register passthrough codecs factory to pjmedia endpoint.
+ *
+ * @param endpt	    The pjmedia endpoint.
+ *
+ * @return	    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_codec_passthrough_init( pjmedia_endpt *endpt );
+
+
+
+/**
+ * Unregister passthrough codecs factory from pjmedia endpoint.
+ *
+ * @return	    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_codec_passthrough_deinit(void);
+
+
+PJ_END_DECL
+
+
+/**
+ * @}
+ */
+
+#endif	/* __PJMEDIA_CODECS_PASSTHROUGH_H__ */
+
Index: pjproject/trunk/pjmedia/include/pjmedia/alaw_ulaw.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/alaw_ulaw.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/alaw_ulaw.h	(revision 2506)
@@ -145,10 +145,10 @@
  * @param dst	    Destination buffer for 8-bit U-Law data.
  * @param src	    Source, 16-bit linear PCM data.
- * @param len	    Number of samples.
+ * @param count	    Number of samples.
  */
 PJ_INLINE(void) pjmedia_ulaw_encode(pj_uint8_t *dst, const pj_int16_t *src, 
-				    pj_size_t len)
-{
-    const pj_int16_t *end = src + len;
+				    pj_size_t count)
+{
+    const pj_int16_t *end = src + count;
     
     while (src < end) {
@@ -162,10 +162,10 @@
  * @param dst	    Destination buffer for 8-bit A-Law data.
  * @param src	    Source, 16-bit linear PCM data.
- * @param len	    Number of samples.
+ * @param count	    Number of samples.
  */
 PJ_INLINE(void) pjmedia_alaw_encode(pj_uint8_t *dst, const pj_int16_t *src, 
-				    pj_size_t len)
-{
-    const pj_int16_t *end = src + len;
+				    pj_size_t count)
+{
+    const pj_int16_t *end = src + count;
     
     while (src < end) {
@@ -179,5 +179,5 @@
  * @param dst	    Destination buffer for 16-bit PCM data.
  * @param src	    Source, 8-bit U-Law data.
- * @param len	    Number of samples.
+ * @param len	    Encoded frame/source length in bytes.
  */
 PJ_INLINE(void) pjmedia_ulaw_decode(pj_int16_t *dst, const pj_uint8_t *src, 
@@ -196,5 +196,5 @@
  * @param dst	    Destination buffer for 16-bit PCM data.
  * @param src	    Source, 8-bit A-Law data.
- * @param len	    Number of samples.
+ * @param len	    Encoded frame/source length in bytes.
  */
 PJ_INLINE(void) pjmedia_alaw_decode(pj_int16_t *dst, const pj_uint8_t *src, 
Index: pjproject/trunk/pjmedia/include/pjmedia/codec.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/codec.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/codec.h	(revision 2506)
@@ -276,4 +276,7 @@
        pj_uint8_t  pcm_bits_per_sample;	/**< Bits/sample in the PCM side    */
        pj_uint8_t  pt;			/**< Payload type.		    */
+       pjmedia_format_id fmt_id;	/**< Source format, it's format of
+					     encoder input and decoder 
+					     output.			    */
     } info;
 
Index: pjproject/trunk/pjmedia/include/pjmedia/conference.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/conference.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/conference.h	(revision 2506)
@@ -44,4 +44,16 @@
 PJ_BEGIN_DECL
 
+/**
+ * The conference bridge signature in pjmedia_port_info.
+ */
+#define PJMEDIA_CONF_BRIDGE_SIGNATURE	\
+		    PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'F')
+
+/**
+ * The audio switchboard signature in pjmedia_port_info.
+ */
+#define PJMEDIA_CONF_SWITCH_SIGNATURE	\
+		    PJMEDIA_PORT_SIGNATURE('A', 'S', 'W', 'I')
+
 
 /**
@@ -57,8 +69,10 @@
     unsigned		slot;		    /**< Slot number.		    */
     pj_str_t		name;		    /**< Port name.		    */
+    pjmedia_format	format;		    /**< Format.		    */
     pjmedia_port_op	tx_setting;	    /**< Transmit settings.	    */
     pjmedia_port_op	rx_setting;	    /**< Receive settings.	    */
     unsigned		listener_cnt;	    /**< Number of listeners.	    */
     unsigned	       *listener_slots;	    /**< Array of listeners.	    */
+    unsigned		transmitter_cnt;    /**< Number of transmitter.	    */
     unsigned		clock_rate;	    /**< Clock rate of the port.    */
     unsigned		channel_count;	    /**< Number of channels.	    */
Index: pjproject/trunk/pjmedia/include/pjmedia/config.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/config.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/config.h	(revision 2506)
@@ -45,54 +45,57 @@
 #endif
 
+/**
+ * Specify whether we prefer to use audio switch board rather than 
+ * conference bridge.
+ *
+ * Audio switch board is a kind of simplified version of conference 
+ * bridge, but not really the subset of conference bridge. It has 
+ * stricter rules on audio routing among the pjmedia ports and has
+ * no audio mixing capability. The power of it is it could work with
+ * encoded audio frames where conference brigde couldn't.
+ *
+ * Default: 0
+ */
+#ifndef PJMEDIA_CONF_USE_SWITCH_BOARD
+#   define PJMEDIA_CONF_USE_SWITCH_BOARD    0
+#endif
+
 /*
  * Types of sound stream backends.
  */
 
-/** Constant for NULL sound backend. */
-#define PJMEDIA_SOUND_NULL_SOUND	    0
-
-/** Constant for PortAudio sound backend. */
-#define PJMEDIA_SOUND_PORTAUDIO_SOUND	    1
-
-/** Constant for Win32 DirectSound sound backend. */
-#define PJMEDIA_SOUND_WIN32_DIRECT_SOUND    2
-
-/** Constant for Win32 MME sound backend. */
-#define PJMEDIA_SOUND_WIN32_MME_SOUND	    3
-
-/** When this is set, pjmedia will not provide any sound device backend. 
- *  Application will have to provide its own sound device backend
- *  and link the application with it.
- */
-#define PJMEDIA_SOUND_EXTERNAL		    255
-
-
-/**
- * Unless specified otherwise, sound device uses PortAudio implementation
- * by default.
- */
-#ifndef PJMEDIA_SOUND_IMPLEMENTATION
-#  if defined(PJ_WIN32) && PJ_WIN32!=0
-/*#   define PJMEDIA_SOUND_IMPLEMENTATION   PJMEDIA_SOUND_WIN32_DIRECT_SOUND*/
-/*#   define PJMEDIA_SOUND_IMPLEMENTATION   PJMEDIA_SOUND_WIN32_MME_SOUND*/
-#   define PJMEDIA_SOUND_IMPLEMENTATION	    PJMEDIA_SOUND_PORTAUDIO_SOUND
-#  else
-#   define PJMEDIA_SOUND_IMPLEMENTATION	    PJMEDIA_SOUND_PORTAUDIO_SOUND
-#  endif
-#endif
-
-
-/**
- * Specify whether we prefer to use DirectSound on Windows.
- *
- * Default: 0
- */
-#ifndef PJMEDIA_PREFER_DIRECT_SOUND
-#   define PJMEDIA_PREFER_DIRECT_SOUND	    0
-#endif
-
-
-/**
- * Specify sound device latency default, in milisecond.
+/**
+ * This macro has been deprecated in releasee 1.1. Please see
+ * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information.
+ */
+#if defined(PJMEDIA_SOUND_IMPLEMENTATION)
+#   error PJMEDIA_SOUND_IMPLEMENTATION has been deprecated
+#endif
+
+/**
+ * This macro has been deprecated in releasee 1.1. Please see
+ * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information.
+ */
+#if defined(PJMEDIA_PREFER_DIRECT_SOUND)
+#   error PJMEDIA_PREFER_DIRECT_SOUND has been deprecated
+#endif
+
+/**
+ * This macro controls whether the legacy sound device API is to be
+ * implemented, for applications that still use the old sound device
+ * API (sound.h). If this macro is set to non-zero, the sound_legacy.c
+ * will be included in the compilation. The sound_legacy.c is an
+ * implementation of old sound device (sound.h) using the new Audio
+ * Device API.
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * info.
+ */
+#ifndef PJMEDIA_HAS_LEGACY_SOUND_API
+#   define PJMEDIA_HAS_LEGACY_SOUND_API	    1
+#endif
+
+/**
+ * Specify default sound device latency, in milisecond.
  */
 #ifndef PJMEDIA_SND_DEFAULT_REC_LATENCY
@@ -100,22 +103,9 @@
 #endif
 
+/**
+ * Specify default sound device latency, in milisecond.
+ */
 #ifndef PJMEDIA_SND_DEFAULT_PLAY_LATENCY
 #   define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 100
-#endif
-
-
-/**
- * Specify whether delay buffer is used for sound device.
- * When delay buffer is enabled, the sound device callback 
- * will be called one after another evenly.
- * The delay buffer also performs the best delay calculation
- * for the sound device, and will try to limit the delay caused
- * by uneven callback calls to this delay.
- *
- * When this setting is enabled, the PJMEDIA_SOUND_BUFFER_COUNT
- * macro will specify the maximum size of the delay buffer.
- */
-#ifndef PJMEDIA_SOUND_USE_DELAYBUF
-#   define PJMEDIA_SOUND_USE_DELAYBUF	    0
 #endif
 
@@ -286,5 +276,5 @@
  */
 #ifndef PJMEDIA_FILE_PORT_BUFSIZE
-#   define PJMEDIA_FILE_PORT_BUFSIZE    4000
+#   define PJMEDIA_FILE_PORT_BUFSIZE		4000
 #endif
 
Index: pjproject/trunk/pjmedia/include/pjmedia/endpoint.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/endpoint.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/endpoint.h	(revision 2506)
@@ -38,5 +38,4 @@
  */
 
-#include <pjmedia/sound.h>
 #include <pjmedia/codec.h>
 #include <pjmedia/sdp.h>
Index: pjproject/trunk/pjmedia/include/pjmedia/port.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/port.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/port.h	(revision 2506)
@@ -26,4 +26,5 @@
  */
 #include <pjmedia/types.h>
+#include <pj/assert.h>
 #include <pj/os.h>
 
@@ -212,4 +213,5 @@
     pj_bool_t	    need_info;		/**< Need info on connect?	    */
     unsigned	    pt;			/**< Payload type (can be dynamic). */
+    pjmedia_format  format;		/**< Format.			    */
     pj_str_t	    encoding_name;	/**< Encoding name.		    */
     unsigned	    clock_rate;		/**< Sampling rate.		    */
@@ -221,32 +223,4 @@
 
 
-/** 
- * Types of media frame. 
- */
-typedef enum pjmedia_frame_type
-{
-    PJMEDIA_FRAME_TYPE_NONE,	    /**< No frame.		*/
-    PJMEDIA_FRAME_TYPE_AUDIO	    /**< Normal audio frame.	*/
-
-} pjmedia_frame_type;
-
-
-/** 
- * This structure describes a media frame. 
- */
-typedef struct pjmedia_frame
-{
-    pjmedia_frame_type	 type;	    /**< Frame type.			    */
-    void		*buf;	    /**< Pointer to buffer.		    */
-    pj_size_t		 size;	    /**< Frame size in bytes.		    */
-    pj_timestamp	 timestamp; /**< Frame timestamp.		    */
-    pj_uint32_t		 bit_info;  /**< Bit info of the frame, sample case:
-					 a frame may not exactly start and end
-					 at the octet boundary, so this field 
-					 may be used for specifying start & 
-					 end bit offset.		    */
-} pjmedia_frame;
-
-
 /**
  * Port interface.
Index: pjproject/trunk/pjmedia/include/pjmedia/sound.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/sound.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/sound.h	(revision 2506)
@@ -24,8 +24,9 @@
 /**
  * @file sound.h
- * @brief Sound player and recorder device framework.
- */
+ * @brief Legacy sound device API
+ */
+#include <pjmedia-audiodev/audiodev.h>
 #include <pjmedia/types.h>
-#include <pj/pool.h>
+
 
 PJ_BEGIN_DECL
@@ -36,4 +37,9 @@
  * @brief PJMEDIA abstraction for sound device hardware
  * @{
+ *
+ * <strong>Warning: this sound device API has been deprecated
+ * and replaced by PJMEDIA Audio Device API. Please see
+ * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * information.</strong>
  *
  * This section describes lower level abstraction for sound device
@@ -62,5 +68,5 @@
  */
 
-/** Opaque data type for audio stream. */
+/** Opaque declaration for pjmedia_snd_stream. */
 typedef struct pjmedia_snd_stream pjmedia_snd_stream;
 
@@ -92,5 +98,4 @@
     unsigned	play_latency;	    /**< Playback latency, in samples.	    */
 } pjmedia_snd_stream_info;
-
 
 /** 
Index: pjproject/trunk/pjmedia/include/pjmedia/sound_port.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/sound_port.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/sound_port.h	(revision 2506)
@@ -25,5 +25,5 @@
  * @brief Media port connection abstraction to sound device.
  */
-#include <pjmedia/sound.h>
+#include <pjmedia-audiodev/audiodev.h>
 #include <pjmedia/port.h>
 
@@ -160,5 +160,20 @@
 						    unsigned options,
 						    pjmedia_snd_port **p_port);
-					      
+
+
+/**
+ * Create sound device port according to the specified parameters.
+ *
+ * @param pool		    Pool to allocate sound port structure.
+ * @param prm		    Sound device settings.
+ * @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_create2(pj_pool_t *pool,
+					      const pjmedia_aud_param *prm,
+					      pjmedia_snd_port **p_port);
+
 
 /**
@@ -180,17 +195,21 @@
  * @return		    The sound stream instance.
  */
-PJ_DECL(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
+PJ_DECL(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream(
 						pjmedia_snd_port *snd_port);
 
 
 /**
- * Configure the echo cancellation tail length. By default, echo canceller
- * is enabled in the sound device with the default tail length. After the
- * sound port is created, application can query the current echo canceller
- * tail length by calling #pjmedia_snd_port_get_ec_tail.
- *
- * Note that you should only change the EC settings when the sound port
- * is not connected to any downstream ports, otherwise race condition may
- * occur.
+ * Change the echo cancellation settings. The echo cancellation settings 
+ * should have been specified when this sound port was created, by setting
+ * the appropriate fields in the pjmedia_aud_param, because not all sound
+ * device implementation supports changing the EC setting once the device
+ * has been opened.
+ *
+ * The behavior of this function depends on whether device or software AEC
+ * is being used. If the device supports AEC, this function will forward
+ * the change request to the device and it will be up to the device whether
+ * to support the request. If software AEC is being used (the software EC
+ * will be used if the device does not support AEC), this function will
+ * change the software EC settings.
  *
  * @param snd_port	    The sound device port.
@@ -200,4 +219,5 @@
  *			    be disabled.
  * @param options	    The options to be passed to #pjmedia_echo_create().
+ *			    This is only used if software EC is being used.
  *
  * @return		    PJ_SUCCESS on success.
Index: pjproject/trunk/pjmedia/include/pjmedia/stream.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/stream.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/stream.h	(revision 2506)
@@ -27,5 +27,4 @@
  */
 
-#include <pjmedia/sound.h>
 #include <pjmedia/codec.h>
 #include <pjmedia/endpoint.h>
Index: pjproject/trunk/pjmedia/include/pjmedia/symbian_sound_aps.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/symbian_sound_aps.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/symbian_sound_aps.h	(revision 2506)
@@ -32,19 +32,14 @@
 
 /**
- * Activate/deactivate loudspeaker, when loudspeaker is inactive, audio
- * will be routed to earpiece.
+ * Set audio routing for APS sound device.
  *
  * @param stream	The sound device stream, the stream should be started 
- *			before calling this function. This param can be NULL
- *			to set the behaviour of next opened stream.
- * @param active	Specify PJ_TRUE to activate loudspeaker, and PJ_FALSE
- *			otherwise.
+ *			before calling this function.
+ * @param route		Audio routing to be set.
  *
  * @return		PJ_SUCCESS on success.
  */
-PJ_DECL(pj_status_t) pjmedia_snd_aps_activate_loudspeaker(
-						pjmedia_snd_stream *stream,
-						pj_bool_t active);
-
+PJ_DECL(pj_status_t) pjmedia_snd_aps_set_route( pjmedia_snd_stream *stream,
+						pjmedia_snd_route route);
 
 PJ_END_DECL
Index: pjproject/trunk/pjmedia/include/pjmedia/types.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/types.h	(revision 2505)
+++ pjproject/trunk/pjmedia/include/pjmedia/types.h	(revision 2506)
@@ -48,6 +48,6 @@
  */
 
-/** 
- * Top most media type. 
+/**
+ * Top most media type.
  */
 typedef enum pjmedia_type
@@ -62,5 +62,5 @@
     PJMEDIA_TYPE_VIDEO = 2,
 
-    /** Unknown media type, in this case the name will be specified in 
+    /** Unknown media type, in this case the name will be specified in
      *  encoding_name.
      */
@@ -73,6 +73,6 @@
 
 
-/** 
- * Media transport protocol. 
+/**
+ * Media transport protocol.
  */
 typedef enum pjmedia_tp_proto
@@ -93,6 +93,6 @@
 
 
-/** 
- * Media direction. 
+/**
+ * Media direction.
  */
 typedef enum pjmedia_dir
@@ -139,6 +139,6 @@
 
 
-/** 
- * Opague declaration of media endpoint. 
+/**
+ * Opaque declaration of media endpoint.
  */
 typedef struct pjmedia_endpt pjmedia_endpt;
@@ -151,5 +151,5 @@
 
 
-/** 
+/**
  * Media socket info is used to describe the underlying sockets
  * to be used as media transport.
@@ -181,6 +181,82 @@
 
 /**
+ * Macro for packing format.
+ */
+#define PJMEDIA_FORMAT_PACK(C1, C2, C3, C4) ( C4<<24 | C3<<16 | C2<<8 | C1 )
+
+/**
+ * This enumeration describes format ID. 
+ */
+typedef enum pjmedia_format_id
+{
+    /**
+     * 16bit linear
+     */
+    PJMEDIA_FORMAT_L16	    = 0,
+    
+    /**
+     * Alias for PJMEDIA_FORMAT_L16
+     */
+    PJMEDIA_FORMAT_PCM	    = PJMEDIA_FORMAT_L16,
+
+    /**
+     * G.711 ALAW
+     */
+    PJMEDIA_FORMAT_PCMA	    = PJMEDIA_FORMAT_PACK('A', 'L', 'A', 'W'),
+
+    /**
+     * Alias for PJMEDIA_FORMAT_PCMA
+     */
+    PJMEDIA_FORMAT_ALAW	    = PJMEDIA_FORMAT_PCMA,
+
+    /**
+     * G.711 ULAW
+     */
+    PJMEDIA_FORMAT_PCMU	    = PJMEDIA_FORMAT_PACK('u', 'L', 'A', 'W'),
+
+    /**
+     * Aliaw for PJMEDIA_FORMAT_PCMU
+     */
+    PJMEDIA_FORMAT_ULAW	    = PJMEDIA_FORMAT_PCMU,
+
+    /**
+     * AMR narrowband
+     */
+    PJMEDIA_FORMAT_AMR	    = PJMEDIA_FORMAT_PACK(' ', 'A', 'M', 'R'),
+
+    /**
+     * ITU G.729
+     */
+    PJMEDIA_FORMAT_G729	    = PJMEDIA_FORMAT_PACK('G', '7', '2', '9'),
+
+    /**
+     * Internet Low Bit-Rate Codec (ILBC)
+     */
+    PJMEDIA_FORMAT_ILBC	    = PJMEDIA_FORMAT_PACK('I', 'L', 'B', 'C')
+
+} pjmedia_format_id;
+
+
+/**
+ * Media format information.
+ */
+typedef struct pjmedia_format
+{
+    /** Format ID */
+    pjmedia_format_id	id;
+
+    /** Bitrate. */
+    pj_uint32_t		bitrate;
+
+    /** Flag to indicate whether VAD is enabled */
+    pj_bool_t		vad;
+
+} pjmedia_format;
+
+
+
+/**
  * This is a general purpose function set PCM samples to zero.
- * Since this function is needed by many parts of the library, 
+ * Since this function is needed by many parts of the library,
  * by putting this functionality in one place, it enables some.
  * clever people to optimize this function.
@@ -206,5 +282,5 @@
 /**
  * This is a general purpose function to copy samples from/to buffers with
- * equal size. Since this function is needed by many parts of the library, 
+ * equal size. Since this function is needed by many parts of the library,
  * by putting this functionality in one place, it enables some.
  * clever people to optimize this function.
@@ -221,5 +297,5 @@
     unsigned i;
     count >>= 1;
-    for (i=0; i<count; ++i) 
+    for (i=0; i<count; ++i)
 	((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
 #endif
@@ -229,5 +305,5 @@
 /**
  * This is a general purpose function to copy samples from/to buffers with
- * equal size. Since this function is needed by many parts of the library, 
+ * equal size. Since this function is needed by many parts of the library,
  * by putting this functionality in one place, it enables some.
  * clever people to optimize this function.
@@ -244,9 +320,209 @@
     unsigned i;
     count >>= 1;
-    for (i=0; i<count; ++i) 
+    for (i=0; i<count; ++i)
 	((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
 #endif
 }
 
+/** 
+ * Types of media frame. 
+ */
+typedef enum pjmedia_frame_type
+{
+    PJMEDIA_FRAME_TYPE_NONE,	    /**< No frame.		*/
+    PJMEDIA_FRAME_TYPE_AUDIO,	    /**< Normal audio frame.	*/
+    PJMEDIA_FRAME_TYPE_EXTENDED	    /**< Extended audio frame.	*/
+
+} pjmedia_frame_type;
+
+
+/** 
+ * This structure describes a media frame. 
+ */
+typedef struct pjmedia_frame
+{
+    pjmedia_frame_type	 type;	    /**< Frame type.			    */
+    void		*buf;	    /**< Pointer to buffer.		    */
+    pj_size_t		 size;	    /**< Frame size in bytes.		    */
+    pj_timestamp	 timestamp; /**< Frame timestamp.		    */
+    pj_uint32_t		 bit_info;  /**< Bit info of the frame, sample case:
+					 a frame may not exactly start and end
+					 at the octet boundary, so this field 
+					 may be used for specifying start & 
+					 end bit offset.		    */
+} pjmedia_frame;
+
+
+/**
+ * The pjmedia_frame_ext is used to carry a more complex audio frames than
+ * the typical PCM audio frames, and it is signaled by setting the "type"
+ * field of a pjmedia_frame to PJMEDIA_FRAME_TYPE_EXTENDED. With this set,
+ * application may typecast pjmedia_frame to pjmedia_frame_ext.
+ *
+ * This structure may contain more than one audio frames, which subsequently
+ * will be called subframes in this structure. The subframes section
+ * immediately follows the end of this structure, and each subframe is
+ * represented by pjmedia_frame_ext_subframe structure. Every next
+ * subframe immediately follows the previous subframe, and all subframes
+ * are byte-aligned although its payload may not be byte-aligned.
+ */
+
+#pragma pack(1)
+typedef struct pjmedia_frame_ext {
+    pjmedia_frame   base;	    /**< Base frame info */
+    pj_uint16_t     samples_cnt;    /**< Number of samples in this frame */
+    pj_uint16_t     subframe_cnt;   /**< Number of (sub)frames in this frame */
+
+    /* Zero or more (sub)frames follows immediately after this,
+     * each will be represented by pjmedia_frame_ext_subframe
+     */
+} pjmedia_frame_ext;
+#pragma pack()
+
+/**
+ * This structure represents the individual subframes in the
+ * pjmedia_frame_ext structure.
+ */
+#pragma pack(1)
+typedef struct pjmedia_frame_ext_subframe {
+    pj_uint16_t     bitlen;	    /**< Number of bits in the data */
+    pj_uint8_t      data[1];	    /**< Start of encoded data */
+} pjmedia_frame_ext_subframe;
+
+#pragma pack()
+
+
+/**
+ * Append one subframe to #pjmedia_frame_ext.
+ *
+ * @param frm		    The #pjmedia_frame_ext.
+ * @param src		    Subframe data.
+ * @param bitlen	    Lenght of subframe, in bits.
+ * @param samples_cnt	    Number of audio samples in subframe.
+ */
+PJ_INLINE(void) pjmedia_frame_ext_append_subframe(pjmedia_frame_ext *frm,
+						  const void *src,
+					          unsigned bitlen,
+						  unsigned samples_cnt)
+{
+    pjmedia_frame_ext_subframe *fsub;
+    pj_uint8_t *p;
+    unsigned i;
+
+    p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
+    for (i = 0; i < frm->subframe_cnt; ++i) {
+	fsub = (pjmedia_frame_ext_subframe*) p;
+	p += sizeof(fsub->bitlen) + ((fsub->bitlen+7) >> 3);
+    }
+
+    fsub = (pjmedia_frame_ext_subframe*) p;
+    fsub->bitlen = (pj_uint16_t)bitlen;
+    if (bitlen)
+	pj_memcpy(fsub->data, src, (bitlen+7) >> 3);
+
+    frm->subframe_cnt++;
+    frm->samples_cnt = (pj_uint16_t)(frm->samples_cnt + samples_cnt);
+}
+
+/**
+ * Get a subframe from #pjmedia_frame_ext.
+ *
+ * @param frm		    The #pjmedia_frame_ext.
+ * @param n		    Subframe index, zero based.
+ *
+ * @return		    The n-th subframe, or NULL if n is out-of-range.
+ */
+PJ_INLINE(pjmedia_frame_ext_subframe*) 
+pjmedia_frame_ext_get_subframe(const pjmedia_frame_ext *frm, unsigned n)
+{
+    pjmedia_frame_ext_subframe *sf = NULL;
+
+    if (n < frm->subframe_cnt) {
+	pj_uint8_t *p;
+	unsigned i;
+
+	p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
+	for (i = 0; i < n; ++i) {	
+	    sf = (pjmedia_frame_ext_subframe*) p;
+	    p += sizeof(sf->bitlen) + ((sf->bitlen+7) >> 3);
+	}
+        
+	sf = (pjmedia_frame_ext_subframe*) p;
+    }
+
+    return sf;
+}
+	
+/**
+ * Extract all frame payload to the specified buffer. 
+ *
+ * @param frm		    The frame.
+ * @param dst		    Destination buffer.
+ * @param maxsize	    Maximum size to copy (i.e. the size of the
+ *			    destination buffer).
+ *
+ * @return		    Total size of payload copied.
+ */
+PJ_INLINE(unsigned) 
+pjmedia_frame_ext_copy_payload(const pjmedia_frame_ext *frm,
+			       void *dst, 
+			       unsigned maxlen)
+{
+    unsigned i, copied=0;
+    for (i=0; i<frm->subframe_cnt; ++i) {
+	pjmedia_frame_ext_subframe *sf;
+	unsigned sz;
+
+	sf = pjmedia_frame_ext_get_subframe(frm, i);
+	if (!sf)
+	    continue;
+
+	sz = ((sf->bitlen + 7) >> 3);
+	if (sz + copied > maxlen)
+	    break;
+
+	pj_memcpy(((pj_uint8_t*)dst) + copied, sf->data, sz);
+	copied += sz;
+    }
+    return copied;
+}
+
+
+/**
+ * Pop out first n subframes from #pjmedia_frame_ext.
+ *
+ * @param frm		    The #pjmedia_frame_ext.
+ * @param n		    Number of first subframes to be popped out.
+ *
+ * @return		    PJ_SUCCESS when successful.
+ */
+PJ_INLINE(pj_status_t) 
+pjmedia_frame_ext_pop_subframes(pjmedia_frame_ext *frm, unsigned n)
+{
+    pjmedia_frame_ext_subframe *sf;
+    pj_uint8_t *move_src;
+    unsigned move_len;
+
+    if (frm->subframe_cnt <= n) {
+	frm->subframe_cnt = 0;
+	frm->samples_cnt = 0;
+	return PJ_SUCCESS;
+    }
+
+    move_src = (pj_uint8_t*)pjmedia_frame_ext_get_subframe(frm, n);
+    sf = pjmedia_frame_ext_get_subframe(frm, frm->subframe_cnt-1);
+    move_len = (pj_uint8_t*)sf - move_src + sizeof(sf->bitlen) + 
+	       ((sf->bitlen+7) >> 3);
+    pj_memmove((pj_uint8_t*)frm+sizeof(pjmedia_frame_ext), 
+	       move_src, move_len);
+	    
+    frm->samples_cnt = (pj_uint16_t)
+		   (frm->samples_cnt - n*frm->samples_cnt/frm->subframe_cnt);
+    frm->subframe_cnt = (pj_uint16_t) (frm->subframe_cnt - n);
+
+    return PJ_SUCCESS;
+}
+
+
 /**
  * @}
Index: pjproject/trunk/pjmedia/include/pjmedia_audiodev.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia_audiodev.h	(revision 2506)
+++ pjproject/trunk/pjmedia/include/pjmedia_audiodev.h	(revision 2506)
@@ -0,0 +1,33 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#ifndef __PJMEDIA_AUDIODEV_H__
+#define __PJMEDIA_AUDIODEV_H__
+
+/**
+ * @file pjmedia_audiodev.h
+ * @brief PJMEDIA main header file.
+ */
+
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/audiotest.h>
+
+#endif	/* __PJMEDIA_AUDIODEV_H__ */
+
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/audiodev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/audiodev.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/audiodev.c	(revision 2506)
@@ -0,0 +1,697 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE   "audiodev.c"
+
+#define DEFINE_CAP(name, info)	{name, info}
+
+/* Capability names */
+static struct cap_info
+{
+    const char *name;
+    const char *info;
+} cap_infos[] = 
+{
+    DEFINE_CAP("ext-fmt",     "Extended/non-PCM format"),
+    DEFINE_CAP("latency-in",  "Input latency/buffer size setting"),
+    DEFINE_CAP("latency-out", "Output latency/buffer size setting"),
+    DEFINE_CAP("vol-in",      "Input volume setting"),
+    DEFINE_CAP("vol-out",     "Output volume setting"),
+    DEFINE_CAP("meter-in",    "Input meter"),
+    DEFINE_CAP("meter-out",   "Output meter"),
+    DEFINE_CAP("route-in",    "Input routing"),
+    DEFINE_CAP("route-out",   "Output routing"),
+    DEFINE_CAP("aec",	      "Accoustic echo cancellation"),
+    DEFINE_CAP("aec-tail",    "Tail length setting for AEC"),
+    DEFINE_CAP("vad",	      "Voice activity detection"),
+    DEFINE_CAP("cng",	      "Comfort noise generation"),
+    DEFINE_CAP("plg",	      "Packet loss concealment")
+};
+
+
+/*
+ * The device index seen by application and driver is different. 
+ *
+ * At application level, device index is index to global list of device.
+ * At driver level, device index is index to device list on that particular
+ * factory only.
+ */
+#define MAKE_DEV_ID(f_id, index)   (((f_id & 0xFFFF) << 16) | (index & 0xFFFF))
+#define GET_INDEX(dev_id)	   ((dev_id) & 0xFFFF)
+#define GET_FID(dev_id)		   ((dev_id) >> 16)
+#define DEFAULT_DEV_ID		    0
+
+
+/* extern functions to create factories */
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+pjmedia_aud_dev_factory* pjmedia_aps_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf);
+#endif
+
+#define MAX_DRIVERS	16
+#define MAX_DEVS	64
+
+/* typedef for factory creation function */
+typedef pjmedia_aud_dev_factory*  (*create_func_ptr)(pj_pool_factory*);
+
+/* driver structure */
+struct driver
+{
+    create_func_ptr	     create;	/* Creation function.		    */
+    pjmedia_aud_dev_factory *f;		/* Factory instance.		    */
+    char		     name[32];	/* Driver name			    */
+    unsigned		     dev_cnt;	/* Number of devices		    */
+    unsigned		     start_idx;	/* Start index in global list	    */
+    int			     rec_dev_idx;/* Default capture device.	    */
+    int			     play_dev_idx;/* Default playback device	    */
+    int			     dev_idx;	/* Default device.		    */
+};
+
+/* The audio subsystem */
+static struct aud_subsys
+{
+    unsigned	     init_count;	/* How many times init() is called  */
+    pj_pool_factory *pf;		/* The pool factory.		    */
+
+    unsigned	     drv_cnt;		/* Number of drivers.		    */
+    struct driver    drv[MAX_DRIVERS];	/* Array of drivers.		    */
+
+    unsigned	     dev_cnt;		/* Total number of devices.	    */
+    pj_uint32_t	     dev_list[MAX_DEVS];/* Array of device IDs.		    */
+
+} aud_subsys;
+
+/* API: get capability name/info */
+PJ_DEF(const char*) pjmedia_aud_dev_cap_name(pjmedia_aud_dev_cap cap,
+					     const char **p_desc)
+{
+    const char *desc;
+    unsigned i;
+
+    if (p_desc==NULL) p_desc = &desc;
+
+    for (i=0; i<PJ_ARRAY_SIZE(cap_infos); ++i) {
+	if ((1 << i)==cap)
+	    break;
+    }
+
+    if (i==32) {
+	*p_desc = "??";
+	return "??";
+    }
+
+    *p_desc = cap_infos[i].info;
+    return cap_infos[i].name;
+}
+
+static pj_status_t get_cap_pointer(const pjmedia_aud_param *param,
+				   pjmedia_aud_dev_cap cap,
+				   void **ptr,
+				   unsigned *size)
+{
+#define FIELD_INFO(name)    *ptr = (void*)&param->name; \
+			    *size = sizeof(param->name)
+
+    switch (cap) {
+    case PJMEDIA_AUD_DEV_CAP_EXT_FORMAT:
+	FIELD_INFO(ext_fmt);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY:
+	FIELD_INFO(input_latency_ms);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY:
+	FIELD_INFO(output_latency_ms);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+	FIELD_INFO(input_vol);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+	FIELD_INFO(output_vol);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE:
+	FIELD_INFO(input_route);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+	FIELD_INFO(output_route);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_EC:
+	FIELD_INFO(ec_enabled);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_EC_TAIL:
+	FIELD_INFO(ec_tail_ms);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_VAD:
+	FIELD_INFO(ext_fmt.vad);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_CNG:
+	FIELD_INFO(cng_enabled);
+	break;
+    case PJMEDIA_AUD_DEV_CAP_PLC:
+	FIELD_INFO(plc_enabled);
+	break;
+    default:
+	return PJMEDIA_EAUD_INVCAP;
+    }
+
+#undef FIELD_INFO
+
+    return PJ_SUCCESS;
+}
+
+/* API: set cap value to param */
+PJ_DEF(pj_status_t) pjmedia_aud_param_set_cap( pjmedia_aud_param *param,
+					       pjmedia_aud_dev_cap cap,
+					       const void *pval)
+{
+    void *cap_ptr;
+    unsigned cap_size;
+    pj_status_t status;
+
+    status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_memcpy(cap_ptr, pval, cap_size);
+    param->flags |= cap;
+
+    return PJ_SUCCESS;
+}
+
+/* API: get cap value from param */
+PJ_DEF(pj_status_t) pjmedia_aud_param_get_cap( const pjmedia_aud_param *param,
+					       pjmedia_aud_dev_cap cap,
+					       void *pval)
+{
+    void *cap_ptr;
+    unsigned cap_size;
+    pj_status_t status;
+
+    status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    if ((param->flags & cap) == 0) {
+	pj_bzero(cap_ptr, cap_size);
+	return PJMEDIA_EAUD_INVCAP;
+    }
+
+    pj_memcpy(pval, cap_ptr, cap_size);
+    return PJ_SUCCESS;
+}
+
+/* Internal: init driver */
+static pj_status_t init_driver(unsigned drv_idx)
+{
+    struct driver *drv = &aud_subsys.drv[drv_idx];
+    pjmedia_aud_dev_factory *f;
+    unsigned i, dev_cnt;
+    pj_status_t status;
+
+    /* Create the factory */
+    f = (*drv->create)(aud_subsys.pf);
+    if (!f)
+	return PJ_EUNKNOWN;
+
+    /* Call factory->init() */
+    status = f->op->init(f);
+    if (status != PJ_SUCCESS) {
+	f->op->destroy(f);
+	return status;
+    }
+
+    /* Get number of devices */
+    dev_cnt = f->op->get_dev_count(f);
+    if (dev_cnt + aud_subsys.dev_cnt > MAX_DEVS) {
+	PJ_LOG(4,(THIS_FILE, "%d device(s) cannot be registered because"
+			      " there are too many devices",
+			      aud_subsys.dev_cnt + dev_cnt - MAX_DEVS));
+	dev_cnt = MAX_DEVS - aud_subsys.dev_cnt;
+    }
+    if (dev_cnt == 0) {
+	f->op->destroy(f);
+	return PJMEDIA_EAUD_NODEV;
+    }
+
+    /* Fill in default devices */
+    drv->play_dev_idx = drv->rec_dev_idx = drv->dev_idx = -1;
+    for (i=0; i<dev_cnt; ++i) {
+	pjmedia_aud_dev_info info;
+
+	status = f->op->get_dev_info(f, i, &info);
+	if (status != PJ_SUCCESS) {
+	    f->op->destroy(f);
+	    return status;
+	}
+
+	if (drv->name[0]=='\0') {
+	    /* Set driver name */
+	    pj_ansi_strncpy(drv->name, info.driver, sizeof(drv->name));
+	    drv->name[sizeof(drv->name)-1] = '\0';
+	}
+
+	if (drv->play_dev_idx < 0 && info.output_count) {
+	    /* Set default playback device */
+	    drv->play_dev_idx = i;
+	}
+	if (drv->rec_dev_idx < 0 && info.input_count) {
+	    /* Set default capture device */
+	    drv->rec_dev_idx = i;
+	}
+	if (drv->dev_idx < 0 && info.input_count &&
+	    info.output_count)
+	{
+	    /* Set default capture and playback device */
+	    drv->dev_idx = i;
+	}
+
+	if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0 && 
+	    drv->dev_idx >= 0) 
+	{
+	    /* Done. */
+	    break;
+	}
+    }
+
+    /* Register the factory */
+    drv->f = f;
+    drv->f->sys.drv_idx = drv_idx;
+    drv->start_idx = aud_subsys.dev_cnt;
+    drv->dev_cnt = dev_cnt;
+
+    /* Register devices to global list */
+    for (i=0; i<dev_cnt; ++i) {
+	aud_subsys.dev_list[aud_subsys.dev_cnt++] = MAKE_DEV_ID(drv_idx, i);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* Internal: deinit driver */
+static void deinit_driver(unsigned drv_idx)
+{
+    struct driver *drv = &aud_subsys.drv[drv_idx];
+
+    if (drv->f) {
+	drv->f->op->destroy(drv->f);
+	drv->f = NULL;
+    }
+
+    drv->dev_cnt = 0;
+    drv->play_dev_idx = drv->rec_dev_idx = drv->dev_idx = -1;
+}
+
+/* API: Initialize the audio subsystem. */
+PJ_DEF(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf)
+{
+    unsigned i;
+    pj_status_t status = PJ_ENOMEM;
+
+    /* Allow init() to be called multiple times as long as there is matching
+     * number of shutdown().
+     */
+    if (aud_subsys.init_count++ != 0) {
+	return PJ_SUCCESS;
+    }
+
+    /* Register error subsystem */
+    pj_register_strerror(PJMEDIA_AUDIODEV_ERRNO_START, 
+			 PJ_ERRNO_SPACE_SIZE, 
+			 &pjmedia_audiodev_strerror);
+
+    /* Init */
+    aud_subsys.pf = pf;
+    aud_subsys.drv_cnt = 0;
+    aud_subsys.dev_cnt = 0;
+
+    /* Register creation functions */
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+    aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_pa_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+    aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_wmme_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+    aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_aps_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+    aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_symb_mda_factory;
+#endif
+
+    /* Initialize each factory and build the device ID list */
+    for (i=0; i<aud_subsys.drv_cnt; ++i) {
+	status = init_driver(i);
+	if (status != PJ_SUCCESS) {
+	    deinit_driver(i);
+	    continue;
+	}
+    }
+
+    return aud_subsys.dev_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: get the pool factory registered to the audio subsystem. */
+PJ_DEF(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void)
+{
+    return aud_subsys.pf;
+}
+
+/* API: Shutdown the audio subsystem. */
+PJ_DEF(pj_status_t) pjmedia_aud_subsys_shutdown(void)
+{
+    unsigned i;
+
+    /* Allow shutdown() to be called multiple times as long as there is matching
+     * number of init().
+     */
+    if (aud_subsys.init_count == 0) {
+	return PJ_SUCCESS;
+    }
+    --aud_subsys.init_count;
+
+    for (i=0; i<aud_subsys.drv_cnt; ++i) {
+	deinit_driver(i);
+    }
+
+    aud_subsys.pf = NULL;
+    return PJ_SUCCESS;
+}
+
+/* API: Get the number of sound devices installed in the system. */
+PJ_DEF(unsigned) pjmedia_aud_dev_count(void)
+{
+    return aud_subsys.dev_cnt;
+}
+
+/* Internal: convert local index to global device index */
+static pj_status_t make_global_index(unsigned drv_idx, 
+				     pjmedia_aud_dev_index *id)
+{
+    if (*id < 0) {
+	return PJ_SUCCESS;
+    }
+
+    /* Check that factory still exists */
+    PJ_ASSERT_RETURN(aud_subsys.drv[drv_idx].f, PJ_EBUG);
+
+    /* Check that device index is valid */
+    PJ_ASSERT_RETURN(*id>=0 && *id<(int)aud_subsys.drv[drv_idx].dev_cnt, 
+		     PJ_EBUG);
+
+    *id += aud_subsys.drv[drv_idx].start_idx;
+    return PJ_SUCCESS;
+}
+
+/* Internal: lookup device id */
+static pj_status_t lookup_dev(pjmedia_aud_dev_index id,
+			      pjmedia_aud_dev_factory **p_f,
+			      unsigned *p_local_index)
+{
+    int f_id, index;
+
+    if (id < 0) {
+	unsigned i;
+
+	if (id == PJMEDIA_AUD_INVALID_DEV)
+	    return PJMEDIA_EAUD_INVDEV;
+
+	for (i=0; i<aud_subsys.drv_cnt; ++i) {
+	    struct driver *drv = &aud_subsys.drv[i];
+	    if (drv->dev_idx >= 0) {
+		id = drv->dev_idx;
+		make_global_index(i, &id);
+		break;
+	    } else if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && 
+		drv->rec_dev_idx >= 0) 
+	    {
+		id = drv->rec_dev_idx;
+		make_global_index(i, &id);
+		break;
+	    } else if (id==PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV && 
+		drv->play_dev_idx >= 0) 
+	    {
+		id = drv->play_dev_idx;
+		make_global_index(i, &id);
+		break;
+	    }
+	}
+
+	if (id < 0) {
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    }
+
+    f_id = GET_FID(aud_subsys.dev_list[id]);
+    index = GET_INDEX(aud_subsys.dev_list[id]);
+
+    if (f_id < 0 || f_id >= (int)aud_subsys.drv_cnt)
+	return PJMEDIA_EAUD_INVDEV;
+
+    if (index < 0 || index >= (int)aud_subsys.drv[f_id].dev_cnt)
+	return PJMEDIA_EAUD_INVDEV;
+
+    *p_f = aud_subsys.drv[f_id].f;
+    *p_local_index = (unsigned)index;
+
+    return PJ_SUCCESS;
+
+}
+
+/* API: Get device information. */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_get_info(pjmedia_aud_dev_index id,
+					     pjmedia_aud_dev_info *info)
+{
+    pjmedia_aud_dev_factory *f;
+    unsigned index;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(info && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL);
+    PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+    status = lookup_dev(id, &f, &index);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return f->op->get_dev_info(f, index, info);
+}
+
+/* API: find device */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_lookup( const char *drv_name,
+					    const char *dev_name,
+					    pjmedia_aud_dev_index *id)
+{
+    pjmedia_aud_dev_factory *f = NULL;
+    unsigned drv_idx, dev_idx;
+
+    PJ_ASSERT_RETURN(drv_name && dev_name && id, PJ_EINVAL);
+    PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+    for (drv_idx=0; drv_idx<aud_subsys.drv_cnt; ++drv_idx) {
+	if (!pj_ansi_stricmp(drv_name, aud_subsys.drv[drv_idx].name)) {
+	    f = aud_subsys.drv[drv_idx].f;
+	    break;
+	}
+    }
+
+    if (!f)
+	return PJ_ENOTFOUND;
+
+    for (dev_idx=0; dev_idx<aud_subsys.drv[drv_idx].dev_cnt; ++dev_idx) {
+	pjmedia_aud_dev_info info;
+	pj_status_t status;
+
+	status = f->op->get_dev_info(f, dev_idx, &info);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	if (!pj_ansi_stricmp(dev_name, info.name))
+	    break;
+    }
+
+    if (dev_idx==aud_subsys.drv[drv_idx].dev_cnt)
+	return PJ_ENOTFOUND;
+
+    *id = dev_idx;
+    make_global_index(drv_idx, id);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Initialize the audio device parameters with default values for the
+ * specified device.
+ */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_default_param(pjmedia_aud_dev_index id,
+						  pjmedia_aud_param *param)
+{
+    pjmedia_aud_dev_factory *f;
+    unsigned index;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(param && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL);
+    PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+    status = lookup_dev(id, &f, &index);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = f->op->default_param(f, index, param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Normalize device IDs */
+    make_global_index(f->sys.drv_idx, &param->rec_id);
+    make_global_index(f->sys.drv_idx, &param->play_id);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Open audio stream object using the specified parameters. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *prm,
+					      pjmedia_aud_rec_cb rec_cb,
+					      pjmedia_aud_play_cb play_cb,
+					      void *user_data,
+					      pjmedia_aud_stream **p_aud_strm)
+{
+    pjmedia_aud_dev_factory *rec_f=NULL, *play_f=NULL, *f=NULL;
+    pjmedia_aud_param param;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(prm && prm->dir && p_aud_strm, PJ_EINVAL);
+    PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+    /* Must make copy of param because we're changing device ID */
+    pj_memcpy(&param, prm, sizeof(param));
+
+    /* Normalize rec_id */
+    if (param.dir & PJMEDIA_DIR_CAPTURE) {
+	unsigned index;
+
+	if (param.rec_id < 0)
+	    param.rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+
+	status = lookup_dev(param.rec_id, &rec_f, &index);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	param.rec_id = index;
+	f = rec_f;
+    }
+
+    /* Normalize play_id */
+    if (param.dir & PJMEDIA_DIR_PLAYBACK) {
+	unsigned index;
+
+	if (param.play_id < 0)
+	    param.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+
+	status = lookup_dev(param.play_id, &play_f, &index);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	param.play_id = index;
+	f = play_f;
+
+	/* For now, rec_id and play_id must belong to the same factory */
+	PJ_ASSERT_RETURN(rec_f == play_f, PJMEDIA_EAUD_INVDEV);
+    }
+
+    
+    /* Create the stream */
+    status = f->op->create_stream(f, &param, rec_cb, play_cb,
+				  user_data, p_aud_strm);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Assign factory id to the stream */
+    (*p_aud_strm)->sys.drv_idx = f->sys.drv_idx;
+    return PJ_SUCCESS;
+}
+
+/* API: Get the running parameters for the specified audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_get_param(pjmedia_aud_stream *strm,
+						 pjmedia_aud_param *param)
+{
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(strm && param, PJ_EINVAL);
+    PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+    status = strm->op->get_param(strm, param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Normalize device id's */
+    make_global_index(strm->sys.drv_idx, &param->rec_id);
+    make_global_index(strm->sys.drv_idx, &param->play_id);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get the value of a specific capability of the audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_get_cap(pjmedia_aud_stream *strm,
+					       pjmedia_aud_dev_cap cap,
+					       void *value)
+{
+    return strm->op->get_cap(strm, cap, value);
+}
+
+/* API: Set the value of a specific capability of the audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_set_cap(pjmedia_aud_stream *strm,
+					       pjmedia_aud_dev_cap cap,
+					       const void *value)
+{
+    return strm->op->set_cap(strm, cap, value);
+}
+
+/* API: Start the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_start(pjmedia_aud_stream *strm)
+{
+    return strm->op->start(strm);
+}
+
+/* API: Stop the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_stop(pjmedia_aud_stream *strm)
+{
+    return strm->op->stop(strm);
+}
+
+/* API: Destroy the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm)
+{
+    return strm->op->destroy(strm);
+}
+
+
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/audiotest.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/audiotest.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/audiotest.c	(revision 2506)
@@ -0,0 +1,269 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia-audiodev/audiotest.h>
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+ 
+#define THIS_FILE	    "audiotest.c"
+
+/* Test duration in msec */
+#define DURATION	    10000
+
+/* Skip the first msec from the calculation */
+#define SKIP_DURATION	    1000
+
+/* Division helper */
+#define DIV_ROUND_UP(a,b) (((a) + ((b) - 1)) / (b))
+#define DIV_ROUND(a,b) (((a) + ((b)/2 - 1)) / (b))
+
+struct stream_data
+{
+    pj_uint32_t	    first_timestamp;
+    pj_uint32_t	    last_timestamp;
+    pj_timestamp    last_called;
+    pj_math_stat    delay;
+};
+
+struct test_data 
+{
+    pj_pool_t			   *pool;
+    const pjmedia_aud_param	   *param;
+    pjmedia_aud_test_results	   *result;
+    pj_bool_t			    running;
+    pj_bool_t			    has_error;
+    pj_mutex_t			   *mutex;
+
+    struct stream_data		    capture_data;
+    struct stream_data		    playback_data;
+};
+
+static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
+{
+    struct test_data *test_data = (struct test_data *)user_data;
+    struct stream_data *strm_data = &test_data->playback_data;
+
+    pj_mutex_lock(test_data->mutex);
+
+    /* Skip frames when test is not started or test has finished */
+    if (!test_data->running) {
+	pj_bzero(frame->buf, frame->size);
+	pj_mutex_unlock(test_data->mutex);
+	return PJ_SUCCESS;
+    }
+
+    /* Save last timestamp seen (to calculate drift) */
+    strm_data->last_timestamp = frame->timestamp.u32.lo;
+
+    if (strm_data->last_called.u64 == 0) {
+	/* Init vars. */
+	pj_get_timestamp(&strm_data->last_called);
+	pj_math_stat_init(&strm_data->delay);
+	strm_data->first_timestamp = frame->timestamp.u32.lo;
+    } else {
+	pj_timestamp now;
+	unsigned delay;
+
+	/* Calculate frame interval */
+	pj_get_timestamp(&now);
+	delay = pj_elapsed_usec(&strm_data->last_called, &now);
+	strm_data->last_called = now;
+
+	/* Update frame interval statistic */
+	pj_math_stat_update(&strm_data->delay, delay);
+    }
+
+    pj_bzero(frame->buf, frame->size);
+
+    pj_mutex_unlock(test_data->mutex);
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
+{
+    struct test_data *test_data = (struct test_data*)user_data;
+    struct stream_data *strm_data = &test_data->capture_data;
+
+    pj_mutex_lock(test_data->mutex);
+
+    /* Skip frames when test is not started or test has finished */
+    if (!test_data->running) {
+	pj_mutex_unlock(test_data->mutex);
+	return PJ_SUCCESS;
+    }
+
+    /* Save last timestamp seen (to calculate drift) */
+    strm_data->last_timestamp = frame->timestamp.u32.lo;
+
+    if (strm_data->last_called.u64 == 0) {
+	/* Init vars. */
+	pj_get_timestamp(&strm_data->last_called);
+	pj_math_stat_init(&strm_data->delay);
+	strm_data->first_timestamp = frame->timestamp.u32.lo;
+    } else {
+	pj_timestamp now;
+	unsigned delay;
+
+	/* Calculate frame interval */
+	pj_get_timestamp(&now);
+	delay = pj_elapsed_usec(&strm_data->last_called, &now);
+	strm_data->last_called = now;
+
+	/* Update frame interval statistic */
+	pj_math_stat_update(&strm_data->delay, delay);
+    }
+
+    pj_mutex_unlock(test_data->mutex);
+    return PJ_SUCCESS;
+}
+
+static void app_perror(const char *title, pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));	
+    printf( "%s: %s (err=%d)\n",
+	    title, errmsg, status);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_aud_test( const pjmedia_aud_param *param,
+				      pjmedia_aud_test_results *result)
+{
+    pj_status_t status = PJ_SUCCESS;
+    pjmedia_aud_stream *strm;
+    struct test_data test_data;
+    unsigned ptime, tmp;
+    
+    /*
+     * Init test parameters
+     */
+    pj_bzero(&test_data, sizeof(test_data));
+    test_data.param = param;
+    test_data.result = result;
+
+    test_data.pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
+				    "audtest", 1000, 1000, NULL);
+    pj_mutex_create_simple(test_data.pool, "sndtest", &test_data.mutex); 
+
+    /*
+     * Open device.
+     */
+    status = pjmedia_aud_stream_create(test_data.param, &rec_cb, &play_cb, 
+				       &test_data, &strm);
+    if (status != PJ_SUCCESS) {
+        app_perror("Unable to open device", status);
+	pj_pool_release(test_data.pool);
+        return status;
+    }
+
+
+    /* Sleep for a while to let sound device "settles" */
+    pj_thread_sleep(200);
+
+    /*
+     * Start the stream.
+     */
+    status = pjmedia_aud_stream_start(strm);
+    if (status != PJ_SUCCESS) {
+        app_perror("Unable to start capture stream", status);
+	pjmedia_aud_stream_destroy(strm);
+	pj_pool_release(test_data.pool);
+        return status;
+    }
+
+    PJ_LOG(3,(THIS_FILE,
+	      " Please wait while test is in progress (~%d secs)..",
+	      (DURATION+SKIP_DURATION)/1000));
+
+    /* Let the stream runs for few msec/sec to get stable result.
+     * (capture normally begins with frames available simultaneously).
+     */
+    pj_thread_sleep(SKIP_DURATION);
+
+
+    /* Begin gather data */
+    test_data.running = 1;
+
+    /* 
+     * Let the test runs for a while.
+     */
+    pj_thread_sleep(DURATION);
+
+
+    /*
+     * Close stream.
+     */
+    test_data.running = 0;
+    pjmedia_aud_stream_destroy(strm);
+    pj_pool_release(test_data.pool);
+
+
+    /* 
+     * Gather results
+     */
+    ptime = param->samples_per_frame * 1000 / param->clock_rate;
+
+    tmp = pj_math_stat_get_stddev(&test_data.capture_data.delay);
+    result->rec.frame_cnt = test_data.capture_data.delay.n;
+    result->rec.min_interval = DIV_ROUND(test_data.capture_data.delay.min, 1000);
+    result->rec.max_interval = DIV_ROUND(test_data.capture_data.delay.max, 1000);
+    result->rec.avg_interval = DIV_ROUND(test_data.capture_data.delay.mean, 1000);
+    result->rec.dev_interval = DIV_ROUND(tmp, 1000);
+    result->rec.max_burst    = DIV_ROUND_UP(result->rec.max_interval, ptime);
+
+    tmp = pj_math_stat_get_stddev(&test_data.playback_data.delay);
+    result->play.frame_cnt = test_data.playback_data.delay.n;
+    result->play.min_interval = DIV_ROUND(test_data.playback_data.delay.min, 1000);
+    result->play.max_interval = DIV_ROUND(test_data.playback_data.delay.max, 1000);
+    result->play.avg_interval = DIV_ROUND(test_data.capture_data.delay.mean, 1000);
+    result->play.dev_interval = DIV_ROUND(tmp, 1000);
+    result->play.max_burst    = DIV_ROUND_UP(result->play.max_interval, ptime);
+
+    /* Check drifting */
+    if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+	int end_diff, start_diff, drift;
+
+	end_diff = test_data.capture_data.last_timestamp -
+		   test_data.playback_data.last_timestamp;
+	start_diff = test_data.capture_data.first_timestamp-
+		      test_data.playback_data.first_timestamp;
+	drift = end_diff > start_diff? end_diff - start_diff :
+		start_diff - end_diff;
+
+	/* Allow one frame tolerance for clock drift detection */
+	if (drift < (int)param->samples_per_frame) {
+	    result->rec_drift_per_sec = 0;
+	} else {
+	    unsigned msec_dur;
+
+	    msec_dur = (test_data.capture_data.last_timestamp - 
+		       test_data.capture_data.first_timestamp) * 1000 /
+		       test_data.param->clock_rate;
+
+	    result->rec_drift_per_sec = drift * 1000 / msec_dur;
+
+	}
+    }
+
+    return test_data.has_error? PJ_EUNKNOWN : PJ_SUCCESS;
+}
+
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/errno.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/errno.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/errno.c	(revision 2506)
@@ -0,0 +1,190 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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-audiodev/errno.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+#   include <portaudio.h>
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+#   ifdef _MSC_VER
+#	pragma warning(push, 3)
+#   endif
+#   include <windows.h>
+#   include <mmsystem.h>
+#   ifdef _MSC_VER
+#	pragma warning(pop)
+#   endif
+#endif
+
+/* PJMEDIA-Audiodev's own error codes/messages 
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct 
+{
+    int code;
+    const char *msg;
+} err_str[] = 
+{
+    PJ_BUILD_ERR( PJMEDIA_EAUD_ERR,	    "Unspecified audio device error" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_SYSERR,	    "Unknown error from audio driver" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_INIT,	    "Audio subsystem not initialized" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_INVDEV,	    "Invalid audio device" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_NODEV,	    "Found no audio devices" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_NODEFDEV,    "Unable to find default audio device" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_NOTREADY,    "Audio device not ready" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_INVCAP,	    "Invalid or unsupported audio capability" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_INVOP,	    "Invalid or unsupported audio device operation" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_BADFORMAT,   "Bad or invalid audio device format" ),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_SAMPFORMAT,  "Invalid audio device sample format"),
+    PJ_BUILD_ERR( PJMEDIA_EAUD_BADLATENCY,  "Bad audio latency setting")
+
+};
+
+#endif	/* PJ_HAS_ERROR_STRING */
+
+
+
+/*
+ * pjmedia_audiodev_strerror()
+ */
+PJ_DEF(pj_str_t) pjmedia_audiodev_strerror(pj_status_t statcode, 
+					   char *buf, pj_size_t bufsize )
+{
+    pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+    /* See if the error comes from PortAudio. */
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+    if (statcode >= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START &&
+	statcode <= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_END)
+    {
+
+	//int pa_err = statcode - PJMEDIA_ERRNO_FROM_PORTAUDIO(0);
+	int pa_err = PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START - statcode;
+	pj_str_t msg;
+	
+	msg.ptr = (char*)Pa_GetErrorText(pa_err);
+	msg.slen = pj_ansi_strlen(msg.ptr);
+
+	errstr.ptr = buf;
+	pj_strncpy_with_null(&errstr, &msg, bufsize);
+	return errstr;
+
+    } else 
+#endif	/* PJMEDIA_SOUND_IMPLEMENTATION */
+
+    /* See if the error comes from WMME */
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+    if ((statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START &&
+	 statcode < PJMEDIA_AUDIODEV_WMME_IN_ERROR_END) ||
+	(statcode >= PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START &&
+	 statcode < PJMEDIA_AUDIODEV_WMME_OUT_ERROR_END))
+    {
+	MMRESULT native_err, mr;
+	MMRESULT (WINAPI *waveGetErrText)(UINT mmrError, LPTSTR pszText, UINT cchText);
+	PJ_DECL_UNICODE_TEMP_BUF(wbuf, 80)
+
+	if (statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START &&
+	    statcode <= PJMEDIA_AUDIODEV_WMME_IN_ERROR_END)
+	{
+	    native_err = statcode - PJMEDIA_AUDIODEV_WMME_IN_ERROR_START;
+	    waveGetErrText = &waveInGetErrorText;
+	} else {
+	    native_err = statcode - PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START;
+	    waveGetErrText = &waveOutGetErrorText;
+	}
+
+#if PJ_NATIVE_STRING_IS_UNICODE
+	mr = (*waveGetErrText)(native_err, wbuf, PJ_ARRAY_SIZE(wbuf));
+	if (mr == MMSYSERR_NOERROR) {
+	    int len = wcslen(wbuf);
+	    pj_unicode_to_ansi(wbuf, len, buf, bufsize);
+	}
+#else
+	mr = (*waveGetErrText)(native_err, buf, bufsize);
+#endif
+
+	if (mr==MMSYSERR_NOERROR) {
+	    errstr.ptr = buf;
+	    errstr.slen = pj_ansi_strlen(buf);
+	    return errstr;
+	} else {
+	    pj_ansi_snprintf(buf, bufsize, "MMSYSTEM native error %d", 
+			     native_err);
+	    return pj_str(buf);
+	}
+
+    } else
+#endif
+
+    /* Audiodev error */
+    if (statcode >= PJMEDIA_AUDIODEV_ERRNO_START && 
+	statcode < PJMEDIA_AUDIODEV_ERRNO_END)
+    {
+	/* Find the error in the table.
+	 * Use binary search!
+	 */
+	int first = 0;
+	int n = PJ_ARRAY_SIZE(err_str);
+
+	while (n > 0) {
+	    int half = n/2;
+	    int mid = first + half;
+
+	    if (err_str[mid].code < statcode) {
+		first = mid+1;
+		n -= (half+1);
+	    } else if (err_str[mid].code > statcode) {
+		n = half;
+	    } else {
+		first = mid;
+		break;
+	    }
+	}
+
+
+	if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+	    pj_str_t msg;
+	    
+	    msg.ptr = (char*)err_str[first].msg;
+	    msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+	    errstr.ptr = buf;
+	    pj_strncpy_with_null(&errstr, &msg, bufsize);
+	    return errstr;
+
+	} 
+    } 
+#endif	/* PJ_HAS_ERROR_STRING */
+
+    /* Error not found. */
+    errstr.ptr = buf;
+    errstr.slen = pj_ansi_snprintf(buf, bufsize, 
+				   "Unknown pjmedia-audiodev error %d",
+				   statcode);
+
+    return errstr;
+}
+
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/legacy_dev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/legacy_dev.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/legacy_dev.c	(revision 2506)
@@ -0,0 +1,459 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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-audiodev/audiodev_imp.h>
+#include <pjmedia/sound.h>
+#include <pj/assert.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE
+
+#define THIS_FILE			"legacy_dev.c"
+
+/* Legacy devices factory */
+struct legacy_factory
+{
+    pjmedia_aud_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+};
+
+
+struct legacy_stream
+{
+    pjmedia_aud_stream	 base;
+
+    pj_pool_t		*pool;
+    pjmedia_aud_param    param;
+    pjmedia_snd_stream	*snd_strm;
+    pjmedia_aud_play_cb	 user_play_cb;
+    pjmedia_aud_rec_cb   user_rec_cb;
+    void		*user_user_data;
+    unsigned		 input_latency;
+    unsigned		 output_latency;
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned    factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+				    pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+    &factory_init,
+    &factory_destroy,
+    &factory_get_dev_count,
+    &factory_get_dev_info,
+    &factory_default_param,
+    &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op = 
+{
+    &stream_get_param,
+    &stream_get_cap,
+    &stream_set_cap,
+    &stream_start,
+    &stream_stop,
+    &stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * Init legacy audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_legacy_factory(pj_pool_factory *pf)
+{
+    struct legacy_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "legacy-snd", 512, 512, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct legacy_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+    struct legacy_factory *wf = (struct legacy_factory*)f;
+
+    return pjmedia_snd_init(wf->pf);
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+    struct legacy_factory *wf = (struct legacy_factory*)f;
+    pj_status_t status;
+
+    status = pjmedia_snd_deinit();
+
+    if (status == PJ_SUCCESS) {
+	pj_pool_t *pool = wf->pool;
+	wf->pool = NULL;
+	pj_pool_release(pool);
+    }
+
+    return status;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return pjmedia_snd_get_dev_count();
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info)
+{
+    const pjmedia_snd_dev_info *si = 
+	pjmedia_snd_get_dev_info(index);;
+
+    PJ_UNUSED_ARG(f);
+
+    if (si == NULL)
+	return PJMEDIA_EAUD_INVDEV;
+
+    pj_bzero(info, sizeof(*info));
+    pj_ansi_strncpy(info->name, si->name, sizeof(info->name));
+    info->name[sizeof(info->name)-1] = '\0';
+    info->input_count = si->input_count;
+    info->output_count = si->output_count;
+    info->default_samples_per_sec = si->default_samples_per_sec;
+    pj_ansi_strcpy(info->driver, "legacy");
+    info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | 
+		 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param)
+{
+    pjmedia_aud_dev_info di;
+    pj_status_t status;
+
+    status = factory_get_dev_info(f, index, &di);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_bzero(param, sizeof(*param));
+    if (di.input_count && di.output_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+	param->rec_id = index;
+	param->play_id = index;
+    } else if (di.input_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE;
+	param->rec_id = index;
+	param->play_id = PJMEDIA_AUD_INVALID_DEV;
+    } else if (di.output_count) {
+	param->dir = PJMEDIA_DIR_PLAYBACK;
+	param->play_id = index;
+	param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+    } else {
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    param->clock_rate = di.default_samples_per_sec;
+    param->channel_count = 1;
+    param->samples_per_frame = di.default_samples_per_sec * 20 / 1000;
+    param->bits_per_sample = 16;
+    param->flags = di.caps;
+    param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+    param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+    return PJ_SUCCESS;
+}
+
+/* Callback from legacy sound device */
+static pj_status_t snd_play_cb(/* in */   void *user_data,
+			       /* in */   pj_uint32_t timestamp,
+			       /* out */  void *output,
+			       /* out */  unsigned size)
+{
+    struct legacy_stream *strm = (struct legacy_stream*)user_data;
+    pjmedia_frame frame;
+    pj_status_t status;
+
+    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+    frame.buf = output;
+    frame.size = size;
+    frame.timestamp.u64 = timestamp;
+
+    status = strm->user_play_cb(strm->user_user_data, &frame);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+	pj_bzero(output, size);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* Callback from legacy sound device */
+static pj_status_t snd_rec_cb( /* in */   void *user_data,
+			       /* in */   pj_uint32_t timestamp,
+			       /* in */   void *input,
+			       /* in*/    unsigned size)
+{
+    struct legacy_stream *strm = (struct legacy_stream*)user_data;
+    pjmedia_frame frame;
+    
+    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+    frame.buf = input;
+    frame.size = size;
+    frame.timestamp.u64 = timestamp;
+
+    return strm->user_rec_cb(strm->user_user_data, &frame);
+}
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm)
+{
+    struct legacy_factory *wf = (struct legacy_factory*)f;
+    pj_pool_t *pool;
+    struct legacy_stream *strm;
+    pj_status_t status;
+
+    /* Initialize our stream data */
+    pool = pj_pool_create(wf->pf, "legacy-snd", 512, 512, NULL);
+    strm = PJ_POOL_ZALLOC_T(pool, struct legacy_stream);
+    strm->pool = pool;
+    strm->user_rec_cb = rec_cb;
+    strm->user_play_cb = play_cb;
+    strm->user_user_data = user_data;
+    pj_memcpy(&strm->param, param, sizeof(*param));
+
+    /* Set the latency if wanted */
+    if (param->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK &&
+	param->flags & (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+			PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY))
+    {
+	PJ_ASSERT_RETURN(param->input_latency_ms &&
+			 param->output_latency_ms,
+			 PJMEDIA_EAUD_BADLATENCY);
+
+	strm->input_latency = param->input_latency_ms;
+	strm->output_latency = param->output_latency_ms;
+
+	status = pjmedia_snd_set_latency(param->input_latency_ms,
+					 param->output_latency_ms);
+	if (status != PJ_SUCCESS) {
+	    pj_pool_release(pool);
+	    return status;
+	}
+    }
+
+    /* Open the stream */
+    if (param->dir == PJMEDIA_DIR_CAPTURE) {
+	status = pjmedia_snd_open_rec(param->rec_id,
+				      param->clock_rate,
+				      param->channel_count,
+				      param->samples_per_frame,
+				      param->bits_per_sample,
+				      &snd_rec_cb,
+				      strm,
+				      &strm->snd_strm);
+    } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
+	status = pjmedia_snd_open_player(param->play_id,
+					 param->clock_rate,
+					 param->channel_count,
+					 param->samples_per_frame,
+					 param->bits_per_sample,
+					 &snd_play_cb,
+					 strm,
+					 &strm->snd_strm);
+
+    } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+	status = pjmedia_snd_open(param->rec_id,
+				  param->play_id,
+				  param->clock_rate,
+				  param->channel_count,
+				  param->samples_per_frame,
+				  param->bits_per_sample,
+				  &snd_rec_cb,
+				  &snd_play_cb,
+				  strm,
+				  &strm->snd_strm);
+    } else {
+	pj_assert(!"Invalid direction!");
+	return PJ_EINVAL;
+    }
+
+    if (status != PJ_SUCCESS) {
+	pj_pool_release(pool);
+	return status;
+    }
+
+    *p_aud_strm = &strm->base;
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+				    pjmedia_aud_param *pi)
+{
+    struct legacy_stream *strm = (struct legacy_stream*)s;
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    if (strm->input_latency) {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+	pi->input_latency_ms = strm->input_latency;
+    } else {
+	pi->flags &= ~PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+    }
+
+    if (strm->output_latency) {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+	pi->output_latency_ms = strm->output_latency;
+    } else {
+	pi->flags &= ~PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  void *pval)
+{
+    struct legacy_stream *strm = (struct legacy_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && 
+	(strm->param.dir & PJMEDIA_DIR_CAPTURE)) 
+    {
+	/* Recording latency */
+	if (strm->input_latency) {
+	    *(unsigned*)pval = strm->input_latency;
+	    return PJ_SUCCESS;
+	} else {
+	    return PJMEDIA_EAUD_INVCAP;
+	}
+
+    } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY  && 
+	       (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+    {
+	/* Playback latency */
+	if (strm->output_latency) {
+	    *(unsigned*)pval = strm->output_latency;
+	    return PJ_SUCCESS;
+	} else {
+	    return PJMEDIA_EAUD_INVCAP;
+	}
+    } else {
+	return PJMEDIA_EAUD_INVCAP;
+    }
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  const void *pval)
+{
+    PJ_UNUSED_ARG(s);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(pval);
+    return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *s)
+{
+    struct legacy_stream *strm = (struct legacy_stream*)s;
+    return pjmedia_snd_stream_start(strm->snd_strm);
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *s)
+{
+    struct legacy_stream *strm = (struct legacy_stream*)s;
+    return pjmedia_snd_stream_stop(strm->snd_strm);
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *s)
+{
+    struct legacy_stream *strm = (struct legacy_stream*)s;
+    pj_status_t status;
+
+    status = pjmedia_snd_stream_close(strm->snd_strm);
+
+    if (status == PJ_SUCCESS) {
+	pj_pool_t *pool = strm->pool;
+
+	strm->pool = NULL;
+	pj_pool_release(pool);
+    }
+
+    return status;
+}
+
+#endif	/* PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE */
+
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/pa_dev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/pa_dev.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/pa_dev.c	(revision 2506)
@@ -0,0 +1,1263 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <portaudio.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+
+
+#define THIS_FILE	"pa_dev.c"
+#define DRIVER_NAME	"PA"
+
+struct pa_aud_factory
+{
+    pjmedia_aud_dev_factory	 base;
+    pj_pool_factory		*pf;
+    pj_pool_t			*pool;
+};
+
+
+/* 
+ * Sound stream descriptor.
+ * This struct may be used for both unidirectional or bidirectional sound
+ * streams.
+ */
+struct pa_aud_stream
+{
+    pjmedia_aud_stream	 base;
+
+    pj_pool_t		*pool;
+    pj_str_t		 name;
+    pjmedia_dir		 dir;
+    int			 play_id;
+    int			 rec_id;
+    int			 bytes_per_sample;
+    pj_uint32_t		 samples_per_sec;
+    unsigned		 samples_per_frame;
+    int			 channel_count;
+
+    PaStream		*rec_strm;
+    PaStream		*play_strm;
+
+    void		*user_data;
+    pjmedia_aud_rec_cb   rec_cb;
+    pjmedia_aud_play_cb  play_cb;
+
+    pj_timestamp	 play_timestamp;
+    pj_timestamp	 rec_timestamp;
+    pj_uint32_t		 underflow;
+    pj_uint32_t		 overflow;
+
+    pj_bool_t		 quit_flag;
+
+    pj_bool_t		 rec_thread_exited;
+    pj_bool_t		 rec_thread_initialized;
+    pj_thread_desc	 rec_thread_desc;
+    pj_thread_t		*rec_thread;
+
+    pj_bool_t		 play_thread_exited;
+    pj_bool_t		 play_thread_initialized;
+    pj_thread_desc	 play_thread_desc;
+    pj_thread_t		*play_thread;
+
+    /* Sometime the record callback does not return framesize as configured
+     * (e.g: in OSS), while this module must guarantee returning framesize
+     * as configured in the creation settings. In this case, we need a buffer 
+     * for the recorded samples.
+     */
+    pj_int16_t		*rec_buf;
+    unsigned		 rec_buf_count;
+
+    /* Sometime the player callback does not request framesize as configured
+     * (e.g: in Linux OSS) while sound device will always get samples from 
+     * the other component as many as configured samples_per_frame. 
+     */
+    pj_int16_t		*play_buf;
+    unsigned		 play_buf_count;
+};
+
+
+/* Factory prototypes */
+static pj_status_t  pa_init(pjmedia_aud_dev_factory *f);
+static pj_status_t  pa_destroy(pjmedia_aud_dev_factory *f);
+static unsigned	    pa_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t  pa_get_dev_info(pjmedia_aud_dev_factory *f, 
+				    unsigned index,
+				    pjmedia_aud_dev_info *info);
+static pj_status_t  pa_default_param(pjmedia_aud_dev_factory *f,
+				     unsigned index,
+				     pjmedia_aud_param *param);
+static pj_status_t  pa_create_stream(pjmedia_aud_dev_factory *f,
+				     const pjmedia_aud_param *param,
+				     pjmedia_aud_rec_cb rec_cb,
+				     pjmedia_aud_play_cb play_cb,
+				     void *user_data,
+				     pjmedia_aud_stream **p_aud_strm);
+
+/* Stream prototypes */
+static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
+				  pjmedia_aud_param *param);
+static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
+	 		        pjmedia_aud_dev_cap cap,
+			        void *value);
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+			        pjmedia_aud_dev_cap cap,
+			        const void *value);
+static pj_status_t strm_start(pjmedia_aud_stream *strm);
+static pj_status_t strm_stop(pjmedia_aud_stream *strm);
+static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
+
+
+static pjmedia_aud_dev_factory_op pa_op = 
+{
+    &pa_init,
+    &pa_destroy,
+    &pa_get_dev_count,
+    &pa_get_dev_info,
+    &pa_default_param,
+    &pa_create_stream
+};
+
+static pjmedia_aud_stream_op pa_strm_op = 
+{
+    &strm_get_param,
+    &strm_get_cap,
+    &strm_set_cap,
+    &strm_start,
+    &strm_stop,
+    &strm_destroy
+};
+
+
+
+static int PaRecorderCallback(const void *input, 
+			      void *output,
+			      unsigned long frameCount,
+			      const PaStreamCallbackTimeInfo* timeInfo,
+			      PaStreamCallbackFlags statusFlags,
+			      void *userData )
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
+    pj_status_t status = 0;
+    unsigned nsamples;
+
+    PJ_UNUSED_ARG(output);
+    PJ_UNUSED_ARG(timeInfo);
+
+    if (stream->quit_flag)
+	goto on_break;
+
+    if (input == NULL)
+	return paContinue;
+
+    /* Known cases of callback's thread:
+     * - The thread may be changed in the middle of a session, e.g: in MacOS 
+     *   it happens when plugging/unplugging headphone.
+     * - The same thread may be reused in consecutive sessions. The first
+     *   session will leave TLS set, but release the TLS data address,
+     *   so the second session must re-register the callback's thread.
+     */
+    if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered()) 
+    {
+	status = pj_thread_register("pa_rec", stream->rec_thread_desc, 
+				    &stream->rec_thread);
+	stream->rec_thread_initialized = 1;
+	PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
+    }
+
+    if (statusFlags & paInputUnderflow)
+	++stream->underflow;
+    if (statusFlags & paInputOverflow)
+	++stream->overflow;
+
+    /* Calculate number of samples we've got */
+    nsamples = frameCount * stream->channel_count + stream->rec_buf_count;
+
+    if (nsamples >= stream->samples_per_frame) 
+    {
+	/* If buffer is not empty, combine the buffer with the just incoming
+	 * samples, then call put_frame.
+	 */
+	if (stream->rec_buf_count) {
+	    unsigned chunk_count = 0;
+	    pjmedia_frame frame;
+	
+	    chunk_count = stream->samples_per_frame - stream->rec_buf_count;
+	    pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
+				 (pj_int16_t*)input, chunk_count);
+
+	    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    frame.buf = (void*) stream->rec_buf;
+	    frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+	    frame.timestamp.u64 = stream->rec_timestamp.u64;
+	    frame.bit_info = 0;
+
+	    status = (*stream->rec_cb)(stream->user_data, &frame);
+
+	    input = (pj_int16_t*) input + chunk_count;
+	    nsamples -= stream->samples_per_frame;
+	    stream->rec_buf_count = 0;
+	    stream->rec_timestamp.u64 += stream->samples_per_frame /
+					 stream->channel_count;
+	}
+
+	/* Give all frames we have */
+	while (nsamples >= stream->samples_per_frame && status == 0) {
+	    pjmedia_frame frame;
+
+	    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    frame.buf = (void*) input;
+	    frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+	    frame.timestamp.u64 = stream->rec_timestamp.u64;
+	    frame.bit_info = 0;
+
+	    status = (*stream->rec_cb)(stream->user_data, &frame);
+
+	    input = (pj_int16_t*) input + stream->samples_per_frame;
+	    nsamples -= stream->samples_per_frame;
+	    stream->rec_timestamp.u64 += stream->samples_per_frame /
+					 stream->channel_count;
+	}
+
+	/* Store the remaining samples into the buffer */
+	if (nsamples && status == 0) {
+	    stream->rec_buf_count = nsamples;
+	    pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input, 
+			         nsamples);
+	}
+
+    } else {
+	/* Not enough samples, let's just store them in the buffer */
+	pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
+			     (pj_int16_t*)input, 
+			     frameCount * stream->channel_count);
+	stream->rec_buf_count += frameCount * stream->channel_count;
+    }
+
+    if (status==0) 
+	return paContinue;
+
+on_break:
+    stream->rec_thread_exited = 1;
+    return paAbort;
+}
+
+static int PaPlayerCallback( const void *input, 
+			     void *output,
+			     unsigned long frameCount,
+			     const PaStreamCallbackTimeInfo* timeInfo,
+			     PaStreamCallbackFlags statusFlags,
+			     void *userData )
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
+    pj_status_t status = 0;
+    unsigned nsamples_req = frameCount * stream->channel_count;
+
+    PJ_UNUSED_ARG(input);
+    PJ_UNUSED_ARG(timeInfo);
+
+    if (stream->quit_flag)
+	goto on_break;
+
+    if (output == NULL)
+	return paContinue;
+
+    /* Known cases of callback's thread:
+     * - The thread may be changed in the middle of a session, e.g: in MacOS 
+     *   it happens when plugging/unplugging headphone.
+     * - The same thread may be reused in consecutive sessions. The first
+     *   session will leave TLS set, but release the TLS data address,
+     *   so the second session must re-register the callback's thread.
+     */
+    if (stream->play_thread_initialized == 0 || !pj_thread_is_registered()) 
+    {
+	status = pj_thread_register("portaudio", stream->play_thread_desc,
+				    &stream->play_thread);
+	stream->play_thread_initialized = 1;
+	PJ_LOG(5,(THIS_FILE, "Player thread started"));
+    }
+
+    if (statusFlags & paOutputUnderflow)
+	++stream->underflow;
+    if (statusFlags & paOutputOverflow)
+	++stream->overflow;
+
+
+    /* Check if any buffered samples */
+    if (stream->play_buf_count) {
+	/* samples buffered >= requested by sound device */
+	if (stream->play_buf_count >= nsamples_req) {
+	    pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
+				 nsamples_req);
+	    stream->play_buf_count -= nsamples_req;
+	    pjmedia_move_samples(stream->play_buf, 
+				 stream->play_buf + nsamples_req,
+				 stream->play_buf_count);
+	    nsamples_req = 0;
+	    
+	    return paContinue;
+	}
+
+	/* samples buffered < requested by sound device */
+	pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
+			     stream->play_buf_count);
+	nsamples_req -= stream->play_buf_count;
+	output = (pj_int16_t*)output + stream->play_buf_count;
+	stream->play_buf_count = 0;
+    }
+
+    /* Fill output buffer as requested */
+    while (nsamples_req && status == 0) {
+	if (nsamples_req >= stream->samples_per_frame) {
+	    pjmedia_frame frame;
+
+	    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    frame.buf = output;
+	    frame.size = stream->samples_per_frame *  stream->bytes_per_sample;
+	    frame.timestamp.u64 = stream->play_timestamp.u64;
+	    frame.bit_info = 0;
+
+	    status = (*stream->play_cb)(stream->user_data, &frame);
+	    if (status != PJ_SUCCESS)
+		goto on_break;
+
+	    if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+		pj_bzero(frame.buf, frame.size);
+
+	    nsamples_req -= stream->samples_per_frame;
+	    output = (pj_int16_t*)output + stream->samples_per_frame;
+	} else {
+	    pjmedia_frame frame;
+
+	    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    frame.buf = stream->play_buf;
+	    frame.size = stream->samples_per_frame *  stream->bytes_per_sample;
+	    frame.timestamp.u64 = stream->play_timestamp.u64;
+	    frame.bit_info = 0;
+
+	    status = (*stream->play_cb)(stream->user_data, &frame);
+	    if (status != PJ_SUCCESS)
+		goto on_break;
+
+	    if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+		pj_bzero(frame.buf, frame.size);
+
+	    pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
+				 nsamples_req);
+	    stream->play_buf_count = stream->samples_per_frame - nsamples_req;
+	    pjmedia_move_samples(stream->play_buf, 
+				 stream->play_buf+nsamples_req,
+				 stream->play_buf_count);
+	    nsamples_req = 0;
+	}
+
+	stream->play_timestamp.u64 += stream->samples_per_frame /
+				      stream->channel_count;
+    }
+    
+    if (status==0) 
+	return paContinue;
+
+on_break:
+    stream->play_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;
+}
+
+/* Logging callback from PA */
+static void pa_log_cb(const char *log)
+{
+    PJ_LOG(5,(THIS_FILE, "PA message: %s", log));
+}
+
+/* We should include pa_debugprint.h for this, but the header
+ * is not available publicly. :(
+ */
+typedef void (*PaUtilLogCallback ) (const char *log);
+void PaUtil_SetDebugPrintFunction(PaUtilLogCallback  cb);
+
+
+/*
+ * Init PortAudio audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf)
+{
+    struct pa_aud_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "portaudio", 64, 64, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct pa_aud_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &pa_op;
+
+    return &f->base;
+}
+
+
+/* API: Init factory */
+static pj_status_t pa_init(pjmedia_aud_dev_factory *f)
+{
+    int err;
+
+    PJ_UNUSED_ARG(f);
+
+    PaUtil_SetDebugPrintFunction(&pa_log_cb);
+
+    err = Pa_Initialize();
+
+    PJ_LOG(4,(THIS_FILE, 
+	      "PortAudio sound library initialized, status=%d", err));
+    PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
+			 Pa_GetHostApiCount()));
+    PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
+			 pa_get_dev_count(f)));
+
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: Destroy factory */
+static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f)
+{
+    struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
+    pj_pool_t *pool;
+    int err;
+
+    PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
+
+    err = Pa_Terminate();
+
+    pool = pa->pool;
+    pa->pool = NULL;
+    pj_pool_release(pool);
+    
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: Get device count. */
+static unsigned	pa_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+    int count = Pa_GetDeviceCount();
+    PJ_UNUSED_ARG(f);
+    return count < 0 ? 0 : count;
+}
+
+
+/* API: Get device info. */
+static pj_status_t  pa_get_dev_info(pjmedia_aud_dev_factory *f, 
+				    unsigned index,
+				    pjmedia_aud_dev_info *info)
+{
+    const PaDeviceInfo *pa_info;
+
+    PJ_UNUSED_ARG(f);
+
+    pa_info = Pa_GetDeviceInfo(index);
+    if (!pa_info)
+	return PJMEDIA_EAUD_INVDEV;
+
+    pj_bzero(info, sizeof(*info));
+    strncpy(info->name, pa_info->name, sizeof(info->name));
+    info->name[sizeof(info->name)-1] = '\0';
+    info->input_count = pa_info->maxInputChannels;
+    info->output_count = pa_info->maxOutputChannels;
+    info->default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
+    strncpy(info->driver, DRIVER_NAME, sizeof(info->driver));
+    info->driver[sizeof(info->driver)-1] = '\0';
+    info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+		 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: fill in with default parameter. */
+static pj_status_t  pa_default_param(pjmedia_aud_dev_factory *f,
+				     unsigned index,
+				     pjmedia_aud_param *param)
+{
+    pjmedia_aud_dev_info adi;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(f);
+
+    status = pa_get_dev_info(f, index, &adi);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_bzero(param, sizeof(*param));
+    if (adi.input_count && adi.output_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+	param->rec_id = index;
+	param->play_id = index;
+    } else if (adi.input_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE;
+	param->rec_id = index;
+	param->play_id = PJMEDIA_AUD_INVALID_DEV;
+    } else if (adi.output_count) {
+	param->dir = PJMEDIA_DIR_PLAYBACK;
+	param->play_id = index;
+	param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+    } else {
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    param->clock_rate = adi.default_samples_per_sec;
+    param->channel_count = 1;
+    param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
+    param->bits_per_sample = 16;
+    param->flags = adi.caps;
+    param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+    param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Internal: Get PortAudio default input device ID */
+static int pa_get_default_input_dev(int channel_count)
+{
+    int i, count;
+
+    /* Special for Windows - try to use the DirectSound implementation
+     * first since it provides better latency.
+     */
+#if PJMEDIA_PREFER_DIRECT_SOUND
+    if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
+	const PaHostApiInfo *pHI;
+	int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
+	pHI = Pa_GetHostApiInfo(index);
+	if (pHI) {
+	    const PaDeviceInfo *paDevInfo = NULL;
+	    paDevInfo = Pa_GetDeviceInfo(pHI->defaultInputDevice);
+	    if (paDevInfo && paDevInfo->maxInputChannels >= channel_count)
+		return pHI->defaultInputDevice;
+	}
+    }
+#endif
+
+    /* Enumerate the host api's for the default devices, and return
+     * the device with suitable channels.
+     */
+    count = Pa_GetHostApiCount();
+    for (i=0; i < count; ++i) {
+	const PaHostApiInfo *pHAInfo;
+
+	pHAInfo = Pa_GetHostApiInfo(i);
+	if (!pHAInfo)
+	    continue;
+
+	if (pHAInfo->defaultInputDevice >= 0) {
+	    const PaDeviceInfo *paDevInfo;
+
+	    paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice);
+
+	    if (paDevInfo->maxInputChannels >= channel_count)
+		return pHAInfo->defaultInputDevice;
+	}
+    }
+
+    /* If still no device is found, enumerate all devices */
+    count = Pa_GetDeviceCount();
+    for (i=0; i<count; ++i) {
+	const PaDeviceInfo *paDevInfo;
+
+	paDevInfo = Pa_GetDeviceInfo(i);
+	if (paDevInfo->maxInputChannels >= channel_count)
+	    return i;
+    }
+    
+    return -1;
+}
+
+/* Internal: Get PortAudio default output device ID */
+static int pa_get_default_output_dev(int channel_count)
+{
+    int i, count;
+
+    /* Special for Windows - try to use the DirectSound implementation
+     * first since it provides better latency.
+     */
+#if PJMEDIA_PREFER_DIRECT_SOUND
+    if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
+	const PaHostApiInfo *pHI;
+	int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
+	pHI = Pa_GetHostApiInfo(index);
+	if (pHI) {
+	    const PaDeviceInfo *paDevInfo = NULL;
+	    paDevInfo = Pa_GetDeviceInfo(pHI->defaultOutputDevice);
+	    if (paDevInfo && paDevInfo->maxOutputChannels >= channel_count)
+		return pHI->defaultOutputDevice;
+	}
+    }
+#endif
+
+    /* Enumerate the host api's for the default devices, and return
+     * the device with suitable channels.
+     */
+    count = Pa_GetHostApiCount();
+    for (i=0; i < count; ++i) {
+	const PaHostApiInfo *pHAInfo;
+
+	pHAInfo = Pa_GetHostApiInfo(i);
+	if (!pHAInfo)
+	    continue;
+
+	if (pHAInfo->defaultOutputDevice >= 0) {
+	    const PaDeviceInfo *paDevInfo;
+
+	    paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice);
+
+	    if (paDevInfo->maxOutputChannels >= channel_count)
+		return pHAInfo->defaultOutputDevice;
+	}
+    }
+
+    /* If still no device is found, enumerate all devices */
+    count = Pa_GetDeviceCount();
+    for (i=0; i<count; ++i) {
+	const PaDeviceInfo *paDevInfo;
+
+	paDevInfo = Pa_GetDeviceInfo(i);
+	if (paDevInfo->maxOutputChannels >= channel_count)
+	    return i;
+    }
+
+    return -1;
+}
+
+
+/* Internal: create capture/recorder stream */
+static pj_status_t create_rec_stream( struct pa_aud_factory *pa,
+				      const pjmedia_aud_param *param,
+				      pjmedia_aud_rec_cb rec_cb,
+				      void *user_data,
+				      pjmedia_aud_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_aud_dev_index rec_id;
+    struct pa_aud_stream *stream;
+    PaStreamParameters inputParam;
+    int sampleFormat;
+    const PaDeviceInfo *paDevInfo = NULL;
+    const PaHostApiInfo *paHostApiInfo = NULL;
+    unsigned paFrames, paRate, paLatency;
+    const PaStreamInfo *paSI;
+    PaError err;
+
+    PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
+
+    rec_id = param->rec_id;
+    if (rec_id < 0) {
+	rec_id = pa_get_default_input_dev(param->channel_count);
+	if (rec_id < 0) {
+	    /* No such device. */
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    }
+
+    paDevInfo = Pa_GetDeviceInfo(rec_id);
+    if (!paDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    if (param->bits_per_sample == 8)
+	sampleFormat = paUInt8;
+    else if (param->bits_per_sample == 16)
+	sampleFormat = paInt16;
+    else if (param->bits_per_sample == 32)
+	sampleFormat = paInt32;
+    else
+	return PJMEDIA_EAUD_SAMPFORMAT;
+    
+    pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+    stream->pool = pool;
+    pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
+    stream->dir = PJMEDIA_DIR_CAPTURE;
+    stream->rec_id = rec_id;
+    stream->play_id = -1;
+    stream->user_data = user_data;
+    stream->samples_per_sec = param->clock_rate;
+    stream->samples_per_frame = param->samples_per_frame;
+    stream->bytes_per_sample = param->bits_per_sample / 8;
+    stream->channel_count = param->channel_count;
+    stream->rec_cb = rec_cb;
+
+    stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, 
+		      stream->samples_per_frame * stream->bytes_per_sample);
+    stream->rec_buf_count = 0;
+
+    pj_bzero(&inputParam, sizeof(inputParam));
+    inputParam.device = rec_id;
+    inputParam.channelCount = param->channel_count;
+    inputParam.hostApiSpecificStreamInfo = NULL;
+    inputParam.sampleFormat = sampleFormat;
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+	inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
+    else
+	inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
+
+    paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
+
+    /* Frames in PortAudio is number of samples in a single channel */
+    paFrames = param->samples_per_frame / param->channel_count;
+
+    err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
+			 param->clock_rate, paFrames, 
+			 paClipOff, &PaRecorderCallback, stream );
+    if (err != paNoError) {
+	pj_pool_release(pool);
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+    }
+
+    paSI = Pa_GetStreamInfo(stream->rec_strm);
+    paRate = (unsigned)paSI->sampleRate;
+    paLatency = (unsigned)(paSI->inputLatency * 1000);
+
+    PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
+			 "rate=%d, ch=%d, "
+			 "bits=%d, %d samples per frame, latency=%d ms",
+			 paDevInfo->name, paHostApiInfo->name,
+			 paRate, param->channel_count,
+			 param->bits_per_sample, param->samples_per_frame,
+			 paLatency));
+
+    *p_snd_strm = &stream->base;
+    return PJ_SUCCESS;
+}
+
+
+/* Internal: create playback stream */
+static pj_status_t create_play_stream(struct pa_aud_factory *pa,
+				      const pjmedia_aud_param *param,
+				      pjmedia_aud_play_cb play_cb,
+				      void *user_data,
+				      pjmedia_aud_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_aud_dev_index play_id;
+    struct pa_aud_stream *stream;
+    PaStreamParameters outputParam;
+    int sampleFormat;
+    const PaDeviceInfo *paDevInfo = NULL;
+    const PaHostApiInfo *paHostApiInfo = NULL;
+    const PaStreamInfo *paSI;
+    unsigned paFrames, paRate, paLatency;
+    PaError err;
+
+    PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
+
+    play_id = param->play_id;
+    if (play_id < 0) {
+	play_id = pa_get_default_output_dev(param->channel_count);
+	if (play_id < 0) {
+	    /* No such device. */
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    } 
+
+    paDevInfo = Pa_GetDeviceInfo(play_id);
+    if (!paDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    if (param->bits_per_sample == 8)
+	sampleFormat = paUInt8;
+    else if (param->bits_per_sample == 16)
+	sampleFormat = paInt16;
+    else if (param->bits_per_sample == 32)
+	sampleFormat = paInt32;
+    else
+	return PJMEDIA_EAUD_SAMPFORMAT;
+    
+    pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+    stream->pool = pool;
+    pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
+    stream->dir = PJMEDIA_DIR_PLAYBACK;
+    stream->play_id = play_id;
+    stream->rec_id = -1;
+    stream->user_data = user_data;
+    stream->samples_per_sec = param->clock_rate;
+    stream->samples_per_frame = param->samples_per_frame;
+    stream->bytes_per_sample = param->bits_per_sample / 8;
+    stream->channel_count = param->channel_count;
+    stream->play_cb = play_cb;
+
+    stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, 
+					    stream->samples_per_frame * 
+					    stream->bytes_per_sample);
+    stream->play_buf_count = 0;
+
+    pj_bzero(&outputParam, sizeof(outputParam));
+    outputParam.device = play_id;
+    outputParam.channelCount = param->channel_count;
+    outputParam.hostApiSpecificStreamInfo = NULL;
+    outputParam.sampleFormat = sampleFormat;
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+	outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
+    else
+	outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
+
+    paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
+
+    /* Frames in PortAudio is number of samples in a single channel */
+    paFrames = param->samples_per_frame / param->channel_count;
+
+    err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
+			 param->clock_rate,  paFrames, 
+			 paClipOff, &PaPlayerCallback, stream );
+    if (err != paNoError) {
+	pj_pool_release(pool);
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+    }
+
+    paSI = Pa_GetStreamInfo(stream->play_strm);
+    paRate = (unsigned)(paSI->sampleRate);
+    paLatency = (unsigned)(paSI->outputLatency * 1000);
+
+    PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
+			 ", ch=%d, "
+			 "bits=%d, %d samples per frame, latency=%d ms",
+			 play_id, paDevInfo->name, paHostApiInfo->name, 
+			 paRate, param->channel_count,
+		 	 param->bits_per_sample, param->samples_per_frame, 
+			 paLatency));
+
+    *p_snd_strm = &stream->base;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Internal: Create both player and recorder stream */
+static pj_status_t create_bidir_stream(struct pa_aud_factory *pa,
+				       const pjmedia_aud_param *param,
+				       pjmedia_aud_rec_cb rec_cb,
+				       pjmedia_aud_play_cb play_cb,
+				       void *user_data,
+				       pjmedia_aud_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_aud_dev_index rec_id, play_id;
+    struct pa_aud_stream *stream;
+    PaStream *paStream = NULL;
+    PaStreamParameters inputParam;
+    PaStreamParameters outputParam;
+    int sampleFormat;
+    const PaDeviceInfo *paRecDevInfo = NULL;
+    const PaDeviceInfo *paPlayDevInfo = NULL;
+    const PaHostApiInfo *paRecHostApiInfo = NULL;
+    const PaHostApiInfo *paPlayHostApiInfo = NULL;
+    const PaStreamInfo *paSI;
+    unsigned paFrames, paRate, paInputLatency, paOutputLatency;
+    PaError err;
+
+    PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL);
+
+    rec_id = param->rec_id;
+    if (rec_id < 0) {
+	rec_id = pa_get_default_input_dev(param->channel_count);
+	if (rec_id < 0) {
+	    /* No such device. */
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    }
+
+    paRecDevInfo = Pa_GetDeviceInfo(rec_id);
+    if (!paRecDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    play_id = param->play_id;
+    if (play_id < 0) {
+	play_id = pa_get_default_output_dev(param->channel_count);
+	if (play_id < 0) {
+	    /* No such device. */
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    } 
+
+    paPlayDevInfo = Pa_GetDeviceInfo(play_id);
+    if (!paPlayDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+
+    if (param->bits_per_sample == 8)
+	sampleFormat = paUInt8;
+    else if (param->bits_per_sample == 16)
+	sampleFormat = paInt16;
+    else if (param->bits_per_sample == 32)
+	sampleFormat = paInt32;
+    else
+	return PJMEDIA_EAUD_SAMPFORMAT;
+    
+    pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+    stream->pool = pool;
+    pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
+    stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    stream->play_id = play_id;
+    stream->rec_id = rec_id;
+    stream->user_data = user_data;
+    stream->samples_per_sec = param->clock_rate;
+    stream->samples_per_frame = param->samples_per_frame;
+    stream->bytes_per_sample = param->bits_per_sample / 8;
+    stream->channel_count = param->channel_count;
+    stream->rec_cb = rec_cb;
+    stream->play_cb = play_cb;
+
+    stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, 
+		      stream->samples_per_frame * stream->bytes_per_sample);
+    stream->rec_buf_count = 0;
+
+    stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, 
+		       stream->samples_per_frame * stream->bytes_per_sample);
+    stream->play_buf_count = 0;
+
+    pj_bzero(&inputParam, sizeof(inputParam));
+    inputParam.device = rec_id;
+    inputParam.channelCount = param->channel_count;
+    inputParam.hostApiSpecificStreamInfo = NULL;
+    inputParam.sampleFormat = sampleFormat;
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+	inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
+    else
+	inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
+
+    paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
+
+    pj_bzero(&outputParam, sizeof(outputParam));
+    outputParam.device = play_id;
+    outputParam.channelCount = param->channel_count;
+    outputParam.hostApiSpecificStreamInfo = NULL;
+    outputParam.sampleFormat = sampleFormat;
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+	outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
+    else
+	outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
+
+    paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
+
+    /* Frames in PortAudio is number of samples in a single channel */
+    paFrames = param->samples_per_frame / param->channel_count;
+
+    /* If both input and output are on the same device, open a single stream
+     * for both input and output.
+     */
+    if (rec_id == play_id) {
+	err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
+			     param->clock_rate, paFrames, 
+			     paClipOff, &PaRecorderPlayerCallback, stream );
+	if (err == paNoError) {
+	    /* Set play stream and record stream to the same stream */
+	    stream->play_strm = stream->rec_strm = paStream;
+	}
+    } else {
+	err = -1;
+    }
+
+    /* .. otherwise if input and output are on the same device, OR if we're
+     * unable to open a bidirectional stream, then open two separate
+     * input and output stream.
+     */
+    if (paStream == NULL) {
+	/* Open input stream */
+	err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
+			     param->clock_rate, paFrames, 
+			     paClipOff, &PaRecorderCallback, stream );
+	if (err == paNoError) {
+	    /* Open output stream */
+	    err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
+				 param->clock_rate, paFrames, 
+				 paClipOff, &PaPlayerCallback, stream );
+	    if (err != paNoError)
+		Pa_CloseStream(stream->rec_strm);
+	}
+    }
+
+    if (err != paNoError) {
+	pj_pool_release(pool);
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+    }
+
+    paSI = Pa_GetStreamInfo(stream->rec_strm);
+    paRate = (unsigned)(paSI->sampleRate);
+    paInputLatency = (unsigned)(paSI->inputLatency * 1000);
+    paSI = Pa_GetStreamInfo(stream->play_strm);
+    paOutputLatency = (unsigned)(paSI->outputLatency * 1000);
+
+    PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and "
+			 "playback, sample rate=%d, ch=%d, "
+			 "bits=%d, %d samples per frame, input latency=%d ms, "
+			 "output latency=%d ms",
+			 paRecDevInfo->name, paRecHostApiInfo->name,
+			 paPlayDevInfo->name, paPlayHostApiInfo->name,
+			 paRate, param->channel_count,
+			 param->bits_per_sample, param->samples_per_frame,
+			 paInputLatency, paOutputLatency));
+
+    *p_snd_strm = &stream->base;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t  pa_create_stream(pjmedia_aud_dev_factory *f,
+				     const pjmedia_aud_param *param,
+				     pjmedia_aud_rec_cb rec_cb,
+				     pjmedia_aud_play_cb play_cb,
+				     void *user_data,
+				     pjmedia_aud_stream **p_aud_strm)
+{
+    struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
+    pj_status_t status;
+
+    if (param->dir == PJMEDIA_DIR_CAPTURE) {
+	status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm);
+    } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
+	status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm);
+    } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+	status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data, 
+				     p_aud_strm);
+    } else {
+	return PJ_EINVAL;
+    }
+
+    if (status != PJ_SUCCESS)
+	return status;
+
+    (*p_aud_strm)->op = &pa_strm_op;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Get stream parameters */
+static pj_status_t strm_get_param(pjmedia_aud_stream *s,
+				  pjmedia_aud_param *pi)
+{
+    struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
+    const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+    PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP);
+
+    if (strm->play_strm) {
+	paPlaySI = Pa_GetStreamInfo(strm->play_strm);
+    }
+    if (strm->rec_strm) {
+	paRecSI = Pa_GetStreamInfo(strm->rec_strm);
+    }
+
+    pj_bzero(pi, sizeof(*pi));
+    pi->dir = strm->dir;
+    pi->play_id = strm->play_id;
+    pi->rec_id = strm->rec_id;
+    pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate : 
+				paRecSI->sampleRate);
+    pi->channel_count = strm->channel_count;
+    pi->samples_per_frame = strm->samples_per_frame;
+    pi->bits_per_sample = strm->bytes_per_sample * 8;
+    if (paRecSI) {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+	pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency * 
+						    1000 : 0);
+    }
+    if (paPlaySI) {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+	pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency * 
+						     1000 : 0);
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: get capability */
+static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
+	 		        pjmedia_aud_dev_cap cap,
+			        void *pval)
+{
+    struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) {
+	const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm);
+	if (!si)
+	    return PJMEDIA_EAUD_SYSERR;
+
+	*(unsigned*)pval = (unsigned)(si->inputLatency * 1000);
+	return PJ_SUCCESS;
+    } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) {
+	const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm);
+	if (!si)
+	    return PJMEDIA_EAUD_SYSERR;
+
+	*(unsigned*)pval = (unsigned)(si->outputLatency * 1000);
+	return PJ_SUCCESS;
+    } else {
+	return PJMEDIA_EAUD_INVCAP;
+    }
+}
+
+
+/* API: set capability */
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+			        pjmedia_aud_dev_cap cap,
+			        const void *value)
+{
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(value);
+
+    /* Nothing is supported */
+    return PJMEDIA_EAUD_INVCAP;
+}
+
+
+/* API: start stream. */
+static pj_status_t strm_start(pjmedia_aud_stream *s)
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+    int err = 0;
+
+    PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
+
+    if (stream->play_strm)
+	err = Pa_StartStream(stream->play_strm);
+
+    if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) {
+	err = Pa_StartStream(stream->rec_strm);
+	if (err != 0)
+	    Pa_StopStream(stream->play_strm);
+    }
+
+    PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
+
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: stop stream. */
+static pj_status_t strm_stop(pjmedia_aud_stream *s)
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+    int i, err = 0;
+
+    stream->quit_flag = 1;
+    for (i=0; !stream->rec_thread_exited && i<100; ++i)
+	pj_thread_sleep(10);
+    for (i=0; !stream->play_thread_exited && i<100; ++i)
+	pj_thread_sleep(10);
+
+    pj_thread_sleep(1);
+
+    PJ_LOG(5,(THIS_FILE, "Stopping stream.."));
+
+    if (stream->play_strm)
+	err = Pa_StopStream(stream->play_strm);
+
+    if (stream->rec_strm && stream->rec_strm != stream->play_strm)
+	err = Pa_StopStream(stream->rec_strm);
+
+    stream->play_thread_initialized = 0;
+    stream->rec_thread_initialized = 0;
+
+    PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
+
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: destroy stream. */
+static pj_status_t strm_destroy(pjmedia_aud_stream *s)
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+    int i, err = 0;
+
+    stream->quit_flag = 1;
+    for (i=0; !stream->rec_thread_exited && i<100; ++i) {
+	pj_thread_sleep(1);
+    }
+    for (i=0; !stream->play_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));
+
+    if (stream->play_strm)
+	err = Pa_CloseStream(stream->play_strm);
+
+    if (stream->rec_strm && stream->rec_strm != stream->play_strm)
+	err = Pa_CloseStream(stream->rec_strm);
+
+    pj_pool_release(stream->pool);
+
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO */
+
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h	(revision 2506)
@@ -0,0 +1,171 @@
+#ifndef __BITSTREAM_H_
+#define __BITSTREAM_H_
+
+#define KPackedFrameLen 10
+#define KUnpackedFrameLen 22
+
+// Below values are taken from the APS design document
+const TUint8 KG729FullPayloadBits[] = { 8, 10, 8, 1, 13, 4, 7, 5, 13, 4, 7 };
+const TUint  KNumFullFrameParams = 11;
+const TUint8 KG729SIDPayloadBits[] = { 1, 5, 4, 5 };
+const TUint  KNumSIDFrameParams = 4;
+
+/*! 
+  @class TBitStream
+  
+  @discussion Provides compression from 16-bit-word-aligned G.729 audio frames
+  (used in S60 G.729 DSP codec) to 8-bit stream, and vice versa.
+  */
+class TBitStream
+  {        
+public:
+    /*!
+      @function TBitStream
+      
+      @discussion Constructor
+      */   
+    TBitStream():iDes(iData,KUnpackedFrameLen){}
+    /*!
+      @function CompressG729Frame
+      
+      @discussion Compress either a 22-byte G.729 full rate frame to 10 bytes
+      or a 8-byte G.729 Annex.B SID frame to 2 bytes.
+      @param aSrc Reference to the uncompressed source frame data
+      @param aIsSIDFrame True if the source is a SID frame
+      @result a reference to the compressed frame
+      */
+    const TDesC8& CompressG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame = EFalse );
+    
+    /*!
+      @function ExpandG729Frame
+            
+      @discussion Expand a 10-byte G.729 full rate frame to 22 bytes
+      or a 2-byte G.729 Annex.B SID frame to 8(22) bytes.
+      @param aSrc Reference to the compressed source frame data
+      @param aIsSIDFrame True if the source is a SID frame
+      @result a reference to a descriptor representing the uncompressed frame.
+      Note that SID frames are zero-padded to 22 bytes as well.
+      */
+    const TDesC8& ExpandG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame = EFalse );
+
+private:
+    void Compress( TUint8 aValue, TUint8 aNumOfBits );
+    void Expand( const TUint8* aSrc, TInt aDstIdx, TUint8 aNumOfBits );
+
+private:
+    TUint8      iData[KUnpackedFrameLen];
+    TPtr8       iDes;
+    TInt        iIdx;
+    TInt        iBitOffset;
+    };
+    
+
+const TDesC8& TBitStream::CompressG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame )
+    {
+    // reset data
+    iDes.FillZ(iDes.MaxLength());
+    iIdx = iBitOffset = 0;
+    
+    TInt numParams = (aIsSIDFrame) ? KNumSIDFrameParams : KNumFullFrameParams;
+    const TUint8* p = const_cast<TUint8*>(aSrc.Ptr());
+    
+    for(TInt i = 0, pIdx = 0; i < numParams; i++, pIdx += 2) 
+        {
+        TUint8 paramBits = (aIsSIDFrame) ? KG729SIDPayloadBits[i] : KG729FullPayloadBits[i];        
+        if(paramBits > 8)
+            {
+            Compress(p[pIdx+1], paramBits - 8); // msb
+            paramBits = 8;
+            }            
+        Compress(p[pIdx], paramBits); // lsb    
+        }
+
+    if( iBitOffset )
+        iIdx++;
+        
+    iDes.SetLength(iIdx);
+    return iDes;
+    }
+
+ 
+const TDesC8& TBitStream::ExpandG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame )
+    {
+    // reset data
+    iDes.FillZ(iDes.MaxLength());
+    iIdx = iBitOffset = 0;
+    
+    TInt numParams = (aIsSIDFrame) ? KNumSIDFrameParams : KNumFullFrameParams;
+    const TUint8* p = const_cast<TUint8*>(aSrc.Ptr());
+    
+    for(TInt i = 0, dIdx = 0; i < numParams; i++, dIdx += 2) 
+        {
+        TUint8 paramBits = (aIsSIDFrame) ? KG729SIDPayloadBits[i] : KG729FullPayloadBits[i];
+        if(paramBits > 8)
+          {
+          Expand(p, dIdx+1, paramBits - 8); // msb
+          paramBits = 8;
+          }    
+        Expand(p, dIdx, paramBits); // lsb 
+        }
+        
+    iDes.SetLength(KUnpackedFrameLen);
+    return iDes;
+    }
+
+
+void TBitStream::Compress( TUint8 aValue, TUint8 aNumOfBits )
+  {
+    // clear bits that will be discarded
+    aValue &= (0xff >> (8 - aNumOfBits));
+    
+    // calculate required bitwise left shift
+    TInt shl = 8 - (iBitOffset + aNumOfBits);
+    
+    if (shl == 0) // no shift required
+        { 
+        iData[iIdx++] |= aValue;
+        iBitOffset = 0;
+        }
+    else if (shl > 0) // bits fit into current byte
+        { 
+        iData[iIdx] |= (aValue << shl);
+        iBitOffset += aNumOfBits;
+        }        
+    else        
+        { 
+        iBitOffset = -shl;
+        iData[iIdx] |= (aValue >> iBitOffset); // right shift
+        iData[++iIdx] |= (aValue << (8-iBitOffset)); // push remaining bits to next byte
+        }
+  }
+    
+
+void TBitStream::Expand( const TUint8* aSrc, TInt aDstIdx, TUint8 aNumOfBits )
+  {    
+    TUint8 aValue = aSrc[iIdx] & (0xff >> iBitOffset);
+       
+    // calculate required bitwise right shift
+    TInt shr = 8 - (iBitOffset + aNumOfBits);
+    
+    if (shr == 0) // no shift required
+        { 
+        iData[aDstIdx] = aValue;
+        iIdx++;
+        iBitOffset = 0;
+        }
+    else if (shr > 0) // right shift
+        { 
+        iData[aDstIdx] = (aValue >> shr);
+        iBitOffset += aNumOfBits;
+        }        
+    else // shift left and take remaining bits from the next src byte
+        {
+        iBitOffset = -shr;
+        iData[aDstIdx] = aValue << iBitOffset;                
+        iData[aDstIdx] |= aSrc[++iIdx] >> (8 - iBitOffset);
+        }
+  }
+
+#endif // __BITSTREAM_H_
+    
+// eof
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp	(revision 2506)
@@ -0,0 +1,1611 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+
+#include <e32msgqueue.h>
+#include <sounddevice.h>
+#include <APSClientSession.h>
+#include <pjmedia-codec/amr_helper.h>
+
+/* Pack/unpack G.729 frame of S60 DSP codec, taken from:  
+ * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
+ */
+#include "s60_g729_bitstream.h"
+
+
+#define THIS_FILE			"symb_aps_dev.c"
+#define BITS_PER_SAMPLE			16
+
+#if 1
+#   define TRACE_(st) PJ_LOG(3, st)
+#else
+#   define TRACE_(st)
+#endif
+
+
+/* App UID to open global APS queues to communicate with the APS server. */
+extern TPtrC APP_UID;
+
+/* APS G.711 frame length */
+static pj_uint8_t aps_g711_frame_len;
+
+
+/* APS factory */
+struct aps_factory
+{
+    pjmedia_aud_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+    pjmedia_aud_dev_info	 dev_info;
+};
+
+
+/* Forward declaration of CPjAudioEngine */
+class CPjAudioEngine;
+
+
+/* APS stream. */
+struct aps_stream
+{
+    // Base
+    pjmedia_aud_stream	 base;			/**< Base class.	*/
+    
+    // Pool
+    pj_pool_t		*pool;			/**< Memory pool.       */
+
+    // Common settings.
+    pjmedia_aud_param param;		/**< Stream param.	*/
+    pjmedia_aud_rec_cb   rec_cb;		/**< Record callback.  	*/
+    pjmedia_aud_play_cb	 play_cb;		/**< Playback callback. */
+    void                *user_data;		/**< Application data.  */
+
+    // Audio engine
+    CPjAudioEngine	*engine;		/**< Internal engine.	*/
+
+    pj_timestamp  	 ts_play;		/**< Playback timestamp.*/
+    pj_timestamp	 ts_rec;		/**< Record timestamp.	*/
+
+    pj_int16_t		*play_buf;		/**< Playback buffer.	*/
+    pj_uint16_t		 play_buf_len;		/**< Playback buffer length. */
+    pj_uint16_t		 play_buf_start;	/**< Playback buffer start index. */
+    pj_int16_t		*rec_buf;		/**< Record buffer.	*/
+    pj_uint16_t		 rec_buf_len;		/**< Record buffer length. */
+    void                *strm_data;		/**< Stream data.	*/
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned    factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+				    pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+    &factory_init,
+    &factory_destroy,
+    &factory_get_dev_count,
+    &factory_get_dev_info,
+    &factory_default_param,
+    &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op = 
+{
+    &stream_get_param,
+    &stream_get_cap,
+    &stream_set_cap,
+    &stream_start,
+    &stream_stop,
+    &stream_destroy
+};
+
+
+/****************************************************************************
+ * Internal APS Engine
+ */
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc)
+{
+    PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
+}
+
+typedef void(*PjAudioCallback)(TAPSCommBuffer &buf, void *user_data);
+
+/**
+ * Abstract class for handler of callbacks from APS client.
+ */
+class MQueueHandlerObserver
+{
+public:
+    MQueueHandlerObserver(PjAudioCallback RecCb_, PjAudioCallback PlayCb_,
+			  void *UserData_)
+    : RecCb(RecCb_), PlayCb(PlayCb_), UserData(UserData_)
+    {}
+
+    virtual void InputStreamInitialized(const TInt aStatus) = 0;
+    virtual void OutputStreamInitialized(const TInt aStatus) = 0;
+    virtual void NotifyError(const TInt aError) = 0;
+
+public:
+    PjAudioCallback RecCb;
+    PjAudioCallback PlayCb;
+    void *UserData;
+};
+
+/**
+ * Handler for communication and data queue.
+ */
+class CQueueHandler : public CActive
+{
+public:
+    // Types of queue handler
+    enum TQueueHandlerType {
+        ERecordCommQueue,
+        EPlayCommQueue,
+        ERecordQueue,
+        EPlayQueue
+    };
+
+    // The order corresponds to the APS Server state, do not change!
+    enum TState {
+    	EAPSPlayerInitialize        = 1,
+    	EAPSRecorderInitialize      = 2,
+    	EAPSPlayData                = 3,
+    	EAPSRecordData              = 4,
+    	EAPSPlayerInitComplete      = 5,
+    	EAPSRecorderInitComplete    = 6
+    };
+
+    static CQueueHandler* NewL(MQueueHandlerObserver* aObserver,
+			       RMsgQueue<TAPSCommBuffer>* aQ,
+			       RMsgQueue<TAPSCommBuffer>* aWriteQ,
+			       TQueueHandlerType aType)
+    {
+	CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aWriteQ,
+							 aType);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+    }
+
+    // Destructor
+    ~CQueueHandler() { Cancel(); }
+
+    // Start listening queue event
+    void Start() {
+	iQ->NotifyDataAvailable(iStatus);
+	SetActive();
+    }
+
+private:
+    // Constructor
+    CQueueHandler(MQueueHandlerObserver* aObserver,
+		  RMsgQueue<TAPSCommBuffer>* aQ,
+		  RMsgQueue<TAPSCommBuffer>* aWriteQ,
+		  TQueueHandlerType aType)
+	: CActive(CActive::EPriorityHigh),
+	  iQ(aQ), iWriteQ(aWriteQ), iObserver(aObserver), iType(aType)
+    {
+	CActiveScheduler::Add(this);
+
+	// use lower priority for comm queues
+	if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue))
+	    SetPriority(CActive::EPriorityStandard);
+    }
+
+    // Second phase constructor
+    void ConstructL() {}
+
+    // Inherited from CActive
+    void DoCancel() { iQ->CancelDataAvailable(); }
+
+    void RunL() {
+	if (iStatus != KErrNone) {
+	    iObserver->NotifyError(iStatus.Int());
+	    return;
+        }
+
+	TAPSCommBuffer buffer;
+	TInt ret = iQ->Receive(buffer);
+
+	if (ret != KErrNone) {
+	    iObserver->NotifyError(ret);
+	    return;
+	}
+
+	switch (iType) {
+	case ERecordQueue:
+	    if (buffer.iCommand == EAPSRecordData) {
+		iObserver->RecCb(buffer, iObserver->UserData);
+	    } else {
+		iObserver->NotifyError(buffer.iStatus);
+	    }
+	    break;
+
+	// Callbacks from the APS main thread
+	case EPlayCommQueue:
+	    switch (buffer.iCommand) {
+		case EAPSPlayData:
+		    if (buffer.iStatus == KErrUnderflow) {
+			iObserver->PlayCb(buffer, iObserver->UserData);
+			iWriteQ->Send(buffer);
+		    }
+		    break;
+		case EAPSPlayerInitialize:
+		    iObserver->NotifyError(buffer.iStatus);
+		    break;
+		case EAPSPlayerInitComplete:
+		    iObserver->OutputStreamInitialized(buffer.iStatus);
+		    break;
+		case EAPSRecorderInitComplete:
+		    iObserver->InputStreamInitialized(buffer.iStatus);
+		    break;
+		default:
+		    iObserver->NotifyError(buffer.iStatus);
+		    break;
+	    }
+	    break;
+
+	// Callbacks from the APS recorder thread
+	case ERecordCommQueue:
+	    switch (buffer.iCommand) {
+		// The APS recorder thread will only report errors
+		// through this handler. All other callbacks will be
+		// sent from the APS main thread through EPlayCommQueue
+		case EAPSRecorderInitialize:
+		case EAPSRecordData:
+		default:
+		    iObserver->NotifyError(buffer.iStatus);
+		    break;
+	    }
+	    break;
+
+	default:
+	    break;
+        }
+
+        // issue next request
+        iQ->NotifyDataAvailable(iStatus);
+        SetActive();
+    }
+
+    TInt RunError(TInt) {
+	return 0;
+    }
+
+    // Data
+    RMsgQueue<TAPSCommBuffer>	*iQ;   // (not owned)
+    RMsgQueue<TAPSCommBuffer>	*iWriteQ;   // (not owned)
+    MQueueHandlerObserver	*iObserver; // (not owned)
+    TQueueHandlerType            iType;
+};
+
+/*
+ * Audio setting for CPjAudioEngine.
+ */
+class CPjAudioSetting
+{
+public:
+    TFourCC		 fourcc;
+    TAPSCodecMode	 mode;
+    TBool		 plc;
+    TBool		 vad;
+    TBool		 cng;
+    TBool		 loudspk;
+};
+
+/*
+ * Implementation: Symbian Input & Output Stream.
+ */
+class CPjAudioEngine : public CBase, MQueueHandlerObserver
+{
+public:
+    enum State
+    {
+	STATE_NULL,
+	STATE_INITIALIZING,
+	STATE_READY,
+	STATE_STREAMING,
+	STATE_PENDING_STOP
+    };
+
+    ~CPjAudioEngine();
+
+    static CPjAudioEngine *NewL(struct aps_stream *parent_strm,
+			        PjAudioCallback rec_cb,
+				PjAudioCallback play_cb,
+				void *user_data,
+				const CPjAudioSetting &setting);
+
+    TInt StartL();
+    void Stop();
+
+    TInt ActivateSpeaker(TBool active);
+    
+    TInt SetVolume(TInt vol) { return iSession.SetVolume(vol); }
+    TInt GetVolume() { return iSession.Volume(); }
+    TInt GetMaxVolume() { return iSession.MaxVolume(); }
+    
+    TInt SetGain(TInt gain) { return iSession.SetGain(gain); }
+    TInt GetGain() { return iSession.Gain(); }
+    TInt GetMaxGain() { return iSession.MaxGain(); }
+
+private:
+    CPjAudioEngine(struct aps_stream *parent_strm,
+		   PjAudioCallback rec_cb,
+		   PjAudioCallback play_cb,
+		   void *user_data,
+		   const CPjAudioSetting &setting);
+    void ConstructL();
+
+    TInt InitPlayL();
+    TInt InitRecL();
+    TInt StartStreamL();
+
+    // Inherited from MQueueHandlerObserver
+    virtual void InputStreamInitialized(const TInt aStatus);
+    virtual void OutputStreamInitialized(const TInt aStatus);
+    virtual void NotifyError(const TInt aError);
+
+    State			 state_;
+    struct aps_stream		*parentStrm_;
+    CPjAudioSetting		 setting_;
+
+    RAPSSession                  iSession;
+    TAPSInitSettings             iPlaySettings;
+    TAPSInitSettings             iRecSettings;
+
+    RMsgQueue<TAPSCommBuffer>    iReadQ;
+    RMsgQueue<TAPSCommBuffer>    iReadCommQ;
+    RMsgQueue<TAPSCommBuffer>    iWriteQ;
+    RMsgQueue<TAPSCommBuffer>    iWriteCommQ;
+
+    CQueueHandler		*iPlayCommHandler;
+    CQueueHandler		*iRecCommHandler;
+    CQueueHandler		*iRecHandler;
+};
+
+
+CPjAudioEngine* CPjAudioEngine::NewL(struct aps_stream *parent_strm,
+				     PjAudioCallback rec_cb,
+				     PjAudioCallback play_cb,
+				     void *user_data,
+				     const CPjAudioSetting &setting)
+{
+    CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
+						       rec_cb, play_cb,
+						       user_data,
+						       setting);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    CleanupStack::Pop(self);
+    return self;
+}
+
+CPjAudioEngine::CPjAudioEngine(struct aps_stream *parent_strm,
+			       PjAudioCallback rec_cb,
+			       PjAudioCallback play_cb,
+			       void *user_data,
+			       const CPjAudioSetting &setting)
+      : MQueueHandlerObserver(rec_cb, play_cb, user_data),
+	state_(STATE_NULL),
+	parentStrm_(parent_strm),
+	setting_(setting),
+	iPlayCommHandler(0),
+	iRecCommHandler(0),
+	iRecHandler(0)
+{
+}
+
+CPjAudioEngine::~CPjAudioEngine()
+{
+    Stop();
+
+    delete iRecHandler;
+    delete iPlayCommHandler;
+    delete iRecCommHandler;
+
+    // On some devices, immediate closing after stopping may cause APS server
+    // panic KERN-EXEC 0, so let's wait for sometime before really closing
+    // the client session.
+    TTime start, now;
+    enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
+    
+    start.UniversalTime();
+    do {
+	pj_symbianos_poll(-1, APS_CLOSE_WAIT_TIME);
+	now.UniversalTime();
+    } while (now.MicroSecondsFrom(start) < APS_CLOSE_WAIT_TIME * 1000);
+
+    iSession.Close();
+
+    if (state_ == STATE_READY) {
+	if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
+	    iReadQ.Close();
+	    iReadCommQ.Close();
+	}
+	iWriteQ.Close();
+	iWriteCommQ.Close();
+    }
+    
+    TRACE_((THIS_FILE, "Sound device destroyed"));
+}
+
+TInt CPjAudioEngine::InitPlayL()
+{
+    TInt err = iSession.InitializePlayer(iPlaySettings);
+    if (err != KErrNone) {
+	snd_perror("Failed to initialize player", err);
+	return err;
+    }
+
+    // Open message queues for the output stream
+    TBuf<128> buf2 = iPlaySettings.iGlobal;
+    buf2.Append(_L("PlayQueue"));
+    TBuf<128> buf3 = iPlaySettings.iGlobal;
+    buf3.Append(_L("PlayCommQueue"));
+
+    while (iWriteQ.OpenGlobal(buf2))
+	User::After(10);
+    while (iWriteCommQ.OpenGlobal(buf3))
+	User::After(10);
+
+    // Construct message queue handler
+    iPlayCommHandler = CQueueHandler::NewL(this, &iWriteCommQ, &iWriteQ,
+					   CQueueHandler::EPlayCommQueue);
+
+    // Start observing APS callbacks on output stream message queue
+    iPlayCommHandler->Start();
+
+    return 0;
+}
+
+TInt CPjAudioEngine::InitRecL()
+{
+    // Initialize input stream device
+    TInt err = iSession.InitializeRecorder(iRecSettings);
+    if (err != KErrNone && err != KErrAlreadyExists) {
+	snd_perror("Failed to initialize recorder", err);
+	return err;
+    }
+
+    TBuf<128> buf1 = iRecSettings.iGlobal;
+    buf1.Append(_L("RecordQueue"));
+    TBuf<128> buf4 = iRecSettings.iGlobal;
+    buf4.Append(_L("RecordCommQueue"));
+
+    // Must wait for APS thread to finish creating message queues
+    // before we can open and use them.
+    while (iReadQ.OpenGlobal(buf1))
+	User::After(10);
+    while (iReadCommQ.OpenGlobal(buf4))
+	User::After(10);
+
+    // Construct message queue handlers
+    iRecHandler = CQueueHandler::NewL(this, &iReadQ, NULL,
+				      CQueueHandler::ERecordQueue);
+    iRecCommHandler = CQueueHandler::NewL(this, &iReadCommQ, NULL,
+					  CQueueHandler::ERecordCommQueue);
+
+    // Start observing APS callbacks from on input stream message queue
+    iRecHandler->Start();
+    iRecCommHandler->Start();
+
+    return 0;
+}
+
+TInt CPjAudioEngine::StartL()
+{
+    if (state_ == STATE_READY)
+	return StartStreamL();
+
+    PJ_ASSERT_RETURN(state_ == STATE_NULL, PJMEDIA_EAUD_INVOP);
+    
+    // Even if only capturer are opened, playback thread of APS Server need
+    // to be run(?). Since some messages will be delivered via play comm queue.
+    state_ = STATE_INITIALIZING;
+
+    return InitPlayL();
+}
+
+void CPjAudioEngine::Stop()
+{
+    if (state_ == STATE_STREAMING) {
+	iSession.Stop();
+	state_ = STATE_READY;
+	TRACE_((THIS_FILE, "Sound device stopped"));
+    } else if (state_ == STATE_INITIALIZING) {
+	// Initialization is on progress, so let's set the state to 
+	// STATE_PENDING_STOP to prevent it starting the stream.
+	state_ = STATE_PENDING_STOP;
+	
+	// Then wait until initialization done.
+	while (state_ != STATE_READY)
+	    pj_symbianos_poll(-1, 100);
+    }
+}
+
+void CPjAudioEngine::ConstructL()
+{
+    // Recorder settings
+    iRecSettings.iFourCC		= setting_.fourcc;
+    iRecSettings.iGlobal		= APP_UID;
+    iRecSettings.iPriority		= TMdaPriority(100);
+    iRecSettings.iPreference		= TMdaPriorityPreference(0x05210001);
+    iRecSettings.iSettings.iChannels	= EMMFMono;
+    iRecSettings.iSettings.iSampleRate	= EMMFSampleRate8000Hz;
+
+    // Player settings
+    iPlaySettings.iFourCC		= setting_.fourcc;
+    iPlaySettings.iGlobal		= APP_UID;
+    iPlaySettings.iPriority		= TMdaPriority(100);
+    iPlaySettings.iPreference		= TMdaPriorityPreference(0x05220001);
+    iPlaySettings.iSettings.iChannels	= EMMFMono;
+    iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
+    iPlaySettings.iSettings.iVolume	= 0;
+
+    User::LeaveIfError(iSession.Connect());
+}
+
+TInt CPjAudioEngine::StartStreamL()
+{
+    pj_assert(state_==STATE_READY || state_==STATE_INITIALIZING); 
+    
+    iSession.SetCng(setting_.cng);
+    iSession.SetVadMode(setting_.vad);
+    iSession.SetPlc(setting_.plc);
+    iSession.SetEncoderMode(setting_.mode);
+    iSession.SetDecoderMode(setting_.mode);
+    iSession.ActivateLoudspeaker(setting_.loudspk);
+
+    // Not only capture
+    if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) {
+	iSession.Write();
+	TRACE_((THIS_FILE, "Player started"));
+    }
+
+    // Not only playback
+    if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
+	iSession.Read();
+	TRACE_((THIS_FILE, "Recorder started"));
+    }
+
+    state_ = STATE_STREAMING;
+    
+    return 0;
+}
+
+void CPjAudioEngine::InputStreamInitialized(const TInt aStatus)
+{
+    TRACE_((THIS_FILE, "Recorder initialized, err=%d", aStatus));
+
+    if (aStatus == KErrNone) {
+	// Don't start the stream since Stop() has been requested. 
+	if (state_ != STATE_PENDING_STOP) {
+	    StartStreamL();
+	} else {
+	    state_ = STATE_READY;
+	}
+    }
+}
+
+void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus)
+{
+    TRACE_((THIS_FILE, "Player initialized, err=%d", aStatus));
+
+    if (aStatus == KErrNone) {
+	if (parentStrm_->param.dir == PJMEDIA_DIR_PLAYBACK) {
+	    // Don't start the stream since Stop() has been requested.
+	    if (state_ != STATE_PENDING_STOP) {
+		StartStreamL();
+	    } else {
+		state_ = STATE_READY;
+	    }
+	} else
+	    InitRecL();
+    }
+}
+
+void CPjAudioEngine::NotifyError(const TInt aError)
+{
+    snd_perror("Error from CQueueHandler", aError);
+}
+
+TInt CPjAudioEngine::ActivateSpeaker(TBool active)
+{
+    if (state_ == STATE_READY || state_ == STATE_STREAMING) {
+        iSession.ActivateLoudspeaker(active);
+        TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off")));
+	return KErrNone;
+    }
+    return KErrNotReady;
+}
+
+
+
+static void RecCbPcm(TAPSCommBuffer &buf, void *user_data)
+{
+    struct aps_stream *strm = (struct aps_stream*) user_data;
+
+    /* Buffer has to contain normal speech. */
+    pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
+
+    /* Detect the recorder G.711 frame size, player frame size will follow
+     * this recorder frame size.
+     */
+    if (aps_g711_frame_len == 0) {
+	aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
+	TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
+		aps_g711_frame_len));
+    }
+
+    /* Decode APS buffer (coded in G.711) and put the PCM result into rec_buf.
+     * Whenever rec_buf is full, call parent stream callback.
+     */
+    unsigned dec_len = 0;
+
+    while (dec_len < aps_g711_frame_len) {
+	unsigned tmp;
+
+	tmp = PJ_MIN(strm->param.samples_per_frame - strm->rec_buf_len,
+		     aps_g711_frame_len - dec_len);
+	pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
+			    buf.iBuffer.Ptr() + 2 + dec_len,
+			    tmp);
+	strm->rec_buf_len += tmp;
+	dec_len += tmp;
+
+	pj_assert(strm->rec_buf_len <= strm->param.samples_per_frame);
+
+	if (strm->rec_buf_len == strm->param.samples_per_frame) {
+	    pjmedia_frame f;
+	    
+	    f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    f.buf = strm->rec_buf;
+	    f.size = strm->rec_buf_len << 1;
+	    
+	    strm->rec_cb(strm->user_data, &f);
+	    strm->rec_buf_len = 0;
+	}
+    }
+}
+
+static void PlayCbPcm(TAPSCommBuffer &buf, void *user_data)
+{
+    struct aps_stream *strm = (struct aps_stream*) user_data;
+    unsigned g711_frame_len = aps_g711_frame_len;
+
+    /* Init buffer attributes and header. */
+    buf.iCommand = CQueueHandler::EAPSPlayData;
+    buf.iStatus = 0;
+    buf.iBuffer.Zero();
+    buf.iBuffer.Append(1);
+    buf.iBuffer.Append(0);
+
+    /* Assume frame size is 10ms if frame size hasn't been known. */
+    if (g711_frame_len == 0)
+	g711_frame_len = 80;
+
+    /* Call parent stream callback to get PCM samples to play,
+     * encode the PCM samples into G.711 and put it into APS buffer.
+     */
+    unsigned enc_len = 0;
+    while (enc_len < g711_frame_len) {
+	if (strm->play_buf_len == 0) {
+	    pjmedia_frame f;
+	    
+	    f.buf = strm->play_buf;
+	    f.size = strm->param.samples_per_frame << 1;
+	    
+	    strm->play_cb(strm->user_data, &f);
+	    if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+		pjmedia_zero_samples(strm->play_buf, 
+				     strm->param.samples_per_frame);
+	    }
+	    
+	    strm->play_buf_len = strm->param.samples_per_frame;
+	    strm->play_buf_start = 0;
+	}
+
+	unsigned tmp;
+
+	tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - enc_len);
+	pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
+			    &strm->play_buf[strm->play_buf_start],
+			    tmp);
+	buf.iBuffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
+	enc_len += tmp;
+	strm->play_buf_len -= tmp;
+	strm->play_buf_start += tmp;
+    }
+}
+
+/****************************************************************************
+ * Internal APS callbacks
+ */
+
+static void RecCb(TAPSCommBuffer &buf, void *user_data)
+{
+    struct aps_stream *strm = (struct aps_stream*) user_data;
+    pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
+    
+    switch(strm->param.ext_fmt.id) {
+    case PJMEDIA_FORMAT_AMR:
+	{
+	    const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 1;
+	    unsigned len = buf.iBuffer.Length() - 1;
+	    
+	    pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
+	    if (frame->samples_cnt == strm->param.samples_per_frame) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_G729:
+	{
+	    /* Check if we got a normal or SID frame. */
+	    if (buf.iBuffer[0] != 0 || buf.iBuffer[1] != 0) {
+		enum { NORMAL_LEN = 22, SID_LEN = 8 };
+		TBitStream *bitstream = (TBitStream*)strm->strm_data;
+		unsigned src_len = buf.iBuffer.Length()- 2;
+		
+		pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
+
+		const TDesC8& p = bitstream->CompressG729Frame(
+					    buf.iBuffer.Right(src_len), 
+					    src_len == SID_LEN);
+		
+		pjmedia_frame_ext_append_subframe(frame, p.Ptr(), 
+						  p.Length() << 3, 80);
+	    } else { /* We got null frame. */
+		pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
+	    }
+	    
+	    if (frame->samples_cnt == strm->param.samples_per_frame) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+
+    case PJMEDIA_FORMAT_ILBC:
+	{
+	    unsigned samples_got;
+	    
+	    samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240;
+	    
+	    /* Check if we got a normal frame. */
+	    if (buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0) {
+		const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 2;
+		unsigned len = buf.iBuffer.Length() - 2;
+		
+		pjmedia_frame_ext_append_subframe(frame, p, len << 3,
+						  samples_got);
+	    } else { /* We got null frame. */
+		pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
+	    }
+	    
+	    if (frame->samples_cnt == strm->param.samples_per_frame) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_PCMU:
+    case PJMEDIA_FORMAT_PCMA:
+	{
+	    unsigned samples_processed = 0;
+	    
+	    /* Make sure it is normal frame. */
+	    pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
+
+	    /* Detect the recorder G.711 frame size, player frame size will 
+	     * follow this recorder frame size.
+	     */
+	    if (aps_g711_frame_len == 0) {
+		aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
+		TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
+			aps_g711_frame_len));
+	    }
+	    
+	    /* Convert APS buffer format into pjmedia_frame_ext. Whenever 
+	     * samples count in the frame is equal to stream's samples per 
+	     * frame, call parent stream callback.
+	     */
+	    while (samples_processed < aps_g711_frame_len) {
+		unsigned tmp;
+		const pj_uint8_t *pb = (const pj_uint8_t*)buf.iBuffer.Ptr() +
+				       2 + samples_processed;
+    
+		tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
+			     aps_g711_frame_len - samples_processed);
+		
+		pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
+		samples_processed += tmp;
+    
+		if (frame->samples_cnt == strm->param.samples_per_frame) {
+		    frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		    strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+		    frame->samples_cnt = 0;
+		    frame->subframe_cnt = 0;
+		}
+	    }
+	}
+	break;
+	
+    default:
+	break;
+    }
+}
+
+static void PlayCb(TAPSCommBuffer &buf, void *user_data)
+{
+    struct aps_stream *strm = (struct aps_stream*) user_data;
+    pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
+
+    /* Init buffer attributes and header. */
+    buf.iCommand = CQueueHandler::EAPSPlayData;
+    buf.iStatus = 0;
+    buf.iBuffer.Zero();
+
+    switch(strm->param.ext_fmt.id) {
+    case PJMEDIA_FORMAT_AMR:
+	{
+	    if (frame->samples_cnt == 0) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+		pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+			  frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+	    }
+
+	    if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { 
+		pjmedia_frame_ext_subframe *sf;
+		unsigned samples_cnt;
+		
+		sf = pjmedia_frame_ext_get_subframe(frame, 0);
+		samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+		
+		if (sf->data && sf->bitlen) {
+		    /* AMR header for APS is one byte, the format (may be!):
+		     * 0xxxxy00, where xxxx:frame type, y:not sure. 
+		     */
+		    unsigned len = (sf->bitlen+7)>>3;
+		    enum {SID_FT = 8 };
+		    pj_uint8_t amr_header = 4, ft = SID_FT;
+
+		    if (len >= pjmedia_codec_amrnb_framelen[0])
+			ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
+		    
+		    amr_header |= ft << 3;
+		    buf.iBuffer.Append(amr_header);
+		    
+		    buf.iBuffer.Append((TUint8*)sf->data, len);
+		} else {
+		    buf.iBuffer.Append(0);
+		}
+
+		pjmedia_frame_ext_pop_subframes(frame, 1);
+	    
+	    } else { /* PJMEDIA_FRAME_TYPE_NONE */
+		buf.iBuffer.Append(0);
+		
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_G729:
+	{
+	    if (frame->samples_cnt == 0) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+		pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+			  frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+	    }
+
+	    if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { 
+		pjmedia_frame_ext_subframe *sf;
+		unsigned samples_cnt;
+		
+		sf = pjmedia_frame_ext_get_subframe(frame, 0);
+		samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+		
+		if (sf->data && sf->bitlen) {
+		    enum { NORMAL_LEN = 10, SID_LEN = 2 };
+		    pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
+		    TBitStream *bitstream = (TBitStream*)strm->strm_data;
+		    const TPtrC8 src(sf->data, sf->bitlen>>3);
+		    const TDesC8 &dst = bitstream->ExpandG729Frame(src,
+								   sid_frame); 
+		    if (sid_frame) {
+			buf.iBuffer.Append(0);
+			buf.iBuffer.Append(1);
+		    } else {
+			buf.iBuffer.Append(1);
+			buf.iBuffer.Append(0);
+		    }
+		    buf.iBuffer.Append(dst);
+		} else {
+		    buf.iBuffer.Append(0);
+		    buf.iBuffer.Append(0);
+		}
+
+		pjmedia_frame_ext_pop_subframes(frame, 1);
+	    
+	    } else { /* PJMEDIA_FRAME_TYPE_NONE */
+		buf.iBuffer.Append(0);
+		buf.iBuffer.Append(0);
+		
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_ILBC:
+	{
+	    if (frame->samples_cnt == 0) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+		pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+			  frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+	    }
+
+	    if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { 
+		pjmedia_frame_ext_subframe *sf;
+		unsigned samples_cnt;
+		
+		sf = pjmedia_frame_ext_get_subframe(frame, 0);
+		samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+		
+		pj_assert((strm->param.ext_fmt.bitrate == 15200 && 
+			   samples_cnt == 160) ||
+			  (strm->param.ext_fmt.bitrate != 15200 &&
+			   samples_cnt == 240));
+		
+		if (sf->data && sf->bitlen) {
+		    buf.iBuffer.Append(1);
+		    buf.iBuffer.Append(0);
+		    buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+		} else {
+		    buf.iBuffer.Append(0);
+		    buf.iBuffer.Append(0);
+		}
+
+		pjmedia_frame_ext_pop_subframes(frame, 1);
+	    
+	    } else { /* PJMEDIA_FRAME_TYPE_NONE */
+		buf.iBuffer.Append(0);
+		buf.iBuffer.Append(0);
+		
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_PCMU:
+    case PJMEDIA_FORMAT_PCMA:
+	{
+	    unsigned samples_ready = 0;
+	    unsigned samples_req = aps_g711_frame_len;
+	    
+	    /* Assume frame size is 10ms if frame size hasn't been known. */
+	    if (samples_req == 0)
+		samples_req = 80;
+	    
+	    buf.iBuffer.Append(1);
+	    buf.iBuffer.Append(0);
+	    
+	    /* Call parent stream callback to get samples to play. */
+	    while (samples_ready < samples_req) {
+		if (frame->samples_cnt == 0) {
+		    frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		    strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+		    pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+			      frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+		}
+    
+		if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { 
+		    pjmedia_frame_ext_subframe *sf;
+		    unsigned samples_cnt;
+		    
+		    sf = pjmedia_frame_ext_get_subframe(frame, 0);
+		    samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+		    if (sf->data && sf->bitlen) {
+			buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+		    } else {
+			pj_uint8_t silc;
+			silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+				pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+			buf.iBuffer.AppendFill(silc, samples_cnt);
+		    }
+		    samples_ready += samples_cnt;
+		    
+		    pjmedia_frame_ext_pop_subframes(frame, 1);
+		
+		} else { /* PJMEDIA_FRAME_TYPE_NONE */
+		    pj_uint8_t silc;
+		    
+		    silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+			    pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+		    buf.iBuffer.AppendFill(silc, samples_req - samples_ready);
+
+		    samples_ready = samples_req;
+		    frame->samples_cnt = 0;
+		    frame->subframe_cnt = 0;
+		}
+	    }
+	}
+	break;
+	
+    default:
+	break;
+    }
+}
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * C compatible declaration of APS factory.
+ */
+PJ_BEGIN_DECL
+PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf);
+PJ_END_DECL
+
+/*
+ * Init APS audio driver.
+ */
+PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf)
+{
+    struct aps_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "APS", 1000, 1000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct aps_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+    struct aps_factory *af = (struct aps_factory*)f;
+
+    pj_ansi_strcpy(af->dev_info.name, "S60 APS");
+    af->dev_info.default_samples_per_sec = 8000;
+    af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
+			//PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
+			PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
+			PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
+			PJMEDIA_AUD_DEV_CAP_VAD |
+			PJMEDIA_AUD_DEV_CAP_CNG;
+    af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE | 
+			  PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+    af->dev_info.input_count = 1;
+    af->dev_info.output_count = 1;
+
+    af->dev_info.ext_fmt_cnt = 5;
+
+    af->dev_info.ext_fmt[0].id = PJMEDIA_FORMAT_AMR;
+    af->dev_info.ext_fmt[0].bitrate = 7400;
+    af->dev_info.ext_fmt[0].vad = PJ_TRUE;
+
+    af->dev_info.ext_fmt[1].id = PJMEDIA_FORMAT_G729;
+    af->dev_info.ext_fmt[1].bitrate = 8000;
+    af->dev_info.ext_fmt[1].vad = PJ_FALSE;
+
+    af->dev_info.ext_fmt[2].id = PJMEDIA_FORMAT_ILBC;
+    af->dev_info.ext_fmt[2].bitrate = 13333;
+    af->dev_info.ext_fmt[2].vad = PJ_TRUE;
+
+    af->dev_info.ext_fmt[3].id = PJMEDIA_FORMAT_PCMU;
+    af->dev_info.ext_fmt[3].bitrate = 64000;
+    af->dev_info.ext_fmt[3].vad = PJ_FALSE;
+
+    af->dev_info.ext_fmt[4].id = PJMEDIA_FORMAT_PCMA;
+    af->dev_info.ext_fmt[4].bitrate = 64000;
+    af->dev_info.ext_fmt[4].vad = PJ_FALSE;
+    
+    PJ_LOG(4, (THIS_FILE, "APS initialized"));
+
+    return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+    struct aps_factory *af = (struct aps_factory*)f;
+    pj_pool_t *pool = af->pool;
+
+    af->pool = NULL;
+    pj_pool_release(pool);
+
+    PJ_LOG(4, (THIS_FILE, "APS destroyed"));
+    
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return 1;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info)
+{
+    struct aps_factory *af = (struct aps_factory*)f;
+
+    PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+    pj_memcpy(info, &af->dev_info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param)
+{
+    struct aps_factory *af = (struct aps_factory*)f;
+
+    PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    param->rec_id = index;
+    param->play_id = index;
+    param->clock_rate = af->dev_info.default_samples_per_sec;
+    param->channel_count = 1;
+    param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
+    param->bits_per_sample = BITS_PER_SAMPLE;
+    param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
+    param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm)
+{
+    struct aps_factory *af = (struct aps_factory*)f;
+    pj_pool_t *pool;
+    struct aps_stream *strm;
+
+    CPjAudioSetting aps_setting;
+    PjAudioCallback aps_rec_cb;
+    PjAudioCallback aps_play_cb;
+
+    /* Can only support 16bits per sample */
+    PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(af->pf, "aps-dev", 1000, 1000, NULL);
+    PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct aps_stream);
+    strm->pool = pool;
+    strm->param = *param;
+
+    if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
+	strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
+	
+    /* Set audio engine fourcc. */
+    switch(strm->param.ext_fmt.id) {
+    case PJMEDIA_FORMAT_L16:
+    case PJMEDIA_FORMAT_PCMU:
+    case PJMEDIA_FORMAT_PCMA:
+	aps_setting.fourcc = TFourCC(KMCPFourCCIdG711);
+	break;
+    case PJMEDIA_FORMAT_AMR:
+	aps_setting.fourcc = TFourCC(KMCPFourCCIdAMRNB);
+	break;
+    case PJMEDIA_FORMAT_G729:
+	aps_setting.fourcc = TFourCC(KMCPFourCCIdG729);
+	break;
+    case PJMEDIA_FORMAT_ILBC:
+	aps_setting.fourcc = TFourCC(KMCPFourCCIdILBC);
+	break;
+    default:
+	aps_setting.fourcc = 0;
+	break;
+    }
+
+    /* Set audio engine mode. */
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
+    {
+	aps_setting.mode = (TAPSCodecMode)strm->param.ext_fmt.bitrate;
+    } 
+    else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
+	     strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
+	    (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC  &&
+	     strm->param.ext_fmt.bitrate != 15200))
+    {
+	aps_setting.mode = EULawOr30ms;
+    } 
+    else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+	    (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
+	     strm->param.ext_fmt.bitrate == 15200))
+    {
+	aps_setting.mode = EALawOr20ms;
+    }
+
+    /* Disable VAD on L16, G711, and also G729 (G729's VAD potentially 
+     * causes noise?).
+     */
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
+	strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+	strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
+	strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729)
+    {
+	aps_setting.vad = EFalse;
+    } else {
+	aps_setting.vad = strm->param.ext_fmt.vad;
+    }
+    
+    /* Set other audio engine attributes. */
+    aps_setting.plc = strm->param.plc_enabled;
+    aps_setting.cng = aps_setting.vad;
+    aps_setting.loudspk = 
+		strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+
+    /* Set audio engine callbacks. */
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
+	aps_play_cb = &PlayCbPcm;
+	aps_rec_cb  = &RecCbPcm;
+    } else {
+	aps_play_cb = &PlayCb;
+	aps_rec_cb  = &RecCb;
+    }
+
+    /* Create the audio engine. */
+    TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
+						   aps_rec_cb, aps_play_cb,
+						   strm, aps_setting));
+    if (err != KErrNone) {
+    	pj_pool_release(pool);
+	return PJ_RETURN_OS_ERROR(err);
+    }
+
+    /* Apply output volume setting if specified */
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+	stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, 
+		       &param->output_vol);
+    }
+
+    strm->rec_cb = rec_cb;
+    strm->play_cb = play_cb;
+    strm->user_data = user_data;
+
+    /* play_buf size is samples per frame. */
+    strm->play_buf = (pj_int16_t*)pj_pool_zalloc(pool, 
+					strm->param.samples_per_frame << 1);
+    strm->play_buf_len = 0;
+    strm->play_buf_start = 0;
+
+    /* rec_buf size is samples per frame. */
+    strm->rec_buf  = (pj_int16_t*)pj_pool_zalloc(pool, 
+					strm->param.samples_per_frame << 1);
+    strm->rec_buf_len = 0;
+
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+	TBitStream *g729_bitstream = new TBitStream;
+	
+	PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
+	strm->strm_data = (void*)g729_bitstream;
+    }
+	
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_aud_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+				    pjmedia_aud_param *pi)
+{
+    struct aps_stream *strm = (struct aps_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    /* Update the output volume setting */
+    if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+		       &pi->output_vol) == PJ_SUCCESS)
+    {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+    }
+    
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  void *pval)
+{
+    struct aps_stream *strm = (struct aps_stream*)s;
+    pj_status_t status = PJ_ENOTSUP;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    switch (cap) {
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: 
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    *(pjmedia_aud_dev_route*)pval = strm->param.output_route;
+	    status = PJ_SUCCESS;
+	}
+	break;
+    
+    /* There is a case that GetMaxGain() stucks, e.g: in N95. */ 
+    /*
+    case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    TInt max_gain = strm->engine->GetMaxGain();
+	    TInt gain = strm->engine->GetGain();
+	    
+	    if (max_gain > 0 && gain >= 0) {
+		*(unsigned*)pval = gain * 100 / max_gain; 
+		status = PJ_SUCCESS;
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	}
+	break;
+    */
+
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    TInt max_vol = strm->engine->GetMaxVolume();
+	    TInt vol = strm->engine->GetVolume();
+	    
+	    if (max_vol > 0 && vol >= 0) {
+		*(unsigned*)pval = vol * 100 / max_vol; 
+		status = PJ_SUCCESS;
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	}
+	break;
+    default:
+	break;
+    }
+    
+    return status;
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  const void *pval)
+{
+    struct aps_stream *strm = (struct aps_stream*)s;
+    pj_status_t status = PJ_ENOTSUP;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    switch (cap) {
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: 
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
+	    TInt err;
+
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    switch (r) {
+	    case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
+	    case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
+		err = strm->engine->ActivateSpeaker(EFalse);
+		status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+		break;
+	    case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
+		err = strm->engine->ActivateSpeaker(ETrue);
+		status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+		break;
+	    default:
+		status = PJ_EINVAL;
+		break;
+	    }
+	    if (status == PJ_SUCCESS)
+		strm->param.output_route = r; 
+	}
+	break;
+
+    /* There is a case that GetMaxGain() stucks, e.g: in N95. */ 
+    /*
+    case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    TInt max_gain = strm->engine->GetMaxGain();
+	    if (max_gain > 0) {
+		TInt gain, err;
+		
+		gain = *(unsigned*)pval * max_gain / 100;
+		err = strm->engine->SetGain(gain);
+		status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	    if (status == PJ_SUCCESS)
+		strm->param.input_vol = *(unsigned*)pval;
+	}
+	break;
+    */
+
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    TInt max_vol = strm->engine->GetMaxVolume();
+	    if (max_vol > 0) {
+		TInt vol, err;
+		
+		vol = *(unsigned*)pval * max_vol / 100;
+		err = strm->engine->SetVolume(vol);
+		status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	    if (status == PJ_SUCCESS)
+		strm->param.output_vol = *(unsigned*)pval;
+	}
+	break;
+    default:
+	break;
+    }
+    
+    return status;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+    struct aps_stream *stream = (struct aps_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    if (stream->engine) {
+	TInt err = stream->engine->StartL();
+    	if (err != KErrNone)
+    	    return PJ_RETURN_OS_ERROR(err);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+    struct aps_stream *stream = (struct aps_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    if (stream->engine) {
+    	stream->engine->Stop();
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+    struct aps_stream *stream = (struct aps_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    stream_stop(strm);
+
+    delete stream->engine;
+    stream->engine = NULL;
+
+    if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+	TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
+	stream->strm_data = NULL;
+	delete g729_bitstream;
+    }
+
+    pj_pool_t *pool;
+    pool = stream->pool;
+    if (pool) {
+    	stream->pool = NULL;
+    	pj_pool_release(pool);
+    }
+
+    return PJ_SUCCESS;
+}
+
+#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp	(revision 2506)
@@ -0,0 +1,1110 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+
+/*
+ * This file provides sound implementation for Symbian Audio Streaming
+ * device. Application using this sound abstraction must link with:
+ *  - mediaclientaudiostream.lib, and
+ *  - mediaclientaudioinputstream.lib 
+ */
+#include <mda/common/audio.h>
+#include <mdaaudiooutputstream.h>
+#include <mdaaudioinputstream.h>
+
+
+#define THIS_FILE			"symb_mda_dev.c"
+#define BITS_PER_SAMPLE			16
+#define BYTES_PER_SAMPLE		(BITS_PER_SAMPLE/8)
+
+
+#if 1
+#   define TRACE_(st) PJ_LOG(3, st)
+#else
+#   define TRACE_(st)
+#endif
+
+
+/* MDA factory */
+struct mda_factory
+{
+    pjmedia_aud_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+    pjmedia_aud_dev_info	 dev_info;
+};
+
+/* Forward declaration of internal engine. */
+class CPjAudioInputEngine;
+class CPjAudioOutputEngine;
+
+/* MDA stream. */
+struct mda_stream
+{
+    // Base
+    pjmedia_aud_stream	 base;			/**< Base class.	*/
+    
+    // Pool
+    pj_pool_t		*pool;			/**< Memory pool.       */
+
+    // Common settings.
+    pjmedia_aud_param param;		/**< Stream param.	*/
+
+    // Audio engine
+    CPjAudioInputEngine	*in_engine;		/**< Record engine.	*/
+    CPjAudioOutputEngine *out_engine;		/**< Playback engine.	*/
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned    factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+				    pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+    &factory_init,
+    &factory_destroy,
+    &factory_get_dev_count,
+    &factory_get_dev_info,
+    &factory_default_param,
+    &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op = 
+{
+    &stream_get_param,
+    &stream_get_cap,
+    &stream_set_cap,
+    &stream_start,
+    &stream_stop,
+    &stream_destroy
+};
+
+
+/*
+ * Convert clock rate to Symbian's TMdaAudioDataSettings capability.
+ */
+static TInt get_clock_rate_cap(unsigned clock_rate)
+{
+    switch (clock_rate) {
+    case 8000:  return TMdaAudioDataSettings::ESampleRate8000Hz;
+    case 11025: return TMdaAudioDataSettings::ESampleRate11025Hz;
+    case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz;
+    case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz;
+    case 22050: return TMdaAudioDataSettings::ESampleRate22050Hz;
+    case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz;
+    case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz;
+    case 44100: return TMdaAudioDataSettings::ESampleRate44100Hz;
+    case 48000: return TMdaAudioDataSettings::ESampleRate48000Hz;
+    case 64000: return TMdaAudioDataSettings::ESampleRate64000Hz;
+    case 96000: return TMdaAudioDataSettings::ESampleRate96000Hz;
+    default:
+	return 0;
+    }
+}
+
+/*
+ * Convert number of channels into Symbian's TMdaAudioDataSettings capability.
+ */
+static TInt get_channel_cap(unsigned channel_count)
+{
+    switch (channel_count) {
+    case 1: return TMdaAudioDataSettings::EChannelsMono;
+    case 2: return TMdaAudioDataSettings::EChannelsStereo;
+    default:
+	return 0;
+    }
+}
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc) 
+{
+    PJ_LOG(1,(THIS_FILE, "%s: error code %d", title, rc));
+}
+ 
+//////////////////////////////////////////////////////////////////////////////
+//
+
+/*
+ * Implementation: Symbian Input Stream.
+ */
+class CPjAudioInputEngine : public CBase, MMdaAudioInputStreamCallback
+{
+public:
+    enum State
+    {
+	STATE_INACTIVE,
+	STATE_ACTIVE,
+    };
+
+    ~CPjAudioInputEngine();
+
+    static CPjAudioInputEngine *NewL(struct mda_stream *parent_strm,
+				     pjmedia_aud_rec_cb rec_cb,
+				     void *user_data);
+
+    static CPjAudioInputEngine *NewLC(struct mda_stream *parent_strm,
+				      pjmedia_aud_rec_cb rec_cb,
+				      void *user_data);
+
+    pj_status_t StartRecord();
+    void Stop();
+
+    pj_status_t SetGain(TInt gain) { 
+	if (iInputStream_) { 
+	    iInputStream_->SetGain(gain);
+	    return PJ_SUCCESS;
+	} else
+	    return PJ_EINVALIDOP;
+    }
+    
+    TInt GetGain() { 
+	if (iInputStream_) { 
+	    return iInputStream_->Gain();
+	} else
+	    return PJ_EINVALIDOP;
+    }
+
+    TInt GetMaxGain() { 
+	if (iInputStream_) { 
+	    return iInputStream_->MaxGain();
+	} else
+	    return PJ_EINVALIDOP;
+    }
+    
+private:
+    State		     state_;
+    struct mda_stream	    *parentStrm_;
+    pjmedia_aud_rec_cb	     recCb_;
+    void		    *userData_;
+    CMdaAudioInputStream    *iInputStream_;
+    HBufC8		    *iStreamBuffer_;
+    TPtr8		     iFramePtr_;
+    TInt		     lastError_;
+    pj_uint32_t		     timeStamp_;
+
+    // cache variable
+    // to avoid calculating frame length repeatedly
+    TInt		     frameLen_;
+    
+    // sometimes recorded size != requested framesize, so let's
+    // provide a buffer to make sure the rec callback returning
+    // framesize as requested.
+    TUint8		    *frameRecBuf_;
+    TInt		     frameRecBufLen_;
+
+    CPjAudioInputEngine(struct mda_stream *parent_strm,
+	    pjmedia_aud_rec_cb rec_cb,
+			void *user_data);
+    void ConstructL();
+    TPtr8 & GetFrame();
+    
+public:
+    virtual void MaiscOpenComplete(TInt aError);
+    virtual void MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer);
+    virtual void MaiscRecordComplete(TInt aError);
+
+};
+
+
+CPjAudioInputEngine::CPjAudioInputEngine(struct mda_stream *parent_strm,
+					 pjmedia_aud_rec_cb rec_cb,
+					 void *user_data)
+    : state_(STATE_INACTIVE), parentStrm_(parent_strm), 
+      recCb_(rec_cb), userData_(user_data), 
+      iInputStream_(NULL), iStreamBuffer_(NULL), iFramePtr_(0, 0),
+      lastError_(KErrNone), timeStamp_(0),
+      frameLen_(parent_strm->param.samples_per_frame * 
+	        parent_strm->param.channel_count * 
+	        BYTES_PER_SAMPLE),
+      frameRecBuf_(NULL), frameRecBufLen_(0)
+{
+}
+
+CPjAudioInputEngine::~CPjAudioInputEngine()
+{
+    Stop();
+
+    delete iStreamBuffer_;
+    iStreamBuffer_ = NULL;
+    
+    delete [] frameRecBuf_;
+    frameRecBuf_ = NULL;
+    frameRecBufLen_ = 0;
+}
+
+void CPjAudioInputEngine::ConstructL()
+{
+    iStreamBuffer_ = HBufC8::NewL(frameLen_);
+    CleanupStack::PushL(iStreamBuffer_);
+
+    frameRecBuf_ = new TUint8[frameLen_*2];
+    CleanupStack::PushL(frameRecBuf_);
+}
+
+CPjAudioInputEngine *CPjAudioInputEngine::NewLC(struct mda_stream *parent,
+						pjmedia_aud_rec_cb rec_cb,
+					        void *user_data)
+{
+    CPjAudioInputEngine* self = new (ELeave) CPjAudioInputEngine(parent,
+								 rec_cb, 
+								 user_data);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    return self;
+}
+
+CPjAudioInputEngine *CPjAudioInputEngine::NewL(struct mda_stream *parent,
+					       pjmedia_aud_rec_cb rec_cb,
+					       void *user_data)
+{
+    CPjAudioInputEngine *self = NewLC(parent, rec_cb, user_data);
+    CleanupStack::Pop(self->frameRecBuf_);
+    CleanupStack::Pop(self->iStreamBuffer_);
+    CleanupStack::Pop(self);
+    return self;
+}
+
+
+pj_status_t CPjAudioInputEngine::StartRecord()
+{
+
+    // Ignore command if recording is in progress.
+    if (state_ == STATE_ACTIVE)
+	return PJ_SUCCESS;
+
+    // According to Nokia's AudioStream example, some 2nd Edition, FP2 devices
+    // (such as Nokia 6630) require the stream to be reconstructed each time 
+    // before calling Open() - otherwise the callback never gets called.
+    // For uniform behavior, lets just delete/re-create the stream for all
+    // devices.
+
+    // Destroy existing stream.
+    if (iInputStream_) delete iInputStream_;
+    iInputStream_ = NULL;
+
+    // Create the stream.
+    TRAPD(err, iInputStream_ = CMdaAudioInputStream::NewL(*this));
+    if (err != KErrNone)
+	return PJ_RETURN_OS_ERROR(err);
+
+    // Initialize settings.
+    TMdaAudioDataSettings iStreamSettings;
+    iStreamSettings.iChannels = 
+			    get_channel_cap(parentStrm_->param.channel_count);
+    iStreamSettings.iSampleRate = 
+			    get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+    pj_assert(iStreamSettings.iChannels != 0 && 
+	      iStreamSettings.iSampleRate != 0);
+
+    PJ_LOG(4,(THIS_FILE, "Opening sound device for capture, "
+    		         "clock rate=%d, channel count=%d..",
+    		         parentStrm_->param.clock_rate, 
+    		         parentStrm_->param.channel_count));
+    
+    // Open stream.
+    lastError_ = KRequestPending;
+    iInputStream_->Open(&iStreamSettings);
+    
+    // Success
+    PJ_LOG(4,(THIS_FILE, "Sound capture started."));
+    return PJ_SUCCESS;
+}
+
+
+void CPjAudioInputEngine::Stop()
+{
+    // If capture is in progress, stop it.
+    if (iInputStream_ && state_ == STATE_ACTIVE) {
+    	lastError_ = KRequestPending;
+    	iInputStream_->Stop();
+
+	// Wait until it's actually stopped
+    	while (lastError_ == KRequestPending)
+	    pj_symbianos_poll(-1, 100);
+    }
+
+    if (iInputStream_) {
+	delete iInputStream_;
+	iInputStream_ = NULL;
+    }
+    
+    state_ = STATE_INACTIVE;
+}
+
+
+TPtr8 & CPjAudioInputEngine::GetFrame() 
+{
+    //iStreamBuffer_->Des().FillZ(frameLen_);
+    iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_);
+    return iFramePtr_;
+}
+
+void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
+{
+    lastError_ = aError;
+    if (aError != KErrNone) {
+        snd_perror("Error in MaiscOpenComplete()", aError);
+    	return;
+    }
+
+    // set stream priority to normal and time sensitive
+    iInputStream_->SetPriority(EPriorityNormal, 
+    			       EMdaPriorityPreferenceTime);				
+
+    // Read the first frame.
+    TPtr8 & frm = GetFrame();
+    TRAPD(err2, iInputStream_->ReadL(frm));
+    if (err2) {
+    	PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
+    }
+}
+
+void CPjAudioInputEngine::MaiscBufferCopied(TInt aError, 
+					    const TDesC8 &aBuffer)
+{
+    lastError_ = aError;
+    if (aError != KErrNone) {
+    	snd_perror("Error in MaiscBufferCopied()", aError);
+	return;
+    }
+
+    if (frameRecBufLen_ || aBuffer.Size() < frameLen_) {
+	pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Size());
+	frameRecBufLen_ += aBuffer.Size();
+    }
+
+    if (frameRecBufLen_) {
+    	while (frameRecBufLen_ >= frameLen_) {
+    	    pjmedia_frame f;
+    	    
+    	    f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+    	    f.buf = frameRecBuf_;
+    	    f.size = frameLen_;
+    	    f.timestamp.u32.lo = timeStamp_;
+    	    f.bit_info = 0;
+    	    
+    	    // Call the callback.
+	    recCb_(userData_, &f);
+	    // Increment timestamp.
+	    timeStamp_ += parentStrm_->param.samples_per_frame;
+
+	    frameRecBufLen_ -= frameLen_;
+	    pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_);
+    	}
+    } else {
+	pjmedia_frame f;
+	
+	f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	f.buf = (void*)aBuffer.Ptr();
+	f.size = aBuffer.Size();
+	f.timestamp.u32.lo = timeStamp_;
+	f.bit_info = 0;
+	
+	// Call the callback.
+	recCb_(userData_, &f);
+	
+	// Increment timestamp.
+	timeStamp_ += parentStrm_->param.samples_per_frame;
+    }
+
+    // Record next frame
+    TPtr8 & frm = GetFrame();
+    TRAPD(err2, iInputStream_->ReadL(frm));
+    if (err2) {
+    	PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
+    }
+}
+
+
+void CPjAudioInputEngine::MaiscRecordComplete(TInt aError)
+{
+    lastError_ = aError;
+    state_ = STATE_INACTIVE;
+    if (aError != KErrNone) {
+    	snd_perror("Error in MaiscRecordComplete()", aError);
+    }
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+
+/*
+ * Implementation: Symbian Output Stream.
+ */
+
+class CPjAudioOutputEngine : public CBase, MMdaAudioOutputStreamCallback
+{
+public:
+    enum State
+    {
+	STATE_INACTIVE,
+	STATE_ACTIVE,
+    };
+
+    ~CPjAudioOutputEngine();
+
+    static CPjAudioOutputEngine *NewL(struct mda_stream *parent_strm,
+				      pjmedia_aud_play_cb play_cb,
+				      void *user_data);
+
+    static CPjAudioOutputEngine *NewLC(struct mda_stream *parent_strm,
+				       pjmedia_aud_play_cb rec_cb,
+				       void *user_data);
+
+    pj_status_t StartPlay();
+    void Stop();
+
+    pj_status_t SetVolume(TInt vol) { 
+	if (iOutputStream_) { 
+	    iOutputStream_->SetVolume(vol);
+	    return PJ_SUCCESS;
+	} else
+	    return PJ_EINVALIDOP;
+    }
+    
+    TInt GetVolume() { 
+	if (iOutputStream_) { 
+	    return iOutputStream_->Volume();
+	} else
+	    return PJ_EINVALIDOP;
+    }
+
+    TInt GetMaxVolume() { 
+	if (iOutputStream_) { 
+	    return iOutputStream_->MaxVolume();
+	} else
+	    return PJ_EINVALIDOP;
+    }
+
+private:
+    State		     state_;
+    struct mda_stream	    *parentStrm_;
+    pjmedia_aud_play_cb	     playCb_;
+    void		    *userData_;
+    CMdaAudioOutputStream   *iOutputStream_;
+    TUint8		    *frameBuf_;
+    unsigned		     frameBufSize_;
+    TPtrC8		     frame_;
+    TInt		     lastError_;
+    unsigned		     timestamp_;
+
+    CPjAudioOutputEngine(struct mda_stream *parent_strm,
+			 pjmedia_aud_play_cb play_cb,
+			 void *user_data);
+    void ConstructL();
+
+    virtual void MaoscOpenComplete(TInt aError);
+    virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer);
+    virtual void MaoscPlayComplete(TInt aError);
+};
+
+
+CPjAudioOutputEngine::CPjAudioOutputEngine(struct mda_stream *parent_strm,
+					   pjmedia_aud_play_cb play_cb,
+					   void *user_data) 
+: state_(STATE_INACTIVE), parentStrm_(parent_strm), playCb_(play_cb), 
+  userData_(user_data), iOutputStream_(NULL), frameBuf_(NULL),
+  lastError_(KErrNone), timestamp_(0)
+{
+}
+
+
+void CPjAudioOutputEngine::ConstructL()
+{
+    frameBufSize_ = parentStrm_->param.samples_per_frame *
+		    parentStrm_->param.channel_count * 
+		    BYTES_PER_SAMPLE;
+    frameBuf_ = new TUint8[frameBufSize_];
+}
+
+CPjAudioOutputEngine::~CPjAudioOutputEngine()
+{
+    Stop();
+    delete [] frameBuf_;	
+}
+
+CPjAudioOutputEngine *
+CPjAudioOutputEngine::NewLC(struct mda_stream *parent_strm,
+			    pjmedia_aud_play_cb play_cb,
+			    void *user_data)
+{
+    CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm,
+								   play_cb, 
+								   user_data);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    return self;
+}
+
+CPjAudioOutputEngine *
+CPjAudioOutputEngine::NewL(struct mda_stream *parent_strm,
+			   pjmedia_aud_play_cb play_cb,
+			   void *user_data)
+{
+    CPjAudioOutputEngine *self = NewLC(parent_strm, play_cb, user_data);
+    CleanupStack::Pop(self);
+    return self;
+}
+
+pj_status_t CPjAudioOutputEngine::StartPlay()
+{
+    // Ignore command if playing is in progress.
+    if (state_ == STATE_ACTIVE)
+	return PJ_SUCCESS;
+    
+    // Destroy existing stream.
+    if (iOutputStream_) delete iOutputStream_;
+    iOutputStream_ = NULL;
+    
+    // Create the stream
+    TRAPD(err, iOutputStream_ = CMdaAudioOutputStream::NewL(*this));
+    if (err != KErrNone)
+	return PJ_RETURN_OS_ERROR(err);
+    
+    // Initialize settings.
+    TMdaAudioDataSettings iStreamSettings;
+    iStreamSettings.iChannels = 
+			    get_channel_cap(parentStrm_->param.channel_count);
+    iStreamSettings.iSampleRate = 
+			    get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+    pj_assert(iStreamSettings.iChannels != 0 && 
+	      iStreamSettings.iSampleRate != 0);
+    
+    PJ_LOG(4,(THIS_FILE, "Opening sound device for playback, "
+    		         "clock rate=%d, channel count=%d..",
+    		         parentStrm_->param.clock_rate, 
+    		         parentStrm_->param.channel_count));
+
+    // Open stream.
+    lastError_ = KRequestPending;
+    iOutputStream_->Open(&iStreamSettings);
+
+    // Success
+    PJ_LOG(4,(THIS_FILE, "Sound playback started"));
+    return PJ_SUCCESS;
+
+}
+
+void CPjAudioOutputEngine::Stop()
+{
+    // Stop stream if it's playing
+    if (iOutputStream_ && state_ != STATE_INACTIVE) {
+    	lastError_ = KRequestPending;
+    	iOutputStream_->Stop();
+
+	// Wait until it's actually stopped
+    	while (lastError_ == KRequestPending)
+	    pj_symbianos_poll(-1, 100);
+    }
+    
+    if (iOutputStream_) {	
+	delete iOutputStream_;
+	iOutputStream_ = NULL;
+    }
+    
+    state_ = STATE_INACTIVE;
+}
+
+void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
+{
+    lastError_ = aError;
+    
+    if (aError==KErrNone) {
+	// output stream opened succesfully, set status to Active
+	state_ = STATE_ACTIVE;
+
+	// set stream properties, 16bit 8KHz mono
+	TMdaAudioDataSettings iSettings;
+	iSettings.iChannels = 
+			get_channel_cap(parentStrm_->param.channel_count);
+	iSettings.iSampleRate = 
+			get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+	iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate, 
+					    iSettings.iChannels);
+
+	// set volume to 1/2th of stream max volume
+	iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2);
+	
+	// set stream priority to normal and time sensitive
+	iOutputStream_->SetPriority(EPriorityNormal, 
+				    EMdaPriorityPreferenceTime);				
+
+	// Call callback to retrieve frame from upstream.
+	pjmedia_frame f;
+	pj_status_t status;
+	
+	f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	f.buf = frameBuf_;
+	f.size = frameBufSize_;
+	f.timestamp.u32.lo = timestamp_;
+	f.bit_info = 0;
+
+	status = playCb_(this->userData_, &f);
+	if (status != PJ_SUCCESS) {
+	    this->Stop();
+	    return;
+	}
+
+	// Increment timestamp.
+	timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
+
+	// issue WriteL() to write the first audio data block, 
+	// subsequent calls to WriteL() will be issued in 
+	// MMdaAudioOutputStreamCallback::MaoscBufferCopied() 
+	// until whole data buffer is written.
+	frame_.Set(frameBuf_, frameBufSize_);
+	iOutputStream_->WriteL(frame_);
+    } else {
+    	snd_perror("Error in MaoscOpenComplete()", aError);
+    }
+}
+
+void CPjAudioOutputEngine::MaoscBufferCopied(TInt aError, 
+					     const TDesC8& aBuffer)
+{
+    PJ_UNUSED_ARG(aBuffer);
+
+    if (aError==KErrNone) {
+    	// Buffer successfully written, feed another one.
+
+	// Call callback to retrieve frame from upstream.
+	pjmedia_frame f;
+	pj_status_t status;
+	
+	f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	f.buf = frameBuf_;
+	f.size = frameBufSize_;
+	f.timestamp.u32.lo = timestamp_;
+	f.bit_info = 0;
+
+	status = playCb_(this->userData_, &f);
+	if (status != PJ_SUCCESS) {
+	    this->Stop();
+	    return;
+	}
+
+	// Increment timestamp.
+	timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
+
+	// Write to playback stream.
+	frame_.Set(frameBuf_, frameBufSize_);
+	iOutputStream_->WriteL(frame_);
+
+    } else if (aError==KErrAbort) {
+	// playing was aborted, due to call to CMdaAudioOutputStream::Stop()
+	state_ = STATE_INACTIVE;
+    } else  {
+	// error writing data to output
+	lastError_ = aError;
+	state_ = STATE_INACTIVE;
+	snd_perror("Error in MaoscBufferCopied()", aError);
+    }
+}
+
+void CPjAudioOutputEngine::MaoscPlayComplete(TInt aError)
+{
+    lastError_ = aError;
+    state_ = STATE_INACTIVE;
+    if (aError != KErrNone) {
+    	snd_perror("Error in MaoscPlayComplete()", aError);
+    }
+}
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * C compatible declaration of MDA factory.
+ */
+PJ_BEGIN_DECL
+PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_symb_mda_factory(pj_pool_factory *pf);
+PJ_END_DECL
+
+/*
+ * Init Symbian audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf)
+{
+    struct mda_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "symb_aud", 1000, 1000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct mda_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+    struct mda_factory *af = (struct mda_factory*)f;
+
+    pj_ansi_strcpy(af->dev_info.name, "Symbian Audio");
+    af->dev_info.default_samples_per_sec = 8000;
+    af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
+			PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+    af->dev_info.input_count = 1;
+    af->dev_info.output_count = 1;
+
+    PJ_LOG(4, (THIS_FILE, "Symb Mda initialized"));
+
+    return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+    struct mda_factory *af = (struct mda_factory*)f;
+    pj_pool_t *pool = af->pool;
+
+    af->pool = NULL;
+    pj_pool_release(pool);
+
+    PJ_LOG(4, (THIS_FILE, "Symbian Mda destroyed"));
+    
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return 1;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info)
+{
+    struct mda_factory *af = (struct mda_factory*)f;
+
+    PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+    pj_memcpy(info, &af->dev_info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param)
+{
+    struct mda_factory *af = (struct mda_factory*)f;
+
+    PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    param->rec_id = index;
+    param->play_id = index;
+    param->clock_rate = af->dev_info.default_samples_per_sec;
+    param->channel_count = 1;
+    param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
+    param->bits_per_sample = BITS_PER_SAMPLE;
+    param->flags = af->dev_info.caps;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm)
+{
+    struct mda_factory *mf = (struct mda_factory*)f;
+    pj_pool_t *pool;
+    struct mda_stream *strm;
+
+    /* Can only support 16bits per sample raw PCM format. */
+    PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
+    PJ_ASSERT_RETURN((param->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT)==0 ||
+		     param->ext_fmt.id == PJMEDIA_FORMAT_L16,
+		     PJ_ENOTSUP);
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(mf->pf, "symb_aud_dev", 1000, 1000, NULL);
+    PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct mda_stream);
+    strm->pool = pool;
+    strm->param = *param;
+
+    // Create the output stream.
+    if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	TRAPD(err, strm->out_engine = CPjAudioOutputEngine::NewL(strm, play_cb,
+								 user_data));
+	if (err != KErrNone) {
+	    pj_pool_release(pool);	
+	    return PJ_RETURN_OS_ERROR(err);
+	}
+    }
+
+    // Create the input stream.
+    if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	TRAPD(err, strm->in_engine = CPjAudioInputEngine::NewL(strm, rec_cb, 
+							       user_data));
+	if (err != KErrNone) {
+	    strm->in_engine = NULL;
+	    delete strm->out_engine;
+	    strm->out_engine = NULL;
+	    pj_pool_release(pool);	
+	    return PJ_RETURN_OS_ERROR(err);
+	}
+    }
+	
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_aud_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+				    pjmedia_aud_param *pi)
+{
+    struct mda_stream *strm = (struct mda_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+    
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  void *pval)
+{
+    struct mda_stream *strm = (struct mda_stream*)s;
+    pj_status_t status = PJ_ENOTSUP;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    switch (cap) {
+    case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	    PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL);
+	    
+	    TInt max_gain = strm->in_engine->GetMaxGain();
+	    TInt gain = strm->in_engine->GetGain();
+	    
+	    if (max_gain > 0 && gain >= 0) {
+		*(unsigned*)pval = gain * 100 / max_gain; 
+		status = PJ_SUCCESS;
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	}
+	break;
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
+	    
+	    TInt max_vol = strm->out_engine->GetMaxVolume();
+	    TInt vol = strm->out_engine->GetVolume();
+	    
+	    if (max_vol > 0 && vol >= 0) {
+		*(unsigned*)pval = vol * 100 / max_vol; 
+		status = PJ_SUCCESS;
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	}
+	break;
+    default:
+	break;
+    }
+    
+    return status;
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  const void *pval)
+{
+    struct mda_stream *strm = (struct mda_stream*)s;
+    pj_status_t status = PJ_ENOTSUP;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    switch (cap) {
+    case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	    PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL);
+	    
+	    TInt max_gain = strm->in_engine->GetMaxGain();
+	    if (max_gain > 0) {
+		TInt gain;
+		
+		gain = *(unsigned*)pval * max_gain / 100;
+		status = strm->in_engine->SetGain(gain);
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	}
+	break;
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	    PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
+	    
+	    TInt max_vol = strm->out_engine->GetMaxVolume();
+	    if (max_vol > 0) {
+		TInt vol;
+		
+		vol = *(unsigned*)pval * max_vol / 100;
+		status = strm->out_engine->SetVolume(vol);
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	}
+	break;
+    default:
+	break;
+    }
+    
+    return status;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+    struct mda_stream *stream = (struct mda_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    if (stream->out_engine) {
+	pj_status_t status;
+    	status = stream->out_engine->StartPlay();
+    	if (status != PJ_SUCCESS)
+    	    return status;
+    }
+    
+    if (stream->in_engine) {
+	pj_status_t status;
+    	status = stream->in_engine->StartRecord();
+    	if (status != PJ_SUCCESS)
+    	    return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+    struct mda_stream *stream = (struct mda_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    if (stream->in_engine) {
+    	stream->in_engine->Stop();
+    }
+    	
+    if (stream->out_engine) {
+    	stream->out_engine->Stop();
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+    struct mda_stream *stream = (struct mda_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    stream_stop(strm);
+
+    delete stream->in_engine;
+    stream->in_engine = NULL;
+
+    delete stream->out_engine;
+    stream->out_engine = NULL;
+
+    pj_pool_t *pool;
+    pool = stream->pool;
+    if (pool) {
+    	stream->pool = NULL;
+    	pj_pool_release(pool);
+    }
+
+    return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA */
Index: pjproject/trunk/pjmedia/src/pjmedia-audiodev/wmme_dev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-audiodev/wmme_dev.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-audiodev/wmme_dev.c	(revision 2506)
@@ -0,0 +1,1311 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+
+#ifdef _MSC_VER
+#   pragma warning(push, 3)
+#endif
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <mmreg.h>
+
+#ifdef _MSC_VER
+#   pragma warning(pop)
+#endif
+
+/* mingw lacks WAVE_FORMAT_ALAW/MULAW */
+#ifndef WAVE_FORMAT_ALAW
+#   define  WAVE_FORMAT_ALAW       0x0006
+#endif
+#ifndef WAVE_FORMAT_MULAW
+#   define  WAVE_FORMAT_MULAW      0x0007
+#endif
+
+#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
+#   pragma comment(lib, "Coredll.lib")
+#elif defined(_MSC_VER)
+#   pragma comment(lib, "winmm.lib")
+#endif
+
+
+#define THIS_FILE			"wmme_dev.c"
+
+/* WMME device info */
+struct wmme_dev_info
+{
+    pjmedia_aud_dev_info	 info;
+    unsigned			 deviceId;
+};
+
+/* WMME factory */
+struct wmme_factory
+{
+    pjmedia_aud_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct wmme_dev_info	*dev_info;
+};
+
+
+/* Individual WMME capture/playback stream descriptor */
+struct wmme_channel
+{
+    union
+    {
+	HWAVEIN   In;
+	HWAVEOUT  Out;
+    } hWave;
+
+    WAVEHDR      *WaveHdr;
+    HANDLE        hEvent;
+    DWORD         dwBufIdx;
+    DWORD         dwMaxBufIdx;
+    pj_timestamp  timestamp;
+};
+
+
+/* Sound stream. */
+struct wmme_stream
+{
+    pjmedia_aud_stream	 base;		    /**< Base stream	       */
+    pjmedia_aud_param	 param;		    /**< Settings	       */
+    pj_pool_t           *pool;              /**< Memory pool.          */
+
+    pjmedia_aud_rec_cb   rec_cb;            /**< Capture callback.     */
+    pjmedia_aud_play_cb  play_cb;           /**< Playback callback.    */
+    void                *user_data;         /**< Application data.     */
+
+    struct wmme_channel  play_strm;         /**< Playback stream.      */
+    struct wmme_channel  rec_strm;          /**< Capture stream.       */
+
+    void    		*buffer;	    /**< Temp. frame buffer.   */
+    pjmedia_format_id	 fmt_id;	    /**< Frame format	       */
+    pj_uint8_t		 silence_char;	    /**< Silence pattern       */
+    unsigned		 bytes_per_frame;   /**< Bytes per frame       */
+
+    pjmedia_frame_ext	*xfrm;		    /**< Extended frame buffer */
+    unsigned		 xfrm_size;	    /**< Total ext frm size    */
+
+    pj_thread_t         *thread;            /**< Thread handle.        */
+    HANDLE               thread_quit_event; /**< Quit signal to thread */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned    factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+				    pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+    &factory_init,
+    &factory_destroy,
+    &factory_get_dev_count,
+    &factory_get_dev_info,
+    &factory_default_param,
+    &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op = 
+{
+    &stream_get_param,
+    &stream_get_cap,
+    &stream_set_cap,
+    &stream_start,
+    &stream_stop,
+    &stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init WMME audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf)
+{
+    struct wmme_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "WMME", 1000, 1000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct wmme_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+
+/* Internal: build device info from WAVEINCAPS/WAVEOUTCAPS */
+static void build_dev_info(UINT deviceId, struct wmme_dev_info *wdi, 
+			   const WAVEINCAPS *wic, const WAVEOUTCAPS *woc)
+{
+#define WIC_WOC(wic,woc,field)	(wic? wic->field : woc->field)
+
+    pj_bzero(wdi, sizeof(*wdi));
+    wdi->deviceId = deviceId;
+
+    /* Device Name */
+    if (deviceId==WAVE_MAPPER) {
+	strncpy(wdi->info.name, "Wave mapper", sizeof(wdi->info.name));
+	wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
+    } else {
+	const pj_char_t *szPname = WIC_WOC(wic, woc, szPname);
+	PJ_DECL_ANSI_TEMP_BUF(wTmp, sizeof(wdi->info.name));
+	
+	strncpy(wdi->info.name, 
+		PJ_NATIVE_TO_STRING(szPname, wTmp, PJ_ARRAY_SIZE(wTmp)),
+		sizeof(wdi->info.name));
+	wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
+    }
+
+    wdi->info.default_samples_per_sec = 16000;
+    strcpy(wdi->info.driver, "WMME");
+
+    if (wic) {
+	wdi->info.input_count = wic->wChannels;
+	wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+
+	/* Sometimes a device can return a rediculously large number of 
+	 * channels. This happened with an SBLive card on a Windows ME box.
+	 * It also happens on Win XP!
+	 */
+	if (wdi->info.input_count<1 || wdi->info.input_count>256) {
+	    wdi->info.input_count = 2;
+	}
+    }
+
+    if (woc) {
+	wdi->info.output_count = woc->wChannels;
+	wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+	
+	if (woc->dwSupport & WAVECAPS_VOLUME) {
+	    wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+	}
+
+	/* Sometimes a device can return a rediculously large number of 
+	 * channels. This happened with an SBLive card on a Windows ME box.
+	 * It also happens on Win XP!
+	 */
+	if (wdi->info.output_count<1 || wdi->info.output_count>256) {
+	    wdi->info.output_count = 2;
+	}
+    }
+
+    /* Extended formats */
+    wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+    wdi->info.ext_fmt_cnt = 2;
+    wdi->info.ext_fmt[0].id = PJMEDIA_FORMAT_PCMU;
+    wdi->info.ext_fmt[0].bitrate = 64000;
+    wdi->info.ext_fmt[0].vad = 0;
+    wdi->info.ext_fmt[1].id = PJMEDIA_FORMAT_PCMA;
+    wdi->info.ext_fmt[1].bitrate = 64000;
+    wdi->info.ext_fmt[1].vad = 0;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+    struct wmme_factory *wf = (struct wmme_factory*)f;
+    unsigned c;
+    int i;
+    int inputDeviceCount, outputDeviceCount, devCount=0;
+    pj_bool_t waveMapperAdded = PJ_FALSE;
+
+    /* Enumerate sound devices */
+    wf->dev_count = 0;
+
+    inputDeviceCount = waveInGetNumDevs();
+    devCount += inputDeviceCount;
+
+    outputDeviceCount = waveOutGetNumDevs();
+    devCount += outputDeviceCount;
+
+    if (devCount) {
+	/* Assume there is WAVE_MAPPER */
+	devCount += 2;
+    }
+
+    if (devCount==0) {
+	PJ_LOG(4,(THIS_FILE, "WMME found no sound devices"));
+	return PJMEDIA_EAUD_NODEV;
+    }
+
+    wf->dev_info = (struct wmme_dev_info*)
+		   pj_pool_calloc(wf->pool, devCount, 
+				  sizeof(struct wmme_dev_info));
+
+    if (inputDeviceCount && outputDeviceCount) {
+	/* Attempt to add WAVE_MAPPER as input and output device */
+	WAVEINCAPS wic;
+	MMRESULT mr;
+
+	pj_bzero(&wic, sizeof(WAVEINCAPS));
+	mr = waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(WAVEINCAPS));
+
+	if (mr == MMSYSERR_NOERROR) {
+	    WAVEOUTCAPS woc;
+
+	    pj_bzero(&woc, sizeof(WAVEOUTCAPS));
+	    mr = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
+	    if (mr == MMSYSERR_NOERROR) {
+		build_dev_info(WAVE_MAPPER, &wf->dev_info[wf->dev_count], 
+			       &wic, &woc);
+		++wf->dev_count;
+		waveMapperAdded = PJ_TRUE;
+	    }
+	}
+
+    }
+
+    if (inputDeviceCount > 0) {
+	/* -1 is the WAVE_MAPPER */
+	for (i = (waveMapperAdded? 0 : -1); i < inputDeviceCount; ++i) {
+	    UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
+	    WAVEINCAPS wic;
+	    MMRESULT mr;
+
+	    pj_bzero(&wic, sizeof(WAVEINCAPS));
+
+	    mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS));
+
+	    if (mr == MMSYSERR_NOMEM)
+		return PJ_ENOMEM;
+
+	    if (mr != MMSYSERR_NOERROR)
+		continue;
+
+	    build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count], 
+			   &wic, NULL);
+	    ++wf->dev_count;
+	}
+    }
+
+    if( outputDeviceCount > 0 )
+    {
+	/* -1 is the WAVE_MAPPER */
+	for (i = (waveMapperAdded? 0 : -1); i < outputDeviceCount; ++i) {
+	    UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
+	    WAVEOUTCAPS woc;
+	    MMRESULT mr;
+
+	    pj_bzero(&woc, sizeof(WAVEOUTCAPS));
+
+	    mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS));
+
+	    if (mr == MMSYSERR_NOMEM)
+		return PJ_ENOMEM;
+
+	    if (mr != MMSYSERR_NOERROR)
+		continue;
+
+	    build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count], 
+			   NULL, &woc);
+	    ++wf->dev_count;
+	}
+    }
+
+    PJ_LOG(4, (THIS_FILE, "WMME initialized, found %d devices:", 
+	       wf->dev_count));
+    for (c = 0; c < wf->dev_count; ++c) {
+	PJ_LOG(4, (THIS_FILE, " dev_id %d: %s  (in=%d, out=%d)", 
+	    c,
+	    wf->dev_info[c].info.name,
+	    wf->dev_info[c].info.input_count,
+	    wf->dev_info[c].info.output_count));
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+    struct wmme_factory *wf = (struct wmme_factory*)f;
+    pj_pool_t *pool = wf->pool;
+
+    wf->pool = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+    struct wmme_factory *wf = (struct wmme_factory*)f;
+    return wf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info)
+{
+    struct wmme_factory *wf = (struct wmme_factory*)f;
+
+    PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+    pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param)
+{
+    struct wmme_factory *wf = (struct wmme_factory*)f;
+    struct wmme_dev_info *di = &wf->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+    pj_bzero(param, sizeof(*param));
+    if (di->info.input_count && di->info.output_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+	param->rec_id = index;
+	param->play_id = index;
+    } else if (di->info.input_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE;
+	param->rec_id = index;
+	param->play_id = PJMEDIA_AUD_INVALID_DEV;
+    } else if (di->info.output_count) {
+	param->dir = PJMEDIA_DIR_PLAYBACK;
+	param->play_id = index;
+	param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+    } else {
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    param->clock_rate = di->info.default_samples_per_sec;
+    param->channel_count = 1;
+    param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
+    param->bits_per_sample = 16;
+    param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+		   PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+    param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+    param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+    return PJ_SUCCESS;
+}
+
+/* Internal: init WAVEFORMATEX */
+static pj_status_t init_waveformatex(LPWAVEFORMATEX wfx, 
+				     const pjmedia_aud_param *prm)
+{
+
+    pj_bzero(wfx, sizeof(PCMWAVEFORMAT)); 
+    if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16) {
+	enum { BYTES_PER_SAMPLE = 2 };
+	wfx->wFormatTag = WAVE_FORMAT_PCM; 
+	wfx->nChannels = (pj_uint16_t)prm->channel_count;
+	wfx->nSamplesPerSec = prm->clock_rate;
+	wfx->nBlockAlign = (pj_uint16_t)(prm->channel_count * 
+					 BYTES_PER_SAMPLE);
+	wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count * 
+			       BYTES_PER_SAMPLE;
+	wfx->wBitsPerSample = 16;
+
+	return PJ_SUCCESS;
+
+    } else if ((prm->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) &&
+	       (prm->ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+	        prm->ext_fmt.id == PJMEDIA_FORMAT_PCMU))
+    {
+	unsigned ptime;
+
+	ptime = prm->samples_per_frame * 1000 / 
+		(prm->clock_rate * prm->channel_count);
+	wfx->wFormatTag = (pj_uint16_t)
+			  ((prm->ext_fmt.id==PJMEDIA_FORMAT_PCMA) ?
+			    WAVE_FORMAT_ALAW : WAVE_FORMAT_MULAW);  
+	wfx->nChannels = (pj_uint16_t)prm->channel_count;
+	wfx->nSamplesPerSec = prm->clock_rate;
+	wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count;
+	wfx->nBlockAlign = (pj_uint16_t)(wfx->nAvgBytesPerSec * ptime /
+					 1000);
+	wfx->wBitsPerSample = 8;
+	wfx->cbSize = 0;
+
+	return PJ_SUCCESS;
+
+    } else {
+
+	return PJMEDIA_EAUD_BADFORMAT;
+
+    }
+}
+
+/* Get format name */
+static const char *get_fmt_name(pj_uint32_t id)
+{
+    static char name[8];
+
+    if (id == PJMEDIA_FORMAT_L16)
+	return "PCM";
+    pj_memcpy(name, &id, 4);
+    name[4] = '\0';
+    return name;
+}
+
+/* Internal: create WMME player device. */
+static pj_status_t init_player_stream(  struct wmme_factory *wf,
+				        pj_pool_t *pool,
+					struct wmme_stream *parent,
+				        struct wmme_channel *wmme_strm,
+					const pjmedia_aud_param *prm,
+					unsigned buffer_count)
+{
+    MMRESULT mr;
+    WAVEFORMATEX wfx; 
+    unsigned i, ptime;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL);
+
+    /*
+     * Create a wait event.
+     */
+    wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+    if (NULL == wmme_strm->hEvent)
+	return pj_get_os_error();
+
+    /*
+     * Set up wave format structure for opening the device.
+     */
+    status = init_waveformatex(&wfx, prm);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    ptime = prm->samples_per_frame * 1000 / 
+	    (prm->clock_rate * prm->channel_count);
+    parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
+
+    /*
+     * Open wave device.
+     */
+    mr = waveOutOpen(&wmme_strm->hWave.Out, 
+		     wf->dev_info[prm->play_id].deviceId,
+		     &wfx, (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
+    if (mr != MMSYSERR_NOERROR) {
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+    }
+
+    /* Pause the wave out device */
+    mr = waveOutPause(wmme_strm->hWave.Out);
+    if (mr != MMSYSERR_NOERROR) {
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+    }
+
+    /*
+     * Create the buffers. 
+     */
+    wmme_strm->WaveHdr = (WAVEHDR*)
+			 pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
+    for (i = 0; i < buffer_count; ++i) {
+	wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool, 
+						      parent->bytes_per_frame);
+	wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
+	mr = waveOutPrepareHeader(wmme_strm->hWave.Out, 
+				  &(wmme_strm->WaveHdr[i]),
+				  sizeof(WAVEHDR));
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); 
+	}
+	mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]), 
+			  sizeof(WAVEHDR));
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+	}
+    }
+
+    wmme_strm->dwBufIdx = 0;
+    wmme_strm->dwMaxBufIdx = buffer_count;
+    wmme_strm->timestamp.u64 = 0;
+
+    /* Done setting up play device. */
+    PJ_LOG(4, (THIS_FILE, 
+	       " WaveAPI Sound player \"%s\" initialized ("
+	       "format=%s, clock_rate=%d, "
+	       "channel_count=%d, samples_per_frame=%d (%dms))",
+	       wf->dev_info[prm->play_id].info.name,
+	       get_fmt_name(prm->ext_fmt.id),
+	       prm->clock_rate, prm->channel_count, prm->samples_per_frame,
+	       prm->samples_per_frame * 1000 / prm->clock_rate));
+
+    return PJ_SUCCESS;
+}
+
+
+/* Internal: create Windows Multimedia recorder device */
+static pj_status_t init_capture_stream( struct wmme_factory *wf,
+				        pj_pool_t *pool,
+					struct wmme_stream *parent,
+					struct wmme_channel *wmme_strm,
+					const pjmedia_aud_param *prm,
+					unsigned buffer_count)
+{
+    MMRESULT mr;
+    WAVEFORMATEX wfx; 
+    unsigned i, ptime;
+
+    PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL);
+
+    /*
+    * Create a wait event.
+    */
+    wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+    if (NULL == wmme_strm->hEvent) {
+	return pj_get_os_error();
+    }
+
+    /*
+     * Set up wave format structure for opening the device.
+     */
+    init_waveformatex(&wfx, prm);
+    ptime = prm->samples_per_frame * 1000 / 
+	    (prm->clock_rate * prm->channel_count);
+    parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
+
+    /*
+     * Open wave device.
+     */
+    mr = waveInOpen(&wmme_strm->hWave.In, 
+		    wf->dev_info[prm->rec_id].deviceId, 
+		    &wfx, (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
+    if (mr != MMSYSERR_NOERROR) {
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+    }
+
+    /*
+     * Create the buffers. 
+     */
+    wmme_strm->WaveHdr = (WAVEHDR*)
+			 pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
+    for (i = 0; i < buffer_count; ++i) {
+	wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool, 
+						      parent->bytes_per_frame);
+	wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
+	mr = waveInPrepareHeader(wmme_strm->hWave.In, 
+				 &(wmme_strm->WaveHdr[i]),
+				 sizeof(WAVEHDR));
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+	}
+	mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]), 
+			     sizeof(WAVEHDR));
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+	}
+    }
+
+    wmme_strm->dwBufIdx = 0;
+    wmme_strm->dwMaxBufIdx = buffer_count;
+    wmme_strm->timestamp.u64 = 0;
+
+    /* Done setting up play device. */
+    PJ_LOG(4,(THIS_FILE, 
+	" WaveAPI Sound recorder \"%s\" initialized "
+	"(format=%s, clock_rate=%d, "
+	"channel_count=%d, samples_per_frame=%d (%dms))",
+	wf->dev_info[prm->rec_id].info.name,
+	get_fmt_name(prm->ext_fmt.id),
+	prm->clock_rate, prm->channel_count, prm->samples_per_frame,
+	prm->samples_per_frame * 1000 / prm->clock_rate));
+
+    return PJ_SUCCESS;
+}
+
+
+/* WMME capture and playback thread. */
+static int PJ_THREAD_FUNC wmme_dev_thread(void *arg)
+{
+    struct wmme_stream *strm = (struct wmme_stream*)arg;
+    HANDLE events[3];
+    unsigned eventCount;
+    pj_status_t status = PJ_SUCCESS;
+    static unsigned rec_cnt, play_cnt;
+    enum { MAX_BURST = 1 };
+
+    rec_cnt = play_cnt = 0;
+
+    eventCount = 0;
+    events[eventCount++] = strm->thread_quit_event;
+    if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
+	events[eventCount++] = strm->play_strm.hEvent;
+    if (strm->param.dir & PJMEDIA_DIR_CAPTURE)
+	events[eventCount++] = strm->rec_strm.hEvent;
+
+
+    /* Raise self priority. We don't want the audio to be distorted by
+     * system activity.
+     */
+#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
+    if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
+	CeSetThreadPriority(GetCurrentThread(), 153);
+    else
+	CeSetThreadPriority(GetCurrentThread(), 247);
+#else
+    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
+#endif
+
+    /*
+     * Loop while not signalled to quit, wait for event objects to be 
+     * signalled by WMME capture and play buffer.
+     */
+    while (status == PJ_SUCCESS)
+    {
+
+	DWORD rc;
+	pjmedia_dir signalled_dir;
+
+	/* Swap hWaveIn and hWaveOut to get equal opportunity for both */
+	if (eventCount==3) {
+	    HANDLE hTemp = events[2];
+	    events[2] = events[1];
+	    events[1] = hTemp;
+	}
+
+	rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
+	if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount)
+	    continue;
+
+	if (rc == WAIT_OBJECT_0)
+	    break;
+
+	if (rc == (WAIT_OBJECT_0 + 1))
+	{
+	    if (events[1] == strm->play_strm.hEvent)
+		signalled_dir = PJMEDIA_DIR_PLAYBACK;
+	    else
+		signalled_dir = PJMEDIA_DIR_CAPTURE;
+	}
+	else
+	{
+	    if (events[2] == strm->play_strm.hEvent)
+		signalled_dir = PJMEDIA_DIR_PLAYBACK;
+	    else
+		signalled_dir = PJMEDIA_DIR_CAPTURE;
+	}
+
+
+	if (signalled_dir == PJMEDIA_DIR_PLAYBACK)
+	{
+	    struct wmme_channel *wmme_strm = &strm->play_strm;
+	    unsigned burst;
+
+	    status = PJ_SUCCESS;
+
+	    /*
+	     * Windows Multimedia has requested us to feed some frames to
+	     * playback buffer.
+	     */
+
+	    for (burst=0; burst<MAX_BURST &&
+		 (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
+	         ++burst)
+	    {
+		void *buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
+		pjmedia_frame pcm_frame, *frame;
+		MMRESULT mr = MMSYSERR_NOERROR;
+
+		//PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d", 
+		//	  wmme_strm->dwBufIdx));
+
+		if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+		    /* PCM mode */
+		    frame = &pcm_frame;
+
+		    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+		    frame->size = strm->bytes_per_frame;
+		    frame->buf = buffer;
+		    frame->timestamp.u64 = wmme_strm->timestamp.u64;
+		    frame->bit_info = 0;
+		} else {
+		    /* Codec mode */
+		    frame = &strm->xfrm->base;
+
+		    strm->xfrm->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		    strm->xfrm->base.size = strm->bytes_per_frame;
+		    strm->xfrm->base.buf = NULL;
+		    strm->xfrm->base.timestamp.u64 = wmme_strm->timestamp.u64;
+		    strm->xfrm->base.bit_info = 0;
+		}
+
+		/* Get frame from application. */
+		//PJ_LOG(5,(THIS_FILE, "xxx %u play_cb", play_cnt++));
+		status = (*strm->play_cb)(strm->user_data, frame);
+
+		if (status != PJ_SUCCESS)
+		    break;
+
+		if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+		    /* PCM mode */
+		    if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+			pj_bzero(buffer, strm->bytes_per_frame);
+		    } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+			pj_assert(!"Frame type not supported");
+		    } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+			/* Nothing to do */
+		    } else {
+			pj_assert(!"Frame type not supported");
+		    }
+		} else {
+		    /* Codec mode */
+		    if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+			pj_memset(buffer, strm->silence_char, 
+				  strm->bytes_per_frame);
+		    } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+			unsigned sz;
+			sz = pjmedia_frame_ext_copy_payload(strm->xfrm,
+							    buffer,
+							    strm->bytes_per_frame);
+			if (sz < strm->bytes_per_frame) {
+			    pj_memset((char*)buffer+sz,
+				      strm->silence_char,
+				      strm->bytes_per_frame - sz);
+			}
+		    } else {
+			pj_assert(!"Frame type not supported");
+		    }
+		}
+
+		/* Write to the device. */
+		mr = waveOutWrite(wmme_strm->hWave.Out, 
+				  &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
+				  sizeof(WAVEHDR));
+		if (mr != MMSYSERR_NOERROR) {
+		    status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+		    break;
+		}
+
+		/* Increment position. */
+		if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
+		    wmme_strm->dwBufIdx = 0;
+		wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
+					    strm->param.channel_count;
+	    } /* for */
+	}
+	else
+	{
+	    struct wmme_channel *wmme_strm = &strm->rec_strm;
+	    unsigned burst;
+	    MMRESULT mr = MMSYSERR_NOERROR;
+	    status = PJ_SUCCESS;
+
+	    /*
+	    * Windows Multimedia has indicated that it has some frames ready
+	    * in the capture buffer. Get as much frames as possible to
+	    * prevent overflows.
+	    */
+#if 0
+	    {
+		static DWORD tc = 0;
+		DWORD now = GetTickCount();
+		DWORD i = 0;
+		DWORD bits = 0;
+
+		if (tc == 0) tc = now;
+
+		for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i)
+		{
+		    bits = bits << 4;
+		    bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE;
+		}
+		PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, "
+			  "Flags: %6.6x\n",
+			  wmme_strm->dwBufIdx,
+			  now - tc,
+			  bits));
+		tc = now;
+	    }
+#endif
+
+	    for (burst=0; burst<MAX_BURST &&
+		 (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
+	         ++burst)
+	    {
+		char* buffer = (char*)
+			       wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
+		unsigned cap_len = 
+			wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded;
+		pjmedia_frame pcm_frame, *frame;
+
+		/*
+		PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len, 
+			  wmme_strm->dwBufIdx));
+		*/
+	    
+		if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+		    /* PCM mode */
+		    if (cap_len < strm->bytes_per_frame)
+			pj_bzero(buffer + cap_len, 
+				 strm->bytes_per_frame - cap_len);
+
+		    /* Copy the audio data out of the wave buffer. */
+		    pj_memcpy(strm->buffer, buffer, strm->bytes_per_frame);
+
+		    /* Prepare frame */
+		    frame = &pcm_frame;
+		    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+		    frame->buf = strm->buffer;
+		    frame->size = strm->bytes_per_frame;
+		    frame->timestamp.u64 = wmme_strm->timestamp.u64;
+		    frame->bit_info = 0;
+
+		} else {
+		    /* Codec mode */
+		    frame = &strm->xfrm->base;
+
+		    frame->type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		    frame->buf = NULL;
+		    frame->size = strm->bytes_per_frame;
+		    frame->timestamp.u64 = wmme_strm->timestamp.u64;
+		    frame->bit_info = 0;
+
+		    strm->xfrm->samples_cnt = 0;
+		    strm->xfrm->subframe_cnt = 0;
+		    pjmedia_frame_ext_append_subframe(
+			strm->xfrm, buffer,
+			strm->bytes_per_frame *8,
+			strm->param.samples_per_frame
+		    );
+		}
+
+		/* Re-add the buffer to the device. */
+		mr = waveInAddBuffer(wmme_strm->hWave.In, 
+				     &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]), 
+				     sizeof(WAVEHDR));
+		if (mr != MMSYSERR_NOERROR) {
+		    status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+		    break;
+		}
+
+
+		/* Call callback */
+		//PJ_LOG(5,(THIS_FILE, "xxx %u rec_cb", rec_cnt++));
+		status = (*strm->rec_cb)(strm->user_data, frame);
+		if (status != PJ_SUCCESS)
+		    break;
+
+		/* Increment position. */
+		if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
+		    wmme_strm->dwBufIdx = 0;
+		wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
+					    strm->param.channel_count;
+	    } /* for */
+	}
+    }
+
+    PJ_LOG(5,(THIS_FILE, "WMME: thread stopping.."));
+    return 0;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm)
+{
+    struct wmme_factory *wf = (struct wmme_factory*)f;
+    pj_pool_t *pool;
+    struct wmme_stream *strm;
+    pj_uint8_t silence_char;
+    pj_status_t status;
+
+    switch (param->ext_fmt.id) {
+    case PJMEDIA_FORMAT_L16:
+	silence_char = '\0';
+	break;
+    case PJMEDIA_FORMAT_ALAW:
+	silence_char = (pj_uint8_t)'\xd5';
+	break;
+    case PJMEDIA_FORMAT_ULAW:
+	silence_char = (pj_uint8_t)'\xff';
+	break;
+    default:
+	return PJMEDIA_EAUD_BADFORMAT;
+    }
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(wf->pf, "wmme-dev", 1000, 1000, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct wmme_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    strm->rec_cb = rec_cb;
+    strm->play_cb = play_cb;
+    strm->user_data = user_data;
+    strm->fmt_id = param->ext_fmt.id;
+    strm->silence_char = silence_char;
+
+    /* Create player stream */
+    if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+	unsigned buf_count;
+
+	if ((param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)==0) {
+	    strm->param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+	    strm->param.output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+	}
+
+	buf_count = strm->param.output_latency_ms * param->clock_rate * 
+		    param->channel_count / param->samples_per_frame / 1000;
+
+	status = init_player_stream(wf, strm->pool,
+				    strm,
+				    &strm->play_strm,
+				    param,
+				    buf_count);
+
+	if (status != PJ_SUCCESS) {
+	    stream_destroy(&strm->base);
+	    return status;
+	}
+    }
+
+    /* Create capture stream */
+    if (param->dir & PJMEDIA_DIR_CAPTURE) {
+	unsigned buf_count;
+
+	if ((param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)==0) {
+	    strm->param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+	    strm->param.input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+	}
+
+	buf_count = strm->param.input_latency_ms * param->clock_rate * 
+		    param->channel_count / param->samples_per_frame / 1000;
+
+	status = init_capture_stream(wf, strm->pool,
+				     strm,
+				     &strm->rec_strm,
+				     param,
+				     buf_count);
+
+	if (status != PJ_SUCCESS) {
+	    stream_destroy(&strm->base);
+	    return status;
+	}
+    }
+
+    strm->buffer = pj_pool_alloc(pool, strm->bytes_per_frame);
+    if (!strm->buffer) {
+	pj_pool_release(pool);
+	return PJ_ENOMEM;
+    }
+
+    /* If format is extended, must create buffer for the extended frame. */
+    if (strm->fmt_id != PJMEDIA_FORMAT_L16) {
+	strm->xfrm_size = sizeof(pjmedia_frame_ext) + 
+			  32 * sizeof(pjmedia_frame_ext_subframe) +
+			  strm->bytes_per_frame + 4;
+	strm->xfrm = (pjmedia_frame_ext*)
+		     pj_pool_alloc(pool, strm->xfrm_size);
+    }
+
+    /* Create the stop event */
+    strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+    if (strm->thread_quit_event == NULL) {
+	status = pj_get_os_error();
+	stream_destroy(&strm->base);
+	return status;
+    }
+
+    /* Create and start the thread */
+    status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0, 
+			      &strm->thread);
+    if (status != PJ_SUCCESS) {
+	stream_destroy(&strm->base);
+	return status;
+    }
+
+    /* Apply the remaining settings */
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+	stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+		       &param->output_vol);
+    }
+
+
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_aud_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+				    pjmedia_aud_param *pi)
+{
+    struct wmme_stream *strm = (struct wmme_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+    
+    /* Update the volume setting */
+    if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+		      &pi->output_vol) == PJ_SUCCESS)
+    {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  void *pval)
+{
+    struct wmme_stream *strm = (struct wmme_stream*)s;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && 
+	(strm->param.dir & PJMEDIA_DIR_CAPTURE)) 
+    {
+	/* Recording latency */
+	*(unsigned*)pval = strm->param.input_latency_ms;
+	return PJ_SUCCESS;
+    } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY  && 
+	       (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+    {
+	/* Playback latency */
+	*(unsigned*)pval = strm->param.output_latency_ms;
+	return PJ_SUCCESS;
+    } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+	       strm->play_strm.hWave.Out)
+    {
+	/* Output volume setting */
+	DWORD waveVol;
+	MMRESULT mr;
+
+	mr = waveOutGetVolume(strm->play_strm.hWave.Out, &waveVol);
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+	}
+
+	waveVol &= 0xFFFF;
+	*(unsigned*)pval = (waveVol * 100) / 0xFFFF;
+	return PJ_SUCCESS;
+    } else {
+	return PJMEDIA_EAUD_INVCAP;
+    }
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  const void *pval)
+{
+    struct wmme_stream *strm = (struct wmme_stream*)s;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+	strm->play_strm.hWave.Out)
+    {
+	/* Output volume setting */
+	unsigned vol = *(unsigned*)pval;
+	DWORD waveVol;
+	MMRESULT mr;
+	pj_status_t status;
+
+	if (vol > 100)
+	    vol = 100;
+
+	waveVol = (vol * 0xFFFF) / 100;
+	waveVol |= (waveVol << 16);
+
+	mr = waveOutSetVolume(strm->play_strm.hWave.Out, waveVol);
+	status = (mr==MMSYSERR_NOERROR)? PJ_SUCCESS : 
+				PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+	if (status == PJ_SUCCESS) {
+	    strm->param.output_vol = *(unsigned*)pval;
+	}
+	return status;
+    }
+
+    return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+    struct wmme_stream *stream = (struct wmme_stream*)strm;
+    MMRESULT mr;
+
+    if (stream->play_strm.hWave.Out != NULL)
+    {
+	mr = waveOutRestart(stream->play_strm.hWave.Out);
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+	}
+	PJ_LOG(4,(THIS_FILE, "WMME playback stream started"));
+    }
+
+    if (stream->rec_strm.hWave.In != NULL)
+    {
+	mr = waveInStart(stream->rec_strm.hWave.In);
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+	}
+	PJ_LOG(4,(THIS_FILE, "WMME capture stream started"));
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+    struct wmme_stream *stream = (struct wmme_stream*)strm;
+    MMRESULT mr;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    if (stream->play_strm.hWave.Out != NULL)
+    {
+	mr = waveOutPause(stream->play_strm.hWave.Out);
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+	}
+	PJ_LOG(4,(THIS_FILE, "Stopped WMME playback stream"));
+    }
+
+    if (stream->rec_strm.hWave.In != NULL)
+    {
+	mr = waveInStop(stream->rec_strm.hWave.In);
+	if (mr != MMSYSERR_NOERROR) {
+	    return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+	}
+	PJ_LOG(4,(THIS_FILE, "Stopped WMME capture stream"));
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+    struct wmme_stream *stream = (struct wmme_stream*)strm;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    stream_stop(strm);
+
+    if (stream->thread)
+    {
+	SetEvent(stream->thread_quit_event);
+	pj_thread_join(stream->thread);
+	pj_thread_destroy(stream->thread);
+	stream->thread = NULL;
+    }
+
+    /* Unprepare the headers and close the play device */
+    if (stream->play_strm.hWave.Out)
+    {
+	waveOutReset(stream->play_strm.hWave.Out);
+	for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
+	    waveOutUnprepareHeader(stream->play_strm.hWave.Out, 
+				   &(stream->play_strm.WaveHdr[i]),
+				   sizeof(WAVEHDR));
+	waveOutClose(stream->play_strm.hWave.Out);
+	stream->play_strm.hWave.Out = NULL;
+    }
+
+    /* Close the play event */
+    if (stream->play_strm.hEvent)
+    {
+	CloseHandle(stream->play_strm.hEvent);
+	stream->play_strm.hEvent = NULL;
+    }
+
+    /* Unprepare the headers and close the record device */
+    if (stream->rec_strm.hWave.In)
+    {
+	waveInReset(stream->rec_strm.hWave.In);
+	for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
+	    waveInUnprepareHeader(stream->rec_strm.hWave.In, 
+				  &(stream->rec_strm.WaveHdr[i]),
+				  sizeof(WAVEHDR));
+	waveInClose(stream->rec_strm.hWave.In);
+	stream->rec_strm.hWave.In = NULL;
+    }
+
+    /* Close the record event */
+    if (stream->rec_strm.hEvent)
+    {
+	CloseHandle(stream->rec_strm.hEvent);
+	stream->rec_strm.hEvent = NULL;
+    }
+
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_AUDIO_DEV_HAS_WMME */
+
Index: pjproject/trunk/pjmedia/src/pjmedia-codec/passthrough.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-codec/passthrough.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia-codec/passthrough.c	(revision 2506)
@@ -0,0 +1,850 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia-codec/passthrough.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/port.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+/*
+ * Only build this file if PJMEDIA_HAS_PASSTHROUGH_CODECS != 0
+ */
+#if defined(PJMEDIA_HAS_PASSTHROUGH_CODECS) && PJMEDIA_HAS_PASSTHROUGH_CODECS!=0
+
+#define THIS_FILE   "passthrough.c"
+
+
+/* Prototypes for passthrough codecs factory */
+static pj_status_t test_alloc( pjmedia_codec_factory *factory, 
+			       const pjmedia_codec_info *id );
+static pj_status_t default_attr( pjmedia_codec_factory *factory, 
+				 const pjmedia_codec_info *id, 
+				 pjmedia_codec_param *attr );
+static pj_status_t enum_codecs( pjmedia_codec_factory *factory, 
+				unsigned *count, 
+				pjmedia_codec_info codecs[]);
+static pj_status_t alloc_codec( pjmedia_codec_factory *factory, 
+				const pjmedia_codec_info *id, 
+				pjmedia_codec **p_codec);
+static pj_status_t dealloc_codec( pjmedia_codec_factory *factory, 
+				  pjmedia_codec *codec );
+
+/* Prototypes for passthrough codecs implementation. */
+static pj_status_t codec_init( pjmedia_codec *codec, 
+			       pj_pool_t *pool );
+static pj_status_t codec_open( pjmedia_codec *codec, 
+			       pjmedia_codec_param *attr );
+static pj_status_t codec_close( pjmedia_codec *codec );
+static pj_status_t codec_modify(pjmedia_codec *codec, 
+			        const pjmedia_codec_param *attr );
+static pj_status_t codec_parse( pjmedia_codec *codec,
+			        void *pkt,
+				pj_size_t pkt_size,
+				const pj_timestamp *ts,
+				unsigned *frame_cnt,
+				pjmedia_frame frames[]);
+static pj_status_t codec_encode( pjmedia_codec *codec, 
+				 const struct pjmedia_frame *input,
+				 unsigned output_buf_len,
+				 struct pjmedia_frame *output);
+static pj_status_t codec_decode( pjmedia_codec *codec,
+				 const struct pjmedia_frame *input,
+				 unsigned output_buf_len, 
+				 struct pjmedia_frame *output);
+static pj_status_t codec_recover( pjmedia_codec *codec, 
+				  unsigned output_buf_len, 
+				  struct pjmedia_frame *output);
+
+/* Definition for passthrough codecs operations. */
+static pjmedia_codec_op codec_op = 
+{
+    &codec_init,
+    &codec_open,
+    &codec_close,
+    &codec_modify,
+    &codec_parse,
+    &codec_encode,
+    &codec_decode,
+    &codec_recover
+};
+
+/* Definition for passthrough codecs factory operations. */
+static pjmedia_codec_factory_op codec_factory_op =
+{
+    &test_alloc,
+    &default_attr,
+    &enum_codecs,
+    &alloc_codec,
+    &dealloc_codec
+};
+
+/* Passthrough codecs factory */
+static struct codec_factory {
+    pjmedia_codec_factory    base;
+    pjmedia_endpt	    *endpt;
+    pj_pool_t		    *pool;
+    pj_mutex_t		    *mutex;
+} codec_factory;
+
+/* Passthrough codecs private data. */
+typedef struct codec_private {
+    pj_pool_t		*pool;		    /**< Pool for each instance.    */
+    int			 codec_idx;	    /**< Codec index.		    */
+    void		*codec_setting;	    /**< Specific codec setting.    */
+    pj_uint16_t		 avg_frame_size;    /**< Average of frame size.	    */
+} codec_private_t;
+
+
+
+/* CUSTOM CALLBACKS */
+
+/* Parse frames from a packet. Default behaviour of frame parsing is 
+ * just separating frames based on calculating frame length derived 
+ * from bitrate. Implement this callback when the default behaviour is 
+ * unapplicable.
+ */
+typedef pj_status_t (*parse_cb)(codec_private_t *codec_data, void *pkt, 
+				pj_size_t pkt_size, const pj_timestamp *ts,
+				unsigned *frame_cnt, pjmedia_frame frames[]);
+
+/* Pack frames into a packet. Default behaviour of packing frames is 
+ * just stacking the frames with octet aligned without adding any 
+ * payload header. Implement this callback when the default behaviour is
+ * unapplicable.
+ */
+typedef pj_status_t (*pack_cb)(codec_private_t *codec_data, 
+			       const struct pjmedia_frame_ext *input,
+			       unsigned output_buf_len, 
+			       struct pjmedia_frame *output);
+
+
+/* Custom callback implementations. */
+static pj_status_t parse_amr( codec_private_t *codec_data, void *pkt, 
+			      pj_size_t pkt_size, const pj_timestamp *ts,
+			      unsigned *frame_cnt, pjmedia_frame frames[]);
+static pj_status_t pack_amr ( codec_private_t *codec_data,
+			      const struct pjmedia_frame_ext *input,
+			      unsigned output_buf_len, 
+			      struct pjmedia_frame *output);
+
+
+/* Passthrough codec implementation descriptions. */
+static struct codec_desc {
+    int		     enabled;		/* Is this codec enabled?	    */
+    const char	    *name;		/* Codec name.			    */
+    pj_uint8_t	     pt;		/* Payload type.		    */
+    pjmedia_format_id fmt_id;		/* Source format.		    */
+    unsigned	     clock_rate;	/* Codec's clock rate.		    */
+    unsigned	     channel_count;	/* Codec's channel count.	    */
+    unsigned	     samples_per_frame;	/* Codec's samples count.	    */
+    unsigned	     def_bitrate;	/* Default bitrate of this codec.   */
+    unsigned	     max_bitrate;	/* Maximum bitrate of this codec.   */
+    pj_uint8_t	     frm_per_pkt;	/* Default num of frames per packet.*/
+    pj_uint8_t	     vad;		/* VAD enabled/disabled.	    */
+    pj_uint8_t	     plc;		/* PLC enabled/disabled.	    */
+    parse_cb	     parse;		/* Callback to parse bitstream.	    */
+    pack_cb	     pack;		/* Callback to pack bitstream.	    */
+    pjmedia_codec_fmtp dec_fmtp;	/* Decoder's fmtp params.	    */
+}
+
+codec_desc[] = 
+{
+#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+    {1, "AMR",	    PJMEDIA_RTP_PT_AMR,       PJMEDIA_FORMAT_AMR,
+		    8000, 1, 160, 
+		    7400, 12200, 2, 1, 1,
+		    &parse_amr, &pack_amr
+		    /*, {1, {{{"octet-align", 11}, {"1", 1}}} } */
+    },
+#   endif
+
+#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+    {1, "G729",	    PJMEDIA_RTP_PT_G729,      PJMEDIA_FORMAT_G729,
+		    8000, 1,  80,
+		    8000, 8000, 2, 1, 1
+    },
+#   endif
+
+#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+    {1, "iLBC",	    PJMEDIA_RTP_PT_ILBC,      PJMEDIA_FORMAT_ILBC,
+		    8000, 1,  240,
+		    13333, 15200, 1, 1, 1,
+		    NULL, NULL,
+		    {1, {{{"mode", 4}, {"30", 2}}} }
+    },
+#   endif
+
+#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU
+    {1, "PCMU",	    PJMEDIA_RTP_PT_PCMU,      PJMEDIA_FORMAT_PCMU,
+		    8000, 1,  80,
+		    64000, 64000, 2, 1, 1
+    },
+#   endif
+
+#   if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA
+    {1, "PCMA",	    PJMEDIA_RTP_PT_PCMA,      PJMEDIA_FORMAT_PCMA,
+		    8000, 1,  80,
+		    64000, 64000, 2, 1, 1
+    },
+#   endif
+};
+
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+
+#include <pjmedia-codec/amr_helper.h>
+
+typedef struct amr_settings_t {
+    pjmedia_codec_amr_pack_setting enc_setting;
+    pjmedia_codec_amr_pack_setting dec_setting;
+    pj_int8_t enc_mode;
+} amr_settings_t;
+
+
+/* Pack AMR payload */
+static pj_status_t pack_amr ( codec_private_t *codec_data,
+			      const struct pjmedia_frame_ext *input,
+			      unsigned output_buf_len, 
+			      struct pjmedia_frame *output)
+{
+    enum {MAX_FRAMES_PER_PACKET = 8};
+
+    pjmedia_frame frames[MAX_FRAMES_PER_PACKET];
+    amr_settings_t* setting = (amr_settings_t*)codec_data->codec_setting;
+    pjmedia_codec_amr_pack_setting *enc_setting = &setting->enc_setting;
+    pj_uint8_t SID_FT;
+    unsigned i;
+
+    pj_assert(input->subframe_cnt <= MAX_FRAMES_PER_PACKET);
+
+    SID_FT = (pj_uint8_t)(enc_setting->amr_nb? 8 : 9);
+
+    /* Get frames */
+    for (i = 0; i < input->subframe_cnt; ++i) {
+	pjmedia_frame_ext_subframe *sf;
+	pjmedia_codec_amr_bit_info *info;
+	unsigned len;
+	
+	sf = pjmedia_frame_ext_get_subframe(input, i);
+	len = (sf->bitlen + 7) >> 3;
+	
+	info = (pjmedia_codec_amr_bit_info*) &frames[i].bit_info;
+	pj_bzero(info, sizeof(*info));
+	
+	if (len == 0) {
+	    info->frame_type = (pj_uint8_t)(enc_setting->amr_nb? 14 : 15);
+	} else {
+	    info->frame_type = pjmedia_codec_amr_get_mode2(enc_setting->amr_nb, 
+							   len);
+	}
+	info->good_quality = 1;
+	info->mode = setting->enc_mode;
+
+	frames[i].buf = sf->data;
+	frames[i].size = len;
+    }
+
+    output->size = output_buf_len;
+
+    return pjmedia_codec_amr_pack(frames, input->subframe_cnt, enc_setting, 
+				  output->buf, &output->size);
+}
+
+
+/* Parse AMR payload into frames. */
+static pj_status_t parse_amr(codec_private_t *codec_data, void *pkt, 
+			     pj_size_t pkt_size, const pj_timestamp *ts,
+			     unsigned *frame_cnt, pjmedia_frame frames[])
+{
+    amr_settings_t* s = (amr_settings_t*)codec_data->codec_setting;
+    pjmedia_codec_amr_pack_setting *setting;
+    pj_status_t status;
+    pj_uint8_t cmr;
+
+    setting = &s->dec_setting;
+
+    status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, setting, frames, 
+				     frame_cnt, &cmr);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    // CMR is not supported for now. 
+    /* Check Change Mode Request. */
+    //if ((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) {
+    //	s->enc_mode = cmr;
+    //}
+
+    return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_PASSTROUGH_CODEC_AMR */
+
+
+/*
+ * Initialize and register passthrough codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_passthrough_init( pjmedia_endpt *endpt )
+{
+    pjmedia_codec_mgr *codec_mgr;
+    pj_status_t status;
+
+    if (codec_factory.pool != NULL) {
+	/* Already initialized. */
+	return PJ_SUCCESS;
+    }
+
+    /* Create passthrough codec factory. */
+    codec_factory.base.op = &codec_factory_op;
+    codec_factory.base.factory_data = NULL;
+    codec_factory.endpt = endpt;
+
+    codec_factory.pool = pjmedia_endpt_create_pool(endpt, "Passthrough codecs",
+						   4000, 4000);
+    if (!codec_factory.pool)
+	return PJ_ENOMEM;
+
+    /* Create mutex. */
+    status = pj_mutex_create_simple(codec_factory.pool, "Passthrough codecs",
+				    &codec_factory.mutex);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Get the codec manager. */
+    codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+    if (!codec_mgr) {
+	status = PJ_EINVALIDOP;
+	goto on_error;
+    }
+
+    /* Register codec factory to endpoint. */
+    status = pjmedia_codec_mgr_register_factory(codec_mgr, 
+						&codec_factory.base);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Done. */
+    return PJ_SUCCESS;
+
+on_error:
+    pj_pool_release(codec_factory.pool);
+    codec_factory.pool = NULL;
+    return status;
+}
+
+/*
+ * Unregister passthrough codecs factory from pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_passthrough_deinit(void)
+{
+    pjmedia_codec_mgr *codec_mgr;
+    pj_status_t status;
+
+    if (codec_factory.pool == NULL) {
+	/* Already deinitialized */
+	return PJ_SUCCESS;
+    }
+
+    pj_mutex_lock(codec_factory.mutex);
+
+    /* Get the codec manager. */
+    codec_mgr = pjmedia_endpt_get_codec_mgr(codec_factory.endpt);
+    if (!codec_mgr) {
+	pj_pool_release(codec_factory.pool);
+	codec_factory.pool = NULL;
+	return PJ_EINVALIDOP;
+    }
+
+    /* Unregister passthrough codecs factory. */
+    status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+						  &codec_factory.base);
+    
+    /* Destroy mutex. */
+    pj_mutex_destroy(codec_factory.mutex);
+
+    /* Destroy pool. */
+    pj_pool_release(codec_factory.pool);
+    codec_factory.pool = NULL;
+
+    return status;
+}
+
+/* 
+ * Check if factory can allocate the specified codec. 
+ */
+static pj_status_t test_alloc( pjmedia_codec_factory *factory, 
+			       const pjmedia_codec_info *info )
+{
+    unsigned i;
+
+    PJ_UNUSED_ARG(factory);
+
+    /* Type MUST be audio. */
+    if (info->type != PJMEDIA_TYPE_AUDIO)
+	return PJMEDIA_CODEC_EUNSUP;
+
+    for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+	pj_str_t name = pj_str((char*)codec_desc[i].name);
+	if ((pj_stricmp(&info->encoding_name, &name) == 0) &&
+	    (info->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+	    (info->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+	    (codec_desc[i].enabled))
+	{
+	    return PJ_SUCCESS;
+	}
+    }
+    
+    /* Unsupported, or mode is disabled. */
+    return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t default_attr ( pjmedia_codec_factory *factory, 
+				  const pjmedia_codec_info *id, 
+				  pjmedia_codec_param *attr )
+{
+    unsigned i;
+
+    PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL);
+
+    pj_bzero(attr, sizeof(pjmedia_codec_param));
+
+    for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+	pj_str_t name = pj_str((char*)codec_desc[i].name);
+	if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
+	    (id->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+	    (id->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+	    (id->pt == (unsigned)codec_desc[i].pt))
+	{
+	    attr->info.pt = (pj_uint8_t)id->pt;
+	    attr->info.channel_cnt = codec_desc[i].channel_count;
+	    attr->info.clock_rate = codec_desc[i].clock_rate;
+	    attr->info.avg_bps = codec_desc[i].def_bitrate;
+	    attr->info.max_bps = codec_desc[i].max_bitrate;
+	    attr->info.pcm_bits_per_sample = 16;
+	    attr->info.frm_ptime =  (pj_uint16_t)
+				    (codec_desc[i].samples_per_frame * 1000 / 
+				    codec_desc[i].channel_count / 
+				    codec_desc[i].clock_rate);
+	    attr->info.fmt_id = codec_desc[i].fmt_id;
+
+	    /* Default flags. */
+	    attr->setting.frm_per_pkt = codec_desc[i].frm_per_pkt;
+	    attr->setting.plc = codec_desc[i].plc;
+	    attr->setting.penh= 0;
+	    attr->setting.vad = codec_desc[i].vad;
+	    attr->setting.cng = attr->setting.vad;
+	    attr->setting.dec_fmtp = codec_desc[i].dec_fmtp;
+
+	    if (attr->setting.vad == 0) {
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+		if (id->pt == PJMEDIA_RTP_PT_G729) {
+		    /* Signal G729 Annex B is being disabled */
+		    attr->setting.dec_fmtp.cnt = 1;
+		    pj_strset2(&attr->setting.dec_fmtp.param[0].name, "annexb");
+		    pj_strset2(&attr->setting.dec_fmtp.param[0].val, "no");
+		}
+#endif
+	    }
+
+	    return PJ_SUCCESS;
+	}
+    }
+
+    return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Enum codecs supported by this factory.
+ */
+static pj_status_t enum_codecs( pjmedia_codec_factory *factory, 
+				unsigned *count, 
+				pjmedia_codec_info codecs[])
+{
+    unsigned max;
+    unsigned i;
+
+    PJ_UNUSED_ARG(factory);
+    PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+    max = *count;
+    
+    for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(codec_desc) && *count < max; ++i) 
+    {
+	if (!codec_desc[i].enabled)
+	    continue;
+
+	pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info));
+	codecs[*count].encoding_name = pj_str((char*)codec_desc[i].name);
+	codecs[*count].pt = codec_desc[i].pt;
+	codecs[*count].type = PJMEDIA_TYPE_AUDIO;
+	codecs[*count].clock_rate = codec_desc[i].clock_rate;
+	codecs[*count].channel_cnt = codec_desc[i].channel_count;
+
+	++*count;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new codec instance.
+ */
+static pj_status_t alloc_codec( pjmedia_codec_factory *factory, 
+				const pjmedia_codec_info *id,
+				pjmedia_codec **p_codec)
+{
+    codec_private_t *codec_data;
+    pjmedia_codec *codec;
+    int idx;
+    pj_pool_t *pool;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+    PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);
+
+    pj_mutex_lock(codec_factory.mutex);
+
+    /* Find codec's index */
+    idx = -1;
+    for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+	pj_str_t name = pj_str((char*)codec_desc[i].name);
+	if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
+	    (id->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+	    (id->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+	    (codec_desc[i].enabled))
+	{
+	    idx = i;
+	    break;
+	}
+    }
+    if (idx == -1) {
+	*p_codec = NULL;
+	return PJMEDIA_CODEC_EUNSUP;
+    }
+
+    /* Create pool for codec instance */
+    pool = pjmedia_endpt_create_pool(codec_factory.endpt, "passthroughcodec",
+				     512, 512);
+    codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
+    codec->op = &codec_op;
+    codec->factory = factory;
+    codec->codec_data = PJ_POOL_ZALLOC_T(pool, codec_private_t);
+    codec_data = (codec_private_t*) codec->codec_data;
+    codec_data->pool = pool;
+    codec_data->codec_idx = idx;
+
+    pj_mutex_unlock(codec_factory.mutex);
+
+    *p_codec = codec;
+    return PJ_SUCCESS;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t dealloc_codec( pjmedia_codec_factory *factory, 
+				  pjmedia_codec *codec )
+{
+    codec_private_t *codec_data;
+
+    PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+    PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);
+
+    /* Close codec, if it's not closed. */
+    codec_data = (codec_private_t*) codec->codec_data;
+    codec_close(codec);
+
+    pj_pool_release(codec_data->pool);
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t codec_init( pjmedia_codec *codec, 
+			       pj_pool_t *pool )
+{
+    PJ_UNUSED_ARG(codec);
+    PJ_UNUSED_ARG(pool);
+    return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t codec_open( pjmedia_codec *codec, 
+			       pjmedia_codec_param *attr )
+{
+    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+    pj_pool_t *pool;
+    int i, j;
+
+    pool = codec_data->pool;
+
+    /* Get bitstream size */
+    i = attr->info.avg_bps * desc->samples_per_frame;
+    j = desc->clock_rate << 3;
+    codec_data->avg_frame_size = (pj_uint16_t)(i / j);
+    if (i % j) ++codec_data->avg_frame_size;
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+    /* Init AMR settings */
+    if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) {
+	amr_settings_t *s;
+	pj_uint8_t octet_align = 0;
+	const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11};
+
+	/* Fetch octet-align setting. It should be fine to fetch only 
+	 * the decoder, since encoder & decoder must use the same setting 
+	 * (RFC 4867 section 8.3.1).
+	 */
+	for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
+	    if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, 
+			   &STR_FMTP_OCTET_ALIGN) == 0)
+	    {
+		octet_align=(pj_uint8_t)
+			    (pj_strtoul(&attr->setting.dec_fmtp.param[i].val));
+		break;
+	    }
+	}
+
+	s = PJ_POOL_ZALLOC_T(pool, amr_settings_t);
+	codec_data->codec_setting = s;
+
+	s->enc_mode = pjmedia_codec_amr_get_mode(desc->def_bitrate);
+	if (s->enc_mode < 0)
+	    return PJMEDIA_CODEC_EINMODE;
+
+	s->enc_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR);
+	s->enc_setting.octet_aligned = octet_align;
+	s->enc_setting.reorder = PJ_FALSE; /* Note this! passthrough codec
+					      doesn't do sensitivity bits 
+					      reordering */
+	s->enc_setting.cmr = 15;
+	
+	s->dec_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR);
+	s->dec_setting.octet_aligned = octet_align;
+	s->dec_setting.reorder = PJ_FALSE; /* Note this! passthrough codec
+					      doesn't do sensitivity bits 
+					      reordering */
+    }
+#endif
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t codec_close( pjmedia_codec *codec )
+{
+    PJ_UNUSED_ARG(codec);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t codec_modify( pjmedia_codec *codec, 
+				 const pjmedia_codec_param *attr )
+{
+    /* Not supported yet. */
+    PJ_UNUSED_ARG(codec);
+    PJ_UNUSED_ARG(attr);
+
+    return PJ_ENOTSUP;
+}
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t codec_parse( pjmedia_codec *codec,
+				void *pkt,
+				pj_size_t pkt_size,
+				const pj_timestamp *ts,
+				unsigned *frame_cnt,
+				pjmedia_frame frames[])
+{
+    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+    unsigned count = 0;
+
+    PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+    if (desc->parse != NULL) {
+	return desc->parse(codec_data, pkt,  pkt_size, ts, frame_cnt, frames);
+    }
+
+    while (pkt_size >= codec_data->avg_frame_size && count < *frame_cnt) {
+	frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+	frames[count].buf = pkt;
+	frames[count].size = codec_data->avg_frame_size;
+	frames[count].timestamp.u64 = ts->u64 + count*desc->samples_per_frame;
+
+	pkt = (pj_uint8_t*)pkt + codec_data->avg_frame_size;
+	pkt_size -= codec_data->avg_frame_size;
+
+	++count;
+    }
+
+    if (pkt_size && count < *frame_cnt) {
+	frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+	frames[count].buf = pkt;
+	frames[count].size = pkt_size;
+	frames[count].timestamp.u64 = ts->u64 + count*desc->samples_per_frame;
+	++count;
+    }
+
+    *frame_cnt = count;
+    return PJ_SUCCESS;
+}
+
+/*
+ * Encode frames.
+ */
+static pj_status_t codec_encode( pjmedia_codec *codec, 
+				 const struct pjmedia_frame *input,
+				 unsigned output_buf_len, 
+				 struct pjmedia_frame *output)
+{
+    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+    const pjmedia_frame_ext *input_ = (const pjmedia_frame_ext*) input;
+
+    pj_assert(input && input->type == PJMEDIA_FRAME_TYPE_EXTENDED);
+
+    if (desc->pack != NULL) {
+	desc->pack(codec_data, input_, output_buf_len, output);
+    } else {
+	if (input_->subframe_cnt == 0) {
+	    /* DTX */
+	    output->buf = NULL;
+	    output->size = 0;
+	    output->type = PJMEDIA_FRAME_TYPE_NONE;
+	} else {
+	    unsigned i;
+	    pj_uint8_t *p = output->buf;
+
+	    output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    output->size = 0;
+	    
+	    for (i = 0; i < input_->subframe_cnt; ++i) {
+		pjmedia_frame_ext_subframe *sf;
+		unsigned sf_len;
+
+		sf = pjmedia_frame_ext_get_subframe(input_, i);
+		pj_assert(sf);
+
+		sf_len = (sf->bitlen + 7) >> 3;
+
+		pj_memcpy(p, sf->data, sf_len);
+		p += sf_len;
+		output->size += sf_len;
+
+		/* If there is SID or DTX frame, break the loop. */
+		if (desc->pt == PJMEDIA_RTP_PT_G729 && 
+		    sf_len < codec_data->avg_frame_size)
+		{
+		    break;
+		}
+		
+	    }
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t codec_decode( pjmedia_codec *codec, 
+				 const struct pjmedia_frame *input,
+				 unsigned output_buf_len, 
+				 struct pjmedia_frame *output)
+{
+    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+    pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output;
+
+    pj_assert(input);
+    PJ_UNUSED_ARG(output_buf_len);
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+    /* Need to rearrange the AMR bitstream, since the bitstream may not be 
+     * started from bit 0 or may need to be reordered from sensitivity order 
+     * into encoder bits order.
+     */
+    if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) {
+	pjmedia_frame input_;
+	pjmedia_codec_amr_pack_setting *setting;
+
+	setting = &((amr_settings_t*)codec_data->codec_setting)->dec_setting;
+
+	input_ = *input;
+	pjmedia_codec_amr_predecode(input, setting, &input_);
+	
+	pjmedia_frame_ext_append_subframe(output_, input_.buf, 
+					  (pj_uint16_t)(input_.size << 3),
+					  (pj_uint16_t)desc->samples_per_frame);
+	
+	return PJ_SUCCESS;
+    }
+#endif
+    
+    pjmedia_frame_ext_append_subframe(output_, input->buf, 
+				      (pj_uint16_t)(input->size << 3),
+				      (pj_uint16_t)desc->samples_per_frame);
+
+    return PJ_SUCCESS;
+}
+
+/* 
+ * Recover lost frame.
+ */
+static pj_status_t codec_recover( pjmedia_codec *codec, 
+				  unsigned output_buf_len, 
+				  struct pjmedia_frame *output)
+{
+    codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+    struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+    pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output;
+
+    PJ_UNUSED_ARG(output_buf_len);
+
+    pjmedia_frame_ext_append_subframe(output_, NULL, 0,
+				      (pj_uint16_t)desc->samples_per_frame);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_HAS_PASSTHROUGH_CODECS */
+
Index: pjproject/trunk/pjmedia/src/pjmedia/conf_switch.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/conf_switch.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia/conf_switch.c	(revision 2506)
@@ -0,0 +1,1466 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia/conference.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pjmedia/sound_port.h>
+#include <pjmedia/stream.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if defined(PJMEDIA_CONF_USE_SWITCH_BOARD) && PJMEDIA_CONF_USE_SWITCH_BOARD!=0
+
+/* 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)   PJ_LOG(5,x)
+#else
+#   define TRACE_(x)
+#endif
+
+#define THIS_FILE	    "conf_switch.c"
+
+#define SIGNATURE	    PJMEDIA_CONF_SWITCH_SIGNATURE
+#define SIGNATURE_PORT	    PJMEDIA_PORT_SIGNATURE('S', 'W', 'T', 'P')
+#define NORMAL_LEVEL	    128
+#define SLOT_TYPE	    unsigned
+#define INVALID_SLOT	    ((SLOT_TYPE)-1)
+#define BUFFER_SIZE	    PJMEDIA_MAX_MTU
+#define MAX_LEVEL	    (32767)
+#define MIN_LEVEL	    (-32768)
+
+/*
+ * DON'T GET CONFUSED WITH TX/RX!!
+ *
+ * TX and RX directions are always viewed from the conference bridge's point
+ * of view, and NOT from the port's point of view. So TX means the bridge
+ * is transmitting to the port, RX means the bridge is receiving from the
+ * port.
+ */
+
+
+/**
+ * This is a port connected to conference bridge.
+ */
+struct conf_port
+{
+    SLOT_TYPE		 slot;		/**< Array of listeners.	    */
+    pj_str_t		 name;		/**< Port name.			    */
+    pjmedia_port	*port;		/**< get_frame() and put_frame()    */
+    pjmedia_port_op	 rx_setting;	/**< Can we receive from this port  */
+    pjmedia_port_op	 tx_setting;	/**< Can we transmit to this port   */
+    unsigned		 listener_cnt;	/**< Number of listeners.	    */
+    SLOT_TYPE		*listener_slots;/**< Array of listeners.	    */
+    unsigned		 transmitter_cnt;/**<Number of transmitters.	    */
+
+    /* Shortcut for port info. */
+    pjmedia_port_info	*info;
+
+    /* Calculated signal levels: */
+    unsigned		 tx_level;	/**< Last tx level to this port.    */
+    unsigned		 rx_level;	/**< Last rx level from this port.  */
+
+    /* The normalized signal level adjustment.
+     * A value of 128 (NORMAL_LEVEL) means there's no adjustment.
+     */
+    unsigned		 tx_adj_level;	/**< Adjustment for TX.		    */
+    unsigned		 rx_adj_level;	/**< Adjustment for RX.		    */
+
+    pj_timestamp	 ts_clock;
+    pj_timestamp	 ts_rx;
+    pj_timestamp	 ts_tx;
+
+    /* Tx buffer is a temporary buffer to be used when there's mismatch 
+     * between port's ptime with conference's ptime. This buffer is used as 
+     * the source to buffer the samples until there are enough samples to 
+     * fulfill a complete frame to be transmitted to the port.
+     */
+    pj_uint8_t		 tx_buf[BUFFER_SIZE]; /**< Tx buffer.		    */
+};
+
+
+/*
+ * Conference bridge.
+ */
+struct pjmedia_conf
+{
+    unsigned		  options;	/**< Bitmask options.		    */
+    unsigned		  max_ports;	/**< Maximum ports.		    */
+    unsigned		  port_cnt;	/**< Current number of ports.	    */
+    unsigned		  connect_cnt;	/**< Total number of connections    */
+    pjmedia_port	 *master_port;	/**< Port zero's port.		    */
+    char		  master_name_buf[80]; /**< Port0 name buffer.	    */
+    pj_mutex_t		 *mutex;	/**< Conference mutex.		    */
+    struct conf_port	**ports;	/**< Array of ports.		    */
+    pj_uint8_t		  buf[BUFFER_SIZE];	/**< Common buffer.	    */
+};
+
+
+/* Prototypes */
+static pj_status_t put_frame(pjmedia_port *this_port, 
+			     const pjmedia_frame *frame);
+static pj_status_t get_frame(pjmedia_port *this_port, 
+			     pjmedia_frame *frame);
+static pj_status_t destroy_port(pjmedia_port *this_port);
+
+
+/*
+ * Create port.
+ */
+static pj_status_t create_conf_port( pj_pool_t *pool,
+				     pjmedia_conf *conf,
+				     pjmedia_port *port,
+				     const pj_str_t *name,
+				     struct conf_port **p_conf_port)
+{
+    struct conf_port *conf_port;
+    pjmedia_frame *f;
+
+    PJ_ASSERT_RETURN(pool && conf && port && name && p_conf_port, PJ_EINVAL);
+
+    /* Create port. */
+    conf_port = PJ_POOL_ZALLOC_T(pool, struct conf_port);
+
+    /* Set name */
+    pj_strdup_with_null(pool, &conf_port->name, name);
+
+    /* Default has tx and rx enabled. */
+    conf_port->rx_setting = PJMEDIA_PORT_ENABLE;
+    conf_port->tx_setting = PJMEDIA_PORT_ENABLE;
+
+    /* Default level adjustment is 128 (which means no adjustment) */
+    conf_port->tx_adj_level = NORMAL_LEVEL;
+    conf_port->rx_adj_level = NORMAL_LEVEL;
+
+    /* Create transmit flag array */
+    conf_port->listener_slots = (SLOT_TYPE*)
+				pj_pool_zalloc(pool, 
+					  conf->max_ports * sizeof(SLOT_TYPE));
+    PJ_ASSERT_RETURN(conf_port->listener_slots, PJ_ENOMEM);
+
+    /* Save some port's infos, for convenience. */
+    conf_port->port = port;
+    conf_port->info = &port->info;
+
+    /* Init pjmedia_frame structure in the TX buffer. */
+    f = (pjmedia_frame*)conf_port->tx_buf;
+    f->buf = conf_port->tx_buf + sizeof(pjmedia_frame);
+
+    /* Done */
+    *p_conf_port = conf_port;
+    return PJ_SUCCESS;
+}
+
+/*
+ * Create port zero for the sound device.
+ */
+static pj_status_t create_sound_port( pj_pool_t *pool,
+				      pjmedia_conf *conf )
+{
+    struct conf_port *conf_port;
+    pj_str_t name = { "Master/sound", 12 };
+    pj_status_t status;
+
+    status = create_conf_port(pool, conf, conf->master_port, &name, &conf_port);
+    if (status != PJ_SUCCESS)
+	return status;
+
+     /* Add the port to the bridge */
+    conf_port->slot = 0;
+    conf->ports[0] = conf_port;
+    conf->port_cnt++;
+
+    PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0"));
+    return PJ_SUCCESS;
+}
+
+/*
+ * Create conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool,
+					 unsigned max_ports,
+					 unsigned clock_rate,
+					 unsigned channel_count,
+					 unsigned samples_per_frame,
+					 unsigned bits_per_sample,
+					 unsigned options,
+					 pjmedia_conf **p_conf )
+{
+    pjmedia_conf *conf;
+    const pj_str_t name = { "Conf", 4 };
+    pj_status_t status;
+
+    /* Can only accept 16bits per sample, for now.. */
+    PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+    PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports",
+	      max_ports));
+
+    /* Create and init conf structure. */
+    conf = PJ_POOL_ZALLOC_T(pool, pjmedia_conf);
+    PJ_ASSERT_RETURN(conf, PJ_ENOMEM);
+
+    conf->ports = (struct conf_port**) 
+		  pj_pool_zalloc(pool, max_ports*sizeof(void*));
+    PJ_ASSERT_RETURN(conf->ports, PJ_ENOMEM);
+
+    conf->options = options;
+    conf->max_ports = max_ports;
+    
+    /* Create and initialize the master port interface. */
+    conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port);
+    PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM);
+    
+    pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE,
+			   clock_rate, channel_count, bits_per_sample,
+			   samples_per_frame);
+
+    conf->master_port->port_data.pdata = conf;
+    conf->master_port->port_data.ldata = 0;
+
+    conf->master_port->get_frame = &get_frame;
+    conf->master_port->put_frame = &put_frame;
+    conf->master_port->on_destroy = &destroy_port;
+
+
+    /* Create port zero for sound device. */
+    status = create_sound_port(pool, conf);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Create mutex. */
+    status = pj_mutex_create_recursive(pool, "conf", &conf->mutex);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Done */
+
+    *p_conf = conf;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Pause sound device.
+ */
+static pj_status_t pause_sound( pjmedia_conf *conf )
+{
+    /* Do nothing. */
+    PJ_UNUSED_ARG(conf);
+    return PJ_SUCCESS;
+}
+
+/*
+ * Resume sound device.
+ */
+static pj_status_t resume_sound( pjmedia_conf *conf )
+{
+    /* Do nothing. */
+    PJ_UNUSED_ARG(conf);
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Destroy conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf )
+{
+    PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL);
+
+    /* Destroy mutex */
+    pj_mutex_destroy(conf->mutex);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy the master port (will destroy the conference)
+ */
+static pj_status_t destroy_port(pjmedia_port *this_port)
+{
+    pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+    return pjmedia_conf_destroy(conf);
+}
+
+/*
+ * Get port zero interface.
+ */
+PJ_DEF(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf)
+{
+    /* Sanity check. */
+    PJ_ASSERT_RETURN(conf != NULL, NULL);
+
+    /* Can only return port interface when PJMEDIA_CONF_NO_DEVICE was
+     * present in the option.
+     */
+    PJ_ASSERT_RETURN((conf->options & PJMEDIA_CONF_NO_DEVICE) != 0, NULL);
+    
+    return conf->master_port;
+}
+
+
+/*
+ * Set master port name.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_set_port0_name(pjmedia_conf *conf,
+						const pj_str_t *name)
+{
+    unsigned len;
+
+    /* Sanity check. */
+    PJ_ASSERT_RETURN(conf != NULL && name != NULL, PJ_EINVAL);
+
+    len = name->slen;
+    if (len > sizeof(conf->master_name_buf))
+	len = sizeof(conf->master_name_buf);
+    
+    if (len > 0) pj_memcpy(conf->master_name_buf, name->ptr, len);
+
+    conf->ports[0]->name.ptr = conf->master_name_buf;
+    conf->ports[0]->name.slen = len;
+
+    conf->master_port->info.name = conf->ports[0]->name;
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Add stream port to the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf,
+					   pj_pool_t *pool,
+					   pjmedia_port *strm_port,
+					   const pj_str_t *port_name,
+					   unsigned *p_port )
+{
+    struct conf_port *conf_port;
+    unsigned index;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(conf && pool && strm_port, PJ_EINVAL);
+    /*
+    PJ_ASSERT_RETURN(conf->clock_rate == strm_port->info.clock_rate, 
+		     PJMEDIA_ENCCLOCKRATE);
+    PJ_ASSERT_RETURN(conf->channel_count == strm_port->info.channel_count, 
+		     PJMEDIA_ENCCHANNEL);
+    PJ_ASSERT_RETURN(conf->bits_per_sample == strm_port->info.bits_per_sample,
+		     PJMEDIA_ENCBITS);
+    */
+
+    /* Port's samples per frame should be equal to or multiplication of 
+     * conference's samples per frame.
+     */
+    /*
+    Not sure if this is needed!
+    PJ_ASSERT_RETURN((conf->samples_per_frame %
+		     strm_port->info.samples_per_frame==0) ||
+		     (strm_port->info.samples_per_frame %
+		     conf->samples_per_frame==0),
+		     PJMEDIA_ENCSAMPLESPFRAME);
+    */
+
+    /* If port_name is not specified, use the port's name */
+    if (!port_name)
+	port_name = &strm_port->info.name;
+
+    pj_mutex_lock(conf->mutex);
+
+    if (conf->port_cnt >= conf->max_ports) {
+	pj_assert(!"Too many ports");
+	pj_mutex_unlock(conf->mutex);
+	return PJ_ETOOMANY;
+    }
+
+    /* Find empty port in the conference bridge. */
+    for (index=0; index < conf->max_ports; ++index) {
+	if (conf->ports[index] == NULL)
+	    break;
+    }
+
+    pj_assert(index != conf->max_ports);
+
+    /* Create conf port structure. */
+    status = create_conf_port(pool, conf, strm_port, port_name, &conf_port);
+    if (status != PJ_SUCCESS) {
+	pj_mutex_unlock(conf->mutex);
+	return status;
+    }
+
+    /* Put the port. */
+    conf_port->slot = index;
+    conf->ports[index] = conf_port;
+    conf->port_cnt++;
+
+    /* Done. */
+    if (p_port) {
+	*p_port = index;
+    }
+
+    pj_mutex_unlock(conf->mutex);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Add passive port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf,
+						   pj_pool_t *pool,
+						   const pj_str_t *name,
+						   unsigned clock_rate,
+						   unsigned channel_count,
+						   unsigned samples_per_frame,
+						   unsigned bits_per_sample,
+						   unsigned options,
+						   unsigned *p_slot,
+						   pjmedia_port **p_port )
+{
+    PJ_UNUSED_ARG(conf);
+    PJ_UNUSED_ARG(pool);
+    PJ_UNUSED_ARG(name);
+    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(options);
+    PJ_UNUSED_ARG(p_slot);
+    PJ_UNUSED_ARG(p_port);
+
+    return PJ_ENOTSUP;
+}
+
+
+
+/*
+ * Change TX and RX settings for the port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf,
+						  unsigned slot,
+						  pjmedia_port_op tx,
+						  pjmedia_port_op rx)
+{
+    struct conf_port *conf_port;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+    /* Port must be valid. */
+    PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+    conf_port = conf->ports[slot];
+
+    if (tx != PJMEDIA_PORT_NO_CHANGE)
+	conf_port->tx_setting = tx;
+
+    if (rx != PJMEDIA_PORT_NO_CHANGE)
+	conf_port->rx_setting = rx;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Connect port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf,
+					       unsigned src_slot,
+					       unsigned sink_slot,
+					       int level )
+{
+    struct conf_port *src_port, *dst_port;
+    pj_bool_t start_sound = PJ_FALSE;
+    unsigned i;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports && 
+		     sink_slot<conf->max_ports, PJ_EINVAL);
+
+    /* Ports must be valid. */
+    PJ_ASSERT_RETURN(conf->ports[src_slot] != NULL, PJ_EINVAL);
+    PJ_ASSERT_RETURN(conf->ports[sink_slot] != NULL, PJ_EINVAL);
+
+    /* For now, level MUST be zero. */
+    PJ_ASSERT_RETURN(level == 0, PJ_EINVAL);
+
+    pj_mutex_lock(conf->mutex);
+
+    src_port = conf->ports[src_slot];
+    dst_port = conf->ports[sink_slot];
+
+    /* Format must match. */
+    if (src_port->info->format.id != dst_port->info->format.id ||
+	src_port->info->format.bitrate != dst_port->info->format.bitrate) 
+    {
+	pj_mutex_unlock(conf->mutex);
+	return PJMEDIA_ENOTCOMPATIBLE;
+    }
+
+    /* Clock rate must match. */
+    if (src_port->info->clock_rate != dst_port->info->clock_rate) {
+	pj_mutex_unlock(conf->mutex);
+	return PJMEDIA_ENCCLOCKRATE;
+    }
+
+    /* Channel count must match. */
+    if (src_port->info->channel_count != dst_port->info->channel_count) {
+	pj_mutex_unlock(conf->mutex);
+	return PJMEDIA_ENCCLOCKRATE;
+    }
+
+    /* Source and sink ptime must be equal or a multiplication factor. */
+    if ((src_port->info->samples_per_frame % 
+	 dst_port->info->samples_per_frame != 0) &&
+        (dst_port->info->samples_per_frame % 
+         src_port->info->samples_per_frame != 0))
+    {
+	pj_mutex_unlock(conf->mutex);
+	return PJMEDIA_ENCSAMPLESPFRAME;
+    }
+    
+    /* Check if sink is listening to other ports */
+    if (dst_port->transmitter_cnt > 0) {
+	pj_mutex_unlock(conf->mutex);
+	return PJ_ETOOMANYCONN;
+    }
+
+    /* Check if connection has been made */
+    for (i=0; i<src_port->listener_cnt; ++i) {
+	if (src_port->listener_slots[i] == sink_slot)
+	    break;
+    }
+
+    if (i == src_port->listener_cnt) {
+	src_port->listener_slots[src_port->listener_cnt] = sink_slot;
+	++conf->connect_cnt;
+	++src_port->listener_cnt;
+	++dst_port->transmitter_cnt;
+
+	if (conf->connect_cnt == 1)
+	    start_sound = 1;
+
+	PJ_LOG(4,(THIS_FILE,"Port %d (%.*s) transmitting to port %d (%.*s)",
+		  src_slot,
+		  (int)src_port->name.slen,
+		  src_port->name.ptr,
+		  sink_slot,
+		  (int)dst_port->name.slen,
+		  dst_port->name.ptr));
+    }
+
+    pj_mutex_unlock(conf->mutex);
+
+    /* Sound device must be started without mutex, otherwise the
+     * sound thread will deadlock (?)
+     */
+    if (start_sound)
+	resume_sound(conf);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Disconnect port
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf,
+						  unsigned src_slot,
+						  unsigned sink_slot )
+{
+    struct conf_port *src_port, *dst_port;
+    unsigned i;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports && 
+		     sink_slot<conf->max_ports, PJ_EINVAL);
+
+    /* Ports must be valid. */
+    PJ_ASSERT_RETURN(conf->ports[src_slot] != NULL, PJ_EINVAL);
+    PJ_ASSERT_RETURN(conf->ports[sink_slot] != NULL, PJ_EINVAL);
+
+    pj_mutex_lock(conf->mutex);
+
+    src_port = conf->ports[src_slot];
+    dst_port = conf->ports[sink_slot];
+
+    /* Check if connection has been made */
+    for (i=0; i<src_port->listener_cnt; ++i) {
+	if (src_port->listener_slots[i] == sink_slot)
+	    break;
+    }
+
+    if (i != src_port->listener_cnt) {
+	pjmedia_frame_ext *f;
+
+	pj_assert(src_port->listener_cnt > 0 && 
+		  src_port->listener_cnt < conf->max_ports);
+	pj_assert(dst_port->transmitter_cnt > 0 && 
+		  dst_port->transmitter_cnt < conf->max_ports);
+	pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE), 
+		       src_port->listener_cnt, i);
+	--conf->connect_cnt;
+	--src_port->listener_cnt;
+	--dst_port->transmitter_cnt;
+	
+	/* Cleanup listener TX buffer. */
+	f = (pjmedia_frame_ext*)dst_port->tx_buf;
+	f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+	f->base.size = 0;
+	f->samples_cnt = 0;
+	f->subframe_cnt = 0;
+
+	PJ_LOG(4,(THIS_FILE,
+		  "Port %d (%.*s) stop transmitting to port %d (%.*s)",
+		  src_slot,
+		  (int)src_port->name.slen,
+		  src_port->name.ptr,
+		  sink_slot,
+		  (int)dst_port->name.slen,
+		  dst_port->name.ptr));
+    }
+
+    pj_mutex_unlock(conf->mutex);
+
+    if (conf->connect_cnt == 0) {
+	pause_sound(conf);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Get number of ports currently registered to the conference bridge.
+ */
+PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf)
+{
+    return conf->port_cnt;
+}
+
+/*
+ * Get total number of ports connections currently set up in the bridge.
+ */
+PJ_DEF(unsigned) pjmedia_conf_get_connect_count(pjmedia_conf *conf)
+{
+    return conf->connect_cnt;
+}
+
+
+/*
+ * Remove the specified port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf,
+					      unsigned port )
+{
+    struct conf_port *conf_port;
+    unsigned i;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL);
+
+    /* Port must be valid. */
+    PJ_ASSERT_RETURN(conf->ports[port] != NULL, PJ_EINVAL);
+
+    /* Suspend the sound devices.
+     * Don't want to remove port while port is being accessed by sound
+     * device's threads!
+     */
+
+    pj_mutex_lock(conf->mutex);
+
+    conf_port = conf->ports[port];
+    conf_port->tx_setting = PJMEDIA_PORT_DISABLE;
+    conf_port->rx_setting = PJMEDIA_PORT_DISABLE;
+
+    /* Remove this port from transmit array of other ports. */
+    for (i=0; i<conf->max_ports; ++i) {
+	unsigned j;
+	struct conf_port *src_port;
+
+	src_port = conf->ports[i];
+
+	if (!src_port)
+	    continue;
+
+	if (src_port->listener_cnt == 0)
+	    continue;
+
+	for (j=0; j<src_port->listener_cnt; ++j) {
+	    if (src_port->listener_slots[j] == port) {
+		pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE),
+			       src_port->listener_cnt, j);
+		pj_assert(conf->connect_cnt > 0);
+		--conf->connect_cnt;
+		--src_port->listener_cnt;
+		break;
+	    }
+	}
+    }
+
+    /* Update transmitter_cnt of ports we're transmitting to */
+    while (conf_port->listener_cnt) {
+	unsigned dst_slot;
+	struct conf_port *dst_port;
+	pjmedia_frame_ext *f;
+
+	dst_slot = conf_port->listener_slots[conf_port->listener_cnt-1];
+	dst_port = conf->ports[dst_slot];
+	--dst_port->transmitter_cnt;
+	--conf_port->listener_cnt;
+	pj_assert(conf->connect_cnt > 0);
+	--conf->connect_cnt;
+
+	/* Cleanup & reinit listener TX buffer. */
+	f = (pjmedia_frame_ext*)dst_port->tx_buf;
+	f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+	f->base.size = 0;
+	f->samples_cnt = 0;
+	f->subframe_cnt = 0;
+    }
+
+    /* Remove the port. */
+    conf->ports[port] = NULL;
+    --conf->port_cnt;
+
+    pj_mutex_unlock(conf->mutex);
+
+
+    /* Stop sound if there's no connection. */
+    if (conf->connect_cnt == 0) {
+	pause_sound(conf);
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum ports.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf,
+					     unsigned ports[],
+					     unsigned *p_count )
+{
+    unsigned i, count=0;
+
+    PJ_ASSERT_RETURN(conf && p_count && ports, PJ_EINVAL);
+
+    for (i=0; i<conf->max_ports && count<*p_count; ++i) {
+	if (!conf->ports[i])
+	    continue;
+
+	ports[count++] = i;
+    }
+
+    *p_count = count;
+    return PJ_SUCCESS;
+}
+
+/*
+ * Get port info
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
+						unsigned slot,
+						pjmedia_conf_port_info *info)
+{
+    struct conf_port *conf_port;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+    /* Port must be valid. */
+    PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+    conf_port = conf->ports[slot];
+
+    pj_bzero(info, sizeof(pjmedia_conf_port_info));
+
+    info->slot = slot;
+    info->name = conf_port->name;
+    info->tx_setting = conf_port->tx_setting;
+    info->rx_setting = conf_port->rx_setting;
+    info->listener_cnt = conf_port->listener_cnt;
+    info->listener_slots = conf_port->listener_slots;
+    info->transmitter_cnt = conf_port->transmitter_cnt;
+    info->clock_rate = conf_port->info->clock_rate;
+    info->channel_count = conf_port->info->channel_count;
+    info->samples_per_frame = conf_port->info->samples_per_frame;
+    info->bits_per_sample = conf_port->info->bits_per_sample;
+    info->format = conf_port->port->info.format;
+    info->tx_adj_level = conf_port->tx_adj_level - NORMAL_LEVEL;
+    info->rx_adj_level = conf_port->rx_adj_level - NORMAL_LEVEL;
+
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf,
+						unsigned *size,
+						pjmedia_conf_port_info info[])
+{
+    unsigned i, count=0;
+
+    PJ_ASSERT_RETURN(conf && size && info, PJ_EINVAL);
+
+    for (i=0; i<conf->max_ports && count<*size; ++i) {
+	if (!conf->ports[i])
+	    continue;
+
+	pjmedia_conf_get_port_info(conf, i, &info[count]);
+	++count;
+    }
+
+    *size = count;
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get signal level.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_get_signal_level( pjmedia_conf *conf,
+						   unsigned slot,
+						   unsigned *tx_level,
+						   unsigned *rx_level)
+{
+    struct conf_port *conf_port;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+    /* Port must be valid. */
+    PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+    conf_port = conf->ports[slot];
+
+    if (tx_level != NULL) {
+	*tx_level = conf_port->tx_level;
+    }
+
+    if (rx_level != NULL) 
+	*rx_level = conf_port->rx_level;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Adjust RX level of individual port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_adjust_rx_level( pjmedia_conf *conf,
+						  unsigned slot,
+						  int adj_level )
+{
+    struct conf_port *conf_port;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+    /* Port must be valid. */
+    PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+    /* Value must be from -128 to +127 */
+    /* Disabled, you can put more than +127, at your own risk: 
+     PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL);
+     */
+    PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL);
+
+    conf_port = conf->ports[slot];
+
+    /* Level adjustment is applicable only for ports that work with raw PCM. */
+    PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16,
+		     PJ_EIGNORED);
+
+    /* Set normalized adjustment level. */
+    conf_port->rx_adj_level = adj_level + NORMAL_LEVEL;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Adjust TX level of individual port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_adjust_tx_level( pjmedia_conf *conf,
+						  unsigned slot,
+						  int adj_level )
+{
+    struct conf_port *conf_port;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+    /* Port must be valid. */
+    PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL);
+
+    /* Value must be from -128 to +127 */
+    /* Disabled, you can put more than +127,, at your own risk:
+     PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL);
+     */
+    PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL);
+
+    conf_port = conf->ports[slot];
+
+    /* Level adjustment is applicable only for ports that work with raw PCM. */
+    PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16,
+		     PJ_EIGNORED);
+
+    /* Set normalized adjustment level. */
+    conf_port->tx_adj_level = adj_level + NORMAL_LEVEL;
+
+    return PJ_SUCCESS;
+}
+
+/* Deliver frm_src to a listener port, eventually call  port's put_frame() 
+ * when samples count in the frm_dst are equal to port's samples_per_frame.
+ */
+static pj_status_t write_frame(struct conf_port *cport_dst,
+			       const pjmedia_frame *frm_src)
+{
+    pjmedia_frame *frm_dst = (pjmedia_frame*)cport_dst->tx_buf;
+    
+    PJ_TODO(MAKE_SURE_DEST_FRAME_HAS_ENOUGH_SPACE);
+
+    frm_dst->type = frm_src->type;
+    frm_dst->timestamp = cport_dst->ts_tx;
+
+    if (frm_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+
+	pjmedia_frame_ext *f_src = (pjmedia_frame_ext*)frm_src;
+	pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst;
+	unsigned i;
+
+	for (i = 0; i < f_src->subframe_cnt; ++i) {
+	    pjmedia_frame_ext_subframe *sf;
+	    
+	    /* Copy frame to listener's TX buffer. */
+	    sf = pjmedia_frame_ext_get_subframe(f_src, i);
+	    pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen, 
+					      f_src->samples_cnt / 
+					      f_src->subframe_cnt);
+
+	    /* Check if it's time to deliver the TX buffer to listener, 
+	     * i.e: samples count in TX buffer equal to listener's
+	     * samples per frame.
+	     */
+	    if (f_dst->samples_cnt >= cport_dst->info->samples_per_frame)
+	    {
+		if (cport_dst->slot) {
+		    pjmedia_port_put_frame(cport_dst->port, 
+					   (pjmedia_frame*)f_dst);
+
+		    /* Reset TX buffer. */
+		    f_dst->subframe_cnt = 0;
+		    f_dst->samples_cnt = 0;
+		}
+
+		/* Update TX timestamp. */
+		pj_add_timestamp32(&cport_dst->ts_tx, 
+				   cport_dst->info->samples_per_frame);
+	    }
+	}
+
+    } else if (frm_src->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+
+	pj_int16_t *f_start, *f_end;
+
+	f_start = (pj_int16_t*)frm_src->buf;
+	f_end   = f_start + (frm_src->size >> 1);
+
+	while (f_start < f_end) {
+	    unsigned nsamples_to_copy, nsamples_req;
+
+	    /* Copy frame to listener's TX buffer. */
+	    nsamples_to_copy = f_end - f_start;
+	    nsamples_req = cport_dst->info->samples_per_frame - 
+			  (frm_dst->size>>1);
+	    if (nsamples_to_copy > nsamples_req)
+		nsamples_to_copy = nsamples_req;
+
+	    /* Adjust TX level. */
+	    if (cport_dst->tx_adj_level != NORMAL_LEVEL) {
+		pj_int16_t *p, *p_end;
+
+		p = f_start;
+		p_end = p + nsamples_to_copy;
+		while (p < p_end) {
+		    pj_int32_t itemp = *p;
+
+		    /* Adjust the level */
+		    itemp = (itemp * cport_dst->tx_adj_level) >> 7;
+
+		    /* Clip the signal if it's too loud */
+		    if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+		    else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+		    /* Put back in the buffer. */
+		    *p = (pj_int16_t)itemp;
+		    ++p;
+		}
+	    }
+
+	    pjmedia_copy_samples((pj_int16_t*)frm_dst->buf + (frm_dst->size>>1),
+				 f_start, 
+				 nsamples_to_copy);
+	    frm_dst->size += nsamples_to_copy << 1;
+	    f_start += nsamples_to_copy;
+
+	    /* Check if it's time to deliver the TX buffer to listener, 
+	     * i.e: samples count in TX buffer equal to listener's
+	     * samples per frame.
+	     */
+	    if ((frm_dst->size >> 1) == cport_dst->info->samples_per_frame)
+	    {
+		if (cport_dst->slot) {
+		    pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+		    /* Reset TX buffer. */
+		    frm_dst->size = 0;
+		}
+
+		/* Update TX timestamp. */
+		pj_add_timestamp32(&cport_dst->ts_tx, 
+				   cport_dst->info->samples_per_frame);
+	    }
+	}
+
+    } else if (frm_src->type == PJMEDIA_FRAME_TYPE_NONE) {
+
+	/* Check port format. */
+	if (cport_dst->port &&
+	    cport_dst->port->info.format.id == PJMEDIA_FORMAT_L16)
+	{
+	    /* When there is already some samples in listener's TX buffer, 
+	     * pad the buffer with "zero samples".
+	     */
+	    if (frm_dst->size != 0) {
+		pjmedia_zero_samples((pj_int16_t*)frm_dst->buf,
+				     cport_dst->info->samples_per_frame - 
+				     (frm_dst->size>>1));
+
+		frm_dst->type = PJMEDIA_FRAME_TYPE_AUDIO;
+		frm_dst->size = cport_dst->info->samples_per_frame << 1;
+		if (cport_dst->slot) {
+		    pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+		    /* Reset TX buffer. */
+		    frm_dst->size = 0;
+		}
+
+		/* Update TX timestamp. */
+		pj_add_timestamp32(&cport_dst->ts_tx, 
+				   cport_dst->info->samples_per_frame);
+	    }
+	} else {
+	    pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst;
+
+	    if (f_dst->samples_cnt != 0) {
+		frm_dst->type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		pjmedia_frame_ext_append_subframe(f_dst, NULL, 0, (pj_uint16_t)
+		    (cport_dst->info->samples_per_frame - f_dst->samples_cnt));
+		if (cport_dst->slot) {
+		    pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+		    /* Reset TX buffer. */
+		    f_dst->subframe_cnt = 0;
+		    f_dst->samples_cnt = 0;
+		}
+
+		/* Update TX timestamp. */
+		pj_add_timestamp32(&cport_dst->ts_tx, 
+				   cport_dst->info->samples_per_frame);
+	    }
+	}
+
+	/* Synchronize clock. */
+	while (pj_cmp_timestamp(&cport_dst->ts_clock, 
+				&cport_dst->ts_tx) > 0)
+	{
+	    frm_dst->type = PJMEDIA_FRAME_TYPE_NONE;
+	    frm_dst->timestamp = cport_dst->ts_tx;
+	    if (cport_dst->slot)
+		pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+	    /* Update TX timestamp. */
+	    pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->info->samples_per_frame);
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Player callback.
+ */
+static pj_status_t get_frame(pjmedia_port *this_port, 
+			     pjmedia_frame *frame)
+{
+    pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+    unsigned ci, i;
+    
+    /* Must lock mutex */
+    pj_mutex_lock(conf->mutex);
+
+    /* Call get_frame() from all ports (except port 0) that has 
+     * receiver and distribute the frame (put the frame to the destination 
+     * port's buffer to accommodate different ptime, and ultimately call 
+     * put_frame() of that port) to ports that are receiving from this port.
+     */
+    for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) {
+	struct conf_port *cport = conf->ports[i];
+
+	/* Skip empty port. */
+	if (!cport)
+	    continue;
+
+	/* Var "ci" is to count how many ports have been visited so far. */
+	++ci;
+
+	/* Update clock of the port. */
+	pj_add_timestamp32(&cport->ts_clock, 
+			   conf->master_port->info.samples_per_frame);
+
+	/* Skip if we're not allowed to receive from this port or 
+	 * the port doesn't have listeners.
+	 */
+	if (cport->rx_setting == PJMEDIA_PORT_DISABLE || 
+	    cport->listener_cnt == 0)
+	{
+	    cport->rx_level = 0;
+	    pj_add_timestamp32(&cport->ts_rx, 
+			       conf->master_port->info.samples_per_frame);
+	    continue;
+	}
+
+	/* Get frame from each port, put it to the listener TX buffer,
+	 * and eventually call put_frame() of the listener. This loop 
+	 * will also make sure the ptime between conf & port synchronized.
+	 */
+	while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_rx) > 0) {
+	    pjmedia_frame *f = (pjmedia_frame*)conf->buf;
+	    pj_status_t status;
+	    unsigned j;
+	    pj_int32_t level = 0;
+
+	    pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame);
+	    
+	    f->buf = &conf->buf[sizeof(pjmedia_frame)];
+	    f->size = cport->info->samples_per_frame<<1;
+
+	    /* Get frame from port. */
+	    status = pjmedia_port_get_frame(cport->port, f);
+	    if (status != PJ_SUCCESS)
+		continue;
+
+	    /* Calculate & adjust RX level. */
+	    if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+		if (cport->rx_adj_level != NORMAL_LEVEL) {
+		    pj_int16_t *p = (pj_int16_t*)f->buf;
+		    pj_int16_t *end;
+
+		    end = p + (f->size >> 1);
+		    while (p < end) {
+			pj_int32_t itemp = *p;
+
+			/* Adjust the level */
+			itemp = (itemp * cport->rx_adj_level) >> 7;
+
+			/* Clip the signal if it's too loud */
+			if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+			else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+			level += PJ_ABS(itemp);
+
+			/* Put back in the buffer. */
+			*p = (pj_int16_t)itemp;
+			++p;
+		    }
+		    level /= (f->size >> 1);
+		} else {
+		    level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf,
+						    f->size >> 1);
+		}
+	    } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+		/* For extended frame, level is unknown, so we just set 
+		 * it to NORMAL_LEVEL. 
+		 */
+		level = NORMAL_LEVEL;
+	    }
+
+	    cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff;
+
+	    /* Put the frame to all listeners. */
+	    for (j=0; j < cport->listener_cnt; ++j) 
+	    {
+		struct conf_port *listener;
+
+		listener = conf->ports[cport->listener_slots[j]];
+
+		/* Skip if this listener doesn't want to receive audio */
+		if (listener->tx_setting == PJMEDIA_PORT_DISABLE) {
+		    pj_add_timestamp32(&listener->ts_tx, 
+				       listener->info->samples_per_frame);
+		    listener->tx_level = 0;
+		    continue;
+		}
+    	    
+		status = write_frame(listener, f);
+		if (status != PJ_SUCCESS) {
+		    listener->tx_level = 0;
+		    continue;
+		}
+
+		/* Set listener TX level based on transmitter RX level & 
+		 * listener TX level.
+		 */
+		listener->tx_level = (cport->rx_level * listener->tx_adj_level)
+				     >> 8;
+	    }
+	}
+    }
+
+    /* Keep alive. Update TX timestamp and send frame type NONE to all 
+     * underflow ports at their own clock.
+     */
+    for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) {
+	struct conf_port *cport = conf->ports[i];
+
+	/* Skip empty port. */
+	if (!cport)
+	    continue;
+
+	/* Var "ci" is to count how many ports have been visited so far. */
+	++ci;
+
+	if (cport->tx_setting==PJMEDIA_PORT_MUTE || cport->transmitter_cnt==0)
+	{
+	    pjmedia_frame_ext *f;
+	    
+	    /* Clear left-over samples in tx_buffer, if any, so that it won't
+	     * be transmitted next time we have audio signal.
+	     */
+	    f = (pjmedia_frame_ext*)cport->tx_buf;
+	    f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+	    f->base.size = 0;
+	    f->samples_cnt = 0;
+	    f->subframe_cnt = 0;
+	    
+	    cport->tx_level = 0;
+
+	    while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_tx) > 0)
+	    {
+		if (cport->tx_setting == PJMEDIA_PORT_ENABLE) {
+		    pjmedia_frame tmp_f;
+
+		    tmp_f.timestamp = cport->ts_tx;
+		    tmp_f.type = PJMEDIA_FRAME_TYPE_NONE;
+		    tmp_f.buf = NULL;
+		    tmp_f.size = 0;
+
+		    pjmedia_port_put_frame(cport->port, &tmp_f);
+		    pj_add_timestamp32(&cport->ts_tx, cport->info->samples_per_frame);
+		}
+	    }
+	}
+    }
+
+    /* Return sound playback frame. */
+    do {
+	struct conf_port *this_cport = conf->ports[this_port->port_data.ldata];
+	pjmedia_frame *f_src = (pjmedia_frame*) this_cport->tx_buf;
+
+	frame->type = f_src->type;
+
+	if (f_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+	    pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src;
+	    pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frame;
+	    pjmedia_frame_ext_subframe *sf;
+	    unsigned samples_per_subframe;
+	    
+	    if (f_src_->samples_cnt < this_cport->info->samples_per_frame) {
+		f_dst->base.type = PJMEDIA_FRAME_TYPE_NONE;
+		f_dst->samples_cnt = 0;
+		f_dst->subframe_cnt = 0;
+		break;
+	    }
+
+	    f_dst->samples_cnt = 0;
+	    f_dst->subframe_cnt = 0;
+	    i = 0;
+	    samples_per_subframe = f_src_->samples_cnt / f_src_->subframe_cnt;
+
+
+	    while (f_dst->samples_cnt < this_cport->info->samples_per_frame) {
+		sf = pjmedia_frame_ext_get_subframe(f_src_, i++);
+		pj_assert(sf);
+		pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen,
+						  samples_per_subframe);
+	    }
+
+	    /* Shift left TX buffer. */
+	    pjmedia_frame_ext_pop_subframes(f_src_, i);
+
+	} else if (f_src->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+	    if ((f_src->size>>1) < this_cport->info->samples_per_frame) {
+		frame->type = PJMEDIA_FRAME_TYPE_NONE;
+		frame->size = 0;
+		break;
+	    }
+
+	    pjmedia_copy_samples((pj_int16_t*)frame->buf, 
+				 (pj_int16_t*)f_src->buf, 
+				 this_cport->info->samples_per_frame);
+	    frame->size = this_cport->info->samples_per_frame << 1;
+
+	    /* Shift left TX buffer. */
+	    f_src->size -= frame->size;
+	    if (f_src->size)
+		pjmedia_move_samples((pj_int16_t*)f_src->buf,
+				     (pj_int16_t*)f_src->buf + 
+				     this_cport->info->samples_per_frame,
+				     f_src->size >> 1);
+	} else { /* PJMEDIA_FRAME_TYPE_NONE */
+	    pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src;
+
+	    /* Reset source/TX buffer */
+	    f_src_->base.size = 0;
+	    f_src_->samples_cnt = 0;
+	    f_src_->subframe_cnt = 0;
+	}
+    } while (0);
+
+    /* Unlock mutex */
+    pj_mutex_unlock(conf->mutex);
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Recorder callback.
+ */
+static pj_status_t put_frame(pjmedia_port *this_port, 
+			     const pjmedia_frame *f)
+{
+    pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+    struct conf_port *cport = conf->ports[this_port->port_data.ldata];
+    unsigned j;
+    pj_int32_t level = 0;
+
+    pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame);
+    
+    /* Skip if this port is muted/disabled. */
+    if (cport->rx_setting == PJMEDIA_PORT_DISABLE) {
+	cport->rx_level = 0;
+	return PJ_SUCCESS;
+    }
+
+    /* Skip if no port is listening to the microphone */
+    if (cport->listener_cnt == 0) {
+	cport->rx_level = 0;
+	return PJ_SUCCESS;
+    }
+
+    /* Calculate & adjust RX level. */
+    if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+	if (cport->rx_adj_level != NORMAL_LEVEL) {
+	    pj_int16_t *p = (pj_int16_t*)f->buf;
+	    pj_int16_t *end;
+
+	    end = p + (f->size >> 1);
+	    while (p < end) {
+		pj_int32_t itemp = *p;
+
+		/* Adjust the level */
+		itemp = (itemp * cport->rx_adj_level) >> 7;
+
+		/* Clip the signal if it's too loud */
+		if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+		else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+		level += PJ_ABS(itemp);
+
+		/* Put back in the buffer. */
+		*p = (pj_int16_t)itemp;
+		++p;
+	    }
+	    level /= (f->size >> 1);
+	} else {
+	    level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf,
+					    f->size >> 1);
+	}
+    } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+	/* For extended frame, level is unknown, so we just set 
+	 * it to NORMAL_LEVEL. 
+	 */
+	level = NORMAL_LEVEL;
+    }
+
+    cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff;
+
+    /* Put the frame to all listeners. */
+    for (j=0; j < cport->listener_cnt; ++j) 
+    {
+	struct conf_port *listener;
+	pj_status_t status;
+
+	listener = conf->ports[cport->listener_slots[j]];
+
+	/* Skip if this listener doesn't want to receive audio */
+	if (listener->tx_setting == PJMEDIA_PORT_DISABLE) {
+	    pj_add_timestamp32(&listener->ts_tx, 
+			       listener->info->samples_per_frame);
+	    listener->tx_level = 0;
+	    continue;
+	}
+
+	/* Skip loopback for now. */
+	if (listener == cport) {
+	    pj_add_timestamp32(&listener->ts_tx, 
+			       listener->info->samples_per_frame);
+	    listener->tx_level = 0;
+	    continue;
+	}
+	    
+	status = write_frame(listener, f);
+	if (status != PJ_SUCCESS) {
+	    listener->tx_level = 0;
+	    continue;
+	}
+
+	/* Set listener TX level based on transmitter RX level & listener
+	 * TX level.
+	 */
+	listener->tx_level = (cport->rx_level * listener->tx_adj_level) >> 8;
+    }
+
+    return PJ_SUCCESS;
+}
+
+#endif
Index: pjproject/trunk/pjmedia/src/pjmedia/conference.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/conference.c	(revision 2505)
+++ pjproject/trunk/pjmedia/src/pjmedia/conference.c	(revision 2506)
@@ -34,4 +34,5 @@
 #include <pj/string.h>
 
+#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0
 
 /* CONF_DEBUG enables detailed operation of the conference bridge.
@@ -64,5 +65,5 @@
 #define BYTES_PER_SAMPLE    2
 
-#define SIGNATURE	    PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'F')
+#define SIGNATURE	    PJMEDIA_CONF_BRIDGE_SIGNATURE
 #define SIGNATURE_PORT	    PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'P')
 /* Normal level is hardcodec to 128 in all over places */
@@ -465,6 +466,6 @@
 
     if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) {
-	pjmedia_snd_stream *strm;
-	pjmedia_snd_stream_info si;
+	pjmedia_aud_stream *strm;
+	pjmedia_aud_param param;
 
 	/*
@@ -494,12 +495,12 @@
 
 	strm = pjmedia_snd_port_get_snd_stream(conf->snd_dev_port);
-	status = pjmedia_snd_stream_get_info(strm, &si);
+	status = pjmedia_aud_stream_get_param(strm, &param);
 	if (status == PJ_SUCCESS) {
-	    const pjmedia_snd_dev_info *snd_dev_info;
+	    pjmedia_aud_dev_info snd_dev_info;
 	    if (conf->options & PJMEDIA_CONF_NO_MIC)
-		snd_dev_info = pjmedia_snd_get_dev_info(si.play_id);
+		pjmedia_aud_dev_get_info(param.play_id, &snd_dev_info);
 	    else
-		snd_dev_info = pjmedia_snd_get_dev_info(si.rec_id);
-	    pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info->name);
+		pjmedia_aud_dev_get_info(param.rec_id, &snd_dev_info);
+	    pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info.name);
 	}
     }
@@ -1171,4 +1172,5 @@
     info->listener_cnt = conf_port->listener_cnt;
     info->listener_slots = conf_port->listener_slots;
+    info->transmitter_cnt = conf_port->transmitter_cnt;
     info->clock_rate = conf_port->clock_rate;
     info->channel_count = conf_port->channel_count;
@@ -1988,2 +1990,3 @@
 }
 
+#endif
Index: pjproject/trunk/pjmedia/src/pjmedia/dsound.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/dsound.c	(revision 2505)
+++ 	(revision )
@@ -1,1113 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * 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 
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/os.h>
-#include <pj/string.h>
-
-#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_WIN32_DIRECT_SOUND
-
-#define	PJ_MIN(x, y)	((x < y) ? (x) : (y))
-
-#ifdef _MSC_VER
-#   pragma warning(push, 3)
-#endif
-
-#include <windows.h>
-#include <mmsystem.h>
-#include <dsound.h>
-
-#ifdef _MSC_VER
-#   pragma warning(pop)
-#endif
-
-
-#define THIS_FILE	    "dsound.c"
-#define BITS_PER_SAMPLE	    16
-#define BYTES_PER_SAMPLE    (BITS_PER_SAMPLE/8)
-
-#define MAX_PACKET_BUFFER_COUNT	    50
-#define MIN_PACKET_BUFFER_COUNT	    2
-
-#define MAX_HARDWARE		    16
-
-struct dsound_dev_info
-{
-    pjmedia_snd_dev_info    info;
-    LPGUID		    lpGuid;
-    GUID		    guid;
-};
-
-static unsigned dev_count;
-static struct dsound_dev_info dev_info[MAX_HARDWARE];
-static int snd_init_count;
-
-/* Latency settings */
-static unsigned snd_input_latency  = PJMEDIA_SND_DEFAULT_REC_LATENCY;
-static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
-
-
-/* Individual DirectSound capture/playback stream descriptor */
-struct dsound_stream
-{
-    union 
-    {
-	struct
-	{
-	    LPDIRECTSOUND	    lpDs;
-	    LPDIRECTSOUNDBUFFER	    lpDsBuffer;
-	} play;
-
-	struct
-	{
-	    LPDIRECTSOUNDCAPTURE	lpDs;
-	    LPDIRECTSOUNDCAPTUREBUFFER	lpDsBuffer;
-	} capture;
-    } ds;
-
-    HANDLE		    hEvent;
-    LPDIRECTSOUNDNOTIFY	    lpDsNotify;
-    DWORD		    dwBytePos;
-    DWORD		    dwDsBufferSize;
-    pj_timestamp	    timestamp;
-    unsigned		    latency;
-};
-
-
-/* Sound stream.    */
-struct pjmedia_snd_stream
-{
-    pjmedia_dir		    dir;		/**< Sound direction.	    */
-    int			    play_id;		/**< Playback dev id.	    */
-    int			    rec_id;		/**< Recording dev id.	    */
-    pj_pool_t		   *pool;		/**< Memory pool.	    */
-  
-    pjmedia_snd_rec_cb	    rec_cb;		/**< Capture callback.	    */
-    pjmedia_snd_play_cb	    play_cb;		/**< Playback callback.	    */
-    void		   *user_data;		/**< Application data.	    */
-
-    struct dsound_stream    play_strm;		/**< Playback stream.	    */
-    struct dsound_stream    rec_strm;		/**< Capture stream.	    */
-
-    void		   *buffer;		/**< Temp. frame buffer.    */
-    unsigned		    clock_rate;		/**< Clock rate.	    */
-    unsigned		    samples_per_frame;	/**< Samples per frame.	    */
-    unsigned		    bits_per_sample;	/**< Bits per sample.	    */
-    unsigned		    channel_count;	/**< Channel count.	    */
-
-    pj_thread_t		   *thread;		/**< Thread handle.	    */
-    HANDLE		    thread_quit_event;	/**< Quit signal to thread  */
-};
-
-
-static pj_pool_factory *pool_factory;
-
-
-static void init_waveformatex (PCMWAVEFORMAT *pcmwf, 
-			       unsigned clock_rate,
-			       unsigned channel_count)
-{
-    pj_bzero(pcmwf, sizeof(PCMWAVEFORMAT)); 
-    pcmwf->wf.wFormatTag = WAVE_FORMAT_PCM; 
-    pcmwf->wf.nChannels = (pj_uint16_t)channel_count;
-    pcmwf->wf.nSamplesPerSec = clock_rate;
-    pcmwf->wf.nBlockAlign = (pj_uint16_t)(channel_count * BYTES_PER_SAMPLE);
-    pcmwf->wf.nAvgBytesPerSec = clock_rate * channel_count * BYTES_PER_SAMPLE;
-    pcmwf->wBitsPerSample = BITS_PER_SAMPLE;
-}
-
-
-/*
- * Initialize DirectSound player device.
- */
-static pj_status_t init_player_stream( struct dsound_stream *ds_strm,
-				       int dev_id,
-				       unsigned clock_rate,
-				       unsigned channel_count,
-				       unsigned samples_per_frame,
-				       unsigned buffer_count)
-{
-    HRESULT hr;
-    HWND hwnd;
-    PCMWAVEFORMAT pcmwf; 
-    DSBUFFERDESC dsbdesc;
-    DSBPOSITIONNOTIFY dsPosNotify[MAX_PACKET_BUFFER_COUNT];
-    unsigned bytes_per_frame;
-    unsigned max_latency;
-    unsigned i;
-
-
-    PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);
-
-    /* Check device ID */
-    if (dev_id == -1)
-	dev_id = 0;
-
-    PJ_ASSERT_RETURN(dev_id>=0 && dev_id < (int)dev_count, PJ_EINVAL);
-
-    /*
-     * Create DirectSound device.
-     */
-    hr = DirectSoundCreate(dev_info[dev_id].lpGuid, &ds_strm->ds.play.lpDs, 
-			   NULL);
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-    hwnd = GetForegroundWindow();
-    if (hwnd == NULL) {
-	hwnd = GetDesktopWindow();
-    }    
-    hr = IDirectSound_SetCooperativeLevel( ds_strm->ds.play.lpDs, hwnd, 
-					   DSSCL_PRIORITY);
-    if FAILED(hr)
-	return PJ_RETURN_OS_ERROR(hr);
-    
-    /*
-     * Set up wave format structure for initialize DirectSound play
-     * buffer. 
-     */
-    init_waveformatex(&pcmwf, clock_rate, channel_count);
-    bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
-
-    /* Set up DSBUFFERDESC structure. */
-    pj_bzero(&dsbdesc, sizeof(DSBUFFERDESC)); 
-    dsbdesc.dwSize = sizeof(DSBUFFERDESC); 
-    dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY |
-		      DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
-
-    dsbdesc.dwBufferBytes = buffer_count * bytes_per_frame;
-    dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf; 
-
-    /*
-     * Create DirectSound playback buffer. 
-     */
-    hr = IDirectSound_CreateSoundBuffer(ds_strm->ds.play.lpDs, &dsbdesc, 
-					&ds_strm->ds.play.lpDsBuffer, NULL); 
-    if (FAILED(hr) )
-	return PJ_RETURN_OS_ERROR(hr);
-
-    /*
-     * Create event for play notification.
-     */
-    ds_strm->hEvent = CreateEvent( NULL, FALSE, FALSE, NULL);
-    if (ds_strm->hEvent == NULL)
-	return pj_get_os_error();
-
-    /*
-     * Setup notification for play.
-     */
-    hr = IDirectSoundBuffer_QueryInterface( ds_strm->ds.play.lpDsBuffer, 
-					    &IID_IDirectSoundNotify, 
-					    (LPVOID *)&ds_strm->lpDsNotify); 
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-    
-    for (i=0; i<buffer_count; ++i) {
-	dsPosNotify[i].dwOffset = i * bytes_per_frame;
-	dsPosNotify[i].hEventNotify = ds_strm->hEvent;
-    }
-    
-    hr = IDirectSoundNotify_SetNotificationPositions( ds_strm->lpDsNotify, 
-						      buffer_count, 
-						      dsPosNotify);
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-
-    hr = IDirectSoundBuffer_SetCurrentPosition(ds_strm->ds.play.lpDsBuffer, 0);
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-
-    ds_strm->dwBytePos = 0;
-    ds_strm->dwDsBufferSize = buffer_count * bytes_per_frame;
-    ds_strm->timestamp.u64 = 0;
-    /*
-     * Play latency does not need to be on a frame boundry, it is just how far
-     * ahead of the read pointer we set the write pointer.  So we should just
-     * use the user configured latency.  However, if the latency measured in
-     * bytes causes more buffers than we are allowed, we must cap the latency
-     * at the time contained in 1-buffer_count.
-     */
-    max_latency = (1 - buffer_count) * samples_per_frame * 1000 / clock_rate /
-        channel_count;
-    ds_strm->latency = PJ_MIN(max_latency, snd_output_latency);
-
-    /* Done setting up play device. */
-    PJ_LOG(5,(THIS_FILE, 
-	      " DirectSound player \"%s\" initialized (clock_rate=%d, "
-	      "channel_count=%d, samples_per_frame=%d (%dms))",
-	      dev_info[dev_id].info.name,
-	      clock_rate, channel_count, samples_per_frame,
-	      samples_per_frame * 1000 / clock_rate));
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Initialize DirectSound recorder device
- */
-static pj_status_t init_capture_stream( struct dsound_stream *ds_strm,
-				        int dev_id,
-				        unsigned clock_rate,
-				        unsigned channel_count,
-				        unsigned samples_per_frame,
-				        unsigned buffer_count)
-{
-    HRESULT hr;
-    PCMWAVEFORMAT pcmwf; 
-    DSCBUFFERDESC dscbdesc;
-    DSBPOSITIONNOTIFY dsPosNotify[MAX_PACKET_BUFFER_COUNT];
-    unsigned bytes_per_frame;
-    unsigned i;
-
-
-    PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);
-
-
-    /* Check device id */
-    if (dev_id == -1)
-	dev_id = 0;
-
-    PJ_ASSERT_RETURN(dev_id>=0 && dev_id < (int)dev_count, PJ_EINVAL);
-
-    /*
-     * Creating recorder device.
-     */
-    hr = DirectSoundCaptureCreate(dev_info[dev_id].lpGuid, 
-				  &ds_strm->ds.capture.lpDs, NULL);
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-
-    /* Init wave format to initialize buffer */
-    init_waveformatex( &pcmwf, clock_rate, channel_count);
-    bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
-
-    /* 
-     * Setup capture buffer using sound buffer structure that was passed
-     * to play buffer creation earlier.
-     */
-    pj_bzero(&dscbdesc, sizeof(DSCBUFFERDESC));
-    dscbdesc.dwSize = sizeof(DSCBUFFERDESC); 
-    dscbdesc.dwFlags = DSCBCAPS_WAVEMAPPED ;
-    dscbdesc.dwBufferBytes = buffer_count * bytes_per_frame; 
-    dscbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf; 
-
-    hr = IDirectSoundCapture_CreateCaptureBuffer( ds_strm->ds.capture.lpDs,
-						  &dscbdesc, 
-						  &ds_strm->ds.capture.lpDsBuffer,
-						  NULL);
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-    /*
-     * Create event for play notification.
-     */
-    ds_strm->hEvent = CreateEvent( NULL, FALSE, FALSE, NULL);
-    if (ds_strm->hEvent == NULL)
-	return pj_get_os_error();
-
-    /*
-     * Setup notifications for recording.
-     */
-    hr = IDirectSoundCaptureBuffer_QueryInterface( ds_strm->ds.capture.lpDsBuffer, 
-						   &IID_IDirectSoundNotify, 
-						   (LPVOID *)&ds_strm->lpDsNotify); 
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-    
-    for (i=0; i<buffer_count; ++i) {
-	dsPosNotify[i].dwOffset = i * bytes_per_frame;
-	dsPosNotify[i].hEventNotify = ds_strm->hEvent;
-    }
-    
-    hr = IDirectSoundNotify_SetNotificationPositions( ds_strm->lpDsNotify, 
-						      buffer_count, 
-						      dsPosNotify);
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-    hr = IDirectSoundCaptureBuffer_GetCurrentPosition( ds_strm->ds.capture.lpDsBuffer, 
-						       NULL, &ds_strm->dwBytePos );
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-    ds_strm->timestamp.u64 = 0;
-    ds_strm->dwDsBufferSize = buffer_count * bytes_per_frame;
-    /*
-     * Capture latency must always be on a frame boundry,
-     * so compute it based off the calculated buffer_count.
-     */
-    ds_strm->latency = buffer_count * samples_per_frame * 1000 / clock_rate / 
-		       channel_count;
-
-    /* Done setting up recorder device. */
-    PJ_LOG(5,(THIS_FILE, 
-	      " DirectSound capture \"%s\" initialized (clock_rate=%d, "
-	      "channel_count=%d, samples_per_frame=%d (%dms))",
-	      dev_info[dev_id].info.name,
-	      clock_rate, channel_count, samples_per_frame,
-	      samples_per_frame * 1000 / clock_rate));
-
-    return PJ_SUCCESS;
-}
-
-
-
-static BOOL AppReadDataFromBuffer(LPDIRECTSOUNDCAPTUREBUFFER lpDsb, // The buffer.
-				  DWORD dwOffset,		    // Our own write cursor.
-				  LPBYTE lpbSoundData,		    // Start of our data.
-				  DWORD dwSoundBytes)		    // Size of block to copy.
-{ 
-    LPVOID  lpvPtr1; 
-    DWORD dwBytes1; 
-    LPVOID  lpvPtr2; 
-    DWORD dwBytes2; 
-    HRESULT hr; 
-    
-    // Obtain memory address of write block. This will be in two parts
-    // if the block wraps around.
-    
-    hr = IDirectSoundCaptureBuffer_Lock( lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, 
-					 &dwBytes1, &lpvPtr2, &dwBytes2, 0); 
-    
-    if SUCCEEDED(hr) { 
-	// Read from pointers. 
-	pj_memcpy(lpbSoundData, lpvPtr1, dwBytes1); 
-	if (lpvPtr2 != NULL)
-	    pj_memcpy(lpbSoundData+dwBytes1, lpvPtr2, dwBytes2); 
-	
-	// Release the data back to DirectSound. 
-	hr = IDirectSoundCaptureBuffer_Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); 
-	if SUCCEEDED(hr)
-	    return TRUE; 
-    } 
-    
-    // Lock, Unlock, or Restore failed. 
-    return FALSE; 
-}
-
-
-static BOOL AppWriteDataToBuffer(LPDIRECTSOUNDBUFFER lpDsb,  // The buffer.
-				 DWORD dwOffset,	      // Our own write cursor.
-				 LPBYTE lpbSoundData,	      // Start of our data.
-				 DWORD dwSoundBytes)	      // Size of block to copy.
-{ 
-    LPVOID  lpvPtr1; 
-    DWORD dwBytes1; 
-    LPVOID  lpvPtr2; 
-    DWORD dwBytes2; 
-    HRESULT hr; 
-    
-    // Obtain memory address of write block. This will be in two parts
-    // if the block wraps around.
-    
-    hr = IDirectSoundBuffer_Lock( lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, 
-				  &dwBytes1, &lpvPtr2, &dwBytes2, 0); 
-    
-    // If the buffer was lost, restore and retry lock. 
-    if (DSERR_BUFFERLOST == hr) { 
-	IDirectSoundBuffer_Restore(lpDsb); 
-	hr = IDirectSoundBuffer_Lock( lpDsb, dwOffset, dwSoundBytes, 
-				      &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0); 
-    } 
-    if SUCCEEDED(hr) { 
-	pj_memcpy(lpvPtr1, lpbSoundData, dwBytes1); 
-	if (NULL != lpvPtr2) 
-	    pj_memcpy(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2); 
-	
-	hr = IDirectSoundBuffer_Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); 
-	if SUCCEEDED(hr)
-	    return TRUE; 
-    } 
-    
-    return FALSE; 
-}
-
-/*
- * Check if there is space in playing buffer.
- */
-static unsigned dsound_play_empty_size(struct dsound_stream *dsound_strm)
-{
-    HRESULT hr;
-    long size_available;
-    DWORD writePos, readPos;
-
-    hr = IDirectSoundBuffer_GetCurrentPosition(dsound_strm->ds.play.lpDsBuffer, 
-					       &readPos, &writePos);
-    if FAILED(hr)
-	return PJ_FALSE;
-
-    if (readPos < dsound_strm->dwBytePos)
-	size_available = readPos + dsound_strm->dwDsBufferSize - 
-			 dsound_strm->dwBytePos;
-    else
-	size_available = readPos - dsound_strm->dwBytePos;
-
-    return size_available;
-}
-
-
-/*
- * Check if there are captured frames in DirectSound capture buffer.
- */
-static unsigned dsound_captured_size(struct dsound_stream *dsound_strm)
-{
-    HRESULT hr;
-    long size_available;
-    DWORD writePos, readPos;
-
-    hr = IDirectSoundCaptureBuffer_GetCurrentPosition(
-					dsound_strm->ds.capture.lpDsBuffer, 
-					&writePos, &readPos);
-    if FAILED(hr)
-	return PJ_FALSE;
-
-    if (readPos < dsound_strm->dwBytePos)
-	size_available = readPos +
-		    (dsound_strm->dwDsBufferSize) - dsound_strm->dwBytePos;
-    else
-	size_available = readPos - dsound_strm->dwBytePos;
-
-    return size_available;
-}
-
-/*
- * DirectSound capture and playback thread.
- */
-static int dsound_dev_thread(void *arg)
-{
-    pjmedia_snd_stream *strm = arg;
-    HANDLE events[3];
-    unsigned eventCount;
-    unsigned bytes_per_frame;
-    pj_status_t status;
-
-
-    eventCount = 0;
-    events[eventCount++] = strm->thread_quit_event;
-    if (strm->dir & PJMEDIA_DIR_PLAYBACK)
-	events[eventCount++] = strm->play_strm.hEvent;
-    if (strm->dir & PJMEDIA_DIR_CAPTURE)
-	events[eventCount++] = strm->rec_strm.hEvent;
-
-
-    /* Raise self priority. We don't want the audio to be distorted by
-     * system activity.
-     */
-    SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
-
-    /* Calculate bytes per frame */
-    bytes_per_frame = strm->samples_per_frame * BYTES_PER_SAMPLE;
-
-    /*
-     * Loop while not signalled to quit, wait for event objects to be 
-     * signalled by DirectSound capture and play buffer.
-     */
-    while (PJ_TRUE) {
-	
-	DWORD rc;
-	pjmedia_dir signalled_dir;
-
-	rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
-	if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0+eventCount)
-	    continue;
-
-
-	if (rc == WAIT_OBJECT_0)
-		break;
-	if (rc == (WAIT_OBJECT_0 + 1)) {
-	    if (events[1] == strm->play_strm.hEvent)
-	        signalled_dir = PJMEDIA_DIR_PLAYBACK;
-	    else
-	        signalled_dir = PJMEDIA_DIR_CAPTURE;
-	} else {
-	    if (events[2] == strm->play_strm.hEvent)
-	        signalled_dir = PJMEDIA_DIR_PLAYBACK;
-	    else
-	        signalled_dir = PJMEDIA_DIR_CAPTURE;
-	}
-
-
-	if (signalled_dir == PJMEDIA_DIR_PLAYBACK) {
-	    
-	    struct dsound_stream *dsound_strm;
-
-	    /*
-	     * DirectSound has requested us to feed some frames to
-	     * playback buffer.
-	     */
-
-	    dsound_strm = &strm->play_strm;
-	    status = PJ_SUCCESS;
-
-	    while (dsound_play_empty_size(dsound_strm) > bytes_per_frame) {
-		/* Get frame from application. */
-		status = (*strm->play_cb)(strm->user_data, 
-					  dsound_strm->timestamp.u32.lo,
-					  strm->buffer,
-					  bytes_per_frame);
-		if (status != PJ_SUCCESS)
-		    break;
-
-		/* Write to DirectSound buffer. */
-		AppWriteDataToBuffer( dsound_strm->ds.play.lpDsBuffer, 
-				      dsound_strm->dwBytePos,
-				      (LPBYTE)strm->buffer, 
-				      bytes_per_frame);
-
-		/* Increment position. */
-		dsound_strm->dwBytePos += bytes_per_frame;
-		if (dsound_strm->dwBytePos >= dsound_strm->dwDsBufferSize)
-		    dsound_strm->dwBytePos -= dsound_strm->dwDsBufferSize;
-		dsound_strm->timestamp.u64 += strm->samples_per_frame;
-	    }
-
-	} else {
-	    /*
-	     * DirectSound has indicated that it has some frames ready
-	     * in the capture buffer. Get as much frames as possible to
-	     * prevent overflows.
-	     */
-	    struct dsound_stream *dsound_strm;
-	    BOOL rc;
-
-	    dsound_strm = &strm->rec_strm;
-
-	    /* Fetch while we have more than 1 frame */
-	    while (dsound_captured_size(dsound_strm) > bytes_per_frame) {
-
-		/* Capture from DirectSound buffer. */
-		rc = AppReadDataFromBuffer(dsound_strm->ds.capture.lpDsBuffer, 
-					   dsound_strm->dwBytePos,
-					   (LPBYTE)strm->buffer, 
-					   bytes_per_frame);
-        	
-		if (!rc) {
-		    pj_bzero(strm->buffer, bytes_per_frame);
-		} 
-
-		/* Call callback */
-		status = (*strm->rec_cb)(strm->user_data, 
-					 dsound_strm->timestamp.u32.lo, 
-					 strm->buffer, 
-					 bytes_per_frame);
-
-		/* Quit thread on error. */
-		if (status != PJ_SUCCESS)
-		    goto on_error;
-
-
-		/* Increment position. */
-		dsound_strm->dwBytePos += bytes_per_frame;
-		if (dsound_strm->dwBytePos >= dsound_strm->dwDsBufferSize)
-		    dsound_strm->dwBytePos -= dsound_strm->dwDsBufferSize;
-		dsound_strm->timestamp.u64 += strm->samples_per_frame;
-	    }
-	}
-    }
-
-
-on_error:
-    PJ_LOG(5,(THIS_FILE, "DirectSound: thread stopping.."));
-    return 0;
-}
-
-
-/* DirectSound enum device callback */
-static BOOL CALLBACK DSEnumCallback( LPGUID lpGuid, LPCTSTR lpcstrDescription,  
-				     LPCTSTR lpcstrModule, LPVOID lpContext)
-{
-    unsigned index, max = sizeof(dev_info[index].info.name);
-    pj_bool_t is_capture_device = (lpContext != NULL);
-
-
-    PJ_UNUSED_ARG(lpcstrModule);
-
-
-    /* Put the capture and playback of the same devices to the same 
-     * dev_info item, by looking at the GUID.
-     */
-    for (index=0; index<dev_count; ++index) {
-	if ((dev_info[index].lpGuid==NULL && lpGuid==NULL) ||
-	    pj_memcmp(&dev_info[index].guid, lpGuid, sizeof(GUID))==0)
-	{
-	    break;
-	}
-    }
-
-    if (index == dev_count)
-	++dev_count;
-    else if (dev_count >= MAX_HARDWARE) {
-	pj_assert(!"Too many DirectSound hardware found");
-	PJ_LOG(4,(THIS_FILE, "Too many hardware found, some devices will "
-			     "not be listed"));
-	return FALSE;
-    }
-
-#ifdef UNICODE
-    WideCharToMultiByte(CP_ACP, 0, lpcstrDescription, wcslen(lpcstrDescription),
-			dev_info[index].info.name, max, NULL, NULL);
-#else
-    strncpy(dev_info[index].info.name, lpcstrDescription, max);
-#endif
-
-    dev_info[index].info.name[max-1] = '\0';
-    if (lpGuid == NULL) {
-	dev_info[index].lpGuid = NULL;
-    } else {
-	pj_memcpy(&dev_info[index].guid, lpGuid, sizeof(GUID));
-	dev_info[index].lpGuid = &dev_info[index].guid;
-    }
-    dev_info[index].info.default_samples_per_sec = 44100;
-    
-    /* Just assumed that device supports stereo capture/playback */
-    if (is_capture_device)
-	dev_info[index].info.input_count+=2;
-    else
-	dev_info[index].info.output_count+=2;
-
-    return TRUE;
-}
-
-
-/*
- * Init sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
-    HRESULT hr;
-    unsigned i;
-
-    if (++snd_init_count != 1)
-	return PJ_SUCCESS;
-
-    pool_factory = factory;
-
-    /* Enumerate sound playback devices */
-    hr = DirectSoundEnumerate(&DSEnumCallback, NULL);
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-    /* Enumerate sound capture devices */
-    hr = DirectSoundCaptureEnumerate(&DSEnumCallback, (void*)1);
-    if (FAILED(hr))
-	return PJ_RETURN_OS_ERROR(hr);
-
-    PJ_LOG(4,(THIS_FILE, "DirectSound initialized, found %d devices:",
-	      dev_count));
-    for (i=0; i<dev_count; ++i) {
-	PJ_LOG(4,(THIS_FILE, " dev_id %d: %s  (in=%d, out=%d)", 
-		  i, dev_info[i].info.name, 
-		  dev_info[i].info.input_count, 
-		  dev_info[i].info.output_count));
-    }
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Deinitialize sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
-    --snd_init_count;
-    return PJ_SUCCESS;
-}
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
-    return dev_count;
-}
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
-    if (index == (unsigned)-1) 
-	index = 0;
-
-    PJ_ASSERT_RETURN(index < dev_count, NULL);
-
-    return &dev_info[index].info;
-}
-
-
-/*
- * Open stream.
- */
-static pj_status_t open_stream( pjmedia_dir dir,
-			        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 *strm;
-    pj_status_t status;
-
-    /* Make sure sound subsystem has been initialized with
-     * pjmedia_snd_init()
-     */
-    PJ_ASSERT_RETURN( pool_factory != NULL, PJ_EINVALIDOP );
-
-
-    /* Can only support 16bits per sample */
-    PJ_ASSERT_RETURN(bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
-
-    /* Create and Initialize stream descriptor */
-    pool = pj_pool_create(pool_factory, "dsound-dev", 1000, 1000, NULL);
-    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
-
-    strm = pj_pool_zalloc(pool, sizeof(pjmedia_snd_stream));
-    strm->dir = dir;
-    strm->play_id = play_id;
-    strm->rec_id = rec_id;
-    strm->pool = pool;
-    strm->rec_cb = rec_cb;
-    strm->play_cb = play_cb;
-    strm->user_data = user_data;
-    strm->clock_rate = clock_rate;
-    strm->samples_per_frame = samples_per_frame;
-    strm->bits_per_sample = bits_per_sample;
-    strm->channel_count = channel_count;
-    strm->buffer = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE);
-    if (!strm->buffer) {
-	pj_pool_release(pool);
-	return PJ_ENOMEM;
-    }
-
-    /*
-     * Create event for stopping the worker thread.
-     */
-    strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
-    if (strm->thread_quit_event == NULL) {
-	status = pj_get_os_error();
-	pj_pool_release(pool);
-	return status;
-    }
-
-    /* Create player stream */
-    if (dir & PJMEDIA_DIR_PLAYBACK) {
-	unsigned buffer_count;
-	
-	/* Calculate buffer count, in frame unit */
-	buffer_count = clock_rate * snd_output_latency / samples_per_frame /
-		       1000;
-	/* There must always be one more buffer than required for the latency */
-	buffer_count += 1;
-	if (buffer_count < MIN_PACKET_BUFFER_COUNT)
-	    buffer_count = MIN_PACKET_BUFFER_COUNT;
-	if (buffer_count > MAX_PACKET_BUFFER_COUNT)
-	    buffer_count = MAX_PACKET_BUFFER_COUNT;
-
-	status = init_player_stream( &strm->play_strm, play_id, clock_rate,
-				     channel_count, samples_per_frame,
-				     buffer_count );
-	if (status != PJ_SUCCESS) {
-	    pjmedia_snd_stream_close(strm);
-	    return status;
-	}
-    }
-
-    /* Create capture stream */
-    if (dir & PJMEDIA_DIR_CAPTURE) {
-	unsigned buffer_count;
-	
-	/* Calculate buffer count, in frame unit */
-	buffer_count = clock_rate * snd_input_latency / samples_per_frame /
-		       1000;
-	if (buffer_count < MIN_PACKET_BUFFER_COUNT)
-	    buffer_count = MIN_PACKET_BUFFER_COUNT;
-	if (buffer_count > MAX_PACKET_BUFFER_COUNT)
-	    buffer_count = MAX_PACKET_BUFFER_COUNT;
-
-	status = init_capture_stream( &strm->rec_strm, rec_id, clock_rate,
-				      channel_count, samples_per_frame,
-				      buffer_count);
-	if (status != PJ_SUCCESS) {
-	    pjmedia_snd_stream_close(strm);
-	    return status;
-	}
-    }
-
-
-    /* Create and start the thread */
-    status = pj_thread_create(pool, "dsound", &dsound_dev_thread, strm,
-			      0, 0, &strm->thread);
-    if (status != PJ_SUCCESS) {
-	pjmedia_snd_stream_close(strm);
-	return status;
-    }
-
-    *p_snd_strm = strm;
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Open stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
-					  unsigned clock_rate,
-					  unsigned channel_count,
-					  unsigned samples_per_frame,
-					  unsigned bits_per_sample,
-					  pjmedia_snd_rec_cb rec_cb,
-					  void *user_data,
-					  pjmedia_snd_stream **p_snd_strm)
-{
-    PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
-
-    return open_stream( PJMEDIA_DIR_CAPTURE, index, -1,
-			clock_rate, channel_count, samples_per_frame,
-			bits_per_sample, rec_cb, NULL, user_data,
-			p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
-					unsigned clock_rate,
-					unsigned channel_count,
-					unsigned samples_per_frame,
-					unsigned bits_per_sample,
-					pjmedia_snd_play_cb play_cb,
-					void *user_data,
-					pjmedia_snd_stream **p_snd_strm)
-{
-    PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
-
-    return open_stream( PJMEDIA_DIR_PLAYBACK, -1, index,
-			clock_rate, channel_count, samples_per_frame,
-			bits_per_sample, NULL, play_cb, user_data,
-			p_snd_strm);
-}
-
-/*
- * 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_ASSERT_RETURN(rec_cb && play_cb && p_snd_strm, PJ_EINVAL);
-
-    return open_stream( PJMEDIA_DIR_CAPTURE_PLAYBACK, rec_id, play_id,
-			clock_rate, channel_count, samples_per_frame,
-			bits_per_sample, rec_cb, play_cb, user_data,
-			p_snd_strm );
-}
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
-						pjmedia_snd_stream_info *pi)
-{
-
-    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-
-    pj_bzero(pi, sizeof(*pi));
-    pi->dir = strm->dir;
-    pi->play_id = strm->play_id;
-    pi->rec_id = strm->rec_id;
-    pi->clock_rate = strm->clock_rate;
-    pi->channel_count = strm->channel_count;
-    pi->samples_per_frame = strm->samples_per_frame;
-    pi->bits_per_sample = strm->bits_per_sample;
-    pi->rec_latency = strm->rec_strm.latency * strm->clock_rate * 
-		      strm->channel_count/ 1000;
-    pi->play_latency = strm->play_strm.latency * strm->clock_rate * 
-		       strm->channel_count/ 1000;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Start stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
-    HRESULT hr;
-
-    if (stream->play_strm.ds.play.lpDsBuffer) {
-	hr = IDirectSoundBuffer_SetCurrentPosition(
-				stream->play_strm.ds.play.lpDsBuffer, 0);
-	if (FAILED(hr))
-	    return PJ_RETURN_OS_ERROR(hr);
-	
-	/* Set the write pointer ahead of the read pointer by latency bytes */
-	stream->play_strm.dwBytePos = BYTES_PER_SAMPLE * stream->clock_rate *
-		stream->channel_count * stream->play_strm.latency / 1000;
-
-	hr = IDirectSoundBuffer_Play(stream->play_strm.ds.play.lpDsBuffer, 
-				     0, 0, DSBPLAY_LOOPING);
-	if (FAILED(hr))
-	    return PJ_RETURN_OS_ERROR(hr);
-	PJ_LOG(5,(THIS_FILE, "DirectSound playback stream started"));
-    }
-    
-    if (stream->rec_strm.ds.capture.lpDsBuffer) {
-	hr = IDirectSoundCaptureBuffer_GetCurrentPosition( 
-				stream->rec_strm.ds.capture.lpDsBuffer, 
-				NULL, &stream->rec_strm.dwBytePos );
-	if (FAILED(hr))
-	    return PJ_RETURN_OS_ERROR(hr);
-
-	hr = IDirectSoundCaptureBuffer_Start(
-				stream->rec_strm.ds.capture.lpDsBuffer,
-				DSCBSTART_LOOPING );
-	if (FAILED(hr))
-	    return PJ_RETURN_OS_ERROR(hr);
-	PJ_LOG(5,(THIS_FILE, "DirectSound capture stream started"));
-    }
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Stop stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
-    if (stream->play_strm.ds.play.lpDsBuffer) {
-	PJ_LOG(5,(THIS_FILE, "Stopping DirectSound playback stream"));
-	IDirectSoundBuffer_Stop( stream->play_strm.ds.play.lpDsBuffer );
-    }
-
-    if (stream->rec_strm.ds.capture.lpDsBuffer) {
-	PJ_LOG(5,(THIS_FILE, "Stopping DirectSound capture stream"));
-	IDirectSoundCaptureBuffer_Stop(stream->rec_strm.ds.capture.lpDsBuffer);
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Destroy stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
-    pjmedia_snd_stream_stop(stream);
-
-    if (stream->thread) {
-	pj_assert(stream->thread_quit_event);
-	SetEvent(stream->thread_quit_event);
-	pj_thread_join(stream->thread);
-	pj_thread_destroy(stream->thread);
-	stream->thread = NULL;
-    }
-
-    if (stream->thread_quit_event) {
-	CloseHandle(stream->thread_quit_event);
-	stream->thread_quit_event = NULL;
-    }
-
-    if (stream->play_strm.lpDsNotify) {
-	IDirectSoundNotify_Release( stream->play_strm.lpDsNotify );
-	stream->play_strm.lpDsNotify = NULL;
-    }
-    
-    if (stream->play_strm.hEvent) {
-	CloseHandle(stream->play_strm.hEvent);
-	stream->play_strm.hEvent = NULL;
-    }
-
-    if (stream->play_strm.ds.play.lpDsBuffer) {
-	IDirectSoundBuffer_Release( stream->play_strm.ds.play.lpDsBuffer );
-	stream->play_strm.ds.play.lpDsBuffer = NULL;
-    }
-
-    if (stream->play_strm.ds.play.lpDs) {
-	IDirectSound_Release( stream->play_strm.ds.play.lpDs );
-	stream->play_strm.ds.play.lpDs = NULL;
-    }
-
-    if (stream->rec_strm.lpDsNotify) {
-	IDirectSoundNotify_Release( stream->rec_strm.lpDsNotify );
-	stream->rec_strm.lpDsNotify = NULL;
-    }
-    
-    if (stream->rec_strm.hEvent) {
-	CloseHandle(stream->rec_strm.hEvent);
-	stream->rec_strm.hEvent = NULL;
-    }
-
-    if (stream->rec_strm.ds.capture.lpDsBuffer) {
-	IDirectSoundCaptureBuffer_Release( stream->rec_strm.ds.capture.lpDsBuffer );
-	stream->rec_strm.ds.capture.lpDsBuffer = NULL;
-    }
-
-    if (stream->rec_strm.ds.capture.lpDs) {
-	IDirectSoundCapture_Release( stream->rec_strm.ds.capture.lpDs );
-	stream->rec_strm.ds.capture.lpDs = NULL;
-    }
-
-
-    pj_pool_release(stream->pool);
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, 
-					    unsigned output_latency)
-{
-    snd_input_latency  = (input_latency == 0)? 
-			 PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency;
-    snd_output_latency = (output_latency == 0)? 
-			 PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency;
-
-    return PJ_SUCCESS;
-}
-
-#endif	/* PJMEDIA_SOUND_IMPLEMENTATION */
-
Index: pjproject/trunk/pjmedia/src/pjmedia/endpoint.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/endpoint.c	(revision 2505)
+++ pjproject/trunk/pjmedia/src/pjmedia/endpoint.c	(revision 2506)
@@ -21,4 +21,5 @@
 #include <pjmedia/errno.h>
 #include <pjmedia/sdp.h>
+#include <pjmedia-audiodev/audiodev.h>
 #include <pj/assert.h>
 #include <pj/ioqueue.h>
@@ -122,5 +123,5 @@
 
     /* Sound */
-    status = pjmedia_snd_init(pf);
+    status = pjmedia_aud_subsys_init(pf);
     if (status != PJ_SUCCESS)
 	goto on_error;
@@ -172,5 +173,5 @@
 	pj_ioqueue_destroy(endpt->ioqueue);
 
-    pjmedia_snd_deinit();
+    pjmedia_aud_subsys_shutdown();
     pj_pool_release(pool);
     return status;
@@ -213,5 +214,5 @@
     endpt->pf = NULL;
 
-    pjmedia_snd_deinit();
+    pjmedia_aud_subsys_shutdown();
     pj_pool_release (endpt->pool);
 
Index: pjproject/trunk/pjmedia/src/pjmedia/errno.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/errno.c	(revision 2505)
+++ pjproject/trunk/pjmedia/src/pjmedia/errno.c	(revision 2506)
@@ -21,5 +21,6 @@
 #include <pjmedia/types.h>
 #include <pj/string.h>
-#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND
+#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \
+    PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND
 #   include <portaudio.h>
 #endif
@@ -180,5 +181,6 @@
 
     /* See if the error comes from PortAudio. */
-#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_PORTAUDIO_SOUND
+#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \
+    PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND
     if (statcode >= PJMEDIA_PORTAUDIO_ERRNO_START &&
 	statcode <= PJMEDIA_PORTAUDIO_ERRNO_END)
Index: pjproject/trunk/pjmedia/src/pjmedia/nullsound.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/nullsound.c	(revision 2505)
+++ 	(revision )
@@ -1,197 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * 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 
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/pool.h>
-
-#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_NULL_SOUND
-
-static pjmedia_snd_dev_info null_info = 
-{
-    "Null Device",
-    1,
-    1,
-    8000
-};
-
-static pj_pool_factory *pool_factory;
-
-struct pjmedia_snd_stream 
-{
-	pj_pool_t		*pool;
-	pjmedia_dir 		dir;
-	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;
-};
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
-    pool_factory = factory;
-    return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
-    return PJ_SUCCESS;
-}
-
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
-    return 1;
-}
-
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
-    PJ_ASSERT_RETURN(index==0 || index==(unsigned)-1, NULL);
-    return &null_info;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
-					  unsigned clock_rate,
-					  unsigned channel_count,
-					  unsigned samples_per_frame,
-					  unsigned bits_per_sample,
-					  pjmedia_snd_rec_cb rec_cb,
-					  void *user_data,
-					  pjmedia_snd_stream **p_snd_strm)
-{
-    return pjmedia_snd_open(index, -2, clock_rate, channel_count,
-    			    samples_per_frame, bits_per_sample,
-    			    rec_cb, NULL, user_data, p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
-					unsigned clock_rate,
-					unsigned channel_count,
-					unsigned samples_per_frame,
-					unsigned bits_per_sample,
-					pjmedia_snd_play_cb play_cb,
-					void *user_data,
-					pjmedia_snd_stream **p_snd_strm )
-{
-    return pjmedia_snd_open(-2, index, clock_rate, channel_count,
-    			    samples_per_frame, bits_per_sample,
-    			    NULL, play_cb, user_data, p_snd_strm);
-}
-
-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 *snd_strm;
-
-    pool = pj_pool_create(pool_factory, NULL, 128, 128, NULL);
-    snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
-    
-    snd_strm->pool = pool;
-    
-    if (rec_id == -1) rec_id = 0;
-    if (play_id == -1) play_id = 0;
-    
-    if (rec_id != -2 && play_id != -2)
-    	snd_strm->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
-    else if (rec_id != -2)
-    	snd_strm->dir = PJMEDIA_DIR_CAPTURE;
-    else if (play_id != -2)
-    	snd_strm->dir = PJMEDIA_DIR_PLAYBACK;
-    
-    snd_strm->rec_id = rec_id;
-    snd_strm->play_id = play_id;
-    snd_strm->clock_rate = clock_rate;
-    snd_strm->channel_count = channel_count;
-    snd_strm->samples_per_frame = samples_per_frame;
-    snd_strm->bits_per_sample = bits_per_sample;
-    snd_strm->rec_cb = rec_cb;
-    snd_strm->play_cb = play_cb;
-    snd_strm->user_data = user_data;
-    
-    *p_snd_strm = snd_strm;
-
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
-    PJ_UNUSED_ARG(stream);
-    return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
-    PJ_UNUSED_ARG(stream);
-    return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
-						pjmedia_snd_stream_info *pi)
-{
-
-    pj_bzero(pi, sizeof(pjmedia_snd_stream_info));
-    pi->dir = strm->dir;
-    pi->play_id = strm->play_id;
-    pi->rec_id = strm->rec_id;
-    pi->clock_rate = strm->clock_rate;
-    pi->channel_count = strm->channel_count;
-    pi->samples_per_frame = strm->samples_per_frame;
-    pi->bits_per_sample = strm->bits_per_sample;
-    pi->rec_latency = 0;
-    pi->play_latency = 0;
-    
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
-    pj_pool_release(stream->pool);
-    return PJ_SUCCESS;
-}
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, 
-					    unsigned output_latency)
-{
-    /* Nothing to do */
-    PJ_UNUSED_ARG(input_latency);
-    PJ_UNUSED_ARG(output_latency);
-    return PJ_SUCCESS;
-}
-
-#endif	/* PJMEDIA_SOUND_IMPLEMENTATION */
Index: pjproject/trunk/pjmedia/src/pjmedia/pasound.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/pasound.c	(revision 2505)
+++ 	(revision )
@@ -1,1037 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * 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 
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/os.h>
-#include <pj/string.h>
-#include <portaudio.h>
-
-#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_PORTAUDIO_SOUND
-
-#define THIS_FILE	"pasound.c"
-
-static int snd_init_count;
-
-/* Latency settings */
-static unsigned snd_input_latency  = PJMEDIA_SND_DEFAULT_REC_LATENCY;
-static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
-
-static struct snd_mgr
-{
-    pj_pool_factory *factory;
-} 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;
-    pjmedia_dir		 dir;
-    int			 play_id;
-    int			 rec_id;
-    int			 bytes_per_sample;
-    pj_uint32_t		 samples_per_sec;
-    unsigned		 samples_per_frame;
-    int			 channel_count;
-
-    PaStream		*rec_strm;
-    PaStream		*play_strm;
-
-    void		*user_data;
-    pjmedia_snd_rec_cb   rec_cb;
-    pjmedia_snd_play_cb  play_cb;
-
-    pj_uint32_t		 play_timestamp;
-    pj_uint32_t		 rec_timestamp;
-    pj_uint32_t		 underflow;
-    pj_uint32_t		 overflow;
-
-    pj_bool_t		 quit_flag;
-
-    pj_bool_t		 rec_thread_exited;
-    pj_bool_t		 rec_thread_initialized;
-    pj_thread_desc	 rec_thread_desc;
-    pj_thread_t		*rec_thread;
-
-    pj_bool_t		 play_thread_exited;
-    pj_bool_t		 play_thread_initialized;
-    pj_thread_desc	 play_thread_desc;
-    pj_thread_t		*play_thread;
-
-    /* Sometime the record callback does not return framesize as configured
-     * (e.g: in OSS), while this module must guarantee returning framesize
-     * as configured in the creation settings. In this case, we need a buffer 
-     * for the recorded samples.
-     */
-    pj_int16_t		*rec_buf;
-    unsigned		 rec_buf_count;
-
-    /* Sometime the player callback does not request framesize as configured
-     * (e.g: in Linux OSS) while sound device will always get samples from 
-     * the other component as many as configured samples_per_frame. 
-     */
-    pj_int16_t		*play_buf;
-    unsigned		 play_buf_count;
-};
-
-
-static int PaRecorderCallback(const void *input, 
-			      void *output,
-			      unsigned long frameCount,
-			      const PaStreamCallbackTimeInfo* timeInfo,
-			      PaStreamCallbackFlags statusFlags,
-			      void *userData )
-{
-    pjmedia_snd_stream *stream = (pjmedia_snd_stream*) userData;
-    pj_status_t status = 0;
-    unsigned nsamples;
-
-    PJ_UNUSED_ARG(output);
-    PJ_UNUSED_ARG(timeInfo);
-
-    if (stream->quit_flag)
-	goto on_break;
-
-    if (input == NULL)
-	return paContinue;
-
-    /* Known cases of callback's thread:
-     * - The thread may be changed in the middle of a session, e.g: in MacOS 
-     *   it happens when plugging/unplugging headphone.
-     * - The same thread may be reused in consecutive sessions. The first
-     *   session will leave TLS set, but release the TLS data address,
-     *   so the second session must re-register the callback's thread.
-     */
-    if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered()) 
-    {
-	status = pj_thread_register("pa_rec", stream->rec_thread_desc, 
-				    &stream->rec_thread);
-	stream->rec_thread_initialized = 1;
-	PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
-    }
-
-    if (statusFlags & paInputUnderflow)
-	++stream->underflow;
-    if (statusFlags & paInputOverflow)
-	++stream->overflow;
-
-    stream->rec_timestamp += frameCount;
-
-    /* Calculate number of samples we've got */
-    nsamples = frameCount * stream->channel_count + stream->rec_buf_count;
-
-    if (nsamples >= stream->samples_per_frame) 
-    {
-	/* If buffer is not empty, combine the buffer with the just incoming
-	 * samples, then call put_frame.
-	 */
-	if (stream->rec_buf_count) {
-	    unsigned chunk_count = 0;
-	
-	    chunk_count = stream->samples_per_frame - stream->rec_buf_count;
-	    pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
-				 (pj_int16_t*)input, chunk_count);
-	    status = (*stream->rec_cb)(stream->user_data, 
-				       stream->rec_timestamp,
-				       (void*) stream->rec_buf, 
-				       stream->samples_per_frame * 
-				       stream->bytes_per_sample);
-
-	    input = (pj_int16_t*) input + chunk_count;
-	    nsamples -= stream->samples_per_frame;
-	    stream->rec_buf_count = 0;
-	}
-
-	/* Give all frames we have */
-	while (nsamples >= stream->samples_per_frame && status == 0) {
-	    status = (*stream->rec_cb)(stream->user_data, 
-				       stream->rec_timestamp,
-				       (void*) input, 
-				       stream->samples_per_frame * 
-				       stream->bytes_per_sample);
-	    input = (pj_int16_t*) input + stream->samples_per_frame;
-	    nsamples -= stream->samples_per_frame;
-	}
-
-	/* Store the remaining samples into the buffer */
-	if (nsamples && status == 0) {
-	    stream->rec_buf_count = nsamples;
-	    pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input, 
-			         nsamples);
-	}
-
-    } else {
-	/* Not enough samples, let's just store them in the buffer */
-	pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
-			     (pj_int16_t*)input, 
-			     frameCount * stream->channel_count);
-	stream->rec_buf_count += frameCount * stream->channel_count;
-    }
-
-    if (status==0) 
-	return paContinue;
-
-on_break:
-    stream->rec_thread_exited = 1;
-    return paAbort;
-}
-
-static int PaPlayerCallback( const void *input, 
-			     void *output,
-			     unsigned long frameCount,
-			     const PaStreamCallbackTimeInfo* timeInfo,
-			     PaStreamCallbackFlags statusFlags,
-			     void *userData )
-{
-    pjmedia_snd_stream *stream = (pjmedia_snd_stream*) userData;
-    pj_status_t status = 0;
-    unsigned nsamples_req = frameCount * stream->channel_count;
-
-    PJ_UNUSED_ARG(input);
-    PJ_UNUSED_ARG(timeInfo);
-
-    if (stream->quit_flag)
-	goto on_break;
-
-    if (output == NULL)
-	return paContinue;
-
-    /* Known cases of callback's thread:
-     * - The thread may be changed in the middle of a session, e.g: in MacOS 
-     *   it happens when plugging/unplugging headphone.
-     * - The same thread may be reused in consecutive sessions. The first
-     *   session will leave TLS set, but release the TLS data address,
-     *   so the second session must re-register the callback's thread.
-     */
-    if (stream->play_thread_initialized == 0 || !pj_thread_is_registered()) 
-    {
-	status = pj_thread_register("portaudio", stream->play_thread_desc,
-				    &stream->play_thread);
-	stream->play_thread_initialized = 1;
-	PJ_LOG(5,(THIS_FILE, "Player thread started"));
-    }
-
-    if (statusFlags & paOutputUnderflow)
-	++stream->underflow;
-    if (statusFlags & paOutputOverflow)
-	++stream->overflow;
-
-    stream->play_timestamp += frameCount;
-
-    /* Check if any buffered samples */
-    if (stream->play_buf_count) {
-	/* samples buffered >= requested by sound device */
-	if (stream->play_buf_count >= nsamples_req) {
-	    pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
-				 nsamples_req);
-	    stream->play_buf_count -= nsamples_req;
-	    pjmedia_move_samples(stream->play_buf, 
-				 stream->play_buf + nsamples_req,
-				 stream->play_buf_count);
-	    nsamples_req = 0;
-	    
-	    return paContinue;
-	}
-
-	/* samples buffered < requested by sound device */
-	pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
-			     stream->play_buf_count);
-	nsamples_req -= stream->play_buf_count;
-	output = (pj_int16_t*)output + stream->play_buf_count;
-	stream->play_buf_count = 0;
-    }
-
-    /* Fill output buffer as requested */
-    while (nsamples_req && status == 0) {
-	if (nsamples_req >= stream->samples_per_frame) {
-	    status = (*stream->play_cb)(stream->user_data, 
-					stream->play_timestamp, 
-					output, 
-					stream->samples_per_frame * 
-					stream->bytes_per_sample);
-	    nsamples_req -= stream->samples_per_frame;
-	    output = (pj_int16_t*)output + stream->samples_per_frame;
-	} else {
-	    status = (*stream->play_cb)(stream->user_data, 
-					stream->play_timestamp, 
-					stream->play_buf,
-					stream->samples_per_frame * 
-					stream->bytes_per_sample);
-	    pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
-				 nsamples_req);
-	    stream->play_buf_count = stream->samples_per_frame - nsamples_req;
-	    pjmedia_move_samples(stream->play_buf, 
-				 stream->play_buf+nsamples_req,
-				 stream->play_buf_count);
-	    nsamples_req = 0;
-	}
-    }
-    
-    if (status==0) 
-	return paContinue;
-
-on_break:
-    stream->play_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;
-}
-
-/* Logging callback from PA */
-static void pa_log_cb(const char *log)
-{
-    PJ_LOG(5,(THIS_FILE, "PA message: %s", log));
-}
-
-/* We should include pa_debugprint.h for this, but the header
- * is not available publicly. :(
- */
-typedef void (*PaUtilLogCallback ) (const char *log);
-void PaUtil_SetDebugPrintFunction(PaUtilLogCallback  cb);
-
-/*
- * Init sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
-    if (++snd_init_count == 1) {
-	int err;
-
-	PaUtil_SetDebugPrintFunction(&pa_log_cb);
-
-	snd_mgr.factory = factory;
-	err = Pa_Initialize();
-
-	PJ_LOG(4,(THIS_FILE, 
-		  "PortAudio sound library initialized, status=%d", err));
-	PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
-			     Pa_GetHostApiCount()));
-	PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
-			     pjmedia_snd_get_dev_count()));
-
-	return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
-    } else {
-	return PJ_SUCCESS;
-    }
-}
-
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
-    int count = Pa_GetDeviceCount();
-    return count < 0 ? 0 : count;
-}
-
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
-    static pjmedia_snd_dev_info info;
-    const PaDeviceInfo *pa_info;
-
-    pa_info = Pa_GetDeviceInfo(index);
-    if (!pa_info)
-	return NULL;
-
-    pj_bzero(&info, sizeof(info));
-    strncpy(info.name, pa_info->name, sizeof(info.name));
-    info.name[sizeof(info.name)-1] = '\0';
-    info.input_count = pa_info->maxInputChannels;
-    info.output_count = pa_info->maxOutputChannels;
-    info.default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
-
-    return &info;
-}
-
-
-/* Get PortAudio default input device ID */
-static int pa_get_default_input_dev(int channel_count)
-{
-    int i, count;
-
-    /* Special for Windows - try to use the DirectSound implementation
-     * first since it provides better latency.
-     */
-#if PJMEDIA_PREFER_DIRECT_SOUND
-    if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
-	const PaHostApiInfo *pHI;
-	int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
-	pHI = Pa_GetHostApiInfo(index);
-	if (pHI) {
-	    const PaDeviceInfo *paDevInfo = NULL;
-	    paDevInfo = Pa_GetDeviceInfo(pHI->defaultInputDevice);
-	    if (paDevInfo && paDevInfo->maxInputChannels >= channel_count)
-		return pHI->defaultInputDevice;
-	}
-    }
-#endif
-
-    /* Enumerate the host api's for the default devices, and return
-     * the device with suitable channels.
-     */
-    count = Pa_GetHostApiCount();
-    for (i=0; i < count; ++i) {
-	const PaHostApiInfo *pHAInfo;
-
-	pHAInfo = Pa_GetHostApiInfo(i);
-	if (!pHAInfo)
-	    continue;
-
-	if (pHAInfo->defaultInputDevice >= 0) {
-	    const PaDeviceInfo *paDevInfo;
-
-	    paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice);
-
-	    if (paDevInfo->maxInputChannels >= channel_count)
-		return pHAInfo->defaultInputDevice;
-	}
-    }
-
-    /* If still no device is found, enumerate all devices */
-    count = pjmedia_snd_get_dev_count();
-    for (i=0; i<count; ++i) {
-	const PaDeviceInfo *paDevInfo;
-
-	paDevInfo = Pa_GetDeviceInfo(i);
-	if (paDevInfo->maxInputChannels >= channel_count)
-	    return i;
-    }
-    
-    return -1;
-}
-
-/* Get PortAudio default output device ID */
-static int pa_get_default_output_dev(int channel_count)
-{
-    int i, count;
-
-    /* Special for Windows - try to use the DirectSound implementation
-     * first since it provides better latency.
-     */
-#if PJMEDIA_PREFER_DIRECT_SOUND
-    if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
-	const PaHostApiInfo *pHI;
-	int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
-	pHI = Pa_GetHostApiInfo(index);
-	if (pHI) {
-	    const PaDeviceInfo *paDevInfo = NULL;
-	    paDevInfo = Pa_GetDeviceInfo(pHI->defaultOutputDevice);
-	    if (paDevInfo && paDevInfo->maxOutputChannels >= channel_count)
-		return pHI->defaultOutputDevice;
-	}
-    }
-#endif
-
-    /* Enumerate the host api's for the default devices, and return
-     * the device with suitable channels.
-     */
-    count = Pa_GetHostApiCount();
-    for (i=0; i < count; ++i) {
-	const PaHostApiInfo *pHAInfo;
-
-	pHAInfo = Pa_GetHostApiInfo(i);
-	if (!pHAInfo)
-	    continue;
-
-	if (pHAInfo->defaultOutputDevice >= 0) {
-	    const PaDeviceInfo *paDevInfo;
-
-	    paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice);
-
-	    if (paDevInfo->maxOutputChannels >= channel_count)
-		return pHAInfo->defaultOutputDevice;
-	}
-    }
-
-    /* If still no device is found, enumerate all devices */
-    count = pjmedia_snd_get_dev_count();
-    for (i=0; i<count; ++i) {
-	const PaDeviceInfo *paDevInfo;
-
-	paDevInfo = Pa_GetDeviceInfo(i);
-	if (paDevInfo->maxOutputChannels >= channel_count)
-	    return i;
-    }
-
-    return -1;
-}
-
-
-/*
- * Open stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
-					  unsigned clock_rate,
-					  unsigned channel_count,
-					  unsigned samples_per_frame,
-					  unsigned bits_per_sample,
-					  pjmedia_snd_rec_cb rec_cb,
-					  void *user_data,
-					  pjmedia_snd_stream **p_snd_strm)
-{
-    pj_pool_t *pool;
-    pjmedia_snd_stream *stream;
-    PaStreamParameters inputParam;
-    int sampleFormat;
-    const PaDeviceInfo *paDevInfo = NULL;
-    const PaHostApiInfo *paHostApiInfo = NULL;
-    unsigned paFrames, paRate, paLatency;
-    const PaStreamInfo *paSI;
-    PaError err;
-
-    if (index < 0) {
-	index = pa_get_default_input_dev(channel_count);
-	if (index < 0) {
-	    /* No such device. */
-	    return PJMEDIA_ENOSNDREC;
-	}
-    }
-
-    paDevInfo = Pa_GetDeviceInfo(index);
-    if (!paDevInfo) {
-	/* 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_T(pool, pjmedia_snd_stream);
-    stream->pool = pool;
-    pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
-    stream->dir = PJMEDIA_DIR_CAPTURE;
-    stream->rec_id = index;
-    stream->play_id = -1;
-    stream->user_data = user_data;
-    stream->samples_per_sec = clock_rate;
-    stream->samples_per_frame = samples_per_frame;
-    stream->bytes_per_sample = bits_per_sample / 8;
-    stream->channel_count = channel_count;
-    stream->rec_cb = rec_cb;
-
-    stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, 
-		      stream->samples_per_frame * stream->bytes_per_sample);
-    stream->rec_buf_count = 0;
-
-    pj_bzero(&inputParam, sizeof(inputParam));
-    inputParam.device = index;
-    inputParam.channelCount = channel_count;
-    inputParam.hostApiSpecificStreamInfo = NULL;
-    inputParam.sampleFormat = sampleFormat;
-    inputParam.suggestedLatency = snd_input_latency / 1000.0;
-
-    paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
-
-    /* Frames in PortAudio is number of samples in a single channel */
-    paFrames = samples_per_frame / channel_count;
-
-    err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
-			 clock_rate, paFrames, 
-			 paClipOff, &PaRecorderCallback, stream );
-    if (err != paNoError) {
-	pj_pool_release(pool);
-	return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
-    }
-
-    paSI = Pa_GetStreamInfo(stream->rec_strm);
-    paRate = (unsigned)paSI->sampleRate;
-    paLatency = (unsigned)(paSI->inputLatency * 1000);
-
-    PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
-			 "rate=%d, ch=%d, "
-			 "bits=%d, %d samples per frame, latency=%d ms",
-			 paDevInfo->name, paHostApiInfo->name,
-			 paRate, channel_count,
-			 bits_per_sample, samples_per_frame,
-			 paLatency));
-
-    *p_snd_strm = stream;
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
-					unsigned clock_rate,
-					unsigned channel_count,
-					unsigned samples_per_frame,
-					unsigned bits_per_sample,
-					pjmedia_snd_play_cb play_cb,
-					void *user_data,
-					pjmedia_snd_stream **p_snd_strm)
-{
-    pj_pool_t *pool;
-    pjmedia_snd_stream *stream;
-    PaStreamParameters outputParam;
-    int sampleFormat;
-    const PaDeviceInfo *paDevInfo = NULL;
-    const PaHostApiInfo *paHostApiInfo = NULL;
-    const PaStreamInfo *paSI;
-    unsigned paFrames, paRate, paLatency;
-    PaError err;
-
-    if (index < 0) {
-	index = pa_get_default_output_dev(channel_count);
-	if (index < 0) {
-	    /* No such device. */
-	    return PJMEDIA_ENOSNDPLAY;
-	}
-    } 
-
-    paDevInfo = Pa_GetDeviceInfo(index);
-    if (!paDevInfo) {
-	/* 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_T(pool, pjmedia_snd_stream);
-    stream->pool = pool;
-    pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
-    stream->dir = PJMEDIA_DIR_PLAYBACK;
-    stream->play_id = index;
-    stream->rec_id = -1;
-    stream->user_data = user_data;
-    stream->samples_per_sec = clock_rate;
-    stream->samples_per_frame = samples_per_frame;
-    stream->bytes_per_sample = bits_per_sample / 8;
-    stream->channel_count = channel_count;
-    stream->play_cb = play_cb;
-
-    stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, 
-		       stream->samples_per_frame * stream->bytes_per_sample);
-    stream->play_buf_count = 0;
-
-    pj_bzero(&outputParam, sizeof(outputParam));
-    outputParam.device = index;
-    outputParam.channelCount = channel_count;
-    outputParam.hostApiSpecificStreamInfo = NULL;
-    outputParam.sampleFormat = sampleFormat;
-    outputParam.suggestedLatency = snd_output_latency / 1000.0;
-
-    paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
-
-    /* Frames in PortAudio is number of samples in a single channel */
-    paFrames = samples_per_frame / channel_count;
-
-    err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
-			 clock_rate,  paFrames, 
-			 paClipOff, &PaPlayerCallback, stream );
-    if (err != paNoError) {
-	pj_pool_release(pool);
-	return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
-    }
-
-    paSI = Pa_GetStreamInfo(stream->play_strm);
-    paRate = (unsigned)(paSI->sampleRate);
-    paLatency = (unsigned)(paSI->outputLatency * 1000);
-
-    PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
-			 ", ch=%d, "
-			 "bits=%d, %d samples per frame, latency=%d ms",
-			 index, paDevInfo->name, paHostApiInfo->name, 
-			 paRate, channel_count,
-		 	 bits_per_sample, samples_per_frame, paLatency));
-
-    *p_snd_strm = stream;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * 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;
-    PaStream *paStream = NULL;
-    PaStreamParameters inputParam;
-    PaStreamParameters outputParam;
-    int sampleFormat;
-    const PaDeviceInfo *paRecDevInfo = NULL;
-    const PaDeviceInfo *paPlayDevInfo = NULL;
-    const PaHostApiInfo *paRecHostApiInfo = NULL;
-    const PaHostApiInfo *paPlayHostApiInfo = NULL;
-    const PaStreamInfo *paSI;
-    unsigned paFrames, paRate, paInputLatency, paOutputLatency;
-    PaError err;
-
-    if (rec_id < 0) {
-	rec_id = pa_get_default_input_dev(channel_count);
-	if (rec_id < 0) {
-	    /* No such device. */
-	    return PJMEDIA_ENOSNDREC;
-	}
-    }
-
-    paRecDevInfo = Pa_GetDeviceInfo(rec_id);
-    if (!paRecDevInfo) {
-	/* Assumed it is "No such device" error. */
-	return PJMEDIA_ESNDINDEVID;
-    }
-
-    if (play_id < 0) {
-	play_id = pa_get_default_output_dev(channel_count);
-	if (play_id < 0) {
-	    /* No such device. */
-	    return PJMEDIA_ENOSNDPLAY;
-	}
-    } 
-
-    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_T(pool, pjmedia_snd_stream);
-    stream->pool = pool;
-    pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
-    stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
-    stream->play_id = play_id;
-    stream->rec_id = rec_id;
-    stream->user_data = user_data;
-    stream->samples_per_sec = clock_rate;
-    stream->samples_per_frame = 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;
-
-    stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, 
-		      stream->samples_per_frame * stream->bytes_per_sample);
-    stream->rec_buf_count = 0;
-
-    stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, 
-		       stream->samples_per_frame * stream->bytes_per_sample);
-    stream->play_buf_count = 0;
-
-    pj_bzero(&inputParam, sizeof(inputParam));
-    inputParam.device = rec_id;
-    inputParam.channelCount = channel_count;
-    inputParam.hostApiSpecificStreamInfo = NULL;
-    inputParam.sampleFormat = sampleFormat;
-    inputParam.suggestedLatency = snd_input_latency / 1000.0;
-
-    paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
-
-    pj_bzero(&outputParam, sizeof(outputParam));
-    outputParam.device = play_id;
-    outputParam.channelCount = channel_count;
-    outputParam.hostApiSpecificStreamInfo = NULL;
-    outputParam.sampleFormat = sampleFormat;
-    outputParam.suggestedLatency = snd_output_latency / 1000.0;
-
-    paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
-
-    /* Frames in PortAudio is number of samples in a single channel */
-    paFrames = samples_per_frame / channel_count;
-
-    /* If both input and output are on the same device, open a single stream
-     * for both input and output.
-     */
-    if (rec_id == play_id) {
-	err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
-			     clock_rate, paFrames, 
-			     paClipOff, &PaRecorderPlayerCallback, stream );
-	if (err == paNoError) {
-	    /* Set play stream and record stream to the same stream */
-	    stream->play_strm = stream->rec_strm = paStream;
-	}
-    } else {
-	err = -1;
-    }
-
-    /* .. otherwise if input and output are on the same device, OR if we're
-     * unable to open a bidirectional stream, then open two separate
-     * input and output stream.
-     */
-    if (paStream == NULL) {
-	/* Open input stream */
-	err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
-			     clock_rate, paFrames, 
-			     paClipOff, &PaRecorderCallback, stream );
-	if (err == paNoError) {
-	    /* Open output stream */
-	    err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
-				 clock_rate, paFrames, 
-				 paClipOff, &PaPlayerCallback, stream );
-	    if (err != paNoError)
-		Pa_CloseStream(stream->rec_strm);
-	}
-    }
-
-    if (err != paNoError) {
-	pj_pool_release(pool);
-	return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
-    }
-
-    paSI = Pa_GetStreamInfo(stream->rec_strm);
-    paRate = (unsigned)(paSI->sampleRate);
-    paInputLatency = (unsigned)(paSI->inputLatency * 1000);
-    paSI = Pa_GetStreamInfo(stream->play_strm);
-    paOutputLatency = (unsigned)(paSI->outputLatency * 1000);
-
-    PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and "
-			 "playback, sample rate=%d, ch=%d, "
-			 "bits=%d, %d samples per frame, input latency=%d ms, "
-			 "output latency=%d ms",
-			 paRecDevInfo->name, paRecHostApiInfo->name,
-			 paPlayDevInfo->name, paPlayHostApiInfo->name,
-			 paRate, channel_count,
-			 bits_per_sample, samples_per_frame,
-			 paInputLatency, paOutputLatency));
-
-    *p_snd_strm = stream;
-
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
-						pjmedia_snd_stream_info *pi)
-{
-    const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
-
-    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-    PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP);
-
-    if (strm->play_strm) {
-	paPlaySI = Pa_GetStreamInfo(strm->play_strm);
-    }
-    if (strm->rec_strm) {
-	paRecSI = Pa_GetStreamInfo(strm->rec_strm);
-    }
-
-    pj_bzero(pi, sizeof(*pi));
-    pi->dir = strm->dir;
-    pi->play_id = strm->play_id;
-    pi->rec_id = strm->rec_id;
-    pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate : 
-				paRecSI->sampleRate);
-    pi->channel_count = strm->channel_count;
-    pi->samples_per_frame = strm->samples_per_frame;
-    pi->bits_per_sample = strm->bytes_per_sample * 8;
-    pi->rec_latency = (unsigned)(paRecSI ? paRecSI->inputLatency * 
-					   paRecSI->sampleRate : 0);
-    pi->play_latency = (unsigned)(paPlaySI ? paPlaySI->outputLatency * 
-					     paPlaySI->sampleRate : 0);
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Start stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
-    int err = 0;
-
-    PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
-
-    if (stream->play_strm)
-	err = Pa_StartStream(stream->play_strm);
-
-    if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) {
-	err = Pa_StartStream(stream->rec_strm);
-	if (err != 0)
-	    Pa_StopStream(stream->play_strm);
-    }
-
-    PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
-
-    return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
-}
-
-/*
- * Stop stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
-    int i, err = 0;
-
-    stream->quit_flag = 1;
-    for (i=0; !stream->rec_thread_exited && i<100; ++i)
-	pj_thread_sleep(10);
-    for (i=0; !stream->play_thread_exited && i<100; ++i)
-	pj_thread_sleep(10);
-
-    pj_thread_sleep(1);
-
-    PJ_LOG(5,(THIS_FILE, "Stopping stream.."));
-
-    if (stream->play_strm)
-	err = Pa_StopStream(stream->play_strm);
-
-    if (stream->rec_strm && stream->rec_strm != stream->play_strm)
-	err = Pa_StopStream(stream->rec_strm);
-
-    stream->play_thread_initialized = 0;
-    stream->rec_thread_initialized = 0;
-
-    PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
-
-    return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
-}
-
-/*
- * Destroy stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
-    int i, err = 0;
-
-    stream->quit_flag = 1;
-    for (i=0; !stream->rec_thread_exited && i<100; ++i) {
-	pj_thread_sleep(1);
-    }
-    for (i=0; !stream->play_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));
-
-    if (stream->play_strm)
-	err = Pa_CloseStream(stream->play_strm);
-
-    if (stream->rec_strm && stream->rec_strm != stream->play_strm)
-	err = Pa_CloseStream(stream->rec_strm);
-
-    pj_pool_release(stream->pool);
-
-    return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
-}
-
-/*
- * Deinitialize sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
-    if (--snd_init_count == 0) {
-	int err;
-
-	PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
-
-	err = Pa_Terminate();
-
-	return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
-    } else {
-	return PJ_SUCCESS;
-    }
-}
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, 
-					    unsigned output_latency)
-{
-    snd_input_latency  = (input_latency == 0)? 
-			 PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency;
-    snd_output_latency = (output_latency == 0)? 
-			 PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency;
-
-    return PJ_SUCCESS;
-}
-
-#endif	/* PJMEDIA_SOUND_IMPLEMENTATION */
Index: pjproject/trunk/pjmedia/src/pjmedia/sound_legacy.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/sound_legacy.c	(revision 2506)
+++ pjproject/trunk/pjmedia/src/pjmedia/sound_legacy.c	(revision 2506)
@@ -0,0 +1,284 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 
+ */
+
+/*
+ * This is implementation of legacy sound device API, for applications
+ * that still use the old/deprecated sound device API. This implementation
+ * uses the new Audio Device API.
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * information.
+ */
+
+#include <pjmedia/sound.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pj/assert.h>
+
+#if PJMEDIA_HAS_LEGACY_SOUND_API
+
+static struct legacy_subsys
+{
+    pjmedia_snd_dev_info     info[4];
+    unsigned		     info_counter;
+    unsigned		     user_rec_latency;
+    unsigned		     user_play_latency;
+} g_sys;
+
+struct pjmedia_snd_stream
+{
+    pj_pool_t		*pool;
+    pjmedia_aud_stream	*aud_strm;
+    pjmedia_snd_rec_cb	 user_rec_cb;
+    pjmedia_snd_play_cb  user_play_cb;
+    void		*user_user_data;
+};
+
+PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
+{
+    return pjmedia_aud_subsys_init(factory);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
+{
+    return pjmedia_aud_subsys_shutdown();
+}
+
+PJ_DEF(int) pjmedia_snd_get_dev_count(void)
+{
+    return pjmedia_aud_dev_count();
+}
+
+PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
+{
+    pjmedia_snd_dev_info *oi = &g_sys.info[g_sys.info_counter];
+    pjmedia_aud_dev_info di;
+
+    g_sys.info_counter = (g_sys.info_counter+1) % PJ_ARRAY_SIZE(g_sys.info);
+
+    if (pjmedia_aud_dev_get_info(index, &di) != PJ_SUCCESS)
+	return NULL;
+
+    pj_bzero(oi, sizeof(*oi));
+    pj_ansi_strncpy(oi->name, di.name, sizeof(oi->name));
+    oi->name[sizeof(oi->name)-1] = '\0';
+    oi->input_count = di.input_count;
+    oi->output_count = di.output_count;
+    oi->default_samples_per_sec = di.default_samples_per_sec;
+
+    return oi;
+}
+
+
+static pj_status_t snd_play_cb(void *user_data,
+			       pjmedia_frame *frame)
+{
+    pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data;
+
+    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+    return strm->user_play_cb(strm->user_user_data, 
+			      frame->timestamp.u32.lo,
+			      frame->buf,
+			      frame->size);
+}
+
+static pj_status_t snd_rec_cb(void *user_data,
+			      pjmedia_frame *frame)
+{
+    pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data;
+    return strm->user_rec_cb(strm->user_user_data, 
+			     frame->timestamp.u32.lo,
+			     frame->buf,
+			     frame->size);
+}
+
+static pj_status_t open_stream( pjmedia_dir dir,
+			        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 *snd_strm;
+    pjmedia_aud_param param;
+    pj_status_t status;
+
+    /* Initialize parameters */
+    if (dir & PJMEDIA_DIR_CAPTURE) {
+	status = pjmedia_aud_dev_default_param(rec_id, &param);
+    } else {
+	status = pjmedia_aud_dev_default_param(play_id, &param);
+    }
+    if (status != PJ_SUCCESS)
+	return status;
+
+    param.dir = dir;
+    param.rec_id = rec_id;
+    param.play_id = play_id;
+    param.clock_rate = clock_rate;
+    param.channel_count = channel_count;
+    param.samples_per_frame = samples_per_frame;
+    param.bits_per_sample = bits_per_sample;
+
+    /* Latencies setting */
+    if ((dir & PJMEDIA_DIR_CAPTURE) && g_sys.user_rec_latency) {
+	param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+	param.input_latency_ms = g_sys.user_rec_latency;
+    }
+    if ((dir & PJMEDIA_DIR_PLAYBACK) && g_sys.user_play_latency) {
+	param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+	param.output_latency_ms = g_sys.user_play_latency;
+    }
+
+    /* Create sound wrapper */
+    pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
+			  "legacy-snd", 512, 512, NULL);
+    snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
+    snd_strm->pool = pool;
+    snd_strm->user_rec_cb = rec_cb;
+    snd_strm->user_play_cb = play_cb;
+    snd_strm->user_user_data = user_data;
+
+    /* Create the stream */
+    status = pjmedia_aud_stream_create(&param, &snd_rec_cb, 
+				       &snd_play_cb, snd_strm,
+				       &snd_strm->aud_strm);
+    if (status != PJ_SUCCESS) {
+	pj_pool_release(pool);
+	return status;
+    }
+
+    *p_snd_strm = snd_strm;
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
+					  unsigned clock_rate,
+					  unsigned channel_count,
+					  unsigned samples_per_frame,
+					  unsigned bits_per_sample,
+					  pjmedia_snd_rec_cb rec_cb,
+					  void *user_data,
+					  pjmedia_snd_stream **p_snd_strm)
+{
+    return open_stream(PJMEDIA_DIR_CAPTURE, index, PJMEDIA_AUD_INVALID_DEV,
+		       clock_rate, channel_count, samples_per_frame,
+		       bits_per_sample, rec_cb, NULL,
+		       user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
+					unsigned clock_rate,
+					unsigned channel_count,
+					unsigned samples_per_frame,
+					unsigned bits_per_sample,
+					pjmedia_snd_play_cb play_cb,
+					void *user_data,
+					pjmedia_snd_stream **p_snd_strm )
+{
+    return open_stream(PJMEDIA_DIR_PLAYBACK, PJMEDIA_AUD_INVALID_DEV, index, 
+		       clock_rate, channel_count, samples_per_frame,
+		       bits_per_sample, NULL, play_cb,
+		       user_data, p_snd_strm);
+}
+
+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)
+{
+    return open_stream(PJMEDIA_DIR_CAPTURE_PLAYBACK, rec_id, play_id,
+		       clock_rate, channel_count, samples_per_frame,
+		       bits_per_sample, rec_cb, play_cb, 
+		       user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
+{
+    return pjmedia_aud_stream_start(stream->aud_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
+{
+    return pjmedia_aud_stream_stop(stream->aud_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
+						pjmedia_snd_stream_info *pi)
+{
+    pjmedia_aud_param param;
+    pj_status_t status;
+
+    status = pjmedia_aud_stream_get_param(strm->aud_strm, &param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_bzero(pi, sizeof(*pi));
+    pi->dir = param.dir;
+    pi->play_id = param.play_id;
+    pi->rec_id = param.rec_id;
+    pi->clock_rate = param.clock_rate;
+    pi->channel_count = param.channel_count;
+    pi->samples_per_frame = param.samples_per_frame;
+    pi->bits_per_sample = param.bits_per_sample;
+
+    if (param.flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
+	pi->rec_latency = param.input_latency_ms;
+    }
+    if (param.flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
+	pi->play_latency = param.output_latency_ms;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
+{
+    pj_status_t status;
+
+    status = pjmedia_aud_stream_destroy(stream->aud_strm);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_pool_release(stream->pool);
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, 
+					    unsigned output_latency)
+{
+    g_sys.user_rec_latency = input_latency;
+    g_sys.user_play_latency = output_latency;
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_HAS_LEGACY_SOUND_API */
+
Index: pjproject/trunk/pjmedia/src/pjmedia/sound_port.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/sound_port.c	(revision 2505)
+++ pjproject/trunk/pjmedia/src/pjmedia/sound_port.c	(revision 2506)
@@ -19,8 +19,8 @@
  */
 #include <pjmedia/sound_port.h>
+#include <pjmedia/alaw_ulaw.h>
 #include <pjmedia/delaybuf.h>
 #include <pjmedia/echo.h>
 #include <pjmedia/errno.h>
-#include <pjmedia/plc.h>
 #include <pj/assert.h>
 #include <pj/log.h>
@@ -28,5 +28,4 @@
 #include <pj/string.h>	    /* pj_memset() */
 
-//#define SIMULATE_LOST_PCT   20
 #define AEC_TAIL	    128	    /* default AEC length in ms */
 #define AEC_SUSPEND_LIMIT   5	    /* seconds of no activity	*/
@@ -36,30 +35,13 @@
 //#define TEST_OVERFLOW_UNDERFLOW
 
-enum
-{
-    PJMEDIA_PLC_ENABLED	    = 1,
-};
-
-//#define DEFAULT_OPTIONS	PJMEDIA_PLC_ENABLED
-#define DEFAULT_OPTIONS	    0
-
-
 struct pjmedia_snd_port
 {
     int			 rec_id;
     int			 play_id;
-    pjmedia_snd_stream	*snd_stream;
+    pj_uint32_t		 aud_caps;
+    pjmedia_aud_param	 aud_param;
+    pjmedia_aud_stream	*aud_stream;
     pjmedia_dir		 dir;
     pjmedia_port	*port;
-    unsigned		 options;
-
-    pjmedia_echo_state	*ec_state;
-    unsigned		 aec_tail_len;
-
-    pj_bool_t		 ec_suspended;
-    unsigned		 ec_suspend_count;
-    unsigned		 ec_suspend_limit;
-
-    pjmedia_plc		*plc;
 
     unsigned		 clock_rate;
@@ -68,7 +50,11 @@
     unsigned		 bits_per_sample;
 
-#if PJMEDIA_SOUND_USE_DELAYBUF
-    pjmedia_delay_buf	*delay_buf;
-#endif
+    /* software ec */
+    pjmedia_echo_state	*ec_state;
+    unsigned		 ec_options;
+    unsigned		 ec_tail_len;
+    pj_bool_t		 ec_suspended;
+    unsigned		 ec_suspend_count;
+    unsigned		 ec_suspend_limit;
 };
 
@@ -77,76 +63,25 @@
  * played.
  */
-static pj_status_t play_cb(/* in */   void *user_data,
-			   /* in */   pj_uint32_t timestamp,
-			   /* out */  void *output,
-			   /* out */  unsigned size)
+static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
 {
     pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
     pjmedia_port *port;
-    pjmedia_frame frame;
+    unsigned required_size = frame->size;
     pj_status_t status;
 
-    /* We're risking accessing the port without holding any mutex.
-     * It's possible that port is disconnected then destroyed while
-     * we're trying to access it.
-     * But in the name of performance, we'll try this approach until
-     * someone complains when it crashes.
-     */
     port = snd_port->port;
     if (port == NULL)
 	goto no_frame;
 
-    frame.buf = output;
-    frame.size = size;
-    frame.timestamp.u32.hi = 0;
-    frame.timestamp.u32.lo = timestamp;
-
-#if PJMEDIA_SOUND_USE_DELAYBUF
-    if (snd_port->delay_buf) {
-	status = pjmedia_delay_buf_get(snd_port->delay_buf, (pj_int16_t*)output);
-	if (status != PJ_SUCCESS)
-	    pj_bzero(output, size);
-
-	frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
-	pjmedia_port_put_frame(port, &frame);
-
-#ifdef TEST_OVERFLOW_UNDERFLOW
-	{
-	    static int count = 1;
-	    if (++count % 10 == 0) {
-		status = pjmedia_delay_buf_get(snd_port->delay_buf, 
-					       (pj_int16_t*)output);
-		if (status != PJ_SUCCESS)
-		    pj_bzero(output, size);
-
-		frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
-		pjmedia_port_put_frame(port, &frame);
-	    }
-	}
-#endif
-
-    }
-#endif
-
-    status = pjmedia_port_get_frame(port, &frame);
+    status = pjmedia_port_get_frame(port, frame);
     if (status != PJ_SUCCESS)
 	goto no_frame;
 
-    if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+    if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO)
 	goto no_frame;
 
     /* Must supply the required samples */
-    pj_assert(frame.size == size);
-
-#ifdef SIMULATE_LOST_PCT
-    /* Simulate packet lost */
-    if (pj_rand() % 100 < SIMULATE_LOST_PCT) {
-	PJ_LOG(4,(THIS_FILE, "Frame dropped"));
-	goto no_frame;
-    }
-#endif
-
-    if (snd_port->plc)
-	pjmedia_plc_save(snd_port->plc, (pj_int16_t*) output);
+    PJ_UNUSED_ARG(required_size);
+    pj_assert(frame->size == required_size);
 
     if (snd_port->ec_state) {
@@ -157,5 +92,5 @@
 	}
 	snd_port->ec_suspend_count = 0;
-	pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)output);
+	pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
     }
 
@@ -173,19 +108,7 @@
 	if (snd_port->ec_state) {
 	    /* To maintain correct delay in EC */
-	    pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)output);
-	}
-    }
-
-    /* Apply PLC */
-    if (snd_port->plc) {
-
-	pjmedia_plc_generate(snd_port->plc, (pj_int16_t*) output);
-#ifdef SIMULATE_LOST_PCT
-	PJ_LOG(4,(THIS_FILE, "Lost frame generated"));
-#endif
-    } else {
-	pj_bzero(output, size);
-    }
-
+	    pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
+	}
+    }
 
     return PJ_SUCCESS;
@@ -197,19 +120,9 @@
  * frame.
  */
-static pj_status_t rec_cb(/* in */   void *user_data,
-			  /* in */   pj_uint32_t timestamp,
-			  /* in */   void *input,
-			  /* in*/    unsigned size)
+static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
 {
     pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
     pjmedia_port *port;
-    pjmedia_frame frame;
-
-    /* We're risking accessing the port without holding any mutex.
-     * It's possible that port is disconnected then destroyed while
-     * we're trying to access it.
-     * But in the name of performance, we'll try this approach until
-     * someone complains when it crashes.
-     */
+
     port = snd_port->port;
     if (port == NULL)
@@ -218,26 +131,46 @@
     /* Cancel echo */
     if (snd_port->ec_state && !snd_port->ec_suspended) {
-	pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) input, 0);
-    }
-
-#if PJMEDIA_SOUND_USE_DELAYBUF
-    if (snd_port->delay_buf) {
-	pjmedia_delay_buf_put(snd_port->delay_buf, (pj_int16_t*)input);
-    } else {
-	frame.buf = (void*)input;
-	frame.size = size;
-	frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
-	frame.timestamp.u32.lo = timestamp;
-
-	pjmedia_port_put_frame(port, &frame);
-    }
-#else
-    frame.buf = (void*)input;
-    frame.size = size;
-    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
-    frame.timestamp.u32.lo = timestamp;
-
-    pjmedia_port_put_frame(port, &frame);
-#endif
+	pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0);
+    }
+
+    pjmedia_port_put_frame(port, frame);
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * The callback called by sound player when it needs more samples to be
+ * played. This version is for non-PCM data.
+ */
+static pj_status_t play_cb_ext(void *user_data, pjmedia_frame *frame)
+{
+    pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
+    pjmedia_port *port = snd_port->port;
+
+    if (port == NULL) {
+	frame->type = PJMEDIA_FRAME_TYPE_NONE;
+	return PJ_SUCCESS;
+    }
+
+    pjmedia_port_get_frame(port, frame);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * The callback called by sound recorder when it has finished capturing a
+ * frame. This version is for non-PCM data.
+ */
+static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame)
+{
+    pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
+    pjmedia_port *port;
+
+    port = snd_port->port;
+    if (port == NULL)
+	return PJ_SUCCESS;
+
+    pjmedia_port_put_frame(port, frame);
 
     return PJ_SUCCESS;
@@ -251,71 +184,66 @@
 				       pjmedia_snd_port *snd_port )
 {
+    pjmedia_aud_rec_cb snd_rec_cb;
+    pjmedia_aud_play_cb snd_play_cb;
+    pjmedia_aud_param param_copy;
     pj_status_t status;
 
     /* Check if sound has been started. */
-    if (snd_port->snd_stream != NULL)
+    if (snd_port->aud_stream != NULL)
 	return PJ_SUCCESS;
 
-    /* Open sound stream. */
-    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,
+    PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE ||
+		     snd_port->dir == PJMEDIA_DIR_PLAYBACK ||
+		     snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK,
+		     PJ_EBUG);
+
+    /* Get device caps */
+    if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) {
+	pjmedia_aud_dev_info dev_info;
+
+	status = pjmedia_aud_dev_get_info(snd_port->aud_param.rec_id, 
+					  &dev_info);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	snd_port->aud_caps = dev_info.caps;
+    } else {
+	snd_port->aud_caps = 0;
+    }
+
+    /* Process EC settings */
+    pj_memcpy(&param_copy, &snd_port->aud_param, sizeof(param_copy));
+    if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) {
+	/* EC is wanted */
+	if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) {
+	    /* Device supports EC */
+	    /* Nothing to do */
+	} else {
+	    /* Device doesn't support EC, remove EC settings from
+	     * device parameters
+	     */
+	    param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC |
+				  PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+	}
+    }
+
+    /* Use different callback if format is not PCM */
+    if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
+	snd_rec_cb = &rec_cb;
+	snd_play_cb = &play_cb;
+    } else {
+	snd_rec_cb = &rec_cb_ext;
+	snd_play_cb = &play_cb_ext;
+    }
+
+    /* Open the device */
+    status = pjmedia_aud_stream_create(&param_copy,
+				       snd_rec_cb,
+				       snd_play_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,
-					  snd_port->samples_per_frame,
-					  snd_port->bits_per_sample,
-					  &play_cb,
-					  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;
-    }
+				       &snd_port->aud_stream);
 
     if (status != PJ_SUCCESS)
 	return status;
-
-
-#ifdef SIMULATE_LOST_PCT
-    snd_port->options |= PJMEDIA_PLC_ENABLED;
-#endif
-
-    /* If we have player components, allocate buffer to save the last
-     * frame played to the speaker. The last frame is used for packet
-     * lost concealment (PLC) algorithm.
-     */
-    if ((snd_port->dir & PJMEDIA_DIR_PLAYBACK) &&
-	(snd_port->options & PJMEDIA_PLC_ENABLED)) 
-    {
-	status = pjmedia_plc_create(pool, snd_port->clock_rate, 
-				    snd_port->samples_per_frame * 
-					snd_port->channel_count,
-				    0, &snd_port->plc);
-	if (status != PJ_SUCCESS) {
-	    PJ_LOG(4,(THIS_FILE, "Unable to create PLC"));
-	    snd_port->plc = NULL;
-	}
-    }
 
     /* Inactivity limit before EC is suspended. */
@@ -324,9 +252,32 @@
 				  snd_port->samples_per_frame);
 
+    /* Create software EC if parameter specifies EC but device 
+     * doesn't support EC. Only do this if the format is PCM!
+     */
+    if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) &&
+	(snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 &&
+	param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM)
+    {
+	if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
+	    snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL;
+	    snd_port->aud_param.ec_tail_ms = AEC_TAIL;
+	    PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms",
+				 snd_port->aud_param.ec_tail_ms));
+	}
+	    
+	status = pjmedia_snd_port_set_ec(snd_port, pool, 
+					 snd_port->aud_param.ec_tail_ms, 0);
+	if (status != PJ_SUCCESS) {
+	    pjmedia_aud_stream_destroy(snd_port->aud_stream);
+	    snd_port->aud_stream = NULL;
+	    return status;
+	}
+    }
+
     /* Start sound stream. */
-    status = pjmedia_snd_stream_start(snd_port->snd_stream);
+    status = pjmedia_aud_stream_start(snd_port->aud_stream);
     if (status != PJ_SUCCESS) {
-	pjmedia_snd_stream_close(snd_port->snd_stream);
-	snd_port->snd_stream = NULL;
+	pjmedia_aud_stream_destroy(snd_port->aud_stream);
+	snd_port->aud_stream = NULL;
 	return status;
     }
@@ -343,8 +294,8 @@
 {
     /* Check if we have sound stream device. */
-    if (snd_port->snd_stream) {
-	pjmedia_snd_stream_stop(snd_port->snd_stream);
-	pjmedia_snd_stream_close(snd_port->snd_stream);
-	snd_port->snd_stream = NULL;
+    if (snd_port->aud_stream) {
+	pjmedia_aud_stream_stop(snd_port->aud_stream);
+	pjmedia_aud_stream_destroy(snd_port->aud_stream);
+	snd_port->aud_stream = NULL;
     }
 
@@ -372,45 +323,22 @@
 					     pjmedia_snd_port **p_port)
 {
-    pjmedia_snd_port *snd_port;
-
-    PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
-
-    snd_port = PJ_POOL_ZALLOC_T(pool, 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->options = options | DEFAULT_OPTIONS;
-    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;
-    
-#if PJMEDIA_SOUND_USE_DELAYBUF
-    do {
-	pj_status_t status;
-	unsigned ptime;
-    
-	ptime = samples_per_frame * 1000 / (clock_rate * channel_count);
-    
-	status = pjmedia_delay_buf_create(pool, "snd_buff", 
-					  clock_rate, samples_per_frame,
-					  channel_count,
-					  PJMEDIA_SOUND_BUFFER_COUNT * ptime,
-					  0, &snd_port->delay_buf);
-	PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
-    } while (0);
-#endif
-
-    *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( pool, snd_port );
-
+    pjmedia_aud_param param;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(options);
+
+    status = pjmedia_aud_dev_default_param(rec_id, &param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    param.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    param.rec_id = rec_id;
+    param.play_id = play_id;
+    param.clock_rate = clock_rate;
+    param.channel_count = channel_count;
+    param.samples_per_frame = samples_per_frame;
+    param.bits_per_sample = bits_per_sample;
+
+    return pjmedia_snd_port_create2(pool, &param, p_port);
 }
 
@@ -427,26 +355,21 @@
 						 pjmedia_snd_port **p_port)
 {
-    pjmedia_snd_port *snd_port;
-
-    PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
-
-    snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);
-    PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
-
-    snd_port->rec_id = dev_id;
-    snd_port->dir = PJMEDIA_DIR_CAPTURE;
-    snd_port->options = options | DEFAULT_OPTIONS;
-    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( pool, snd_port );
+    pjmedia_aud_param param;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(options);
+
+    status = pjmedia_aud_dev_default_param(dev_id, &param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    param.dir = PJMEDIA_DIR_CAPTURE;
+    param.rec_id = dev_id;
+    param.clock_rate = clock_rate;
+    param.channel_count = channel_count;
+    param.samples_per_frame = samples_per_frame;
+    param.bits_per_sample = bits_per_sample;
+
+    return pjmedia_snd_port_create2(pool, &param, p_port);
 }
 
@@ -464,26 +387,61 @@
 						    pjmedia_snd_port **p_port)
 {
+    pjmedia_aud_param param;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(options);
+
+    status = pjmedia_aud_dev_default_param(dev_id, &param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    param.dir = PJMEDIA_DIR_PLAYBACK;
+    param.play_id = dev_id;
+    param.clock_rate = clock_rate;
+    param.channel_count = channel_count;
+    param.samples_per_frame = samples_per_frame;
+    param.bits_per_sample = bits_per_sample;
+
+    return pjmedia_snd_port_create2(pool, &param, p_port);
+}
+
+
+/*
+ * Create sound port.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool,
+					     const pjmedia_aud_param *prm,
+					     pjmedia_snd_port **p_port)
+{
     pjmedia_snd_port *snd_port;
-
-    PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL);
 
     snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);
     PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
 
-    snd_port->play_id = dev_id;
-    snd_port->dir = PJMEDIA_DIR_PLAYBACK;
-    snd_port->options = options | DEFAULT_OPTIONS;
-    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;
-
+    snd_port->dir = prm->dir;
+    snd_port->rec_id = prm->rec_id;
+    snd_port->play_id = prm->play_id;
+    snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    snd_port->clock_rate = prm->clock_rate;
+    snd_port->channel_count = prm->channel_count;
+    snd_port->samples_per_frame = prm->samples_per_frame;
+    snd_port->bits_per_sample = prm->bits_per_sample;
+    pj_memcpy(&snd_port->aud_param, prm, sizeof(*prm));
+    
     /* Start sound device immediately.
      * If there's no port connected, the sound callback will return
      * empty signal.
      */
-    return start_sound_device( pool, snd_port );
+    status = start_sound_device( pool, snd_port );
+    if (status != PJ_SUCCESS) {
+	pjmedia_snd_port_destroy(snd_port);
+	return status;
+    }
+
+    *p_port = snd_port;
+    return PJ_SUCCESS;
 }
 
@@ -503,14 +461,14 @@
  * Retrieve the sound stream associated by this sound device port.
  */
-PJ_DEF(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
+PJ_DEF(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream(
 						pjmedia_snd_port *snd_port)
 {
     PJ_ASSERT_RETURN(snd_port, NULL);
-    return snd_port->snd_stream;
-}
-
-
-/*
- * Enable AEC
+    return snd_port->aud_stream;
+}
+
+
+/*
+ * Change EC settings.
  */
 PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port,
@@ -519,5 +477,5 @@
 					     unsigned options)
 {
-    pjmedia_snd_stream_info si;
+    pjmedia_aud_param prm;
     pj_status_t status;
 
@@ -527,41 +485,99 @@
 		     PJ_EINVALIDOP);
 
-    /* Sound port must have 16bits per sample */
-    PJ_ASSERT_RETURN(snd_port->bits_per_sample == 16,
-		     PJ_EINVALIDOP);
-
-    /* Destroy AEC */
-    if (snd_port->ec_state) {
-	pjmedia_echo_destroy(snd_port->ec_state);
-	snd_port->ec_state = NULL;
-    }
-
-    snd_port->aec_tail_len = tail_ms;
-
-    if (tail_ms != 0) {
-	unsigned delay_ms;
-
-	status = pjmedia_snd_stream_get_info(snd_port->snd_stream, &si);
+    /* Determine whether we use device or software EC */
+    if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) {
+	/* We use device EC */
+	pj_bool_t ec_enabled;
+
+	/* Query EC status */
+	status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+					    PJMEDIA_AUD_DEV_CAP_EC,
+					    &ec_enabled);
 	if (status != PJ_SUCCESS)
-	    si.rec_latency = si.play_latency = 0;
-
-	//No need to add input latency in the latency calculation,
-	//since actual input latency should be zero.
-	//delay_ms = (si.rec_latency + si.play_latency) * 1000 /
-	//	   snd_port->clock_rate;
-	delay_ms = si.play_latency * 1000 / snd_port->clock_rate;
-	status = pjmedia_echo_create2(pool, snd_port->clock_rate, 
-				      snd_port->channel_count,
-				      snd_port->samples_per_frame, 
-				      tail_ms, delay_ms,
-				      options, &snd_port->ec_state);
+	    return status;
+
+	if (tail_ms != 0) {
+	    /* Change EC setting */
+
+	    if (!ec_enabled) {
+		/* Enable EC first */
+		pj_bool_t value = PJ_TRUE;
+		status = pjmedia_aud_stream_set_cap(snd_port->aud_stream, 
+						    PJMEDIA_AUD_DEV_CAP_EC,
+						    &value);
+		if (status != PJ_SUCCESS)
+		    return status;
+	    }
+
+	    if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
+		/* Device does not support setting EC tail */
+		return PJMEDIA_EAUD_INVCAP;
+	    }
+
+	    return pjmedia_aud_stream_set_cap(snd_port->aud_stream,
+					      PJMEDIA_AUD_DEV_CAP_EC_TAIL,
+					      &tail_ms);
+
+	} else if (ec_enabled) {
+	    /* Disable EC */
+	    pj_bool_t value = PJ_FALSE;
+	    return pjmedia_aud_stream_set_cap(snd_port->aud_stream, 
+					      PJMEDIA_AUD_DEV_CAP_EC,
+					      &value);
+	} else {
+	    /* Request to disable EC but EC has been disabled */
+	    /* Do nothing */
+	    return PJ_SUCCESS;
+	}
+
+    } else {
+	/* We use software EC */
+
+	/* Check if there is change in parameters */
+	if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) {
+	    PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no "
+				 "change in settings"));
+	    return PJ_SUCCESS;
+	}
+
+	status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm);
 	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Audio stream must be in PCM format */
+	PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM,
+			 PJ_EINVALIDOP);
+
+	/* Destroy AEC */
+	if (snd_port->ec_state) {
+	    pjmedia_echo_destroy(snd_port->ec_state);
 	    snd_port->ec_state = NULL;
-	else
-	    snd_port->ec_suspended = PJ_FALSE;
-    } else {
-	PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the "
-			     "sound port"));
-	status = PJ_SUCCESS;
+	}
+
+	if (tail_ms != 0) {
+	    unsigned delay_ms;
+
+	    //No need to add input latency in the latency calculation,
+	    //since actual input latency should be zero.
+	    //delay_ms = (si.rec_latency + si.play_latency) * 1000 /
+	    //	   snd_port->clock_rate;
+	    delay_ms = prm.output_latency_ms;
+	    status = pjmedia_echo_create2(pool, snd_port->clock_rate, 
+					  snd_port->channel_count,
+					  snd_port->samples_per_frame, 
+					  tail_ms, delay_ms,
+					  options, &snd_port->ec_state);
+	    if (status != PJ_SUCCESS)
+		snd_port->ec_state = NULL;
+	    else
+		snd_port->ec_suspended = PJ_FALSE;
+	} else {
+	    PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the "
+				 "sound port"));
+	    status = PJ_SUCCESS;
+	}
+
+	snd_port->ec_options = options;
+	snd_port->ec_tail_len = tail_ms;
     }
 
@@ -575,8 +591,38 @@
 {
     PJ_ASSERT_RETURN(snd_port && p_length, PJ_EINVAL);
-    *p_length =  snd_port->ec_state ? snd_port->aec_tail_len : 0;
-    return PJ_SUCCESS;
-}
-
+
+    /* Determine whether we use device or software EC */
+    if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) {
+	/* We use device EC */
+	pj_bool_t ec_enabled;
+	pj_status_t status;
+
+	/* Query EC status */
+	status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+					    PJMEDIA_AUD_DEV_CAP_EC,
+					    &ec_enabled);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	if (!ec_enabled) {
+	    *p_length = 0;
+	} else if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) {
+	    /* Get device EC tail */
+	    status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+						PJMEDIA_AUD_DEV_CAP_EC_TAIL,
+						p_length);
+	    if (status != PJ_SUCCESS)
+		return status;
+	} else {
+	    /* Just use default */
+	    *p_length = AEC_TAIL;
+	}
+
+    } else {
+	/* We use software EC */
+	*p_length =  snd_port->ec_state ? snd_port->ec_tail_len : 0;
+    }
+    return PJ_SUCCESS;
+}
 
 
Index: pjproject/trunk/pjmedia/src/pjmedia/stream.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/stream.c	(revision 2505)
+++ pjproject/trunk/pjmedia/src/pjmedia/stream.c	(revision 2506)
@@ -409,4 +409,114 @@
 
 
+/* The other version of get_frame callback used when stream port format
+ * is non linear PCM.
+ */
+static pj_status_t get_frame_ext( pjmedia_port *port, pjmedia_frame *frame)
+{
+    pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
+    pjmedia_channel *channel = stream->dec;
+    pjmedia_frame_ext *f = (pjmedia_frame_ext*)frame;
+    unsigned samples_per_frame, samples_required;
+    pj_status_t status;
+
+    /* Return no frame if channel is paused */
+    if (channel->paused) {
+	frame->type = PJMEDIA_FRAME_TYPE_NONE;
+	return PJ_SUCCESS;
+    }
+
+    /* Repeat get frame from the jitter buffer and decode the frame
+     * until we have enough frames according to codec's ptime.
+     */
+
+    samples_required = stream->port.info.samples_per_frame;
+    samples_per_frame = stream->codec_param.info.frm_ptime *
+			stream->codec_param.info.clock_rate *
+			stream->codec_param.info.channel_cnt / 
+			1000;
+
+    pj_bzero(f, sizeof(pjmedia_frame_ext));
+    f->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+
+    while (f->samples_cnt < samples_required) {
+	char frame_type;
+	pj_size_t frame_size;
+	pj_uint32_t bit_info;
+
+	/* Lock jitter buffer mutex first */
+	pj_mutex_lock( stream->jb_mutex );
+
+	/* Get frame from jitter buffer. */
+	pjmedia_jbuf_get_frame2(stream->jb, channel->out_pkt, &frame_size,
+			        &frame_type, &bit_info);
+	
+	/* Unlock jitter buffer mutex. */
+	pj_mutex_unlock( stream->jb_mutex );
+
+	if (frame_type == PJMEDIA_JB_NORMAL_FRAME) {
+	    /* Got "NORMAL" frame from jitter buffer */
+	    pjmedia_frame frame_in;
+
+	    /* Decode */
+	    frame_in.buf = channel->out_pkt;
+	    frame_in.size = frame_size;
+	    frame_in.bit_info = bit_info;
+	    frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+	    status = stream->codec->op->decode( stream->codec, &frame_in,
+						0, frame);
+	    if (status != PJ_SUCCESS) {
+		LOGERR_((port->info.name.ptr, "codec decode() error", 
+			 status));
+		pjmedia_frame_ext_append_subframe(f, NULL, 0,
+					    (pj_uint16_t)samples_per_frame);
+	    }
+	} else {
+	    status = (*stream->codec->op->recover)(stream->codec,
+						   0, frame);
+	    if (status != PJ_SUCCESS) {
+		pjmedia_frame_ext_append_subframe(f, NULL, 0,
+					    (pj_uint16_t)samples_per_frame);
+	    }
+
+	    if (frame_type == PJMEDIA_JB_MISSING_FRAME) {
+		PJ_LOG(5,(stream->port.info.name.ptr,  "Frame lost!"));
+	    } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) {
+		/* Jitter buffer is empty. Check if this is the first "empty" 
+		 * state.
+		 */
+		if (frame_type != stream->jb_last_frm) {
+		    pjmedia_jb_state jb_state;
+
+		    /* Report the state of jitter buffer */
+		    pjmedia_jbuf_get_state(stream->jb, &jb_state);
+		    PJ_LOG(5,(stream->port.info.name.ptr, 
+			      "Jitter buffer empty (prefetch=%d)", 
+			      jb_state.prefetch));
+		}
+	    } else {
+		pjmedia_jb_state jb_state;
+
+		/* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */
+		pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME);
+
+		/* Get the state of jitter buffer */
+		pjmedia_jbuf_get_state(stream->jb, &jb_state);
+
+		if (stream->jb_last_frm != frame_type) {
+		    PJ_LOG(5,(stream->port.info.name.ptr, 
+			      "Jitter buffer is bufferring (prefetch=%d)",
+			      jb_state.prefetch));
+		}
+	    }
+	}
+
+	stream->jb_last_frm = frame_type;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
 /*
  * Transmit DTMF
@@ -687,4 +797,7 @@
     if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO)
 	ts_len = (frame->size >> 1) / stream->codec_param.info.channel_cnt;
+    else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED)
+	ts_len = stream->port.info.samples_per_frame / 
+		 stream->port.info.channel_count;
     else
 	ts_len = 0;
@@ -753,4 +866,5 @@
     } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
 	       frame->buf == NULL &&
+	       stream->port.info.format.id == PJMEDIA_FORMAT_L16 &&
 	       (stream->dir & PJMEDIA_DIR_ENCODING) &&
 	       stream->codec_param.info.frm_ptime *
@@ -1484,7 +1598,16 @@
     stream->port.info.clock_rate = info->fmt.clock_rate;
     stream->port.info.channel_count = info->fmt.channel_cnt;
+    stream->port.info.format.id = info->param->info.fmt_id;
     stream->port.port_data.pdata = stream;
-    stream->port.put_frame = &put_frame;
-    stream->port.get_frame = &get_frame;
+    if (stream->port.info.format.id == PJMEDIA_FORMAT_L16) {
+	stream->port.put_frame = &put_frame;
+	stream->port.get_frame = &get_frame;
+    } else {
+	stream->port.info.format.bitrate = info->param->info.avg_bps;
+	stream->port.info.format.vad = (info->param->setting.vad != 0);
+
+	stream->port.put_frame = &put_frame;
+	stream->port.get_frame = &get_frame_ext;
+    }
 
 
Index: pjproject/trunk/pjmedia/src/pjmedia/symbian_sound.cpp
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/symbian_sound.cpp	(revision 2505)
+++ 	(revision )
@@ -1,944 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * 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 
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/os.h>
-
-
-/*
- * This file provides sound implementation for Symbian Audio Streaming
- * device. Application using this sound abstraction must link with:
- *  - mediaclientaudiostream.lib, and
- *  - mediaclientaudioinputstream.lib 
- */
-#include <mda/common/audio.h>
-#include <mdaaudiooutputstream.h>
-#include <mdaaudioinputstream.h>
-
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-#define THIS_FILE	    "symbian_sound.cpp"
-#define BYTES_PER_SAMPLE    2
-#define POOL_NAME	    "SymbianSound"
-#define POOL_SIZE	    512
-#define POOL_INC	    512
-
-static pjmedia_snd_dev_info symbian_snd_dev_info = 
-{
-    "Symbian Sound Device",
-    1,
-    1,
-    8000
-};
-
-class CPjAudioInputEngine;
-class CPjAudioOutputEngine;
-
-/* 
- * PJMEDIA Sound Stream instance 
- */
-struct pjmedia_snd_stream
-{
-    // Pool
-    pj_pool_t			*pool;
-
-    // Common settings.
-    pjmedia_dir		 	 dir;
-    unsigned			 clock_rate;
-    unsigned			 channel_count;
-    unsigned			 samples_per_frame;
-
-    // Input stream
-    CPjAudioInputEngine		*inEngine;
-
-    // Output stream
-    CPjAudioOutputEngine	*outEngine;
-};
-
-static pj_pool_factory *snd_pool_factory;
-
-
-/*
- * Convert clock rate to Symbian's TMdaAudioDataSettings capability.
- */
-static TInt get_clock_rate_cap(unsigned clock_rate)
-{
-    switch (clock_rate) {
-    case 8000:  return TMdaAudioDataSettings::ESampleRate8000Hz;
-    case 11025: return TMdaAudioDataSettings::ESampleRate11025Hz;
-    case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz;
-    case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz;
-    case 22050: return TMdaAudioDataSettings::ESampleRate22050Hz;
-    case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz;
-    case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz;
-    case 44100: return TMdaAudioDataSettings::ESampleRate44100Hz;
-    case 48000: return TMdaAudioDataSettings::ESampleRate48000Hz;
-    case 64000: return TMdaAudioDataSettings::ESampleRate64000Hz;
-    case 96000: return TMdaAudioDataSettings::ESampleRate96000Hz;
-    default:
-	return 0;
-    }
-}
-
-
-/*
- * Convert number of channels into Symbian's TMdaAudioDataSettings capability.
- */
-static TInt get_channel_cap(unsigned channel_count)
-{
-    switch (channel_count) {
-    case 1: return TMdaAudioDataSettings::EChannelsMono;
-    case 2: return TMdaAudioDataSettings::EChannelsStereo;
-    default:
-	return 0;
-    }
-}
-
-
-/*
- * Utility: print sound device error
- */
-static void snd_perror(const char *title, TInt rc) 
-{
-    PJ_LOG(1,(THIS_FILE, "%s: error code %d", title, rc));
-}
- 
-//////////////////////////////////////////////////////////////////////////////
-//
-
-/*
- * Implementation: Symbian Input Stream.
- */
-class CPjAudioInputEngine : public CBase, MMdaAudioInputStreamCallback
-{
-public:
-    enum State
-    {
-	STATE_INACTIVE,
-	STATE_ACTIVE,
-    };
-
-    ~CPjAudioInputEngine();
-
-    static CPjAudioInputEngine *NewL(pjmedia_snd_stream *parent_strm,
-				     pjmedia_snd_rec_cb rec_cb,
-				     void *user_data);
-
-    static CPjAudioInputEngine *NewLC(pjmedia_snd_stream *parent_strm,
-				      pjmedia_snd_rec_cb rec_cb,
-				      void *user_data);
-
-    pj_status_t StartRecord();
-    void Stop();
-
-private:
-    State		     state_;
-    pjmedia_snd_stream	    *parentStrm_;
-    pjmedia_snd_rec_cb	     recCb_;
-    void		    *userData_;
-    CMdaAudioInputStream    *iInputStream_;
-    HBufC8		    *iStreamBuffer_;
-    TPtr8		     iFramePtr_;
-    TInt		     lastError_;
-    pj_uint32_t		     timeStamp_;
-
-    // cache variable
-    // to avoid calculating frame length repeatedly
-    TInt			  frameLen_;
-    
-    // in some SymbianOS (e.g: OSv9.1), sometimes recorded size != requested framesize
-    // so let's provide a buffer to make sure the rec callback returns framesize as requested.
-    TUint8		     *frameRecBuf_;
-    TInt			  frameRecBufLen_;
-
-    CPjAudioInputEngine(pjmedia_snd_stream *parent_strm,
-			pjmedia_snd_rec_cb rec_cb,
-			void *user_data);
-    void ConstructL();
-    TPtr8 & GetFrame();
-    
-public:
-    virtual void MaiscOpenComplete(TInt aError);
-    virtual void MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer);
-    virtual void MaiscRecordComplete(TInt aError);
-
-};
-
-
-CPjAudioInputEngine::CPjAudioInputEngine(pjmedia_snd_stream *parent_strm,
-					 pjmedia_snd_rec_cb rec_cb,
-					 void *user_data)
-    : state_(STATE_INACTIVE), parentStrm_(parent_strm), 
-      recCb_(rec_cb), userData_(user_data), 
-      iInputStream_(NULL), iStreamBuffer_(NULL), iFramePtr_(0, 0),
-      lastError_(KErrNone), timeStamp_(0),
-      frameLen_(parent_strm->samples_per_frame * parent_strm->channel_count * BYTES_PER_SAMPLE),
-      frameRecBuf_(NULL), frameRecBufLen_(0)
-{
-}
-
-CPjAudioInputEngine::~CPjAudioInputEngine()
-{
-    Stop();
-
-    delete iStreamBuffer_;
-    iStreamBuffer_ = NULL;
-    
-    delete [] frameRecBuf_;
-    frameRecBuf_ = NULL;
-    frameRecBufLen_ = 0;
-}
-
-void CPjAudioInputEngine::ConstructL()
-{
-    iStreamBuffer_ = HBufC8::NewL(frameLen_);
-    CleanupStack::PushL(iStreamBuffer_);
-
-    frameRecBuf_ = new TUint8[frameLen_*2];
-    CleanupStack::PushL(frameRecBuf_);
-}
-
-CPjAudioInputEngine *CPjAudioInputEngine::NewLC(pjmedia_snd_stream *parent,
-					        pjmedia_snd_rec_cb rec_cb,
-					        void *user_data)
-{
-    CPjAudioInputEngine* self = new (ELeave) CPjAudioInputEngine(parent,
-								 rec_cb, 
-								 user_data);
-    CleanupStack::PushL(self);
-    self->ConstructL();
-    return self;
-}
-
-CPjAudioInputEngine *CPjAudioInputEngine::NewL(pjmedia_snd_stream *parent,
-					       pjmedia_snd_rec_cb rec_cb,
-					       void *user_data)
-{
-    CPjAudioInputEngine *self = NewLC(parent, rec_cb, user_data);
-    CleanupStack::Pop(self->frameRecBuf_);
-    CleanupStack::Pop(self->iStreamBuffer_);
-    CleanupStack::Pop(self);
-    return self;
-}
-
-
-pj_status_t CPjAudioInputEngine::StartRecord()
-{
-
-    // Ignore command if recording is in progress.
-    if (state_ == STATE_ACTIVE)
-	return PJ_SUCCESS;
-
-    // According to Nokia's AudioStream example, some 2nd Edition, FP2 devices
-    // (such as Nokia 6630) require the stream to be reconstructed each time 
-    // before calling Open() - otherwise the callback never gets called.
-    // For uniform behavior, lets just delete/re-create the stream for all
-    // devices.
-
-    // Destroy existing stream.
-    if (iInputStream_) delete iInputStream_;
-    iInputStream_ = NULL;
-
-    // Create the stream.
-    TRAPD(err, iInputStream_ = CMdaAudioInputStream::NewL(*this));
-    if (err != KErrNone)
-	return PJ_RETURN_OS_ERROR(err);
-
-    // Initialize settings.
-    TMdaAudioDataSettings iStreamSettings;
-    iStreamSettings.iChannels = get_channel_cap(parentStrm_->channel_count);
-    iStreamSettings.iSampleRate = get_clock_rate_cap(parentStrm_->clock_rate);
-
-    pj_assert(iStreamSettings.iChannels != 0 && 
-	      iStreamSettings.iSampleRate != 0);
-
-    PJ_LOG(4,(THIS_FILE, "Opening sound device for capture, "
-    		         "clock rate=%d, channel count=%d..",
-    		         parentStrm_->clock_rate, 
-    		         parentStrm_->channel_count));
-    
-    // Open stream.
-    lastError_ = KRequestPending;
-    iInputStream_->Open(&iStreamSettings);
-    
-    // Success
-    PJ_LOG(4,(THIS_FILE, "Sound capture started."));
-    return PJ_SUCCESS;
-}
-
-
-void CPjAudioInputEngine::Stop()
-{
-    // If capture is in progress, stop it.
-    if (iInputStream_ && state_ == STATE_ACTIVE) {
-    	lastError_ = KRequestPending;
-    	iInputStream_->Stop();
-
-	// Wait until it's actually stopped
-    	while (lastError_ == KRequestPending)
-	    pj_symbianos_poll(-1, 100);
-    }
-
-    if (iInputStream_) {
-	delete iInputStream_;
-	iInputStream_ = NULL;
-    }
-    
-    state_ = STATE_INACTIVE;
-}
-
-
-TPtr8 & CPjAudioInputEngine::GetFrame() 
-{
-    //iStreamBuffer_->Des().FillZ(frameLen_);
-    iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_);
-    return iFramePtr_;
-}
-
-void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
-{
-    lastError_ = aError;
-    if (aError != KErrNone) {
-        snd_perror("Error in MaiscOpenComplete()", aError);
-    	return;
-    }
-
-    // set stream priority to normal and time sensitive
-    iInputStream_->SetPriority(EPriorityNormal, 
-    			       EMdaPriorityPreferenceTime);				
-
-    // Read the first frame.
-    TPtr8 & frm = GetFrame();
-    TRAPD(err2, iInputStream_->ReadL(frm));
-    if (err2) {
-    	PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
-    }
-}
-
-void CPjAudioInputEngine::MaiscBufferCopied(TInt aError, 
-					    const TDesC8 &aBuffer)
-{
-    lastError_ = aError;
-    if (aError != KErrNone) {
-    	snd_perror("Error in MaiscBufferCopied()", aError);
-	return;
-    }
-
-    if (frameRecBufLen_ || aBuffer.Size() < frameLen_) {
-		pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Size());
-		frameRecBufLen_ += aBuffer.Size();
-    }
-
-    if (frameRecBufLen_) {
-    	while (frameRecBufLen_ >= frameLen_) {
-	    	// Call the callback.
-		    recCb_(userData_, timeStamp_, frameRecBuf_, frameLen_);
-		    // Increment timestamp.
-		    timeStamp_ += parentStrm_->samples_per_frame;
-
-		    frameRecBufLen_ -= frameLen_;
-		    pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_);
-    	}
-    } else {
-    	// Call the callback.
-	    recCb_(userData_, timeStamp_, (void*) aBuffer.Ptr(), aBuffer.Size());
-	    // Increment timestamp.
-	    timeStamp_ += parentStrm_->samples_per_frame;
-    }
-
-    // Record next frame
-    TPtr8 & frm = GetFrame();
-    TRAPD(err2, iInputStream_->ReadL(frm));
-    if (err2) {
-    	PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
-    }
-}
-
-
-void CPjAudioInputEngine::MaiscRecordComplete(TInt aError)
-{
-    lastError_ = aError;
-    state_ = STATE_INACTIVE;
-    if (aError != KErrNone) {
-    	snd_perror("Error in MaiscRecordComplete()", aError);
-    }
-}
-
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-/*
- * Implementation: Symbian Output Stream.
- */
-
-class CPjAudioOutputEngine : public CBase, MMdaAudioOutputStreamCallback
-{
-public:
-    enum State
-    {
-	STATE_INACTIVE,
-	STATE_ACTIVE,
-    };
-
-    ~CPjAudioOutputEngine();
-
-    static CPjAudioOutputEngine *NewL(pjmedia_snd_stream *parent_strm,
-				      pjmedia_snd_play_cb play_cb,
-				      void *user_data);
-
-    static CPjAudioOutputEngine *NewLC(pjmedia_snd_stream *parent_strm,
-				       pjmedia_snd_play_cb rec_cb,
-				       void *user_data);
-
-    pj_status_t StartPlay();
-    void Stop();
-
-private:
-    State		     state_;
-    pjmedia_snd_stream	    *parentStrm_;
-    pjmedia_snd_play_cb	     playCb_;
-    void		    *userData_;
-    CMdaAudioOutputStream   *iOutputStream_;
-    TUint8		    *frameBuf_;
-    unsigned		     frameBufSize_;
-    TPtrC8		     frame_;
-    TInt		     lastError_;
-    unsigned		     timestamp_;
-
-    CPjAudioOutputEngine(pjmedia_snd_stream *parent_strm,
-			 pjmedia_snd_play_cb play_cb,
-			 void *user_data);
-    void ConstructL();
-
-    virtual void MaoscOpenComplete(TInt aError);
-    virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer);
-    virtual void MaoscPlayComplete(TInt aError);
-};
-
-
-CPjAudioOutputEngine::CPjAudioOutputEngine(pjmedia_snd_stream *parent_strm,
-					   pjmedia_snd_play_cb play_cb,
-					   void *user_data) 
-: state_(STATE_INACTIVE), parentStrm_(parent_strm), playCb_(play_cb), 
-  userData_(user_data), iOutputStream_(NULL), frameBuf_(NULL),
-  lastError_(KErrNone), timestamp_(0)
-{
-}
-
-
-void CPjAudioOutputEngine::ConstructL()
-{
-    frameBufSize_ = parentStrm_->samples_per_frame *
-			   parentStrm_->channel_count * 
-			   BYTES_PER_SAMPLE;
-    frameBuf_ = new TUint8[frameBufSize_];
-}
-
-CPjAudioOutputEngine::~CPjAudioOutputEngine()
-{
-    Stop();
-    delete [] frameBuf_;	
-}
-
-CPjAudioOutputEngine *
-CPjAudioOutputEngine::NewLC(pjmedia_snd_stream *parent_strm,
-			    pjmedia_snd_play_cb rec_cb,
-			    void *user_data)
-{
-    CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm,
-								   rec_cb, 
-								   user_data);
-    CleanupStack::PushL(self);
-    self->ConstructL();
-    return self;
-}
-
-CPjAudioOutputEngine *
-CPjAudioOutputEngine::NewL(pjmedia_snd_stream *parent_strm,
-			   pjmedia_snd_play_cb play_cb,
-			   void *user_data)
-{
-    CPjAudioOutputEngine *self = NewLC(parent_strm, play_cb, user_data);
-    CleanupStack::Pop(self);
-    return self;
-}
-
-pj_status_t CPjAudioOutputEngine::StartPlay()
-{
-    // Ignore command if playing is in progress.
-    if (state_ == STATE_ACTIVE)
-	return PJ_SUCCESS;
-    
-    // Destroy existing stream.
-    if (iOutputStream_) delete iOutputStream_;
-    iOutputStream_ = NULL;
-    
-    // Create the stream
-    TRAPD(err, iOutputStream_ = CMdaAudioOutputStream::NewL(*this));
-    if (err != KErrNone)
-	return PJ_RETURN_OS_ERROR(err);
-    
-    // Initialize settings.
-    TMdaAudioDataSettings iStreamSettings;
-    iStreamSettings.iChannels = get_channel_cap(parentStrm_->channel_count);
-    iStreamSettings.iSampleRate = get_clock_rate_cap(parentStrm_->clock_rate);
-
-    pj_assert(iStreamSettings.iChannels != 0 && 
-	      iStreamSettings.iSampleRate != 0);
-    
-    PJ_LOG(4,(THIS_FILE, "Opening sound device for playback, "
-    		         "clock rate=%d, channel count=%d..",
-    		         parentStrm_->clock_rate, 
-    		         parentStrm_->channel_count));
-
-    // Open stream.
-    lastError_ = KRequestPending;
-    iOutputStream_->Open(&iStreamSettings);
-
-    // Success
-    PJ_LOG(4,(THIS_FILE, "Sound playback started"));
-    return PJ_SUCCESS;
-
-}
-
-void CPjAudioOutputEngine::Stop()
-{
-    // Stop stream if it's playing
-    if (iOutputStream_ && state_ != STATE_INACTIVE) {
-    	lastError_ = KRequestPending;
-    	iOutputStream_->Stop();
-
-	// Wait until it's actually stopped
-    	while (lastError_ == KRequestPending)
-	    pj_symbianos_poll(-1, 100);
-    }
-    
-    if (iOutputStream_) {	
-	delete iOutputStream_;
-	iOutputStream_ = NULL;
-    }
-    
-    state_ = STATE_INACTIVE;
-}
-
-void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
-{
-    lastError_ = aError;
-    
-    if (aError==KErrNone) {
-	// output stream opened succesfully, set status to Active
-	state_ = STATE_ACTIVE;
-
-	// set stream properties, 16bit 8KHz mono
-	TMdaAudioDataSettings iSettings;
-	iSettings.iChannels = get_channel_cap(parentStrm_->channel_count);
-	iSettings.iSampleRate = get_clock_rate_cap(parentStrm_->clock_rate);
-
-	iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate, 
-					    iSettings.iChannels);
-
-	// set volume to 1/2th of stream max volume
-	iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2);
-	
-	// set stream priority to normal and time sensitive
-	iOutputStream_->SetPriority(EPriorityNormal, 
-				    EMdaPriorityPreferenceTime);				
-
-	// Call callback to retrieve frame from upstream.
-	pj_status_t status;
-	status = playCb_(this->userData_, timestamp_, frameBuf_, 
-			 frameBufSize_);
-	if (status != PJ_SUCCESS) {
-	    this->Stop();
-	    return;
-	}
-
-	// Increment timestamp.
-	timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
-
-	// issue WriteL() to write the first audio data block, 
-	// subsequent calls to WriteL() will be issued in 
-	// MMdaAudioOutputStreamCallback::MaoscBufferCopied() 
-	// until whole data buffer is written.
-	frame_.Set(frameBuf_, frameBufSize_);
-	iOutputStream_->WriteL(frame_);
-    } else {
-    	snd_perror("Error in MaoscOpenComplete()", aError);
-    }
-}
-
-void CPjAudioOutputEngine::MaoscBufferCopied(TInt aError, 
-					     const TDesC8& aBuffer)
-{
-    PJ_UNUSED_ARG(aBuffer);
-
-    if (aError==KErrNone) {
-    	// Buffer successfully written, feed another one.
-
-	// Call callback to retrieve frame from upstream.
-	pj_status_t status;
-	status = playCb_(this->userData_, timestamp_, frameBuf_, 
-			 frameBufSize_);
-	if (status != PJ_SUCCESS) {
-	    this->Stop();
-	    return;
-	}
-
-	// Increment timestamp.
-	timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
-
-	// Write to playback stream.
-	frame_.Set(frameBuf_, frameBufSize_);
-	iOutputStream_->WriteL(frame_);
-
-    } else if (aError==KErrAbort) {
-	// playing was aborted, due to call to CMdaAudioOutputStream::Stop()
-	state_ = STATE_INACTIVE;
-    } else  {
-	// error writing data to output
-	lastError_ = aError;
-	state_ = STATE_INACTIVE;
-	snd_perror("Error in MaoscBufferCopied()", aError);
-    }
-}
-
-void CPjAudioOutputEngine::MaoscPlayComplete(TInt aError)
-{
-    lastError_ = aError;
-    state_ = STATE_INACTIVE;
-    if (aError != KErrNone) {
-    	snd_perror("Error in MaoscPlayComplete()", aError);
-    }
-}
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-
-/*
- * Initialize sound subsystem.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
-    snd_pool_factory = factory;
-    return PJ_SUCCESS;
-}
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
-    /* Always return 1 */
-    return 1;
-}
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
-    /* Always return the default sound device */
-    if (index == (unsigned)-1)
-	index = 0;
-
-    PJ_ASSERT_RETURN(index==0, NULL);
-    return &symbian_snd_dev_info;
-}
-
-
-
-/*
- * Open sound recorder stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
-					  unsigned clock_rate,
-					  unsigned channel_count,
-					  unsigned samples_per_frame,
-					  unsigned bits_per_sample,
-					  pjmedia_snd_rec_cb rec_cb,
-					  void *user_data,
-					  pjmedia_snd_stream **p_snd_strm)
-{
-    pj_pool_t *pool;
-    pjmedia_snd_stream *strm;
-
-    if (index==-1) index = 0;
-    
-    PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
-    PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame &&
-    		     bits_per_sample && rec_cb && p_snd_strm, PJ_EINVAL);
-
-    pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, 
-    			  NULL);
-    if (!pool)
-	return PJ_ENOMEM;
-
-    strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, 
-    						sizeof(pjmedia_snd_stream));
-    strm->dir = PJMEDIA_DIR_CAPTURE;
-    strm->pool = pool;
-    strm->clock_rate = clock_rate;
-    strm->channel_count = channel_count;
-    strm->samples_per_frame = samples_per_frame;
-
-    PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
-    PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL);
-    PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL);
-
-    // Create the input stream.
-    TRAPD(err, strm->inEngine = CPjAudioInputEngine::NewL(strm, rec_cb, 
-    							  user_data));
-    if (err != KErrNone) {
-    	pj_pool_release(pool);
-    	return PJ_RETURN_OS_ERROR(err);
-    }
-
-    // Done.
-    *p_snd_strm = strm;
-    return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
-					unsigned clock_rate,
-					unsigned channel_count,
-					unsigned samples_per_frame,
-					unsigned bits_per_sample,
-					pjmedia_snd_play_cb play_cb,
-					void *user_data,
-					pjmedia_snd_stream **p_snd_strm )
-{
-    pj_pool_t *pool;
-    pjmedia_snd_stream *strm;
-
-    if (index == -1) index = 0;
-    
-    PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
-    PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame &&
-    		     bits_per_sample && play_cb && p_snd_strm, PJ_EINVAL);
-
-    pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, 
-    			  NULL);
-    if (!pool)
-	return PJ_ENOMEM;
-
-    strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, 
-    						sizeof(pjmedia_snd_stream));
-    strm->dir = PJMEDIA_DIR_PLAYBACK;    
-    strm->pool = pool;
-    strm->clock_rate = clock_rate;
-    strm->channel_count = channel_count;
-    strm->samples_per_frame = samples_per_frame;
-
-    PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
-    PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL);
-    PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL);
-
-    // Create the output stream.
-    TRAPD(err, strm->outEngine = CPjAudioOutputEngine::NewL(strm, play_cb, 
-    							    user_data));
-    if (err != KErrNone) {
-    	pj_pool_release(pool);	
-	return PJ_RETURN_OS_ERROR(err);
-    }
-
-    // Done.
-    *p_snd_strm = strm;
-    return PJ_SUCCESS;
-}
-
-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 *strm;
-
-    if (rec_id == -1) rec_id = 0;
-    if (play_id == -1) play_id = 0;
-    
-    PJ_ASSERT_RETURN(rec_id == 0 && play_id == 0, PJ_EINVAL);
-    PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame &&
-    		     bits_per_sample && rec_cb && play_cb && p_snd_strm, 
-    		     PJ_EINVAL);
-
-    pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, 
-    			  NULL);
-    if (!pool)
-	return PJ_ENOMEM;
-
-    strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, 
-    						sizeof(pjmedia_snd_stream));
-    strm->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
-    strm->pool = pool;
-    strm->clock_rate = clock_rate;
-    strm->channel_count = channel_count;
-    strm->samples_per_frame = samples_per_frame;
-
-    PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
-    PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL);
-    PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL);
-
-    // Create the output stream.
-    TRAPD(err, strm->outEngine = CPjAudioOutputEngine::NewL(strm, play_cb, 
-    							    user_data));
-    if (err != KErrNone) {
-    	pj_pool_release(pool);	
-	return PJ_RETURN_OS_ERROR(err);
-    }
-
-    // Create the input stream.
-    TRAPD(err1, strm->inEngine = CPjAudioInputEngine::NewL(strm, rec_cb, 
-    							   user_data));
-    if (err1 != KErrNone) {
-        strm->inEngine = NULL;
-        delete strm->outEngine;
-    	strm->outEngine = NULL;
-    	pj_pool_release(pool);	
-	return PJ_RETURN_OS_ERROR(err1);
-    }
-
-    // Done.
-    *p_snd_strm = strm;
-    return PJ_SUCCESS;
-}
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
-						pjmedia_snd_stream_info *pi)
-{
-    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-
-    pj_bzero(pi, sizeof(*pi));
-    pi->dir = strm->dir;
-    pi->play_id = 0;
-    pi->rec_id = 0;
-    pi->clock_rate = strm->clock_rate;
-    pi->channel_count = strm->channel_count;
-    pi->samples_per_frame = strm->samples_per_frame;
-    pi->bits_per_sample = BYTES_PER_SAMPLE * 8;
-    // Symbian uses 4096 bytes buffer (~2048 samples/256 ms) for PCM rec & play.
-    // The latencies below are rounded up to be a multiplication of 80.
-    pi->rec_latency  = 2080;
-    pi->play_latency = 2080;
-
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
-    pj_status_t status;
-    
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-    
-    if (stream->outEngine) {
-    	status = stream->outEngine->StartPlay();
-    	if (status != PJ_SUCCESS)
-    	    return status;
-    }
-    
-    if (stream->inEngine) {
-    	status = stream->inEngine->StartRecord();
-    	if (status != PJ_SUCCESS)
-    	    return status;
-    }
-    	
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-    
-    if (stream->inEngine) {
-    	stream->inEngine->Stop();
-    }
-    	
-    if (stream->outEngine) {
-    	stream->outEngine->Stop();
-    }
-    
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
-    pj_pool_t *pool;
-    
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-    
-    if (stream->inEngine) {
-    	delete stream->inEngine;
-    	stream->inEngine = NULL;
-    }
-
-    if (stream->outEngine) {
-    	delete stream->outEngine;
-    	stream->outEngine = NULL;
-    }
-    
-    pool = stream->pool;
-    if (pool) {	
-    	stream->pool = NULL;
-    	pj_pool_release(pool);
-    }
-    
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
-    /* Nothing to do */
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, 
-					    unsigned output_latency)
-{
-    /* Nothing to do */
-    PJ_UNUSED_ARG(input_latency);
-    PJ_UNUSED_ARG(output_latency);
-    return PJ_SUCCESS;
-}
Index: pjproject/trunk/pjmedia/src/pjmedia/symbian_sound_aps.cpp
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/symbian_sound_aps.cpp	(revision 2505)
+++ 	(revision )
@@ -1,920 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * 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 
- */
-#include <pjmedia/sound.h>
-#include <pjmedia/alaw_ulaw.h>
-#include <pjmedia/errno.h>
-#include <pjmedia/symbian_sound_aps.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/math.h>
-#include <pj/os.h>
-
-#include <e32msgqueue.h>
-#include <sounddevice.h>
-#include <APSClientSession.h>
-
-//////////////////////////////////////////////////////////////////////////////
-//
-
-#define THIS_FILE	    "symbian_sound_aps.cpp"
-
-#define BYTES_PER_SAMPLE    2
-#define POOL_NAME	    "SymbianSoundAps"
-#define POOL_SIZE	    512
-#define POOL_INC	    512
-
-#if 1
-#   define TRACE_(st) PJ_LOG(3, st)
-#else
-#   define TRACE_(st)
-#endif
-
-static pjmedia_snd_dev_info symbian_snd_dev_info = 
-{
-    "Symbian Sound Device (APS)",
-    1,
-    1,
-    8000
-};
-
-/* App UID to open global APS queues to communicate with the APS server. */
-extern TPtrC		    APP_UID;
-
-/* Default setting for loudspeaker */
-static pj_bool_t act_loudspeaker = PJ_FALSE;
-
-/* Forward declaration of CPjAudioEngine */
-class CPjAudioEngine;
-
-/* 
- * PJMEDIA Sound Stream instance 
- */
-struct pjmedia_snd_stream
-{
-    // Pool
-    pj_pool_t			*pool;
-
-    // Common settings.
-    pjmedia_dir		 	 dir;
-    unsigned			 clock_rate;
-    unsigned			 channel_count;
-    unsigned			 samples_per_frame;
-
-    // Audio engine
-    CPjAudioEngine		*engine;
-};
-
-static pj_pool_factory *snd_pool_factory;
-
-
-/*
- * Utility: print sound device error
- */
-static void snd_perror(const char *title, TInt rc) 
-{
-    PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
-}
- 
-//////////////////////////////////////////////////////////////////////////////
-//
-
-/**
- * Abstract class for handler of callbacks from APS client.
- */
-class MQueueHandlerObserver
-{
-public:
-    virtual void InputStreamInitialized(const TInt aStatus) = 0;
-    virtual void OutputStreamInitialized(const TInt aStatus) = 0;
-    virtual void NotifyError(const TInt aError) = 0;
-
-    virtual void RecCb(TAPSCommBuffer &buffer) = 0;
-    virtual void PlayCb(TAPSCommBuffer &buffer) = 0;
-};
-
-/**
- * Handler for communication and data queue.
- */
-class CQueueHandler : public CActive
-{
-public:
-    // Types of queue handler
-    enum TQueueHandlerType {
-        ERecordCommQueue,
-        EPlayCommQueue,
-        ERecordQueue,
-        EPlayQueue
-    };
-
-    // The order corresponds to the APS Server state, do not change!
-    enum TState {
-    	EAPSPlayerInitialize        = 1,
-    	EAPSRecorderInitialize      = 2,
-    	EAPSPlayData                = 3,
-    	EAPSRecordData              = 4,
-    	EAPSPlayerInitComplete      = 5,
-    	EAPSRecorderInitComplete    = 6
-    };
-
-    static CQueueHandler* NewL(MQueueHandlerObserver* aObserver, 
-			       RMsgQueue<TAPSCommBuffer>* aQ, 
-			       TQueueHandlerType aType)
-    {
-	CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aType);
-	CleanupStack::PushL(self);
-	self->ConstructL();
-	CleanupStack::Pop(self);
-	return self;
-    }
-
-    // Destructor
-    ~CQueueHandler() { Cancel(); }
-
-    // Start listening queue event
-    void Start() {
-	iQ->NotifyDataAvailable(iStatus);
-	SetActive();
-    }
-
-private:
-    // Constructor
-    CQueueHandler(MQueueHandlerObserver* aObserver, 
-		  RMsgQueue<TAPSCommBuffer>* aQ, 
-		  TQueueHandlerType aType) 
-	: CActive(CActive::EPriorityHigh),
-	  iQ(aQ), iObserver(aObserver), iType(aType)
-    {
-	CActiveScheduler::Add(this);
-
-	// use lower priority for comm queues
-	if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue))
-	    SetPriority(CActive::EPriorityStandard);
-    }
-
-    // Second phase constructor
-    void ConstructL() {}
-
-    // Inherited from CActive
-    void DoCancel() { iQ->CancelDataAvailable(); }
-
-    void RunL() {
-	if (iStatus != KErrNone) {
-	    iObserver->NotifyError(iStatus.Int());
-	    return;
-        }    
-
-	TAPSCommBuffer buffer;
-	TInt ret = iQ->Receive(buffer);
-
-	if (ret != KErrNone) {
-	    iObserver->NotifyError(ret);
-	    return;
-	}
-
-	switch (iType) {
-	case ERecordQueue:
-	    if (buffer.iCommand == EAPSRecordData) {
-		iObserver->RecCb(buffer);
-	    }
-	    break;
-
-	// Callbacks from the APS main thread
-	case EPlayCommQueue:
-	    switch (buffer.iCommand) {
-		case EAPSPlayData:
-		    if (buffer.iStatus == KErrUnderflow) {
-			iObserver->PlayCb(buffer);
-		    }
-		    break;
-		case EAPSPlayerInitialize:
-		    iObserver->NotifyError(buffer.iStatus);
-		    break;
-		case EAPSPlayerInitComplete:
-		    iObserver->OutputStreamInitialized(buffer.iStatus);
-		    break;
-		case EAPSRecorderInitComplete:
-		    iObserver->InputStreamInitialized(buffer.iStatus);
-		    break;
-		default:
-		    iObserver->NotifyError(buffer.iStatus);
-		    break;
-	    }
-	    break;
-
-	// Callbacks from the APS recorder thread
-	case ERecordCommQueue:
-	    switch (buffer.iCommand) {
-		// The APS recorder thread will only report errors
-		// through this handler. All other callbacks will be
-		// sent from the APS main thread through EPlayCommQueue
-		case EAPSRecorderInitialize:
-		    if (buffer.iStatus == KErrNone) {
-			iObserver->InputStreamInitialized(buffer.iStatus);
-			break;
-		    }
-		case EAPSRecordData:
-		    iObserver->NotifyError(buffer.iStatus);
-		    break;
-		default:
-		    break;
-	    }
-	    break;
-
-	default:
-	    break;
-        }
-
-        // issue next request
-        iQ->NotifyDataAvailable(iStatus);
-        SetActive();
-    }
-
-    // Data
-    RMsgQueue<TAPSCommBuffer>	*iQ;   // (not owned)
-    MQueueHandlerObserver	*iObserver; // (not owned)
-    TQueueHandlerType            iType;
-};
-
-
-/*
- * Implementation: Symbian Input & Output Stream.
- */
-class CPjAudioEngine : public CBase, MQueueHandlerObserver
-{
-public:
-    enum State
-    {
-	STATE_NULL,
-	STATE_READY,
-	STATE_STREAMING
-    };
-
-    ~CPjAudioEngine();
-
-    static CPjAudioEngine *NewL(pjmedia_snd_stream *parent_strm,
-				pjmedia_snd_rec_cb rec_cb,
-				pjmedia_snd_play_cb play_cb,
-				void *user_data);
-
-    TInt StartL();
-    void Stop();
-
-    TInt ActivateSpeaker(TBool active);
-
-private:
-    CPjAudioEngine(pjmedia_snd_stream *parent_strm,
-		   pjmedia_snd_rec_cb rec_cb,
-		   pjmedia_snd_play_cb play_cb,
-		   void *user_data);
-    void ConstructL();
-    
-    TInt InitPlayL();
-    TInt InitRecL();
-    TInt StartStreamL();
-    
-    // Inherited from MQueueHandlerObserver
-    virtual void InputStreamInitialized(const TInt aStatus);
-    virtual void OutputStreamInitialized(const TInt aStatus);
-    virtual void NotifyError(const TInt aError);
-
-    virtual void RecCb(TAPSCommBuffer &buffer);
-    virtual void PlayCb(TAPSCommBuffer &buffer);
-
-    State			 state_;
-    pjmedia_snd_stream		*parentStrm_;
-    pjmedia_snd_rec_cb		 recCb_;
-    pjmedia_snd_play_cb		 playCb_;
-    void			*userData_;
-    pj_uint32_t			 TsPlay_;
-    pj_uint32_t			 TsRec_;
-
-    RAPSSession                  iSession;
-    TAPSInitSettings             iSettings;
-    RMsgQueue<TAPSCommBuffer>    iReadQ;
-    RMsgQueue<TAPSCommBuffer>    iReadCommQ;
-    RMsgQueue<TAPSCommBuffer>    iWriteQ;
-    RMsgQueue<TAPSCommBuffer>    iWriteCommQ;
-
-    CQueueHandler		*iPlayCommHandler;
-    CQueueHandler		*iRecCommHandler;
-    CQueueHandler		*iRecHandler;
-    
-    static pj_uint8_t		 aps_samples_per_frame;
-    
-    pj_int16_t			*play_buf;
-    pj_uint16_t			 play_buf_len;
-    pj_uint16_t			 play_buf_start;
-    pj_int16_t			*rec_buf;
-    pj_uint16_t			 rec_buf_len;
-};
-
-
-pj_uint8_t CPjAudioEngine::aps_samples_per_frame = 0;
-
-
-CPjAudioEngine* CPjAudioEngine::NewL(pjmedia_snd_stream *parent_strm,
-				     pjmedia_snd_rec_cb rec_cb,
-				     pjmedia_snd_play_cb play_cb,
-				     void *user_data)
-{
-    CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
-						       rec_cb, play_cb,
-						       user_data);
-    CleanupStack::PushL(self);
-    self->ConstructL();
-    CleanupStack::Pop(self);
-    return self;
-}
-
-CPjAudioEngine::CPjAudioEngine(pjmedia_snd_stream *parent_strm,
-			       pjmedia_snd_rec_cb rec_cb,
-			       pjmedia_snd_play_cb play_cb,
-			       void *user_data) 
-      : state_(STATE_NULL),
-	parentStrm_(parent_strm),
-	recCb_(rec_cb),
-	playCb_(play_cb),
-	userData_(user_data),
-	iPlayCommHandler(0),
-	iRecCommHandler(0),
-	iRecHandler(0)
-{
-}
-
-CPjAudioEngine::~CPjAudioEngine()
-{
-    Stop();
-
-    delete iPlayCommHandler;
-    iPlayCommHandler = NULL;
-    delete iRecCommHandler;
-    iRecCommHandler = NULL;
-
-    // On some devices, immediate closing after stopping may cause APS server
-    // panic KERN-EXEC 0, so let's wait for sometime before really closing
-    // the client session.
-    TTime start, now;
-    enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
-    
-    start.UniversalTime();
-    do {
-	pj_symbianos_poll(-1, APS_CLOSE_WAIT_TIME);
-	now.UniversalTime();
-    } while (now.MicroSecondsFrom(start) < APS_CLOSE_WAIT_TIME * 1000);
-    
-    iSession.Close();
-
-    if (state_ == STATE_READY) {
-	if (parentStrm_->dir != PJMEDIA_DIR_PLAYBACK) {
-	    iReadQ.Close();
-	    iReadCommQ.Close();
-	}
-	iWriteQ.Close();
-	iWriteCommQ.Close();
-    }
-}
-
-TInt CPjAudioEngine::InitPlayL()
-{
-    if (state_ == STATE_STREAMING || state_ == STATE_READY)
-	return 0;
-
-    TInt err = iSession.InitializePlayer(iSettings);
-    if (err != KErrNone) {
-	snd_perror("Failed to initialize player", err);
-	return err;
-    }
-
-    // Open message queues for the output stream
-    TBuf<128> buf2 = iSettings.iGlobal;
-    buf2.Append(_L("PlayQueue"));
-    TBuf<128> buf3 = iSettings.iGlobal;
-    buf3.Append(_L("PlayCommQueue"));
-
-    while (iWriteQ.OpenGlobal(buf2))
-	User::After(10);
-    while (iWriteCommQ.OpenGlobal(buf3))
-	User::After(10);
-
-    // Construct message queue handler
-    iPlayCommHandler = CQueueHandler::NewL(this,
-					   &iWriteCommQ,
-					   CQueueHandler::EPlayCommQueue);
-
-    // Start observing APS callbacks on output stream message queue
-    iPlayCommHandler->Start();
-
-    return 0;
-}
-
-TInt CPjAudioEngine::InitRecL()
-{
-    if (state_ == STATE_STREAMING || state_ == STATE_READY)
-	return 0;
-
-    // Initialize input stream device
-    TInt err = iSession.InitializeRecorder(iSettings);
-    if (err != KErrNone) {
-	snd_perror("Failed to initialize recorder", err);
-	return err;
-    }
-
-    TBuf<128> buf1 = iSettings.iGlobal;
-    buf1.Append(_L("RecordQueue"));
-    TBuf<128> buf4 = iSettings.iGlobal;
-    buf4.Append(_L("RecordCommQueue"));
-
-    // Must wait for APS thread to finish creating message queues
-    // before we can open and use them.
-    while (iReadQ.OpenGlobal(buf1))
-	User::After(10);
-    while (iReadCommQ.OpenGlobal(buf4))
-	User::After(10);
-
-    // Construct message queue handlers
-    iRecCommHandler = CQueueHandler::NewL(this,
-					  &iReadCommQ,
-					  CQueueHandler::ERecordCommQueue);
-
-    // Start observing APS callbacks from on input stream message queue
-    iRecCommHandler->Start();
-    
-    return 0;
-}
-
-TInt CPjAudioEngine::StartL()
-{
-    TInt err = iSession.Connect();
-    if (err != KErrNone && err != KErrAlreadyExists)
-	return err;
-
-    if (state_ == STATE_READY)
-	return StartStreamL();
-
-    // Even if only capturer are opened, playback thread of APS Server need 
-    // to be run(?). Since some messages will be delivered via play comm queue.
-    return InitPlayL();
-}
-
-void CPjAudioEngine::Stop()
-{
-    iSession.Stop();
-
-    delete iRecHandler;
-    iRecHandler = NULL;
-
-    state_ = STATE_READY;
-}
-
-void CPjAudioEngine::ConstructL()
-{
-    iSettings.iFourCC		    = TFourCC(KMCPFourCCIdG711);
-    iSettings.iGlobal		    = APP_UID;
-    iSettings.iPriority		    = TMdaPriority(100);
-    iSettings.iPreference	    = TMdaPriorityPreference(0x05210001);
-    iSettings.iSettings.iChannels   = EMMFMono;
-    iSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
-    iSettings.iSettings.iVolume	    = 0;
-    
-    /* play_buf size is samples per frame of parent stream. */
-    play_buf = (pj_int16_t*)pj_pool_alloc(parentStrm_->pool, 
-				          parentStrm_->samples_per_frame << 1);
-    play_buf_len = 0;
-    play_buf_start = 0;
-    
-    /* rec_buf size is samples per frame of parent stream. */
-    rec_buf  = (pj_int16_t*)pj_pool_alloc(parentStrm_->pool, 
-					  parentStrm_->samples_per_frame << 1);
-    rec_buf_len = 0;
-}
-
-TInt CPjAudioEngine::StartStreamL()
-{
-    if (state_ == STATE_STREAMING)
-	return 0;
-
-    iSession.SetCng(EFalse);
-    iSession.SetVadMode(EFalse);
-    iSession.SetPlc(EFalse);
-    iSession.SetEncoderMode(EULawOr30ms);
-    iSession.SetDecoderMode(EULawOr30ms);
-    iSession.ActivateLoudspeaker(act_loudspeaker);
-
-    // Not only playback
-    if (parentStrm_->dir != PJMEDIA_DIR_PLAYBACK) {
-	iRecHandler = CQueueHandler::NewL(this, &iReadQ, 
-					  CQueueHandler::ERecordQueue);
-	iRecHandler->Start();
-	iSession.Read();
-	TRACE_((THIS_FILE, "APS recorder started"));
-    }
-
-    // Not only capture
-    if (parentStrm_->dir != PJMEDIA_DIR_CAPTURE) {
-	iSession.Write();
-	TRACE_((THIS_FILE, "APS player started"));
-    }
-
-    state_ = STATE_STREAMING;
-    return 0;
-}
-
-///////////////////////////////////////////////////////////
-// Inherited from MQueueHandlerObserver
-//
-
-void CPjAudioEngine::InputStreamInitialized(const TInt aStatus)
-{
-    TRACE_((THIS_FILE, "InputStreamInitialized %d", aStatus));
-
-    state_ = STATE_READY;
-    if (aStatus == KErrNone) {
-	StartStreamL();
-    }
-}
-
-void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus)
-{
-    TRACE_((THIS_FILE, "OutputStreamInitialized %d", aStatus));
-
-    if (aStatus == KErrNone) {
-	if (parentStrm_->dir == PJMEDIA_DIR_PLAYBACK) {
-	    state_ = STATE_READY;
-	    // Only playback, start directly
-	    StartStreamL();
-	} else
-	    InitRecL();
-    }
-}
-
-void CPjAudioEngine::NotifyError(const TInt aError)
-{
-    snd_perror("Error from CQueueHandler", aError);
-}
-
-void CPjAudioEngine::RecCb(TAPSCommBuffer &buffer)
-{
-    pj_assert(buffer.iBuffer[0] == 1 && buffer.iBuffer[1] == 0);
-
-    /* Detect the recorder G.711 frame size, player frame size will follow 
-     * this recorder frame size. 
-     */
-    if (CPjAudioEngine::aps_samples_per_frame == 0) {
-	CPjAudioEngine::aps_samples_per_frame = buffer.iBuffer.Length() < 160?
-						 80 : 160;
-	TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples", 
-		CPjAudioEngine::aps_samples_per_frame));
-    }
-
-    /* Decode APS buffer (coded in G.711) and put the PCM result into rec_buf.
-     * Whenever rec_buf is full, call parent stream callback.  
-     */ 
-    unsigned dec_len = 0;
-
-    while (dec_len < CPjAudioEngine::aps_samples_per_frame) {
-	unsigned tmp;
-
-	tmp = PJ_MIN(parentStrm_->samples_per_frame - rec_buf_len, 
-		     CPjAudioEngine::aps_samples_per_frame - dec_len);
-	pjmedia_ulaw_decode(&rec_buf[rec_buf_len], 
-			    buffer.iBuffer.Ptr() + 2 + dec_len, 
-			    tmp);
-	rec_buf_len += tmp;
-	dec_len += tmp;
-	
-	pj_assert(rec_buf_len <= parentStrm_->samples_per_frame);
-	
-	if (rec_buf_len == parentStrm_->samples_per_frame) {
-	    recCb_(userData_, 0, rec_buf, rec_buf_len << 1);
-	    rec_buf_len = 0;
-	}
-    }
-}
-
-void CPjAudioEngine::PlayCb(TAPSCommBuffer &buffer)
-{
-    buffer.iCommand = CQueueHandler::EAPSPlayData;
-    buffer.iStatus = 0;
-    buffer.iBuffer.Zero();
-    buffer.iBuffer.Append(1);
-    buffer.iBuffer.Append(0);
-
-    /* Send 10ms silence frame if frame size hasn't been known. */
-    if (CPjAudioEngine::aps_samples_per_frame == 0) {
-	pjmedia_zero_samples(play_buf, 80);
-	pjmedia_ulaw_encode((pj_uint8_t*)play_buf, play_buf, 80);
-	buffer.iBuffer.Append((TUint8*)play_buf, 80);
-	iWriteQ.Send(buffer);
-	return;
-    }
-    
-    unsigned enc_len = 0;
-    
-    /* Call parent stream callback to get PCM samples to play,
-     * encode the PCM samples into G.711 and put it into APS buffer. 
-     */
-    while (enc_len < CPjAudioEngine::aps_samples_per_frame) {
-	if (play_buf_len == 0) {
-	    playCb_(userData_, 0, play_buf, parentStrm_->samples_per_frame<<1);
-	    play_buf_len = parentStrm_->samples_per_frame;
-	    play_buf_start = 0;
-	}
-	
-	unsigned tmp;
-	
-	tmp = PJ_MIN(play_buf_len, 
-		     CPjAudioEngine::aps_samples_per_frame - enc_len);
-	pjmedia_ulaw_encode((pj_uint8_t*)&play_buf[play_buf_start], 
-			    &play_buf[play_buf_start], 
-			    tmp);
-	buffer.iBuffer.Append((TUint8*)&play_buf[play_buf_start], tmp);
-	enc_len += tmp;
-	play_buf_len -= tmp;
-	play_buf_start += tmp;
-    }
-
-    iWriteQ.Send(buffer);
-}
-
-//
-// End of inherited from MQueueHandlerObserver
-/////////////////////////////////////////////////////////////
-
-
-TInt CPjAudioEngine::ActivateSpeaker(TBool active)
-{
-    if (state_ == STATE_READY || state_ == STATE_STREAMING) {
-        iSession.ActivateLoudspeaker(active);
-	return KErrNone;
-    }
-    return KErrNotReady;
-}
-//////////////////////////////////////////////////////////////////////////////
-//
-
-
-/*
- * Initialize sound subsystem.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
-    snd_pool_factory = factory;
-    return PJ_SUCCESS;
-}
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
-    /* Always return 1 */
-    return 1;
-}
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
-    /* Always return the default sound device */
-    if (index == (unsigned)-1)
-	index = 0;
-
-    PJ_ASSERT_RETURN(index==0, NULL);
-    return &symbian_snd_dev_info;
-}
-
-static pj_status_t sound_open(pjmedia_dir dir,
-			      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 *strm;
-
-    PJ_ASSERT_RETURN(p_snd_strm, PJ_EINVAL);
-    PJ_ASSERT_RETURN(clock_rate == 8000 && channel_count == 1 && 
-		     bits_per_sample == 16, PJ_ENOTSUP);
-    PJ_ASSERT_RETURN((dir == PJMEDIA_DIR_CAPTURE_PLAYBACK && rec_cb && play_cb)
-		     || (dir == PJMEDIA_DIR_CAPTURE && rec_cb && !play_cb)
-		     || (dir == PJMEDIA_DIR_PLAYBACK && !rec_cb && play_cb),
-		     PJ_EINVAL);
-
-    pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, 
-    			  NULL);
-    if (!pool)
-	return PJ_ENOMEM;
-
-    strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, 
-    						sizeof(pjmedia_snd_stream));
-    strm->dir = dir;
-    strm->pool = pool;
-    strm->clock_rate = clock_rate;
-    strm->channel_count = channel_count;
-    strm->samples_per_frame = samples_per_frame;
-
-    // Create the audio engine.
-    TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm, rec_cb, play_cb, 
-						   user_data));
-    if (err != KErrNone) {
-    	pj_pool_release(pool);	
-	return PJ_RETURN_OS_ERROR(err);
-    }
-
-    // Done.
-    *p_snd_strm = strm;
-    return PJ_SUCCESS;
-}
-
-
-
-/*
- * Open sound recorder stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
-					  unsigned clock_rate,
-					  unsigned channel_count,
-					  unsigned samples_per_frame,
-					  unsigned bits_per_sample,
-					  pjmedia_snd_rec_cb rec_cb,
-					  void *user_data,
-					  pjmedia_snd_stream **p_snd_strm)
-{
-    if (index < 0) index = 0;
-    PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
-
-    return sound_open(PJMEDIA_DIR_CAPTURE, clock_rate, channel_count, 
-		      samples_per_frame, bits_per_sample, rec_cb, NULL,
-		      user_data, p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
-					unsigned clock_rate,
-					unsigned channel_count,
-					unsigned samples_per_frame,
-					unsigned bits_per_sample,
-					pjmedia_snd_play_cb play_cb,
-					void *user_data,
-					pjmedia_snd_stream **p_snd_strm )
-{
-    if (index < 0) index = 0;
-    PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
-
-    return sound_open(PJMEDIA_DIR_PLAYBACK, clock_rate, channel_count, 
-		      samples_per_frame, bits_per_sample, NULL, play_cb,
-		      user_data, p_snd_strm);
-}
-
-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)
-{
-    if (rec_id < 0) rec_id = 0;
-    if (play_id < 0) play_id = 0;
-    PJ_ASSERT_RETURN(play_id == 0 && rec_id == 0, PJ_EINVAL);
-
-    return sound_open(PJMEDIA_DIR_CAPTURE_PLAYBACK, clock_rate, channel_count, 
-		      samples_per_frame, bits_per_sample, rec_cb, play_cb,
-		      user_data, p_snd_strm);
-}
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
-						pjmedia_snd_stream_info *pi)
-{
-    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-
-    pj_bzero(pi, sizeof(*pi));
-    pi->dir = strm->dir;
-    pi->play_id = 0;
-    pi->rec_id = 0;
-    pi->clock_rate = strm->clock_rate;
-    pi->channel_count = strm->channel_count;
-    pi->samples_per_frame = strm->samples_per_frame;
-    pi->bits_per_sample = BYTES_PER_SAMPLE * 8;
-    // latencies approximation (in samples)
-    pi->rec_latency  = strm->samples_per_frame * 2;
-    pi->play_latency = strm->samples_per_frame * 2;
-
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-    
-    if (stream->engine) {
-	TInt err = stream->engine->StartL();
-    	if (err != KErrNone)
-    	    return PJ_RETURN_OS_ERROR(err);
-    }
-    	
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-    
-    if (stream->engine) {
-    	stream->engine->Stop();
-    }
-    
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
-    pj_pool_t *pool;
-    
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-    
-    if (stream->engine) {
-    	delete stream->engine;
-    	stream->engine = NULL;
-    }
-
-    pool = stream->pool;
-    if (pool) {	
-    	stream->pool = NULL;
-    	pj_pool_release(pool);
-    }
-    
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
-    /* Nothing to do */
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, 
-					    unsigned output_latency)
-{
-    /* Nothing to do */
-    PJ_UNUSED_ARG(input_latency);
-    PJ_UNUSED_ARG(output_latency);
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Activate/deactivate loudspeaker.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_aps_activate_loudspeaker(
-					pjmedia_snd_stream *stream, 
-					pj_bool_t active)
-{
-    if (stream == NULL) {
-	act_loudspeaker = active;
-    } else {
-	if (stream->engine == NULL)
-	    return PJ_EINVAL;
-
-	TInt err = stream->engine->ActivateSpeaker(active);
-	if (err != KErrNone)
-	    return PJ_RETURN_OS_ERROR(err);
-    }
-
-    return PJ_SUCCESS;
-}
Index: pjproject/trunk/pjmedia/src/pjmedia/transport_ice.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/transport_ice.c	(revision 2505)
+++ pjproject/trunk/pjmedia/src/pjmedia/transport_ice.c	(revision 2506)
@@ -22,4 +22,5 @@
 #include <pj/assert.h>
 #include <pj/log.h>
+#include <pj/pool.h>
 #include <pj/rand.h>
 
Index: pjproject/trunk/pjmedia/src/pjmedia/transport_loop.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/transport_loop.c	(revision 2505)
+++ pjproject/trunk/pjmedia/src/pjmedia/transport_loop.c	(revision 2506)
@@ -23,4 +23,5 @@
 #include <pj/ioqueue.h>
 #include <pj/log.h>
+#include <pj/pool.h>
 #include <pj/rand.h>
 #include <pj/string.h>
Index: pjproject/trunk/pjmedia/src/pjmedia/transport_udp.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/transport_udp.c	(revision 2505)
+++ pjproject/trunk/pjmedia/src/pjmedia/transport_udp.c	(revision 2506)
@@ -24,4 +24,5 @@
 #include <pj/ioqueue.h>
 #include <pj/log.h>
+#include <pj/pool.h>
 #include <pj/rand.h>
 #include <pj/string.h>
Index: pjproject/trunk/pjmedia/src/pjmedia/wmme_sound.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/wmme_sound.c	(revision 2505)
+++ 	(revision )
@@ -1,1008 +1,0 @@
-#include <pjmedia/sound.h>
-#include <pjmedia/errno.h>
-#include <pj/assert.h>
-#include <pj/log.h>
-#include <pj/os.h>
-#include <pj/string.h>
-
-#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_WIN32_MME_SOUND
-
-#ifdef _MSC_VER
-#   pragma warning(push, 3)
-#endif
-
-#include <windows.h>
-#include <mmsystem.h>
-
-#ifdef _MSC_VER
-#   pragma warning(pop)
-#endif
-
-#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
-#   pragma comment(lib, "Coredll.lib")
-#elif defined(_MSC_VER)
-#   pragma comment(lib, "winmm.lib")
-#endif
-
-
-#define THIS_FILE			"wmme_sound.c"
-#define BITS_PER_SAMPLE			16
-#define BYTES_PER_SAMPLE		(BITS_PER_SAMPLE/8)
-
-#define MAX_PACKET_BUFFER_COUNT		32
-#define MAX_HARDWARE			16
-
-struct wmme_dev_info
-{
-    pjmedia_snd_dev_info info;
-    unsigned             deviceId;
-};
-
-static unsigned dev_count;
-static struct wmme_dev_info dev_info[MAX_HARDWARE];
-static pj_bool_t snd_initialized = PJ_FALSE;
-
-/* Latency settings */
-static unsigned snd_input_latency  = PJMEDIA_SND_DEFAULT_REC_LATENCY;
-static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
-
-
-/* Individual WMME capture/playback stream descriptor */
-struct wmme_stream
-{
-    union
-    {
-	HWAVEIN   In;
-	HWAVEOUT  Out;
-    } hWave;
-
-    WAVEHDR      *WaveHdr;
-    HANDLE        hEvent;
-    DWORD         dwBufIdx;
-    DWORD         dwMaxBufIdx;
-    pj_timestamp  timestamp;
-};
-
-
-/* Sound stream. */
-struct pjmedia_snd_stream
-{
-    pjmedia_dir          dir;               /**< Sound direction.      */
-    int                  play_id;           /**< Playback dev id.      */
-    int                  rec_id;            /**< Recording dev id.     */
-    pj_pool_t           *pool;              /**< Memory pool.          */
-
-    pjmedia_snd_rec_cb   rec_cb;            /**< Capture callback.     */
-    pjmedia_snd_play_cb  play_cb;           /**< Playback callback.    */
-    void                *user_data;         /**< Application data.     */
-
-    struct wmme_stream   play_strm;         /**< Playback stream.      */
-    struct wmme_stream   rec_strm;          /**< Capture stream.       */
-
-    void    		*buffer;	    /**< Temp. frame buffer.   */
-    unsigned             clock_rate;        /**< Clock rate.           */
-    unsigned             samples_per_frame; /**< Samples per frame.    */
-    unsigned             bits_per_sample;   /**< Bits per sample.      */
-    unsigned             channel_count;     /**< Channel count.        */
-
-    pj_thread_t         *thread;            /**< Thread handle.        */
-    HANDLE               thread_quit_event; /**< Quit signal to thread */
-};
-
-
-static pj_pool_factory *pool_factory;
-
-static void init_waveformatex (LPWAVEFORMATEX pcmwf, 
-			       unsigned clock_rate,
-			       unsigned channel_count)
-{
-    pj_bzero(pcmwf, sizeof(PCMWAVEFORMAT)); 
-    pcmwf->wFormatTag = WAVE_FORMAT_PCM; 
-    pcmwf->nChannels = (pj_uint16_t)channel_count;
-    pcmwf->nSamplesPerSec = clock_rate;
-    pcmwf->nBlockAlign = (pj_uint16_t)(channel_count * BYTES_PER_SAMPLE);
-    pcmwf->nAvgBytesPerSec = clock_rate * channel_count * BYTES_PER_SAMPLE;
-    pcmwf->wBitsPerSample = BITS_PER_SAMPLE;
-}
-
-
-/*
- * Initialize WMME player device.
- */
-static pj_status_t init_player_stream(  pj_pool_t *pool,
-				        struct wmme_stream *wmme_strm,
-					int dev_id,
-					unsigned clock_rate,
-					unsigned channel_count,
-					unsigned samples_per_frame,
-					unsigned buffer_count)
-{
-    MMRESULT mr;
-    WAVEFORMATEX pcmwf; 
-    unsigned bytes_per_frame;
-    unsigned i;
-
-    PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);
-
-    /* Check device ID */
-    if (dev_id == -1)
-	dev_id = 0;
-
-    PJ_ASSERT_RETURN(dev_id >= 0 && dev_id < (int)dev_count, PJ_EINVAL);
-
-    /*
-     * Create a wait event.
-     */
-    wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
-    if (NULL == wmme_strm->hEvent)
-	return pj_get_os_error();
-
-    /*
-     * Set up wave format structure for opening the device.
-     */
-    init_waveformatex(&pcmwf, clock_rate, channel_count);
-    bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
-
-    /*
-     * Open wave device.
-     */
-    mr = waveOutOpen(&wmme_strm->hWave.Out, dev_info[dev_id].deviceId, &pcmwf, 
-		     (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
-    if (mr != MMSYSERR_NOERROR)
-	/* TODO: This is for HRESULT/GetLastError() */
-	PJ_RETURN_OS_ERROR(mr);
-
-    /* Pause the wave out device */
-    mr = waveOutPause(wmme_strm->hWave.Out);
-    if (mr != MMSYSERR_NOERROR)
-	/* TODO: This is for HRESULT/GetLastError() */
-	PJ_RETURN_OS_ERROR(mr);
-
-    /*
-     * Create the buffers. 
-     */
-    wmme_strm->WaveHdr = pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
-    for (i = 0; i < buffer_count; ++i)
-    {
-	wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool, bytes_per_frame);
-	wmme_strm->WaveHdr[i].dwBufferLength = bytes_per_frame;
-	mr = waveOutPrepareHeader(wmme_strm->hWave.Out, 
-				  &(wmme_strm->WaveHdr[i]),
-				  sizeof(WAVEHDR));
-	if (mr != MMSYSERR_NOERROR)
-	    /* TODO: This is for HRESULT/GetLastError() */
-	    PJ_RETURN_OS_ERROR(mr); 
-	mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]), 
-			  sizeof(WAVEHDR));
-	if (mr != MMSYSERR_NOERROR)
-	    /* TODO: This is for HRESULT/GetLastError() */
-	    PJ_RETURN_OS_ERROR(mr);
-    }
-
-    wmme_strm->dwBufIdx = 0;
-    wmme_strm->dwMaxBufIdx = buffer_count;
-    wmme_strm->timestamp.u64 = 0;
-
-    /* Done setting up play device. */
-    PJ_LOG(5, (THIS_FILE, 
-	       " WaveAPI Sound player \"%s\" initialized (clock_rate=%d, "
-	       "channel_count=%d, samples_per_frame=%d (%dms))",
-	       dev_info[dev_id].info.name,
-	       clock_rate, channel_count, samples_per_frame,
-	       samples_per_frame * 1000 / clock_rate));
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Initialize Windows Multimedia recorder device
- */
-static pj_status_t init_capture_stream( pj_pool_t *pool,
-					struct wmme_stream *wmme_strm,
-					int dev_id,
-					unsigned clock_rate,
-					unsigned channel_count,
-					unsigned samples_per_frame,
-					unsigned buffer_count)
-{
-    MMRESULT mr;
-    WAVEFORMATEX pcmwf; 
-    unsigned bytes_per_frame;
-    unsigned i;
-
-    PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);
-
-    /* Check device ID */
-    if (dev_id == -1)
-	dev_id = 0;
-
-    PJ_ASSERT_RETURN(dev_id >= 0 && dev_id < (int)dev_count, PJ_EINVAL);
-
-    /*
-    * Create a wait event.
-    */
-    wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
-    if (NULL == wmme_strm->hEvent)
-	return pj_get_os_error();
-
-    /*
-     * Set up wave format structure for opening the device.
-     */
-    init_waveformatex(&pcmwf, clock_rate, channel_count);
-    bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;
-
-    /*
-     * Open wave device.
-     */
-    mr = waveInOpen(&wmme_strm->hWave.In, dev_info[dev_id].deviceId, &pcmwf, 
-		    (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
-    if (mr != MMSYSERR_NOERROR)
-	/* TODO: This is for HRESULT/GetLastError() */
-	PJ_RETURN_OS_ERROR(mr);
-
-    /*
-     * Create the buffers. 
-     */
-    wmme_strm->WaveHdr = pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
-    for (i = 0; i < buffer_count; ++i)
-    {
-	wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool, bytes_per_frame);
-	wmme_strm->WaveHdr[i].dwBufferLength = bytes_per_frame;
-	mr = waveInPrepareHeader(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]),
-							sizeof(WAVEHDR));
-	if (mr != MMSYSERR_NOERROR)
-	    /* TODO: This is for HRESULT/GetLastError() */
-	    PJ_RETURN_OS_ERROR(mr);
-	mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]), 
-			     sizeof(WAVEHDR));
-	if (mr != MMSYSERR_NOERROR)
-	    /* TODO: This is for HRESULT/GetLastError() */
-	    PJ_RETURN_OS_ERROR(mr);
-    }
-
-    wmme_strm->dwBufIdx = 0;
-    wmme_strm->dwMaxBufIdx = buffer_count;
-    wmme_strm->timestamp.u64 = 0;
-
-    /* Done setting up play device. */
-    PJ_LOG(5,(THIS_FILE, 
-	" WaveAPI Sound recorder \"%s\" initialized (clock_rate=%d, "
-	"channel_count=%d, samples_per_frame=%d (%dms))",
-	dev_info[dev_id].info.name,
-	clock_rate, channel_count, samples_per_frame,
-	samples_per_frame * 1000 / clock_rate));
-
-    return PJ_SUCCESS;
-}
-
-
-
-/*
-* WMME capture and playback thread.
-*/
-static int PJ_THREAD_FUNC wmme_dev_thread(void *arg)
-{
-    pjmedia_snd_stream *strm = arg;
-    HANDLE events[3];
-    unsigned eventCount;
-    unsigned bytes_per_frame;
-    pj_status_t status = PJ_SUCCESS;
-
-
-    eventCount = 0;
-    events[eventCount++] = strm->thread_quit_event;
-    if (strm->dir & PJMEDIA_DIR_PLAYBACK)
-	events[eventCount++] = strm->play_strm.hEvent;
-    if (strm->dir & PJMEDIA_DIR_CAPTURE)
-	events[eventCount++] = strm->rec_strm.hEvent;
-
-
-    /* Raise self priority. We don't want the audio to be distorted by
-     * system activity.
-     */
-#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
-    if (strm->dir & PJMEDIA_DIR_PLAYBACK)
-	CeSetThreadPriority(GetCurrentThread(), 153);
-    else
-	CeSetThreadPriority(GetCurrentThread(), 247);
-#else
-    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
-#endif
-
-    /* Calculate bytes per frame */
-    bytes_per_frame = strm->samples_per_frame * BYTES_PER_SAMPLE;
-
-    /*
-     * Loop while not signalled to quit, wait for event objects to be 
-     * signalled by WMME capture and play buffer.
-     */
-    while (status == PJ_SUCCESS)
-    {
-
-	DWORD rc;
-	pjmedia_dir signalled_dir;
-
-	rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
-	if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount)
-	    continue;
-
-	if (rc == WAIT_OBJECT_0)
-	    break;
-
-	if (rc == (WAIT_OBJECT_0 + 1))
-	{
-	    if (events[1] == strm->play_strm.hEvent)
-		signalled_dir = PJMEDIA_DIR_PLAYBACK;
-	    else
-		signalled_dir = PJMEDIA_DIR_CAPTURE;
-	}
-	else
-	{
-	    if (events[2] == strm->play_strm.hEvent)
-		signalled_dir = PJMEDIA_DIR_PLAYBACK;
-	    else
-		signalled_dir = PJMEDIA_DIR_CAPTURE;
-	}
-
-
-	if (signalled_dir == PJMEDIA_DIR_PLAYBACK)
-	{
-	    struct wmme_stream *wmme_strm = &strm->play_strm;
-	    MMRESULT mr = MMSYSERR_NOERROR;
-	    status = PJ_SUCCESS;
-
-	    /*
-	    * Windows Multimedia has requested us to feed some frames to
-	    * playback buffer.
-	    */
-
-	    while (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE)
-	    {
-		void* buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
-
-		PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d", 
-			  wmme_strm->dwBufIdx));
-
-		/* Get frame from application. */
-		status = (*strm->play_cb)(strm->user_data, 
-					  wmme_strm->timestamp.u32.lo,
-					  buffer,
-					  bytes_per_frame);
-
-		if (status != PJ_SUCCESS)
-		    break;
-
-		/* Write to the device. */
-		mr = waveOutWrite(wmme_strm->hWave.Out, 
-				  &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]), 
-				  sizeof(WAVEHDR));
-		if (mr != MMSYSERR_NOERROR)
-		{
-		    status = PJ_STATUS_FROM_OS(mr);
-		    break;
-		}
-
-		/* Increment position. */
-		if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
-		    wmme_strm->dwBufIdx = 0;
-		wmme_strm->timestamp.u64 += strm->samples_per_frame / 
-					    strm->channel_count;
-	    }
-	}
-	else
-	{
-	    struct wmme_stream *wmme_strm = &strm->rec_strm;
-	    MMRESULT mr = MMSYSERR_NOERROR;
-	    status = PJ_SUCCESS;
-
-	    /*
-	    * Windows Multimedia has indicated that it has some frames ready
-	    * in the capture buffer. Get as much frames as possible to
-	    * prevent overflows.
-	    */
-#if 0
-	    {
-		static DWORD tc = 0;
-		DWORD now = GetTickCount();
-		DWORD i = 0;
-		DWORD bits = 0;
-
-		if (tc == 0) tc = now;
-
-		for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i)
-		{
-		    bits = bits << 4;
-		    bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE;
-		}
-		PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, "
-			  "Flags: %6.6x\n",
-			  wmme_strm->dwBufIdx,
-			  now - tc,
-			  bits));
-		tc = now;
-	    }
-#endif
-
-	    while (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE)
-	    {
-		char* buffer = (char*)
-			       wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
-		unsigned cap_len = 
-			wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded;
-
-		/*
-		PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len, 
-			  wmme_strm->dwBufIdx));
-		*/
-
-		if (cap_len < bytes_per_frame)
-		    pj_bzero(buffer + cap_len, bytes_per_frame - cap_len);
-
-		/* Copy the audio data out of the wave buffer. */
-		pj_memcpy(strm->buffer, buffer, bytes_per_frame);
-
-		/* Re-add the buffer to the device. */
-		mr = waveInAddBuffer(wmme_strm->hWave.In, 
-				     &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]), 
-				     sizeof(WAVEHDR));
-		if (mr != MMSYSERR_NOERROR) {
-		    status = PJ_STATUS_FROM_OS(mr);
-		    break;
-		}
-
-		/* Call callback */
-		status = (*strm->rec_cb)(strm->user_data, 
-					 wmme_strm->timestamp.u32.lo, 
-					 strm->buffer, 
-					 bytes_per_frame);
-
-		if (status != PJ_SUCCESS)
-		    break;
-
-		/* Increment position. */
-		if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
-		    wmme_strm->dwBufIdx = 0;
-		wmme_strm->timestamp.u64 += strm->samples_per_frame / 
-					    strm->channel_count;
-	    }
-	}
-    }
-
-    PJ_LOG(5,(THIS_FILE, "WMME: thread stopping.."));
-    return 0;
-}
-
-
-/*
-* Init sound library.
-*/
-PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
-{
-    unsigned c;
-    int i;
-    int inputDeviceCount, outputDeviceCount, maximumPossibleDeviceCount;
-
-    if (snd_initialized)
-	return PJ_SUCCESS;
-
-    pj_bzero(&dev_info, sizeof(dev_info));
-
-    dev_count = 0;
-    pool_factory = factory;
-
-    /* Enumerate sound playback devices */
-    maximumPossibleDeviceCount = 0;
-
-    inputDeviceCount = waveInGetNumDevs();
-    if (inputDeviceCount > 0)
-	/* assume there is a WAVE_MAPPER */
-	maximumPossibleDeviceCount += inputDeviceCount + 1;
-
-    outputDeviceCount = waveOutGetNumDevs();
-    if (outputDeviceCount > 0)
-	/* assume there is a WAVE_MAPPER */
-	maximumPossibleDeviceCount += outputDeviceCount + 1;
-
-    if (maximumPossibleDeviceCount >= MAX_HARDWARE)
-    {
-	pj_assert(!"Too many hardware found");
-	PJ_LOG(3,(THIS_FILE, "Too many hardware found, "
-		  "some devices will not be listed"));
-    }
-
-    if (inputDeviceCount > 0)
-    {
-	/* -1 is the WAVE_MAPPER */
-	for (i = -1; i < inputDeviceCount && dev_count < MAX_HARDWARE; ++i)
-	{
-	    UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
-	    WAVEINCAPS wic;
-	    MMRESULT mr;
-
-	    pj_bzero(&wic, sizeof(WAVEINCAPS));
-
-	    mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS));
-
-	    if (mr == MMSYSERR_NOMEM)
-		return PJ_ENOMEM;
-
-	    if (mr != MMSYSERR_NOERROR)
-		continue;
-
-#ifdef UNICODE
-	    WideCharToMultiByte(CP_ACP, 0, wic.szPname, wcslen(wic.szPname), 
-				dev_info[dev_count].info.name, 64, NULL, NULL);
-#else
-	    strncpy(dev_info[dev_count].info.name, wic.szPname, MAXPNAMELEN);
-#endif
-	    if (uDeviceID == WAVE_MAPPER)
-		strcat(dev_info[dev_count].info.name, " - Input");
-
-	    dev_info[dev_count].info.input_count = wic.wChannels;
-	    dev_info[dev_count].info.output_count = 0;
-	    dev_info[dev_count].info.default_samples_per_sec = 44100;
-	    dev_info[dev_count].deviceId = uDeviceID;
-
-	    /* Sometimes a device can return a rediculously large number of 
-	     * channels. This happened with an SBLive card on a Windows ME box.
-	     * It also happens on Win XP!
-	     */
-	    if ((dev_info[dev_count].info.input_count < 1) || 
-		(dev_info[dev_count].info.input_count > 256))
-		dev_info[dev_count].info.input_count = 2;
-
-	    ++dev_count;
-	}
-    }
-
-    if( outputDeviceCount > 0 )
-    {
-	/* -1 is the WAVE_MAPPER */
-	for (i = -1; i < outputDeviceCount && dev_count < MAX_HARDWARE; ++i)
-	{
-	    UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
-	    WAVEOUTCAPS woc;
-	    MMRESULT mr;
-
-	    pj_bzero(&woc, sizeof(WAVEOUTCAPS));
-
-	    mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS));
-
-	    if (mr == MMSYSERR_NOMEM)
-		return PJ_ENOMEM;
-
-	    if (mr != MMSYSERR_NOERROR)
-		continue;
-
-#ifdef UNICODE
-	    WideCharToMultiByte(CP_ACP, 0, woc.szPname, wcslen(woc.szPname),
-				dev_info[dev_count].info.name, 64, NULL, NULL);
-#else
-	    strncpy(dev_info[dev_count].info.name, woc.szPname, MAXPNAMELEN);
-#endif
-	    if (uDeviceID == WAVE_MAPPER)
-		strcat(dev_info[dev_count].info.name, " - Output");
-
-	    dev_info[dev_count].info.output_count = woc.wChannels;
-	    dev_info[dev_count].info.input_count = 0;
-	    dev_info[dev_count].deviceId = uDeviceID;
-	    /* TODO: Perform a search! */
-	    dev_info[dev_count].info.default_samples_per_sec = 44100;
-
-	    /* Sometimes a device can return a rediculously large number of channels.
-	     * This happened with an SBLive card on a Windows ME box.
-	     * It also happens on Win XP!
-	     */
-	    if ((dev_info[dev_count].info.output_count < 1) || 
-		(dev_info[dev_count].info.output_count > 256))
-		dev_info[dev_count].info.output_count = 2;
-
-	    ++dev_count;
-	}
-    }
-
-    PJ_LOG(4, (THIS_FILE, "WMME initialized, found %d devices:", dev_count));
-    for (c = 0; c < dev_count; ++c)
-    {
-	PJ_LOG(4, (THIS_FILE, " dev_id %d: %s  (in=%d, out=%d)", 
-	    c,
-	    dev_info[c].info.name,
-	    dev_info[c].info.input_count,
-	    dev_info[c].info.output_count));
-    }
-    return PJ_SUCCESS;
-}
-
-/*
- * Deinitialize sound library.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
-{
-    snd_initialized = PJ_FALSE;
-    return PJ_SUCCESS;
-}
-
-/*
- * Get device count.
- */
-PJ_DEF(int) pjmedia_snd_get_dev_count(void)
-{
-    return dev_count;
-}
-
-/*
- * Get device info.
- */
-PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
-{
-    if (index == (unsigned)-1) 
-	index = 0;
-
-    PJ_ASSERT_RETURN(index < dev_count, NULL);
-
-    return &dev_info[index].info;
-}
-
-
-/*
- * Open stream.
- */
-static pj_status_t open_stream(pjmedia_dir dir,
-			       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 *strm;
-    pj_status_t status;
-
-
-    /* Make sure sound subsystem has been initialized with
-     * pjmedia_snd_init()
-     */
-    PJ_ASSERT_RETURN(pool_factory != NULL, PJ_EINVALIDOP);
-
-
-    /* Can only support 16bits per sample */
-    PJ_ASSERT_RETURN(bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
-
-    /* Create and Initialize stream descriptor */
-    pool = pj_pool_create(pool_factory, "wmme-dev", 1000, 1000, NULL);
-    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
-
-    strm = pj_pool_zalloc(pool, sizeof(pjmedia_snd_stream));
-    strm->dir = dir;
-    strm->play_id = play_id;
-    strm->rec_id = rec_id;
-    strm->pool = pool;
-    strm->rec_cb = rec_cb;
-    strm->play_cb = play_cb;
-    strm->user_data = user_data;
-    strm->clock_rate = clock_rate;
-    strm->samples_per_frame = samples_per_frame;
-    strm->bits_per_sample = bits_per_sample;
-    strm->channel_count = channel_count;
-    strm->buffer = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE);
-    if (!strm->buffer)
-    {
-	pj_pool_release(pool);
-	return PJ_ENOMEM;
-    }
-
-    /* Create player stream */
-    if (dir & PJMEDIA_DIR_PLAYBACK)
-    {
-	unsigned buf_count;
-
-	buf_count = snd_output_latency * clock_rate * channel_count / 
-		    samples_per_frame / 1000;
-
-	status = init_player_stream(strm->pool,
-				    &strm->play_strm,
-				    play_id,
-				    clock_rate,
-				    channel_count,
-				    samples_per_frame,
-				    buf_count);
-
-	if (status != PJ_SUCCESS)
-	{
-	    pjmedia_snd_stream_close(strm);
-	    return status;
-	}
-    }
-
-    /* Create capture stream */
-    if (dir & PJMEDIA_DIR_CAPTURE)
-    {
-	unsigned buf_count;
-
-	buf_count = snd_input_latency * clock_rate * channel_count / 
-		    samples_per_frame / 1000;
-
-	status = init_capture_stream(strm->pool,
-				     &strm->rec_strm,
-				     rec_id,
-				     clock_rate,
-				     channel_count,
-				     samples_per_frame,
-				     buf_count);
-
-	if (status != PJ_SUCCESS)
-	{
-	    pjmedia_snd_stream_close(strm);
-	    return status;
-	}
-    }
-
-    /* Create the stop event */
-    strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
-    if (strm->thread_quit_event == NULL)
-	return pj_get_os_error();
-
-    /* Create and start the thread */
-    status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0, 
-			      &strm->thread);
-    if (status != PJ_SUCCESS)
-    {
-	pjmedia_snd_stream_close(strm);
-	return status;
-    }
-
-    *p_snd_strm = strm;
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Open stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_open_rec(int index,
-					 unsigned clock_rate,
-					 unsigned channel_count,
-					 unsigned samples_per_frame,
-					 unsigned bits_per_sample,
-					 pjmedia_snd_rec_cb rec_cb,
-					 void *user_data,
-					 pjmedia_snd_stream **p_snd_strm)
-{
-    PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
-
-    return open_stream( PJMEDIA_DIR_CAPTURE,
-			index,
-			-1,
-			clock_rate,
-			channel_count,
-			samples_per_frame,
-			bits_per_sample,
-			rec_cb,
-			NULL,
-			user_data,
-			p_snd_strm);
-}
-
-PJ_DEF(pj_status_t) pjmedia_snd_open_player(int index,
-					    unsigned clock_rate,
-					    unsigned channel_count,
-					    unsigned samples_per_frame,
-					    unsigned bits_per_sample,
-					    pjmedia_snd_play_cb play_cb,
-					    void *user_data,
-					    pjmedia_snd_stream **p_snd_strm)
-{
-    PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
-
-    return open_stream( PJMEDIA_DIR_PLAYBACK,
-			-1,
-			index,
-			clock_rate,
-			channel_count,
-			samples_per_frame,
-			bits_per_sample,
-			NULL,
-			play_cb,
-			user_data,
-			p_snd_strm);
-}
-
-/*
- * 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_ASSERT_RETURN(rec_cb && play_cb && p_snd_strm, PJ_EINVAL);
-
-    return open_stream( PJMEDIA_DIR_CAPTURE_PLAYBACK,
-			rec_id,
-			play_id,
-			clock_rate,
-			channel_count,
-			samples_per_frame,
-			bits_per_sample,
-			rec_cb,
-			play_cb,
-			user_data,
-			p_snd_strm);
-}
-
-/*
- * Get stream info.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, 
-						pjmedia_snd_stream_info *pi)
-{
-    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
-
-    pj_bzero(pi, sizeof(*pi));
-    pi->dir = strm->dir;
-    pi->play_id = strm->play_id;
-    pi->rec_id = strm->rec_id;
-    pi->clock_rate = strm->clock_rate;
-    pi->channel_count = strm->channel_count;
-    pi->samples_per_frame = strm->samples_per_frame;
-    pi->bits_per_sample = strm->bits_per_sample;
-    pi->rec_latency = snd_input_latency * strm->clock_rate * 
-		      strm->channel_count / 1000;
-    pi->play_latency = snd_output_latency * strm->clock_rate * 
-		       strm->channel_count / 1000;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
-* Start stream.
-*/
-PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
-{
-    MMRESULT mr;
-
-    PJ_UNUSED_ARG(stream);
-
-    if (stream->play_strm.hWave.Out != NULL)
-    {
-	mr = waveOutRestart(stream->play_strm.hWave.Out);
-	if (mr != MMSYSERR_NOERROR)
-	    /* TODO: This macro is supposed to be used for HRESULT, fix. */
-	    PJ_RETURN_OS_ERROR(mr);
-	PJ_LOG(5,(THIS_FILE, "WMME playback stream started"));
-    }
-
-    if (stream->rec_strm.hWave.In != NULL)
-    {
-	mr = waveInStart(stream->rec_strm.hWave.In);
-	if (mr != MMSYSERR_NOERROR)
-	    /* TODO: This macro is supposed to be used for HRESULT, fix. */
-	    PJ_RETURN_OS_ERROR(mr);
-	PJ_LOG(5,(THIS_FILE, "WMME capture stream started"));
-    }
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Stop stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
-{
-    MMRESULT mr;
-
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
-    if (stream->play_strm.hWave.Out != NULL)
-    {
-	mr = waveOutPause(stream->play_strm.hWave.Out);
-	if (mr != MMSYSERR_NOERROR)
-	    /* TODO: This macro is supposed to be used for HRESULT, fix. */
-	    PJ_RETURN_OS_ERROR(mr);
-	PJ_LOG(5,(THIS_FILE, "Stopped WMME playback stream"));
-    }
-
-    if (stream->rec_strm.hWave.In != NULL)
-    {
-	mr = waveInStop(stream->rec_strm.hWave.In);
-	if (mr != MMSYSERR_NOERROR)
-	    /* TODO: This macro is supposed to be used for HRESULT, fix. */
-	    PJ_RETURN_OS_ERROR(mr);
-	PJ_LOG(5,(THIS_FILE, "Stopped WMME capture stream"));
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Destroy stream.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
-{
-    unsigned i;
-
-    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-
-    pjmedia_snd_stream_stop(stream);
-
-    if (stream->thread)
-    {
-	SetEvent(stream->thread_quit_event);
-	pj_thread_join(stream->thread);
-	pj_thread_destroy(stream->thread);
-	stream->thread = NULL;
-    }
-
-    /* Unprepare the headers and close the play device */
-    if (stream->play_strm.hWave.Out)
-    {
-	waveOutReset(stream->play_strm.hWave.Out);
-	for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
-	    waveOutUnprepareHeader(stream->play_strm.hWave.Out, 
-				   &(stream->play_strm.WaveHdr[i]),
-				   sizeof(WAVEHDR));
-	waveOutClose(stream->play_strm.hWave.Out);
-	stream->play_strm.hWave.Out = NULL;
-    }
-
-    /* Close the play event */
-    if (stream->play_strm.hEvent)
-    {
-	CloseHandle(stream->play_strm.hEvent);
-	stream->play_strm.hEvent = NULL;
-    }
-
-    /* Unprepare the headers and close the record device */
-    if (stream->rec_strm.hWave.In)
-    {
-	waveInReset(stream->rec_strm.hWave.In);
-	for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
-	    waveInUnprepareHeader(stream->rec_strm.hWave.In, 
-				  &(stream->rec_strm.WaveHdr[i]),
-				  sizeof(WAVEHDR));
-	waveInClose(stream->rec_strm.hWave.In);
-	stream->rec_strm.hWave.In = NULL;
-    }
-
-    /* Close the record event */
-    if (stream->rec_strm.hEvent)
-    {
-	CloseHandle(stream->rec_strm.hEvent);
-	stream->rec_strm.hEvent = NULL;
-    }
-
-    pj_pool_release(stream->pool);
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Set sound latency.
- */
-PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, 
-					    unsigned output_latency)
-{
-    snd_input_latency  = (input_latency == 0)? 
-			  PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency;
-    snd_output_latency = (output_latency == 0)? 
-			 PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency;
-
-    return PJ_SUCCESS;
-}
-
-#endif	/* PJMEDIA_SOUND_IMPLEMENTATION */
-
Index: pjproject/trunk/pjproject-vs8.sln
===================================================================
--- pjproject/trunk/pjproject-vs8.sln	(revision 2505)
+++ pjproject/trunk/pjproject-vs8.sln	(revision 2506)
@@ -13,6 +13,6 @@
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjlib_util_test", "pjlib-util\build\pjlib_util_test.vcproj", "{ED02BE13-8297-4770-8097-27DC2CCABF9A}"
 	ProjectSection(ProjectDependencies) = postProject
-		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
-		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
 	EndProjectSection
 EndProject
@@ -29,114 +29,119 @@
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjsua", "pjsip-apps\build\pjsua.vcproj", "{8310649E-A25E-4AF0-91E8-9E3CC659BB89}"
 	ProjectSection(ProjectDependencies) = postProject
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA} = {4281CA5E-1D48-45D4-A991-2718A454B4BA}
+		{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
+		{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+		{4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
+		{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
+		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+		{6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
+		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+		{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
+		{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
+		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+		{4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
+		{B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
+		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+		{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
+		{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
 		{E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
+	EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjsua_lib", "pjsip\build\pjsua_lib.vcproj", "{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample_debug", "pjsip-apps\build\sample_debug.vcproj", "{A0F1AA62-0F6F-420D-B09A-AC04B6862821}"
+	ProjectSection(ProjectDependencies) = postProject
+		{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+		{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
+		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+		{6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
+		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+		{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
+		{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
+		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+		{4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
+		{B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
+		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+		{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
 		{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
+		{E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA} = {4281CA5E-1D48-45D4-A991-2718A454B4BA}
+		{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
+	EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "samples", "pjsip-apps\build\samples.vcproj", "{E378A1FC-0C9C-4462-860F-7E60BC1BF84E}"
+	ProjectSection(ProjectDependencies) = postProject
+		{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
+		{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+		{4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
+		{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
+		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+		{6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
+		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+		{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
+		{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
+		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+		{4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
+		{B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
+		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
 		{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
-		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+		{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
+		{E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
+	EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_pjsip", "pjsip\build\test_pjsip.vcproj", "{B3F7D4E9-702F-4EB4-ADA8-098D0A83D770}"
+	ProjectSection(ProjectDependencies) = postProject
+		{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
+		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
+		{4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
 		{B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
-		{4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
+		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+		{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
+	EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjnath", "pjnath\build\pjnath.vcproj", "{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libgsmcodec", "third_party\build\gsm\libgsmcodec.vcproj", "{E53AA5FF-B737-40AA-BD13-387EFA99023D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libilbccodec", "third_party\build\ilbc\libilbccodec.vcproj", "{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libportaudio", "third_party\build\portaudio\libportaudio.vcproj", "{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libresample", "third_party\build\resample\libresample.vcproj", "{6794B975-4E84-4F49-B2DC-C31F2224E03E}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspeex", "third_party\build\speex\libspeex.vcproj", "{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libresample_dll", "third_party\build\resample\libresample_dll.vcproj", "{C48EAAF5-F69E-410B-9CE4-23AB41B00E2A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmilenage", "third_party\build\milenage\libmilenage.vcproj", "{4BF51C21-5A30-423B-82FE-1ED410E5769D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsrtp", "third_party\build\srtp\libsrtp.vcproj", "{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjnath_test", "pjnath\build\pjnath_test.vcproj", "{553C094C-F581-4A80-9540-D5D7B398A2C6}"
+	ProjectSection(ProjectDependencies) = postProject
+		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+		{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
+	EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjmedia_test", "pjmedia\build\pjmedia_test.vcproj", "{21C0CECF-69DD-4F70-BC2B-9B4DE7F15277}"
+	ProjectSection(ProjectDependencies) = postProject
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA} = {4281CA5E-1D48-45D4-A991-2718A454B4BA}
+		{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
+		{4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
+		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
+		{6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
+		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
+		{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
+		{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
 		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
-		{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
-		{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
-		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
-		{6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
-		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
-		{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
-		{4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
-		{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
-		{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
-	EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjsua_lib", "pjsip\build\pjsua_lib.vcproj", "{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample_debug", "pjsip-apps\build\sample_debug.vcproj", "{A0F1AA62-0F6F-420D-B09A-AC04B6862821}"
-	ProjectSection(ProjectDependencies) = postProject
+		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
+		{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
 		{E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
-		{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
-		{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
-		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
-		{B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
-		{4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
-		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
-		{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
-		{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
-		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
-		{6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
-		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
-		{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
-		{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
-		{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
-	EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "samples", "pjsip-apps\build\samples.vcproj", "{E378A1FC-0C9C-4462-860F-7E60BC1BF84E}"
-	ProjectSection(ProjectDependencies) = postProject
-		{E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
-		{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
-		{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
-		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
-		{B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
-		{4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
-		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
-		{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
-		{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
-		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
-		{6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
-		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
-		{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
-		{4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
-		{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
-		{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
-	EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_pjsip", "pjsip\build\test_pjsip.vcproj", "{B3F7D4E9-702F-4EB4-ADA8-098D0A83D770}"
-	ProjectSection(ProjectDependencies) = postProject
-		{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
-		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
-		{B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
-		{4B5945CD-0CB3-49AA-A7FF-7612D93F82C0} = {4B5945CD-0CB3-49AA-A7FF-7612D93F82C0}
-		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
-		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
-		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
-		{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
-	EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjnath", "pjnath\build\pjnath.vcproj", "{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libgsmcodec", "third_party\build\gsm\libgsmcodec.vcproj", "{E53AA5FF-B737-40AA-BD13-387EFA99023D}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libilbccodec", "third_party\build\ilbc\libilbccodec.vcproj", "{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libportaudio", "third_party\build\portaudio\libportaudio.vcproj", "{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libresample", "third_party\build\resample\libresample.vcproj", "{6794B975-4E84-4F49-B2DC-C31F2224E03E}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspeex", "third_party\build\speex\libspeex.vcproj", "{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libresample_dll", "third_party\build\resample\libresample_dll.vcproj", "{C48EAAF5-F69E-410B-9CE4-23AB41B00E2A}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmilenage", "third_party\build\milenage\libmilenage.vcproj", "{4BF51C21-5A30-423B-82FE-1ED410E5769D}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsrtp", "third_party\build\srtp\libsrtp.vcproj", "{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjnath_test", "pjnath\build\pjnath_test.vcproj", "{553C094C-F581-4A80-9540-D5D7B398A2C6}"
-	ProjectSection(ProjectDependencies) = postProject
-		{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4} = {A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}
-		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
-		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
-	EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjmedia_test", "pjmedia\build\pjmedia_test.vcproj", "{21C0CECF-69DD-4F70-BC2B-9B4DE7F15277}"
-	ProjectSection(ProjectDependencies) = postProject
-		{E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
-		{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
-		{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
-		{855DC8C0-D3E9-4A2E-AE47-116605A7BC9B} = {855DC8C0-D3E9-4A2E-AE47-116605A7BC9B}
-		{4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9} = {4B059DBA-CD9C-4D0F-BE8C-FFB4EFD498E9}
-		{3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA} = {3CF9FFA9-8387-4635-9D1B-E7944CBEFEAA}
-		{7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65} = {7FDE3880-A4AB-49E3-B439-EBEF0A0C7A65}
-		{6794B975-4E84-4F49-B2DC-C31F2224E03E} = {6794B975-4E84-4F49-B2DC-C31F2224E03E}
-		{FE07F272-AE7F-4549-9E9F-EF9B80CB1693} = {FE07F272-AE7F-4549-9E9F-EF9B80CB1693}
-		{4BF51C21-5A30-423B-82FE-1ED410E5769D} = {4BF51C21-5A30-423B-82FE-1ED410E5769D}
-		{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
-	EndProjectSection
+	EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjmedia_audiodev", "pjmedia\build\pjmedia_audiodev.vcproj", "{4281CA5E-1D48-45D4-A991-2718A454B4BA}"
 EndProject
 Global
@@ -386,4 +391,10 @@
 		{21C0CECF-69DD-4F70-BC2B-9B4DE7F15277}.Release|Windows Mobile 6 Standard SDK (ARMV4I).Build.0 = Release|Windows Mobile 6 Standard SDK (ARMV4I)
 		{21C0CECF-69DD-4F70-BC2B-9B4DE7F15277}.Release|Windows Mobile 6 Standard SDK (ARMV4I).Deploy.0 = Release|Windows Mobile 6 Standard SDK (ARMV4I)
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA}.Debug|Win32.ActiveCfg = Debug|Win32
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA}.Debug|Win32.Build.0 = Debug|Win32
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA}.Debug|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Debug|Win32
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA}.Release|Win32.ActiveCfg = Release|Win32
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA}.Release|Win32.Build.0 = Release|Win32
+		{4281CA5E-1D48-45D4-A991-2718A454B4BA}.Release|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
Index: pjproject/trunk/pjproject.dsw
===================================================================
--- pjproject/trunk/pjproject.dsw	(revision 2505)
+++ pjproject/trunk/pjproject.dsw	(revision 2506)
@@ -4,113 +4,113 @@
 ###############################################################################
 
-Project: "libgsmcodec"=.\THIRD_PARTY\BUILD\GSM\libgsmcodec.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "libilbccodec"=.\THIRD_PARTY\BUILD\ILBC\libilbccodec.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "libmilenage"=.\third_party\build\milenage\libmilenage.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "libportaudio"=.\THIRD_PARTY\BUILD\PORTAUDIO\libportaudio.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "libresample"=.\THIRD_PARTY\BUILD\RESAMPLE\libresample.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "libresample_dll"=.\THIRD_PARTY\BUILD\RESAMPLE\libresample_dll.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "libspeex"=.\third_party\build\speex\libspeex.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "libsrtp"=.\third_party\build\srtp\libsrtp.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "pjlib"=.\pjlib\build\pjlib.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "pjlib_test"=.\pjlib\build\pjlib_test.dsp - Package Owner=<4>
+Project: "libgsmcodec"=".\THIRD_PARTY\BUILD\GSM\libgsmcodec.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "libilbccodec"=".\THIRD_PARTY\BUILD\ILBC\libilbccodec.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "libmilenage"=".\third_party\build\milenage\libmilenage.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "libportaudio"=".\THIRD_PARTY\BUILD\PORTAUDIO\libportaudio.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "libresample"=".\THIRD_PARTY\BUILD\RESAMPLE\libresample.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "libresample_dll"=".\THIRD_PARTY\BUILD\RESAMPLE\libresample_dll.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "libspeex"=".\third_party\build\speex\libspeex.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "libsrtp"=".\third_party\build\srtp\libsrtp.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjlib"=".\pjlib\build\pjlib.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjlib_test"=".\pjlib\build\pjlib_test.dsp" - Package Owner=<4>
 
 Package=<5>
@@ -157,29 +157,41 @@
 ###############################################################################
 
-Project: "pjmedia"=.\pjmedia\build\pjmedia.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "pjmedia_codec"=.\pjmedia\build\pjmedia_codec.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "pjmedia_test"=.\pjmedia\build\pjmedia_test.dsp - Package Owner=<4>
+Project: "pjmedia"=".\pjmedia\build\pjmedia.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjmedia_audiodev"=".\pjmedia\build\pjmedia_audiodev.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjmedia_codec"=".\pjmedia\build\pjmedia_codec.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjmedia_test"=".\pjmedia\build\pjmedia_test.dsp" - Package Owner=<4>
 
 Package=<5>
@@ -222,66 +234,69 @@
     Project_Dep_Name pjnath
     End Project Dependency
-}}}
-
-###############################################################################
-
-Project: "pjnath"=.\pjnath\build\pjnath.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "pjnath_test"=.\pjnath\build\pjnath_test.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-    Begin Project Dependency
-    Project_Dep_Name pjlib
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjlib_util
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjnath
-    End Project Dependency
-}}}
-
-###############################################################################
-
-Project: "pjsip_core"=.\pjsip\build\pjsip_core.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "pjsip_simple"=.\pjsip\build\pjsip_simple.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "pjsip_ua"=.\pjsip\build\pjsip_ua.dsp - Package Owner=<4>
+    Begin Project Dependency
+    Project_Dep_Name pjmedia_audiodev
+    End Project Dependency
+}}}
+
+###############################################################################
+
+Project: "pjnath"=".\pjnath\build\pjnath.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjnath_test"=".\pjnath\build\pjnath_test.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+    Begin Project Dependency
+    Project_Dep_Name pjlib
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjlib_util
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjnath
+    End Project Dependency
+}}}
+
+###############################################################################
+
+Project: "pjsip_core"=".\pjsip\build\pjsip_core.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjsip_simple"=".\pjsip\build\pjsip_simple.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjsip_ua"=".\pjsip\build\pjsip_ua.dsp" - Package Owner=<4>
 
 Package=<5>
@@ -351,42 +366,45 @@
     Project_Dep_Name libsrtp
     End Project Dependency
-}}}
-
-###############################################################################
-
-Project: "pjsua_lib"=.\pjsip\build\pjsua_lib.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Project: "pjturn_client"=.\pjnath\build\pjturn_client.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-    Begin Project Dependency
-    Project_Dep_Name pjlib
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjlib_util
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjnath
-    End Project Dependency
-}}}
-
-###############################################################################
-
-Project: "pjturn_srv"=.\pjnath\build\pjturn_srv.dsp - Package Owner=<4>
+    Begin Project Dependency
+    Project_Dep_Name pjmedia_audiodev
+    End Project Dependency
+}}}
+
+###############################################################################
+
+Project: "pjsua_lib"=".\pjsip\build\pjsua_lib.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjturn_client"=".\pjnath\build\pjturn_client.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+    Begin Project Dependency
+    Project_Dep_Name pjlib
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjlib_util
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjnath
+    End Project Dependency
+}}}
+
+###############################################################################
+
+Project: "pjturn_srv"=".\pjnath\build\pjturn_srv.dsp" - Package Owner=<4>
 
 Package=<5>
@@ -439,4 +457,61 @@
     End Project Dependency
     Begin Project Dependency
+    Project_Dep_Name libgsmcodec
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name libilbccodec
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name libportaudio
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name libresample
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name libspeex
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjnath
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name libsrtp
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjmedia_audiodev
+    End Project Dependency
+}}}
+
+###############################################################################
+
+Project: "samples"=".\pjsip-apps\build\samples.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+    Begin Project Dependency
+    Project_Dep_Name pjlib
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjlib_util
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjmedia
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjmedia_codec
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjsip_core
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjsip_simple
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjsip_ua
+    End Project Dependency
+    Begin Project Dependency
     Project_Dep_Name pjsua_lib
     End Project Dependency
@@ -462,9 +537,12 @@
     Project_Dep_Name libsrtp
     End Project Dependency
-}}}
-
-###############################################################################
-
-Project: "samples"=".\pjsip-apps\build\samples.dsp" - Package Owner=<4>
+    Begin Project Dependency
+    Project_Dep_Name pjmedia_audiodev
+    End Project Dependency
+}}}
+
+###############################################################################
+
+Project: "test_pjsip"=".\pjsip\build\test_pjsip.dsp" - Package Owner=<4>
 
 Package=<5>
@@ -498,61 +576,4 @@
     Project_Dep_Name pjsua_lib
     End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name libgsmcodec
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name libilbccodec
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name libportaudio
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name libresample
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name libspeex
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjnath
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name libsrtp
-    End Project Dependency
-}}}
-
-###############################################################################
-
-Project: "test_pjsip"=.\pjsip\build\test_pjsip.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-    Begin Project Dependency
-    Project_Dep_Name pjlib
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjlib_util
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjmedia
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjmedia_codec
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjsip_core
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjsip_simple
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjsip_ua
-    End Project Dependency
-    Begin Project Dependency
-    Project_Dep_Name pjsua_lib
-    End Project Dependency
 }}}
 
Index: pjproject/trunk/pjsip-apps/build/Makefile
===================================================================
--- pjproject/trunk/pjsip-apps/build/Makefile	(revision 2505)
+++ pjproject/trunk/pjsip-apps/build/Makefile	(revision 2506)
@@ -9,4 +9,5 @@
 PJNATH_LIB:=../../pjnath/lib/libpjnath-$(TARGET_NAME)$(LIBEXT)
 PJMEDIA_LIB:=../../pjmedia/lib/libpjmedia-$(TARGET_NAME)$(LIBEXT)
+PJMEDIA_AUDIODEV_LIB:=../../pjmedia/lib/libpjmedia-audiodev-$(TARGET_NAME)$(LIBEXT)
 PJMEDIA_CODEC_LIB:=../../pjmedia/lib/libpjmedia-codec-$(TARGET_NAME)$(LIBEXT)
 PJSIP_LIB:=../../pjsip/lib/libpjsip-$(TARGET_NAME)$(LIBEXT)
Index: pjproject/trunk/pjsip-apps/build/Samples-vc.mak
===================================================================
--- pjproject/trunk/pjsip-apps/build/Samples-vc.mak	(revision 2505)
+++ pjproject/trunk/pjsip-apps/build/Samples-vc.mak	(revision 2506)
@@ -14,4 +14,5 @@
 PJMEDIA_LIB = ..\..\pjmedia\lib\pjmedia-$(TARGET)$(LIBEXT)
 PJMEDIA_CODEC_LIB = ..\..\pjmedia\lib\pjmedia-codec-$(TARGET)$(LIBEXT)
+PJMEDIA_AUDIODEV_LIB = ..\..\pjmedia\lib\pjmedia-audiodev-$(TARGET)$(LIBEXT)
 PJSIP_LIB = ..\..\pjsip\lib\pjsip-core-$(TARGET)$(LIBEXT)
 PJSIP_UA_LIB = ..\..\pjsip\lib\pjsip-ua-$(TARGET)$(LIBEXT)
@@ -30,6 +31,6 @@
 
 LIBS = $(PJSUA_LIB_LIB) $(PJSIP_UA_LIB) $(PJSIP_SIMPLE_LIB) \
-	  $(PJSIP_LIB) $(PJMEDIA_CODEC_LIB) $(PJMEDIA_LIB) $(PJNATH_LIB) \
-	  $(PJLIB_UTIL_LIB) $(PJLIB_LIB) \
+	  $(PJSIP_LIB) $(PJMEDIA_CODEC_LIB) $(PJMEDIA_AUDIODEV_LIB) \
+	  $(PJMEDIA_LIB) $(PJNATH_LIB) $(PJLIB_UTIL_LIB) $(PJLIB_LIB) \
 	  $(THIRD_PARTY_LIBS)
 
@@ -50,5 +51,6 @@
 
 
-SAMPLES = $(BINDIR)\confsample.exe \
+SAMPLES = $(BINDIR)\auddemo.exe \
+	  $(BINDIR)\confsample.exe \
 	  $(BINDIR)\confbench.exe \
 	  $(BINDIR)\encdec.exe \
@@ -66,6 +68,4 @@
 	  $(BINDIR)\siprtp.exe \
 	  $(BINDIR)\sipstateless.exe \
-	  $(BINDIR)\sndinfo.exe \
-	  $(BINDIR)\sndtest.exe \
 	  $(BINDIR)\stateful_proxy.exe \
 	  $(BINDIR)\stateless_proxy.exe \
Index: pjproject/trunk/pjsip-apps/build/Samples.mak
===================================================================
--- pjproject/trunk/pjsip-apps/build/Samples.mak	(revision 2505)
+++ pjproject/trunk/pjsip-apps/build/Samples.mak	(revision 2506)
@@ -14,5 +14,6 @@
 BINDIR := ../bin/samples
 
-SAMPLES := confsample \
+SAMPLES := auddemo \
+	   confsample \
 	   encdec \
 	   latency \
@@ -29,6 +30,4 @@
 	   siprtp \
 	   sipstateless \
-	   sndinfo \
-	   sndtest \
 	   stateful_proxy \
 	   stateless_proxy \
Index: pjproject/trunk/pjsip-apps/build/sample_debug.dsp
===================================================================
--- pjproject/trunk/pjsip-apps/build/sample_debug.dsp	(revision 2505)
+++ pjproject/trunk/pjsip-apps/build/sample_debug.dsp	(revision 2506)
@@ -91,11 +91,4 @@
 
 SOURCE=..\src\samples\debug.c
-
-!IF  "$(CFG)" == "sample_debug - Win32 Release"
-
-!ELSEIF  "$(CFG)" == "sample_debug - Win32 Debug"
-
-!ENDIF 
-
 # End Source File
 # End Group
Index: pjproject/trunk/pjsip-apps/build/samples.dsp
===================================================================
--- pjproject/trunk/pjsip-apps/build/samples.dsp	(revision 2505)
+++ pjproject/trunk/pjsip-apps/build/samples.dsp	(revision 2506)
@@ -91,4 +91,8 @@
 # Begin Source File
 
+SOURCE=..\src\samples\auddemo.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\samples\confbench.c
 # End Source File
Index: pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 2505)
+++ pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 2506)
@@ -4159,4 +4159,10 @@
     app_config.cfg.cb.on_nat_detect = &on_nat_detect;
 
+    /* Set sound device latency */
+    if (app_config.capture_lat > 0)
+	app_config.media_cfg.snd_rec_latency = app_config.capture_lat;
+    if (app_config.playback_lat)
+	app_config.media_cfg.snd_play_latency = app_config.playback_lat;
+
     /* Initialize pjsua */
     status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
@@ -4423,7 +4429,4 @@
     if (status != PJ_SUCCESS)
 	goto on_error;
-
-    /* Set sound device latency */
-    pjmedia_snd_set_latency(app_config.capture_lat, app_config.playback_lat);
 
     /* Use null sound device? */
Index: pjproject/trunk/pjsip-apps/src/samples/auddemo.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/auddemo.c	(revision 2506)
+++ pjproject/trunk/pjsip-apps/src/samples/auddemo.c	(revision 2506)
@@ -0,0 +1,544 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * 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 
+ */
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-audiodev/audiotest.h>
+#include <pjmedia.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+
+#define THIS_FILE	"auddemo.c"
+#define MAX_DEVICES	64
+#define WAV_FILE	"auddemo.wav"
+
+
+static unsigned dev_count;
+
+static void app_perror(const char *title, pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));	
+    printf( "%s: %s (err=%d)\n",
+	    title, errmsg, status);
+}
+
+static void list_devices(void)
+{
+    unsigned i;
+    pj_status_t status;
+    
+    dev_count = pjmedia_aud_dev_count();
+    if (dev_count == 0) {
+	PJ_LOG(3,(THIS_FILE, "No devices found"));
+	return;
+    }
+
+    PJ_LOG(3,(THIS_FILE, "Found %d devices:", dev_count));
+
+    for (i=0; i<dev_count; ++i) {
+	pjmedia_aud_dev_info info;
+
+	status = pjmedia_aud_dev_get_info(i, &info);
+	if (status != PJ_SUCCESS)
+	    continue;
+
+	PJ_LOG(3,(THIS_FILE," %2d: %s [%s] (%d/%d)",
+	          i, info.driver, info.name, info.input_count, info.output_count));
+    }
+}
+
+static const char *decode_caps(unsigned caps)
+{
+    static char text[200];
+    unsigned i;
+
+    text[0] = '\0';
+
+    for (i=0; i<31; ++i) {
+	if ((1 << i) & caps) {
+	    const char *capname;
+	    capname = pjmedia_aud_dev_cap_name((pjmedia_aud_dev_cap)(1 << i), 
+					       NULL);
+	    strcat(text, capname);
+	    strcat(text, " ");
+	}
+    }
+
+    return text;
+}
+
+static void show_dev_info(unsigned index)
+{
+#define H   "%-20s"
+    pjmedia_aud_dev_info info;
+    char formats[200];
+    pj_status_t status;
+
+    if (index >= dev_count) {
+	PJ_LOG(1,(THIS_FILE, "Error: invalid index %u", index));
+	return;
+    }
+
+    status = pjmedia_aud_dev_get_info(index, &info);
+    if (status != PJ_SUCCESS) {
+	app_perror("pjmedia_aud_dev_get_info() error", status);
+	return;
+    }
+
+    PJ_LOG(3, (THIS_FILE, "Device at index %u:", index));
+    PJ_LOG(3, (THIS_FILE, "-------------------------"));
+
+    PJ_LOG(3, (THIS_FILE, H": %u (0x%x)", "ID", index, index));
+    PJ_LOG(3, (THIS_FILE, H": %s", "Name", info.name));
+    PJ_LOG(3, (THIS_FILE, H": %s", "Driver", info.driver));
+    PJ_LOG(3, (THIS_FILE, H": %u", "Input channels", info.input_count));
+    PJ_LOG(3, (THIS_FILE, H": %u", "Output channels", info.output_count));
+    PJ_LOG(3, (THIS_FILE, H": %s", "Capabilities", decode_caps(info.caps)));
+
+    formats[0] = '\0';
+    if (info.caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
+	unsigned i;
+
+	for (i=0; i<info.ext_fmt_cnt; ++i) {
+	    char bitrate[32];
+
+	    switch (info.ext_fmt[i].id) {
+	    case PJMEDIA_FORMAT_L16:
+		strcat(formats, "L16/");
+		break;
+	    case PJMEDIA_FORMAT_PCMA:
+		strcat(formats, "PCMA/");
+		break;
+	    case PJMEDIA_FORMAT_PCMU:
+		strcat(formats, "PCMU/");
+		break;
+	    case PJMEDIA_FORMAT_AMR:
+		strcat(formats, "AMR/");
+		break;
+	    case PJMEDIA_FORMAT_G729:
+		strcat(formats, "G729/");
+		break;
+	    case PJMEDIA_FORMAT_ILBC:
+		strcat(formats, "ILBC/");
+		break;
+	    default:
+		strcat(formats, "unknown/");
+		break;
+	    }
+	    sprintf(bitrate, "%u", info.ext_fmt[i].bitrate);
+	    strcat(formats, bitrate);
+	    strcat(formats, " ");
+	}
+    }
+    PJ_LOG(3, (THIS_FILE, H": %s", "Extended formats", formats));
+
+#undef H
+}
+
+static void test_device(pjmedia_dir dir, unsigned rec_id, unsigned play_id, 
+			unsigned clock_rate, unsigned ptime, 
+			unsigned chnum)
+{
+    pjmedia_aud_param param;
+    pjmedia_aud_test_results result;
+    pj_status_t status;
+
+    if (dir & PJMEDIA_DIR_CAPTURE) {
+	status = pjmedia_aud_dev_default_param(rec_id, &param);
+    } else {
+	status = pjmedia_aud_dev_default_param(play_id, &param);
+    }
+
+    if (status != PJ_SUCCESS) {
+	app_perror("pjmedia_aud_dev_default_param()", status);
+	return;
+    }
+
+    param.dir = dir;
+    param.rec_id = rec_id;
+    param.play_id = play_id;
+    param.clock_rate = clock_rate;
+    param.channel_count = chnum;
+    param.samples_per_frame = clock_rate * chnum * ptime / 1000;
+
+    PJ_LOG(3,(THIS_FILE, "Performing test.."));
+
+    status = pjmedia_aud_test(&param, &result);
+    if (status != PJ_SUCCESS) {
+	app_perror("Test has completed with error", status);
+	return;
+    }
+
+    PJ_LOG(3,(THIS_FILE, "Done. Result:"));
+
+    if (dir & PJMEDIA_DIR_CAPTURE) {
+	if (result.rec.frame_cnt==0) {
+	    PJ_LOG(1,(THIS_FILE, "Error: no frames captured!"));
+	} else {
+	    PJ_LOG(3,(THIS_FILE, "  %-20s: interval (min/max/avg/dev)=%u/%u/%u/%u, burst=%u",
+		      "Recording result",
+		      result.rec.min_interval,
+		      result.rec.max_interval,
+		      result.rec.avg_interval,
+		      result.rec.dev_interval,
+		      result.rec.max_burst));
+	}
+    }
+
+    if (dir & PJMEDIA_DIR_PLAYBACK) {
+	if (result.play.frame_cnt==0) {
+	    PJ_LOG(1,(THIS_FILE, "Error: no playback!"));
+	} else {
+	    PJ_LOG(3,(THIS_FILE, "  %-20s: interval (min/max/avg/dev)=%u/%u/%u/%u, burst=%u",
+		      "Playback result",
+		      result.play.min_interval,
+		      result.play.max_interval,
+		      result.play.avg_interval,
+		      result.play.dev_interval,
+		      result.play.max_burst));
+	}
+    }
+
+    if (dir==PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+	if (result.rec_drift_per_sec == 0) {
+	    PJ_LOG(3,(THIS_FILE, " No clock drift detected"));
+	} else {
+	    const char *which = result.rec_drift_per_sec>=0 ? "faster" : "slower";
+	    unsigned drift = result.rec_drift_per_sec>=0 ? 
+				result.rec_drift_per_sec :
+				-result.rec_drift_per_sec;
+
+	    PJ_LOG(3,(THIS_FILE, " Clock drifts detected. Capture device "
+				 "is running %d samples per second %s "
+				 "than the playback device",
+				 drift, which));
+	}
+    }
+}
+
+
+static pj_status_t wav_rec_cb(void *user_data, pjmedia_frame *frame)
+{
+    return pjmedia_port_put_frame((pjmedia_port*)user_data, frame);
+}
+
+static void record(unsigned rec_index, const char *filename)
+{
+    pj_pool_t *pool = NULL;
+    pjmedia_port *wav = NULL;
+    pjmedia_aud_param param;
+    pjmedia_aud_stream *strm = NULL;
+    char line[10];
+    pj_status_t status;
+
+    if (filename == NULL)
+	filename = WAV_FILE;
+
+    pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
+			  1000, 1000, NULL);
+
+    status = pjmedia_wav_writer_port_create(pool, filename, 16000, 
+					    1, 320, 16, 0, 0, &wav);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error creating WAV file", status);
+	goto on_return;
+    }
+
+    status = pjmedia_aud_dev_default_param(rec_index, &param);
+    if (status != PJ_SUCCESS) {
+	app_perror("pjmedia_aud_dev_default_param()", status);
+	goto on_return;
+    }
+
+    param.dir = PJMEDIA_DIR_CAPTURE;
+    param.clock_rate = wav->info.clock_rate;
+    param.samples_per_frame = wav->info.samples_per_frame;
+    param.channel_count = wav->info.channel_count;
+    param.bits_per_sample = wav->info.bits_per_sample;
+
+    status = pjmedia_aud_stream_create(&param, &wav_rec_cb, NULL, wav,
+				       &strm);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error opening the sound device", status);
+	goto on_return;
+    }
+
+    status = pjmedia_aud_stream_start(strm);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error starting the sound device", status);
+	goto on_return;
+    }
+
+    PJ_LOG(3,(THIS_FILE, "Recording started, press ENTER to stop"));
+    fgets(line, sizeof(line), stdin);
+
+on_return:
+    if (strm) {
+	pjmedia_aud_stream_stop(strm);
+	pjmedia_aud_stream_destroy(strm);
+    }
+    if (wav)
+	pjmedia_port_destroy(wav);
+    if (pool)
+	pj_pool_release(pool);
+}
+
+
+static pj_status_t wav_play_cb(void *user_data, pjmedia_frame *frame)
+{
+    return pjmedia_port_get_frame((pjmedia_port*)user_data, frame);
+}
+
+
+static void play_file(unsigned play_index, const char *filename)
+{
+    pj_pool_t *pool = NULL;
+    pjmedia_port *wav = NULL;
+    pjmedia_aud_param param;
+    pjmedia_aud_stream *strm = NULL;
+    char line[10];
+    pj_status_t status;
+
+    if (filename == NULL)
+	filename = WAV_FILE;
+
+    pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
+			  1000, 1000, NULL);
+
+    status = pjmedia_wav_player_port_create(pool, filename, 20, 0, 0, &wav);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error opening WAV file", status);
+	goto on_return;
+    }
+
+    status = pjmedia_aud_dev_default_param(play_index, &param);
+    if (status != PJ_SUCCESS) {
+	app_perror("pjmedia_aud_dev_default_param()", status);
+	goto on_return;
+    }
+
+    param.dir = PJMEDIA_DIR_PLAYBACK;
+    param.clock_rate = wav->info.clock_rate;
+    param.samples_per_frame = wav->info.samples_per_frame;
+    param.channel_count = wav->info.channel_count;
+    param.bits_per_sample = wav->info.bits_per_sample;
+
+    status = pjmedia_aud_stream_create(&param, NULL, &wav_play_cb, wav,
+				       &strm);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error opening the sound device", status);
+	goto on_return;
+    }
+
+    status = pjmedia_aud_stream_start(strm);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error starting the sound device", status);
+	goto on_return;
+    }
+
+    PJ_LOG(3,(THIS_FILE, "Playback started, press ENTER to stop"));
+    fgets(line, sizeof(line), stdin);
+
+on_return:
+    if (strm) {
+	pjmedia_aud_stream_stop(strm);
+	pjmedia_aud_stream_destroy(strm);
+    }
+    if (wav)
+	pjmedia_port_destroy(wav);
+    if (pool)
+	pj_pool_release(pool);
+}
+
+
+static void print_menu(void)
+{
+    puts("");
+    puts("Audio demo menu:");
+    puts("-------------------------------");
+    puts("  l                        List devices");
+    puts("  i ID                     Show device info for device ID");
+    puts("  t RID PID CR PTIM [CH]   Perform test on the device:");
+    puts("                             RID:  record device ID (-1 for no)");
+    puts("                             PID:  playback device ID (-1 for no)");
+    puts("                             CR:   clock rate");
+    puts("                             PTIM: ptime in ms");
+    puts("                             CH:   # of channels");
+    puts("  r RID [FILE]             Record capture device RID to WAV file");
+    puts("  p PID [FILE]             Playback WAV file to device ID PID");
+    puts("  v                        Toggle log verbosity");
+    puts("  q                        Quit");
+    puts("");
+    printf("Enter selection: ");
+    fflush(stdout);
+}
+
+int main()
+{
+    pj_caching_pool cp;
+    pj_bool_t done = PJ_FALSE;
+    pj_status_t status;
+
+    /* Init pjlib */
+    status = pj_init();
+    PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
+    
+    pj_log_set_decor(PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_COLOR);
+
+    /* Must create a pool factory before we can allocate any memory. */
+    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+    status = pjmedia_aud_subsys_init(&cp.factory);
+    if (status != PJ_SUCCESS) {
+	app_perror("pjmedia_aud_subsys_init()", status);
+	pj_caching_pool_destroy(&cp);
+	pj_shutdown();
+	return 1;
+    }
+
+    list_devices();
+
+    while (!done) {
+	char line[80];
+
+	print_menu();
+
+	if (fgets(line, sizeof(line), stdin)==NULL)
+	    break;
+
+	switch (line[0]) {
+	case 'l':
+	    list_devices();
+	    break;
+
+	case 'i':
+	    {
+		unsigned dev_index;
+		if (sscanf(line+2, "%u", &dev_index) != 1) {
+		    puts("error: device ID required");
+		    break;
+		}
+		show_dev_info(dev_index);
+	    }
+	    break;
+
+	case 't':
+	    {
+		pjmedia_dir dir;
+		int rec_id, play_id;
+		unsigned clock_rate, ptime, chnum;
+		int cnt;
+
+		cnt = sscanf(line+2, "%d %d %u %u %u", &rec_id, &play_id, 
+			     &clock_rate, &ptime, &chnum);
+		if (cnt < 4) {
+		    puts("error: not enough parameters");
+		    break;
+		}
+		if (clock_rate < 8000 || clock_rate > 128000) {
+		    puts("error: invalid clock rate");
+		    break;
+		}
+		if (ptime < 10 || ptime > 500) {
+		    puts("error: invalid ptime");
+		    break;
+		}
+		if (cnt==5) {
+		    if (chnum < 1 || chnum > 4) {
+			puts("error: invalid number of channels");
+			break;
+		    }
+		} else {
+		    chnum = 1;
+		}
+
+		if (rec_id >= 0 && rec_id < (int)dev_count) {
+		    if (play_id >= 0 && play_id < (int)dev_count)
+			dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+		    else
+			dir = PJMEDIA_DIR_CAPTURE;
+		} else if (play_id >= 0 && play_id < (int)dev_count) {
+		    dir = PJMEDIA_DIR_PLAYBACK;
+		} else {
+		    puts("error: at least one valid device index required");
+		    break;
+		}
+
+		test_device(dir, rec_id, play_id, clock_rate, ptime, chnum);
+		
+	    }
+	    break;
+
+	case 'r':
+	    /* record */
+	    {
+		int index;
+		char filename[80];
+		int count;
+
+		count = sscanf(line+2, "%d %s", &index, filename);
+		if (count==1)
+		    record(index, NULL);
+		else if (count==2)
+		    record(index, filename);
+		else
+		    puts("error: invalid command syntax");
+	    }
+	    break;
+
+	case 'p':
+	    /* playback */
+	    {
+		int index;
+		char filename[80];
+		int count;
+
+		count = sscanf(line+2, "%d %s", &index, filename);
+		if (count==1)
+		    play_file(index, NULL);
+		else if (count==2)
+		    play_file(index, filename);
+		else
+		    puts("error: invalid command syntax");
+	    }
+	    break;
+
+	case 'v':
+	    if (pj_log_get_level() <= 3) {
+		pj_log_set_level(5);
+		puts("Logging set to detail");
+	    } else {
+		pj_log_set_level(3);
+		puts("Logging set to quiet");
+	    }
+	    break;
+
+	case 'q':
+	    done = PJ_TRUE;
+	    break;
+	}
+    }
+
+    pj_caching_pool_destroy(&cp);
+    pj_shutdown();
+    return 0;
+}
+
+
Index: pjproject/trunk/pjsip-apps/src/samples/debug.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 2505)
+++ pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 2506)
@@ -29,4 +29,4 @@
  *  #include "playfile.c"
  */
-#include "aectest.c"
+#include "auddemo.c"
 
Index: pjproject/trunk/pjsip-apps/src/samples/sndinfo.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/sndinfo.c	(revision 2505)
+++ 	(revision )
@@ -1,295 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * 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 
- */
-
-static const char *desc = 
- " sndinfo.c								    \n"
- "									    \n"
- " PURPOSE:								    \n"
- "  Print sound device info and test open device.			    \n"
- "									    \n"
- " USAGE:								    \n"
- "  sndinfo [id rec/play/both clockrate nchan bits]			    \n"
- "									    \n"
- " DESCRIPTION:								    \n"
- "  When invoked without any arguments, it displays information about all   \n"
- "  sound devices in the system.					    \n"
- "									    \n"
- "  When invoked with arguments, the program tests if device can be opened  \n"
- "  with the specified arguments. All these arguments must be specified:    \n"
- "  - id             The device ID (-1 for the first capable device)	    \n"
- "  - rec/play/both  Specify which streams to open.			    \n"
- "  - clockrate      Specify clock rate (e.g. 8000, 11025, etc.)	    \n"
- "  - nchan	     Number of channels (1=mono, 2=stereo).		    \n"
- "  - bits	     Number of bits per sample (normally 16).		    \n";
-
-#include <pjmedia.h>
-#include <pjlib.h>
-
-#include <stdlib.h>	/* atoi() */
-#include <stdio.h>
-
-
-static void enum_devices(void)
-{
-    int i, count;
-    
-    count = pjmedia_snd_get_dev_count();
-    if (count == 0) {
-	puts("No devices found");
-	return;
-    }
-
-    for (i=0; i<count; ++i) {
-	const pjmedia_snd_dev_info *info;
-
-	info = pjmedia_snd_get_dev_info(i);
-	pj_assert(info != NULL);
-
-	printf( "Device #%02d: \n"
-		"  Name                : %s\n"
-		"  # of input channels : %d\n"
-		"  # of output channels: %d\n"
-		"  Default clock rate  : %d Hz\n\n",
-		i, info->name, info->input_count, info->output_count,
-		info->default_samples_per_sec);
-    }
-    puts("");
-    puts("Run with -h to get more options");
-}
-
-static unsigned clock_rate;
-static unsigned play_counter;
-static unsigned rec_counter;
-static unsigned min_delay = 0xFFFF, max_delay;
-static char play_delays[1000];
-static pj_uint32_t last_play_timestamp, last_rec_timestamp;
-
-static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp,
-			   void *output, unsigned size)
-{
-    static pj_timestamp last_cb;
-
-
-    PJ_UNUSED_ARG(user_data);
-    PJ_UNUSED_ARG(output);
-    PJ_UNUSED_ARG(size);
-
-
-    ++play_counter;
-    last_play_timestamp = timestamp;
-
-    if (last_cb.u64 == 0) {
-	pj_get_timestamp(&last_cb);
-    } else if (play_counter <= PJ_ARRAY_SIZE(play_delays)) {
-	pj_timestamp now;
-	unsigned delay;
-
-	pj_get_timestamp(&now);
-	
-	delay = pj_elapsed_msec(&last_cb, &now);
-	if (delay < min_delay)
-	    min_delay = delay;
-	if (delay > max_delay)
-	    max_delay = delay;
-
-	last_cb = now;
-
-	play_delays[play_counter-1] = (char)delay;
-    }
-
-    return PJ_SUCCESS;
-}
-
-static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp,
-			  void *input, unsigned size)
-{
-
-    PJ_UNUSED_ARG(size);
-    PJ_UNUSED_ARG(input);
-    PJ_UNUSED_ARG(user_data);
-
-
-    ++rec_counter;
-
-    if (timestamp - last_rec_timestamp >= clock_rate && last_play_timestamp) {
-	int diff;
-	diff = last_play_timestamp - timestamp;
-	printf("Play timestamp=%u, capture timestamp=%u, diff=%d\n",
-	       last_play_timestamp, timestamp, diff);
-	last_rec_timestamp = timestamp;
-    }
-    return PJ_SUCCESS;
-}
-
-static void app_perror(const char *title, pj_status_t status)
-{
-    char errmsg[PJ_ERR_MSG_SIZE];
-
-    pj_strerror(status, errmsg, sizeof(errmsg));	
-    printf( "%s: %s (err=%d)\n",
-	    title, errmsg, status);
-}
-
-static int open_device(int dev_id, pjmedia_dir dir,
-		       int nchannel, int bits)
-{
-    pj_status_t status = PJ_SUCCESS;
-    unsigned nsamples;
-    pjmedia_snd_stream *strm;
-    const char *dirtype;
-    char tmp[10];
-    unsigned i;
-
-    switch (dir) {
-    case PJMEDIA_DIR_CAPTURE:
-	dirtype = "capture"; break;    
-    case PJMEDIA_DIR_PLAYBACK:
-	dirtype = "playback"; break;    
-    case PJMEDIA_DIR_CAPTURE_PLAYBACK:
-	dirtype = "capture/playback"; break;    
-    default:
-	return 1;
-    }
-    
-    nsamples = clock_rate * 20 / 1000;
-
-    printf( "Opening device %d for %s: clockrate=%d, nchannel=%d, "
-	    "bits=%d, nsamples=%d..\n",
-	    dev_id, dirtype, clock_rate, nchannel, bits, nsamples);
-
-    if (dir == PJMEDIA_DIR_CAPTURE) {
-	status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel,
-				       nsamples, bits, &rec_cb, NULL,
-				       &strm);
-    } else if (dir == PJMEDIA_DIR_PLAYBACK) {
-	status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel,
-					  nsamples, bits, &play_cb, NULL,
-					  &strm);
-    } else {
-	status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel,
-				   nsamples, bits, &rec_cb, &play_cb, NULL,
-				   &strm);
-    }
-    
-    if (status != PJ_SUCCESS) {
-        app_perror("Unable to open device for capture", status);
-        return 1;
-    }
-
-    status = pjmedia_snd_stream_start(strm);
-    if (status != PJ_SUCCESS) {
-        app_perror("Unable to start capture stream", status);
-        return 1;
-    }
-    
-    /* Let playback/capture runs for a while */
-    //pj_thread_sleep(1000);
-    puts("Press <ENTER> to stop");
-    if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
-	puts("EOF while reading stdin, will quit now..");
-    }
-
-    pjmedia_snd_stream_close(strm);
-
-    if ((dir & PJMEDIA_DIR_CAPTURE) && rec_counter==0) {
-	printf("Error: capture stream was not running\n");
-	return 1;
-    }
-
-    if ((dir & PJMEDIA_DIR_PLAYBACK) && play_counter==0) {
-	printf("Error: playback stream was not running\n");
-	return 1;
-    }
-
-    puts("Success.");
-
-    printf("Delay: ");
-    for (i=0; i<play_counter; ++i)
-	printf("%d ", play_delays[i]);
-
-    puts("");
-    if (dir & PJMEDIA_DIR_PLAYBACK) {
-	printf("Callback interval: min interval=%d ms, max interval=%d ms\n",
-	       min_delay, max_delay);
-    }
-    
-
-    return 0;
-}
-
-
-int main(int argc, char *argv[])
-{
-    pj_caching_pool cp;
-    pjmedia_endpt *med_endpt;
-    pj_status_t status;
-
-    /* Init pjlib */
-    status = pj_init();
-    PJ_ASSERT_RETURN(status==PJ_SUCCESS, 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);
-
-    
-    if (argc == 1) {
-	enum_devices();
-    } else if (argc == 6) {
-	
-	int dev_id;
-	pjmedia_dir dir = PJMEDIA_DIR_NONE;
-	int nchannel;
-	int bits;
-
-	dev_id = atoi(argv[1]);
-
-	if (strcmp(argv[2], "rec")==0)
-	    dir = PJMEDIA_DIR_CAPTURE;
-	else if (strcmp(argv[2], "play")==0)
-	    dir = PJMEDIA_DIR_PLAYBACK;
-	else if (strcmp(argv[2], "both")==0)
-	    dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
-
-	clock_rate = atoi(argv[3]);
-	nchannel = atoi(argv[4]);
-	bits = atoi(argv[5]);
-
-	return open_device(dev_id, dir, nchannel, bits);
-
-    } else {
-	puts("Error: invalid arguments");
-	puts(desc);
-	return 1;
-    }
-
-    /* Shutdown PJLIB */
-    pj_shutdown();
-
-    return 0;
-}
-
-
Index: pjproject/trunk/pjsip-apps/src/samples/sndtest.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/sndtest.c	(revision 2505)
+++ 	(revision )
@@ -1,621 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
- * 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_sndtest_c Samples: Sound Card Benchmark
- *
- * This example can be used to benchmark the quality of the sound card
- * installed in the system. At the end of the test, it will report
- * the jitter and clock drifts of the device.
- *
- * This file is pjsip-apps/src/samples/sndtest.c
- *
- * Screenshots on WinXP: \image html sndtest.jpg
- *
- * \includelineno sndtest.c
- */
-
-
-#include <pjmedia.h>
-#include <pjlib.h>
-#include <pjlib-util.h>
-
-#include <stdlib.h>	/* atoi() */
-#include <stdio.h>
-
-
-
-#define THIS_FILE	    "sndtest.c"
-
-/* Warn (print log with yellow color) if frame jitter is larger than
- * this value (in usec).
- */
-#define WARN_JITTER_USEC    1000
-
-/* Test duration in msec */
-#define DURATION	    10000
-
-/* Skip the first msec from the calculation */
-#define SKIP_DURATION	    1000
-
-/* Max frames per sec (to calculate number of delays to keep). */
-#define MAX_FRAMES_PER_SEC  100
-
-/* Number of frame durations to keep */
-#define MAX_DELAY_COUNTER   (((DURATION/1000)+1)*MAX_FRAMES_PER_SEC)
-
-
-struct stream_data
-{
-    pj_uint32_t	    first_timestamp;
-    pj_uint32_t	    last_timestamp;
-    pj_timestamp    last_called;
-    unsigned	    counter;
-    unsigned	    min_delay;
-    unsigned	    max_delay;
-    unsigned	    delay[MAX_DELAY_COUNTER];
-};
-
-struct test_data {
-    pjmedia_dir dir;
-    unsigned clock_rate;
-    unsigned samples_per_frame;
-    unsigned channel_count;
-    pj_bool_t running;
-    pj_bool_t has_error;
-    pj_mutex_t *mutex;
-
-    struct stream_data capture_data;
-    struct stream_data playback_data;
-};
-
-
-
-static const char *desc = 
- " sndtest.c						\n"
- "							\n"
- " PURPOSE:						\n"
- "  Test the performance of sound device.		\n"
- "							\n"
- " USAGE:						\n"
- "  sndtest --help					\n"
- "  sndtest [options]					\n"
- "							\n"
- " where options:					\n"
- "  --id=ID          -i  Use device ID (default is -1)	\n"
- "  --rate=HZ        -r  Set test clock rate (default=8000)\n"
- "  --frame=SAMPLES  -f  Set number of samples per frame\n"
- "  --channel=CH     -n  Set number of channels (default=1)\n"
- "  --verbose        -v  Show verbose result		\n"
- "  --help           -h  Show this screen		\n"
-;
-
-
-
-static void enum_devices(void)
-{
-    int i, count;
-    
-    count = pjmedia_snd_get_dev_count();
-    if (count == 0) {
-	PJ_LOG(3,(THIS_FILE, "No devices found"));
-	return;
-    }
-
-    PJ_LOG(3,(THIS_FILE, "Found %d devices:", count));
-    for (i=0; i<count; ++i) {
-	const pjmedia_snd_dev_info *info;
-
-	info = pjmedia_snd_get_dev_info(i);
-	pj_assert(info != NULL);
-
-	PJ_LOG(3,(THIS_FILE," %d: %s (capture=%d, playback=%d)",
-	          i, info->name, info->input_count, info->output_count));
-    }
-}
-
-
-static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp,
-			   void *output, unsigned size)
-{
-    struct test_data *test_data = user_data;
-    struct stream_data *strm_data = &test_data->playback_data;
-
-    pj_mutex_lock(test_data->mutex);
-
-    /* Skip frames when test is not started or test has finished */
-    if (!test_data->running) {
-	pj_bzero(output, size);
-
-	pj_mutex_unlock(test_data->mutex);
-	return PJ_SUCCESS;
-    }
-
-    /* Save last timestamp seen (to calculate drift) */
-    strm_data->last_timestamp = timestamp;
-
-    if (strm_data->last_called.u64 == 0) {
-	pj_get_timestamp(&strm_data->last_called);
-	/* Init min_delay to one frame */
-	strm_data->min_delay = test_data->samples_per_frame * 1000000 /
-			       test_data->clock_rate;
-	strm_data->first_timestamp = timestamp;
-
-    } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
-	pj_timestamp now;
-	unsigned delay;
-
-	pj_get_timestamp(&now);
-	
-	/* Calculate frame interval */
-	delay = pj_elapsed_usec(&strm_data->last_called, &now);
-	if (delay < strm_data->min_delay)
-	    strm_data->min_delay = delay;
-	if (delay > strm_data->max_delay)
-	    strm_data->max_delay = delay;
-
-	strm_data->last_called = now;
-
-	/* Save the frame interval for later calculation */
-	strm_data->delay[strm_data->counter] = delay;
-	++strm_data->counter;
-
-    } else {
-
-	/* No space, can't take anymore frames */
-	test_data->running = 0;
-
-    }
-
-    pj_bzero(output, size);
-
-    pj_mutex_unlock(test_data->mutex);
-
-    return PJ_SUCCESS;
-}
-
-static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp,
-			  void *input, unsigned size)
-{
-
-    struct test_data *test_data = user_data;
-    struct stream_data *strm_data = &test_data->capture_data;
-
-    pj_mutex_lock(test_data->mutex);
-
-    PJ_UNUSED_ARG(input);
-    PJ_UNUSED_ARG(size);
-
-    /* Skip frames when test is not started or test has finished */
-    if (!test_data->running) {
-	pj_mutex_unlock(test_data->mutex);
-	return PJ_SUCCESS;
-    }
-
-    /* Save last timestamp seen (to calculate drift) */
-    strm_data->last_timestamp = timestamp;
-
-    if (strm_data->last_called.u64 == 0) {
-	pj_get_timestamp(&strm_data->last_called);
-	/* Init min_delay to one frame */
-	strm_data->min_delay = test_data->samples_per_frame * 1000000 /
-			       test_data->clock_rate;
-	strm_data->first_timestamp = timestamp;
-
-    } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
-	pj_timestamp now;
-	unsigned delay;
-
-	pj_get_timestamp(&now);
-
-	/* Calculate frame interval */
-	delay = pj_elapsed_usec(&strm_data->last_called, &now);
-	if (delay < strm_data->min_delay)
-	    strm_data->min_delay = delay;
-	if (delay > strm_data->max_delay)
-	    strm_data->max_delay = delay;
-
-	strm_data->last_called = now;
-
-	/* Save the frame interval for later calculation */
-	strm_data->delay[strm_data->counter] = delay;
-	++strm_data->counter;
-
-    } else {
-
-	/* No space, can't take anymore frames */
-	test_data->running = 0;
-
-    }
-
-    pj_mutex_unlock(test_data->mutex);
-    return PJ_SUCCESS;
-}
-
-static void app_perror(const char *title, pj_status_t status)
-{
-    char errmsg[PJ_ERR_MSG_SIZE];
-
-    pj_strerror(status, errmsg, sizeof(errmsg));	
-    printf( "%s: %s (err=%d)\n",
-	    title, errmsg, status);
-}
-
-
-static void print_stream_data(const char *title, 
-			      struct test_data *test_data,
-			      struct stream_data *strm_data,
-			      int verbose)
-{
-    unsigned i, dur;
-    int ptime;
-    unsigned min_jitter, max_jitter, sum_jitter, avg_jitter=0;
-
-    PJ_LOG(3,(THIS_FILE, "  %s stream report:", title));
-
-    /* Check that frames are captured/played */
-    if (strm_data->counter == 0) {
-	PJ_LOG(1,(THIS_FILE, "   Error: no frames are captured/played!"));
-	test_data->has_error = 1;
-	return;
-    }
-
-    /* Duration */
-    dur = (strm_data->counter+1) * test_data->samples_per_frame * 1000 /
-		test_data->clock_rate;
-    PJ_LOG(3,(THIS_FILE, "   Duration: %ds.%03d",
-	      dur/1000, dur%1000));
-
-    /* Frame interval */
-    if (strm_data->max_delay - strm_data->min_delay < WARN_JITTER_USEC) {
-	PJ_LOG(3,(THIS_FILE, 
-		  "   Frame interval: min=%d.%03dms, max=%d.%03dms",
-		  strm_data->min_delay/1000, strm_data->min_delay%1000,
-		  strm_data->max_delay/1000, strm_data->max_delay%1000));
-    } else {
-	test_data->has_error = 1;
-	PJ_LOG(2,(THIS_FILE, 
-		  "   Frame interval: min=%d.%03dms, max=%d.%03dms",
-		  strm_data->min_delay/1000, strm_data->min_delay%1000,
-		  strm_data->max_delay/1000, strm_data->max_delay%1000));
-    }
-
-    if (verbose) {
-	unsigned i;
-	unsigned decor = pj_log_get_decor();
-
-	PJ_LOG(3,(THIS_FILE, "    Dumping frame delays:"));
-
-	pj_log_set_decor(0);
-	for (i=0; i<strm_data->counter; ++i)
-	    PJ_LOG(3,(THIS_FILE, " %d.%03d", strm_data->delay[i]/1000, 
-		      strm_data->delay[i]%1000));
-	PJ_LOG(3,(THIS_FILE, "\r\n"));
-	pj_log_set_decor(decor);
-    }
-
-    /* Calculate frame ptime in usec */
-    ptime = test_data->samples_per_frame * 1000000 /
-	    test_data->clock_rate;
-
-    /* Calculate jitter */
-    min_jitter = 0xFFFFF;
-    max_jitter = 0;
-    sum_jitter = 0;
-
-    for (i=1; i<strm_data->counter; ++i) {
-	int jitter1, jitter2, jitter;
-
-	/* jitter1 is interarrival difference */
-	jitter1 = strm_data->delay[i] - strm_data->delay[i-1];
-	if (jitter1 < 0) jitter1 = -jitter1;
-	
-	/* jitter2 is difference between actual and scheduled arrival.
-	 * This is intended to capture situation when frames are coming
-	 * instantaneously, which will calculate as zero jitter with
-	 * jitter1 calculation.
-	 */
-	jitter2 = ptime - strm_data->delay[i];
-	if (jitter2 < 0) jitter2 = -jitter2;
-
-	/* Set jitter as the maximum of the two jitter calculations. 
-	 * This is intended to show the worst result.
-	 */
-	jitter = (jitter1>jitter2) ? jitter1 : jitter2;
-
-	/* Calculate min, max, avg jitter */
-	if (jitter < (int)min_jitter) min_jitter = jitter;
-	if (jitter > (int)max_jitter) max_jitter = jitter;
-
-	sum_jitter += jitter;
-    }
-
-    avg_jitter = (sum_jitter) / (strm_data->counter - 1);
-
-    if (max_jitter < WARN_JITTER_USEC) {
-	PJ_LOG(3,(THIS_FILE, 
-		  "   Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
-		  min_jitter/1000, min_jitter%1000, 
-		  avg_jitter/1000, avg_jitter%1000,
-		  max_jitter/1000, max_jitter%1000));
-    } else {
-	test_data->has_error = 1;
-	PJ_LOG(2,(THIS_FILE, 
-		  "   Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
-		  min_jitter/1000, min_jitter%1000, 
-		  avg_jitter/1000, avg_jitter%1000,
-		  max_jitter/1000, max_jitter%1000));
-    }
-}
-
-
-static int perform_test(pj_pool_t *pool, int dev_id, pjmedia_dir dir,
-		        unsigned clock_rate, unsigned samples_per_frame, 
-			unsigned nchannel, int verbose)
-{
-    pj_status_t status = PJ_SUCCESS;
-    pjmedia_snd_stream *strm;
-    struct test_data test_data;
-    pjmedia_snd_stream_info si;
-    
-    /*
-     * Init test parameters
-     */
-    pj_bzero(&test_data, sizeof(test_data));
-    test_data.dir = dir;
-    test_data.clock_rate = clock_rate;
-    test_data.samples_per_frame = samples_per_frame;
-    test_data.channel_count = nchannel;
-
-    pj_mutex_create_simple(pool, "sndtest", &test_data.mutex); 
-
-    /*
-     * Open device.
-     */
-    if (dir == PJMEDIA_DIR_CAPTURE) {
-	status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel,
-				       samples_per_frame, 16, &rec_cb, 
-				       &test_data, &strm);
-    } else if (dir == PJMEDIA_DIR_PLAYBACK) {
-	status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel,
-					  samples_per_frame, 16, &play_cb, 
-					  &test_data, &strm);
-    } else {
-	status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel,
-				   samples_per_frame, 16, &rec_cb, &play_cb, 
-				   &test_data, &strm);
-    }
-    
-    if (status != PJ_SUCCESS) {
-        app_perror("Unable to open device for capture", status);
-        return status;
-    }
-
-    pjmedia_snd_stream_get_info(strm, &si);
-    if (si.play_id >= 0) {
-	PJ_LOG(3,(THIS_FILE, "Testing playback device %s", 
-		  pjmedia_snd_get_dev_info(si.play_id)->name));
-    }
-    if (si.rec_id >= 0) {
-	PJ_LOG(3,(THIS_FILE, "Testing capture device %s", 
-		  pjmedia_snd_get_dev_info(si.rec_id)->name));
-    }
-
-    /* Sleep for a while to let sound device "settles" */
-    pj_thread_sleep(200);
-
-
-    /*
-     * Start the stream.
-     */
-    status = pjmedia_snd_stream_start(strm);
-    if (status != PJ_SUCCESS) {
-        app_perror("Unable to start capture stream", status);
-        return status;
-    }
-
-    PJ_LOG(3,(THIS_FILE,
-	      " Please wait while test is in progress (~%d secs)..",
-	      (DURATION+SKIP_DURATION)/1000));
-
-    /* Let the stream runs for few msec/sec to get stable result.
-     * (capture normally begins with frames available simultaneously).
-     */
-    pj_thread_sleep(SKIP_DURATION);
-
-
-    /* Begin gather data */
-    test_data.running = 1;
-
-    /* 
-     * Let the test runs for a while.
-     */
-    pj_thread_sleep(DURATION);
-
-
-    /*
-     * Close stream.
-     */
-    test_data.running = 0;
-    pjmedia_snd_stream_close(strm);
-
-
-    /* 
-     * Print results.
-     */
-    PJ_LOG(3,(THIS_FILE, " Dumping results:"));
-
-    PJ_LOG(3,(THIS_FILE, "  Parameters: clock rate=%dHz, %d samples/frame",
-	      clock_rate, samples_per_frame));
-
-    if (dir & PJMEDIA_DIR_PLAYBACK)
-	print_stream_data("Playback", &test_data, &test_data.playback_data, 
-			  verbose);
-    if (dir & PJMEDIA_DIR_CAPTURE)
-	print_stream_data("Capture", &test_data, &test_data.capture_data, 
-			   verbose);
-
-    /* Check drifting */
-    if (dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
-	int end_diff, start_diff, drift;
-
-	end_diff = test_data.capture_data.last_timestamp -
-		   test_data.playback_data.last_timestamp;
-	start_diff = test_data.capture_data.first_timestamp-
-		      test_data.playback_data.first_timestamp;
-	drift = end_diff > start_diff? end_diff - start_diff :
-		start_diff - end_diff;
-
-	PJ_LOG(3,(THIS_FILE, "  Checking for clock drifts:"));
-
-	/* Allow one frame tolerance for clock drift detection */
-	if (drift < (int)samples_per_frame) {
-	    PJ_LOG(3,(THIS_FILE, "   No clock drifts is detected"));
-	} else {
-	    const char *which = (drift<0 ? "slower" : "faster");
-	    unsigned msec_dur;
-
-	    if (drift < 0) drift = -drift;
-
-
-	    msec_dur = (test_data.capture_data.last_timestamp - 
-		       test_data.capture_data.first_timestamp) * 1000 /
-		       test_data.clock_rate;
-
-	    PJ_LOG(2,(THIS_FILE, 
-		      "   Sound capture is %d samples %s than playback "
-		      "at the end of the test (average is %d samples"
-		      " per second)",
-		      drift, which, 
-		      drift * 1000 / msec_dur));
-
-	}
-    }
-
-    if (test_data.has_error == 0) {
-	PJ_LOG(3,(THIS_FILE, " Test completed, sound device looks okay."));
-	return 0;
-    } else {
-	PJ_LOG(2,(THIS_FILE, " Test completed with some warnings"));
-	return 1;
-    }
-}
-
-
-int main(int argc, char *argv[])
-{
-    pj_caching_pool cp;
-    pj_pool_t *pool;
-    pjmedia_endpt *med_endpt;
-    int id = -1, verbose = 0;
-    int clock_rate = 8000;
-    int frame = -1;
-    int channel = 1;
-    struct pj_getopt_option long_options[] = {
-	{ "id",	     1, 0, 'i' },
-	{ "rate",    1, 0, 'r' },
-	{ "frame",   1, 0, 'f' },
-	{ "channel", 1, 0, 'n' },
-	{ "verbose", 0, 0, 'v' },
-	{ "help",    0, 0, 'h' },
-	{ NULL, 0, 0, 0 }
-    };
-    int c, option_index;
-    
-
-    pj_status_t status;
-
-    /* Init pjlib */
-    status = pj_init();
-    PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
-    
-    /* Must create a pool factory before we can allocate any memory. */
-    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
-
-    /* Also create pool for misc purposes */
-    pool = pj_pool_create(&cp.factory, "sndtest", 1000, 1000, NULL);
-
-    /* 
-     * 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);
-
-    /* Print devices */
-    enum_devices();
-
-    /* Parse options */
-    pj_optind = 0;
-    while((c=pj_getopt_long(argc,argv, "i:r:f:n:vh", 
-			    long_options, &option_index))!=-1) 
-    {
-	switch (c) {
-	case 'i':
-	    id = atoi(pj_optarg);
-	    break;
-	case 'r':
-	    clock_rate = atoi(pj_optarg);
-	    break;
-	case 'f':
-	    frame = atoi(pj_optarg);
-	    break;
-	case 'n':
-	    channel = atoi(pj_optarg);
-	    break;
-	case 'v':
-	    verbose = 1;
-	    break;
-	case 'h':
-	    puts(desc);
-	    return 0;
-	    break;
-	default:
-	    printf("Error: invalid options %s\n", argv[pj_optind-1]);
-	    puts(desc);
-	    return 1;
-	}
-    }
-
-    if (pj_optind != argc) {
-	printf("Error: invalid options\n");
-	puts(desc);
-	return 1;
-    }
-
-    if (!verbose)
-	pj_log_set_level(3);
-
-    if (frame == -1)
-	frame = 10 * clock_rate / 1000;
-
-
-    status = perform_test(pool, id, PJMEDIA_DIR_CAPTURE_PLAYBACK, 
-			  clock_rate, frame, channel, verbose);
-
-    pjmedia_endpt_destroy(med_endpt);
-    pj_pool_release(pool);
-    pj_caching_pool_destroy(&cp);
-    pj_shutdown();
-    
-    return status == PJ_SUCCESS ? 0 : 1;
-}
-
-
Index: pjproject/trunk/pjsip-apps/src/symbian_ua/main_symbian.cpp
===================================================================
--- pjproject/trunk/pjsip-apps/src/symbian_ua/main_symbian.cpp	(revision 2505)
+++ pjproject/trunk/pjsip-apps/src/symbian_ua/main_symbian.cpp	(revision 2506)
@@ -30,5 +30,5 @@
 
 // Needed by APS
-TPtrC APP_UID = _L("A000000D");
+TPtrC APP_UID = _L("200235D3");
 
 
Index: pjproject/trunk/pjsip-apps/src/symbian_ua/ua.cpp
===================================================================
--- pjproject/trunk/pjsip-apps/src/symbian_ua/ua.cpp	(revision 2505)
+++ pjproject/trunk/pjsip-apps/src/symbian_ua/ua.cpp	(revision 2506)
@@ -20,8 +20,10 @@
 #include <pjsua-lib/pjsua.h>
 #include <pjsua-lib/pjsua_internal.h>
+//#include <pjmedia/symbian_sound_aps.h>
 #include "ua.h"
 
 #define THIS_FILE	"symbian_ua.cpp"
-#define LOG_LEVEL	3
+#define CON_LOG_LEVEL	3 // console log level
+#define FILE_LOG_LEVEL	4 // logfile log level
 
 //
@@ -281,5 +283,5 @@
     
     /* Set log level */
-    pj_log_set_level(LOG_LEVEL);
+    pj_log_set_level(CON_LOG_LEVEL);
 
     /* Create pjsua first! */
@@ -329,8 +331,8 @@
     
     pjsua_logging_config_default(&log_cfg);
-    log_cfg.level = LOG_LEVEL;
-    log_cfg.console_level = LOG_LEVEL;
+    log_cfg.level = FILE_LOG_LEVEL;
+    log_cfg.console_level = CON_LOG_LEVEL;
     log_cfg.cb = &log_writer;
-    //log_cfg.log_filename = pj_str("C:\\data\\symbian_ua.log");
+    log_cfg.log_filename = pj_str("C:\\data\\symbian_ua.log");
 
     pjsua_media_config_default(&med_cfg);
@@ -338,12 +340,9 @@
     med_cfg.has_ioqueue = PJ_FALSE;
     med_cfg.clock_rate = 8000;
-#if defined(PJMEDIA_SYM_SND_USE_APS) && (PJMEDIA_SYM_SND_USE_APS==1)
-    med_cfg.audio_frame_ptime = 20;
-#else
     med_cfg.audio_frame_ptime = 40;
-#endif
     med_cfg.ec_tail_len = 0;
     med_cfg.enable_ice = USE_ICE;
-    med_cfg.snd_auto_close_time = 5; // wait for 5 seconds idle before sound dev get auto-closed
+    med_cfg.snd_auto_close_time = 0; // wait for 0 seconds idle before sound dev get auto-closed
+    //med_cfg.no_vad = PJ_TRUE;
     
     status = pjsua_init(&cfg, &log_cfg, &med_cfg);
@@ -492,21 +491,202 @@
 }
 
-static void PrintMenu() 
-{
-    PJ_LOG(3, (THIS_FILE, "\n\n"
-	    "Menu:\n"
-	    "  d    Dump states\n"
-	    "  D    Dump states detail\n"
-	    "  P    Dump pool factory\n"
-   	    "  l    Start loopback audio device\n"
-   	    "  L    Stop loopback audio device\n"
+static void PrintMainMenu() 
+{
+    const char *menu =
+	    "\n\n"
+	    "Main Menu:\n"
+	    "  d    Enable/disable codecs\n"
 	    "  m    Call " SIP_DST_URI "\n"
 	    "  a    Answer call\n"
 	    "  g    Hangup all calls\n"
+   	    "  t    Toggle audio route\n"
+#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0
+   	    "  j    Toggle loopback audio\n"
+#endif
+   	    "up/dn  Increase/decrease output volume\n"
 	    "  s    Subscribe " SIP_DST_URI "\n"
 	    "  S    Unsubscribe presence\n"
 	    "  o    Set account online\n"
 	    "  O    Set account offline\n"
-	    "  w    Quit\n"));
+	    "  w    Quit\n";
+    
+    PJ_LOG(3, (THIS_FILE, menu));
+}
+
+static void PrintCodecMenu() 
+{
+    const char *menu = 
+	    "\n\n"
+	    "Codec Menu:\n"
+	    "  a    Enable all codecs\n"
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+	    "  d    Enable only AMR\n"
+#endif
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+	    "  g    Enable only G.729\n"
+#endif
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+	    "  j    Enable only iLBC\n"
+#endif
+	    "  m    Enable only Speex\n"
+	    "  p    Enable only GSM\n"
+	    "  t    Enable only PCMU\n"
+	    "  w    Enable only PCMA\n";
+    
+    PJ_LOG(3, (THIS_FILE, menu));
+}
+
+static void HandleMainMenu(TKeyCode kc) {
+    switch (kc) {
+    
+    case EKeyUpArrow:
+    case EKeyDownArrow:
+	{
+	    unsigned vol;
+	    pj_status_t status;
+	    
+	    status = pjsua_snd_get_setting(
+			     PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, &vol);
+	    if (status == PJ_SUCCESS) {
+		if (kc == EKeyUpArrow)
+		    vol = PJ_MIN(100, vol+10);
+		else
+		    vol = (vol>=10 ? vol-10 : 0);
+		status = pjsua_snd_set_setting(
+				    PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+				    &vol, PJ_TRUE);
+	    }
+
+	    if (status == PJ_SUCCESS) {
+		PJ_LOG(3,(THIS_FILE, "Output volume set to %d", vol));
+	    } else {
+		pjsua_perror(THIS_FILE, "Error setting volume", status);
+	    }
+	}
+	break;
+    
+    case 't':
+	{
+	    pjmedia_aud_dev_route route;
+	    pj_status_t status;
+	    
+	    status = pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, 
+					   &route);
+	    
+	    if (status == PJ_SUCCESS) {
+		if (route == PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER)
+		    route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+		else
+		    route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+
+		status = pjsua_snd_set_setting(
+				    PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+				    &route, PJ_TRUE);
+	    }
+
+	    if (status != PJ_SUCCESS)
+		pjsua_perror(THIS_FILE, "Error switch audio route", status);
+	}
+	break;
+	
+    case 'j':
+	{
+	    static pj_bool_t loopback_active = PJ_FALSE;
+	    if (!loopback_active)
+		pjsua_conf_connect(0, 0);
+	    else
+		pjsua_conf_disconnect(0, 0);
+	    loopback_active = !loopback_active;
+	}
+	break;
+	
+    case 'm':
+	if (g_call_id != PJSUA_INVALID_ID) {
+		PJ_LOG(3,(THIS_FILE, "Another call is active"));	
+		break;
+	}
+
+	if (pjsua_verify_sip_url(SIP_DST_URI) == PJ_SUCCESS) {
+		pj_str_t dst = pj_str(SIP_DST_URI);
+		pjsua_call_make_call(g_acc_id, &dst, 0, NULL,
+				     NULL, &g_call_id);
+	} else {
+		PJ_LOG(3,(THIS_FILE, "Invalid SIP URI"));
+	}
+	break;
+    case 'a':
+	if (g_call_id != PJSUA_INVALID_ID)
+		pjsua_call_answer(g_call_id, 200, NULL, NULL);
+	break;
+    case 'g':
+	pjsua_call_hangup_all();
+	break;
+    case 's':
+    case 'S':
+	if (g_buddy_id != PJSUA_INVALID_ID)
+		pjsua_buddy_subscribe_pres(g_buddy_id, kc=='s');
+	break;
+    case 'o':
+    case 'O':
+	pjsua_acc_set_online_status(g_acc_id, kc=='o');
+	break;
+	    
+    default:
+	PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed", kc, kc));
+	break;
+    }
+
+    PrintMainMenu();
+}
+
+static void HandleCodecMenu(TKeyCode kc) {
+    const pj_str_t ID_ALL = {"*", 1};
+    pj_str_t codec = {NULL, 0};
+    
+    if (kc == 'a') {
+	pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_NORMAL);
+	PJ_LOG(3,(THIS_FILE, "All codecs activated"));
+    } else {
+	switch (kc) {
+	case 'd':
+	    codec = pj_str("AMR");
+	    break;
+	case 'g':
+	    codec = pj_str("G729");
+	    break;
+	case 'j':
+	    codec = pj_str("ILBC");
+	    break;
+	case 'm':
+	    codec = pj_str("SPEEX/8000");
+	    break;
+	case 'p':
+	    codec = pj_str("GSM");
+	    break;
+	case 't':
+	    codec = pj_str("PCMU");
+	    break;
+	case 'w':
+	    codec = pj_str("PCMA");
+	    break;
+	default:
+	    PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed", kc, kc));
+	    break;
+	}
+
+	if (codec.slen) {
+	    pj_status_t status;
+	    
+	    pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_DISABLED);
+		
+	    status = pjsua_codec_set_priority(&codec, 
+					      PJMEDIA_CODEC_PRIO_NORMAL);
+	    if (status == PJ_SUCCESS)
+		PJ_LOG(3,(THIS_FILE, "%s activated", codec.ptr));
+	    else
+		PJ_LOG(3,(THIS_FILE, "Failed activating %s, err=%d", 
+			  codec.ptr, status));
+	}
+    }
 }
 
@@ -514,63 +694,28 @@
 void ConsoleUI::RunL() 
 {
+    enum {
+	MENU_TYPE_MAIN = 0,
+	MENU_TYPE_CODEC = 1
+    };
+    static int menu_type = MENU_TYPE_MAIN;
     TKeyCode kc = con_->KeyCode();
     pj_bool_t reschedule = PJ_TRUE;
     
-    switch (kc) {
-    case 'w':
+    if (menu_type == MENU_TYPE_MAIN) {
+	if (kc == 'w') {
 	    CActiveScheduler::Stop();
 	    reschedule = PJ_FALSE;
-	    break;
-    case 'D':
-    case 'd':
-	    pjsua_dump(kc == 'D');
-	    break;
-    case 'p':
-    case 'P':
-	    pj_pool_factory_dump(pjsua_get_pool_factory(), PJ_TRUE);
-	    break;
-    case 'l':
-		pjsua_conf_connect(0, 0);
-	    break;
-    case 'L':
-		pjsua_conf_disconnect(0, 0);
-	    break;
-    case 'm':
-	    if (g_call_id != PJSUA_INVALID_ID) {
-		    PJ_LOG(3,(THIS_FILE, "Another call is active"));	
-		    break;
-	    }
-    
-	    if (pjsua_verify_sip_url(SIP_DST_URI) == PJ_SUCCESS) {
-		    pj_str_t dst = pj_str(SIP_DST_URI);
-		    pjsua_call_make_call(g_acc_id, &dst, 0, NULL,
-					 NULL, &g_call_id);
-	    } else {
-		    PJ_LOG(3,(THIS_FILE, "Invalid SIP URI"));
-	    }
-	    break;
-    case 'a':
-	    if (g_call_id != PJSUA_INVALID_ID)
-		    pjsua_call_answer(g_call_id, 200, NULL, NULL);
-	    break;
-    case 'g':
-	    pjsua_call_hangup_all();
-	    break;
-    case 's':
-    case 'S':
-	    if (g_buddy_id != PJSUA_INVALID_ID)
-		    pjsua_buddy_subscribe_pres(g_buddy_id, kc=='s');
-	    break;
-    case 'o':
-    case 'O':
-	    pjsua_acc_set_online_status(g_acc_id, kc=='o');
-	    break;
-    default:
-	    PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed",
-		      kc, kc));
-	    break;
-    }
-
-    PrintMenu();
+	} else if (kc == 'd') {
+	    menu_type = MENU_TYPE_CODEC;
+	    PrintCodecMenu();
+	} else {
+	    HandleMainMenu(kc);
+	}
+    } else {
+	HandleCodecMenu(kc);
+	
+	menu_type = MENU_TYPE_MAIN;
+	PrintMainMenu();
+    }
     
     if (reschedule)
@@ -816,5 +961,5 @@
 	    
 	    PJ_LOG(3, (THIS_FILE, "PJSUA restarted."));
-	    PrintMenu();
+	    PrintMainMenu();
 	}
 	
@@ -874,5 +1019,5 @@
     
     con->Run();
-    PrintMenu();
+    PrintMainMenu();
 
     // Init & start connection monitor
@@ -911,5 +1056,5 @@
     aConn.Close();
     aSocketServer.Close();
-	
+    
     return status;
 }
Index: pjproject/trunk/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
===================================================================
--- pjproject/trunk/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp	(revision 2505)
+++ pjproject/trunk/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp	(revision 2506)
@@ -40,4 +40,5 @@
 STATICLIBRARY	pjsua_lib.lib pjsip_ua.lib
 STATICLIBRARY	pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
+STATICLIBRARY	pjmedia_audiodev.lib
 STATICLIBRARY	pjnath.lib pjlib_util.lib pjlib.lib
 STATICLIBRARY	libsrtp.lib
@@ -50,10 +51,8 @@
     CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
 #elif SND_USE_APS
-    STATICLIBRARY	symbian_audio_aps.lib
-    LIBRARY		APSSession2.lib
+    LIBRARY			APSSession2.lib
     CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
-    MACRO		PJMEDIA_SYM_SND_USE_APS=1
+    MACRO			PJMEDIA_SYM_SND_USE_APS=1
 #else
-    STATICLIBRARY	symbian_audio.lib
     LIBRARY 		mediaclientaudiostream.lib
     LIBRARY 		mediaclientaudioinputstream.lib
Index: pjproject/trunk/pjsip-apps/src/symsndtest/app_main.cpp
===================================================================
--- pjproject/trunk/pjsip-apps/src/symsndtest/app_main.cpp	(revision 2505)
+++ pjproject/trunk/pjsip-apps/src/symsndtest/app_main.cpp	(revision 2506)
@@ -50,5 +50,5 @@
 
     PJ_UNUSED_ARG(level);
-    
+
     pj_ansi_to_unicode(buf, len, buf16, PJ_ARRAY_SIZE(buf16));
 
@@ -58,7 +58,7 @@
 
 /* perror util */
-static void app_perror(const char *title, pj_status_t status) 
-{
-    char errmsg[PJ_ERR_MSG_SIZE];	
+static void app_perror(const char *title, pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
     pj_strerror(status, errmsg, sizeof(errmsg));
     PJ_LOG(1,(THIS_FILE, "Error: %s: %s", title, errmsg));
@@ -66,14 +66,14 @@
 
 /* Application init */
-static pj_status_t app_init() 
+static pj_status_t app_init()
 {
     unsigned i, count;
     pj_status_t status;
-   
+
     /* Redirect log */
     pj_log_set_log_func((void (*)(int,const char*,int)) &log_writer);
     pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
     pj_log_set_level(3);
-    
+
     /* Init pjlib */
     status = pj_init();
@@ -84,5 +84,5 @@
 
     pj_caching_pool_init(&cp, NULL, 0);
-    
+
     /* Init sound subsystem */
     status = pjmedia_snd_init(&cp.factory);
@@ -93,14 +93,14 @@
     	return status;
     }
-    
+
     count = pjmedia_snd_get_dev_count();
     PJ_LOG(3,(THIS_FILE, "Device count: %d", count));
     for (i=0; i<count; ++i) {
     	const pjmedia_snd_dev_info *info;
-    	
+
     	info = pjmedia_snd_get_dev_info(i);
     	PJ_LOG(3, (THIS_FILE, "%d: %s %d/%d %dHz",
     		   i, info->name, info->input_count, info->output_count,
-    		   info->default_samples_per_sec));	
+    		   info->default_samples_per_sec));
     }
 
@@ -115,6 +115,6 @@
 
     /* Init delay buffer */
-    status = pjmedia_delay_buf_create(pool, THIS_FILE, CLOCK_RATE, 
-				      SAMPLES_PER_FRAME, CHANNEL_COUNT, 
+    status = pjmedia_delay_buf_create(pool, THIS_FILE, CLOCK_RATE,
+				      SAMPLES_PER_FRAME, CHANNEL_COUNT,
 				      0, 0, &delaybuf);
     if (status != PJ_SUCCESS) {
@@ -124,5 +124,5 @@
     	//return status;
     }
-    
+
     return PJ_SUCCESS;
 }
@@ -130,8 +130,8 @@
 
 /* Sound capture callback */
-static pj_status_t rec_cb(void *user_data, 
+static pj_status_t rec_cb(void *user_data,
 			  pj_uint32_t timestamp,
 			  void *input,
-			  unsigned size) 
+			  unsigned size)
 {
     PJ_UNUSED_ARG(user_data);
@@ -154,26 +154,26 @@
 			   pj_uint32_t timestamp,
 			   void *output,
-			   unsigned size) 
+			   unsigned size)
 {
     PJ_UNUSED_ARG(user_data);
     PJ_UNUSED_ARG(timestamp);
     PJ_UNUSED_ARG(size);
-    
+
     pjmedia_delay_buf_get(delaybuf, (pj_int16_t*)output);
-    
+
     ++play_cnt;
-    return PJ_SUCCESS;	
+    return PJ_SUCCESS;
 }
 
 /* Start sound */
-static pj_status_t snd_start(unsigned flag) 
+static pj_status_t snd_start(unsigned flag)
 {
     pj_status_t status;
-    
+
     if (strm != NULL) {
     	app_perror("snd already open", PJ_EINVALIDOP);
     	return PJ_EINVALIDOP;
     }
-    
+
     if (flag==PJMEDIA_DIR_CAPTURE_PLAYBACK)
     	status = pjmedia_snd_open(-1, -1, CLOCK_RATE, CHANNEL_COUNT,
@@ -188,5 +188,5 @@
     					 SAMPLES_PER_FRAME, BITS_PER_SAMPLE,
     					 &play_cb, NULL, &strm);
-			 
+
     if (status != PJ_SUCCESS) {
     	app_perror("snd open", status);
@@ -211,17 +211,21 @@
 
 /* Stop sound */
-static pj_status_t snd_stop() 
+static pj_status_t snd_stop()
 {
     pj_time_val now;
     pj_status_t status;
-    
+
     if (strm == NULL) {
     	app_perror("snd not open", PJ_EINVALIDOP);
     	return PJ_EINVALIDOP;
     }
-    
+
+    status = pjmedia_snd_stream_stop(strm);
+    if (status != PJ_SUCCESS) {
+    	app_perror("snd failed to stop", status);
+    }
     status = pjmedia_snd_stream_close(strm);
     strm = NULL;
-    
+
     pj_gettimeofday(&now);
     PJ_TIME_VAL_SUB(now, t_start);
@@ -235,9 +239,9 @@
 
 /* Shutdown application */
-static void app_fini() 
+static void app_fini()
 {
     if (strm)
     	snd_stop();
-    
+
     pjmedia_snd_deinit();
     pjmedia_delay_buf_destroy(delaybuf);
@@ -254,8 +258,8 @@
 #include <e32base.h>
 
-class ConsoleUI : public CActive 
+class ConsoleUI : public CActive
 {
 public:
-    ConsoleUI(CActiveSchedulerWait *asw, CConsoleBase *con);
+    ConsoleUI(CConsoleBase *con);
 
     // Run console UI
@@ -264,5 +268,5 @@
     // Stop
     void Stop();
-    
+
 protected:
     // Cancel asynchronous read.
@@ -271,13 +275,12 @@
     // Implementation: called when read has completed.
     void RunL();
-    
+
 private:
-    CActiveSchedulerWait *asw_;
     CConsoleBase *con_;
 };
 
 
-ConsoleUI::ConsoleUI(CActiveSchedulerWait *asw, CConsoleBase *con) 
-: CActive(EPriorityHigh), asw_(asw), con_(con)
+ConsoleUI::ConsoleUI(CConsoleBase *con)
+: CActive(EPriorityUserInput), con_(con)
 {
     CActiveScheduler::Add(this);
@@ -285,5 +288,5 @@
 
 // Run console UI
-void ConsoleUI::Run() 
+void ConsoleUI::Run()
 {
     con_->Read(iStatus);
@@ -292,5 +295,5 @@
 
 // Stop console UI
-void ConsoleUI::Stop() 
+void ConsoleUI::Stop()
 {
     DoCancel();
@@ -298,10 +301,10 @@
 
 // Cancel asynchronous read.
-void ConsoleUI::DoCancel() 
+void ConsoleUI::DoCancel()
 {
     con_->ReadCancel();
 }
 
-static void PrintMenu() 
+static void PrintMenu()
 {
     PJ_LOG(3, (THIS_FILE, "\n\n"
@@ -315,12 +318,13 @@
 
 // Implementation: called when read has completed.
-void ConsoleUI::RunL() 
+void ConsoleUI::RunL()
 {
     TKeyCode kc = con_->KeyCode();
     pj_bool_t reschedule = PJ_TRUE;
-    
+
     switch (kc) {
     case 'w':
-	    asw_->AsyncStop();
+	    snd_stop();
+	    CActiveScheduler::Stop();
 	    reschedule = PJ_FALSE;
 	    break;
@@ -344,5 +348,5 @@
 
     PrintMenu();
-    
+
     if (reschedule)
 	Run();
@@ -351,21 +355,19 @@
 
 ////////////////////////////////////////////////////////////////////////////
-int app_main() 
+int app_main()
 {
     if (app_init() != PJ_SUCCESS)
         return -1;
-    
+
     // Run the UI
-    CActiveSchedulerWait *asw = new CActiveSchedulerWait;
-    ConsoleUI *con = new ConsoleUI(asw, console);
-    
+    ConsoleUI *con = new ConsoleUI(console);
+
     con->Run();
-    
+
     PrintMenu();
-    asw->Start();
-    
+    CActiveScheduler::Start();
+
     delete con;
-    delete asw;
-    
+
     app_fini();
     return 0;
Index: pjproject/trunk/pjsip-apps/src/symsndtest/main_symbian.cpp
===================================================================
--- pjproject/trunk/pjsip-apps/src/symsndtest/main_symbian.cpp	(revision 2505)
+++ pjproject/trunk/pjsip-apps/src/symsndtest/main_symbian.cpp	(revision 2506)
@@ -30,71 +30,8 @@
 
 // Needed by APS
-TPtrC APP_UID = _L("A000000D");
+TPtrC APP_UID = _L("A000000E");
 
 int app_main();
 
-
-////////////////////////////////////////////////////////////////////////////
-class MyTask : public CActive
-{
-public:
-    static MyTask *NewL(CActiveSchedulerWait *asw);
-    ~MyTask();
-    void Start();
-
-protected:
-    MyTask(CActiveSchedulerWait *asw);
-    void ConstructL();
-    virtual void RunL();
-    virtual void DoCancel();
-
-private:
-    RTimer timer_;
-    CActiveSchedulerWait *asw_;
-};
-
-MyTask::MyTask(CActiveSchedulerWait *asw)
-: CActive(EPriorityNormal), asw_(asw)
-{
-}
-
-MyTask::~MyTask() 
-{
-    timer_.Close();
-}
-
-void MyTask::ConstructL()
-{
-    timer_.CreateLocal();
-    CActiveScheduler::Add(this);
-}
-
-MyTask *MyTask::NewL(CActiveSchedulerWait *asw)
-{
-    MyTask *self = new (ELeave) MyTask(asw);
-    CleanupStack::PushL(self);
-
-    self->ConstructL();
-
-    CleanupStack::Pop(self);
-    return self;
-}
-
-void MyTask::Start()
-{
-    timer_.After(iStatus, 0);
-    SetActive();
-}
-
-void MyTask::RunL()
-{
-    int rc = app_main();
-    asw_->AsyncStop();
-}
-
-void MyTask::DoCancel()
-{
-
-}
 
 ////////////////////////////////////////////////////////////////////////////
@@ -106,17 +43,6 @@
     CActiveScheduler::Install(scheduler);
 
-    CActiveSchedulerWait *asw = new CActiveSchedulerWait;
-    CleanupStack::PushL(asw);
-    
-    MyTask *task = MyTask::NewL(asw);
-    task->Start();
+    app_main();
 
-    asw->Start();
-    
-    delete task;
-    
-    CleanupStack::Pop(asw);
-    delete asw;
-    
     CActiveScheduler::Install(NULL);
     CleanupStack::Pop(scheduler);
@@ -143,11 +69,11 @@
     TRAPD(startError, DoStartL());
 
-    console->Printf(_L("[press any key to close]\n"));
-    console->Getch();
-    
+    //console->Printf(_L("[press any key to close]\n"));
+    //console->Getch();
+
     delete console;
     delete cleanup;
 
-    CloseSTDLIB(); 
+    CloseSTDLIB();
 
     // Mark end of heap usage, detect memory leaks
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 2505)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 2506)
@@ -4201,4 +4201,18 @@
     unsigned		ec_tail_len;
 
+    /**
+     * Audio capture buffer length, in milliseconds.
+     *
+     * Default: PJMEDIA_SND_DEFAULT_REC_LATENCY
+     */
+    unsigned		snd_rec_latency;
+
+    /**
+     * Audio playback buffer length, in milliseconds.
+     *
+     * Default: PJMEDIA_SND_DEFAULT_PLAY_LATENCY
+     */
+    unsigned		snd_play_latency;
+
     /** 
      * Jitter buffer initial prefetch delay in msec. The value must be
@@ -4273,7 +4287,8 @@
     /**
      * Specify idle time of sound device before it is automatically closed,
-     * in seconds.
-     *
-     * Default : -1 (Disable the auto-close feature of sound device)
+     * in seconds. Use value -1 to disable the auto-close feature of sound
+     * device
+     *
+     * Default : 1
      */
     int			snd_auto_close_time;
@@ -4788,5 +4803,5 @@
 
 /**
- * Enum all sound devices installed in the system.
+ * Enum all audio devices installed in the system.
  *
  * @param info		Array of info to be initialized.
@@ -4796,4 +4811,17 @@
  *
  * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_aud_devs(pjmedia_aud_dev_info info[],
+					 unsigned *count);
+
+/**
+ * Enum all sound devices installed in the system (old API).
+ *
+ * @param info		Array of info to be initialized.
+ * @param count		On input, specifies max elements in the array.
+ *			On return, it contains actual number of elements
+ *			that have been initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  *
  *
@@ -4807,6 +4835,4 @@
 PJ_DECL(pj_status_t) pjsua_enum_snd_devs(pjmedia_snd_dev_info info[],
 					 unsigned *count);
-
-
 
 /**
@@ -4880,5 +4906,20 @@
 
 /**
- * Configure the echo canceller tail length of the sound port.
+ * Change the echo cancellation settings.
+ *
+ * The behavior of this function depends on whether the sound device is
+ * currently active, and if it is, whether device or software AEC is 
+ * being used. 
+ *
+ * If the sound device is currently active, and if the device supports AEC,
+ * this function will forward the change request to the device and it will
+ * be up to the device on whether support the request. If software AEC is
+ * being used (the software EC will be used if the device does not support
+ * AEC), this function will change the software EC settings. In all cases,
+ * the setting will be saved for future opening of the sound device.
+ *
+ * If the sound device is not currently active, this will only change the
+ * default AEC settings and the setting will be applied next time the 
+ * sound device is opened.
  *
  * @param tail_ms	The tail length, in miliseconds. Set to zero to
@@ -4898,5 +4939,5 @@
 
 /**
- * Get current echo canceller tail length.
+ * Get current echo canceller tail length. 
  *
  * @param p_tail_ms	Pointer to receive the tail length, in miliseconds. 
@@ -4912,4 +4953,67 @@
 PJ_DECL(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms);
 
+
+/**
+ * Check whether the sound device is currently active. The sound device
+ * may be inactive if the application has set the auto close feature to
+ * non-zero (the snd_auto_close_time setting in #pjsua_media_config), or
+ * if null sound device or no sound device has been configured via the
+ * #pjsua_set_no_snd_dev() function.
+ */
+PJ_DECL(pj_bool_t) pjsua_snd_is_active(void);
+
+    
+/**
+ * Configure sound device setting to the sound device being used. If sound 
+ * device is currently active, the function will forward the setting to the
+ * sound device instance to be applied immediately, if it supports it. 
+ *
+ * The setting will be saved for future opening of the sound device, if the 
+ * "keep" argument is set to non-zero. If the sound device is currently
+ * inactive, and the "keep" argument is false, this function will return
+ * error.
+ * 
+ * Note that in case the setting is kept for future use, it will be applied
+ * to any devices, even when application has changed the sound device to be
+ * used.
+ *
+ * Note also that the echo cancellation setting should be set with 
+ * #pjsua_set_ec() API instead.
+ *
+ * See also #pjmedia_aud_stream_set_cap() for more information about setting
+ * an audio device capability.
+ *
+ * @param cap		The sound device setting to change.
+ * @param pval		Pointer to value. Please see #pjmedia_aud_dev_cap
+ *			documentation about the type of value to be 
+ *			supplied for each setting.
+ * @param keep		Specify whether the setting is to be kept for future
+ *			use.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_snd_set_setting(pjmedia_aud_dev_cap cap,
+					   const void *pval,
+					   pj_bool_t keep);
+
+/**
+ * Retrieve a sound device setting. If sound device is currently active,
+ * the function will forward the request to the sound device. If sound device
+ * is currently inactive, and if application had previously set the setting
+ * and mark the setting as kept, then that setting will be returned.
+ * Otherwise, this function will return error.
+ *
+ * Note that echo cancellation settings should be retrieved with 
+ * #pjsua_get_ec_tail() API instead.
+ *
+ * @param cap		The sound device setting to retrieve.
+ * @param pval		Pointer to receive the value. 
+ *			Please see #pjmedia_aud_dev_cap documentation about
+ *			the type of value to be supplied for each setting.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_snd_get_setting(pjmedia_aud_dev_cap cap,
+					   void *pval);
 
 
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 2505)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 2506)
@@ -273,6 +273,13 @@
     pjsua_conf_setting	 mconf_cfg; /**< Additionan conf. bridge. param */
     pjmedia_conf	*mconf;	    /**< Conference bridge.		*/
-    int			 cap_dev;   /**< Capture device ID.		*/
-    int			 play_dev;  /**< Playback device ID.		*/
+    pj_bool_t		 is_mswitch;/**< Are we using audio switchboard
+				         (a.k.a APS-Direct)		*/
+
+    /* Sound device */
+    pjmedia_aud_dev_index cap_dev;  /**< Capture device ID.		*/
+    pjmedia_aud_dev_index play_dev; /**< Playback device ID.		*/
+    pj_uint32_t		 aud_svmask;/**< Which settings to save		*/
+    pjmedia_aud_param	 aud_param; /**< User settings to sound dev	*/
+    pj_bool_t		 aud_open_cnt;/**< How many # device is opened	*/
     pj_bool_t		 no_snd;    /**< No sound (app will manage it)	*/
     pj_pool_t		*snd_pool;  /**< Sound's private pool.		*/
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 2505)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 2506)
@@ -371,7 +371,11 @@
     PJSUA_LOCK();
 
-    /* Create sound port if none is instantiated */
-    if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && 
-	!pjsua_var.no_snd) 
+    /* Create sound port if none is instantiated, to check if sound device
+     * can be used. But only do this with the conference bridge, as with 
+     * audio switchboard (i.e. APS-Direct), we can only open the sound 
+     * device once the correct format has been known
+     */
+    if (!pjsua_var.is_mswitch && pjsua_var.snd_port==NULL && 
+	pjsua_var.null_snd==NULL && !pjsua_var.no_snd) 
     {
 	pj_status_t status;
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 2505)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 2506)
@@ -178,6 +178,8 @@
     cfg->ilbc_mode = PJSUA_DEFAULT_ILBC_MODE;
     cfg->ec_tail_len = PJSUA_DEFAULT_EC_TAIL_LEN;
+    cfg->snd_rec_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+    cfg->snd_play_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
     cfg->jb_init = cfg->jb_min_pre = cfg->jb_max_pre = cfg->jb_max = -1;
-    cfg->snd_auto_close_time = -1;
+    cfg->snd_auto_close_time = 1;
 
     cfg->turn_conn_type = PJ_TURN_TP_UDP;
@@ -582,5 +584,6 @@
 
     /* Set default sound device ID */
-    pjsua_var.cap_dev = pjsua_var.play_dev = -1;
+    pjsua_var.cap_dev = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+    pjsua_var.play_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
 
     /* Init caching pool. */
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 2505)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 2506)
@@ -36,6 +36,16 @@
 static pj_uint16_t next_rtp_port;
 
+/* Open sound dev */
+static pj_status_t open_snd_dev(pjmedia_aud_param *param);
 /* Close existing sound device */
 static void close_snd_dev(void);
+/* Create audio device param */
+static pj_status_t create_aud_param(pjmedia_aud_param *param,
+				    pjmedia_aud_dev_index capture_dev,
+				    pjmedia_aud_dev_index playback_dev,
+				    unsigned clock_rate,
+				    unsigned channel_count,
+				    unsigned samples_per_frame,
+				    unsigned bits_per_sample);
 
 
@@ -60,4 +70,14 @@
     /* To suppress warning about unused var when all codecs are disabled */
     PJ_UNUSED_ARG(codec_id);
+
+    /* Specify which audio device settings are save-able */
+    pjsua_var.aud_svmask = 0xFFFFFFFF;
+    /* These are not-settable */
+    pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
+			      PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
+			      PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
+    /* EC settings use different API */
+    pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
+			      PJMEDIA_AUD_DEV_CAP_EC_TAIL);
 
     /* Copy configuration */
@@ -172,4 +192,14 @@
 
 #endif /* PJMEDIA_HAS_INTEL_IPP */
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODECS
+    /* Register passthrough codecs */
+    status = pjmedia_codec_passthrough_init(pjsua_var.med_endpt);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
+		     status);
+	return status;
+    }
+#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
 
 #if PJMEDIA_HAS_L16_CODEC
@@ -226,4 +256,8 @@
 	return status;
     }
+
+    /* Are we using the audio switchboard (a.k.a APS-Direct)? */
+    pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
+			    ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
 
     /* Create null port just in case user wants to use null sound. */
@@ -574,4 +608,8 @@
 	    pjmedia_codec_ipp_deinit();
 #	endif	/* PJMEDIA_HAS_INTEL_IPP */
+
+#	if PJMEDIA_HAS_PASSTHROUGH_CODECS
+	    pjmedia_codec_passthrough_deinit();
+#	endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
 
 #	if PJMEDIA_HAS_L16_CODEC
@@ -1496,5 +1534,4 @@
 }
 
-
 /*
  * Get maxinum number of conference ports.
@@ -1611,15 +1648,83 @@
     }
 
-    /* Create sound port if none is instantiated */
-    if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && 
-	!pjsua_var.no_snd) 
-    {
+
+    /* For audio switchboard (i.e. APS-Direct):
+     * Check if sound device need to be reopened, i.e: its attributes
+     * (format, clock rate, channel count) must match to peer's. 
+     * Note that sound device can be reopened only if it doesn't have
+     * any connection.
+     */
+    if (pjsua_var.is_mswitch) {
+	pjmedia_conf_port_info port0_info;
+	pjmedia_conf_port_info peer_info;
+	unsigned peer_id;
+	pj_bool_t need_reopen = PJ_FALSE;
 	pj_status_t status;
 
-	status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Error opening sound device", status);
-	    return status;
-	}
+	peer_id = (source!=0)? source : sink;
+	status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id, 
+					    &peer_info);
+	pj_assert(status == PJ_SUCCESS);
+
+	status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
+	pj_assert(status == PJ_SUCCESS);
+
+	/* Check if sound device is instantiated. */
+	need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && 
+		      !pjsua_var.no_snd);
+
+	/* Check if sound device need to reopen because it needs to modify 
+	 * settings to match its peer. Sound device must be idle in this case 
+	 * though.
+	 */
+	if (!need_reopen && 
+	    port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0) 
+	{
+	    need_reopen = (peer_info.format.id != port0_info.format.id ||
+			   peer_info.format.bitrate != port0_info.format.bitrate ||
+			   peer_info.clock_rate != port0_info.clock_rate ||
+			   peer_info.channel_count != port0_info.channel_count);
+	}
+
+	if (need_reopen) {
+	    pjmedia_aud_param param;
+
+	    /* Create parameter based on peer info */
+	    status = create_aud_param(&param, pjsua_var.cap_dev, 
+				      pjsua_var.play_dev,
+				      peer_info.clock_rate,
+				      peer_info.channel_count,
+				      peer_info.samples_per_frame,
+				      peer_info.bits_per_sample);
+
+	    /* And peer format */
+	    if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
+		param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+		param.ext_fmt = peer_info.format;
+	    }
+
+	    status = open_snd_dev(&param);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Error opening sound device", status);
+		return status;
+	    }
+	}
+
+    } else {
+	/* The bridge version */
+
+	/* Create sound port if none is instantiated */
+	if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && 
+	    !pjsua_var.no_snd) 
+	{
+	    pj_status_t status;
+
+	    status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Error opening sound device", status);
+		return status;
+	    }
+	}
+
     }
 
@@ -2098,4 +2203,28 @@
  * Enum sound devices.
  */
+
+PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
+					 unsigned *count)
+{
+    unsigned i, dev_count;
+
+    dev_count = pjmedia_aud_dev_count();
+    
+    if (dev_count > *count) dev_count = *count;
+
+    for (i=0; i<dev_count; ++i) {
+	pj_status_t status;
+
+	status = pjmedia_aud_dev_get_info(i, &info[i]);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    *count = dev_count;
+
+    return PJ_SUCCESS;
+}
+
+
 PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
 					 unsigned *count)
@@ -2103,13 +2232,22 @@
     unsigned i, dev_count;
 
-    dev_count = pjmedia_snd_get_dev_count();
+    dev_count = pjmedia_aud_dev_count();
     
     if (dev_count > *count) dev_count = *count;
+    pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
 
     for (i=0; i<dev_count; ++i) {
-	const pjmedia_snd_dev_info *ci;
-
-	ci = pjmedia_snd_get_dev_info(i);
-	pj_memcpy(&info[i], ci, sizeof(*ci));
+	pjmedia_aud_dev_info ai;
+	pj_status_t status;
+
+	status = pjmedia_aud_dev_get_info(i, &ai);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	strncpy(info[i].name, ai.name, sizeof(info[i].name));
+	info[i].name[sizeof(info[i].name)-1] = '\0';
+	info[i].input_count = ai.input_count;
+	info[i].output_count = ai.output_count;
+	info[i].default_samples_per_sec = ai.default_samples_per_sec;
     }
 
@@ -2119,55 +2257,122 @@
 }
 
-
-/* Close existing sound device */
-static void close_snd_dev(void)
-{
-    /* Close sound device */
-    if (pjsua_var.snd_port) {
-	const pjmedia_snd_dev_info *cap_info, *play_info;
-
-	cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
-	play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
-
-	PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
-			     "%s sound capture device",
-			     play_info->name, cap_info->name));
-
-	pjmedia_snd_port_disconnect(pjsua_var.snd_port);
-	pjmedia_snd_port_destroy(pjsua_var.snd_port);
-	pjsua_var.snd_port = NULL;
-    }
-
-    /* Close null sound device */
-    if (pjsua_var.null_snd) {
-	PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
-	pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
-	pjsua_var.null_snd = NULL;
-    }
-
-    if (pjsua_var.snd_pool) 
-	pj_pool_release(pjsua_var.snd_pool);
-    pjsua_var.snd_pool = NULL;
-}
-
-/*
- * Select or change sound device. Application may call this function at
- * any time to replace current sound device.
- */
-PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
-				       int playback_dev)
+/* Create audio device parameter to open the device */
+static pj_status_t create_aud_param(pjmedia_aud_param *param,
+				    pjmedia_aud_dev_index capture_dev,
+				    pjmedia_aud_dev_index playback_dev,
+				    unsigned clock_rate,
+				    unsigned channel_count,
+				    unsigned samples_per_frame,
+				    unsigned bits_per_sample)
+{
+    pj_status_t status;
+
+    /* Normalize device ID with new convention about default device ID */
+    if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
+	playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+
+    /* Create default parameters for the device */
+    status = pjmedia_aud_dev_default_param(capture_dev, param);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error retrieving default audio "
+				"device parameters", status);
+	return status;
+    }
+    param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    param->rec_id = capture_dev;
+    param->play_id = playback_dev;
+    param->clock_rate = clock_rate;
+    param->channel_count = channel_count;
+    param->samples_per_frame = samples_per_frame;
+    param->bits_per_sample = bits_per_sample;
+
+    /* Update the setting with user preference */
+#define update_param(cap, field)    \
+	if (pjsua_var.aud_param.flags & cap) { \
+	    param->flags |= cap; \
+	    param->field = pjsua_var.aud_param.field; \
+	}
+    update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
+    update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
+    update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
+    update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
+#undef update_param
+
+    /* Latency settings */
+    param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | 
+		     PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
+    param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
+    param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
+
+    /* EC settings */
+    if (pjsua_var.media_cfg.ec_tail_len) {
+	param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+	param->ec_enabled = PJ_TRUE;
+	param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
+    } else {
+	param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* Internal: the first time the audio device is opened (during app
+ *   startup), retrieve the audio settings such as volume level
+ *   so that aud_get_settings() will work.
+ */
+static pj_status_t update_initial_aud_param()
+{
+    pjmedia_aud_stream *strm;
+    pjmedia_aud_param param;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
+
+    strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+
+    status = pjmedia_aud_stream_get_param(strm, &param);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error audio stream "
+				"device parameters", status);
+	return status;
+    }
+
+#define update_saved_param(cap, field)  \
+	if (param.flags & cap) { \
+	    pjsua_var.aud_param.flags |= cap; \
+	    pjsua_var.aud_param.field = param.field; \
+	}
+
+    update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
+    update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
+    update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
+    update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
+#undef update_saved_param
+
+    return PJ_SUCCESS;
+}
+
+/* Get format name */
+static const char *get_fmt_name(pj_uint32_t id)
+{
+    static char name[8];
+
+    if (id == PJMEDIA_FORMAT_L16)
+	return "PCM";
+    pj_memcpy(name, &id, 4);
+    name[4] = '\0';
+    return name;
+}
+
+/* Open sound device with the setting. */
+static pj_status_t open_snd_dev(pjmedia_aud_param *param)
 {
     pjmedia_port *conf_port;
-    const pjmedia_snd_dev_info *play_info;
-    unsigned clock_rates[] = {0, 44100, 48000, 32000, 16000, 8000};
-    unsigned selected_clock_rate = 0;
-    unsigned i;
-    pjmedia_snd_stream *strm;
-    pjmedia_snd_stream_info si;
-    pj_str_t tmp;
-    pj_status_t status = -1;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(param, PJ_EINVAL);
 
     /* Check if NULL sound device is used */
-    if (NULL_SND_DEV_ID == capture_dev || NULL_SND_DEV_ID == playback_dev) {
+    if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
 	return pjsua_set_null_snd_dev();
     }
@@ -2180,8 +2385,15 @@
     PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
 
-    /* Set default clock rate */
-    clock_rates[0] = pjsua_var.media_cfg.snd_clock_rate;
-    if (clock_rates[0] == 0)
-	clock_rates[0] = pjsua_var.media_cfg.clock_rate;
+
+    PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
+	      get_fmt_name(param->ext_fmt.id),
+	      param->clock_rate, param->channel_count,
+	      param->samples_per_frame / param->channel_count * 1000 /
+	      param->clock_rate));
+
+    status = pjmedia_snd_port_create2( pjsua_var.snd_pool, 
+				       param,  &pjsua_var.snd_port);
+    if (status != PJ_SUCCESS)
+	return status;
 
     /* Get the port0 of the conference bridge. */
@@ -2189,83 +2401,54 @@
     pj_assert(conf_port != NULL);
 
-    /* Attempts to open the sound device with different clock rates */
-    for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
-	char errmsg[PJ_ERR_MSG_SIZE];
-	unsigned samples_per_frame;
-
-	PJ_LOG(4,(THIS_FILE, 
-		  "pjsua_set_snd_dev(): attempting to open devices "
-		  "@%d Hz", clock_rates[i]));
-
-	samples_per_frame = clock_rates[i] *
-			    pjsua_var.media_cfg.audio_frame_ptime *
-			    pjsua_var.media_cfg.channel_count / 1000;
-
-	/* Create the sound device. Sound port will start immediately. */
-	status = pjmedia_snd_port_create(pjsua_var.snd_pool, capture_dev,
-					 playback_dev, 
-					 clock_rates[i], 
-					 pjsua_var.media_cfg.channel_count,
-					 samples_per_frame,
-					 16, 0, &pjsua_var.snd_port);
-
-	if (status == PJ_SUCCESS) {
-	    selected_clock_rate = clock_rates[i];
-
-	    /* If there's mismatch between sound port and conference's port,
-	     * create a resample port to bridge them.
-	     */
-	    if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
-		pjmedia_port *resample_port;
-		unsigned resample_opt = 0;
-
-		if (pjsua_var.media_cfg.quality >= 3 &&
-		    pjsua_var.media_cfg.quality <= 4)
-		{
-		    resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
-		}
-		else if (pjsua_var.media_cfg.quality < 3) {
-		    resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
-		}
-		
-		status = pjmedia_resample_port_create(pjsua_var.snd_pool, 
-						      conf_port,
-						      selected_clock_rate,
-						      resample_opt, 
-						      &resample_port);
-		if (status != PJ_SUCCESS) {
-		    pj_strerror(status, errmsg, sizeof(errmsg));
-		    PJ_LOG(4, (THIS_FILE, 
-			       "Error creating resample port, trying next "
-			       "clock rate", 
-			       errmsg));
-		    pjmedia_snd_port_destroy(pjsua_var.snd_port);
-		    pjsua_var.snd_port = NULL;
-		    continue;
-		} else {
-		    conf_port = resample_port;
-		    break;
-		}
-
-	    } else {
-		break;
-	    }
-	}
-
-	pj_strerror(status, errmsg, sizeof(errmsg));
-	PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
-    }
-
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to open sound device", status);
-	return status;
-    }
-
-    /* Set AEC */
-    pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.snd_pool, 
-			     pjsua_var.media_cfg.ec_tail_len, 
-			     pjsua_var.media_cfg.ec_options);
-
-    /* Connect sound port to the bridge */ 	 
+    /* For conference bridge, resample if necessary if the bridge's
+     * clock rate is different than the sound device's clock rate.
+     */
+    if (!pjsua_var.is_mswitch &&
+	param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
+	conf_port->info.clock_rate != param->clock_rate)
+    {
+	pjmedia_port *resample_port;
+	unsigned resample_opt = 0;
+
+	if (pjsua_var.media_cfg.quality >= 3 &&
+	    pjsua_var.media_cfg.quality <= 4)
+	{
+	    resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
+	}
+	else if (pjsua_var.media_cfg.quality < 3) {
+	    resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
+	}
+	
+	status = pjmedia_resample_port_create(pjsua_var.snd_pool, 
+					      conf_port,
+					      param->clock_rate,
+					      resample_opt, 
+					      &resample_port);
+	if (status != PJ_SUCCESS) {
+	    char errmsg[PJ_ERR_MSG_SIZE];
+	    pj_strerror(status, errmsg, sizeof(errmsg));
+	    PJ_LOG(4, (THIS_FILE, 
+		       "Error creating resample port: %s", 
+		       errmsg));
+	    close_snd_dev();
+	    return status;
+	} 
+	    
+	conf_port = resample_port;
+    }
+
+    /* Otherwise for audio switchboard, the switch's port0 setting is
+     * derived from the sound device setting, so update the setting.
+     */
+    if (pjsua_var.is_mswitch) {
+	pj_memcpy(&conf_port->info.format, &param->ext_fmt, 
+		  sizeof(conf_port->info.format));
+	conf_port->info.clock_rate = param->clock_rate;
+	conf_port->info.samples_per_frame = param->samples_per_frame;
+	conf_port->info.channel_count = param->channel_count;
+	conf_port->info.bits_per_sample = 16;
+    }
+
+    /* Connect sound port to the bridge */
     status = pjmedia_snd_port_connect(pjsua_var.snd_port, 	 
 				      conf_port ); 	 
@@ -2279,23 +2462,143 @@
 
     /* Save the device IDs */
-    pjsua_var.cap_dev = capture_dev;
-    pjsua_var.play_dev = playback_dev;
+    pjsua_var.cap_dev = param->rec_id;
+    pjsua_var.play_dev = param->play_id;
 
     /* Update sound device name. */
-    strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
-    pjmedia_snd_stream_get_info(strm, &si);
-    play_info = pjmedia_snd_get_dev_info(si.rec_id);
-
-    if (si.clock_rate != pjsua_var.media_cfg.clock_rate) {
-	char tmp_buf[128];
-	int tmp_buf_len = sizeof(tmp_buf);
-
-	tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, "%s (%dKHz)",
-				       play_info->name, si.clock_rate/1000);
-	pj_strset(&tmp, tmp_buf, tmp_buf_len);
-        pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp); 
+    {
+	pjmedia_aud_dev_info rec_info;
+	pjmedia_aud_stream *strm;
+	pjmedia_aud_param si;
+        pj_str_t tmp;
+
+	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+	status = pjmedia_aud_stream_get_param(strm, &si);
+	if (status == PJ_SUCCESS)
+	    status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
+
+	if (status==PJ_SUCCESS) {
+	    if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
+		char tmp_buf[128];
+		int tmp_buf_len = sizeof(tmp_buf);
+
+		tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, 
+					       "%s (%dKHz)",
+					       rec_info.name, 
+					       param->clock_rate/1000);
+		pj_strset(&tmp, tmp_buf, tmp_buf_len);
+		pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp); 
+	    } else {
+		pjmedia_conf_set_port0_name(pjsua_var.mconf, 
+					    pj_cstr(&tmp, rec_info.name));
+	    }
+	}
+
+	/* Any error is not major, let it through */
+	status = PJ_SUCCESS;
+    };
+
+    /* If this is the first time the audio device is open, retrieve some
+     * settings from the device (such as volume settings) so that the
+     * pjsua_snd_get_setting() work.
+     */
+    if (pjsua_var.aud_open_cnt == 0) {
+	update_initial_aud_param();
+	++pjsua_var.aud_open_cnt;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* Close existing sound device */
+static void close_snd_dev(void)
+{
+    /* Close sound device */
+    if (pjsua_var.snd_port) {
+	pjmedia_aud_dev_info cap_info, play_info;
+	pjmedia_aud_stream *strm;
+	pjmedia_aud_param param;
+
+	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+	pjmedia_aud_stream_get_param(strm, &param);
+
+	if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
+	    cap_info.name[0] = '\0';
+	if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
+	    play_info.name[0] = '\0';
+
+	PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
+			     "%s sound capture device",
+			     play_info.name, cap_info.name));
+
+	pjmedia_snd_port_disconnect(pjsua_var.snd_port);
+	pjmedia_snd_port_destroy(pjsua_var.snd_port);
+	pjsua_var.snd_port = NULL;
+    }
+
+    /* Close null sound device */
+    if (pjsua_var.null_snd) {
+	PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
+	pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
+	pjsua_var.null_snd = NULL;
+    }
+
+    if (pjsua_var.snd_pool) 
+	pj_pool_release(pjsua_var.snd_pool);
+    pjsua_var.snd_pool = NULL;
+}
+
+
+/*
+ * Select or change sound device. Application may call this function at
+ * any time to replace current sound device.
+ */
+PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
+				       int playback_dev)
+{
+    unsigned alt_cr_cnt = 1;
+    unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
+    unsigned i;
+    pj_status_t status = -1;
+
+    /* Set default clock rate */
+    alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
+    if (alt_cr[0] == 0)
+	alt_cr[0] = pjsua_var.media_cfg.clock_rate;
+
+    /* Allow retrying of different clock rate if we're using conference 
+     * bridge (meaning audio format is always PCM), otherwise lock on
+     * to one clock rate.
+     */
+    if (pjsua_var.is_mswitch) {
+	alt_cr_cnt = 1;
     } else {
-        pjmedia_conf_set_port0_name(pjsua_var.mconf, 
-				    pj_cstr(&tmp, play_info->name));
+	alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
+    }
+
+    /* Attempts to open the sound device with different clock rates */
+    for (i=0; i<alt_cr_cnt; ++i) {
+	pjmedia_aud_param param;
+	unsigned samples_per_frame;
+
+	/* Create the default audio param */
+	samples_per_frame = alt_cr[i] *
+			    pjsua_var.media_cfg.audio_frame_ptime *
+			    pjsua_var.media_cfg.channel_count / 1000;
+	status = create_aud_param(&param, capture_dev, playback_dev, 
+				  alt_cr[i], pjsua_var.media_cfg.channel_count,
+				  samples_per_frame, 16);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Open! */
+	status = open_snd_dev(&param);
+	if (status == PJ_SUCCESS)
+	    break;
+    }
+
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to open sound device", status);
+	return status;
     }
 
@@ -2405,4 +2708,79 @@
 
 
+/*
+ * Check whether the sound device is currently active.
+ */
+PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
+{
+    return pjsua_var.snd_port != NULL;
+}
+
+
+/*
+ * Configure sound device setting to the sound device being used. 
+ */
+PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
+					   const void *pval,
+					   pj_bool_t keep)
+{
+    pj_status_t status;
+
+    /* Check if we are allowed to set the cap */
+    if ((cap & pjsua_var.aud_svmask) == 0) {
+	return PJMEDIA_EAUD_INVCAP;
+    }
+
+    /* If sound is active, set it immediately */
+    if (pjsua_snd_is_active()) {
+	pjmedia_aud_stream *strm;
+	
+	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+	status = pjmedia_aud_stream_set_cap(strm, cap, pval);
+    } else {
+	status = PJ_SUCCESS;
+    }
+
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Save in internal param for later device open */
+    if (keep) {
+	status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
+					   cap, pval);
+    }
+
+    return status;
+}
+
+/*
+ * Retrieve a sound device setting.
+ */
+PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
+					   void *pval)
+{
+    /* If sound device has never been opened before, open it to 
+     * retrieve the initial setting from the device (e.g. audio
+     * volume)
+     */
+    if (pjsua_var.aud_open_cnt==0) {
+	PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
+	pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+	close_snd_dev();
+    }
+
+    if (pjsua_snd_is_active()) {
+	/* Sound is active, retrieve from device directly */
+	pjmedia_aud_stream *strm;
+	
+	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+	return pjmedia_aud_stream_get_cap(strm, cap, pval);
+    } else {
+	/* Otherwise retrieve from internal param */
+	return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
+					 cap, pval);
+    }
+}
+
+
 /*****************************************************************************
  * Codecs.
