Index: /pjproject/trunk/build.symbian/bld.inf
===================================================================
--- /pjproject/trunk/build.symbian/bld.inf	(revision 2173)
+++ /pjproject/trunk/build.symbian/bld.inf	(revision 2174)
@@ -1,2 +1,5 @@
+#define SND_USE_NULL	0
+#define SND_USE_APS	0
+
 prj_platforms
 winscw
@@ -11,6 +14,4 @@
 pjnath.mmp
 pjsdp.mmp
-//null_audio.mmp
-symbian_audio.mmp
 pjmedia.mmp
 pjsip.mmp
@@ -24,4 +25,13 @@
 libspeexcodec.mmp
 
+/* Sound device impl */
+#if SND_USE_NULL
+	null_audio.mmp
+#elif SND_USE_APS
+	symbian_audio_aps.mmp
+#else
+	symbian_audio.mmp
+#endif
+
 /* Applications */
 //pjlib_test.mmp
Index: /pjproject/trunk/build.symbian/symbian_audio_aps.mmp
===================================================================
--- /pjproject/trunk/build.symbian/symbian_audio_aps.mmp	(revision 2174)
+++ /pjproject/trunk/build.symbian/symbian_audio_aps.mmp	(revision 2174)
@@ -0,0 +1,20 @@
+TARGET 					symbian_audio_aps.lib
+TARGETTYPE 				lib
+
+OPTION                                  CW -lang c++ 
+OPTION                                  GCCE -O2 -fno-unit-at-a-time 
+
+MACRO                                   PJ_M_I386=1
+MACRO					PJ_SYMBIAN=1 
+
+SYSTEMINCLUDE                           ..\pjmedia\include 
+SYSTEMINCLUDE                           ..\pjlib\include 
+
+SOURCEPATH                              ..\pjmedia\src\pjmedia 
+SOURCE                                  symbian_sound_aps.cpp 
+
+SYSTEMINCLUDE                           \epoc32\include\mmf\server 
+SYSTEMINCLUDE                           \epoc32\include\mmf\common 
+SYSTEMINCLUDE                           \epoc32\include\mda\common 
+SYSTEMINCLUDE                           \epoc32\include\libc 
+SYSTEMINCLUDE                           \epoc32\include 
Index: /pjproject/trunk/build.symbian/symbian_ua.mmp
===================================================================
--- /pjproject/trunk/build.symbian/symbian_ua.mmp	(revision 2173)
+++ /pjproject/trunk/build.symbian/symbian_ua.mmp	(revision 2174)
@@ -1,2 +1,5 @@
+#define SND_USE_NULL	0
+#define SND_USE_APS	0
+
 TARGET 		symbian_ua.exe
 TARGETTYPE 	exe
@@ -8,8 +11,4 @@
 MACRO		PJ_SYMBIAN=1
 
-#if defined(PJ_BUILD_DLL)
-	MACRO		PJ_DLL=1
-#endif
-
 // Source files
 
@@ -18,4 +17,8 @@
 
 DOCUMENT ua.h
+
+START RESOURCE  symbian_ua_reg.rss
+	TARGETPATH      \private\10003a3f\apps
+END
 
 SYSTEMINCLUDE	..\pjlib\include
@@ -28,16 +31,23 @@
 SYSTEMINCLUDE	\epoc32\include\libc
 
-#if defined(PJ_BUILD_DLL)
-	LIBRARY		pjsua_lib.lib pjsip_ua.lib
-	LIBRARY		pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
-	LIBRARY		pjnath.lib pjlib_util.lib pjlib.lib
-	LIBRARY		symbian_audio.lib libsrtp.lib
-	LIBRARY		libgsmcodec.lib libspeexcodec.lib
+STATICLIBRARY	pjsua_lib.lib pjsip_ua.lib
+STATICLIBRARY	pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
+STATICLIBRARY	pjnath.lib pjlib_util.lib pjlib.lib
+STATICLIBRARY	libsrtp.lib
+STATICLIBRARY	libgsmcodec.lib libspeexcodec.lib
+
+#if SND_USE_NULL || defined(WINSCW)
+    STATICLIBRARY	null_audio.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
+#elif SND_USE_APS
+    STATICLIBRARY	symbian_audio_aps.lib
+    LIBRARY		APSSession2.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
+    MACRO		PJMEDIA_SYM_SND_USE_APS=1
 #else
-	STATICLIBRARY	pjsua_lib.lib pjsip_ua.lib
-	STATICLIBRARY	pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
-	STATICLIBRARY	pjnath.lib pjlib_util.lib pjlib.lib
-	STATICLIBRARY	symbian_audio.lib libsrtp.lib
-	STATICLIBRARY	libgsmcodec.lib libspeexcodec.lib
+    STATICLIBRARY	symbian_audio.lib
+    LIBRARY 		mediaclientaudiostream.lib
+    LIBRARY 		mediaclientaudioinputstream.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
 #endif
 
@@ -47,12 +57,4 @@
 
 LIBRARY		esock.lib insock.lib charconv.lib euser.lib estlib.lib 
-LIBRARY 	mediaclientaudiostream.lib
-LIBRARY 	mediaclientaudioinputstream.lib
-
-CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
-
-START RESOURCE  symbian_ua_reg.rss
-	TARGETPATH      \private\10003a3f\apps
-END
 
 // The default 8KB seems to be insufficient with all bells and
Index: /pjproject/trunk/build.symbian/symsndtest.mmp
===================================================================
--- /pjproject/trunk/build.symbian/symsndtest.mmp	(revision 2173)
+++ /pjproject/trunk/build.symbian/symsndtest.mmp	(revision 2174)
@@ -1,2 +1,5 @@
+#define SND_USE_NULL	0
+#define SND_USE_APS	0
+
 TARGET 		symsndtest.exe
 TARGETTYPE 	exe
@@ -8,12 +11,12 @@
 MACRO		PJ_SYMBIAN=1
 
-#if defined(PJ_BUILD_DLL)
-	MACRO		PJ_DLL=1
-#endif
-
 // Test files
 
 SOURCE	app_main.cpp
 SOURCE	main_symbian.cpp
+
+START RESOURCE  symsndtest_reg.rss
+	TARGETPATH	\private\10003a3f\apps
+END
 
 SYSTEMINCLUDE	..\pjlib\include
@@ -24,23 +27,29 @@
 
 LIBRARY		charconv.lib euser.lib estlib.lib
+LIBRARY		esock.lib insock.lib
+STATICLIBRARY	pjlib.lib pjmedia.lib
 
-#if defined(PJ_BUILD_DLL)
-	LIBRARY		symbian_audio.lib pjlib.lib 
+#if SND_USE_NULL || defined(WINSCW)
+    STATICLIBRARY	null_audio.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
+#elif SND_USE_APS
+    SOURCEPATH                              ..\pjmedia\src\pjmedia 
+    SOURCE                                  symbian_sound_aps.cpp 
+
+    SYSTEMINCLUDE                           \epoc32\include\mmf\server 
+    SYSTEMINCLUDE                           \epoc32\include\mmf\common 
+    SYSTEMINCLUDE                           \epoc32\include\mda\common 
+
+    //STATICLIBRARY	symbian_audio_aps.lib
+    LIBRARY		APSSession2.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
 #else
-	STATICLIBRARY	symbian_audio.lib pjlib.lib 
+    STATICLIBRARY	symbian_audio.lib
+    LIBRARY 		mediaclientaudiostream.lib
+    LIBRARY 		mediaclientaudioinputstream.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
 #endif
-
-LIBRARY		esock.lib insock.lib
-LIBRARY 	mediaclientaudiostream.lib
-LIBRARY 	mediaclientaudioinputstream.lib
 
 #ifdef WINSCW
 	STATICLIBRARY   eexe.lib ecrt0.lib
 #endif
-
-CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
-
-START RESOURCE  symsndtest_reg.rss
-	TARGETPATH	\private\10003a3f\apps
-END
-
Index: /pjproject/trunk/pjmedia/include/pjmedia/symbian_sound_aps.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/symbian_sound_aps.h	(revision 2174)
+++ /pjproject/trunk/pjmedia/include/pjmedia/symbian_sound_aps.h	(revision 2174)
@@ -0,0 +1,52 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJMEDIA_SYMBIAN_SOUND_APS_H__
+#define __PJMEDIA_SYMBIAN_SOUND_APS_H__
+
+
+/**
+ * @file symbian_sound_aps.h
+ * @brief Sound device wrapper using Audio Proxy Server on 
+ * Symbian S60 3rd edition.
+ */
+#include <pjmedia/types.h>
+
+PJ_BEGIN_DECL
+
+/**
+ * Activate/deactivate loudspeaker, when loudspeaker is inactive, audio
+ * will be routed to earpiece.
+ *
+ * @param stream	The sound device stream, the stream should be started 
+ *			before calling this function. This param can be NULL
+ *			to set the behaviour of next opened stream.
+ * @param active	Specify PJ_TRUE to activate loudspeaker, and PJ_FALSE
+ *			otherwise.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_snd_aps_activate_loudspeaker(
+						pjmedia_snd_stream *stream,
+						pj_bool_t active);
+
+
+PJ_END_DECL
+
+
+#endif	/* __PJMEDIA_SYMBIAN_SOUND_APS_H__ */
Index: /pjproject/trunk/pjmedia/src/pjmedia/symbian_sound_aps.cpp
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/symbian_sound_aps.cpp	(revision 2174)
+++ /pjproject/trunk/pjmedia/src/pjmedia/symbian_sound_aps.cpp	(revision 2174)
@@ -0,0 +1,824 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjmedia/sound.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#include <e32msgqueue.h>
+#include <sounddevice.h>
+#include <APSClientSession.h>
+
+//////////////////////////////////////////////////////////////////////////////
+//
+
+#define THIS_FILE	    "symbian_sound_aps.cpp"
+
+#define BYTES_PER_SAMPLE    2
+#define POOL_NAME	    "SymbianSoundAps"
+#define POOL_SIZE	    512
+#define POOL_INC	    512
+
+#if 1
+#   define TRACE_(st) PJ_LOG(3, st)
+#else
+#   define TRACE_(st)
+#endif
+
+static pjmedia_snd_dev_info symbian_snd_dev_info = 
+{
+    "Symbian Sound Device (APS)",
+    1,
+    1,
+    8000
+};
+
+/* App UID to open global APS queues to communicate with the APS server. */
+extern TPtrC		    APP_UID;
+
+/* Default setting for loudspeaker */
+static pj_bool_t act_loudspeaker = PJ_FALSE;
+
+/* Forward declaration of CPjAudioEngine */
+class CPjAudioEngine;
+
+/* 
+ * PJMEDIA Sound Stream instance 
+ */
+struct pjmedia_snd_stream
+{
+    // Pool
+    pj_pool_t			*pool;
+
+    // Common settings.
+    pjmedia_dir		 	 dir;
+    unsigned			 clock_rate;
+    unsigned			 channel_count;
+    unsigned			 samples_per_frame;
+
+    // Audio engine
+    CPjAudioEngine		*engine;
+};
+
+static pj_pool_factory *snd_pool_factory;
+
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc) 
+{
+    PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
+}
+ 
+//////////////////////////////////////////////////////////////////////////////
+//
+
+/**
+ * Abstract class for handler of callbacks from APS client.
+ */
+class MQueueHandlerObserver
+{
+public:
+    virtual void InputStreamInitialized(const TInt aStatus) = 0;
+    virtual void OutputStreamInitialized(const TInt aStatus) = 0;
+    virtual void NotifyError(const TInt aError) = 0;
+
+    virtual void RecCb(TAPSCommBuffer &buffer) = 0;
+    virtual void PlayCb(TAPSCommBuffer &buffer) = 0;
+};
+
+/**
+ * Handler for communication and data queue.
+ */
+class CQueueHandler : public CActive
+{
+public:
+    // Types of queue handler
+    enum TQueueHandlerType {
+        ERecordCommQueue,
+        EPlayCommQueue,
+        ERecordQueue,
+        EPlayQueue
+    };
+
+    // The order corresponds to the APS Server state, do not change!
+    enum TState {
+    	EAPSPlayerInitialize        = 1,
+    	EAPSRecorderInitialize      = 2,
+    	EAPSPlayData                = 3,
+    	EAPSRecordData              = 4,
+    	EAPSPlayerInitComplete      = 5,
+    	EAPSRecorderInitComplete    = 6
+    };
+
+    static CQueueHandler* NewL(MQueueHandlerObserver* aObserver, 
+			       RMsgQueue<TAPSCommBuffer>* aQ, 
+			       TQueueHandlerType aType)
+    {
+	CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aType);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+    }
+
+    // Destructor
+    ~CQueueHandler() { Cancel(); }
+
+    // Start listening queue event
+    void Start() {
+	iQ->NotifyDataAvailable(iStatus);
+	SetActive();
+    }
+
+private:
+    // Constructor
+    CQueueHandler(MQueueHandlerObserver* aObserver, 
+		  RMsgQueue<TAPSCommBuffer>* aQ, 
+		  TQueueHandlerType aType) 
+	: CActive(CActive::EPriorityHigh),
+	  iQ(aQ), iObserver(aObserver), iType(aType)
+    {
+	CActiveScheduler::Add(this);
+
+	// use lower priority for comm queues
+	if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue))
+	    SetPriority(CActive::EPriorityStandard);
+    }
+
+    // Second phase constructor
+    void ConstructL() {}
+
+    // Inherited from CActive
+    void DoCancel() { iQ->CancelDataAvailable(); }
+
+    void RunL() {
+	if (iStatus != KErrNone) {
+	    iObserver->NotifyError(iStatus.Int());
+	    return;
+        }    
+
+	TAPSCommBuffer buffer;
+	TInt ret = iQ->Receive(buffer);
+
+	if (ret != KErrNone) {
+	    iObserver->NotifyError(ret);
+	    return;
+	}
+
+	switch (iType) {
+	case ERecordQueue:
+	    if (buffer.iCommand == EAPSRecordData) {
+		iObserver->RecCb(buffer);
+	    }
+	    break;
+
+	// Callbacks from the APS main thread
+	case EPlayCommQueue:
+	    switch (buffer.iCommand) {
+		case EAPSPlayData:
+		    if (buffer.iStatus == KErrUnderflow) {
+			iObserver->PlayCb(buffer);
+		    }
+		    break;
+		case EAPSPlayerInitialize:
+		    iObserver->NotifyError(buffer.iStatus);
+		    break;
+		case EAPSPlayerInitComplete:
+		    iObserver->OutputStreamInitialized(buffer.iStatus);
+		    break;
+		case EAPSRecorderInitComplete:
+		    iObserver->InputStreamInitialized(buffer.iStatus);
+		    break;
+		default:
+		    iObserver->NotifyError(buffer.iStatus);
+		    break;
+	    }
+	    break;
+
+	// Callbacks from the APS recorder thread
+	case ERecordCommQueue:
+	    switch (buffer.iCommand) {
+		// The APS recorder thread will only report errors
+		// through this handler. All other callbacks will be
+		// sent from the APS main thread through EPlayCommQueue
+		case EAPSRecorderInitialize:
+		    if (buffer.iStatus == KErrNone) {
+			iObserver->InputStreamInitialized(buffer.iStatus);
+			break;
+		    }
+		case EAPSRecordData:
+		    iObserver->NotifyError(buffer.iStatus);
+		    break;
+		default:
+		    break;
+	    }
+	    break;
+
+	default:
+	    break;
+        }
+
+        // issue next request
+        iQ->NotifyDataAvailable(iStatus);
+        SetActive();
+    }
+
+    // Data
+    RMsgQueue<TAPSCommBuffer>	*iQ;   // (not owned)
+    MQueueHandlerObserver	*iObserver; // (not owned)
+    TQueueHandlerType            iType;
+};
+
+
+/*
+ * Implementation: Symbian Input & Output Stream.
+ */
+class CPjAudioEngine : public CBase, MQueueHandlerObserver
+{
+public:
+    enum State
+    {
+	STATE_NULL,
+	STATE_READY,
+	STATE_STREAMING
+    };
+
+    ~CPjAudioEngine();
+
+    static CPjAudioEngine *NewL(pjmedia_snd_stream *parent_strm,
+				pjmedia_dir dir,
+				pjmedia_snd_rec_cb rec_cb,
+				pjmedia_snd_play_cb play_cb,
+				void *user_data);
+
+    TInt StartL();
+    void Stop();
+
+    TInt ActivateSpeaker(TBool active);
+
+private:
+    CPjAudioEngine(pjmedia_snd_stream *parent_strm,
+		   pjmedia_dir dir,
+		   pjmedia_snd_rec_cb rec_cb,
+		   pjmedia_snd_play_cb play_cb,
+		   void *user_data);
+    void ConstructL();
+    
+    TInt InitPlayL();
+    TInt InitRecL();
+    TInt StartStreamL();
+    
+    // Inherited from MQueueHandlerObserver
+    virtual void InputStreamInitialized(const TInt aStatus);
+    virtual void OutputStreamInitialized(const TInt aStatus);
+    virtual void NotifyError(const TInt aError);
+
+    virtual void RecCb(TAPSCommBuffer &buffer);
+    virtual void PlayCb(TAPSCommBuffer &buffer);
+
+    State			 state_;
+    pjmedia_snd_stream		*parentStrm_;
+    pjmedia_dir			 dir_;
+    pjmedia_snd_rec_cb		 recCb_;
+    pjmedia_snd_play_cb		 playCb_;
+    void			*userData_;
+    pj_uint32_t			 TsPlay_;
+    pj_uint32_t			 TsRec_;
+
+    RAPSSession                  iSession;
+    TAPSInitSettings             iSettings;
+    RMsgQueue<TAPSCommBuffer>    iReadQ;
+    RMsgQueue<TAPSCommBuffer>    iReadCommQ;
+    RMsgQueue<TAPSCommBuffer>    iWriteQ;
+    RMsgQueue<TAPSCommBuffer>    iWriteCommQ;
+
+    CQueueHandler		*iPlayCommHandler;
+    CQueueHandler		*iRecCommHandler;
+    CQueueHandler		*iRecHandler;
+};
+
+
+CPjAudioEngine* CPjAudioEngine::NewL(pjmedia_snd_stream *parent_strm,
+				     pjmedia_dir dir,
+				     pjmedia_snd_rec_cb rec_cb,
+				     pjmedia_snd_play_cb play_cb,
+				     void *user_data)
+{
+    CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm, dir,
+						       rec_cb, play_cb,
+						       user_data);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    CleanupStack::Pop(self);
+    return self;
+}
+
+CPjAudioEngine::CPjAudioEngine(pjmedia_snd_stream *parent_strm,
+			       pjmedia_dir dir,
+			       pjmedia_snd_rec_cb rec_cb,
+			       pjmedia_snd_play_cb play_cb,
+			       void *user_data) 
+      : state_(STATE_NULL),
+	parentStrm_(parent_strm),
+	dir_(dir),
+	recCb_(rec_cb),
+	playCb_(play_cb),
+	userData_(user_data),
+	iPlayCommHandler(0),
+	iRecCommHandler(0),
+	iRecHandler(0)
+{
+}
+
+CPjAudioEngine::~CPjAudioEngine()
+{
+    Stop();
+
+    delete iPlayCommHandler;
+    iPlayCommHandler = NULL;
+    delete iRecCommHandler;
+    iRecCommHandler = NULL;
+
+    iSession.Close();
+
+    if (state_ == STATE_READY) {
+	if (dir_ != PJMEDIA_DIR_PLAYBACK) {
+	    iReadQ.Close();
+	    iReadCommQ.Close();
+	}
+	iWriteQ.Close();
+	iWriteCommQ.Close();
+    }
+}
+
+TInt CPjAudioEngine::InitPlayL()
+{
+    if (state_ == STATE_STREAMING || state_ == STATE_READY)
+	return 0;
+
+    TInt err = iSession.InitializePlayer(iSettings);
+    if (err != KErrNone) {
+	snd_perror("Failed to initialize player", err);
+	return err;
+    }
+
+    // Open message queues for the output stream
+    TBuf<128> buf2 = iSettings.iGlobal;
+    buf2.Append(_L("PlayQueue"));
+    TBuf<128> buf3 = iSettings.iGlobal;
+    buf3.Append(_L("PlayCommQueue"));
+
+    while (iWriteQ.OpenGlobal(buf2))
+	User::After(10);
+    while (iWriteCommQ.OpenGlobal(buf3))
+	User::After(10);
+
+    // Construct message queue handler
+    iPlayCommHandler = CQueueHandler::NewL(this,
+					   &iWriteCommQ,
+					   CQueueHandler::EPlayCommQueue);
+
+    // Start observing APS callbacks on output stream message queue
+    iPlayCommHandler->Start();
+
+    return 0;
+}
+
+TInt CPjAudioEngine::InitRecL()
+{
+    if (state_ == STATE_STREAMING || state_ == STATE_READY)
+	return 0;
+
+    // Initialize input stream device
+    TInt err = iSession.InitializeRecorder(iSettings);
+    if (err != KErrNone) {
+	snd_perror("Failed to initialize recorder", err);
+	return err;
+    }
+
+    TBuf<128> buf1 = iSettings.iGlobal;
+    buf1.Append(_L("RecordQueue"));
+    TBuf<128> buf4 = iSettings.iGlobal;
+    buf4.Append(_L("RecordCommQueue"));
+
+    // Must wait for APS thread to finish creating message queues
+    // before we can open and use them.
+    while (iReadQ.OpenGlobal(buf1))
+	User::After(10);
+    while (iReadCommQ.OpenGlobal(buf4))
+	User::After(10);
+
+    // Construct message queue handlers
+    iRecCommHandler = CQueueHandler::NewL(this,
+					  &iReadCommQ,
+					  CQueueHandler::ERecordCommQueue);
+
+    // Start observing APS callbacks from on input stream message queue
+    iRecCommHandler->Start();
+    
+    return 0;
+}
+
+TInt CPjAudioEngine::StartL()
+{
+    TInt err = iSession.Connect();
+    if (err != KErrNone && err != KErrAlreadyExists)
+	return err;
+
+    if (state_ == STATE_READY)
+	return StartStreamL();
+
+    // Even if only capturer are opened, playback thread of APS Server need 
+    // to be run(?). Since some messages will be delivered via play comm queue.
+    return InitPlayL();
+}
+
+void CPjAudioEngine::Stop()
+{
+    iSession.Stop();
+
+    delete iRecHandler;
+    iRecHandler = NULL;
+
+    state_ = STATE_READY;
+}
+
+void CPjAudioEngine::ConstructL()
+{
+    iSettings.iFourCC		    = TFourCC(KMCPFourCCIdG711);
+    iSettings.iGlobal		    = APP_UID;
+    iSettings.iPriority		    = TMdaPriority(100);
+    iSettings.iPreference	    = TMdaPriorityPreference(0x05210001);
+    iSettings.iSettings.iChannels   = EMMFMono;
+    iSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
+    iSettings.iSettings.iVolume	    = 0;
+}
+
+TInt CPjAudioEngine::StartStreamL()
+{
+    if (state_ == STATE_STREAMING)
+	return 0;
+
+    iSession.SetCng(EFalse);
+    iSession.SetVadMode(EFalse);
+    iSession.SetPlc(EFalse);
+    iSession.SetEncoderMode(EALawOr20ms);
+    iSession.SetDecoderMode(EALawOr20ms);
+    iSession.ActivateLoudspeaker(act_loudspeaker);
+
+    // Not only playback
+    if (dir_ != PJMEDIA_DIR_PLAYBACK) {
+	iRecHandler = CQueueHandler::NewL(this, &iReadQ, 
+					  CQueueHandler::ERecordQueue);
+	iRecHandler->Start();
+	iSession.Read();
+    }
+
+    // Not only capture
+    if (dir_ != PJMEDIA_DIR_CAPTURE) {
+	iSession.Write();
+    }
+
+    state_ = STATE_STREAMING;
+    return 0;
+}
+
+// Inherited from MQueueHandlerObserver
+void CPjAudioEngine::InputStreamInitialized(const TInt aStatus)
+{
+    TRACE_((THIS_FILE, "InputStreamInitialized %d", aStatus));
+
+    state_ = STATE_READY;
+    if (aStatus == KErrNone) {
+	StartStreamL();
+    }
+}
+
+void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus)
+{
+    TRACE_((THIS_FILE, "OutputStreamInitialized %d", aStatus));
+
+    if (aStatus == KErrNone) {
+	if (dir_ == PJMEDIA_DIR_PLAYBACK) {
+	    state_ = STATE_READY;
+	    // Only playback, start directly
+	    StartStreamL();
+	} else
+	    InitRecL();
+    }
+}
+
+void CPjAudioEngine::NotifyError(const TInt aError)
+{
+    snd_perror("Error from CQueueHandler", aError);
+}
+
+void CPjAudioEngine::RecCb(TAPSCommBuffer &buffer)
+{
+    pj_int16_t buf[160];
+    pj_assert(buffer.iBuffer[0] == 1 && buffer.iBuffer[1] == 0);
+
+    for (int i=0; i<160; ++i)
+	 buf[i] = pjmedia_alaw2linear(buffer.iBuffer[i+2]);
+
+    recCb_(userData_, 0, buf, sizeof(buf));
+}
+
+void CPjAudioEngine::PlayCb(TAPSCommBuffer &buffer)
+{
+    pj_int16_t buf[160];
+
+    playCb_(userData_, 0, buf, sizeof(buf));
+
+    buffer.iCommand = CQueueHandler::EAPSPlayData;
+    buffer.iStatus = 0;
+    buffer.iBuffer.Zero();
+    buffer.iBuffer.Append(1);
+    buffer.iBuffer.Append(0);
+    for (int i=0; i<160; ++i)
+	buffer.iBuffer.Append(pjmedia_linear2alaw(buf[i]));
+
+    iWriteQ.Send(buffer);
+}
+
+
+TInt CPjAudioEngine::ActivateSpeaker(TBool active)
+{
+    if (state_ == STATE_READY || state_ == STATE_STREAMING) {
+        iSession.ActivateLoudspeaker(active);
+	return KErrNone;
+    }
+    return KErrNotReady;
+}
+//////////////////////////////////////////////////////////////////////////////
+//
+
+
+/*
+ * Initialize sound subsystem.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
+{
+    snd_pool_factory = factory;
+    return PJ_SUCCESS;
+}
+
+/*
+ * Get device count.
+ */
+PJ_DEF(int) pjmedia_snd_get_dev_count(void)
+{
+    /* Always return 1 */
+    return 1;
+}
+
+/*
+ * Get device info.
+ */
+PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
+{
+    /* Always return the default sound device */
+    if (index == (unsigned)-1)
+	index = 0;
+
+    PJ_ASSERT_RETURN(index==0, NULL);
+    return &symbian_snd_dev_info;
+}
+
+static pj_status_t sound_open(pjmedia_dir dir,
+			      unsigned clock_rate,
+			      unsigned channel_count,
+			      unsigned samples_per_frame,
+			      unsigned bits_per_sample,
+			      pjmedia_snd_rec_cb rec_cb,
+			      pjmedia_snd_play_cb play_cb,
+			      void *user_data,
+			      pjmedia_snd_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_snd_stream *strm;
+
+    PJ_ASSERT_RETURN(p_snd_strm, PJ_EINVAL);
+    PJ_ASSERT_RETURN(clock_rate == 8000 && channel_count == 1 && 
+		     bits_per_sample == 16, PJ_ENOTSUP);
+    PJ_ASSERT_RETURN((dir == PJMEDIA_DIR_CAPTURE_PLAYBACK && rec_cb && play_cb)
+		     || (dir == PJMEDIA_DIR_CAPTURE && rec_cb && !play_cb)
+		     || (dir == PJMEDIA_DIR_PLAYBACK && !rec_cb && play_cb),
+		     PJ_EINVAL);
+
+    pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, 
+    			  NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, 
+    						sizeof(pjmedia_snd_stream));
+    strm->dir = dir;
+    strm->pool = pool;
+    strm->clock_rate = clock_rate;
+    strm->channel_count = channel_count;
+    strm->samples_per_frame = samples_per_frame;
+
+    // Create the audio engine.
+    TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm, strm->dir, 
+						   rec_cb, play_cb, 
+						   user_data));
+    if (err != KErrNone) {
+    	pj_pool_release(pool);	
+	return PJ_RETURN_OS_ERROR(err);
+    }
+
+    // Done.
+    *p_snd_strm = strm;
+    return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Open sound recorder stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
+					  unsigned clock_rate,
+					  unsigned channel_count,
+					  unsigned samples_per_frame,
+					  unsigned bits_per_sample,
+					  pjmedia_snd_rec_cb rec_cb,
+					  void *user_data,
+					  pjmedia_snd_stream **p_snd_strm)
+{
+    if (index < 0) index = 0;
+    PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
+
+    return sound_open(PJMEDIA_DIR_CAPTURE, clock_rate, channel_count, 
+		      samples_per_frame, bits_per_sample, rec_cb, NULL,
+		      user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
+					unsigned clock_rate,
+					unsigned channel_count,
+					unsigned samples_per_frame,
+					unsigned bits_per_sample,
+					pjmedia_snd_play_cb play_cb,
+					void *user_data,
+					pjmedia_snd_stream **p_snd_strm )
+{
+    if (index < 0) index = 0;
+    PJ_ASSERT_RETURN(index == 0, PJ_EINVAL);
+
+    return sound_open(PJMEDIA_DIR_PLAYBACK, clock_rate, channel_count, 
+		      samples_per_frame, bits_per_sample, NULL, play_cb,
+		      user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
+				      int play_id,
+				      unsigned clock_rate,
+				      unsigned channel_count,
+				      unsigned samples_per_frame,
+				      unsigned bits_per_sample,
+				      pjmedia_snd_rec_cb rec_cb,
+				      pjmedia_snd_play_cb play_cb,
+				      void *user_data,
+				      pjmedia_snd_stream **p_snd_strm)
+{
+    if (rec_id < 0) rec_id = 0;
+    if (play_id < 0) play_id = 0;
+    PJ_ASSERT_RETURN(play_id == 0 && rec_id == 0, PJ_EINVAL);
+
+    return sound_open(PJMEDIA_DIR_CAPTURE_PLAYBACK, clock_rate, channel_count, 
+		      samples_per_frame, bits_per_sample, rec_cb, play_cb,
+		      user_data, p_snd_strm);
+}
+
+/*
+ * Get stream info.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
+						pjmedia_snd_stream_info *pi)
+{
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_bzero(pi, sizeof(*pi));
+    pi->dir = strm->dir;
+    pi->play_id = 0;
+    pi->rec_id = 0;
+    pi->clock_rate = strm->clock_rate;
+    pi->channel_count = strm->channel_count;
+    pi->samples_per_frame = strm->samples_per_frame;
+    pi->bits_per_sample = BYTES_PER_SAMPLE * 8;
+    pi->rec_latency = 0;
+    pi->play_latency = 0;
+
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
+{
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+    
+    if (stream->engine) {
+	TInt err = stream->engine->StartL();
+    	if (err != KErrNone)
+    	    return PJ_RETURN_OS_ERROR(err);
+    }
+    	
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
+{
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+    
+    if (stream->engine) {
+    	stream->engine->Stop();
+    }
+    
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
+{
+    pj_pool_t *pool;
+    
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+    
+    if (stream->engine) {
+    	delete stream->engine;
+    	stream->engine = NULL;
+    }
+
+    pool = stream->pool;
+    if (pool) {	
+    	stream->pool = NULL;
+    	pj_pool_release(pool);
+    }
+    
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
+{
+    /* Nothing to do */
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Set sound latency.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, 
+					    unsigned output_latency)
+{
+    /* Nothing to do */
+    PJ_UNUSED_ARG(input_latency);
+    PJ_UNUSED_ARG(output_latency);
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Activate/deactivate loudspeaker.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_aps_activate_loudspeaker(
+					pjmedia_snd_stream *stream, 
+					pj_bool_t active)
+{
+    if (stream == NULL) {
+	act_loudspeaker = active;
+    } else {
+	if (stream->engine == NULL)
+	    return PJ_EINVAL;
+
+	TInt err = stream->engine->ActivateSpeaker(active);
+	if (err != KErrNone)
+	    return PJ_RETURN_OS_ERROR(err);
+    }
+
+    return PJ_SUCCESS;
+}
Index: /pjproject/trunk/pjsip-apps/src/symbian_ua/main_symbian.cpp
===================================================================
--- /pjproject/trunk/pjsip-apps/src/symbian_ua/main_symbian.cpp	(revision 2173)
+++ /pjproject/trunk/pjsip-apps/src/symbian_ua/main_symbian.cpp	(revision 2174)
@@ -27,4 +27,7 @@
 //  Global Variables
 CConsoleBase* console;
+
+// Needed by APS
+TPtrC APP_UID = _L("A000000E");
 
 
Index: /pjproject/trunk/pjsip-apps/src/symbian_ua/ua.cpp
===================================================================
--- /pjproject/trunk/pjsip-apps/src/symbian_ua/ua.cpp	(revision 2173)
+++ /pjproject/trunk/pjsip-apps/src/symbian_ua/ua.cpp	(revision 2174)
@@ -327,4 +327,5 @@
     log_cfg.console_level = 4;
     log_cfg.cb = &log_writer;
+    //log_cfg.log_filename = pj_str("C:\\data\\symbian_ua.log");
 
     pjsua_media_config_default(&med_cfg);
@@ -332,5 +333,9 @@
     med_cfg.has_ioqueue = PJ_FALSE;
     med_cfg.clock_rate = 8000;
+#if defined(PJMEDIA_SYM_SND_USE_APS) && (PJMEDIA_SYM_SND_USE_APS==1)
+    med_cfg.audio_frame_ptime = 20;
+#else
     med_cfg.audio_frame_ptime = 40;
+#endif
     med_cfg.ec_tail_len = 0;
     med_cfg.enable_ice = USE_ICE;
Index: /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
===================================================================
--- /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp	(revision 2173)
+++ /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp	(revision 2174)
@@ -1,2 +1,5 @@
+#define SND_USE_NULL	0
+#define SND_USE_APS	0
+
 TARGET			symbian_ua_gui.exe
 UID			   0x100039CE 0xEBD12EE4
@@ -33,15 +36,28 @@
 LIBRARY			etext.lib gdi.lib egul.lib insock.lib
 LIBRARY			ecom.lib inetprotutil.lib http.lib esock.lib
+LIBRARY			charconv.lib estlib.lib 
 
 STATICLIBRARY	pjsua_lib.lib pjsip_ua.lib
 STATICLIBRARY	pjsip_simple.lib pjsip.lib pjsdp.lib pjmedia.lib
 STATICLIBRARY	pjnath.lib pjlib_util.lib pjlib.lib
-STATICLIBRARY	symbian_audio.lib libsrtp.lib
+STATICLIBRARY	libsrtp.lib
 STATICLIBRARY	libgsmcodec.lib
 STATICLIBRARY	libspeexcodec.lib
 
-LIBRARY		charconv.lib estlib.lib 
-LIBRARY 	mediaclientaudiostream.lib
-LIBRARY 	mediaclientaudioinputstream.lib
+
+#if SND_USE_NULL || defined(WINSCW)
+    STATICLIBRARY	null_audio.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
+#elif SND_USE_APS
+    STATICLIBRARY	symbian_audio_aps.lib
+    LIBRARY		APSSession2.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
+    MACRO		PJMEDIA_SYM_SND_USE_APS=1
+#else
+    STATICLIBRARY	symbian_audio.lib
+    LIBRARY 		mediaclientaudiostream.lib
+    LIBRARY 		mediaclientaudioinputstream.lib
+    CAPABILITY		NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
+#endif
 
 LANG 				01
@@ -57,4 +73,2 @@
 SOURCE symbian_ua_guiContainerView.cpp symbian_ua_guiContainer.cpp symbian_ua_guiAppUi.cpp symbian_ua_guiDocument.cpp symbian_ua_guiApplication.cpp symbian_ua_guiSettingItemList.cpp Symbian_ua_guiSettingItemListSets.cpp symbian_ua_guiSettingItemListView.cpp
 SOURCE symbian_ua.cpp
-
-CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment 
Index: /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp
===================================================================
--- /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp	(revision 2173)
+++ /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp	(revision 2174)
@@ -324,5 +324,9 @@
     med_cfg.has_ioqueue = PJ_FALSE;
     med_cfg.clock_rate = 8000;
+#if defined(PJMEDIA_SYM_SND_USE_APS) && (PJMEDIA_SYM_SND_USE_APS==1)
+    med_cfg.audio_frame_ptime = 20;
+#else
     med_cfg.audio_frame_ptime = 40;
+#endif
     med_cfg.ec_tail_len = 0;
     med_cfg.enable_ice = USE_ICE;
Index: /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/src/symbian_ua_guiApplication.cpp
===================================================================
--- /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/src/symbian_ua_guiApplication.cpp	(revision 2173)
+++ /pjproject/trunk/pjsip-apps/src/symbian_ua_gui/src/symbian_ua_guiApplication.cpp	(revision 2174)
@@ -17,4 +17,8 @@
 #endif
 // ]]] end generated region [Generated Includes]
+
+
+// Needed by APS
+TPtrC APP_UID = _L("EBD12EE4");
 
 /**
Index: /pjproject/trunk/pjsip-apps/src/symsndtest/app_main.cpp
===================================================================
--- /pjproject/trunk/pjsip-apps/src/symsndtest/app_main.cpp	(revision 2173)
+++ /pjproject/trunk/pjsip-apps/src/symsndtest/app_main.cpp	(revision 2174)
@@ -17,4 +17,5 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
+#include <pjmedia/delaybuf.h>
 #include <pjmedia/sound.h>
 #include <pj/errno.h>
@@ -28,8 +29,7 @@
 #define CLOCK_RATE		8000
 #define CHANNEL_COUNT		1
-#define PTIME			100
-#define SAMPLES_PER_FRAME	(80)
+#define PTIME			20
+#define SAMPLES_PER_FRAME	(CLOCK_RATE*PTIME/1000)
 #define BITS_PER_SAMPLE		16
-#define LOOPBACK_BUFF_COUNT 100
 
 extern CConsoleBase* console;
@@ -40,7 +40,6 @@
 static pj_time_val t_start;
 
-
-static pj_int16_t buff_loopback[SAMPLES_PER_FRAME*LOOPBACK_BUFF_COUNT];
-static pj_uint32_t pointer_w, pointer_r;
+pj_pool_t *pool;
+pjmedia_delay_buf *delaybuf;
 
 /* Logging callback */
@@ -74,5 +73,5 @@
     pj_log_set_log_func((void (*)(int,const char*,int)) &log_writer);
     pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
-    pj_log_set_level(5);
+    pj_log_set_level(3);
     
     /* Init pjlib */
@@ -104,4 +103,24 @@
     		   info->default_samples_per_sec));	
     }
+
+    /* Create pool */
+    pool = pj_pool_create(&cp.factory, THIS_FILE, 512, 512, NULL);
+    if (pool == NULL) {
+    	app_perror("pj_pool_create()", status);
+        pj_caching_pool_destroy(&cp);
+    	pj_shutdown();
+    	return status;
+    }
+
+    /* Init delay buffer */
+    status = pjmedia_delay_buf_create(pool, THIS_FILE, CLOCK_RATE, 
+				      SAMPLES_PER_FRAME, CHANNEL_COUNT, 
+				      0, 0, &delaybuf);
+    if (status != PJ_SUCCESS) {
+    	app_perror("pjmedia_delay_buf_create()", status);
+        //pj_caching_pool_destroy(&cp);
+    	//pj_shutdown();
+    	//return status;
+    }
     
     return PJ_SUCCESS;
@@ -117,19 +136,13 @@
     PJ_UNUSED_ARG(user_data);
     PJ_UNUSED_ARG(timestamp);
-    PJ_UNUSED_ARG(input);
     PJ_UNUSED_ARG(size);
 
-    pj_memcpy(&buff_loopback[pointer_w*SAMPLES_PER_FRAME], input, size);
+    pjmedia_delay_buf_put(delaybuf, (pj_int16_t*)input);
 
     if (size != SAMPLES_PER_FRAME*2) {
 		PJ_LOG(3, (THIS_FILE, "Size captured = %u",
 	 		   size));
-		pj_bzero(&buff_loopback[pointer_w*SAMPLES_PER_FRAME]+size/2, SAMPLES_PER_FRAME*2 - size);
-    }
-
-    if (++pointer_w >= LOOPBACK_BUFF_COUNT) {
-    	pointer_w = 0;
-    }
-    
+    }
+
     ++rec_cnt;
     return PJ_SUCCESS;
@@ -144,11 +157,7 @@
     PJ_UNUSED_ARG(user_data);
     PJ_UNUSED_ARG(timestamp);
-    
-    //pj_bzero(output, size);
-    pj_memcpy(output, &buff_loopback[pointer_r*SAMPLES_PER_FRAME], SAMPLES_PER_FRAME*2);
-
-    if (++pointer_r >= LOOPBACK_BUFF_COUNT) {
-    	pointer_r = 0;
-    }
+    PJ_UNUSED_ARG(size);
+    
+    pjmedia_delay_buf_get(delaybuf, (pj_int16_t*)output);
     
     ++play_cnt;
@@ -187,4 +196,6 @@
     pj_gettimeofday(&t_start);
 
+    pjmedia_delay_buf_reset(delaybuf);
+
     status = pjmedia_snd_stream_start(strm);
     if (status != PJ_SUCCESS) {
@@ -195,7 +206,4 @@
     }
 
-    pointer_w = LOOPBACK_BUFF_COUNT/2;
-    pointer_r = 0;
-    
     return PJ_SUCCESS;
 }
@@ -232,4 +240,6 @@
     
     pjmedia_snd_deinit();
+    pjmedia_delay_buf_destroy(delaybuf);
+    pj_pool_release(pool);
     pj_caching_pool_destroy(&cp);
     pj_shutdown();
@@ -296,9 +306,9 @@
     PJ_LOG(3, (THIS_FILE, "\n\n"
 	    "Menu:\n"
-	    "  b    Start bidir sound\n"
-	    "  r    Start recorder\n"
+	    "  a    Start bidir sound\n"
+	    "  t    Start recorder\n"
 	    "  p    Start player\n"
-	    "  c    Stop & close sound\n"
-	    "  q    Quit\n"));
+	    "  d    Stop & close sound\n"
+	    "  w    Quit\n"));
 }
 
@@ -310,12 +320,12 @@
     
     switch (kc) {
-    case 'q':
+    case 'w':
 	    asw_->AsyncStop();
 	    reschedule = PJ_FALSE;
 	    break;
-    case 'b':
+    case 'a':
     	snd_start(PJMEDIA_DIR_CAPTURE_PLAYBACK);
 	break;
-    case 'r':
+    case 't':
     	snd_start(PJMEDIA_DIR_CAPTURE);
 	break;
@@ -323,5 +333,5 @@
     	snd_start(PJMEDIA_DIR_PLAYBACK);
     break;
-    case 'c':
+    case 'd':
     	snd_stop();
 	break;
Index: /pjproject/trunk/pjsip-apps/src/symsndtest/main_symbian.cpp
===================================================================
--- /pjproject/trunk/pjsip-apps/src/symsndtest/main_symbian.cpp	(revision 2173)
+++ /pjproject/trunk/pjsip-apps/src/symsndtest/main_symbian.cpp	(revision 2174)
@@ -27,4 +27,7 @@
 //  Global Variables
 CConsoleBase* console;
+
+// Needed by APS
+TPtrC APP_UID = _L("A000000D");
 
 int app_main();
