blob: 645ed39453f04244e6c928a7a4bbbff27f60c9bb [file] [log] [blame]
/* $Id$ */
/*
* 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 */