/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * 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 #if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA /* * This file provides sound implementation for Symbian Audio Streaming * device. Application using this sound abstraction must link with: * - mediaclientaudiostream.lib, and * - mediaclientaudioinputstream.lib */ #include #include #include #define THIS_FILE "symb_mda_dev.c" #define BITS_PER_SAMPLE 16 #define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8) #if 1 # define TRACE_(st) PJ_LOG(3, st) #else # define TRACE_(st) #endif /* MDA factory */ struct mda_factory { pjmedia_aud_dev_factory base; pj_pool_t *pool; pj_pool_factory *pf; pjmedia_aud_dev_info dev_info; }; /* Forward declaration of internal engine. */ class CPjAudioInputEngine; class CPjAudioOutputEngine; /* MDA stream. */ struct mda_stream { // Base pjmedia_aud_stream base; /**< Base class. */ // Pool pj_pool_t *pool; /**< Memory pool. */ // Common settings. pjmedia_aud_param param; /**< Stream param. */ // Audio engine CPjAudioInputEngine *in_engine; /**< Record engine. */ CPjAudioOutputEngine *out_engine; /**< Playback engine. */ }; /* Prototypes */ static pj_status_t factory_init(pjmedia_aud_dev_factory *f); static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f); static pj_status_t factory_refresh(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, &factory_refresh }; static pjmedia_aud_stream_op stream_op = { &stream_get_param, &stream_get_cap, &stream_set_cap, &stream_start, &stream_stop, &stream_destroy }; /* * Convert clock rate to Symbian's TMdaAudioDataSettings capability. */ static TInt get_clock_rate_cap(unsigned clock_rate) { switch (clock_rate) { case 8000: return TMdaAudioDataSettings::ESampleRate8000Hz; case 11025: return TMdaAudioDataSettings::ESampleRate11025Hz; case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz; case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz; case 22050: return TMdaAudioDataSettings::ESampleRate22050Hz; case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz; case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz; case 44100: return TMdaAudioDataSettings::ESampleRate44100Hz; case 48000: return TMdaAudioDataSettings::ESampleRate48000Hz; case 64000: return TMdaAudioDataSettings::ESampleRate64000Hz; case 96000: return TMdaAudioDataSettings::ESampleRate96000Hz; default: return 0; } } /* * Convert number of channels into Symbian's TMdaAudioDataSettings capability. */ static TInt get_channel_cap(unsigned channel_count) { switch (channel_count) { case 1: return TMdaAudioDataSettings::EChannelsMono; case 2: return TMdaAudioDataSettings::EChannelsStereo; default: return 0; } } /* * 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)); } ////////////////////////////////////////////////////////////////////////////// // /* * Implementation: Symbian Input Stream. */ class CPjAudioInputEngine : public CBase, MMdaAudioInputStreamCallback { public: enum State { STATE_INACTIVE, STATE_ACTIVE, }; ~CPjAudioInputEngine(); static CPjAudioInputEngine *NewL(struct mda_stream *parent_strm, pjmedia_aud_rec_cb rec_cb, void *user_data); static CPjAudioInputEngine *NewLC(struct mda_stream *parent_strm, pjmedia_aud_rec_cb rec_cb, void *user_data); pj_status_t StartRecord(); void Stop(); pj_status_t SetGain(TInt gain) { if (iInputStream_) { iInputStream_->SetGain(gain); return PJ_SUCCESS; } else return PJ_EINVALIDOP; } TInt GetGain() { if (iInputStream_) { return iInputStream_->Gain(); } else return PJ_EINVALIDOP; } TInt GetMaxGain() { if (iInputStream_) { return iInputStream_->MaxGain(); } else return PJ_EINVALIDOP; } private: State state_; struct mda_stream *parentStrm_; pjmedia_aud_rec_cb recCb_; void *userData_; CMdaAudioInputStream *iInputStream_; HBufC8 *iStreamBuffer_; TPtr8 iFramePtr_; TInt lastError_; pj_uint32_t timeStamp_; CActiveSchedulerWait startAsw_; // cache variable // to avoid calculating frame length repeatedly TInt frameLen_; // sometimes recorded size != requested framesize, so let's // provide a buffer to make sure the rec callback returning // framesize as requested. TUint8 *frameRecBuf_; TInt frameRecBufLen_; CPjAudioInputEngine(struct mda_stream *parent_strm, pjmedia_aud_rec_cb rec_cb, void *user_data); void ConstructL(); TPtr8 & GetFrame(); public: virtual void MaiscOpenComplete(TInt aError); virtual void MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer); virtual void MaiscRecordComplete(TInt aError); }; CPjAudioInputEngine::CPjAudioInputEngine(struct mda_stream *parent_strm, pjmedia_aud_rec_cb rec_cb, void *user_data) : state_(STATE_INACTIVE), parentStrm_(parent_strm), recCb_(rec_cb), userData_(user_data), iInputStream_(NULL), iStreamBuffer_(NULL), iFramePtr_(0, 0), lastError_(KErrNone), timeStamp_(0), frameLen_(parent_strm->param.samples_per_frame * BYTES_PER_SAMPLE), frameRecBuf_(NULL), frameRecBufLen_(0) { } CPjAudioInputEngine::~CPjAudioInputEngine() { Stop(); delete iStreamBuffer_; iStreamBuffer_ = NULL; delete [] frameRecBuf_; frameRecBuf_ = NULL; frameRecBufLen_ = 0; } void CPjAudioInputEngine::ConstructL() { iStreamBuffer_ = HBufC8::NewL(frameLen_); CleanupStack::PushL(iStreamBuffer_); frameRecBuf_ = new TUint8[frameLen_*2]; CleanupStack::PushL(frameRecBuf_); } CPjAudioInputEngine *CPjAudioInputEngine::NewLC(struct mda_stream *parent, pjmedia_aud_rec_cb rec_cb, void *user_data) { CPjAudioInputEngine* self = new (ELeave) CPjAudioInputEngine(parent, rec_cb, user_data); CleanupStack::PushL(self); self->ConstructL(); return self; } CPjAudioInputEngine *CPjAudioInputEngine::NewL(struct mda_stream *parent, pjmedia_aud_rec_cb rec_cb, void *user_data) { CPjAudioInputEngine *self = NewLC(parent, rec_cb, user_data); CleanupStack::Pop(self->frameRecBuf_); CleanupStack::Pop(self->iStreamBuffer_); CleanupStack::Pop(self); return self; } pj_status_t CPjAudioInputEngine::StartRecord() { // Ignore command if recording is in progress. if (state_ == STATE_ACTIVE) return PJ_SUCCESS; // According to Nokia's AudioStream example, some 2nd Edition, FP2 devices // (such as Nokia 6630) require the stream to be reconstructed each time // before calling Open() - otherwise the callback never gets called. // For uniform behavior, lets just delete/re-create the stream for all // devices. // Destroy existing stream. if (iInputStream_) delete iInputStream_; iInputStream_ = NULL; // Create the stream. TRAPD(err, iInputStream_ = CMdaAudioInputStream::NewL(*this)); if (err != KErrNone) return PJ_RETURN_OS_ERROR(err); // Initialize settings. TMdaAudioDataSettings iStreamSettings; iStreamSettings.iChannels = get_channel_cap(parentStrm_->param.channel_count); iStreamSettings.iSampleRate = get_clock_rate_cap(parentStrm_->param.clock_rate); pj_assert(iStreamSettings.iChannels != 0 && iStreamSettings.iSampleRate != 0); PJ_LOG(4,(THIS_FILE, "Opening sound device for capture, " "clock rate=%d, channel count=%d..", parentStrm_->param.clock_rate, parentStrm_->param.channel_count)); // Open stream. lastError_ = KRequestPending; iInputStream_->Open(&iStreamSettings); #if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \ PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0 startAsw_.Start(); #endif // Success PJ_LOG(4,(THIS_FILE, "Sound capture started.")); return PJ_SUCCESS; } void CPjAudioInputEngine::Stop() { // If capture is in progress, stop it. if (iInputStream_ && state_ == STATE_ACTIVE) { lastError_ = KRequestPending; iInputStream_->Stop(); // Wait until it's actually stopped while (lastError_ == KRequestPending) pj_symbianos_poll(-1, 100); } if (iInputStream_) { delete iInputStream_; iInputStream_ = NULL; } if (startAsw_.IsStarted()) { startAsw_.AsyncStop(); } state_ = STATE_INACTIVE; } TPtr8 & CPjAudioInputEngine::GetFrame() { //iStreamBuffer_->Des().FillZ(frameLen_); iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_); return iFramePtr_; } void CPjAudioInputEngine::MaiscOpenComplete(TInt aError) { if (startAsw_.IsStarted()) { startAsw_.AsyncStop(); } lastError_ = aError; if (aError != KErrNone) { snd_perror("Error in MaiscOpenComplete()", aError); return; } /* Apply input volume setting if specified */ if (parentStrm_->param.flags & PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) { stream_set_cap(&parentStrm_->base, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, &parentStrm_->param.input_vol); } // set stream priority to normal and time sensitive iInputStream_->SetPriority(EPriorityNormal, EMdaPriorityPreferenceTime); // Read the first frame. TPtr8 & frm = GetFrame(); TRAPD(err2, iInputStream_->ReadL(frm)); if (err2) { PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()")); lastError_ = err2; return; } // input stream opened succesfully, set status to Active state_ = STATE_ACTIVE; } void CPjAudioInputEngine::MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer) { lastError_ = aError; if (aError != KErrNone) { snd_perror("Error in MaiscBufferCopied()", aError); return; } if (frameRecBufLen_ || aBuffer.Length() < frameLen_) { pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Length()); frameRecBufLen_ += aBuffer.Length(); } if (frameRecBufLen_) { while (frameRecBufLen_ >= frameLen_) { pjmedia_frame f; f.type = PJMEDIA_FRAME_TYPE_AUDIO; f.buf = frameRecBuf_; f.size = frameLen_; f.timestamp.u32.lo = timeStamp_; f.bit_info = 0; // Call the callback. recCb_(userData_, &f); // Increment timestamp. timeStamp_ += parentStrm_->param.samples_per_frame; frameRecBufLen_ -= frameLen_; pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_); } } else { pjmedia_frame f; f.type = PJMEDIA_FRAME_TYPE_AUDIO; f.buf = (void*)aBuffer.Ptr(); f.size = aBuffer.Length(); f.timestamp.u32.lo = timeStamp_; f.bit_info = 0; // Call the callback. recCb_(userData_, &f); // Increment timestamp. timeStamp_ += parentStrm_->param.samples_per_frame; } // Record next frame TPtr8 & frm = GetFrame(); TRAPD(err2, iInputStream_->ReadL(frm)); if (err2) { PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()")); } } void CPjAudioInputEngine::MaiscRecordComplete(TInt aError) { lastError_ = aError; state_ = STATE_INACTIVE; if (aError != KErrNone && aError != KErrCancel) { snd_perror("Error in MaiscRecordComplete()", aError); } } ////////////////////////////////////////////////////////////////////////////// // /* * Implementation: Symbian Output Stream. */ class CPjAudioOutputEngine : public CBase, MMdaAudioOutputStreamCallback { public: enum State { STATE_INACTIVE, STATE_ACTIVE, }; ~CPjAudioOutputEngine(); static CPjAudioOutputEngine *NewL(struct mda_stream *parent_strm, pjmedia_aud_play_cb play_cb, void *user_data); static CPjAudioOutputEngine *NewLC(struct mda_stream *parent_strm, pjmedia_aud_play_cb rec_cb, void *user_data); pj_status_t StartPlay(); void Stop(); pj_status_t SetVolume(TInt vol) { if (iOutputStream_) { iOutputStream_->SetVolume(vol); return PJ_SUCCESS; } else return PJ_EINVALIDOP; } TInt GetVolume() { if (iOutputStream_) { return iOutputStream_->Volume(); } else return PJ_EINVALIDOP; } TInt GetMaxVolume() { if (iOutputStream_) { return iOutputStream_->MaxVolume(); } else return PJ_EINVALIDOP; } private: State state_; struct mda_stream *parentStrm_; pjmedia_aud_play_cb playCb_; void *userData_; CMdaAudioOutputStream *iOutputStream_; TUint8 *frameBuf_; unsigned frameBufSize_; TPtrC8 frame_; TInt lastError_; unsigned timestamp_; CActiveSchedulerWait startAsw_; CPjAudioOutputEngine(struct mda_stream *parent_strm, pjmedia_aud_play_cb play_cb, void *user_data); void ConstructL(); virtual void MaoscOpenComplete(TInt aError); virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer); virtual void MaoscPlayComplete(TInt aError); }; CPjAudioOutputEngine::CPjAudioOutputEngine(struct mda_stream *parent_strm, pjmedia_aud_play_cb play_cb, void *user_data) : state_(STATE_INACTIVE), parentStrm_(parent_strm), playCb_(play_cb), userData_(user_data), iOutputStream_(NULL), frameBuf_(NULL), lastError_(KErrNone), timestamp_(0) { } void CPjAudioOutputEngine::ConstructL() { frameBufSize_ = parentStrm_->param.samples_per_frame * BYTES_PER_SAMPLE; frameBuf_ = new TUint8[frameBufSize_]; } CPjAudioOutputEngine::~CPjAudioOutputEngine() { Stop(); delete [] frameBuf_; } CPjAudioOutputEngine * CPjAudioOutputEngine::NewLC(struct mda_stream *parent_strm, pjmedia_aud_play_cb play_cb, void *user_data) { CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm, play_cb, user_data); CleanupStack::PushL(self); self->ConstructL(); return self; } CPjAudioOutputEngine * CPjAudioOutputEngine::NewL(struct mda_stream *parent_strm, pjmedia_aud_play_cb play_cb, void *user_data) { CPjAudioOutputEngine *self = NewLC(parent_strm, play_cb, user_data); CleanupStack::Pop(self); return self; } pj_status_t CPjAudioOutputEngine::StartPlay() { // Ignore command if playing is in progress. if (state_ == STATE_ACTIVE) return PJ_SUCCESS; // Destroy existing stream. if (iOutputStream_) delete iOutputStream_; iOutputStream_ = NULL; // Create the stream TRAPD(err, iOutputStream_ = CMdaAudioOutputStream::NewL(*this)); if (err != KErrNone) return PJ_RETURN_OS_ERROR(err); // Initialize settings. TMdaAudioDataSettings iStreamSettings; iStreamSettings.iChannels = get_channel_cap(parentStrm_->param.channel_count); iStreamSettings.iSampleRate = get_clock_rate_cap(parentStrm_->param.clock_rate); pj_assert(iStreamSettings.iChannels != 0 && iStreamSettings.iSampleRate != 0); PJ_LOG(4,(THIS_FILE, "Opening sound device for playback, " "clock rate=%d, channel count=%d..", parentStrm_->param.clock_rate, parentStrm_->param.channel_count)); // Open stream. lastError_ = KRequestPending; iOutputStream_->Open(&iStreamSettings); #if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \ PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0 startAsw_.Start(); #endif // Success PJ_LOG(4,(THIS_FILE, "Sound playback started")); return PJ_SUCCESS; } void CPjAudioOutputEngine::Stop() { // Stop stream if it's playing if (iOutputStream_ && state_ != STATE_INACTIVE) { lastError_ = KRequestPending; iOutputStream_->Stop(); // Wait until it's actually stopped while (lastError_ == KRequestPending) pj_symbianos_poll(-1, 100); } if (iOutputStream_) { delete iOutputStream_; iOutputStream_ = NULL; } if (startAsw_.IsStarted()) { startAsw_.AsyncStop(); } state_ = STATE_INACTIVE; } void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError) { if (startAsw_.IsStarted()) { startAsw_.AsyncStop(); } lastError_ = aError; if (aError==KErrNone) { // set stream properties, 16bit 8KHz mono TMdaAudioDataSettings iSettings; iSettings.iChannels = get_channel_cap(parentStrm_->param.channel_count); iSettings.iSampleRate = get_clock_rate_cap(parentStrm_->param.clock_rate); iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate, iSettings.iChannels); /* Apply output volume setting if specified */ if (parentStrm_->param.flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { stream_set_cap(&parentStrm_->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, &parentStrm_->param.output_vol); } else { // set volume to 1/2th of stream max volume iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2); } // set stream priority to normal and time sensitive iOutputStream_->SetPriority(EPriorityNormal, EMdaPriorityPreferenceTime); // Call callback to retrieve frame from upstream. pjmedia_frame f; pj_status_t status; f.type = PJMEDIA_FRAME_TYPE_AUDIO; f.buf = frameBuf_; f.size = frameBufSize_; f.timestamp.u32.lo = timestamp_; f.bit_info = 0; status = playCb_(this->userData_, &f); if (status != PJ_SUCCESS) { this->Stop(); return; } if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) pj_bzero(frameBuf_, frameBufSize_); // Increment timestamp. timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE); // issue WriteL() to write the first audio data block, // subsequent calls to WriteL() will be issued in // MMdaAudioOutputStreamCallback::MaoscBufferCopied() // until whole data buffer is written. frame_.Set(frameBuf_, frameBufSize_); iOutputStream_->WriteL(frame_); // output stream opened succesfully, set status to Active state_ = STATE_ACTIVE; } else { snd_perror("Error in MaoscOpenComplete()", aError); } } void CPjAudioOutputEngine::MaoscBufferCopied(TInt aError, const TDesC8& aBuffer) { PJ_UNUSED_ARG(aBuffer); if (aError==KErrNone) { // Buffer successfully written, feed another one. // Call callback to retrieve frame from upstream. pjmedia_frame f; pj_status_t status; f.type = PJMEDIA_FRAME_TYPE_AUDIO; f.buf = frameBuf_; f.size = frameBufSize_; f.timestamp.u32.lo = timestamp_; f.bit_info = 0; status = playCb_(this->userData_, &f); if (status != PJ_SUCCESS) { this->Stop(); return; } if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) pj_bzero(frameBuf_, frameBufSize_); // Increment timestamp. timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE); // Write to playback stream. frame_.Set(frameBuf_, frameBufSize_); iOutputStream_->WriteL(frame_); } else if (aError==KErrAbort) { // playing was aborted, due to call to CMdaAudioOutputStream::Stop() state_ = STATE_INACTIVE; } else { // error writing data to output lastError_ = aError; state_ = STATE_INACTIVE; snd_perror("Error in MaoscBufferCopied()", aError); } } void CPjAudioOutputEngine::MaoscPlayComplete(TInt aError) { lastError_ = aError; state_ = STATE_INACTIVE; if (aError != KErrNone && aError != KErrCancel) { snd_perror("Error in MaoscPlayComplete()", aError); } } /**************************************************************************** * Factory operations */ /* * C compatible declaration of MDA factory. */ PJ_BEGIN_DECL PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_symb_mda_factory(pj_pool_factory *pf); PJ_END_DECL /* * Init Symbian audio driver. */ pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf) { struct mda_factory *f; pj_pool_t *pool; pool = pj_pool_create(pf, "symb_aud", 1000, 1000, NULL); f = PJ_POOL_ZALLOC_T(pool, struct mda_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 mda_factory *af = (struct mda_factory*)f; pj_ansi_strcpy(af->dev_info.name, "Symbian Audio"); af->dev_info.default_samples_per_sec = 8000; af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING | PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; af->dev_info.input_count = 1; af->dev_info.output_count = 1; PJ_LOG(4, (THIS_FILE, "Symb Mda initialized")); return PJ_SUCCESS; } /* API: destroy factory */ static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f) { struct mda_factory *af = (struct mda_factory*)f; pj_pool_t *pool = af->pool; af->pool = NULL; pj_pool_release(pool); PJ_LOG(4, (THIS_FILE, "Symbian Mda destroyed")); return PJ_SUCCESS; } /* API: refresh the device list */ static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f) { PJ_UNUSED_ARG(f); return PJ_ENOTSUP; } /* 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 mda_factory *af = (struct mda_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 mda_factory *af = (struct mda_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; // Don't set the flags without specifying the flags value. //param->flags = af->dev_info.caps; 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 mda_factory *mf = (struct mda_factory*)f; pj_pool_t *pool; struct mda_stream *strm; /* Can only support 16bits per sample raw PCM format. */ PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL); PJ_ASSERT_RETURN((param->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT)==0 || param->ext_fmt.id == PJMEDIA_FORMAT_L16, PJ_ENOTSUP); /* It seems that MDA recorder only supports for mono channel. */ PJ_ASSERT_RETURN(param->channel_count == 1, PJ_EINVAL); /* Create and Initialize stream descriptor */ pool = pj_pool_create(mf->pf, "symb_aud_dev", 1000, 1000, NULL); PJ_ASSERT_RETURN(pool, PJ_ENOMEM); strm = PJ_POOL_ZALLOC_T(pool, struct mda_stream); strm->pool = pool; strm->param = *param; // Create the output stream. if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { TRAPD(err, strm->out_engine = CPjAudioOutputEngine::NewL(strm, play_cb, user_data)); if (err != KErrNone) { pj_pool_release(pool); return PJ_RETURN_OS_ERROR(err); } } // Create the input stream. if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { TRAPD(err, strm->in_engine = CPjAudioInputEngine::NewL(strm, rec_cb, user_data)); if (err != KErrNone) { strm->in_engine = NULL; delete strm->out_engine; strm->out_engine = NULL; pj_pool_release(pool); return PJ_RETURN_OS_ERROR(err); } } /* 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 mda_stream *strm = (struct mda_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; } /* Update the input volume setting */ if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, &pi->input_vol) == PJ_SUCCESS) { pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_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 mda_stream *strm = (struct mda_stream*)s; pj_status_t status = PJ_ENOTSUP; PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); switch (cap) { case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL); TInt max_gain = strm->in_engine->GetMaxGain(); TInt gain = strm->in_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->out_engine, PJ_EINVAL); TInt max_vol = strm->out_engine->GetMaxVolume(); TInt vol = strm->out_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 mda_stream *strm = (struct mda_stream*)s; pj_status_t status = PJ_ENOTSUP; PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); switch (cap) { case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL); TInt max_gain = strm->in_engine->GetMaxGain(); if (max_gain > 0) { TInt gain; gain = *(unsigned*)pval * max_gain / 100; status = strm->in_engine->SetGain(gain); } 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->out_engine, PJ_EINVAL); TInt max_vol = strm->out_engine->GetMaxVolume(); if (max_vol > 0) { TInt vol; vol = *(unsigned*)pval * max_vol / 100; status = strm->out_engine->SetVolume(vol); } else { status = PJMEDIA_EAUD_NOTREADY; } } break; default: break; } return status; } /* API: Start stream. */ static pj_status_t stream_start(pjmedia_aud_stream *strm) { struct mda_stream *stream = (struct mda_stream*)strm; PJ_ASSERT_RETURN(stream, PJ_EINVAL); if (stream->out_engine) { pj_status_t status; status = stream->out_engine->StartPlay(); if (status != PJ_SUCCESS) return status; } if (stream->in_engine) { pj_status_t status; status = stream->in_engine->StartRecord(); if (status != PJ_SUCCESS) return status; } return PJ_SUCCESS; } /* API: Stop stream. */ static pj_status_t stream_stop(pjmedia_aud_stream *strm) { struct mda_stream *stream = (struct mda_stream*)strm; PJ_ASSERT_RETURN(stream, PJ_EINVAL); if (stream->in_engine) { stream->in_engine->Stop(); } if (stream->out_engine) { stream->out_engine->Stop(); } return PJ_SUCCESS; } /* API: Destroy stream. */ static pj_status_t stream_destroy(pjmedia_aud_stream *strm) { struct mda_stream *stream = (struct mda_stream*)strm; PJ_ASSERT_RETURN(stream, PJ_EINVAL); stream_stop(strm); delete stream->in_engine; stream->in_engine = NULL; delete stream->out_engine; stream->out_engine = NULL; 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_MDA */