Index: /pjproject/trunk/Makefile
===================================================================
--- /pjproject/trunk/Makefile	(revision 253)
+++ /pjproject/trunk/Makefile	(revision 254)
@@ -2,5 +2,5 @@
 include build/host-$(HOST_NAME).mak
 
-DIRS = pjlib pjlib-util pjmedia pjsip
+DIRS = pjlib pjlib-util pjmedia pjsip pjsip-apps
 
 ifdef MINSIZE
@@ -22,7 +22,10 @@
 	pjlib-util/lib/libpjlib-util-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).a \
 	pjmedia/lib/libpjmedia-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).a \
+	pjmedia/lib/libpjmedia-codec-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).a \
        	pjsip/lib/libpjsip-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).a \
-	pjsip/lib/libpjsip-ua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).a
-BINS = 	pjsip/bin/pjsua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(HOST_EXE) 
+	pjsip/lib/libpjsip-ua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).a \
+	pjsip/lib/libpjsip-simple-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).a \
+	pjsip/lib/libpjsua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).a
+BINS = 	pjsip-apps/bin/pjsua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(HOST_EXE) 
 
 size:
Index: /pjproject/trunk/pjsip-apps/build/Makefile
===================================================================
--- /pjproject/trunk/pjsip-apps/build/Makefile	(revision 254)
+++ /pjproject/trunk/pjsip-apps/build/Makefile	(revision 254)
@@ -0,0 +1,80 @@
+include ../../build/common.mak
+
+RULES_MAK := ../../build/rules.mak
+
+PJLIB_LIB:=../../pjlib/lib/libpj-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(LIBEXT)
+PJLIB_UTIL_LIB:=../../pjlib-util/lib/libpjlib-util-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(LIBEXT)
+PJMEDIA_LIB:=../../pjmedia/lib/libpjmedia-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(LIBEXT)
+PJMEDIA_CODEC_LIB:=../../pjmedia/lib/libpjmedia-codec-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(LIBEXT)
+PJSIP_LIB:=../../pjsip/lib/libpjsip-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(LIBEXT)
+PJSIP_UA_LIB:=../../pjsip/lib/libpjsip-ua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(LIBEXT)
+PJSIP_SIMPLE_LIB:=../../pjsip/lib/libpjsip-simple-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(LIBEXT)
+PJSUA_LIB_LIB=../../pjsip/lib/libpjsua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(LIBEXT)
+
+
+###############################################################################
+# Gather all flags.
+#
+export _CFLAGS 	:= $(CC_CFLAGS) $(OS_CFLAGS) $(HOST_CFLAGS) $(M_CFLAGS) \
+		   $(CFLAGS) $(CC_INC)../../pjsip/include $(CC_INC)../../pjlib/include \
+		   $(CC_INC)../../pjlib-util/include $(CC_INC)../../pjmedia/include
+export _CXXFLAGS:= $(_CFLAGS) $(CC_CXXFLAGS) $(OS_CXXFLAGS) $(M_CXXFLAGS) \
+		   $(HOST_CXXFLAGS) $(CXXFLAGS)
+export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJSUA_LIB_LIB)) \
+		   $(subst /,$(HOST_PSEP),$(PJSIP_UA_LIB)) \
+		   $(subst /,$(HOST_PSEP),$(PJSIP_SIMPLE_LIB)) \
+		   $(subst /,$(HOST_PSEP),$(PJSIP_LIB)) \
+		   $(subst /,$(HOST_PSEP),$(PJMEDIA_CODEC_LIB)) \
+		   $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \
+		   $(subst /,$(HOST_PSEP),$(PJLIB_UTIL_LIB)) \
+		   $(subst /,$(HOST_PSEP),$(PJLIB_LIB)) \
+		   $(CC_LDFLAGS) $(OS_LDFLAGS) $(M_LDFLAGS) $(HOST_LDFLAGS) \
+		   $(LDFLAGS) -lm
+
+
+###############################################################################
+# Defines for building PJSUA
+#
+export PJSUA_SRCDIR = ../src/pjsua
+export PJSUA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
+			main.o 
+export PJSUA_CFLAGS += $(_CFLAGS)
+export PJSUA_LDFLAGS += $(_LDFLAGS)
+export PJSUA_EXE:=../bin/pjsua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME)$(HOST_EXE)
+
+
+
+export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT 
+###############################################################################
+# Main entry
+#
+#
+TARGETS := pjsua
+
+.PHONY: $(TARGETS)
+
+all: $(TARGETS)
+
+doc:
+	cd .. && doxygen docs/doxygen.cfg
+
+dep: depend
+distclean: realclean
+
+.PHONY: dep depend pjsua clean realclean distclean
+
+pjsua:
+	$(MAKE) -f $(RULES_MAK) APP=PJSUA app=pjsua $(PJSUA_EXE)
+
+.PHONY: ../lib/pjsua.ko
+../lib/pjsua.ko:
+	$(MAKE) -f $(RULES_MAK) APP=PJSUA app=pjsua $@
+
+clean depend realclean:
+	$(MAKE) -f $(RULES_MAK) APP=PJSUA app=pjsua $@
+	@if test "$@" == "depend"; then \
+	  echo '$(PJSUA_EXE): $(PJSIP_LIB) $(PJSIP_UA_LIB) $(PJSIP_SIMPLE) $(PJSUA_LIB_LIB) $(PJLIB_LIB) $(PJLIB_UTIL_LIB) $(PJMEDIA_LIB) $(PJMEDIA_CODEC_LIB)' >> .pjsua-$(MACHINE_NAME)-$(OS_NAME)-$(CC_NAME).depend; \
+	fi
+
+
+
Index: /pjproject/trunk/pjsip-apps/build/os-win32.mak
===================================================================
--- /pjproject/trunk/pjsip-apps/build/os-win32.mak	(revision 254)
+++ /pjproject/trunk/pjsip-apps/build/os-win32.mak	(revision 254)
@@ -0,0 +1,2 @@
+
+export LDFLAGS += -lwinmm
Index: /pjproject/trunk/pjsip-apps/build/pjsip_apps.dsw
===================================================================
--- /pjproject/trunk/pjsip-apps/build/pjsip_apps.dsw	(revision 254)
+++ /pjproject/trunk/pjsip-apps/build/pjsip_apps.dsw	(revision 254)
@@ -0,0 +1,185 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "pjlib"="..\..\pjlib\build\pjlib.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjlib_util"="..\..\pjlib-util\build\pjlib_util.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+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: "pjsip_core"="..\..\pjsip\build\pjsip_core.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjsip_perf"=".\pjsip_perf.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
+}}}
+
+###############################################################################
+
+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>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjsua"=".\pjsua.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
+}}}
+
+###############################################################################
+
+Project: "pjsua_lib"="..\..\pjsip\build\pjsua_lib.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
Index: /pjproject/trunk/pjsip-apps/build/pjsip_perf.dsp
===================================================================
--- /pjproject/trunk/pjsip-apps/build/pjsip_perf.dsp	(revision 254)
+++ /pjproject/trunk/pjsip-apps/build/pjsip_perf.dsp	(revision 254)
@@ -0,0 +1,114 @@
+# Microsoft Developer Studio Project File - Name="pjsip_perf" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=pjsip_perf - 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 "pjsip_perf.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 "pjsip_perf.mak" CFG="pjsip_perf - Win32 Debug"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "pjsip_perf - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "pjsip_perf - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE 
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "pjsip_perf - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir ".\output\pjsip-perf-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir ".\output\pjsip-perf-i386-win32-vc6-release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir ".\output\pjsip-perf-i386-win32-vc6-release"
+# PROP Intermediate_Dir ".\output\pjsip-perf-i386-win32-vc6-release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "../../pjsip/include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"..\bin\pjsip-perf-i386-win32-vc6d.exe"
+
+!ELSEIF  "$(CFG)" == "pjsip_perf - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir ".\output\pjsip-perf-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir ".\output\pjsip-perf-i386-win32-vc6-debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir ".\output\pjsip-perf-i386-win32-vc6-debug"
+# PROP Intermediate_Dir ".\output\pjsip-perf-i386-win32-vc6-debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../../pjsip/include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"..\bin\pjsip-perf-i386-win32-vc6d.exe" /pdbtype:sept
+
+!ENDIF 
+
+# Begin Target
+
+# Name "pjsip_perf - Win32 Release"
+# Name "pjsip_perf - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE="..\src\pjsip-perf\handler_call.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjsip-perf\handler_options.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjsip-perf\main.c"
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE="..\src\pjsip-perf\pjsip_perf.h"
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
Index: /pjproject/trunk/pjsip-apps/build/pjsua.dsp
===================================================================
--- /pjproject/trunk/pjsip-apps/build/pjsua.dsp	(revision 254)
+++ /pjproject/trunk/pjsip-apps/build/pjsua.dsp	(revision 254)
@@ -0,0 +1,105 @@
+# Microsoft Developer Studio Project File - Name="pjsua" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=pjsua - 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 "pjsua.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 "pjsua.mak" CFG="pjsua - Win32 Debug"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "pjsua - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "pjsua - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE 
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""$/pjproject/pjsip/build", RIAAAAAA"
+# PROP Scc_LocalPath "."
+CPP=cl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "pjsua - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir ".\output\pjsua-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir ".\output\pjsua-i386-win32-vc6-release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir ".\output\pjsua-i386-win32-vc6-release"
+# PROP Intermediate_Dir ".\output\pjsua-i386-win32-vc6-release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../../pjsip/include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /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
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 dsound.lib dxguid.lib netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /map /machine:I386 /out:"../bin/pjsua_vc6.exe" /fixed:no
+# SUBTRACT LINK32 /pdb:none /debug
+
+!ELSEIF  "$(CFG)" == "pjsua - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir ".\output\pjsua-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir ".\output\pjsua-i386-win32-vc6-debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir ".\output\pjsua-i386-win32-vc6-debug"
+# PROP Intermediate_Dir ".\output\pjsua-i386-win32-vc6-debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../../pjsip/include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /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
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 dsound.lib dxguid.lib netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"../bin/pjsua_vc6d.exe" /pdbtype:sept
+
+!ENDIF 
+
+# Begin Target
+
+# Name "pjsua - Win32 Release"
+# Name "pjsua - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=..\src\pjsua\main.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
Index: /pjproject/trunk/pjsip-apps/src/pjsip-perf/handler_call.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsip-perf/handler_call.c	(revision 254)
+++ /pjproject/trunk/pjsip-apps/src/pjsip-perf/handler_call.c	(revision 254)
@@ -0,0 +1,412 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include "pjsip_perf.h"
+
+/*
+ * This file handles call generation and incoming calls.
+ */
+#define THIS_FILE   "handler_call.c"
+
+/*
+ * Dummy SDP.
+ */
+static pjmedia_sdp_session *local_sdp;
+
+
+#define TIMER_ID    1234
+
+/* Call data, to be attached to invite session. */
+struct call_data
+{
+    pjsip_inv_session	*inv;
+    pj_bool_t		 confirmed;
+    pj_timer_entry	 bye_timer;
+    void		*test_data;
+    void	       (*completion_cb)(void*,pj_bool_t);
+};
+
+
+/****************************************************************************
+ *
+ * INCOMING CALL HANDLER
+ *
+ ****************************************************************************
+ */
+
+
+static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata);
+
+/* The module instance. */
+static pjsip_module mod_call = 
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-perf-call", 13 },		/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority	        */
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &mod_call_on_rx_request,		/* on_rx_request()	*/
+    NULL,				/* on_rx_response()	*/
+    NULL,				/* on_tx_request.	*/
+    NULL,				/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+};
+
+
+
+/*
+ * Handle incoming requests.
+ * Because this module is registered to the INVITE module too, this
+ * callback may be called for requests inside a dialog.
+ */
+static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata)
+{
+    pjsip_msg *msg = rdata->msg_info.msg;
+    pjsip_dialog *dlg;
+    pjsip_inv_session *inv;
+    pjsip_tx_data *response;
+    struct call_data *call_data;
+    unsigned options;
+    pj_status_t status;
+
+
+    /* Don't want to handle anything but INVITE */
+    if (msg->line.req.method.id != PJSIP_INVITE_METHOD)
+	return PJ_FALSE;
+
+    /* Don't want to handle request that's already associated with
+     * existing dialog or transaction.
+     */
+    if (pjsip_rdata_get_dlg(rdata) || pjsip_rdata_get_tsx(rdata))
+	return PJ_FALSE;
+
+
+    /* Verify that we can handle the request. */
+    options = 0;
+    status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
+				      settings.endpt, &response);
+    if (status != PJ_SUCCESS) {
+
+	/*
+	 * No we can't handle the incoming INVITE request.
+	 */
+
+	if (response) {
+	    pjsip_response_addr res_addr;
+
+	    pjsip_get_response_addr(response->pool, rdata, &res_addr);
+	    pjsip_endpt_send_response(settings.endpt, &res_addr, response, 
+				      NULL, NULL);
+
+	} else {
+
+	    /* Respond with 500 (Internal Server Error) */
+	    pjsip_endpt_respond_stateless(settings.endpt, rdata, 500, NULL,
+					  NULL, NULL);
+	}
+
+	return PJ_TRUE;
+    } 
+
+    /*
+     * Yes we can handle the incoming INVITE request.
+     */
+
+    /* Create dialog. */
+    status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, NULL, &dlg);
+    if (status != PJ_SUCCESS) {
+	pjsip_dlg_respond(dlg, rdata, 500, NULL);
+	return PJ_TRUE;
+    }
+
+    /* Create invite session: */
+    status = pjsip_inv_create_uas( dlg, rdata, local_sdp, 0, &inv);
+    if (status != PJ_SUCCESS) {
+
+	pjsip_dlg_respond(dlg, rdata, 500, NULL);
+
+	// TODO: Need to delete dialog
+	return PJ_TRUE;
+    }
+
+    /* Create and associate call data. */
+    call_data = pj_pool_zalloc(inv->pool, sizeof(struct call_data));
+    call_data->inv = inv;
+    call_data->bye_timer.user_data = call_data;
+    inv->mod_data[mod_call.id] = call_data;
+
+    /* Answer with 200 straight away. */
+    status = pjsip_inv_initial_answer(inv, rdata,  200, 
+				      NULL, NULL, &response);
+    if (status != PJ_SUCCESS) {
+	
+	app_perror(THIS_FILE, "Unable to create 200 response", status);
+
+	pjsip_dlg_respond(dlg, rdata, 500, NULL);
+
+	// TODO: Need to delete dialog
+
+    } else {
+	status = pjsip_inv_send_msg(inv, response, NULL);
+	if (status != PJ_SUCCESS)
+	    app_perror(THIS_FILE, "Unable to send 100 response", status);
+    }
+
+
+    return PJ_TRUE;
+}
+
+
+/****************************************************************************
+ *
+ * OUTGOING CALL GENERATOR
+ *
+ ****************************************************************************
+ */
+
+/**
+ * Make outgoing call.
+ */
+pj_status_t call_spawn_test( const pj_str_t *target,
+			     const pj_str_t *from,
+			     const pj_str_t *to,
+			     unsigned cred_cnt,
+			     const pjsip_cred_info cred[],
+			     const pjsip_route_hdr *route_set,
+			     void *test_data,
+			     void (*completion_cb)(void*,pj_bool_t))
+{
+    pjsip_dialog *dlg;
+    pjsip_inv_session *inv;
+    pjsip_tx_data *tdata;
+    struct call_data *call_data;
+    pj_status_t status;
+
+    /* Create outgoing dialog: */
+    status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
+				   from, NULL, 
+				   to, target,
+				   &dlg);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Dialog creation failed", status);
+	return status;
+    }
+
+    /* Create the INVITE session: */
+    status = pjsip_inv_create_uac( dlg, local_sdp, 0, &inv);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Invite session creation failed", status);
+	goto on_error;
+    }
+
+
+    /* Set dialog Route-Set: */
+    if (route_set)
+	pjsip_dlg_set_route_set(dlg, route_set);
+
+
+    /* Set credentials: */
+    pjsip_auth_clt_set_credentials( &dlg->auth_sess, cred_cnt,  cred);
+
+
+    /* Create initial INVITE: */
+    status = pjsip_inv_invite(inv, &tdata);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to create initial INVITE request", 
+		   status);
+	goto on_error;
+    }
+
+
+    /* Create and associate our call data */
+    call_data = pj_pool_zalloc(inv->pool, sizeof(struct call_data));
+    call_data->inv = inv;
+    call_data->test_data = test_data;
+    call_data->bye_timer.user_data = call_data;
+    call_data->completion_cb = completion_cb;
+
+    inv->mod_data[mod_call.id] = call_data;
+
+
+    /* Send initial INVITE: */
+    status = pjsip_inv_send_msg(inv, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	app_perror( THIS_FILE, "Unable to send initial INVITE request", 
+		    status);
+	goto on_error;
+    }
+
+
+    return PJ_SUCCESS;
+
+
+on_error:
+    PJ_TODO(DESTROY_DIALOG_ON_FAIL);
+    return status;
+}
+
+
+/* Timer callback to send BYE. */
+static void bye_callback( pj_timer_heap_t *ht, pj_timer_entry *e)
+{
+    struct call_data *call_data = e->user_data;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    e->id = 0;
+
+    status = pjsip_inv_end_session(call_data->inv, PJSIP_SC_REQUEST_TIMEOUT, 
+				   NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to create BYE", status);
+	return;
+    }
+
+    status = pjsip_inv_send_msg(call_data->inv, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to send BYE", status);
+	return;
+    }
+
+}
+
+/*
+ * This callback receives notification from invite session when the
+ * session state has changed.
+ */
+static void call_on_state_changed( pjsip_inv_session *inv, pjsip_event *e)
+{
+    struct call_data *call_data;
+
+    call_data = inv->mod_data[mod_call.id];
+    if (call_data == NULL)
+	return;
+
+    /* Once call has been confirmed, schedule timer to terminate the call. */
+    if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
+
+	pj_time_val interval;
+
+	call_data->confirmed = PJ_TRUE;
+
+	/* For UAC, schedule time to send BYE.
+	 * For UAS, schedule time to disconnect INVITE, just in case BYE
+	 * is not received.
+	 */
+	if (inv->role == PJSIP_ROLE_UAC)
+	    interval.sec = settings.duration, interval.msec = 0;
+	else
+	    interval.sec = settings.duration+5, interval.msec = 0;
+
+	call_data->bye_timer.id = TIMER_ID;
+	call_data->bye_timer.cb = &bye_callback;
+	pjsip_endpt_schedule_timer(settings.endpt, &call_data->bye_timer,
+				   &interval);
+
+    }
+    /* If call has been terminated, cancel our timer, if any.
+     * And call tester's callback.
+     */
+    else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+
+	/* Cancel timer, if any. */
+	if (call_data->bye_timer.id == TIMER_ID) {
+	    call_data->bye_timer.id = 0;
+	    pjsip_endpt_cancel_timer(settings.endpt, &call_data->bye_timer);
+	}
+
+	/* Detach call data from the invite session. */
+	inv->mod_data[mod_call.id] = NULL;
+
+	/* Call tester callback. */
+	if (call_data->completion_cb) {
+	    (*call_data->completion_cb)(call_data->test_data,
+					call_data->confirmed);
+	}
+    }
+}
+
+
+/*
+ * This callback is called by invite session framework when UAC session
+ * has forked.
+ */
+static void call_on_forked( pjsip_inv_session *inv, pjsip_event *e)
+{
+    PJ_UNUSED_ARG(inv);
+    PJ_UNUSED_ARG(e);
+
+    PJ_TODO(HANDLE_FORKED_DIALOG);
+}
+
+
+
+/****************************************************************************
+ *
+ * INITIALIZATION
+ *
+ ****************************************************************************
+ */
+
+pj_status_t call_handler_init(void)
+{
+    pjsip_inv_callback inv_cb;
+    pjmedia_sock_info skinfo;
+    pj_status_t status;
+
+    /* Register incoming call handler. */
+    status = pjsip_endpt_register_module(settings.endpt, &mod_call);
+    if (status != PJ_SUCCESS) {
+	app_perror( THIS_FILE, "Unable to register call handler", 
+		    status);
+	return status;
+    }
+
+    /* Invite session callback: */
+    pj_memset(&inv_cb, 0, sizeof(inv_cb));
+    inv_cb.on_state_changed = &call_on_state_changed;
+    inv_cb.on_new_session = &call_on_forked;
+
+    /* Initialize invite session module: */
+    status = pjsip_inv_usage_init(settings.endpt, &mod_call, &inv_cb);
+    if (status != PJ_SUCCESS) {
+	app_perror( THIS_FILE, "Unable to initialize INVITE session module", 
+		    status);
+	return status;
+    }
+
+    /* Create dummy SDP. */
+    pj_memset(&skinfo, 0, sizeof(skinfo));
+    pj_sockaddr_in_init(&skinfo.rtp_addr_name, pj_gethostname(), 4000);
+    pj_sockaddr_in_init(&skinfo.rtcp_addr_name, pj_gethostname(), 4001);
+
+    status = pjmedia_endpt_create_sdp( settings.med_endpt, settings.pool,
+				       1, &skinfo, &local_sdp);
+    if (status != PJ_SUCCESS) {
+	app_perror( THIS_FILE, "Unable to generate local SDP", 
+		    status);
+	return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
Index: /pjproject/trunk/pjsip-apps/src/pjsip-perf/handler_options.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsip-perf/handler_options.c	(revision 254)
+++ /pjproject/trunk/pjsip-apps/src/pjsip-perf/handler_options.c	(revision 254)
@@ -0,0 +1,148 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include "pjsip_perf.h"
+
+
+/*
+ * This file handles OPTIONS generator and incoming OPTIONS requests.
+ */
+#define THIS_FILE   "handler_options.c"
+
+
+/****************************************************************************
+ *
+ * INCOMING OPTIONS HANDLER
+ *
+ ****************************************************************************
+ */
+
+
+static pj_bool_t mod_options_on_rx_request(pjsip_rx_data *rdata);
+
+
+/* The module instance. */
+static pjsip_module mod_perf_options = 
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-perf-options", 16 },		/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority	        */
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &mod_options_on_rx_request,		/* on_rx_request()	*/
+    NULL,				/* on_rx_response()	*/
+    NULL,				/* on_tx_request.	*/
+    NULL,				/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+
+};
+
+static pj_bool_t mod_options_on_rx_request(pjsip_rx_data *rdata)
+{
+    pjsip_msg *msg = rdata->msg_info.msg;
+
+    if (msg->line.req.method.id == PJSIP_OPTIONS_METHOD) {
+
+	if (settings.stateless) {
+	    pjsip_endpt_respond_stateless( settings.endpt, rdata, 200, NULL, 
+					   NULL, NULL);
+	} else {
+
+	    pjsip_endpt_respond( settings.endpt, NULL, rdata, 200, NULL,
+				 NULL, NULL, NULL);
+	}
+
+	return PJ_TRUE;
+    }
+
+    return PJ_FALSE;
+}
+
+
+/****************************************************************************
+ *
+ * OUTGOING OPTIONS GENERATOR.
+ *
+ ****************************************************************************
+ */
+
+struct callback_data
+{
+    void     *test_data;
+    void    (*completion_cb)(void*,pj_bool_t);
+};
+
+static void options_callback(void *token, const pjsip_event *e)
+{
+    struct callback_data *cb_data = token;
+
+    if (e->type == PJSIP_EVENT_TSX_STATE) {
+	(*cb_data->completion_cb)(cb_data->test_data,
+				  e->body.tsx_state.tsx->status_code/100==2);
+    }
+}
+
+pj_status_t options_spawn_test(const pj_str_t *target,
+			       const pj_str_t *from,
+			       const pj_str_t *to,
+			       unsigned cred_cnt,
+			       const pjsip_cred_info cred[],
+			       const pjsip_route_hdr *route_set,
+			       void *test_data,
+			       void (*completion_cb)(void*,pj_bool_t))
+{
+    pj_status_t status;
+    struct callback_data *cb_data;
+    pjsip_tx_data *tdata;
+
+    status = pjsip_endpt_create_request( settings.endpt, 
+					 &pjsip_options_method,
+					 target,
+					 from,
+					 to,
+					 NULL, NULL, -1, NULL,
+					 &tdata);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to create request", status);
+	return status;
+    }
+
+    cb_data = pj_pool_alloc(tdata->pool, sizeof(struct callback_data));
+    cb_data->test_data = test_data;
+    cb_data->completion_cb = completion_cb;
+
+    status = pjsip_endpt_send_request( settings.endpt, tdata, -1,
+				       cb_data, &options_callback);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to send request", status);
+	return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+pj_status_t options_handler_init(void)
+{
+    return pjsip_endpt_register_module(settings.endpt, &mod_perf_options);
+}
+
+
Index: /pjproject/trunk/pjsip-apps/src/pjsip-perf/main.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsip-perf/main.c	(revision 254)
+++ /pjproject/trunk/pjsip-apps/src/pjsip-perf/main.c	(revision 254)
@@ -0,0 +1,668 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include "pjsip_perf.h"
+#include <pjsua-lib/getopt.h>
+#include <stdlib.h>		/* atoi */
+
+#define THIS_FILE   "main.c"
+
+pjsip_perf_settings settings;
+
+/* Show error message. */
+void app_perror(const char *sender, const char *title, pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));
+    PJ_LOG(3,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+/* Init default settings. */
+static void init_settings(void)
+{
+    pj_status_t status;
+
+    settings.stateless = 1;
+    settings.start_rate = 10;
+    settings.max_capacity = 64;
+    settings.duration = 0;
+    settings.thread_cnt = 1;
+    settings.local_port = 5060;
+
+    pjsip_method_set(&settings.method, PJSIP_OPTIONS_METHOD);
+
+    pj_init();
+
+    /* Create caching pool. */
+    pj_caching_pool_init(&settings.cp, &pj_pool_factory_default_policy, 
+			 4 * 1024 * 1024);
+
+    /* Create application pool. */
+    settings.pool = pj_pool_create(&settings.cp.factory, "pjsip-perf", 1024,
+				   1024, NULL);
+
+    /* Create endpoint. */
+    status = pjsip_endpt_create(&settings.cp.factory, NULL, &settings.endpt);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to create endpoint", status);
+	return;
+    }
+
+}
+
+/* Poll function. */
+static int PJ_THREAD_FUNC poll_pjsip(void *arg)
+{
+    pj_status_t last_err = 0;
+
+    PJ_UNUSED_ARG(arg);
+
+    do {
+	pj_time_val timeout = { 0, 10 };
+	pj_status_t status;
+	
+	status = pjsip_endpt_handle_events (settings.endpt, &timeout);
+	if (status != last_err) {
+	    last_err = status;
+	    app_perror(THIS_FILE, "handle_events() returned error", status);
+	}
+    } while (!settings.quit_flag);
+
+    return 0;
+}
+
+/* Initialize */
+static pj_status_t initialize(void)
+{
+    pj_sockaddr_in addr;
+    int i;
+    pj_status_t status;
+
+    /* Create UDP transport. */
+    pj_memset(&addr, 0, sizeof(addr));
+    addr.sin_family = PJ_AF_INET;
+    addr.sin_port = pj_htons((pj_uint16_t)settings.local_port);
+    status = pjsip_udp_transport_start(settings.endpt, &addr, NULL, 
+				       settings.thread_cnt, NULL);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to start UDP transport", status);
+	return status;
+    }
+
+
+    /* Initialize transaction layer: */
+    status = pjsip_tsx_layer_init_module(settings.endpt);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Transaction layer initialization error", 
+		   status);
+	return status;
+    }
+
+    /* Initialize UA layer module: */
+    pjsip_ua_init_module( settings.endpt, NULL );
+
+    /* Init core SIMPLE module : */
+    pjsip_evsub_init_module(settings.endpt);
+
+    /* Init presence module: */
+    pjsip_pres_init_module( settings.endpt, pjsip_evsub_instance());
+
+    /* Init xfer/REFER module */
+    pjsip_xfer_init_module( settings.endpt );
+
+    /* Init multimedia endpoint. */
+    status = pjmedia_endpt_create(&settings.cp.factory, &settings.med_endpt);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to create media endpoint", 
+		   status);
+	return status;
+    }
+
+    /* Init OPTIONS test handler */
+    status = options_handler_init();
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to create OPTIONS handler", status);
+	return status;
+    }
+
+    /* Init call test handler */
+    status = call_handler_init();
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to initialize call handler", status);
+	return status;
+    }
+
+
+    /* Start worker thread. */
+    for (i=0; i<settings.thread_cnt; ++i) {
+	status = pj_thread_create(settings.pool, "pjsip-perf", &poll_pjsip,
+				  NULL, 0, 0, &settings.thread[i]);
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to create thread", status);
+	    return status;
+	}
+    }
+
+    pj_log_set_level(3);
+    return PJ_SUCCESS;
+}
+
+
+/* Shutdown */
+static void shutdown(void)
+{
+    int i;
+
+    /* Signal and wait worker thread to quit. */
+    settings.quit_flag = 1;
+
+    for (i=0; i<settings.thread_cnt; ++i) {
+	pj_thread_join(settings.thread[i]);
+	pj_thread_destroy(settings.thread[i]);
+    }
+
+    pjsip_endpt_destroy(settings.endpt);
+    pj_caching_pool_destroy(&settings.cp);
+}
+
+
+/* Verify that valid SIP url is given. */
+pj_status_t verify_sip_url(const char *c_url)
+{
+    pjsip_uri *p;
+    pj_pool_t *pool;
+    char *url;
+    int len = (c_url ? pj_ansi_strlen(c_url) : 0);
+
+    if (!len) return -1;
+
+    pool = pj_pool_create(&settings.cp.factory, "check%p", 1024, 0, NULL);
+    if (!pool) return -1;
+
+    url = pj_pool_alloc(pool, len+1);
+    pj_ansi_strcpy(url, c_url);
+
+    p = pjsip_parse_uri(pool, url, len, 0);
+    if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
+	p = NULL;
+
+    pj_pool_release(pool);
+    return p ? 0 : -1;
+}
+
+
+
+/* Usage */
+static void usage(void)
+{
+    puts("Usage:");
+    puts("  pjsip-perf [options] [target]");
+    puts("where");
+    puts("  target		Optional default target URL");
+    puts("");
+    puts("General options:");
+    puts("  --help              Display this help screen");
+    puts("  --version           Display version info");
+    puts("");
+    puts("SIP options:");
+    puts("  --local-port=N      SIP local port");
+    puts("  --stateless		Handle incoming request statelessly if possible");
+    puts("  --thread-cnt=N	Number of worker threads (default=1)");
+    puts("");
+    puts("Rate control:");
+    puts("  --start-rate=N      Start rate in tasks per seconds (default=1)");
+    puts("");
+    puts("Capacity control:");
+    puts("  --max-capacity=N    Maximum outstanding sessions (default=64)");
+    puts("");
+    puts("Duration control:");
+    puts("  --duration=secs     Sessions duration (default=0)");
+    puts("");
+}
+
+
+/* Read options. */
+static pj_status_t parse_options(int argc, char *argv[])
+{
+    enum {
+	OPT_HELP,
+	OPT_VERSION,
+	OPT_LOCAL_PORT,
+	OPT_STATELESS,
+	OPT_THREAD_CNT,
+	OPT_START_RATE,
+	OPT_MAX_CAPACITY,
+	OPT_DURATION
+    };
+    struct option long_opts[] = {
+	{ "help",	    0, 0, OPT_HELP},
+	{ "version",	    0, 0, OPT_VERSION},
+	{ "local-port",	    1, 0, OPT_LOCAL_PORT},
+	{ "stateless",	    0, 0, OPT_STATELESS},
+	{ "thread-cnt",	    1, 0, OPT_THREAD_CNT},
+	{ "start-rate",	    1, 0, OPT_START_RATE},
+	{ "max-capacity",   1, 0, OPT_MAX_CAPACITY},
+	{ "duration",	    1, 0, OPT_DURATION},
+	{ NULL, 0, 0, 0}
+    };
+    int c, option_index;
+
+    optind = 0;
+    while ((c=getopt_long(argc, argv, "", long_opts, &option_index)) != -1) {
+	switch (c) {
+
+	case OPT_HELP:
+	    usage();
+	    return PJ_EINVAL;
+
+	case OPT_VERSION:
+	    pj_dump_config();
+	    return PJ_EINVAL;
+
+	case OPT_LOCAL_PORT:
+	    settings.local_port = atoi(optarg);
+	    if (settings.local_port < 1 || settings.local_port > 65535) {
+		PJ_LOG(1,(THIS_FILE,"Invalid --local-port %s", optarg));
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	case OPT_STATELESS:
+	    settings.stateless = 1;
+	    break;
+
+	case OPT_THREAD_CNT:
+	    settings.thread_cnt = atoi(optarg);
+	    if (settings.thread_cnt < 1 || 
+		settings.thread_cnt > PJ_ARRAY_SIZE(settings.thread)) 
+	    {
+		PJ_LOG(1,(THIS_FILE,"Invalid --thread-cnt %s", optarg));
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	case OPT_START_RATE:
+	    settings.start_rate = atoi(optarg);
+	    if (settings.start_rate < 1 || settings.start_rate > 1000000) {
+		PJ_LOG(1,(THIS_FILE,"Invalid --start-rate %s", optarg));
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	case OPT_MAX_CAPACITY:
+	    settings.max_capacity = atoi(optarg);
+	    if (settings.max_capacity < 1 || settings.max_capacity > 65000) {
+		PJ_LOG(1,(THIS_FILE,
+			  "Invalid --max-capacity %s (range=1-65000)", 
+			  optarg));
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	case OPT_DURATION:
+	    settings.duration = atoi(optarg);
+	    if (settings.duration < 0 || settings.duration > 1000000) {
+		PJ_LOG(1,(THIS_FILE,"Invalid --duration %s", optarg));
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	}
+    }
+
+    if (optind != argc) {
+	if (verify_sip_url(argv[optind]) != PJ_SUCCESS) {
+	    PJ_LOG(3,(THIS_FILE, "Invalid SIP URL %s", argv[optind]));
+	    return PJ_EINVAL;
+	}
+	
+	settings.target = pj_str(argv[optind]);
+	++optind;
+    }
+
+    if (optind != argc) {
+	printf("Error: unknown options %s\n", argv[optind]);
+	return PJ_EINVAL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+static void spawn_batch( pj_timer_heap_t *timer_heap,
+			 struct pj_timer_entry *entry );
+
+/* Completion callback. */
+static void completion_cb(void *token, pj_bool_t success)
+{
+    batch *batch = token;
+
+    if (success)
+	batch->success++;
+    else
+	batch->failed++;
+
+    if (batch->success+batch->failed == batch->rate) {
+	pj_time_val elapsed, sess_elapsed;
+	unsigned msec;
+
+	pj_gettimeofday(&batch->end_time);
+	elapsed = sess_elapsed = batch->end_time;
+
+	PJ_TIME_VAL_SUB(elapsed, batch->start_time);
+	PJ_TIME_VAL_SUB(sess_elapsed, settings.session->start_time);
+	msec = PJ_TIME_VAL_MSEC(elapsed);
+	if (msec == 0) msec = 1;
+
+	PJ_LOG(3,(THIS_FILE, "%02d:%02d:%02d: %d tasks in %d.%ds (%d tasks/sec)",
+			     (sess_elapsed.sec / 3600),
+			     (sess_elapsed.sec % 3600) / 60,
+			     (sess_elapsed.sec % 60),
+			     batch->rate,
+			     elapsed.sec, elapsed.msec,
+			     batch->rate * 1000 / msec));
+
+	if (!settings.session->stopping) {
+	    pj_time_val interval;
+
+	    if (msec >= 1000)
+		interval.sec = interval.msec = 0;
+	    else
+		interval.sec = 0, interval.msec = 1000-msec;
+
+	    settings.timer.cb = &spawn_batch;
+	    pjsip_endpt_schedule_timer( settings.endpt, &settings.timer, &interval);
+	} else {
+	    PJ_LOG(3,(THIS_FILE, "%.*s test session completed",
+		      (int)settings.session->method.name.slen,
+		      settings.session->method.name.ptr));
+	    pj_pool_release(settings.session->pool);
+	    settings.session = NULL;
+	}
+    }
+}
+
+/* Spawn new batch. */
+static void spawn_batch( pj_timer_heap_t *timer_heap,
+			 struct pj_timer_entry *entry )
+{
+    session *sess = settings.session;
+    batch *batch;
+    pj_status_t status = PJ_SUCCESS;
+    pjsip_cred_info cred_info[1];
+    pj_time_val now, spawn_time, sess_time;
+
+    unsigned i;
+
+    if (!pj_list_empty(&sess->free_list)) {
+	batch = sess->free_list.next;
+	pj_list_erase(batch);
+    } else {
+	batch = pj_pool_alloc(sess->pool, sizeof(struct batch));
+    }
+
+    pj_gettimeofday(&batch->start_time);
+    batch->rate = settings.cur_rate;
+    batch->started = 0;
+    batch->success = 0;
+    batch->failed = 0;
+
+    pj_list_push_back(&sess->active_list, batch);
+
+    for (i=0; i<batch->rate; ++i) {
+	pj_str_t from = { "sip:user@127.0.0.1", 18};
+
+	if (sess->method.id == PJSIP_OPTIONS_METHOD) {
+	    status = options_spawn_test(&settings.target, &from, 
+					&settings.target,
+					0, cred_info, NULL, batch, 
+					&completion_cb);
+	} else if (sess->method.id == PJSIP_INVITE_METHOD) {
+	    status = call_spawn_test( &settings.target, &from, 
+				      &settings.target,
+				      0, cred_info, NULL, batch, 
+				      &completion_cb);
+	}
+	if (status != PJ_SUCCESS)
+	    break;
+
+	batch->started++;
+    }
+
+    pj_gettimeofday(&now);
+    spawn_time = sess_time = now;
+    PJ_TIME_VAL_SUB(spawn_time, batch->start_time);
+    PJ_TIME_VAL_SUB(sess_time, sess->start_time);
+
+    sess->total_created += batch->started;
+    
+    batch = sess->active_list.next;
+    sess->outstanding = 0;
+    while (batch != &sess->active_list) {
+	sess->outstanding += (batch->started - batch->success - batch->failed);
+
+	if (batch->started == batch->success + batch->failed) {
+	    struct batch *next = batch->next;
+	    pj_list_erase(batch);
+	    pj_list_push_back(&sess->free_list, batch);
+	    batch = next;
+	} else {
+	    batch = batch->next;
+	}
+    }
+}
+
+
+/* Start new session */
+static void start_session(pj_bool_t auto_repeat)
+{
+    pj_time_val interval = { 1, 0 };
+    pj_pool_t *pool;
+    session *sess;
+
+    pool = pjsip_endpt_create_pool(settings.endpt, "session", 4000, 4000);
+    if (!pool) {
+	app_perror(THIS_FILE, "Unable to create pool", PJ_ENOMEM);
+	return;
+    }
+
+    sess = pj_pool_zalloc(pool, sizeof(session));
+    sess->pool = pool;
+    sess->stopping = auto_repeat ? 0 : 1;
+    sess->method = settings.method;
+
+    pj_list_init(&sess->active_list);
+    pj_list_init(&sess->free_list);
+    pj_gettimeofday(&sess->start_time);
+
+    settings.session = sess;
+
+    spawn_batch(NULL, NULL);
+}
+
+
+/* Dump state */
+static void dump(pj_bool_t detail)
+{
+    pjsip_endpt_dump(settings.endpt, detail);
+    pjsip_tsx_layer_dump(detail);
+    pjsip_ua_dump(detail);
+}
+
+
+/* help screen */
+static void help_screen(void)
+{
+    puts  ("+============================================================================+");
+    printf("| Current mode: %-10s    Current rate: %-5d     Call Capacity: %-7d |\n",
+	    settings.method.name.ptr, settings.cur_rate, settings.max_capacity);
+    printf("|                                                     Call Duration: %-7d |\n",
+	    settings.duration);
+
+    puts  ("+--------------------------------------+-------------------------------------+");
+    puts  ("|          Test Settings               |           Misc Commands:            |");
+    puts  ("|                                      |                                     |");
+    puts  ("|   m  Change mode                     |                                     |");
+    puts  ("| + -  Increment/decrement rate by 10  |   d   Dump status                   |");
+    puts  ("| * /  Increment/decrement rate by 100 |   d1  Dump detailed (e.g. tables)   |");
+    puts  ("+--------------------------------------+-------------------------------------+");
+    puts  ("|                              Test Commands                                 |");
+    puts  ("|                                                                            |");
+    puts  ("|  s   Start single test batch                                               |");
+    puts  ("| sc   Start continuous test               x  Stop continuous tests          |");
+    puts  ("+----------------------------------------------------------------------------+");
+    puts  ("|  q:  Quit                                                                  |");
+    puts  ("+============================================================================+");
+    puts  ("");
+
+}
+
+/*
+ * Input simple string
+ */
+static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
+{
+    char *p;
+
+    printf("%s (empty to cancel): ", title); fflush(stdout);
+    fgets(buf, len, stdin);
+
+    /* Remove trailing newlines. */
+    for (p=buf; ; ++p) {
+	if (*p=='\r' || *p=='\n') *p='\0';
+	else if (!*p) break;
+    }
+
+    if (!*buf)
+	return PJ_FALSE;
+    
+    return PJ_TRUE;
+}
+
+/* Main input loop */
+static void test_main(void)
+{
+    char menuin[10];
+    char input[80];
+
+    settings.cur_rate = settings.start_rate;
+
+    help_screen();
+
+    for (;;) {
+	printf(">>>> "); fflush(stdout);
+
+	fgets(menuin, sizeof(menuin), stdin);
+
+	switch (menuin[0]) {
+	case 's':
+	    if (settings.session != NULL) {
+		PJ_LOG(3,(THIS_FILE,"Error: another session is in progress"));
+	    } else if (settings.target.slen == 0) {
+		PJ_LOG(3,(THIS_FILE,"Error: target URL is not configured"));
+	    } else {
+		start_session(menuin[1]=='c');
+	    }
+	    break;
+
+	case 'x':
+	    if (settings.session) {
+		settings.session->stopping = 1;
+	    } else {
+		PJ_LOG(3,(THIS_FILE,"Error: no sessions"));
+	    }
+	    break;
+
+	case 'm':
+	    if (!simple_input("Change method [OPTIONS,INVITE]", input, sizeof(input)))
+		continue;
+
+	    if (pj_ansi_stricmp(input, "OPTIONS")==0)
+		pjsip_method_set(&settings.method, PJSIP_OPTIONS_METHOD);
+	    else if (pj_ansi_stricmp(input, "INVITE")==0)
+		pjsip_method_set(&settings.method, PJSIP_INVITE_METHOD);
+	    else {
+		puts("Error: invalid method");
+	    }
+	    break;
+
+	case 'd':
+	    dump(menuin[1]=='1');
+	    break;
+
+	case '+':
+	    settings.cur_rate += 10;
+	    PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate));
+	    break;
+
+	case '-':
+	    if (settings.cur_rate > 10) {
+		settings.cur_rate -= 10;
+		PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate));
+	    }
+	    break;
+
+	case '*':
+	    settings.cur_rate += 100;
+	    PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate));
+	    break;
+
+	case '/':
+	    if (settings.cur_rate > 100) {
+		settings.cur_rate -= 100;
+		PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate));
+	    }
+	    break;
+
+	case 'q':
+	    return;
+
+	default:
+	    help_screen();
+	    break;
+
+	}
+    }
+}
+
+
+/* main() */
+int main(int argc, char *argv[])
+{
+    pj_status_t status;
+
+    init_settings();
+
+    status = parse_options(argc, argv);
+    if (status != PJ_SUCCESS)
+	return 1;
+
+    status = initialize();
+    if (status != PJ_SUCCESS)
+	return 1;
+
+
+    test_main();
+
+    shutdown();
+
+    return 0;
+}
+
Index: /pjproject/trunk/pjsip-apps/src/pjsip-perf/pjsip_perf.h
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsip-perf/pjsip_perf.h	(revision 254)
+++ /pjproject/trunk/pjsip-apps/src/pjsip-perf/pjsip_perf.h	(revision 254)
@@ -0,0 +1,164 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJSIP_PERF_H__
+#define __PJSIP_PERF_H__
+
+#include <pjsua-lib/pjsua.h>
+
+
+PJ_BEGIN_DECL
+
+
+typedef struct batch batch;
+typedef struct session session;
+
+/**
+ * A test batch.
+ */
+struct batch
+{
+    PJ_DECL_LIST_MEMBER(struct batch);
+
+    unsigned	 rate;
+    unsigned	 started;
+    unsigned	 success;
+    unsigned	 failed;
+    pj_time_val	 start_time;
+    pj_time_val	 end_time;
+};
+
+/**
+ * Test session.
+ */
+struct session
+{
+    pj_pool_t	    *pool;
+    pj_time_val	     start_time;
+    pj_bool_t	     stopping;
+    pjsip_method     method;
+    struct batch     active_list;
+    struct batch     free_list;
+
+    unsigned	     outstanding;
+    unsigned	     total_created;
+};
+
+
+/**
+ * Request parameter.
+ */
+struct request_param
+{
+    pj_str_t	    dst;
+    pj_str_t	    src;
+    pjsip_cred_info cred;
+};
+
+
+typedef struct request_param request_param;
+
+
+void app_perror(const char *sender, const char *title, pj_status_t status);
+
+/* OPTIONS test */
+pj_status_t options_handler_init(void);
+pj_status_t options_spawn_test(const pj_str_t *target,
+			       const pj_str_t *from,
+			       const pj_str_t *to,
+			       unsigned cred_cnt,
+			       const pjsip_cred_info cred[],
+			       const pjsip_route_hdr *route_set,
+			       void *test_data,
+			       void (*completion_cb)(void*,pj_bool_t));
+
+/* CALL test */
+pj_status_t call_handler_init(void);
+pj_status_t call_spawn_test( const pj_str_t *target,
+			     const pj_str_t *from,
+			     const pj_str_t *to,
+			     unsigned cred_cnt,
+			     const pjsip_cred_info cred[],
+			     const pjsip_route_hdr *route_set,
+			     void *test_data,
+			     void (*completion_cb)(void*,pj_bool_t));
+
+
+
+/**
+ * Global settings
+ */
+struct pjsip_perf_settings
+{
+    /* Global */
+    pj_caching_pool  cp;
+    pj_pool_t	    *pool;
+    pjsip_endpoint  *endpt;
+    pj_mutex_t	    *mutex;
+
+    /* Network: */
+    int		     local_port;
+
+    /* Threads. */
+    pj_bool_t	     quit_flag;
+    int		     thread_cnt;
+    pj_thread_t	    *thread[16];
+
+    /* Outgoing request method: */
+    pjsip_method     method;
+
+    /* Default target: */
+    pj_str_t	     target;
+
+    /* Media: */
+    pjmedia_endpt   *med_endpt;
+    pjmedia_conf    *mconf;
+
+    /* Handling incoming requests: */
+    pj_bool_t	     stateless;
+
+    /* Rate control.	*/
+    pj_uint32_t	     start_rate;
+    pj_uint32_t	     cur_rate;
+    
+    /* Capacity control. */
+    pj_uint32_t	     max_capacity;
+
+    /* Duration control: */
+    pj_uint32_t	     duration;
+
+    /* Test control: */
+    session	    *session;
+    pj_timer_entry   timer;
+};
+
+
+typedef struct pjsip_perf_settings pjsip_perf_settings;
+
+extern pjsip_perf_settings settings;
+
+
+
+PJ_END_DECL
+
+
+#endif	/* __PJSIP_PERF_H__ */
+
+
+
+
Index: /pjproject/trunk/pjsip-apps/src/pjsua/main.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/main.c	(revision 254)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/main.c	(revision 254)
@@ -0,0 +1,944 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjsua-lib/pjsua.h>
+#include <stdlib.h>		/* atoi */
+
+
+#define THIS_FILE	"main.c"
+
+/* Current dialog */
+static int current_acc;
+static int current_call = -1;
+
+
+/*
+ * Find next call.
+ */
+static pj_bool_t find_next_call(void)
+{
+    int i;
+
+    for (i=current_call+1; i<(int)pjsua.max_calls; ++i) {
+	if (pjsua.calls[i].inv != NULL) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    for (i=0; i<current_call; ++i) {
+	if (pjsua.calls[i].inv != NULL) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    current_call = -1;
+    return PJ_FALSE;
+}
+
+
+/*
+ * Find previous call.
+ */
+static pj_bool_t find_prev_call(void)
+{
+    int i;
+
+    for (i=current_call-1; i>=0; --i) {
+	if (pjsua.calls[i].inv != NULL) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    for (i=pjsua.max_calls-1; i>current_call; --i) {
+	if (pjsua.calls[i].inv != NULL) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    current_call = -1;
+    return PJ_FALSE;
+}
+
+
+
+/*
+ * Notify UI when invite state has changed.
+ */
+void pjsua_ui_inv_on_state_changed(int call_index, pjsip_event *e)
+{
+    pjsua_call *call = &pjsua.calls[call_index];
+
+    PJ_UNUSED_ARG(e);
+
+    PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", 
+	      call_index,
+	      pjsua_inv_state_names[call->inv->state]));
+
+    if (call->inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+	call->inv = NULL;
+	if ((int)call->index == current_call) {
+	    find_next_call();
+	}
+
+    } else {
+
+	if (call && current_call==-1)
+	    current_call = call->index;
+
+    }
+}
+
+/**
+ * Notify UI when registration status has changed.
+ */
+void pjsua_ui_regc_on_state_changed(int code)
+{
+    PJ_UNUSED_ARG(code);
+
+    // Log already written.
+}
+
+
+/*
+ * Print buddy list.
+ */
+static void print_buddy_list(void)
+{
+    int i;
+
+    puts("Buddy list:");
+
+    if (pjsua.buddy_cnt == 0)
+	puts(" -none-");
+    else {
+	for (i=0; i<pjsua.buddy_cnt; ++i) {
+	    const char *status;
+
+	    if (pjsua.buddies[i].sub == NULL || 
+		pjsua.buddies[i].status.info_cnt==0)
+	    {
+		status = "   ?   ";
+	    } 
+	    else if (pjsua.buddies[i].status.info[0].basic_open)
+		status = " Online";
+	    else
+		status = "Offline";
+
+	    printf(" [%2d] <%s>  %s\n", 
+		    i+1, status, pjsua.buddies[i].uri.ptr);
+	}
+    }
+    puts("");
+}
+
+
+/*
+ * Print account status.
+ */
+static void print_acc_status(int acc_index)
+{
+    char reg_status[128];
+
+    if (pjsua.acc[acc_index].regc == NULL) {
+	pj_ansi_strcpy(reg_status, " -not registered to server-");
+
+    } else if (pjsua.acc[acc_index].reg_last_err != PJ_SUCCESS) {
+	pj_strerror(pjsua.acc[acc_index].reg_last_err, reg_status, sizeof(reg_status));
+
+    } else if (pjsua.acc[acc_index].reg_last_code>=200 && 
+	       pjsua.acc[acc_index].reg_last_code<=699) {
+
+	pjsip_regc_info info;
+
+	pjsip_regc_get_info(pjsua.acc[acc_index].regc, &info);
+
+	pj_snprintf(reg_status, sizeof(reg_status),
+		    "%s (%.*s;expires=%d)",
+		    pjsip_get_status_text(pjsua.acc[acc_index].reg_last_code)->ptr,
+		    (int)info.client_uri.slen,
+		    info.client_uri.ptr,
+		    info.next_reg);
+
+    } else {
+	pj_sprintf(reg_status, "in progress (%d)", 
+		   pjsua.acc[acc_index].reg_last_code);
+    }
+
+    printf("[%2d] Registration status: %s\n", acc_index, reg_status);
+    printf("     Online status: %s\n", 
+	   (pjsua.acc[acc_index].online_status ? "Online" : "Invisible"));
+}
+
+/*
+ * Show a bit of help.
+ */
+static void keystroke_help(void)
+{
+    int i;
+
+    printf(">>>>\n");
+
+    for (i=0; i<pjsua.acc_cnt; ++i)
+	print_acc_status(i);
+
+    print_buddy_list();
+    
+    //puts("Commands:");
+    puts("+=============================================================================+");
+    puts("|       Call Commands:         |      IM & Presence:      |   Misc:           |");
+    puts("|                              |                          |                   |");
+    puts("|  m  Make new call            |  i  Send IM              |  o  Send OPTIONS  |");
+    puts("|  M  Make multiple calls      |  s  Subscribe presence   | rr  (Re-)register |");
+    puts("|  a  Answer call              |  u  Unsubscribe presence | ru  Unregister    |");
+    puts("|  h  Hangup call              |  t  ToGgle Online status |  d  Dump status   |");
+    puts("|  H  Hold call                |                          | dc  Dump config   |");
+    puts("|  v  re-inVite (release hold) +--------------------------+-------------------+");
+    puts("|  ]  Select next dialog       |     Conference Command   |                   |");
+    puts("|  [  Select previous dialog   | cl  List ports           |                   |");
+    puts("|  x  Xfer call                | cc  Connect port         |                   |");
+    puts("|  #  Send DTMF string         | cd  Disconnect port      |                   |");
+    puts("+------------------------------+--------------------------+-------------------+");
+    puts("|  q  QUIT                                                                    |");
+    puts("+=============================================================================+");
+}
+
+
+/*
+ * Input simple string
+ */
+static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
+{
+    char *p;
+
+    printf("%s (empty to cancel): ", title); fflush(stdout);
+    fgets(buf, len, stdin);
+
+    /* Remove trailing newlines. */
+    for (p=buf; ; ++p) {
+	if (*p=='\r' || *p=='\n') *p='\0';
+	else if (!*p) break;
+    }
+
+    if (!*buf)
+	return PJ_FALSE;
+    
+    return PJ_TRUE;
+}
+
+
+#define NO_NB	-2
+struct input_result
+{
+    int	  nb_result;
+    char *uri_result;
+};
+
+
+/*
+ * Input URL.
+ */
+static void ui_input_url(const char *title, char *buf, int len, 
+			 struct input_result *result)
+{
+    result->nb_result = NO_NB;
+    result->uri_result = NULL;
+
+    print_buddy_list();
+
+    printf("Choices:\n"
+	   "   0         For current dialog.\n"
+	   "  -1         All %d buddies in buddy list\n"
+	   "  [1 -%2d]    Select from buddy list\n"
+	   "  URL        An URL\n"
+	   "  <Enter>    Empty input (or 'q') to cancel\n"
+	   , pjsua.buddy_cnt, pjsua.buddy_cnt);
+    printf("%s: ", title);
+
+    fflush(stdout);
+    fgets(buf, len, stdin);
+    len = strlen(buf);
+
+    /* Left trim */
+    while (isspace(*buf)) {
+	++buf;
+	--len;
+    }
+
+    /* Remove trailing newlines */
+    while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
+	buf[--len] = '\0';
+
+    if (len == 0 || buf[0]=='q')
+	return;
+
+    if (isdigit(*buf) || *buf=='-') {
+	
+	int i;
+	
+	if (*buf=='-')
+	    i = 1;
+	else
+	    i = 0;
+
+	for (; i<len; ++i) {
+	    if (!isdigit(buf[i])) {
+		puts("Invalid input");
+		return;
+	    }
+	}
+
+	result->nb_result = atoi(buf);
+
+	if (result->nb_result > 0 && result->nb_result <= (int)pjsua.buddy_cnt) {
+	    --result->nb_result;
+	    return;
+	}
+	if (result->nb_result == -1)
+	    return;
+
+	puts("Invalid input");
+	result->nb_result = NO_NB;
+	return;
+
+    } else {
+	pj_status_t status;
+
+	if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Invalid URL", status);
+	    return;
+	}
+
+	result->uri_result = buf;
+    }
+}
+
+static void conf_list(void)
+{
+    unsigned i, count;
+    pjmedia_conf_port_info info[PJSUA_MAX_CALLS];
+
+    printf("Conference ports:\n");
+
+    count = PJ_ARRAY_SIZE(info);
+    pjmedia_conf_get_ports_info(pjsua.mconf, &count, info);
+    for (i=0; i<count; ++i) {
+	char txlist[PJSUA_MAX_CALLS*4+10];
+	int j;
+	pjmedia_conf_port_info *port_info = &info[i];	
+	
+	txlist[0] = '\0';
+	for (j=0; j<pjsua.max_calls+PJSUA_CONF_MORE_PORTS; ++j) {
+	    char s[10];
+	    if (port_info->listener[j]) {
+		pj_sprintf(s, "#%d ", j);
+		pj_ansi_strcat(txlist, s);
+	    }
+	}
+	printf("Port #%02d %20.*s  transmitting to: %s\n", 
+	       port_info->slot, 
+	       (int)port_info->name.slen, 
+	       port_info->name.ptr,
+	       txlist);
+
+    }
+    puts("");
+}
+
+
+static void ui_console_main(void)
+{
+    char menuin[10];
+    char buf[128];
+    int i, count;
+    char *uri;
+    struct input_result result;
+
+    keystroke_help();
+
+    for (;;) {
+
+	printf(">>> ");
+	fflush(stdout);
+
+	fgets(menuin, sizeof(menuin), stdin);
+
+	switch (menuin[0]) {
+
+	case 'm':
+	    /* Make call! : */
+	    printf("(You currently have %d calls)\n", pjsua.call_cnt);
+	    
+	    ui_input_url("Make call", buf, sizeof(buf), &result);
+	    if (result.nb_result != NO_NB) {
+		if (result.nb_result == -1)
+		    puts("You can't do that with make call!");
+		else
+		    pjsua_make_call( current_acc, 
+				     pjsua.buddies[result.nb_result].uri.ptr, 
+				     NULL);
+	    } else if (result.uri_result)
+		pjsua_make_call( current_acc, result.uri_result, NULL);
+	    
+	    break;
+
+	case 'M':
+	    /* Make multiple calls! : */
+	    printf("(You currently have %d calls)\n", pjsua.call_cnt);
+	    
+	    ui_input_url("Make call", buf, sizeof(buf), &result);
+	    if (result.nb_result != NO_NB) {
+		if (result.nb_result == -1) {
+		    puts("You can't do that with make call!");
+		    continue;
+		}
+		uri = pjsua.buddies[result.nb_result].uri.ptr;
+	    } else {
+		uri =  result.uri_result;
+	    }
+
+	    if (!simple_input("Number of calls", menuin, sizeof(menuin)))
+		continue;
+
+	    count = atoi(menuin);
+	    if (count < 1)
+		continue;
+
+	    for (i=0; i<atoi(menuin); ++i) {
+		pj_status_t status;
+
+		status = pjsua_make_call(current_acc, uri, NULL);
+		if (status != PJ_SUCCESS)
+		    break;
+	    }
+	    break;
+
+	case 'a':
+
+	    if (current_call == -1 || 
+		pjsua.calls[current_call].inv->role != PJSIP_ROLE_UAS ||
+		pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING)
+	    {
+		puts("No pending incoming call");
+		fflush(stdout);
+		continue;
+
+	    } else {
+		pj_status_t status;
+		pjsip_tx_data *tdata;
+
+		if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
+		    continue;
+		
+		if (atoi(buf) < 100)
+		    continue;
+
+		/*
+		 * Must check again!
+		 * Call may have been disconnected while we're waiting for 
+		 * keyboard input.
+		 */
+		if (current_call == -1) {
+		    puts("Call has been disconnected");
+		    fflush(stdout);
+		    continue;
+		}
+
+		status = pjsip_inv_answer(pjsua.calls[current_call].inv, 
+					  atoi(buf), 
+					  NULL, NULL, &tdata);
+		if (status == PJ_SUCCESS)
+		    status = pjsip_inv_send_msg(pjsua.calls[current_call].inv,
+						tdata, NULL);
+
+		if (status != PJ_SUCCESS)
+		    pjsua_perror(THIS_FILE, "Unable to create/send response", 
+				 status);
+	    }
+
+	    break;
+
+
+	case 'h':
+
+	    if (current_call == -1) {
+		puts("No current call");
+		fflush(stdout);
+		continue;
+
+	    } else {
+		pjsua_call_hangup(current_call, PJSIP_SC_DECLINE);
+	    }
+	    break;
+
+	case ']':
+	case '[':
+	    /*
+	     * Cycle next/prev dialog.
+	     */
+	    if (menuin[0] == ']') {
+		find_next_call();
+
+	    } else {
+		find_prev_call();
+	    }
+
+	    if (current_call != -1) {
+		char url[PJSIP_MAX_URL_SIZE];
+		int len;
+		const pjsip_uri *u;
+
+		u = pjsua.calls[current_call].inv->dlg->remote.info->uri;
+		len = pjsip_uri_print(0, u, url, sizeof(url)-1);
+		if (len < 1) {
+		    pj_ansi_strcpy(url, "<uri is too long>");
+		} else {
+		    url[len] = '\0';
+		}
+
+		PJ_LOG(3,(THIS_FILE,"Current dialog: %s", url));
+
+	    } else {
+		PJ_LOG(3,(THIS_FILE,"No current dialog"));
+	    }
+	    break;
+
+	case 'H':
+	    /*
+	     * Hold call.
+	     */
+	    if (current_call != -1) {
+		
+		pjsua_call_set_hold(current_call);
+
+	    } else {
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+	    }
+	    break;
+
+	case 'v':
+	    /*
+	     * Send re-INVITE (to release hold, etc).
+	     */
+	    if (current_call != -1) {
+		
+		pjsua_call_reinvite(current_call);
+
+	    } else {
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+	    }
+	    break;
+
+	case 'x':
+	    /*
+	     * Transfer call.
+	     */
+	    if (current_call == -1) {
+		
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+
+	    } else {
+		int call = current_call;
+
+		ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
+
+		/* Check if call is still there. */
+
+		if (call != current_call) {
+		    puts("Call has been disconnected");
+		    continue;
+		}
+
+		if (result.nb_result != NO_NB) {
+		    if (result.nb_result == -1) 
+			puts("You can't do that with transfer call!");
+		    else
+			pjsua_call_xfer( current_call,
+					 pjsua.buddies[result.nb_result].uri.ptr);
+
+		} else if (result.uri_result) {
+		    pjsua_call_xfer( current_call, result.uri_result);
+		}
+	    }
+	    break;
+
+	case '#':
+	    /*
+	     * Send DTMF strings.
+	     */
+	    if (current_call == -1) {
+		
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+
+	    } else if (pjsua.calls[current_call].session == NULL) {
+
+		PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
+
+	    } else {
+		pj_str_t digits;
+		int call = current_call;
+		pj_status_t status;
+
+		if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
+				  sizeof(buf)))
+		{
+			break;
+		}
+
+		if (call != current_call) {
+		    puts("Call has been disconnected");
+		    continue;
+		}
+
+		digits = pj_str(buf);
+		status = pjmedia_session_dial_dtmf(pjsua.calls[current_call].session, 0, 
+						   &digits);
+		if (status != PJ_SUCCESS) {
+		    pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
+		} else {
+		    puts("DTMF digits enqueued for transmission");
+		}
+	    }
+	    break;
+
+	case 's':
+	case 'u':
+	    /*
+	     * Subscribe/unsubscribe presence.
+	     */
+	    ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
+	    if (result.nb_result != NO_NB) {
+		if (result.nb_result == -1) {
+		    int i;
+		    for (i=0; i<pjsua.buddy_cnt; ++i)
+			pjsua.buddies[i].monitor = (menuin[0]=='s');
+		} else {
+		    pjsua.buddies[result.nb_result].monitor = (menuin[0]=='s');
+		}
+
+		pjsua_pres_refresh(current_acc);
+
+	    } else if (result.uri_result) {
+		puts("Sorry, can only subscribe to buddy's presence, "
+		     "not arbitrary URL (for now)");
+	    }
+
+	    break;
+
+	case 'r':
+	    switch (menuin[1]) {
+	    case 'r':
+		/*
+		 * Re-Register.
+		 */
+		pjsua_regc_update(current_acc, PJ_TRUE);
+		break;
+	    case 'u':
+		/*
+		 * Unregister
+		 */
+		pjsua_regc_update(current_acc, PJ_FALSE);
+		break;
+	    }
+	    break;
+	    
+	case 't':
+	    pjsua.acc[current_acc].online_status = 
+		!pjsua.acc[current_acc].online_status;
+	    pjsua_pres_refresh(current_acc);
+	    break;
+
+	case 'c':
+	    switch (menuin[1]) {
+	    case 'l':
+		conf_list();
+		break;
+	    case 'c':
+	    case 'd':
+		{
+		    char src_port[10], dst_port[10];
+		    pj_status_t status;
+		    const char *src_title, *dst_title;
+
+		    conf_list();
+
+		    src_title = (menuin[1]=='c'?
+				 "Connect src port #":
+				 "Disconnect src port #");
+		    dst_title = (menuin[1]=='c'?
+				 "To dst port #":
+				 "From dst port #");
+
+		    if (!simple_input(src_title, src_port, sizeof(src_port)))
+			break;
+
+		    if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
+			break;
+
+		    if (menuin[1]=='c') {
+			status = pjmedia_conf_connect_port(pjsua.mconf, 
+							   atoi(src_port), 
+							   atoi(dst_port));
+		    } else {
+			status = pjmedia_conf_disconnect_port(pjsua.mconf, 
+							      atoi(src_port), 
+							      atoi(dst_port));
+		    }
+		    if (status == PJ_SUCCESS) {
+			puts("Success");
+		    } else {
+			puts("ERROR!!");
+		    }
+		}
+		break;
+	    }
+	    break;
+
+	case 'd':
+	    if (menuin[1] == 'c') {
+		char settings[2000];
+		int len;
+
+		len = pjsua_dump_settings(settings, sizeof(settings));
+		if (len < 1)
+		    PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
+		else
+		    PJ_LOG(3,(THIS_FILE, 
+			      "Dumping configuration (%d bytes):\n%s\n",
+			      len, settings));
+	    } else {
+		pjsua_dump();
+	    }
+	    break;
+
+	case 'q':
+	    goto on_exit;
+
+	default:
+	    keystroke_help();
+	    break;
+	}
+    }
+
+on_exit:
+    ;
+}
+
+
+/*****************************************************************************
+ * This is a very simple PJSIP module, whose sole purpose is to display
+ * incoming and outgoing messages to log. This module will have priority
+ * higher than transport layer, which means:
+ *
+ *  - incoming messages will come to this module first before reaching
+ *    transaction layer.
+ *
+ *  - outgoing messages will come to this module last, after the message
+ *    has been 'printed' to contiguous buffer by transport layer and
+ *    appropriate transport instance has been decided for this message.
+ *
+ */
+
+/* Notification on incoming messages */
+static pj_bool_t console_on_rx_msg(pjsip_rx_data *rdata)
+{
+    PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
+			 "%s\n"
+			 "--end msg--",
+			 rdata->msg_info.len,
+			 pjsip_rx_data_get_info(rdata),
+			 rdata->pkt_info.src_name,
+			 rdata->pkt_info.src_port,
+			 rdata->msg_info.msg_buf));
+    
+    /* Always return false, otherwise messages will not get processed! */
+    return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t console_on_tx_msg(pjsip_tx_data *tdata)
+{
+    
+    /* Important note:
+     *	tp_info field is only valid after outgoing messages has passed
+     *	transport layer. So don't try to access tp_info when the module
+     *	has lower priority than transport layer.
+     */
+
+    PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
+			 "%s\n"
+			 "--end msg--",
+			 (tdata->buf.cur - tdata->buf.start),
+			 pjsip_tx_data_get_info(tdata),
+			 tdata->tp_info.dst_name,
+			 tdata->tp_info.dst_port,
+			 tdata->buf.start));
+
+    /* Always return success, otherwise message will not get sent! */
+    return PJ_SUCCESS;
+}
+
+/* The module instance. */
+static pjsip_module console_msg_logger = 
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-pjsua-log", 13 },		/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority	        */
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &console_on_rx_msg,			/* on_rx_request()	*/
+    &console_on_rx_msg,			/* on_rx_response()	*/
+    &console_on_tx_msg,			/* on_tx_request.	*/
+    &console_on_tx_msg,			/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+
+};
+
+
+
+/*****************************************************************************
+ * Console application custom logging:
+ */
+
+
+static FILE *log_file;
+
+
+static void app_log_writer(int level, const char *buffer, int len)
+{
+    /* Write to both stdout and file. */
+
+    if (level <= pjsua.app_log_level)
+	pj_log_write(level, buffer, len);
+
+    if (log_file) {
+	fwrite(buffer, len, 1, log_file);
+	fflush(log_file);
+    }
+}
+
+
+void app_logging_init(void)
+{
+    /* Redirect log function to ours */
+
+    pj_log_set_log_func( &app_log_writer );
+
+    /* If output log file is desired, create the file: */
+
+    if (pjsua.log_filename)
+	log_file = fopen(pjsua.log_filename, "wt");
+}
+
+
+void app_logging_shutdown(void)
+{
+    /* Close logging file, if any: */
+
+    if (log_file) {
+	fclose(log_file);
+	log_file = NULL;
+    }
+}
+
+/*****************************************************************************
+ * Error display:
+ */
+
+/*
+ * Display error message for the specified error code.
+ */
+void pjsua_perror(const char *sender, const char *title, 
+		  pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));
+
+    PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+
+
+/*****************************************************************************
+ * main():
+ */
+int main(int argc, char *argv[])
+{
+
+    /* Init default settings. */
+    pjsua_default();
+
+
+    /* Initialize pjsua (to create pool etc).
+     */
+    if (pjsua_init() != PJ_SUCCESS)
+	return 1;
+
+
+    /* Parse command line arguments: */
+    if (pjsua_parse_args(argc, argv) != PJ_SUCCESS)
+	return 1;
+
+
+    /* Init logging: */
+    app_logging_init();
+
+
+    /* Register message logger to print incoming and outgoing
+     * messages.
+     */
+    pjsip_endpt_register_module(pjsua.endpt, &console_msg_logger);
+
+
+    /* Start pjsua! */
+    if (pjsua_start() != PJ_SUCCESS) {
+
+	pjsua_destroy();
+	return 1;
+    }
+
+
+    /* Sleep for a while, let any messages get printed to console: */
+    pj_thread_sleep(500);
+
+
+    /* Start UI console main loop: */
+    ui_console_main();
+
+
+    /* Destroy pjsua: */
+    pjsua_destroy();
+
+
+    /* Close logging: */
+    app_logging_shutdown();
+
+
+    /* Exit... */
+
+    return 0;
+}
+
