From d687a5028cc392f9172ed78521482212257eb4c7 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Tue, 30 Jun 2009 13:37:26 +0000 Subject: [PATCH] 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 --- build.symbian/pjmedia_audiodev.mmp | 1 + build.symbian/symbian_ua.mmp | 2 +- build.symbian/symsndtest.mmp | 7 +- build.symbian/symsndtest.pkg | 4 +- pjlib/include/pj/config_site_sample.h | 28 + pjmedia/build/pjmedia_audiodev.vcproj | 760 +++++-- pjmedia/include/pjmedia-audiodev/config.h | 8 + pjmedia/src/pjmedia-audiodev/audiodev.c | 7 + pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp | 1888 +++++++++++++++++ .../symbian_ua_gui/group/symbian_ua_gui.mmp | 2 +- pjsip-apps/src/symsndtest/app_main.cpp | 70 +- 11 files changed, 2521 insertions(+), 256 deletions(-) create mode 100644 pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp diff --git a/build.symbian/pjmedia_audiodev.mmp b/build.symbian/pjmedia_audiodev.mmp index ccabc2fc8..da98b05ef 100644 --- a/build.symbian/pjmedia_audiodev.mmp +++ b/build.symbian/pjmedia_audiodev.mmp @@ -15,6 +15,7 @@ SOURCE audiodev.c 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 1473486df..8ef9f73f3 100644 --- a/build.symbian/symbian_ua.mmp +++ b/build.symbian/symbian_ua.mmp @@ -51,7 +51,7 @@ STATICLIBRARY libresample.lib #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 470de9d09..415807df3 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 @@ SYSTEMINCLUDE \epoc32\include\libc 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 4c505fa97..9dbb1180a 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 e64d97fc6..2357c4161 100644 --- a/pjlib/include/pj/config_site_sample.h +++ b/pjlib/include/pj/config_site_sample.h @@ -239,6 +239,34 @@ #endif +/* + * 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 a3b5593a2..6feb110f7 100644 --- a/pjmedia/build/pjmedia_audiodev.vcproj +++ b/pjmedia/build/pjmedia_audiodev.vcproj @@ -34,11 +34,11 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pjmedia/include/pjmedia-audiodev/config.h b/pjmedia/include/pjmedia-audiodev/config.h index b4603c15b..f2b429828 100644 --- a/pjmedia/include/pjmedia-audiodev/config.h +++ b/pjmedia/include/pjmedia-audiodev/config.h @@ -66,6 +66,14 @@ PJ_BEGIN_DECL #endif +/** + * 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 8025e04d5..93939aa66 100644 --- a/pjmedia/src/pjmedia-audiodev/audiodev.c +++ b/pjmedia/src/pjmedia-audiodev/audiodev.c @@ -74,6 +74,10 @@ pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf); 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 @@ PJ_DEF(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf) #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 000000000..d84e17a27 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS + +/* VAS headers */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AMR helper */ +#include + +/* 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 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, + ¶m->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 e3e209a28..f5a7931bc 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 @@ STATICLIBRARY libresample.lib #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 056cde104..e58be0814 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 #include -#include +#include #include #include #include @@ -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 @@ static pj_status_t app_init() 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 @@ static pj_status_t app_init() 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; iname, 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 @@ static pj_status_t app_init() /* 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 @@ static pj_status_t rec_cb(void *user_data, /* 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 @@ static pj_status_t play_cb(void *user_data, /* 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 @@ static pj_status_t snd_start(unsigned flag) 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, ¶m); + 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(¶m, &rec_cb, &play_cb, NULL, &strm); if (status != PJ_SUCCESS) { app_perror("snd open", status); return status; @@ -198,10 +190,10 @@ static pj_status_t snd_start(unsigned flag) 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 @@ static pj_status_t snd_stop() 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 @@ static void app_fini() 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);