Index: pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia.dsp
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia.dsp	(revision 2457)
+++ pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia.dsp	(revision 2458)
@@ -314,4 +314,8 @@
 # Begin Source File
 
+SOURCE=..\include\pjmedia\audio_dev.h
+# End Source File
+# Begin Source File
+
 SOURCE=..\include\pjmedia\bidirectional.h
 # End Source File
Index: pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia_audiodev.dsp
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia_audiodev.dsp	(revision 2458)
+++ pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia_audiodev.dsp	(revision 2458)
@@ -0,0 +1,112 @@
+# 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\pa_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
+# End Group
+# End Target
+# End Project
Index: pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev.h
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev.h	(revision 2458)
+++ pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev.h	(revision 2458)
@@ -0,0 +1,556 @@
+/* $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_H__
+#define __AUDIODEV_H__
+
+/**
+ * @file audiodev.h
+ * @brief Audio device API.
+ */
+#include <pjmedia/types.h>
+#include <pj/pool.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup PJMED_AUD_DEV Audio device API
+ * @ingroup PJMED_AUD_PORT
+ * @brief API to work with audio devices and to implement new audio device backends.
+ * @{
+ *
+ * The audio device API contains two parts:
+ *  - the base API, for both applications and audio device implementors, and
+ *  - the API for audio device implementors, for extending the audio device
+ *    framework with new audio device drivers.
+ *
+ * @}
+ */
+
+/**
+ * @defgroup PJMED_AUD_DEV_API Base Audio Device API
+ * @ingroup PJMED_AUD_DEV
+ * @brief The base API, for both applications and audio device implementors
+ * @{
+ */
+
+/** Device identifier */
+typedef pj_int32_t pjmedia_aud_dev_id;
+
+/** Constant to denote default ID */
+#define PJMEDIA_AUD_DEV_DEFAULT_ID  (-1)
+
+
+/**
+ * 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_dev_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 the audio device volume level. The value of this
+     * capability is an unsigned integer representing the audio volume in 
+     * percent.
+     */
+    PJMEDIA_AUD_DEV_CAP_SET_VOLUME = 8,
+
+    /** 
+     * Support for audio volume level query. The value of this capability 
+     * is unsigned integer representing the audio volume in percent.
+     */
+    PJMEDIA_AUD_DEV_CAP_GET_VOLUME = 16,
+
+    /** 
+     * Support for audio routing (e.g. loudspeaker vs earpiece). The value
+     * of this capability is an integer containing #pjmedia_aud_dev_route
+     * enumeration.
+     */
+    PJMEDIA_AUD_DEV_CAP_ROUTE = 32,
+
+    /** 
+     * The audio device has echo cancellation feature. The value of this
+     * capability is an integer containing boolean PJ_TRUE or PJ_FALSE.
+     */
+    PJMEDIA_AUD_DEV_CAP_EC = 64,
+
+    /** 
+     * 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 = 128,
+
+    /** 
+     * The audio device has voice activity detection feature. The value
+     * of this capability is an integer containing boolean PJ_TRUE or 
+     * PJ_FALSE.
+     */
+    PJMEDIA_AUD_DEV_CAP_VAD = 256,
+
+    /** 
+     * The audio device has comfort noise generation feature. The value
+     * of this capability is an integer containing boolean PJ_TRUE or 
+     * PJ_FALSE.
+     */
+    PJMEDIA_AUD_DEV_CAP_CNG = 512,
+
+    /** 
+     * The audio device has packet loss concealment feature. The value
+     * of this capability is an integer containing boolean PJ_TRUE or 
+     * PJ_FALSE.
+     */
+    PJMEDIA_AUD_DEV_CAP_PLC = 1024
+    
+} 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_get_dev_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[64];
+
+    /** 
+     * 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.
+ *
+ * @param user_data	User data associated with the stream.
+ * @param ts		Timestamp, in samples.
+ * @param output	Buffer to be filled out by application.
+ * @param size		The size requested in bytes, which will be equal to
+ *			the size of one whole packet.
+ *
+ * @return		Non-zero to stop the stream.
+ */
+typedef pj_status_t (*pjmedia_aud_play_cb)(/* in */   void *user_data,
+					   /* in */   const pj_timestamp *ts,
+					   /* out */  void *output,
+					   /* out */  unsigned size);
+
+/**
+ * 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 ts		Timestamp, in samples.
+ * @param output	Buffer containing the captured audio samples.
+ * @param size		The size of the data in the buffer, in bytes.
+ *
+ * @return		Non-zero to stop the stream.
+ */
+typedef pj_status_t (*pjmedia_aud_rec_cb)(/* in */   void *user_data,
+					  /* in */   const pj_timestamp *ts,
+					  /* in */   void *input,
+					  /* in*/    unsigned size);
+
+/**
+ * This structure specifies the parameters to open the audio device stream.
+ */
+typedef struct pjmedia_aud_dev_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_id rec_id;
+
+    /**
+     * The audio playback device ID. This setting is mandatory if the audio
+     * direction includes output/playback direction.
+     */
+    pjmedia_aud_dev_id 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;
+
+    /** 
+     * Set the audio route. This setting is optional, and will only be used
+     * if PJMEDIA_AUD_DEV_CAP_ROUTE is set in the flags.
+     */
+    pjmedia_aud_dev_route 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_dev_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;
+
+
+/**
+ * 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);
+
+
+/**
+ * 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);
+
+
+/**
+ * Enumerate device ID's.
+ *
+ * @param max_count	Maximum number of device id's to retrieve.
+ * @param ids		Array to receive the device id's.
+ *
+ * @return		The actual number of device id's filled in.
+ */
+PJ_DECL(unsigned) pjmedia_aud_dev_enum(unsigned max_count,
+				       pjmedia_aud_dev_id ids[]);
+
+/**
+ * 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_id id,
+					      pjmedia_aud_dev_info *info);
+
+
+/**
+ * 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_id id,
+						   pjmedia_aud_dev_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_aud_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_dev_param *param,
+					       pjmedia_aud_rec_cb rec_cb,
+					       pjmedia_aud_play_cb play_cb,
+					       void *user_data,
+					       pjmedia_aud_stream **p_aud_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_dev_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.
+ *
+ * @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.
+ *
+ * @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);
+
+
+/* Invalid audio device */
+#define PJMEDIA_EAUD_INVDEV	-1
+
+/**
+ * @)
+ */
+
+PJ_END_DECL
+
+
+#endif	/* __AUDIODEV_H__ */
+
Index: pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev_imp.h
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev_imp.h	(revision 2458)
+++ pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev_imp.h	(revision 2458)
@@ -0,0 +1,175 @@
+/* $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 PJMED_AUD_DEV_IMP_API Audio Device Implementors API
+ * @ingroup PJMED_AUD_DEV
+ * @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 id	The audio device ID.
+     * @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, 
+				pjmedia_aud_dev_id id,
+				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 id	Device ID.
+     * @param param	The audio device parameter.
+     */
+    pj_status_t (*default_param)(pjmedia_aud_dev_factory *f,
+				 pjmedia_aud_dev_id id,
+				 pjmedia_aud_dev_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_dev_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 the framework. */
+    struct {
+	unsigned    id;
+    } internal;
+
+    /** 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_dev_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
+{
+    /** Factory */
+    pjmedia_aud_dev_factory *factory;
+
+    /** Operations */
+    pjmedia_aud_stream_op *op;
+};
+
+
+/**
+ * @}
+ */
+
+
+
+#endif __AUDIODEV_IMP_H__
Index: pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiodev.c
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiodev.c	(revision 2458)
+++ pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiodev.c	(revision 2458)
@@ -0,0 +1,273 @@
+/* $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/errno.h>
+
+
+#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)
+
+
+/* extern */
+pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf);
+
+/* Array of factories */
+static struct factory
+{
+    pjmedia_aud_dev_factory*   (*create)(pj_pool_factory*);
+    pjmedia_aud_dev_factory	*f;
+
+} factories[] = 
+{
+    {
+	&pjmedia_pa_factory
+    }
+};
+static unsigned factory_cnt;
+
+
+/* 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;
+
+    factory_cnt = 0;
+
+    for (i=0; i<PJ_ARRAY_SIZE(factories); ++i) {
+	factories[i].f = (*factories[i].create)(pf);
+	if (!factories[i].f)
+	    continue;
+
+	status = factories[i].f->op->init(factories[i].f);
+	if (status != PJ_SUCCESS) {
+	    factories[i].f->op->destroy(factories[i].f);
+	    factories[i].f = NULL;
+	}
+
+	factories[i].f->internal.id = i;
+	++factory_cnt;
+    }
+
+    return factory_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: Shutdown the audio subsystem. */
+PJ_DEF(pj_status_t) pjmedia_aud_subsys_shutdown(void)
+{
+    unsigned i;
+
+    for (i=0; i<PJ_ARRAY_SIZE(factories); ++i) {
+	if (!factories[i].f)
+	    continue;
+
+	factories[i].f->op->destroy(factories[i].f);
+	factories[i].f = NULL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get the number of sound devices installed in the system. */
+PJ_DEF(unsigned) pjmedia_aud_dev_count(void)
+{
+    unsigned i, count = 0;
+
+    for (i=0; i<PJ_ARRAY_SIZE(factories); ++i) {
+	if (!factories[i].f)
+	    continue;
+
+	count += factories[i].f->op->get_dev_count(factories[i].f);
+    }
+
+    return count;
+}
+
+/* API: Enumerate device ID's. */
+PJ_DEF(unsigned) pjmedia_aud_dev_enum(unsigned max_count,
+				      pjmedia_aud_dev_id ids[])
+{
+    unsigned i, count = 0;
+
+    for (i=0; i<PJ_ARRAY_SIZE(factories) && count < max_count; ++i) {
+	unsigned j, fcount;
+
+	if (!factories[i].f)
+	    continue;
+
+	fcount = factories[i].f->op->get_dev_count(factories[i].f);
+	for (j=0; j<fcount && count<max_count; ++j) {
+	    ids[count++] = MAKE_DEV_ID(i, j);
+	}
+    }
+
+    return count;
+}
+
+
+/* API: Get device information. */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_get_info(pjmedia_aud_dev_id id,
+					     pjmedia_aud_dev_info *info)
+{
+    int f_id, index;
+
+    f_id = GET_FID(id);
+    index = GET_INDEX(id);
+
+    if (f_id < 0 || f_id >= PJ_ARRAY_SIZE(factories))
+	return PJMEDIA_EAUD_INVDEV;
+
+    if (factories[f_id].f == NULL)
+	return PJMEDIA_EAUD_INVDEV;
+
+    return factories[f_id].f->op->get_dev_info(factories[f_id].f,
+					       index, info);
+}
+
+/* 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_id id,
+						  pjmedia_aud_dev_param *param)
+{
+    int f_id, index;
+    pj_status_t status;
+
+    f_id = GET_FID(id);
+    index = GET_INDEX(id);
+
+    if (f_id < 0 || f_id >= PJ_ARRAY_SIZE(factories))
+	return PJMEDIA_EAUD_INVDEV;
+
+    if (factories[f_id].f == NULL)
+	return PJMEDIA_EAUD_INVDEV;
+
+    status = factories[f_id].f->op->default_param(factories[f_id].f,
+					          index, param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Normalize device IDs */
+    if (param->rec_id != PJMEDIA_AUD_DEV_DEFAULT_ID)
+	param->rec_id = MAKE_DEV_ID(f_id, param->rec_id);
+    if (param->play_id != PJMEDIA_AUD_DEV_DEFAULT_ID)
+	param->play_id = MAKE_DEV_ID(f_id, 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_dev_param *p,
+					      pjmedia_aud_rec_cb rec_cb,
+					      pjmedia_aud_play_cb play_cb,
+					      void *user_data,
+					      pjmedia_aud_stream **p_aud_strm)
+{
+    pjmedia_aud_dev_param param;
+    int f_id;
+    pj_status_t status;
+
+    /* Must make copy of param because we're changing device ID */
+    pj_memcpy(&param, p, sizeof(param));
+
+    /* Set default device */
+    if (param.rec_id == PJMEDIA_AUD_DEV_DEFAULT_ID) param.rec_id = 0;
+    if (param.play_id == PJMEDIA_AUD_DEV_DEFAULT_ID) param.play_id = 0;
+    
+    if (param.dir & PJMEDIA_DIR_CAPTURE)
+	f_id = GET_FID(param.rec_id);
+    else
+	f_id = GET_FID(param.play_id);
+
+    if (f_id < 0 || f_id >= PJ_ARRAY_SIZE(factories))
+	return PJMEDIA_EAUD_INVDEV;
+    
+    /* Normalize device id's */
+    param.rec_id = GET_INDEX(param.rec_id);
+    param.play_id = GET_INDEX(param.play_id);
+
+    if (factories[f_id].f == NULL)
+	return PJMEDIA_EAUD_INVDEV;
+
+    status = factories[f_id].f->op->create_stream(factories[f_id].f,
+					          &param, rec_cb, play_cb,
+						  user_data, p_aud_strm);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    (*p_aud_strm)->factory = factories[f_id].f;
+    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_dev_param *param)
+{
+    pj_status_t status;
+
+    status = strm->op->get_param(strm, param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Normalize device id's */
+    if (param->rec_id != PJMEDIA_AUD_DEV_DEFAULT_ID)
+	param->rec_id = MAKE_DEV_ID(strm->factory->internal.id, param->rec_id);
+    if (param->play_id != PJMEDIA_AUD_DEV_DEFAULT_ID)
+	param->play_id = MAKE_DEV_ID(strm->factory->internal.id, 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/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/pa_dev.c
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/pa_dev.c	(revision 2458)
+++ pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/pa_dev.c	(revision 2458)
@@ -0,0 +1,1220 @@
+/* $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/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <portaudio.h>
+
+#define THIS_FILE	"pa_dev.c"
+#define DRIVER_NAME	"portaudio"
+
+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, 
+				    pjmedia_aud_dev_id id,
+				    pjmedia_aud_dev_info *info);
+static pj_status_t  pa_default_param(pjmedia_aud_dev_factory *f,
+				     pjmedia_aud_dev_id id,
+				     pjmedia_aud_dev_param *param);
+static pj_status_t  pa_create_stream(pjmedia_aud_dev_factory *f,
+				     const pjmedia_aud_dev_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_dev_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;
+
+    stream->rec_timestamp.u64 += 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 )
+{
+    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;
+
+    stream->play_timestamp.u64 += 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 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_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_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, 
+				    pjmedia_aud_dev_id id,
+				    pjmedia_aud_dev_info *info)
+{
+    const PaDeviceInfo *pa_info;
+
+    PJ_UNUSED_ARG(f);
+
+    pa_info = Pa_GetDeviceInfo(id);
+    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,
+				     pjmedia_aud_dev_id id,
+				     pjmedia_aud_dev_param *param)
+{
+    pjmedia_aud_dev_info adi;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(f);
+
+    status = pjmedia_aud_dev_get_info(id, &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 = id;
+	param->play_id = id;
+    } else if (adi.input_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE;
+	param->rec_id = id;
+	param->play_id = PJMEDIA_AUD_DEV_DEFAULT_ID;
+    } else if (adi.output_count) {
+	param->dir = PJMEDIA_DIR_PLAYBACK;
+	param->play_id = id;
+	param->rec_id = PJMEDIA_AUD_DEV_DEFAULT_ID;
+    } 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_dev_param *param,
+				      pjmedia_aud_rec_cb rec_cb,
+				      void *user_data,
+				      pjmedia_aud_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_aud_dev_id 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;
+
+    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_ENOSNDREC;
+	}
+    }
+
+    paDevInfo = Pa_GetDeviceInfo(rec_id);
+    if (!paDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_ESNDINDEVID;
+    }
+
+    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_ESNDINSAMPLEFMT;
+    
+    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_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_dev_param *param,
+				      pjmedia_aud_play_cb play_cb,
+				      void *user_data,
+				      pjmedia_aud_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_aud_dev_id 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;
+
+    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_ENOSNDPLAY;
+	}
+    } 
+
+    paDevInfo = Pa_GetDeviceInfo(play_id);
+    if (!paDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_ESNDINDEVID;
+    }
+
+    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_ESNDINSAMPLEFMT;
+    
+    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_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_dev_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_id 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;
+
+    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_ENOSNDREC;
+	}
+    }
+
+    paRecDevInfo = Pa_GetDeviceInfo(rec_id);
+    if (!paRecDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_ESNDINDEVID;
+    }
+
+    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_ENOSNDPLAY;
+	}
+    } 
+
+    paPlayDevInfo = Pa_GetDeviceInfo(play_id);
+    if (!paPlayDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_ESNDINDEVID;
+    }
+
+
+    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_ESNDINSAMPLEFMT;
+    
+    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_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_dev_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_dev_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 * 
+						    paRecSI->sampleRate : 0);
+    }
+    if (paPlaySI) {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+	pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency * 
+						     paPlaySI->sampleRate : 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 PJ_EINVALIDOP;
+
+	*(unsigned*)pval = (unsigned)(si->inputLatency * si->sampleRate);
+	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 PJ_EINVALIDOP;
+
+	*(unsigned*)pval = (unsigned)(si->outputLatency * si->sampleRate);
+	return PJ_SUCCESS;
+    } else {
+	return PJ_ENOTSUP;
+    }
+}
+
+
+/* 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 PJ_ENOTSUP;
+}
+
+
+/* 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_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_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_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
Index: pjproject/branches/projects/aps-direct/pjproject.dsw
===================================================================
--- pjproject/branches/projects/aps-direct/pjproject.dsw	(revision 2457)
+++ pjproject/branches/projects/aps-direct/pjproject.dsw	(revision 2458)
@@ -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>
@@ -226,62 +238,62 @@
 ###############################################################################
 
-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>
+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>
@@ -355,38 +367,38 @@
 ###############################################################################
 
-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>
+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>
@@ -523,5 +535,5 @@
 ###############################################################################
 
-Project: "test_pjsip"=.\pjsip\build\test_pjsip.dsp - Package Owner=<4>
+Project: "test_pjsip"=".\pjsip\build\test_pjsip.dsp" - Package Owner=<4>
 
 Package=<5>
