Ticket #909:
 - Added new audio device VAS for Symbian platform.
 - Updated symsndtest to use the latest audio device framework.



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@2821 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/build.symbian/pjmedia_audiodev.mmp b/build.symbian/pjmedia_audiodev.mmp
index ccabc2f..da98b05 100644
--- a/build.symbian/pjmedia_audiodev.mmp
+++ b/build.symbian/pjmedia_audiodev.mmp
@@ -15,6 +15,7 @@
 SOURCE		errno.c
 SOURCE		symb_aps_dev.cpp
 SOURCE		symb_mda_dev.cpp
+SOURCE		symb_vas_dev.cpp
 
 SYSTEMINCLUDE	..\pjmedia\include
 SYSTEMINCLUDE	..\pjlib\include 
diff --git a/build.symbian/symbian_ua.mmp b/build.symbian/symbian_ua.mmp
index 1473486..8ef9f73 100644
--- a/build.symbian/symbian_ua.mmp
+++ b/build.symbian/symbian_ua.mmp
@@ -51,7 +51,7 @@
 #endif
 
 #if SND_HAS_VAS
-//	LIBRARY		
+	LIBRARY		VoIPAudioIntfc.lib		
 #endif
 
 #if SND_HAS_MDA
diff --git a/build.symbian/symsndtest.mmp b/build.symbian/symsndtest.mmp
index 470de9d..415807d 100644
--- a/build.symbian/symsndtest.mmp
+++ b/build.symbian/symsndtest.mmp
@@ -1,4 +1,4 @@
-#define SND_USE_APS	1
+#define SND_USE_APS	0
 #define SND_USE_VAS	0
 
 TARGET 			symsndtest.exe
@@ -28,13 +28,14 @@
 LIBRARY			charconv.lib euser.lib estlib.lib
 LIBRARY			esock.lib insock.lib
 STATICLIBRARY		pjlib.lib pjmedia.lib
-STATICLIBRARY		symbian_audio.lib
+STATICLIBRARY		pjmedia_audiodev.lib
+STATICLIBRARY		libresample.lib
 
 #if SND_USE_APS
 	LIBRARY		APSSession2.lib
 	CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
 #elif SND_USE_VAS
-//	LIBRARY		
+	LIBRARY		VoIPAudioIntfc.lib
 	CAPABILITY	NetworkServices LocalServices ReadUserData WriteUserData UserEnvironment MultimediaDD
 #else
 	LIBRARY 	mediaclientaudiostream.lib
diff --git a/build.symbian/symsndtest.pkg b/build.symbian/symsndtest.pkg
index 4c505fa..9dbb118 100644
--- a/build.symbian/symsndtest.pkg
+++ b/build.symbian/symsndtest.pkg
@@ -14,6 +14,6 @@
 :"PJSIP"

 

 ; Target

-"C:\Symbian\9.1\S60_3rd_MR\Epoc32\release\gcce\UDEB\symsndtest.exe"-"!:\sys\bin\symsndtest.exe"

-"C:\Symbian\9.1\S60_3rd_MR\Epoc32\data\z\private\10003a3f\apps\symsndtest_reg.rSC"-"!:\private\10003a3f\import\apps\symsndtest_reg.rSC"

+"$(EPOCROOT)Epoc32\release\$(PLATFORM)\$(TARGET)\symsndtest.exe"-"!:\sys\bin\symsndtest.exe"

+"$(EPOCROOT)Epoc32\data\z\private\10003a3f\apps\symsndtest_reg.rSC"-"!:\private\10003a3f\import\apps\symsndtest_reg.rSC"

 

diff --git a/pjlib/include/pj/config_site_sample.h b/pjlib/include/pj/config_site_sample.h
index e64d97f..2357c41 100644
--- a/pjlib/include/pj/config_site_sample.h
+++ b/pjlib/include/pj/config_site_sample.h
@@ -240,6 +240,34 @@
 
 
 /*
+ * Additional configuration to activate VAS-Direct feature for
+ * Nokia S60 target
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct
+ */
+#ifdef PJ_CONFIG_NOKIA_VAS_DIRECT
+
+    /* MUST use switchboard rather than the conference bridge */
+    #define PJMEDIA_CONF_USE_SWITCH_BOARD	1
+
+    /* Enable APS sound device backend and disable MDA */
+    #define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA	0
+    #define PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS	1
+
+    /* Enable passthrough codec framework */
+    #define PJMEDIA_HAS_PASSTHROUGH_CODECS	1
+
+    /* And selectively enable which codecs are supported by the handset */
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729	1
+    #define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC	1
+
+#endif
+
+
+/*
  * Configuration to activate "APS-Direct" media mode on Windows,
  * useful for testing purposes only.
  */
diff --git a/pjmedia/build/pjmedia_audiodev.vcproj b/pjmedia/build/pjmedia_audiodev.vcproj
index a3b5593..6feb110 100644
--- a/pjmedia/build/pjmedia_audiodev.vcproj
+++ b/pjmedia/build/pjmedia_audiodev.vcproj
Binary files differ
diff --git a/pjmedia/include/pjmedia-audiodev/config.h b/pjmedia/include/pjmedia-audiodev/config.h
index b4603c1..f2b4298 100644
--- a/pjmedia/include/pjmedia-audiodev/config.h
+++ b/pjmedia/include/pjmedia-audiodev/config.h
@@ -67,6 +67,14 @@
 
 
 /**
+ * This setting controls whether Symbian VAS support should be included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+#   define PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS	0
+#endif
+
+
+/**
  * This setting controls whether Symbian audio (using built-in multimedia 
  * framework) support should be included.
  */
diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c
index 8025e04..93939aa 100644
--- a/pjmedia/src/pjmedia-audiodev/audiodev.c
+++ b/pjmedia/src/pjmedia-audiodev/audiodev.c
@@ -74,6 +74,10 @@
 pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf);
 #endif
 
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+pjmedia_aud_dev_factory* pjmedia_symb_vas_factory(pj_pool_factory *pf);
+#endif
+
 #if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
 pjmedia_aud_dev_factory* pjmedia_aps_factory(pj_pool_factory *pf);
 #endif
@@ -370,6 +374,9 @@
 #if PJMEDIA_AUDIO_DEV_HAS_WMME
     aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_wmme_factory;
 #endif
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+    aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_symb_vas_factory;
+#endif
 #if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
     aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_aps_factory;
 #endif
diff --git a/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp
new file mode 100644
index 0000000..d84e17a
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp
@@ -0,0 +1,1888 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/resample.h>
+#include <pjmedia/stereo.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+
+/* VAS headers */
+#include <VoIPUtilityFactory.h>
+#include <VoIPDownlinkStream.h>
+#include <VoIPUplinkStream.h>
+#include <VoIPFormatIntfc.h>
+#include <VoIPG711DecoderIntfc.h>
+#include <VoIPG711EncoderIntfc.h>
+#include <VoIPG729DecoderIntfc.h>
+#include <VoIPILBCDecoderIntfc.h>
+#include <VoIPILBCEncoderIntfc.h>
+
+/* AMR helper */  
+#include <pjmedia-codec/amr_helper.h>
+
+/* Pack/unpack G.729 frame of S60 DSP codec, taken from:  
+ * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
+ */
+#include "s60_g729_bitstream.h"
+
+
+#define THIS_FILE			"symb_vas_dev.c"
+#define BITS_PER_SAMPLE			16
+
+
+/* When this macro is set, VAS will use EPCM16 format for PCM input/output,
+ * otherwise VAS will use EG711 then transcode it to PCM.
+ * Note that using native EPCM16 format may introduce (much) delay.
+ */
+//#define USE_NATIVE_PCM
+
+#if 1
+#   define TRACE_(st) PJ_LOG(3, st)
+#else
+#   define TRACE_(st)
+#endif
+
+/* VAS G.711 frame length */
+static pj_uint8_t vas_g711_frame_len;
+
+
+/* VAS factory */
+struct vas_factory
+{
+    pjmedia_aud_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+    pjmedia_aud_dev_info	 dev_info;
+};
+
+
+/* Forward declaration of CPjAudioEngine */
+class CPjAudioEngine;
+
+
+/* VAS stream. */
+struct vas_stream
+{
+    // Base
+    pjmedia_aud_stream	 base;			/**< Base class.	*/
+    
+    // Pool
+    pj_pool_t		*pool;			/**< Memory pool.       */
+
+    // Common settings.
+    pjmedia_aud_param 	 param;			/**< Stream param.	*/
+    pjmedia_aud_rec_cb   rec_cb;		/**< Record callback.  	*/
+    pjmedia_aud_play_cb	 play_cb;		/**< Playback callback. */
+    void                *user_data;		/**< Application data.  */
+
+    // Audio engine
+    CPjAudioEngine	*engine;		/**< Internal engine.	*/
+
+    pj_timestamp  	 ts_play;		/**< Playback timestamp.*/
+    pj_timestamp	 ts_rec;		/**< Record timestamp.	*/
+
+    pj_int16_t		*play_buf;		/**< Playback buffer.	*/
+    pj_uint16_t		 play_buf_len;		/**< Playback buffer length. */
+    pj_uint16_t		 play_buf_start;	/**< Playback buffer start index. */
+    pj_int16_t		*rec_buf;		/**< Record buffer.	*/
+    pj_uint16_t		 rec_buf_len;		/**< Record buffer length. */
+    void                *strm_data;		/**< Stream data.	*/
+
+    /* Resampling is needed, in case audio device is opened with clock rate 
+     * other than 8kHz (only for PCM format).
+     */
+    pjmedia_resample	*play_resample;		/**< Resampler for playback. */
+    pjmedia_resample	*rec_resample;		/**< Resampler for recording */
+    pj_uint16_t		 resample_factor;	/**< Resample factor, requested
+						     clock rate / 8000	     */
+
+    /* When stream is working in PCM format, where the samples may need to be
+     * resampled from/to different clock rate and/or channel count, PCM buffer
+     * is needed to perform such resampling operations.
+     */
+    pj_int16_t		*pcm_buf;		/**< PCM buffer.	     */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static unsigned    factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+				    pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+				  pjmedia_aud_dev_cap cap,
+				  const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+    &factory_init,
+    &factory_destroy,
+    &factory_get_dev_count,
+    &factory_get_dev_info,
+    &factory_default_param,
+    &factory_create_stream
+};
+
+static pjmedia_aud_stream_op stream_op = 
+{
+    &stream_get_param,
+    &stream_get_cap,
+    &stream_set_cap,
+    &stream_start,
+    &stream_stop,
+    &stream_destroy
+};
+
+
+/****************************************************************************
+ * Internal VAS Engine
+ */
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc)
+{
+    PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
+}
+
+typedef void(*PjAudioCallback)(CVoIPDataBuffer *buf, void *user_data);
+
+/*
+ * Audio setting for CPjAudioEngine.
+ */
+class CPjAudioSetting
+{
+public:
+    TVoIPCodecFormat	 format;
+    TInt		 mode;
+    TBool		 plc;
+    TBool		 vad;
+    TBool		 cng;
+    TBool		 loudspk;
+};
+
+/*
+ * Implementation: Symbian Input & Output Stream.
+ */
+class CPjAudioEngine :  public CBase,
+			public MVoIPDownlinkObserver,
+			public MVoIPUplinkObserver,
+			public MVoIPFormatObserver
+{
+public:
+    enum State
+    {
+	STATE_NULL,
+	STATE_STARTING,
+	STATE_READY,
+	STATE_STREAMING
+    };
+
+    ~CPjAudioEngine();
+
+    static CPjAudioEngine *NewL(struct vas_stream *parent_strm,
+			        PjAudioCallback rec_cb,
+				PjAudioCallback play_cb,
+				void *user_data,
+				const CPjAudioSetting &setting);
+
+    TInt Start();
+    void Stop();
+
+    TInt ActivateSpeaker(TBool active);
+    
+    TInt SetVolume(TInt vol) { return iVoIPDnlink->SetVolume(vol); }
+    TInt GetVolume() { TInt vol;iVoIPDnlink->GetVolume(vol);return vol; }
+    TInt GetMaxVolume() { TInt vol;iVoIPDnlink->GetMaxVolume(vol);return vol; }
+    
+    TInt SetGain(TInt gain) { return iVoIPUplink->SetGain(gain); }
+    TInt GetGain() { TInt gain;iVoIPUplink->GetGain(gain);return gain; }
+    TInt GetMaxGain() { TInt gain;iVoIPUplink->GetMaxGain(gain);return gain; }
+
+private:
+    CPjAudioEngine(struct vas_stream *parent_strm,
+		   PjAudioCallback rec_cb,
+		   PjAudioCallback play_cb,
+		   void *user_data,
+		   const CPjAudioSetting &setting);
+    void ConstructL();
+
+    TInt InitPlay();
+    TInt InitRec();
+
+    TInt StartPlay();
+    TInt StartRec();
+
+    // From MVoIPDownlinkObserver
+    virtual void FillBuffer(const CVoIPAudioDownlinkStream& aSrc,
+                            CVoIPDataBuffer* aBuffer);
+    virtual void Event(const CVoIPAudioDownlinkStream& aSrc,
+                       TInt aEventType,
+                       TInt aError);
+
+    // From MVoIPUplinkObserver
+    virtual void EmptyBuffer(const CVoIPAudioUplinkStream& aSrc,
+                             CVoIPDataBuffer* aBuffer);
+    virtual void Event(const CVoIPAudioUplinkStream& aSrc,
+                       TInt aEventType,
+                       TInt aError);
+
+    // From MVoIPFormatObserver
+    virtual void Event(const CVoIPFormatIntfc& aSrc, TInt aEventType);
+
+    State			 dn_state_;
+    State			 up_state_;
+    struct vas_stream		*parentStrm_;
+    CPjAudioSetting		 setting_;
+    PjAudioCallback 		 rec_cb_;
+    PjAudioCallback 		 play_cb_;
+    void 			*user_data_;
+
+    // VAS objects
+    CVoIPUtilityFactory         *iFactory;
+    CVoIPAudioDownlinkStream    *iVoIPDnlink;
+    CVoIPAudioUplinkStream      *iVoIPUplink;
+    CVoIPFormatIntfc		*enc_fmt_if;
+    CVoIPFormatIntfc		*dec_fmt_if;
+};
+
+
+CPjAudioEngine* CPjAudioEngine::NewL(struct vas_stream *parent_strm,
+				     PjAudioCallback rec_cb,
+				     PjAudioCallback play_cb,
+				     void *user_data,
+				     const CPjAudioSetting &setting)
+{
+    CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
+						       rec_cb, play_cb,
+						       user_data,
+						       setting);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    CleanupStack::Pop(self);
+    return self;
+}
+
+void CPjAudioEngine::ConstructL()
+{
+    TInt err;
+    const TVersion ver(1, 0, 0);
+
+    err = CVoIPUtilityFactory::CreateFactory(iFactory);
+    User::LeaveIfError(err);
+
+    if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) {
+	err = iFactory->CreateDownlinkStream(ver, 
+					     CVoIPUtilityFactory::EVoIPCall,
+					     iVoIPDnlink);
+	User::LeaveIfError(err);
+    }
+
+    if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
+	err = iFactory->CreateUplinkStream(ver, 
+					   CVoIPUtilityFactory::EVoIPCall,
+					   iVoIPUplink);
+	User::LeaveIfError(err);
+    }
+}
+
+CPjAudioEngine::CPjAudioEngine(struct vas_stream *parent_strm,
+			       PjAudioCallback rec_cb,
+			       PjAudioCallback play_cb,
+			       void *user_data,
+			       const CPjAudioSetting &setting)
+      : dn_state_(STATE_NULL),
+        up_state_(STATE_NULL),
+	parentStrm_(parent_strm),
+	setting_(setting),
+        rec_cb_(rec_cb),
+        play_cb_(play_cb),
+        user_data_(user_data),
+        iFactory(NULL),
+        iVoIPDnlink(NULL),
+        iVoIPUplink(NULL),
+        enc_fmt_if(NULL),
+        dec_fmt_if(NULL)
+{
+}
+
+CPjAudioEngine::~CPjAudioEngine()
+{
+    Stop();
+    
+    if (iVoIPUplink)
+	iVoIPUplink->Close();
+    
+    if (iVoIPDnlink)
+	iVoIPDnlink->Close();
+
+    delete iVoIPDnlink;
+    delete iVoIPUplink;
+    delete iFactory;
+    
+    TRACE_((THIS_FILE, "Sound device destroyed"));
+}
+
+TInt CPjAudioEngine::InitPlay()
+{
+    TInt err;
+
+    pj_assert(iVoIPDnlink);
+
+    err = iVoIPDnlink->SetFormat(setting_.format, dec_fmt_if);
+    if (err != KErrNone)
+	return err;
+    
+    err = dec_fmt_if->SetObserver(*this);
+    if (err != KErrNone)
+	return err;
+
+    return iVoIPDnlink->Open(*this);
+}
+
+TInt CPjAudioEngine::InitRec()
+{
+    TInt err;
+    
+    pj_assert(iVoIPUplink);
+
+    err = iVoIPUplink->SetFormat(setting_.format, enc_fmt_if);
+    if (err != KErrNone)
+	return err;
+
+    return iVoIPUplink->Open(*this);
+}
+
+TInt CPjAudioEngine::StartPlay()
+{
+    pj_assert(iVoIPDnlink);
+    pj_assert(dn_state_ == STATE_READY);
+
+    /* Configure specific codec setting */
+    switch (setting_.format) {
+    case EG711:
+    //case EG711_10MS:
+	{
+	    CVoIPG711DecoderIntfc *g711dec_if = (CVoIPG711DecoderIntfc*)
+						dec_fmt_if;
+	    g711dec_if->SetMode((CVoIPFormatIntfc::TG711CodecMode)
+				setting_.mode);
+	}
+	break;
+	
+    case EILBC:
+	{
+	    CVoIPILBCDecoderIntfc *ilbcdec_if = (CVoIPILBCDecoderIntfc*)
+						dec_fmt_if;
+	    ilbcdec_if->SetMode((CVoIPFormatIntfc::TILBCCodecMode)
+				setting_.mode);
+	}
+	break;
+
+    default:
+	break;
+    }
+    
+    /* Configure audio routing */
+    ActivateSpeaker(setting_.loudspk);
+
+    /* Start player */
+    TInt err = iVoIPDnlink->Start();
+    
+    if (err == KErrNone) {
+	dn_state_ = STATE_STREAMING;
+	TRACE_((THIS_FILE, "Downlink started"));
+    } else {
+	snd_perror("Failed starting downlink", err);
+    }
+
+    return err;
+}
+
+TInt CPjAudioEngine::StartRec()
+{
+    pj_assert(iVoIPUplink);
+    pj_assert(up_state_ == STATE_READY);
+
+    /* Configure general codec setting */
+    enc_fmt_if->SetVAD(setting_.vad);
+    
+    /* Configure specific codec setting */
+    switch (setting_.format) {
+    case EG711:
+    //case EG711_10MS:
+	{
+	    CVoIPG711EncoderIntfc *g711enc_if = (CVoIPG711EncoderIntfc*)
+						enc_fmt_if;
+	    g711enc_if->SetMode((CVoIPFormatIntfc::TG711CodecMode)
+				setting_.mode);
+	}
+	break;
+
+    case EILBC:
+	{
+	    CVoIPILBCEncoderIntfc *ilbcenc_if = (CVoIPILBCEncoderIntfc*)
+						enc_fmt_if;
+	    ilbcenc_if->SetMode((CVoIPFormatIntfc::TILBCCodecMode)
+				setting_.mode);
+	}
+	break;
+	
+    default:
+	break;
+    }
+    
+    /* Start recorder */
+    TInt err = iVoIPUplink->Start();
+    
+    if (err == KErrNone) {
+	up_state_ = STATE_STREAMING;
+	TRACE_((THIS_FILE, "Uplink started"));
+    } else {
+	snd_perror("Failed starting uplink", err);
+    }
+
+    return err;
+}
+
+TInt CPjAudioEngine::Start()
+{
+    TInt err = KErrNone;
+    
+    if (iVoIPDnlink) {
+	switch(dn_state_) {
+	case STATE_READY:
+	    err = StartPlay();
+	    break;
+	case STATE_NULL:
+	    err = InitPlay();
+	    if (err != KErrNone)
+		return err;
+	    dn_state_ = STATE_STARTING;
+	    break;
+	default:
+	    break;
+	}
+    }
+    
+    if (iVoIPUplink) {
+	switch(up_state_) {
+	case STATE_READY:
+	    err = StartRec();
+	    break;
+	case STATE_NULL:
+	    err = InitRec();
+	    if (err != KErrNone)
+		return err;
+	    up_state_ = STATE_STARTING;
+	    break;
+	default:
+	    break;
+	}
+    }
+
+    return err;
+}
+
+void CPjAudioEngine::Stop()
+{
+    if (iVoIPDnlink) {
+	switch(dn_state_) {
+	case STATE_STREAMING:
+	    iVoIPDnlink->Stop();
+	    dn_state_ = STATE_READY;
+	    break;
+	case STATE_STARTING:
+	    dn_state_ = STATE_NULL;
+	    break;
+	default:
+	    break;
+	}
+    }
+
+    if (iVoIPUplink) {
+	switch(up_state_) {
+	case STATE_STREAMING:
+	    iVoIPUplink->Stop();
+	    up_state_ = STATE_READY;
+	    break;
+	case STATE_STARTING:
+	    up_state_ = STATE_NULL;
+	    break;
+	default:
+	    break;
+	}
+    }
+}
+
+
+TInt CPjAudioEngine::ActivateSpeaker(TBool active)
+{
+    TInt err = KErrNotSupported;
+    
+    if (iVoIPDnlink) {
+	err = iVoIPDnlink->SetAudioDevice(active?
+				    CVoIPAudioDownlinkStream::ELoudSpeaker :
+				    CVoIPAudioDownlinkStream::EHandset);
+	TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off")));
+    }
+    
+    return err;
+}
+
+// Callback from MVoIPDownlinkObserver
+void CPjAudioEngine::FillBuffer(const CVoIPAudioDownlinkStream& aSrc,
+                                CVoIPDataBuffer* aBuffer)
+{
+    play_cb_(aBuffer, user_data_);
+    iVoIPDnlink->BufferFilled(aBuffer);
+}
+
+// Callback from MVoIPUplinkObserver
+void CPjAudioEngine::EmptyBuffer(const CVoIPAudioUplinkStream& aSrc,
+                                 CVoIPDataBuffer* aBuffer)
+{
+    rec_cb_(aBuffer, user_data_);
+    iVoIPUplink->BufferEmptied(aBuffer);
+}
+
+// Callback from MVoIPDownlinkObserver
+void CPjAudioEngine::Event(const CVoIPAudioDownlinkStream& /*aSrc*/,
+                           TInt aEventType,
+                           TInt aError)
+{
+    switch (aEventType) {
+    case MVoIPDownlinkObserver::KOpenComplete:
+	if (aError == KErrNone) {
+	    State last_state = up_state_;
+
+	    dn_state_ = STATE_READY;
+	    TRACE_((THIS_FILE, "Downlink opened"));
+
+	    if (last_state == STATE_STARTING)
+		StartPlay();
+	}
+	break;
+
+    case MVoIPDownlinkObserver::KDownlinkClosed:
+	dn_state_ = STATE_NULL;
+	TRACE_((THIS_FILE, "Downlink closed"));
+	break;
+
+    case MVoIPDownlinkObserver::KDownlinkError:
+	dn_state_ = STATE_READY;
+	snd_perror("Downlink problem", aError);
+	break;
+    default:
+	break;
+    }
+}
+
+// Callback from MVoIPUplinkObserver
+void CPjAudioEngine::Event(const CVoIPAudioUplinkStream& /*aSrc*/,
+                           TInt aEventType,
+                           TInt aError)
+{
+    switch (aEventType) {
+    case MVoIPUplinkObserver::KOpenComplete:
+	if (aError == KErrNone) {
+	    State last_state = up_state_;
+
+	    up_state_ = STATE_READY;
+	    TRACE_((THIS_FILE, "Uplink opened"));
+	    
+	    if (last_state == STATE_STARTING)
+		StartRec();
+	}
+	break;
+
+    case MVoIPUplinkObserver::KUplinkClosed:
+	up_state_ = STATE_NULL;
+	TRACE_((THIS_FILE, "Uplink closed"));
+	break;
+
+    case MVoIPUplinkObserver::KUplinkError:
+	up_state_ = STATE_READY;
+	snd_perror("Uplink problem", aError);
+	break;
+    default:
+	break;
+    }
+}
+
+// Callback from MVoIPFormatObserver
+void CPjAudioEngine::Event(const CVoIPFormatIntfc& /*aSrc*/, 
+			   TInt /*aEventType*/)
+{
+}
+
+/****************************************************************************
+ * Internal VAS callbacks for PCM format
+ */
+
+#ifdef USE_NATIVE_PCM
+
+static void RecCbPcm2(CVoIPDataBuffer *buf, void *user_data)
+{
+    struct vas_stream *strm = (struct vas_stream*) user_data;
+    TPtr8 buffer(0, 0, 0);
+    pj_int16_t *p_buf;
+    unsigned buf_len;
+
+    /* Get the buffer */
+    buf->GetPayloadPtr(buffer);
+    
+    /* Call parent callback */
+    p_buf = (pj_int16_t*) buffer.Ptr();
+    buf_len = buffer.Length() >> 1;
+    while (buf_len) {
+	unsigned req;
+	
+	req = strm->param.samples_per_frame - strm->rec_buf_len;
+	if (req > buf_len)
+	    req = buf_len;
+	pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_len, p_buf, req);
+	p_buf += req;
+	buf_len -= req;
+	strm->rec_buf_len += req;
+	
+	if (strm->rec_buf_len >= strm->param.samples_per_frame) {
+	    pjmedia_frame f;
+
+	    f.buf = strm->rec_buf;
+	    f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    f.size = strm->param.samples_per_frame << 1;
+	    strm->rec_cb(strm->user_data, &f);
+	    strm->rec_buf_len = 0;
+	}
+    }
+}
+
+static void PlayCbPcm2(CVoIPDataBuffer *buf, void *user_data)
+{
+    struct vas_stream *strm = (struct vas_stream*) user_data;
+    TPtr8 buffer(0, 0, 0);
+    pjmedia_frame f;
+
+    /* Get the buffer */
+    buf->GetPayloadPtr(buffer);
+
+    /* Call parent callback */
+    f.buf = strm->play_buf;
+    f.size = strm->param.samples_per_frame << 1;
+    strm->play_cb(strm->user_data, &f);
+    if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+	pjmedia_zero_samples((pj_int16_t*)f.buf, 
+			     strm->param.samples_per_frame);
+    }
+    f.size = strm->param.samples_per_frame << 1;
+
+    /* Init buffer attributes and header. */
+    buffer.Zero();
+    buffer.Append((TUint8*)f.buf, f.size);
+
+    /* Set the buffer */
+    buf->SetPayloadPtr(buffer);
+}
+
+#else // not USE_NATIVE_PCM
+
+static void RecCbPcm(CVoIPDataBuffer *buf, void *user_data)
+{
+    struct vas_stream *strm = (struct vas_stream*) user_data;
+    TPtr8 buffer(0, 0, 0);
+
+    /* Get the buffer */
+    buf->GetPayloadPtr(buffer);
+    
+    /* Buffer has to contain normal speech. */
+    pj_assert(buffer[0] == 1 && buffer[1] == 0);
+
+    /* Detect the recorder G.711 frame size, player frame size will follow
+     * this recorder frame size.
+     */
+    if (vas_g711_frame_len == 0) {
+	vas_g711_frame_len = buffer.Length() < 160? 80 : 160;
+	TRACE_((THIS_FILE, "Detected VAS G.711 frame size = %u samples",
+		vas_g711_frame_len));
+    }
+
+    /* Decode VAS buffer (coded in G.711) and put the PCM result into rec_buf.
+     * Whenever rec_buf is full, call parent stream callback.
+     */
+    unsigned samples_processed = 0;
+
+    while (samples_processed < vas_g711_frame_len) {
+	unsigned samples_to_process;
+	unsigned samples_req;
+
+	samples_to_process = vas_g711_frame_len - samples_processed;
+	samples_req = (strm->param.samples_per_frame /
+		       strm->param.channel_count /
+		       strm->resample_factor) -
+		      strm->rec_buf_len;
+	if (samples_to_process > samples_req)
+	    samples_to_process = samples_req;
+
+	pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
+			    buffer.Ptr() + 2 + samples_processed,
+			    samples_to_process);
+
+	strm->rec_buf_len += samples_to_process;
+	samples_processed += samples_to_process;
+
+	/* Buffer is full, time to call parent callback */
+	if (strm->rec_buf_len == strm->param.samples_per_frame / 
+				 strm->param.channel_count /
+				 strm->resample_factor) 
+	{
+	    pjmedia_frame f;
+
+	    /* Need to resample clock rate? */
+	    if (strm->rec_resample) {
+		unsigned resampled = 0;
+		
+		while (resampled < strm->rec_buf_len) {
+		    pjmedia_resample_run(strm->rec_resample, 
+				&strm->rec_buf[resampled],
+				strm->pcm_buf + 
+				resampled * strm->resample_factor);
+		    resampled += 80;
+		}
+		f.buf = strm->pcm_buf;
+	    } else {
+		f.buf = strm->rec_buf;
+	    }
+
+	    /* Need to convert channel count? */
+	    if (strm->param.channel_count != 1) {
+		pjmedia_convert_channel_1ton((pj_int16_t*)f.buf,
+					     (pj_int16_t*)f.buf,
+					     strm->param.channel_count,
+					     strm->param.samples_per_frame /
+					     strm->param.channel_count,
+					     0);
+	    }
+
+	    /* Call parent callback */
+	    f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    f.size = strm->param.samples_per_frame << 1;
+	    strm->rec_cb(strm->user_data, &f);
+	    strm->rec_buf_len = 0;
+	}
+    }
+}
+
+#endif // USE_NATIVE_PCM
+
+static void PlayCbPcm(CVoIPDataBuffer *buf, void *user_data)
+{
+    struct vas_stream *strm = (struct vas_stream*) user_data;
+    unsigned g711_frame_len = vas_g711_frame_len;
+    TPtr8 buffer(0, 0, 0);
+
+    /* Get the buffer */
+    buf->GetPayloadPtr(buffer);
+
+    /* Init buffer attributes and header. */
+    buffer.Zero();
+    buffer.Append(1);
+    buffer.Append(0);
+
+    /* Assume frame size is 10ms if frame size hasn't been known. */
+    if (g711_frame_len == 0)
+	g711_frame_len = 80;
+
+    /* Call parent stream callback to get PCM samples to play,
+     * encode the PCM samples into G.711 and put it into VAS buffer.
+     */
+    unsigned samples_processed = 0;
+    
+    while (samples_processed < g711_frame_len) {
+	/* Need more samples to play, time to call parent callback */
+	if (strm->play_buf_len == 0) {
+	    pjmedia_frame f;
+	    unsigned samples_got;
+	    
+	    f.size = strm->param.samples_per_frame << 1;
+	    if (strm->play_resample || strm->param.channel_count != 1)
+		f.buf = strm->pcm_buf;
+	    else
+		f.buf = strm->play_buf;
+
+	    /* Call parent callback */
+	    strm->play_cb(strm->user_data, &f);
+	    if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+		pjmedia_zero_samples((pj_int16_t*)f.buf, 
+				     strm->param.samples_per_frame);
+	    }
+	    
+	    samples_got = strm->param.samples_per_frame / 
+			  strm->param.channel_count /
+			  strm->resample_factor;
+
+	    /* Need to convert channel count? */
+	    if (strm->param.channel_count != 1) {
+		pjmedia_convert_channel_nto1((pj_int16_t*)f.buf,
+					     (pj_int16_t*)f.buf,
+					     strm->param.channel_count,
+					     strm->param.samples_per_frame,
+					     PJ_FALSE,
+					     0);
+	    }
+
+	    /* Need to resample clock rate? */
+	    if (strm->play_resample) {
+		unsigned resampled = 0;
+		
+		while (resampled < samples_got) 
+		{
+		    pjmedia_resample_run(strm->play_resample, 
+				strm->pcm_buf + 
+				resampled * strm->resample_factor,
+				&strm->play_buf[resampled]);
+		    resampled += 80;
+		}
+	    }
+	    
+	    strm->play_buf_len = samples_got;
+	    strm->play_buf_start = 0;
+	}
+
+	unsigned tmp;
+
+	tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - samples_processed);
+	pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
+			    &strm->play_buf[strm->play_buf_start],
+			    tmp);
+	buffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
+	samples_processed += tmp;
+	strm->play_buf_len -= tmp;
+	strm->play_buf_start += tmp;
+    }
+
+    /* Set the buffer */
+    buf->SetPayloadPtr(buffer);
+}
+
+/****************************************************************************
+ * Internal VAS callbacks for non-PCM format
+ */
+
+static void RecCb(CVoIPDataBuffer *buf, void *user_data)
+{
+    struct vas_stream *strm = (struct vas_stream*) user_data;
+    pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
+    TPtr8 buffer(0, 0, 0);
+
+    /* Get the buffer */
+    buf->GetPayloadPtr(buffer);
+    
+    switch(strm->param.ext_fmt.id) {
+    case PJMEDIA_FORMAT_AMR:
+	{
+	    const pj_uint8_t *p = (const pj_uint8_t*)buffer.Ptr() + 1;
+	    unsigned len = buffer.Length() - 1;
+	    
+	    pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
+	    if (frame->samples_cnt == strm->param.samples_per_frame) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_G729:
+	{
+	    /* Check if we got a normal or SID frame. */
+	    if (buffer[0] != 0 || buffer[1] != 0) {
+		enum { NORMAL_LEN = 22, SID_LEN = 8 };
+		TBitStream *bitstream = (TBitStream*)strm->strm_data;
+		unsigned src_len = buffer.Length()- 2;
+		
+		pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
+
+		const TDesC8& p = bitstream->CompressG729Frame(
+					    buffer.Right(src_len), 
+					    src_len == SID_LEN);
+		
+		pjmedia_frame_ext_append_subframe(frame, p.Ptr(), 
+						  p.Length() << 3, 80);
+	    } else { /* We got null frame. */
+		pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
+	    }
+	    
+	    if (frame->samples_cnt == strm->param.samples_per_frame) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+
+    case PJMEDIA_FORMAT_ILBC:
+	{
+	    unsigned samples_got;
+	    
+	    samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240;
+	    
+	    /* Check if we got a normal frame. */
+	    if (buffer[0] == 1 && buffer[1] == 0) {
+		const pj_uint8_t *p = (const pj_uint8_t*)buffer.Ptr() + 2;
+		unsigned len = buffer.Length() - 2;
+		
+		pjmedia_frame_ext_append_subframe(frame, p, len << 3,
+						  samples_got);
+	    } else { /* We got null frame. */
+		pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
+	    }
+	    
+	    if (frame->samples_cnt == strm->param.samples_per_frame) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_PCMU:
+    case PJMEDIA_FORMAT_PCMA:
+	{
+	    unsigned samples_processed = 0;
+	    
+	    /* Make sure it is normal frame. */
+	    pj_assert(buffer[0] == 1 && buffer[1] == 0);
+
+	    /* Detect the recorder G.711 frame size, player frame size will 
+	     * follow this recorder frame size.
+	     */
+	    if (vas_g711_frame_len == 0) {
+		vas_g711_frame_len = buffer.Length() < 160? 80 : 160;
+		TRACE_((THIS_FILE, "Detected VAS G.711 frame size = %u samples",
+			vas_g711_frame_len));
+	    }
+	    
+	    /* Convert VAS buffer format into pjmedia_frame_ext. Whenever 
+	     * samples count in the frame is equal to stream's samples per 
+	     * frame, call parent stream callback.
+	     */
+	    while (samples_processed < vas_g711_frame_len) {
+		unsigned tmp;
+		const pj_uint8_t *pb = (const pj_uint8_t*)buffer.Ptr() +
+				       2 + samples_processed;
+    
+		tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
+			     vas_g711_frame_len - samples_processed);
+		
+		pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
+		samples_processed += tmp;
+    
+		if (frame->samples_cnt == strm->param.samples_per_frame) {
+		    frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		    strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+		    frame->samples_cnt = 0;
+		    frame->subframe_cnt = 0;
+		}
+	    }
+	}
+	break;
+	
+    default:
+	break;
+    }
+}
+
+static void PlayCb(CVoIPDataBuffer *buf, void *user_data)
+{
+    struct vas_stream *strm = (struct vas_stream*) user_data;
+    pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
+    TPtr8 buffer(0, 0, 0);
+
+    /* Get the buffer */
+    buf->GetPayloadPtr(buffer);
+
+    /* Init buffer attributes and header. */
+    buffer.Zero();
+
+    switch(strm->param.ext_fmt.id) {
+    case PJMEDIA_FORMAT_AMR:
+	{
+	    if (frame->samples_cnt == 0) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+		pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+			  frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+	    }
+
+	    if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { 
+		pjmedia_frame_ext_subframe *sf;
+		unsigned samples_cnt;
+		
+		sf = pjmedia_frame_ext_get_subframe(frame, 0);
+		samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+		
+		if (sf->data && sf->bitlen) {
+		    /* AMR header for VAS is one byte, the format (may be!):
+		     * 0xxxxy00, where xxxx:frame type, y:not sure. 
+		     */
+		    unsigned len = (sf->bitlen+7)>>3;
+		    enum {SID_FT = 8 };
+		    pj_uint8_t amr_header = 4, ft = SID_FT;
+
+		    if (len >= pjmedia_codec_amrnb_framelen[0])
+			ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
+		    
+		    amr_header |= ft << 3;
+		    buffer.Append(amr_header);
+		    
+		    buffer.Append((TUint8*)sf->data, len);
+		} else {
+		    buffer.Append(0);
+		}
+
+		pjmedia_frame_ext_pop_subframes(frame, 1);
+	    
+	    } else { /* PJMEDIA_FRAME_TYPE_NONE */
+		buffer.Append(0);
+		
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_G729:
+	{
+	    if (frame->samples_cnt == 0) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+		pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+			  frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+	    }
+
+	    if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { 
+		pjmedia_frame_ext_subframe *sf;
+		unsigned samples_cnt;
+		
+		sf = pjmedia_frame_ext_get_subframe(frame, 0);
+		samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+		
+		if (sf->data && sf->bitlen) {
+		    enum { NORMAL_LEN = 10, SID_LEN = 2 };
+		    pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
+		    TBitStream *bitstream = (TBitStream*)strm->strm_data;
+		    const TPtrC8 src(sf->data, sf->bitlen>>3);
+		    const TDesC8 &dst = bitstream->ExpandG729Frame(src,
+								   sid_frame); 
+		    if (sid_frame) {
+			buffer.Append(0);
+			buffer.Append(1);
+		    } else {
+			buffer.Append(1);
+			buffer.Append(0);
+		    }
+		    buffer.Append(dst);
+		} else {
+		    buffer.Append(0);
+		    buffer.Append(0);
+		}
+
+		pjmedia_frame_ext_pop_subframes(frame, 1);
+	    
+	    } else { /* PJMEDIA_FRAME_TYPE_NONE */
+		buffer.Append(0);
+		buffer.Append(0);
+		
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_ILBC:
+	{
+	    if (frame->samples_cnt == 0) {
+		frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+		pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+			  frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+	    }
+
+	    if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { 
+		pjmedia_frame_ext_subframe *sf;
+		unsigned samples_cnt;
+		
+		sf = pjmedia_frame_ext_get_subframe(frame, 0);
+		samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+		
+		pj_assert((strm->param.ext_fmt.bitrate == 15200 && 
+			   samples_cnt == 160) ||
+			  (strm->param.ext_fmt.bitrate != 15200 &&
+			   samples_cnt == 240));
+		
+		if (sf->data && sf->bitlen) {
+		    buffer.Append(1);
+		    buffer.Append(0);
+		    buffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+		} else {
+		    buffer.Append(0);
+		    buffer.Append(0);
+		}
+
+		pjmedia_frame_ext_pop_subframes(frame, 1);
+	    
+	    } else { /* PJMEDIA_FRAME_TYPE_NONE */
+		buffer.Append(0);
+		buffer.Append(0);
+		
+		frame->samples_cnt = 0;
+		frame->subframe_cnt = 0;
+	    }
+	}
+	break;
+	
+    case PJMEDIA_FORMAT_PCMU:
+    case PJMEDIA_FORMAT_PCMA:
+	{
+	    unsigned samples_ready = 0;
+	    unsigned samples_req = vas_g711_frame_len;
+	    
+	    /* Assume frame size is 10ms if frame size hasn't been known. */
+	    if (samples_req == 0)
+		samples_req = 80;
+	    
+	    buffer.Append(1);
+	    buffer.Append(0);
+	    
+	    /* Call parent stream callback to get samples to play. */
+	    while (samples_ready < samples_req) {
+		if (frame->samples_cnt == 0) {
+		    frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+		    strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+		    pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+			      frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+		}
+    
+		if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { 
+		    pjmedia_frame_ext_subframe *sf;
+		    unsigned samples_cnt;
+		    
+		    sf = pjmedia_frame_ext_get_subframe(frame, 0);
+		    samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+		    if (sf->data && sf->bitlen) {
+			buffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+		    } else {
+			pj_uint8_t silc;
+			silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+				pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+			buffer.AppendFill(silc, samples_cnt);
+		    }
+		    samples_ready += samples_cnt;
+		    
+		    pjmedia_frame_ext_pop_subframes(frame, 1);
+		
+		} else { /* PJMEDIA_FRAME_TYPE_NONE */
+		    pj_uint8_t silc;
+		    
+		    silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+			    pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+		    buffer.AppendFill(silc, samples_req - samples_ready);
+
+		    samples_ready = samples_req;
+		    frame->samples_cnt = 0;
+		    frame->subframe_cnt = 0;
+		}
+	    }
+	}
+	break;
+	
+    default:
+	break;
+    }
+
+    /* Set the buffer */
+    buf->SetPayloadPtr(buffer);
+}
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * C compatible declaration of VAS factory.
+ */
+PJ_BEGIN_DECL
+PJ_DECL(pjmedia_aud_dev_factory*)pjmedia_symb_vas_factory(pj_pool_factory *pf);
+PJ_END_DECL
+
+/*
+ * Init VAS audio driver.
+ */
+PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_symb_vas_factory(pj_pool_factory *pf)
+{
+    struct vas_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "VAS", 1000, 1000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct vas_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+    struct vas_factory *af = (struct vas_factory*)f;
+    CVoIPUtilityFactory *vas_factory;
+    RArray<TVoIPCodecFormat> uplink_formats, dnlink_formats;
+    unsigned ext_fmt_cnt = 0;
+    TInt err;
+
+    pj_ansi_strcpy(af->dev_info.name, "S60 VAS");
+    af->dev_info.default_samples_per_sec = 8000;
+    af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
+			//PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
+			PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
+			PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
+			PJMEDIA_AUD_DEV_CAP_VAD |
+			PJMEDIA_AUD_DEV_CAP_CNG;
+    af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE | 
+			  PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+    af->dev_info.input_count = 1;
+    af->dev_info.output_count = 1;
+    af->dev_info.ext_fmt_cnt = 0;
+
+    /* Enumerate supported formats */
+    err = CVoIPUtilityFactory::CreateFactory(vas_factory);
+    if (err != KErrNone)
+	goto on_error;
+
+    uplink_formats.Reset();
+    err = vas_factory->GetSupportedUplinkFormats(uplink_formats);
+    if (err != KErrNone)
+	goto on_error;
+
+    dnlink_formats.Reset();
+    err = vas_factory->GetSupportedDownlinkFormats(dnlink_formats);
+    if (err != KErrNone)
+	goto on_error;
+
+    for (TInt i = 0; i < dnlink_formats.Count(); i++) {
+	/* Format must be supported by both downlink & uplink. */
+	if (uplink_formats.Find(dnlink_formats[i]) == KErrNotFound)
+	    continue;
+	
+	switch (dnlink_formats[i]) {
+	case EAMR_NB:
+	    af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_AMR;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 7400;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_TRUE;
+	    break;
+
+	case EG729:
+	    af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_G729;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 8000;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
+	    break;
+
+	case EILBC:
+	    af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_ILBC;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 13333;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_TRUE;
+	    break;
+
+	case EG711:
+	    af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_PCMU;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 64000;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
+	    ++ext_fmt_cnt;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_PCMA;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 64000;
+	    af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
+	    break;
+	
+	default:
+	    continue;
+	}
+	
+	++ext_fmt_cnt;
+    }
+    
+    af->dev_info.ext_fmt_cnt = ext_fmt_cnt;
+
+    uplink_formats.Close();
+    dnlink_formats.Close();
+    
+    PJ_LOG(3, (THIS_FILE, "VAS initialized"));
+
+    return PJ_SUCCESS;
+    
+on_error:
+    return PJ_RETURN_OS_ERROR(err);
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+    struct vas_factory *af = (struct vas_factory*)f;
+    pj_pool_t *pool = af->pool;
+
+    af->pool = NULL;
+    pj_pool_release(pool);
+
+    PJ_LOG(3, (THIS_FILE, "VAS destroyed"));
+    
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return 1;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
+					unsigned index,
+					pjmedia_aud_dev_info *info)
+{
+    struct vas_factory *af = (struct vas_factory*)f;
+
+    PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+    pj_memcpy(info, &af->dev_info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+					 unsigned index,
+					 pjmedia_aud_param *param)
+{
+    struct vas_factory *af = (struct vas_factory*)f;
+
+    PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    param->rec_id = index;
+    param->play_id = index;
+    param->clock_rate = af->dev_info.default_samples_per_sec;
+    param->channel_count = 1;
+    param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
+    param->bits_per_sample = BITS_PER_SAMPLE;
+    param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
+    param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+					 const pjmedia_aud_param *param,
+					 pjmedia_aud_rec_cb rec_cb,
+					 pjmedia_aud_play_cb play_cb,
+					 void *user_data,
+					 pjmedia_aud_stream **p_aud_strm)
+{
+    struct vas_factory *af = (struct vas_factory*)f;
+    pj_pool_t *pool;
+    struct vas_stream *strm;
+
+    CPjAudioSetting vas_setting;
+    PjAudioCallback vas_rec_cb;
+    PjAudioCallback vas_play_cb;
+
+    /* Can only support 16bits per sample */
+    PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
+
+    /* Supported clock rates:
+     * - for non-PCM format: 8kHz  
+     * - for PCM format: 8kHz and 16kHz  
+     */
+    PJ_ASSERT_RETURN(param->clock_rate == 8000 ||
+		     (param->clock_rate == 16000 && 
+		      param->ext_fmt.id == PJMEDIA_FORMAT_L16),
+		     PJ_EINVAL);
+
+    /* Supported channels number:
+     * - for non-PCM format: mono
+     * - for PCM format: mono and stereo  
+     */
+    PJ_ASSERT_RETURN(param->channel_count == 1 || 
+		     (param->channel_count == 2 &&
+		      param->ext_fmt.id == PJMEDIA_FORMAT_L16),
+		     PJ_EINVAL);
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(af->pf, "vas-dev", 1000, 1000, NULL);
+    PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct vas_stream);
+    strm->pool = pool;
+    strm->param = *param;
+
+    if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
+	strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
+	
+    /* Set audio engine fourcc. */
+    switch(strm->param.ext_fmt.id) {
+    case PJMEDIA_FORMAT_L16:
+#ifdef USE_NATIVE_PCM	
+	vas_setting.format = EPCM16;
+#else
+	vas_setting.format = EG711;
+#endif
+	break;
+    case PJMEDIA_FORMAT_PCMU:
+    case PJMEDIA_FORMAT_PCMA:
+	vas_setting.format = EG711;
+	break;
+    case PJMEDIA_FORMAT_AMR:
+	vas_setting.format = EAMR_NB;
+	break;
+    case PJMEDIA_FORMAT_G729:
+	vas_setting.format = EG729;
+	break;
+    case PJMEDIA_FORMAT_ILBC:
+	vas_setting.format = EILBC;
+	break;
+    default:
+	vas_setting.format = ENULL;
+	break;
+    }
+
+    /* Set audio engine mode. */
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
+    {
+#ifdef USE_NATIVE_PCM	
+	vas_setting.mode = 0;
+#else
+	vas_setting.mode = CVoIPFormatIntfc::EG711uLaw;
+#endif
+    } 
+    else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
+    {
+	vas_setting.mode = strm->param.ext_fmt.bitrate;
+    } 
+    else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU)
+    {
+	vas_setting.mode = CVoIPFormatIntfc::EG711uLaw;
+    }
+    else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA)
+    {
+	vas_setting.mode = CVoIPFormatIntfc::EG711ALaw;
+    }
+    else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC)
+    {
+	if (strm->param.ext_fmt.bitrate == 15200)
+	    vas_setting.mode = CVoIPFormatIntfc::EiLBC20mSecFrame;
+	else
+	    vas_setting.mode = CVoIPFormatIntfc::EiLBC30mSecFrame;
+    } else {
+	vas_setting.mode = 0;
+    }
+
+    /* Disable VAD on L16, G711, and also G729 (G729's VAD potentially 
+     * causes noise?).
+     */
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
+	strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+	strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
+	strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729)
+    {
+	vas_setting.vad = EFalse;
+    } else {
+	vas_setting.vad = strm->param.ext_fmt.vad;
+    }
+    
+    /* Set other audio engine attributes. */
+    vas_setting.plc = strm->param.plc_enabled;
+    vas_setting.cng = vas_setting.vad;
+    vas_setting.loudspk = 
+		strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+
+    /* Set audio engine callbacks. */
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
+#ifdef USE_NATIVE_PCM
+	vas_play_cb = &PlayCbPcm2;
+	vas_rec_cb  = &RecCbPcm2;
+#else
+	vas_play_cb = &PlayCbPcm;
+	vas_rec_cb  = &RecCbPcm;
+#endif
+    } else {
+	vas_play_cb = &PlayCb;
+	vas_rec_cb  = &RecCb;
+    }
+
+    strm->rec_cb = rec_cb;
+    strm->play_cb = play_cb;
+    strm->user_data = user_data;
+    strm->resample_factor = strm->param.clock_rate / 8000;
+
+    /* play_buf size is samples per frame scaled in to 8kHz mono. */
+    strm->play_buf = (pj_int16_t*)pj_pool_zalloc(
+					pool, 
+					(strm->param.samples_per_frame / 
+					strm->resample_factor /
+					strm->param.channel_count) << 1);
+    strm->play_buf_len = 0;
+    strm->play_buf_start = 0;
+
+    /* rec_buf size is samples per frame scaled in to 8kHz mono. */
+    strm->rec_buf  = (pj_int16_t*)pj_pool_zalloc(
+					pool, 
+					(strm->param.samples_per_frame / 
+					strm->resample_factor /
+					strm->param.channel_count) << 1);
+    strm->rec_buf_len = 0;
+
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+	TBitStream *g729_bitstream = new TBitStream;
+	
+	PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
+	strm->strm_data = (void*)g729_bitstream;
+    }
+	
+    /* Init resampler when format is PCM and clock rate is not 8kHz */
+    if (strm->param.clock_rate != 8000 && 
+	strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
+    {
+	pj_status_t status;
+	
+	if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	    /* Create resample for recorder */
+	    status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1, 
+					      8000,
+					      strm->param.clock_rate,
+					      80,
+					      &strm->rec_resample);
+	    if (status != PJ_SUCCESS)
+		return status;
+	}
+    
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    /* Create resample for player */
+	    status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1, 
+					      strm->param.clock_rate,
+					      8000,
+					      80 * strm->resample_factor,
+					      &strm->play_resample);
+	    if (status != PJ_SUCCESS)
+		return status;
+	}
+    }
+
+    /* Create PCM buffer, when the clock rate is not 8kHz or not mono */
+    if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 &&
+	(strm->resample_factor > 1 || strm->param.channel_count != 1)) 
+    {
+	strm->pcm_buf = (pj_int16_t*)pj_pool_zalloc(pool, 
+					strm->param.samples_per_frame << 1);
+    }
+
+    
+    /* Create the audio engine. */
+    TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
+						   vas_rec_cb, vas_play_cb,
+						   strm, vas_setting));
+    if (err != KErrNone) {
+    	pj_pool_release(pool);
+	return PJ_RETURN_OS_ERROR(err);
+    }
+
+    /* Apply output volume setting if specified */
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+	stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, 
+		       &param->output_vol);
+    }
+
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_aud_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+				    pjmedia_aud_param *pi)
+{
+    struct vas_stream *strm = (struct vas_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    /* Update the output volume setting */
+    if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+		       &pi->output_vol) == PJ_SUCCESS)
+    {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+    }
+    
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  void *pval)
+{
+    struct vas_stream *strm = (struct vas_stream*)s;
+    pj_status_t status = PJ_ENOTSUP;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    switch (cap) {
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: 
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    *(pjmedia_aud_dev_route*)pval = strm->param.output_route;
+	    status = PJ_SUCCESS;
+	}
+	break;
+    
+    /* There is a case that GetMaxGain() stucks, e.g: in N95. */ 
+    /*
+    case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    TInt max_gain = strm->engine->GetMaxGain();
+	    TInt gain = strm->engine->GetGain();
+	    
+	    if (max_gain > 0 && gain >= 0) {
+		*(unsigned*)pval = gain * 100 / max_gain; 
+		status = PJ_SUCCESS;
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	}
+	break;
+    */
+
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    TInt max_vol = strm->engine->GetMaxVolume();
+	    TInt vol = strm->engine->GetVolume();
+	    
+	    if (max_vol > 0 && vol >= 0) {
+		*(unsigned*)pval = vol * 100 / max_vol; 
+		status = PJ_SUCCESS;
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	}
+	break;
+    default:
+	break;
+    }
+    
+    return status;
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+				  pjmedia_aud_dev_cap cap,
+				  const void *pval)
+{
+    struct vas_stream *strm = (struct vas_stream*)s;
+    pj_status_t status = PJ_ENOTSUP;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    switch (cap) {
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: 
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
+	    TInt err;
+
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    switch (r) {
+	    case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
+	    case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
+		err = strm->engine->ActivateSpeaker(EFalse);
+		status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+		break;
+	    case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
+		err = strm->engine->ActivateSpeaker(ETrue);
+		status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+		break;
+	    default:
+		status = PJ_EINVAL;
+		break;
+	    }
+	    if (status == PJ_SUCCESS)
+		strm->param.output_route = r; 
+	}
+	break;
+
+    /* There is a case that GetMaxGain() stucks, e.g: in N95. */ 
+    /*
+    case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    TInt max_gain = strm->engine->GetMaxGain();
+	    if (max_gain > 0) {
+		TInt gain, err;
+		
+		gain = *(unsigned*)pval * max_gain / 100;
+		err = strm->engine->SetGain(gain);
+		status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	    if (status == PJ_SUCCESS)
+		strm->param.input_vol = *(unsigned*)pval;
+	}
+	break;
+    */
+
+    case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+	if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+	    PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+	    
+	    TInt max_vol = strm->engine->GetMaxVolume();
+	    if (max_vol > 0) {
+		TInt vol, err;
+		
+		vol = *(unsigned*)pval * max_vol / 100;
+		err = strm->engine->SetVolume(vol);
+		status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+	    } else {
+		status = PJMEDIA_EAUD_NOTREADY;
+	    }
+	    if (status == PJ_SUCCESS)
+		strm->param.output_vol = *(unsigned*)pval;
+	}
+	break;
+    default:
+	break;
+    }
+    
+    return status;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+    struct vas_stream *stream = (struct vas_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    if (stream->engine) {
+	TInt err = stream->engine->Start();
+    	if (err != KErrNone)
+    	    return PJ_RETURN_OS_ERROR(err);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+    struct vas_stream *stream = (struct vas_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    if (stream->engine) {
+    	stream->engine->Stop();
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+    struct vas_stream *stream = (struct vas_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+    stream_stop(strm);
+
+    delete stream->engine;
+    stream->engine = NULL;
+
+    if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+	TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
+	stream->strm_data = NULL;
+	delete g729_bitstream;
+    }
+
+    pj_pool_t *pool;
+    pool = stream->pool;
+    if (pool) {
+    	stream->pool = NULL;
+    	pj_pool_release(pool);
+    }
+
+    return PJ_SUCCESS;
+}
+
+#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+
diff --git a/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp b/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
index e3e209a..f5a7931 100644
--- a/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
+++ b/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
@@ -62,7 +62,7 @@
 #endif

 

 #if SND_HAS_VAS

-//	LIBRARY		

+	LIBRARY		VoIPAudioIntfc.lib		

 #endif

 

 #if SND_HAS_MDA

diff --git a/pjsip-apps/src/symsndtest/app_main.cpp b/pjsip-apps/src/symsndtest/app_main.cpp
index 056cde1..e58be08 100644
--- a/pjsip-apps/src/symsndtest/app_main.cpp
+++ b/pjsip-apps/src/symsndtest/app_main.cpp
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
+#include <pjmedia-audiodev/audiodev.h>
 #include <pjmedia/delaybuf.h>
-#include <pjmedia/sound.h>
+#include <pj/assert.h>
 #include <pj/errno.h>
 #include <pj/os.h>
 #include <pj/log.h>
@@ -36,7 +37,7 @@
 extern CConsoleBase* console;
 
 static pj_caching_pool cp;
-static pjmedia_snd_stream *strm;
+static pjmedia_aud_stream *strm;
 static unsigned rec_cnt, play_cnt;
 static pj_time_val t_start;
 
@@ -85,7 +86,7 @@
     pj_caching_pool_init(&cp, NULL, 0);
 
     /* Init sound subsystem */
-    status = pjmedia_snd_init(&cp.factory);
+    status = pjmedia_aud_subsys_init(&cp.factory);
     if (status != PJ_SUCCESS) {
     	app_perror("pjmedia_snd_init()", status);
         pj_caching_pool_destroy(&cp);
@@ -93,15 +94,17 @@
     	return status;
     }
 
-    count = pjmedia_snd_get_dev_count();
+    count = pjmedia_aud_dev_count();
     PJ_LOG(3,(THIS_FILE, "Device count: %d", count));
     for (i=0; i<count; ++i) {
-    	const pjmedia_snd_dev_info *info;
+    	pjmedia_aud_dev_info info;
+    	pj_status_t status;
 
-    	info = pjmedia_snd_get_dev_info(i);
+    	status = pjmedia_aud_dev_get_info(i, &info);
+    	pj_assert(status == PJ_SUCCESS);
     	PJ_LOG(3, (THIS_FILE, "%d: %s %d/%d %dHz",
-    		   i, info->name, info->input_count, info->output_count,
-    		   info->default_samples_per_sec));
+    		   i, info.name, info.input_count, info.output_count,
+    		   info.default_samples_per_sec));
     }
 
     /* Create pool */
@@ -130,19 +133,15 @@
 
 /* Sound capture callback */
 static pj_status_t rec_cb(void *user_data,
-			  pj_uint32_t timestamp,
-			  void *input,
-			  unsigned size)
+			  pjmedia_frame *frame)
 {
     PJ_UNUSED_ARG(user_data);
-    PJ_UNUSED_ARG(timestamp);
-    PJ_UNUSED_ARG(size);
 
-    pjmedia_delay_buf_put(delaybuf, (pj_int16_t*)input);
+    pjmedia_delay_buf_put(delaybuf, (pj_int16_t*)frame->buf);
 
-    if (size != SAMPLES_PER_FRAME*2) {
+    if (frame->size != SAMPLES_PER_FRAME*2) {
 		PJ_LOG(3, (THIS_FILE, "Size captured = %u",
-	 		   size));
+	 		   frame->size));
     }
 
     ++rec_cnt;
@@ -151,15 +150,13 @@
 
 /* Play cb */
 static pj_status_t play_cb(void *user_data,
-			   pj_uint32_t timestamp,
-			   void *output,
-			   unsigned size)
+			   pjmedia_frame *frame)
 {
     PJ_UNUSED_ARG(user_data);
-    PJ_UNUSED_ARG(timestamp);
-    PJ_UNUSED_ARG(size);
 
-    pjmedia_delay_buf_get(delaybuf, (pj_int16_t*)output);
+    pjmedia_delay_buf_get(delaybuf, (pj_int16_t*)frame->buf);
+    frame->size = SAMPLES_PER_FRAME*2;
+    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
 
     ++play_cnt;
     return PJ_SUCCESS;
@@ -168,6 +165,7 @@
 /* Start sound */
 static pj_status_t snd_start(unsigned flag)
 {
+    pjmedia_aud_param param;
     pj_status_t status;
 
     if (strm != NULL) {
@@ -175,19 +173,13 @@
     	return PJ_EINVALIDOP;
     }
 
-    if (flag==PJMEDIA_DIR_CAPTURE_PLAYBACK)
-    	status = pjmedia_snd_open(-1, -1, CLOCK_RATE, CHANNEL_COUNT,
-    				  SAMPLES_PER_FRAME, BITS_PER_SAMPLE,
-    				  &rec_cb, &play_cb, NULL, &strm);
-    else if (flag==PJMEDIA_DIR_CAPTURE)
-    	status = pjmedia_snd_open_rec(-1, CLOCK_RATE, CHANNEL_COUNT,
-    				      SAMPLES_PER_FRAME, BITS_PER_SAMPLE,
-    				      &rec_cb, NULL, &strm);
-    else
-    	status = pjmedia_snd_open_player(-1, CLOCK_RATE, CHANNEL_COUNT,
-    					 SAMPLES_PER_FRAME, BITS_PER_SAMPLE,
-    					 &play_cb, NULL, &strm);
+    pjmedia_aud_dev_default_param(0, &param);
+    param.channel_count = CHANNEL_COUNT;
+    param.clock_rate = CLOCK_RATE;
+    param.samples_per_frame = SAMPLES_PER_FRAME;
+    param.dir = (pjmedia_dir) flag;
 
+    status = pjmedia_aud_stream_create(&param, &rec_cb, &play_cb, NULL, &strm);
     if (status != PJ_SUCCESS) {
     	app_perror("snd open", status);
     	return status;
@@ -198,10 +190,10 @@
 
     pjmedia_delay_buf_reset(delaybuf);
 
-    status = pjmedia_snd_stream_start(strm);
+    status = pjmedia_aud_stream_start(strm);
     if (status != PJ_SUCCESS) {
     	app_perror("snd start", status);
-    	pjmedia_snd_stream_close(strm);
+    	pjmedia_aud_stream_destroy(strm);
     	strm = NULL;
     	return status;
     }
@@ -220,11 +212,11 @@
     	return PJ_EINVALIDOP;
     }
 
-    status = pjmedia_snd_stream_stop(strm);
+    status = pjmedia_aud_stream_stop(strm);
     if (status != PJ_SUCCESS) {
     	app_perror("snd failed to stop", status);
     }
-    status = pjmedia_snd_stream_close(strm);
+    status = pjmedia_aud_stream_destroy(strm);
     strm = NULL;
 
     pj_gettimeofday(&now);
@@ -243,7 +235,7 @@
     if (strm)
     	snd_stop();
 
-    pjmedia_snd_deinit();
+    pjmedia_aud_subsys_shutdown();
     pjmedia_delay_buf_destroy(delaybuf);
     pj_pool_release(pool);
     pj_caching_pool_destroy(&cp);