summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp')
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp1196
1 files changed, 1196 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
new file mode 100644
index 0000000..b4a6b9c
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
@@ -0,0 +1,1196 @@
+/* $Id: symb_mda_dev.cpp 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * 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-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_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 <mda/common/audio.h>
+#include <mdaaudiooutputstream.h>
+#include <mdaaudioinputstream.h>
+
+
+#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 */