blob: 2f35a660fc8c022551bc8aa92a0ef71d0def9337 [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2009 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjmedia/resample.h>
#include <pjmedia/stereo.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_VAS
/* VAS headers */
#include <VoIPUtilityFactory.h>
#include <VoIPDownlinkStream.h>
#include <VoIPUplinkStream.h>
#include <VoIPFormatIntfc.h>
#include <VoIPG711DecoderIntfc.h>
#include <VoIPG711EncoderIntfc.h>
#include <VoIPG729DecoderIntfc.h>
#include <VoIPILBCDecoderIntfc.h>
#include <VoIPILBCEncoderIntfc.h>
/* AMR helper */
#include <pjmedia-codec/amr_helper.h>
/* Pack/unpack G.729 frame of S60 DSP codec, taken from:
* http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
*/
#include "s60_g729_bitstream.h"
#define THIS_FILE "symb_vas_dev.c"
#define BITS_PER_SAMPLE 16
/* When this macro is set, VAS will use EPCM16 format for PCM input/output,
* otherwise VAS will use EG711 then transcode it to PCM.
* Note that using native EPCM16 format may introduce (much) delay.
*/
//#define USE_NATIVE_PCM
#if 1
# define TRACE_(st) PJ_LOG(3, st)
#else
# define TRACE_(st)
#endif
/* VAS G.711 frame length */
static pj_uint8_t vas_g711_frame_len;
/* VAS factory */
struct vas_factory
{
pjmedia_aud_dev_factory base;
pj_pool_t *pool;
pj_pool_factory *pf;
pjmedia_aud_dev_info dev_info;
};
/* Forward declaration of CPjAudioEngine */
class CPjAudioEngine;
/* VAS stream. */
struct vas_stream
{
// Base
pjmedia_aud_stream base; /**< Base class. */
// Pool
pj_pool_t *pool; /**< Memory pool. */
// Common settings.
pjmedia_aud_param param; /**< Stream param. */
pjmedia_aud_rec_cb rec_cb; /**< Record callback. */
pjmedia_aud_play_cb play_cb; /**< Playback callback. */
void *user_data; /**< Application data. */
// Audio engine
CPjAudioEngine *engine; /**< Internal engine. */
pj_timestamp ts_play; /**< Playback timestamp.*/
pj_timestamp ts_rec; /**< Record timestamp. */
pj_int16_t *play_buf; /**< Playback buffer. */
pj_uint16_t play_buf_len; /**< Playback buffer length. */
pj_uint16_t play_buf_start; /**< Playback buffer start index. */
pj_int16_t *rec_buf; /**< Record buffer. */
pj_uint16_t rec_buf_len; /**< Record buffer length. */
void *strm_data; /**< Stream data. */
/* Resampling is needed, in case audio device is opened with clock rate
* other than 8kHz (only for PCM format).
*/
pjmedia_resample *play_resample; /**< Resampler for playback. */
pjmedia_resample *rec_resample; /**< Resampler for recording */
pj_uint16_t resample_factor; /**< Resample factor, requested
clock rate / 8000 */
/* When stream is working in PCM format, where the samples may need to be
* resampled from/to different clock rate and/or channel count, PCM buffer
* is needed to perform such resampling operations.
*/
pj_int16_t *pcm_buf; /**< PCM buffer. */
};
/* Prototypes */
static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
static pj_status_t factory_destroy(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
};
static pjmedia_aud_stream_op stream_op =
{
&stream_get_param,
&stream_get_cap,
&stream_set_cap,
&stream_start,
&stream_stop,
&stream_destroy
};
/****************************************************************************
* Internal VAS Engine
*/
/*
* 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));
}
typedef void(*PjAudioCallback)(CVoIPDataBuffer *buf, void *user_data);
/*
* Audio setting for CPjAudioEngine.
*/
class CPjAudioSetting
{
public:
TVoIPCodecFormat format;
TInt mode;
TBool plc;
TBool vad;
TBool cng;
TBool loudspk;
};
/*
* Implementation: Symbian Input & Output Stream.
*/
class CPjAudioEngine : public CBase,
public MVoIPDownlinkObserver,
public MVoIPUplinkObserver,
public MVoIPFormatObserver
{
public:
enum State
{
STATE_NULL,
STATE_STARTING,
STATE_READY,
STATE_STREAMING
};
~CPjAudioEngine();
static CPjAudioEngine *NewL(struct vas_stream *parent_strm,
PjAudioCallback rec_cb,
PjAudioCallback play_cb,
void *user_data,
const CPjAudioSetting &setting);
TInt Start();
void Stop();
TInt ActivateSpeaker(TBool active);
TInt SetVolume(TInt vol) { return iVoIPDnlink->SetVolume(vol); }
TInt GetVolume() { TInt vol;iVoIPDnlink->GetVolume(vol);return vol; }
TInt GetMaxVolume() { TInt vol;iVoIPDnlink->GetMaxVolume(vol);return vol; }
TInt SetGain(TInt gain) { return iVoIPUplink->SetGain(gain); }
TInt GetGain() { TInt gain;iVoIPUplink->GetGain(gain);return gain; }
TInt GetMaxGain() { TInt gain;iVoIPUplink->GetMaxGain(gain);return gain; }
TBool IsStarted();
private:
CPjAudioEngine(struct vas_stream *parent_strm,
PjAudioCallback rec_cb,
PjAudioCallback play_cb,
void *user_data,
const CPjAudioSetting &setting);
void ConstructL();
TInt InitPlay();
TInt InitRec();
TInt StartPlay();
TInt StartRec();
// From MVoIPDownlinkObserver
void FillBuffer(const CVoIPAudioDownlinkStream& aSrc,
CVoIPDataBuffer* aBuffer);
void Event(const CVoIPAudioDownlinkStream& aSrc,
TInt aEventType,
TInt aError);
// From MVoIPUplinkObserver
void EmptyBuffer(const CVoIPAudioUplinkStream& aSrc,
CVoIPDataBuffer* aBuffer);
void Event(const CVoIPAudioUplinkStream& aSrc,
TInt aEventType,
TInt aError);
// From MVoIPFormatObserver
void Event(const CVoIPFormatIntfc& aSrc, TInt aEventType);
State dn_state_;
State up_state_;
struct vas_stream *parentStrm_;
CPjAudioSetting setting_;
PjAudioCallback rec_cb_;
PjAudioCallback play_cb_;
void *user_data_;
// VAS objects
CVoIPUtilityFactory *iFactory;
CVoIPAudioDownlinkStream *iVoIPDnlink;
CVoIPAudioUplinkStream *iVoIPUplink;
CVoIPFormatIntfc *enc_fmt_if;
CVoIPFormatIntfc *dec_fmt_if;
};
CPjAudioEngine* CPjAudioEngine::NewL(struct vas_stream *parent_strm,
PjAudioCallback rec_cb,
PjAudioCallback play_cb,
void *user_data,
const CPjAudioSetting &setting)
{
CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
rec_cb, play_cb,
user_data,
setting);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
return self;
}
void CPjAudioEngine::ConstructL()
{
TInt err;
const TVersion ver(1, 0, 0); /* Not really used at this time */
err = CVoIPUtilityFactory::CreateFactory(iFactory);
User::LeaveIfError(err);
if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) {
err = iFactory->CreateDownlinkStream(ver,
CVoIPUtilityFactory::EVoIPCall,
iVoIPDnlink);
User::LeaveIfError(err);
}
if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
err = iFactory->CreateUplinkStream(ver,
CVoIPUtilityFactory::EVoIPCall,
iVoIPUplink);
User::LeaveIfError(err);
}
}
CPjAudioEngine::CPjAudioEngine(struct vas_stream *parent_strm,
PjAudioCallback rec_cb,
PjAudioCallback play_cb,
void *user_data,
const CPjAudioSetting &setting)
: dn_state_(STATE_NULL),
up_state_(STATE_NULL),
parentStrm_(parent_strm),
setting_(setting),
rec_cb_(rec_cb),
play_cb_(play_cb),
user_data_(user_data),
iFactory(NULL),
iVoIPDnlink(NULL),
iVoIPUplink(NULL),
enc_fmt_if(NULL),
dec_fmt_if(NULL)
{
}
CPjAudioEngine::~CPjAudioEngine()
{
Stop();
if (iVoIPUplink)
iVoIPUplink->Close();
if (iVoIPDnlink)
iVoIPDnlink->Close();
delete enc_fmt_if;
delete dec_fmt_if;
delete iVoIPDnlink;
delete iVoIPUplink;
delete iFactory;
TRACE_((THIS_FILE, "Sound device destroyed"));
}
TBool CPjAudioEngine::IsStarted()
{
return ((((parentStrm_->param.dir & PJMEDIA_DIR_CAPTURE) == 0) ||
up_state_ == STATE_STREAMING) &&
(((parentStrm_->param.dir & PJMEDIA_DIR_PLAYBACK) == 0) ||
dn_state_ == STATE_STREAMING));
}
TInt CPjAudioEngine::InitPlay()
{
TInt err;
pj_assert(iVoIPDnlink);
delete dec_fmt_if;
dec_fmt_if = NULL;
err = iVoIPDnlink->SetFormat(setting_.format, dec_fmt_if);
if (err != KErrNone)
return err;
err = dec_fmt_if->SetObserver(*this);
if (err != KErrNone)
return err;
return iVoIPDnlink->Open(*this);
}
TInt CPjAudioEngine::InitRec()
{
TInt err;
pj_assert(iVoIPUplink);
delete enc_fmt_if;
enc_fmt_if = NULL;
err = iVoIPUplink->SetFormat(setting_.format, enc_fmt_if);
if (err != KErrNone)
return err;
err = enc_fmt_if->SetObserver(*this);
if (err != KErrNone)
return err;
return iVoIPUplink->Open(*this);
}
TInt CPjAudioEngine::StartPlay()
{
TInt err = KErrNone;
pj_assert(iVoIPDnlink);
pj_assert(dn_state_ == STATE_READY);
/* Configure specific codec setting */
switch (setting_.format) {
case EG711:
{
CVoIPG711DecoderIntfc *g711dec_if = (CVoIPG711DecoderIntfc*)
dec_fmt_if;
err = g711dec_if->SetMode((CVoIPFormatIntfc::TG711CodecMode)
setting_.mode);
}
break;
case EILBC:
{
CVoIPILBCDecoderIntfc *ilbcdec_if = (CVoIPILBCDecoderIntfc*)
dec_fmt_if;
err = ilbcdec_if->SetMode((CVoIPFormatIntfc::TILBCCodecMode)
setting_.mode);
}
break;
case EAMR_NB:
/* Ticket #1008: AMR playback issue on few devices, e.g: E72, E52 */
err = dec_fmt_if->SetFrameMode(ETrue);
break;
default:
break;
}
if (err != KErrNone)
goto on_return;
/* Configure audio routing */
ActivateSpeaker(setting_.loudspk);
/* Start player */
err = iVoIPDnlink->Start();
on_return:
if (err == KErrNone) {
dn_state_ = STATE_STREAMING;
TRACE_((THIS_FILE, "Downlink started"));
} else {
snd_perror("Failed starting downlink", err);
}
return err;
}
TInt CPjAudioEngine::StartRec()
{
TInt err = KErrNone;
pj_assert(iVoIPUplink);
pj_assert(up_state_ == STATE_READY);
/* Configure specific codec setting */
switch (setting_.format) {
case EG711:
{
CVoIPG711EncoderIntfc *g711enc_if = (CVoIPG711EncoderIntfc*)
enc_fmt_if;
err = g711enc_if->SetMode((CVoIPFormatIntfc::TG711CodecMode)
setting_.mode);
}
break;
case EILBC:
{
CVoIPILBCEncoderIntfc *ilbcenc_if = (CVoIPILBCEncoderIntfc*)
enc_fmt_if;
err = ilbcenc_if->SetMode((CVoIPFormatIntfc::TILBCCodecMode)
setting_.mode);
}
break;
case EAMR_NB:
err = enc_fmt_if->SetBitRate(setting_.mode);
break;
default:
break;
}
if (err != KErrNone)
goto on_return;
/* Configure general codec setting */
enc_fmt_if->SetVAD(setting_.vad);
/* Start recorder */
err = iVoIPUplink->Start();
on_return:
if (err == KErrNone) {
up_state_ = STATE_STREAMING;
TRACE_((THIS_FILE, "Uplink started"));
} else {
snd_perror("Failed starting uplink", err);
}
return err;
}
TInt CPjAudioEngine::Start()
{
TInt err = KErrNone;
if (iVoIPDnlink) {
switch(dn_state_) {
case STATE_READY:
err = StartPlay();
break;
case STATE_NULL:
err = InitPlay();
if (err != KErrNone)
return err;
dn_state_ = STATE_STARTING;
break;
default:
break;
}
}
if (iVoIPUplink) {
switch(up_state_) {
case STATE_READY:
err = StartRec();
break;
case STATE_NULL:
err = InitRec();
if (err != KErrNone)
return err;
up_state_ = STATE_STARTING;
break;
default:
break;
}
}
return err;
}
void CPjAudioEngine::Stop()
{
if (iVoIPDnlink) {
switch(dn_state_) {
case STATE_STREAMING:
iVoIPDnlink->Stop();
dn_state_ = STATE_READY;
break;
case STATE_STARTING:
dn_state_ = STATE_NULL;
break;
default:
break;
}
}
if (iVoIPUplink) {
switch(up_state_) {
case STATE_STREAMING:
iVoIPUplink->Stop();
up_state_ = STATE_READY;
break;
case STATE_STARTING:
up_state_ = STATE_NULL;
break;
default:
break;
}
}
}
TInt CPjAudioEngine::ActivateSpeaker(TBool active)
{
TInt err = KErrNotSupported;
if (iVoIPDnlink) {
err = iVoIPDnlink->SetAudioDevice(active?
CVoIPAudioDownlinkStream::ELoudSpeaker :
CVoIPAudioDownlinkStream::EHandset);
TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off")));
}
return err;
}
// Callback from MVoIPDownlinkObserver
void CPjAudioEngine::FillBuffer(const CVoIPAudioDownlinkStream& aSrc,
CVoIPDataBuffer* aBuffer)
{
play_cb_(aBuffer, user_data_);
iVoIPDnlink->BufferFilled(aBuffer);
}
// Callback from MVoIPUplinkObserver
void CPjAudioEngine::EmptyBuffer(const CVoIPAudioUplinkStream& aSrc,
CVoIPDataBuffer* aBuffer)
{
rec_cb_(aBuffer, user_data_);
iVoIPUplink->BufferEmptied(aBuffer);
}
// Callback from MVoIPDownlinkObserver
void CPjAudioEngine::Event(const CVoIPAudioDownlinkStream& /*aSrc*/,
TInt aEventType,
TInt aError)
{
switch (aEventType) {
case MVoIPDownlinkObserver::KOpenComplete:
if (aError == KErrNone) {
State last_state = dn_state_;
dn_state_ = STATE_READY;
TRACE_((THIS_FILE, "Downlink opened"));
if (last_state == STATE_STARTING)
StartPlay();
}
break;
case MVoIPDownlinkObserver::KDownlinkClosed:
dn_state_ = STATE_NULL;
TRACE_((THIS_FILE, "Downlink closed"));
break;
case MVoIPDownlinkObserver::KDownlinkError:
dn_state_ = STATE_READY;
snd_perror("Downlink problem", aError);
break;
default:
break;
}
}
// Callback from MVoIPUplinkObserver
void CPjAudioEngine::Event(const CVoIPAudioUplinkStream& /*aSrc*/,
TInt aEventType,
TInt aError)
{
switch (aEventType) {
case MVoIPUplinkObserver::KOpenComplete:
if (aError == KErrNone) {
State last_state = up_state_;
up_state_ = STATE_READY;
TRACE_((THIS_FILE, "Uplink opened"));
if (last_state == STATE_STARTING)
StartRec();
}
break;
case MVoIPUplinkObserver::KUplinkClosed:
up_state_ = STATE_NULL;
TRACE_((THIS_FILE, "Uplink closed"));
break;
case MVoIPUplinkObserver::KUplinkError:
up_state_ = STATE_READY;
snd_perror("Uplink problem", aError);
break;
default:
break;
}
}
// Callback from MVoIPFormatObserver
void CPjAudioEngine::Event(const CVoIPFormatIntfc& /*aSrc*/,
TInt aEventType)
{
snd_perror("Format event", aEventType);
}
/****************************************************************************
* Internal VAS callbacks for PCM format
*/
#ifdef USE_NATIVE_PCM
static void RecCbPcm2(CVoIPDataBuffer *buf, void *user_data)
{
struct vas_stream *strm = (struct vas_stream*) user_data;
TPtr8 buffer(0, 0, 0);
pj_int16_t *p_buf;
unsigned buf_len;
/* Get the buffer */
buf->GetPayloadPtr(buffer);
/* Call parent callback */
p_buf = (pj_int16_t*) buffer.Ptr();
buf_len = buffer.Length() >> 1;
while (buf_len) {
unsigned req;
req = strm->param.samples_per_frame - strm->rec_buf_len;
if (req > buf_len)
req = buf_len;
pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_len, p_buf, req);
p_buf += req;
buf_len -= req;
strm->rec_buf_len += req;
if (strm->rec_buf_len >= strm->param.samples_per_frame) {
pjmedia_frame f;
f.buf = strm->rec_buf;
f.type = PJMEDIA_FRAME_TYPE_AUDIO;
f.size = strm->param.samples_per_frame << 1;
strm->rec_cb(strm->user_data, &f);
strm->rec_buf_len = 0;
}
}
}
static void PlayCbPcm2(CVoIPDataBuffer *buf, void *user_data)
{
struct vas_stream *strm = (struct vas_stream*) user_data;
TPtr8 buffer(0, 0, 0);
pjmedia_frame f;
/* Get the buffer */
buf->GetPayloadPtr(buffer);
/* Call parent callback */
f.buf = strm->play_buf;
f.size = strm->param.samples_per_frame << 1;
strm->play_cb(strm->user_data, &f);
if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
pjmedia_zero_samples((pj_int16_t*)f.buf,
strm->param.samples_per_frame);
}
f.size = strm->param.samples_per_frame << 1;
/* Init buffer attributes and header. */
buffer.Zero();
buffer.Append((TUint8*)f.buf, f.size);
/* Set the buffer */
buf->SetPayloadPtr(buffer);
}
#else // not USE_NATIVE_PCM
static void RecCbPcm(CVoIPDataBuffer *buf, void *user_data)
{
struct vas_stream *strm = (struct vas_stream*) user_data;
TPtr8 buffer(0, 0, 0);
/* Get the buffer */
buf->GetPayloadPtr(buffer);
/* Buffer has to contain normal speech. */
pj_assert(buffer[0] == 1 && buffer[1] == 0);
/* Detect the recorder G.711 frame size, player frame size will follow
* this recorder frame size.
*/
if (vas_g711_frame_len == 0) {
vas_g711_frame_len = buffer.Length() < 160? 80 : 160;
TRACE_((THIS_FILE, "Detected VAS G.711 frame size = %u samples",
vas_g711_frame_len));
}
/* Decode VAS buffer (coded in G.711) and put the PCM result into rec_buf.
* Whenever rec_buf is full, call parent stream callback.
*/
unsigned samples_processed = 0;
while (samples_processed < vas_g711_frame_len) {
unsigned samples_to_process;
unsigned samples_req;
samples_to_process = vas_g711_frame_len - samples_processed;
samples_req = (strm->param.samples_per_frame /
strm->param.channel_count /
strm->resample_factor) -
strm->rec_buf_len;
if (samples_to_process > samples_req)
samples_to_process = samples_req;
pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
buffer.Ptr() + 2 + samples_processed,
samples_to_process);
strm->rec_buf_len += samples_to_process;
samples_processed += samples_to_process;
/* Buffer is full, time to call parent callback */
if (strm->rec_buf_len == strm->param.samples_per_frame /
strm->param.channel_count /
strm->resample_factor)
{
pjmedia_frame f;
/* Need to resample clock rate? */
if (strm->rec_resample) {
unsigned resampled = 0;
while (resampled < strm->rec_buf_len) {
pjmedia_resample_run(strm->rec_resample,
&strm->rec_buf[resampled],
strm->pcm_buf +
resampled * strm->resample_factor);
resampled += 80;
}
f.buf = strm->pcm_buf;
} else {
f.buf = strm->rec_buf;
}
/* Need to convert channel count? */
if (strm->param.channel_count != 1) {
pjmedia_convert_channel_1ton((pj_int16_t*)f.buf,
(pj_int16_t*)f.buf,
strm->param.channel_count,
strm->param.samples_per_frame /
strm->param.channel_count,
0);
}
/* Call parent callback */
f.type = PJMEDIA_FRAME_TYPE_AUDIO;
f.size = strm->param.samples_per_frame << 1;
strm->rec_cb(strm->user_data, &f);
strm->rec_buf_len = 0;
}
}
}
#endif // USE_NATIVE_PCM
static void PlayCbPcm(CVoIPDataBuffer *buf, void *user_data)
{
struct vas_stream *strm = (struct vas_stream*) user_data;
unsigned g711_frame_len = vas_g711_frame_len;
TPtr8 buffer(0, 0, 0);
/* Get the buffer */
buf->GetPayloadPtr(buffer);
/* Init buffer attributes and header. */
buffer.Zero();
buffer.Append(1);
buffer.Append(0);
/* Assume frame size is 10ms if frame size hasn't been known. */
if (g711_frame_len == 0)
g711_frame_len = 80;
/* Call parent stream callback to get PCM samples to play,
* encode the PCM samples into G.711 and put it into VAS buffer.
*/
unsigned samples_processed = 0;
while (samples_processed < g711_frame_len) {
/* Need more samples to play, time to call parent callback */
if (strm->play_buf_len == 0) {
pjmedia_frame f;
unsigned samples_got;
f.size = strm->param.samples_per_frame << 1;
if (strm->play_resample || strm->param.channel_count != 1)
f.buf = strm->pcm_buf;
else
f.buf = strm->play_buf;
/* Call parent callback */
strm->play_cb(strm->user_data, &f);
if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
pjmedia_zero_samples((pj_int16_t*)f.buf,
strm->param.samples_per_frame);
}
samples_got = strm->param.samples_per_frame /
strm->param.channel_count /
strm->resample_factor;
/* Need to convert channel count? */
if (strm->param.channel_count != 1) {
pjmedia_convert_channel_nto1((pj_int16_t*)f.buf,
(pj_int16_t*)f.buf,
strm->param.channel_count,
strm->param.samples_per_frame,
PJ_FALSE,
0);
}
/* Need to resample clock rate? */
if (strm->play_resample) {
unsigned resampled = 0;
while (resampled < samples_got)
{
pjmedia_resample_run(strm->play_resample,
strm->pcm_buf +
resampled * strm->resample_factor,
&strm->play_buf[resampled]);
resampled += 80;
}
}
strm->play_buf_len = samples_got;
strm->play_buf_start = 0;
}
unsigned tmp;
tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - samples_processed);
pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
&strm->play_buf[strm->play_buf_start],
tmp);
buffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
samples_processed += tmp;
strm->play_buf_len -= tmp;
strm->play_buf_start += tmp;
}
/* Set the buffer */
buf->SetPayloadPtr(buffer);
}
/****************************************************************************
* Internal VAS callbacks for non-PCM format
*/
static void RecCb(CVoIPDataBuffer *buf, void *user_data)
{
struct vas_stream *strm = (struct vas_stream*) user_data;
pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
TPtr8 buffer(0, 0, 0);
/* Get the buffer */
buf->GetPayloadPtr(buffer);
switch(strm->param.ext_fmt.id) {
case PJMEDIA_FORMAT_AMR:
{
const pj_uint8_t *p = (const pj_uint8_t*)buffer.Ptr() + 1;
unsigned len = buffer.Length() - 1;
pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
if (frame->samples_cnt == strm->param.samples_per_frame) {
frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
frame->samples_cnt = 0;
frame->subframe_cnt = 0;
}
}
break;
case PJMEDIA_FORMAT_G729:
{
/* Check if we got a normal or SID frame. */
if (buffer[0] != 0) {
enum { NORMAL_LEN = 22, SID_LEN = 8 };
TBitStream *bitstream = (TBitStream*)strm->strm_data;
unsigned src_len = buffer.Length()- 2;
pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
const TDesC8& p = bitstream->CompressG729Frame(
buffer.Right(src_len),
src_len == SID_LEN);
pjmedia_frame_ext_append_subframe(frame, p.Ptr(),
p.Length() << 3, 80);
} else { /* We got null frame. */
pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
}
if (frame->samples_cnt == strm->param.samples_per_frame) {
frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
frame->samples_cnt = 0;
frame->subframe_cnt = 0;
}
}
break;
case PJMEDIA_FORMAT_ILBC:
{
unsigned samples_got;
samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240;
/* Check if we got a normal or SID frame. */
if (buffer[0] != 0) {
const pj_uint8_t *p = (const pj_uint8_t*)buffer.Ptr() + 2;
unsigned len = buffer.Length() - 2;
pjmedia_frame_ext_append_subframe(frame, p, len << 3,
samples_got);
} else { /* We got null frame. */
pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
}
if (frame->samples_cnt == strm->param.samples_per_frame) {
frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
frame->samples_cnt = 0;
frame->subframe_cnt = 0;
}
}
break;
case PJMEDIA_FORMAT_PCMU:
case PJMEDIA_FORMAT_PCMA:
{
unsigned samples_processed = 0;
/* Make sure it is normal frame. */
pj_assert(buffer[0] == 1 && buffer[1] == 0);
/* Detect the recorder G.711 frame size, player frame size will
* follow this recorder frame size.
*/
if (vas_g711_frame_len == 0) {
vas_g711_frame_len = buffer.Length() < 160? 80 : 160;
TRACE_((THIS_FILE, "Detected VAS G.711 frame size = %u samples",
vas_g711_frame_len));
}
/* Convert VAS buffer format into pjmedia_frame_ext. Whenever
* samples count in the frame is equal to stream's samples per
* frame, call parent stream callback.
*/
while (samples_processed < vas_g711_frame_len) {
unsigned tmp;
const pj_uint8_t *pb = (const pj_uint8_t*)buffer.Ptr() +
2 + samples_processed;
tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
vas_g711_frame_len - samples_processed);
pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
samples_processed += tmp;
if (frame->samples_cnt == strm->param.samples_per_frame) {
frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
frame->samples_cnt = 0;
frame->subframe_cnt = 0;
}
}
}
break;
default:
break;
}
}
static void PlayCb(CVoIPDataBuffer *buf, void *user_data)
{
struct vas_stream *strm = (struct vas_stream*) user_data;
pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
TPtr8 buffer(0, 0, 0);
/* Get the buffer */
buf->GetPayloadPtr(buffer);
/* Init buffer attributes and header. */
buffer.Zero();
switch(strm->param.ext_fmt.id) {
case PJMEDIA_FORMAT_AMR:
{
if (frame->samples_cnt == 0) {
frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
}
if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
pjmedia_frame_ext_subframe *sf;
unsigned samples_cnt;
sf = pjmedia_frame_ext_get_subframe(frame, 0);
samples_cnt = frame->samples_cnt / frame->subframe_cnt;
if (sf->data && sf->bitlen) {
/* AMR header for VAS is one byte, the format (may be!):
* 0xxxxy00, where xxxx:frame type, y:not sure.
*/
unsigned len = (sf->bitlen+7)>>3;
enum {SID_FT = 8 };
pj_uint8_t amr_header = 4, ft = SID_FT;
if (len >= pjmedia_codec_amrnb_framelen[0])
ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
amr_header |= ft << 3;
buffer.Append(amr_header);
buffer.Append((TUint8*)sf->data, len);
} else {
enum {NO_DATA_FT = 15 };
pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3);
buffer.Append(amr_header);
}
pjmedia_frame_ext_pop_subframes(frame, 1);
} else { /* PJMEDIA_FRAME_TYPE_NONE */
enum {NO_DATA_FT = 15 };
pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3);
buffer.Append(amr_header);
frame->samples_cnt = 0;
frame->subframe_cnt = 0;
}
}
break;
case PJMEDIA_FORMAT_G729:
{
if (frame->samples_cnt == 0) {
frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
}
if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
pjmedia_frame_ext_subframe *sf;
unsigned samples_cnt;
sf = pjmedia_frame_ext_get_subframe(frame, 0);
samples_cnt = frame->samples_cnt / frame->subframe_cnt;
if (sf->data && sf->bitlen) {
enum { NORMAL_LEN = 10, SID_LEN = 2 };
pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
TBitStream *bitstream = (TBitStream*)strm->strm_data;
const TPtrC8 src(sf->data, sf->bitlen>>3);
const TDesC8 &dst = bitstream->ExpandG729Frame(src,
sid_frame);
if (sid_frame) {
buffer.Append(2);
buffer.Append(0);
} else {
buffer.Append(1);
buffer.Append(0);
}
buffer.Append(dst);
} else {
buffer.Append(2);
buffer.Append(0);
buffer.AppendFill(0, 22);
}
pjmedia_frame_ext_pop_subframes(frame, 1);
} else { /* PJMEDIA_FRAME_TYPE_NONE */
buffer.Append(2);
buffer.Append(0);
buffer.AppendFill(0, 22);
}
}
break;
case PJMEDIA_FORMAT_ILBC:
{
if (frame->samples_cnt == 0) {
frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
}
if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
pjmedia_frame_ext_subframe *sf;
unsigned samples_cnt;
sf = pjmedia_frame_ext_get_subframe(frame, 0);
samples_cnt = frame->samples_cnt / frame->subframe_cnt;
pj_assert((strm->param.ext_fmt.bitrate == 15200 &&
samples_cnt == 160) ||
(strm->param.ext_fmt.bitrate != 15200 &&
samples_cnt == 240));
if (sf->data && sf->bitlen) {
buffer.Append(1);
buffer.Append(0);
buffer.Append((TUint8*)sf->data, sf->bitlen>>3);
} else {
unsigned frame_len;
buffer.Append(1);
buffer.Append(0);
/* VAS iLBC frame is 20ms or 30ms */
frame_len = strm->param.ext_fmt.bitrate == 15200? 38 : 50;
buffer.AppendFill(0, frame_len);
}
pjmedia_frame_ext_pop_subframes(frame, 1);
} else { /* PJMEDIA_FRAME_TYPE_NONE */
unsigned frame_len;
buffer.Append(1);
buffer.Append(0);
/* VAS iLBC frame is 20ms or 30ms */
frame_len = strm->param.ext_fmt.bitrate == 15200? 38 : 50;
buffer.AppendFill(0, frame_len);
}
}
break;
case PJMEDIA_FORMAT_PCMU:
case PJMEDIA_FORMAT_PCMA:
{
unsigned samples_ready = 0;
unsigned samples_req = vas_g711_frame_len;
/* Assume frame size is 10ms if frame size hasn't been known. */
if (samples_req == 0)
samples_req = 80;
buffer.Append(1);
buffer.Append(0);
/* Call parent stream callback to get samples to play. */
while (samples_ready < samples_req) {
if (frame->samples_cnt == 0) {
frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
}
if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
pjmedia_frame_ext_subframe *sf;
unsigned samples_cnt;
sf = pjmedia_frame_ext_get_subframe(frame, 0);
samples_cnt = frame->samples_cnt / frame->subframe_cnt;
if (sf->data && sf->bitlen) {
buffer.Append((TUint8*)sf->data, sf->bitlen>>3);
} else {
pj_uint8_t silc;
silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
buffer.AppendFill(silc, samples_cnt);
}
samples_ready += samples_cnt;
pjmedia_frame_ext_pop_subframes(frame, 1);
} else { /* PJMEDIA_FRAME_TYPE_NONE */
pj_uint8_t silc;
silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
buffer.AppendFill(silc, samples_req - samples_ready);
samples_ready = samples_req;
frame->samples_cnt = 0;
frame->subframe_cnt = 0;
}
}
}
break;
default:
break;
}
/* Set the buffer */
buf->SetPayloadPtr(buffer);
}
/****************************************************************************
* Factory operations
*/
/*
* C compatible declaration of VAS factory.
*/
PJ_BEGIN_DECL
PJ_DECL(pjmedia_aud_dev_factory*)pjmedia_symb_vas_factory(pj_pool_factory *pf);
PJ_END_DECL
/*
* Init VAS audio driver.
*/
PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_symb_vas_factory(pj_pool_factory *pf)
{
struct vas_factory *f;
pj_pool_t *pool;
pool = pj_pool_create(pf, "VAS", 1000, 1000, NULL);
f = PJ_POOL_ZALLOC_T(pool, struct vas_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 vas_factory *af = (struct vas_factory*)f;
CVoIPUtilityFactory *vas_factory_;
CVoIPAudioUplinkStream *vas_uplink;
CVoIPAudioDownlinkStream *vas_dnlink;
RArray<TVoIPCodecFormat> uplink_formats, dnlink_formats;
unsigned ext_fmt_cnt = 0;
TVersion vas_version(1, 0, 0); /* Not really used at this time */
TInt err;
pj_ansi_strcpy(af->dev_info.name, "S60 VAS");
af->dev_info.default_samples_per_sec = 8000;
af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
//PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
PJMEDIA_AUD_DEV_CAP_VAD |
PJMEDIA_AUD_DEV_CAP_CNG;
af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
af->dev_info.input_count = 1;
af->dev_info.output_count = 1;
af->dev_info.ext_fmt_cnt = 0;
/* Enumerate supported formats */
err = CVoIPUtilityFactory::CreateFactory(vas_factory_);
if (err != KErrNone)
goto on_error;
/* On VAS 2.0, uplink & downlink stream should be instantiated before
* querying formats.
*/
err = vas_factory_->CreateUplinkStream(vas_version,
CVoIPUtilityFactory::EVoIPCall,
vas_uplink);
if (err != KErrNone)
goto on_error;
err = vas_factory_->CreateDownlinkStream(vas_version,
CVoIPUtilityFactory::EVoIPCall,
vas_dnlink);
if (err != KErrNone)
goto on_error;
uplink_formats.Reset();
err = vas_factory_->GetSupportedUplinkFormats(uplink_formats);
if (err != KErrNone)
goto on_error;
dnlink_formats.Reset();
err = vas_factory_->GetSupportedDownlinkFormats(dnlink_formats);
if (err != KErrNone)
goto on_error;
/* Free the streams, they are just used for querying formats */
delete vas_uplink;
vas_uplink = NULL;
delete vas_dnlink;
vas_dnlink = NULL;
delete vas_factory_;
vas_factory_ = NULL;
for (TInt i = 0; i < dnlink_formats.Count(); i++) {
/* Format must be supported by both downlink & uplink. */
if (uplink_formats.Find(dnlink_formats[i]) == KErrNotFound)
continue;
switch (dnlink_formats[i]) {
case EAMR_NB:
af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_AMR;
af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 7400;
af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_TRUE;
break;
case EG729:
af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_G729;
af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 8000;
af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
break;
case EILBC:
af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_ILBC;
af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 13333;
af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_TRUE;
break;
case EG711:
#if PJMEDIA_AUDIO_DEV_SYMB_VAS_VERSION==2
case EG711_10MS:
#endif
af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_PCMU;
af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 64000;
af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
++ext_fmt_cnt;
af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_PCMA;
af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 64000;
af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
break;
default:
continue;
}
++ext_fmt_cnt;
}
af->dev_info.ext_fmt_cnt = ext_fmt_cnt;
uplink_formats.Close();
dnlink_formats.Close();
PJ_LOG(3, (THIS_FILE, "VAS initialized"));
return PJ_SUCCESS;
on_error:
return PJ_RETURN_OS_ERROR(err);
}
/* API: destroy factory */
static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
{
struct vas_factory *af = (struct vas_factory*)f;
pj_pool_t *pool = af->pool;
af->pool = NULL;
pj_pool_release(pool);
PJ_LOG(3, (THIS_FILE, "VAS destroyed"));
return PJ_SUCCESS;
}
/* 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 vas_factory *af = (struct vas_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 vas_factory *af = (struct vas_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;
param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
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 vas_factory *af = (struct vas_factory*)f;
pj_pool_t *pool;
struct vas_stream *strm;
CPjAudioSetting vas_setting;
PjAudioCallback vas_rec_cb;
PjAudioCallback vas_play_cb;
/* Can only support 16bits per sample */
PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
/* Supported clock rates:
* - for non-PCM format: 8kHz
* - for PCM format: 8kHz and 16kHz
*/
PJ_ASSERT_RETURN(param->clock_rate == 8000 ||
(param->clock_rate == 16000 &&
param->ext_fmt.id == PJMEDIA_FORMAT_L16),
PJ_EINVAL);
/* Supported channels number:
* - for non-PCM format: mono
* - for PCM format: mono and stereo
*/
PJ_ASSERT_RETURN(param->channel_count == 1 ||
(param->channel_count == 2 &&
param->ext_fmt.id == PJMEDIA_FORMAT_L16),
PJ_EINVAL);
/* Create and Initialize stream descriptor */
pool = pj_pool_create(af->pf, "vas-dev", 1000, 1000, NULL);
PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
strm = PJ_POOL_ZALLOC_T(pool, struct vas_stream);
strm->pool = pool;
strm->param = *param;
if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
/* Set audio engine fourcc. */
switch(strm->param.ext_fmt.id) {
case PJMEDIA_FORMAT_L16:
#ifdef USE_NATIVE_PCM
vas_setting.format = EPCM16;
#else
vas_setting.format = EG711;
#endif
break;
case PJMEDIA_FORMAT_PCMU:
case PJMEDIA_FORMAT_PCMA:
vas_setting.format = EG711;
break;
case PJMEDIA_FORMAT_AMR:
vas_setting.format = EAMR_NB;
break;
case PJMEDIA_FORMAT_G729:
vas_setting.format = EG729;
break;
case PJMEDIA_FORMAT_ILBC:
vas_setting.format = EILBC;
break;
default:
vas_setting.format = ENULL;
break;
}
/* Set audio engine mode. */
if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
{
#ifdef USE_NATIVE_PCM
vas_setting.mode = 0;
#else
vas_setting.mode = CVoIPFormatIntfc::EG711uLaw;
#endif
}
else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
{
vas_setting.mode = strm->param.ext_fmt.bitrate;
}
else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU)
{
vas_setting.mode = CVoIPFormatIntfc::EG711uLaw;
}
else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA)
{
vas_setting.mode = CVoIPFormatIntfc::EG711ALaw;
}
else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC)
{
if (strm->param.ext_fmt.bitrate == 15200)
vas_setting.mode = CVoIPFormatIntfc::EiLBC20mSecFrame;
else
vas_setting.mode = CVoIPFormatIntfc::EiLBC30mSecFrame;
} else {
vas_setting.mode = 0;
}
/* Disable VAD on L16, G711, iLBC, and also G729 (G729's SID
* potentially cause noise?).
*/
if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC ||
strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729)
{
vas_setting.vad = EFalse;
} else {
vas_setting.vad = strm->param.ext_fmt.vad;
}
/* Set other audio engine attributes. */
vas_setting.plc = strm->param.plc_enabled;
vas_setting.cng = vas_setting.vad;
vas_setting.loudspk =
strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
/* Set audio engine callbacks. */
if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
#ifdef USE_NATIVE_PCM
vas_play_cb = &PlayCbPcm2;
vas_rec_cb = &RecCbPcm2;
#else
vas_play_cb = &PlayCbPcm;
vas_rec_cb = &RecCbPcm;
#endif
} else {
vas_play_cb = &PlayCb;
vas_rec_cb = &RecCb;
}
strm->rec_cb = rec_cb;
strm->play_cb = play_cb;
strm->user_data = user_data;
strm->resample_factor = strm->param.clock_rate / 8000;
/* play_buf size is samples per frame scaled in to 8kHz mono. */
strm->play_buf = (pj_int16_t*)pj_pool_zalloc(
pool,
(strm->param.samples_per_frame /
strm->resample_factor /
strm->param.channel_count) << 1);
strm->play_buf_len = 0;
strm->play_buf_start = 0;
/* rec_buf size is samples per frame scaled in to 8kHz mono. */
strm->rec_buf = (pj_int16_t*)pj_pool_zalloc(
pool,
(strm->param.samples_per_frame /
strm->resample_factor /
strm->param.channel_count) << 1);
strm->rec_buf_len = 0;
if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
TBitStream *g729_bitstream = new TBitStream;
PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
strm->strm_data = (void*)g729_bitstream;
}
/* Init resampler when format is PCM and clock rate is not 8kHz */
if (strm->param.clock_rate != 8000 &&
strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
{
pj_status_t status;
if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
/* Create resample for recorder */
status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
8000,
strm->param.clock_rate,
80,
&strm->rec_resample);
if (status != PJ_SUCCESS)
return status;
}
if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
/* Create resample for player */
status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
strm->param.clock_rate,
8000,
80 * strm->resample_factor,
&strm->play_resample);
if (status != PJ_SUCCESS)
return status;
}
}
/* Create PCM buffer, when the clock rate is not 8kHz or not mono */
if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 &&
(strm->resample_factor > 1 || strm->param.channel_count != 1))
{
strm->pcm_buf = (pj_int16_t*)pj_pool_zalloc(pool,
strm->param.samples_per_frame << 1);
}
/* Create the audio engine. */
TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
vas_rec_cb, vas_play_cb,
strm, vas_setting));
if (err != KErrNone) {
pj_pool_release(pool);
return PJ_RETURN_OS_ERROR(err);
}
/* Apply output volume setting if specified */
if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
&param->output_vol);
}
/* 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 vas_stream *strm = (struct vas_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;
}
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 vas_stream *strm = (struct vas_stream*)s;
pj_status_t status = PJ_ENOTSUP;
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
switch (cap) {
case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
*(pjmedia_aud_dev_route*)pval = strm->param.output_route;
status = PJ_SUCCESS;
}
break;
/* There is a case that GetMaxGain() stucks, e.g: in N95. */
/*
case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
TInt max_gain = strm->engine->GetMaxGain();
TInt gain = strm->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->engine, PJ_EINVAL);
TInt max_vol = strm->engine->GetMaxVolume();
TInt vol = strm->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 vas_stream *strm = (struct vas_stream*)s;
pj_status_t status = PJ_ENOTSUP;
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
switch (cap) {
case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
TInt err;
PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
switch (r) {
case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
err = strm->engine->ActivateSpeaker(EFalse);
status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
break;
case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
err = strm->engine->ActivateSpeaker(ETrue);
status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
break;
default:
status = PJ_EINVAL;
break;
}
if (status == PJ_SUCCESS)
strm->param.output_route = r;
}
break;
/* There is a case that GetMaxGain() stucks, e.g: in N95. */
/*
case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
TInt max_gain = strm->engine->GetMaxGain();
if (max_gain > 0) {
TInt gain, err;
gain = *(unsigned*)pval * max_gain / 100;
err = strm->engine->SetGain(gain);
status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
} else {
status = PJMEDIA_EAUD_NOTREADY;
}
if (status == PJ_SUCCESS)
strm->param.input_vol = *(unsigned*)pval;
}
break;
*/
case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
TInt max_vol = strm->engine->GetMaxVolume();
if (max_vol > 0) {
TInt vol, err;
vol = *(unsigned*)pval * max_vol / 100;
err = strm->engine->SetVolume(vol);
status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
} else {
status = PJMEDIA_EAUD_NOTREADY;
}
if (status == PJ_SUCCESS)
strm->param.output_vol = *(unsigned*)pval;
}
break;
default:
break;
}
return status;
}
/* API: Start stream. */
static pj_status_t stream_start(pjmedia_aud_stream *strm)
{
struct vas_stream *stream = (struct vas_stream*)strm;
PJ_ASSERT_RETURN(stream, PJ_EINVAL);
if (stream->engine) {
enum { VAS_WAIT_START = 2000 }; /* in msecs */
TTime start, now;
TInt err = stream->engine->Start();
if (err != KErrNone)
return PJ_RETURN_OS_ERROR(err);
/* Perform synchronous start, timeout after VAS_WAIT_START ms */
start.UniversalTime();
do {
pj_symbianos_poll(-1, 100);
now.UniversalTime();
} while (!stream->engine->IsStarted() &&
(now.MicroSecondsFrom(start) < VAS_WAIT_START * 1000));
if (stream->engine->IsStarted())
return PJ_SUCCESS;
else
return PJ_ETIMEDOUT;
}
return PJ_EINVALIDOP;
}
/* API: Stop stream. */
static pj_status_t stream_stop(pjmedia_aud_stream *strm)
{
struct vas_stream *stream = (struct vas_stream*)strm;
PJ_ASSERT_RETURN(stream, PJ_EINVAL);
if (stream->engine) {
stream->engine->Stop();
}
return PJ_SUCCESS;
}
/* API: Destroy stream. */
static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
{
struct vas_stream *stream = (struct vas_stream*)strm;
PJ_ASSERT_RETURN(stream, PJ_EINVAL);
stream_stop(strm);
delete stream->engine;
stream->engine = NULL;
if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
stream->strm_data = NULL;
delete g729_bitstream;
}
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_VAS