From a6e117f38a70b6cf03579cdeceed7d5af2a0e9de Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Thu, 24 Jul 2008 15:30:44 +0000 Subject: Ticket #577: Initial source of sound device wrapper with APS git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2174 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/include/pjmedia/symbian_sound_aps.h | 52 ++ pjmedia/src/pjmedia/symbian_sound_aps.cpp | 824 ++++++++++++++++++++++++++++ 2 files changed, 876 insertions(+) create mode 100644 pjmedia/include/pjmedia/symbian_sound_aps.h create mode 100644 pjmedia/src/pjmedia/symbian_sound_aps.cpp (limited to 'pjmedia') diff --git a/pjmedia/include/pjmedia/symbian_sound_aps.h b/pjmedia/include/pjmedia/symbian_sound_aps.h new file mode 100644 index 00000000..c42c9580 --- /dev/null +++ b/pjmedia/include/pjmedia/symbian_sound_aps.h @@ -0,0 +1,52 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_SYMBIAN_SOUND_APS_H__ +#define __PJMEDIA_SYMBIAN_SOUND_APS_H__ + + +/** + * @file symbian_sound_aps.h + * @brief Sound device wrapper using Audio Proxy Server on + * Symbian S60 3rd edition. + */ +#include + +PJ_BEGIN_DECL + +/** + * Activate/deactivate loudspeaker, when loudspeaker is inactive, audio + * will be routed to earpiece. + * + * @param stream The sound device stream, the stream should be started + * before calling this function. This param can be NULL + * to set the behaviour of next opened stream. + * @param active Specify PJ_TRUE to activate loudspeaker, and PJ_FALSE + * otherwise. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_snd_aps_activate_loudspeaker( + pjmedia_snd_stream *stream, + pj_bool_t active); + + +PJ_END_DECL + + +#endif /* __PJMEDIA_SYMBIAN_SOUND_APS_H__ */ diff --git a/pjmedia/src/pjmedia/symbian_sound_aps.cpp b/pjmedia/src/pjmedia/symbian_sound_aps.cpp new file mode 100644 index 00000000..9b54c3d3 --- /dev/null +++ b/pjmedia/src/pjmedia/symbian_sound_aps.cpp @@ -0,0 +1,824 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 + +////////////////////////////////////////////////////////////////////////////// +// + +#define THIS_FILE "symbian_sound_aps.cpp" + +#define BYTES_PER_SAMPLE 2 +#define POOL_NAME "SymbianSoundAps" +#define POOL_SIZE 512 +#define POOL_INC 512 + +#if 1 +# define TRACE_(st) PJ_LOG(3, st) +#else +# define TRACE_(st) +#endif + +static pjmedia_snd_dev_info symbian_snd_dev_info = +{ + "Symbian Sound Device (APS)", + 1, + 1, + 8000 +}; + +/* App UID to open global APS queues to communicate with the APS server. */ +extern TPtrC APP_UID; + +/* Default setting for loudspeaker */ +static pj_bool_t act_loudspeaker = PJ_FALSE; + +/* Forward declaration of CPjAudioEngine */ +class CPjAudioEngine; + +/* + * PJMEDIA Sound Stream instance + */ +struct pjmedia_snd_stream +{ + // Pool + pj_pool_t *pool; + + // Common settings. + pjmedia_dir dir; + unsigned clock_rate; + unsigned channel_count; + unsigned samples_per_frame; + + // Audio engine + CPjAudioEngine *engine; +}; + +static pj_pool_factory *snd_pool_factory; + + +/* + * Utility: print sound device error + */ +static void snd_perror(const char *title, TInt rc) +{ + PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc)); +} + +////////////////////////////////////////////////////////////////////////////// +// + +/** + * Abstract class for handler of callbacks from APS client. + */ +class MQueueHandlerObserver +{ +public: + virtual void InputStreamInitialized(const TInt aStatus) = 0; + virtual void OutputStreamInitialized(const TInt aStatus) = 0; + virtual void NotifyError(const TInt aError) = 0; + + virtual void RecCb(TAPSCommBuffer &buffer) = 0; + virtual void PlayCb(TAPSCommBuffer &buffer) = 0; +}; + +/** + * Handler for communication and data queue. + */ +class CQueueHandler : public CActive +{ +public: + // Types of queue handler + enum TQueueHandlerType { + ERecordCommQueue, + EPlayCommQueue, + ERecordQueue, + EPlayQueue + }; + + // The order corresponds to the APS Server state, do not change! + enum TState { + EAPSPlayerInitialize = 1, + EAPSRecorderInitialize = 2, + EAPSPlayData = 3, + EAPSRecordData = 4, + EAPSPlayerInitComplete = 5, + EAPSRecorderInitComplete = 6 + }; + + static CQueueHandler* NewL(MQueueHandlerObserver* aObserver, + RMsgQueue* aQ, + TQueueHandlerType aType) + { + CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aType); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + + // Destructor + ~CQueueHandler() { Cancel(); } + + // Start listening queue event + void Start() { + iQ->NotifyDataAvailable(iStatus); + SetActive(); + } + +private: + // Constructor + CQueueHandler(MQueueHandlerObserver* aObserver, + RMsgQueue* aQ, + TQueueHandlerType aType) + : CActive(CActive::EPriorityHigh), + iQ(aQ), iObserver(aObserver), iType(aType) + { + CActiveScheduler::Add(this); + + // use lower priority for comm queues + if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue)) + SetPriority(CActive::EPriorityStandard); + } + + // Second phase constructor + void ConstructL() {} + + // Inherited from CActive + void DoCancel() { iQ->CancelDataAvailable(); } + + void RunL() { + if (iStatus != KErrNone) { + iObserver->NotifyError(iStatus.Int()); + return; + } + + TAPSCommBuffer buffer; + TInt ret = iQ->Receive(buffer); + + if (ret != KErrNone) { + iObserver->NotifyError(ret); + return; + } + + switch (iType) { + case ERecordQueue: + if (buffer.iCommand == EAPSRecordData) { + iObserver->RecCb(buffer); + } + break; + + // Callbacks from the APS main thread + case EPlayCommQueue: + switch (buffer.iCommand) { + case EAPSPlayData: + if (buffer.iStatus == KErrUnderflow) { + iObserver->PlayCb(buffer); + } + break; + case EAPSPlayerInitialize: + iObserver->NotifyError(buffer.iStatus); + break; + case EAPSPlayerInitComplete: + iObserver->OutputStreamInitialized(buffer.iStatus); + break; + case EAPSRecorderInitComplete: + iObserver->InputStreamInitialized(buffer.iStatus); + break; + default: + iObserver->NotifyError(buffer.iStatus); + break; + } + break; + + // Callbacks from the APS recorder thread + case ERecordCommQueue: + switch (buffer.iCommand) { + // The APS recorder thread will only report errors + // through this handler. All other callbacks will be + // sent from the APS main thread through EPlayCommQueue + case EAPSRecorderInitialize: + if (buffer.iStatus == KErrNone) { + iObserver->InputStreamInitialized(buffer.iStatus); + break; + } + case EAPSRecordData: + iObserver->NotifyError(buffer.iStatus); + break; + default: + break; + } + break; + + default: + break; + } + + // issue next request + iQ->NotifyDataAvailable(iStatus); + SetActive(); + } + + // Data + RMsgQueue *iQ; // (not owned) + MQueueHandlerObserver *iObserver; // (not owned) + TQueueHandlerType iType; +}; + + +/* + * Implementation: Symbian Input & Output Stream. + */ +class CPjAudioEngine : public CBase, MQueueHandlerObserver +{ +public: + enum State + { + STATE_NULL, + STATE_READY, + STATE_STREAMING + }; + + ~CPjAudioEngine(); + + static CPjAudioEngine *NewL(pjmedia_snd_stream *parent_strm, + pjmedia_dir dir, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data); + + TInt StartL(); + void Stop(); + + TInt ActivateSpeaker(TBool active); + +private: + CPjAudioEngine(pjmedia_snd_stream *parent_strm, + pjmedia_dir dir, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data); + void ConstructL(); + + TInt InitPlayL(); + TInt InitRecL(); + TInt StartStreamL(); + + // Inherited from MQueueHandlerObserver + virtual void InputStreamInitialized(const TInt aStatus); + virtual void OutputStreamInitialized(const TInt aStatus); + virtual void NotifyError(const TInt aError); + + virtual void RecCb(TAPSCommBuffer &buffer); + virtual void PlayCb(TAPSCommBuffer &buffer); + + State state_; + pjmedia_snd_stream *parentStrm_; + pjmedia_dir dir_; + pjmedia_snd_rec_cb recCb_; + pjmedia_snd_play_cb playCb_; + void *userData_; + pj_uint32_t TsPlay_; + pj_uint32_t TsRec_; + + RAPSSession iSession; + TAPSInitSettings iSettings; + RMsgQueue iReadQ; + RMsgQueue iReadCommQ; + RMsgQueue iWriteQ; + RMsgQueue iWriteCommQ; + + CQueueHandler *iPlayCommHandler; + CQueueHandler *iRecCommHandler; + CQueueHandler *iRecHandler; +}; + + +CPjAudioEngine* CPjAudioEngine::NewL(pjmedia_snd_stream *parent_strm, + pjmedia_dir dir, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data) +{ + CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm, dir, + rec_cb, play_cb, + user_data); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +CPjAudioEngine::CPjAudioEngine(pjmedia_snd_stream *parent_strm, + pjmedia_dir dir, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data) + : state_(STATE_NULL), + parentStrm_(parent_strm), + dir_(dir), + recCb_(rec_cb), + playCb_(play_cb), + userData_(user_data), + iPlayCommHandler(0), + iRecCommHandler(0), + iRecHandler(0) +{ +} + +CPjAudioEngine::~CPjAudioEngine() +{ + Stop(); + + delete iPlayCommHandler; + iPlayCommHandler = NULL; + delete iRecCommHandler; + iRecCommHandler = NULL; + + iSession.Close(); + + if (state_ == STATE_READY) { + if (dir_ != PJMEDIA_DIR_PLAYBACK) { + iReadQ.Close(); + iReadCommQ.Close(); + } + iWriteQ.Close(); + iWriteCommQ.Close(); + } +} + +TInt CPjAudioEngine::InitPlayL() +{ + if (state_ == STATE_STREAMING || state_ == STATE_READY) + return 0; + + TInt err = iSession.InitializePlayer(iSettings); + if (err != KErrNone) { + snd_perror("Failed to initialize player", err); + return err; + } + + // Open message queues for the output stream + TBuf<128> buf2 = iSettings.iGlobal; + buf2.Append(_L("PlayQueue")); + TBuf<128> buf3 = iSettings.iGlobal; + buf3.Append(_L("PlayCommQueue")); + + while (iWriteQ.OpenGlobal(buf2)) + User::After(10); + while (iWriteCommQ.OpenGlobal(buf3)) + User::After(10); + + // Construct message queue handler + iPlayCommHandler = CQueueHandler::NewL(this, + &iWriteCommQ, + CQueueHandler::EPlayCommQueue); + + // Start observing APS callbacks on output stream message queue + iPlayCommHandler->Start(); + + return 0; +} + +TInt CPjAudioEngine::InitRecL() +{ + if (state_ == STATE_STREAMING || state_ == STATE_READY) + return 0; + + // Initialize input stream device + TInt err = iSession.InitializeRecorder(iSettings); + if (err != KErrNone) { + snd_perror("Failed to initialize recorder", err); + return err; + } + + TBuf<128> buf1 = iSettings.iGlobal; + buf1.Append(_L("RecordQueue")); + TBuf<128> buf4 = iSettings.iGlobal; + buf4.Append(_L("RecordCommQueue")); + + // Must wait for APS thread to finish creating message queues + // before we can open and use them. + while (iReadQ.OpenGlobal(buf1)) + User::After(10); + while (iReadCommQ.OpenGlobal(buf4)) + User::After(10); + + // Construct message queue handlers + iRecCommHandler = CQueueHandler::NewL(this, + &iReadCommQ, + CQueueHandler::ERecordCommQueue); + + // Start observing APS callbacks from on input stream message queue + iRecCommHandler->Start(); + + return 0; +} + +TInt CPjAudioEngine::StartL() +{ + TInt err = iSession.Connect(); + if (err != KErrNone && err != KErrAlreadyExists) + return err; + + if (state_ == STATE_READY) + return StartStreamL(); + + // Even if only capturer are opened, playback thread of APS Server need + // to be run(?). Since some messages will be delivered via play comm queue. + return InitPlayL(); +} + +void CPjAudioEngine::Stop() +{ + iSession.Stop(); + + delete iRecHandler; + iRecHandler = NULL; + + state_ = STATE_READY; +} + +void CPjAudioEngine::ConstructL() +{ + iSettings.iFourCC = TFourCC(KMCPFourCCIdG711); + iSettings.iGlobal = APP_UID; + iSettings.iPriority = TMdaPriority(100); + iSettings.iPreference = TMdaPriorityPreference(0x05210001); + iSettings.iSettings.iChannels = EMMFMono; + iSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz; + iSettings.iSettings.iVolume = 0; +} + +TInt CPjAudioEngine::StartStreamL() +{ + if (state_ == STATE_STREAMING) + return 0; + + iSession.SetCng(EFalse); + iSession.SetVadMode(EFalse); + iSession.SetPlc(EFalse); + iSession.SetEncoderMode(EALawOr20ms); + iSession.SetDecoderMode(EALawOr20ms); + iSession.ActivateLoudspeaker(act_loudspeaker); + + // Not only playback + if (dir_ != PJMEDIA_DIR_PLAYBACK) { + iRecHandler = CQueueHandler::NewL(this, &iReadQ, + CQueueHandler::ERecordQueue); + iRecHandler->Start(); + iSession.Read(); + } + + // Not only capture + if (dir_ != PJMEDIA_DIR_CAPTURE) { + iSession.Write(); + } + + state_ = STATE_STREAMING; + return 0; +} + +// Inherited from MQueueHandlerObserver +void CPjAudioEngine::InputStreamInitialized(const TInt aStatus) +{ + TRACE_((THIS_FILE, "InputStreamInitialized %d", aStatus)); + + state_ = STATE_READY; + if (aStatus == KErrNone) { + StartStreamL(); + } +} + +void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus) +{ + TRACE_((THIS_FILE, "OutputStreamInitialized %d", aStatus)); + + if (aStatus == KErrNone) { + if (dir_ == PJMEDIA_DIR_PLAYBACK) { + state_ = STATE_READY; + // Only playback, start directly + StartStreamL(); + } else + InitRecL(); + } +} + +void CPjAudioEngine::NotifyError(const TInt aError) +{ + snd_perror("Error from CQueueHandler", aError); +} + +void CPjAudioEngine::RecCb(TAPSCommBuffer &buffer) +{ + pj_int16_t buf[160]; + pj_assert(buffer.iBuffer[0] == 1 && buffer.iBuffer[1] == 0); + + for (int i=0; i<160; ++i) + buf[i] = pjmedia_alaw2linear(buffer.iBuffer[i+2]); + + recCb_(userData_, 0, buf, sizeof(buf)); +} + +void CPjAudioEngine::PlayCb(TAPSCommBuffer &buffer) +{ + pj_int16_t buf[160]; + + playCb_(userData_, 0, buf, sizeof(buf)); + + buffer.iCommand = CQueueHandler::EAPSPlayData; + buffer.iStatus = 0; + buffer.iBuffer.Zero(); + buffer.iBuffer.Append(1); + buffer.iBuffer.Append(0); + for (int i=0; i<160; ++i) + buffer.iBuffer.Append(pjmedia_linear2alaw(buf[i])); + + iWriteQ.Send(buffer); +} + + +TInt CPjAudioEngine::ActivateSpeaker(TBool active) +{ + if (state_ == STATE_READY || state_ == STATE_STREAMING) { + iSession.ActivateLoudspeaker(active); + return KErrNone; + } + return KErrNotReady; +} +////////////////////////////////////////////////////////////////////////////// +// + + +/* + * Initialize sound subsystem. + */ +PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) +{ + snd_pool_factory = factory; + return PJ_SUCCESS; +} + +/* + * Get device count. + */ +PJ_DEF(int) pjmedia_snd_get_dev_count(void) +{ + /* Always return 1 */ + return 1; +} + +/* + * Get device info. + */ +PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) +{ + /* Always return the default sound device */ + if (index == (unsigned)-1) + index = 0; + + PJ_ASSERT_RETURN(index==0, NULL); + return &symbian_snd_dev_info; +} + +static pj_status_t sound_open(pjmedia_dir dir, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm) +{ + pj_pool_t *pool; + pjmedia_snd_stream *strm; + + PJ_ASSERT_RETURN(p_snd_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(clock_rate == 8000 && channel_count == 1 && + bits_per_sample == 16, PJ_ENOTSUP); + PJ_ASSERT_RETURN((dir == PJMEDIA_DIR_CAPTURE_PLAYBACK && rec_cb && play_cb) + || (dir == PJMEDIA_DIR_CAPTURE && rec_cb && !play_cb) + || (dir == PJMEDIA_DIR_PLAYBACK && !rec_cb && play_cb), + PJ_EINVAL); + + pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, + NULL); + if (!pool) + return PJ_ENOMEM; + + strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, + sizeof(pjmedia_snd_stream)); + strm->dir = dir; + strm->pool = pool; + strm->clock_rate = clock_rate; + strm->channel_count = channel_count; + strm->samples_per_frame = samples_per_frame; + + // Create the audio engine. + TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm, strm->dir, + rec_cb, play_cb, + user_data)); + if (err != KErrNone) { + pj_pool_release(pool); + return PJ_RETURN_OS_ERROR(err); + } + + // Done. + *p_snd_strm = strm; + return PJ_SUCCESS; +} + + + +/* + * Open sound recorder stream. + */ +PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_rec_cb rec_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm) +{ + if (index < 0) index = 0; + PJ_ASSERT_RETURN(index == 0, PJ_EINVAL); + + return sound_open(PJMEDIA_DIR_CAPTURE, clock_rate, channel_count, + samples_per_frame, bits_per_sample, rec_cb, NULL, + user_data, p_snd_strm); +} + +PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_play_cb play_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm ) +{ + if (index < 0) index = 0; + PJ_ASSERT_RETURN(index == 0, PJ_EINVAL); + + return sound_open(PJMEDIA_DIR_PLAYBACK, clock_rate, channel_count, + samples_per_frame, bits_per_sample, NULL, play_cb, + user_data, p_snd_strm); +} + +PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, + int play_id, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm) +{ + if (rec_id < 0) rec_id = 0; + if (play_id < 0) play_id = 0; + PJ_ASSERT_RETURN(play_id == 0 && rec_id == 0, PJ_EINVAL); + + return sound_open(PJMEDIA_DIR_CAPTURE_PLAYBACK, clock_rate, channel_count, + samples_per_frame, bits_per_sample, rec_cb, play_cb, + user_data, p_snd_strm); +} + +/* + * Get stream info. + */ +PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, + pjmedia_snd_stream_info *pi) +{ + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_bzero(pi, sizeof(*pi)); + pi->dir = strm->dir; + pi->play_id = 0; + pi->rec_id = 0; + pi->clock_rate = strm->clock_rate; + pi->channel_count = strm->channel_count; + pi->samples_per_frame = strm->samples_per_frame; + pi->bits_per_sample = BYTES_PER_SAMPLE * 8; + pi->rec_latency = 0; + pi->play_latency = 0; + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) +{ + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + if (stream->engine) { + TInt err = stream->engine->StartL(); + if (err != KErrNone) + return PJ_RETURN_OS_ERROR(err); + } + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) +{ + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + if (stream->engine) { + stream->engine->Stop(); + } + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) +{ + pj_pool_t *pool; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + if (stream->engine) { + delete stream->engine; + stream->engine = NULL; + } + + pool = stream->pool; + if (pool) { + stream->pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) +{ + /* Nothing to do */ + return PJ_SUCCESS; +} + + +/* + * Set sound latency. + */ +PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, + unsigned output_latency) +{ + /* Nothing to do */ + PJ_UNUSED_ARG(input_latency); + PJ_UNUSED_ARG(output_latency); + return PJ_SUCCESS; +} + + +/* + * Activate/deactivate loudspeaker. + */ +PJ_DEF(pj_status_t) pjmedia_snd_aps_activate_loudspeaker( + pjmedia_snd_stream *stream, + pj_bool_t active) +{ + if (stream == NULL) { + act_loudspeaker = active; + } else { + if (stream->engine == NULL) + return PJ_EINVAL; + + TInt err = stream->engine->ActivateSpeaker(active); + if (err != KErrNone) + return PJ_RETURN_OS_ERROR(err); + } + + return PJ_SUCCESS; +} -- cgit v1.2.3