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