/*
 * Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description:  VoIP Audio Services
 *               Implements uplink thread object.
 *
 */

#include <IlbcEncoderIntfc.h>
#include <G711EncoderIntfc.h>
#include <G729EncoderIntfc.h>
#include <SpeechEncoderConfig.h>
#include "debugtracemacros.h"
#include "VoIPSharedData.h"
#include "VoIPServerThread.h"

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::CVoIPUplinkThread
// Standard Constructor
// -----------------------------------------------------------------------------
//
CVoIPUplinkThread::CVoIPUplinkThread(TSharedData& aData) :
    iShared(aData)
    {
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::~CVoIPUplinkThread
// Standard Constructor
// -----------------------------------------------------------------------------
//
CVoIPUplinkThread::~CVoIPUplinkThread()
    {
    TRACE_PRN_FN_ENT;

    Stop();
    delete iSpeechEncoderConfig;
    delete iG711EncoderIntfc;
    delete iG729EncoderIntfc;
    delete iIlbcEncoderIntfc;

    TRACE_PRN_FN_EXT;
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::NewL
// Symbian two-phase constructor
// -----------------------------------------------------------------------------
//
CVoIPUplinkThread* CVoIPUplinkThread::NewL(TSharedData& aData)
    {
    CVoIPUplinkThread* self = new (ELeave) CVoIPUplinkThread(aData);
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    return self;
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::ConstructL
// Part two of Symbian two phase construction
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::ConstructL()
    {
    TRACE_PRN_FN_ENT;

    InitThreadL();
    iCodecID = iShared.iCodecSettings.iFourCC;
    InitMsgQueuesL(KUpLinkQueue, KUpLinkThreadComQueue);

    iShared.iMutex.Wait();
    TRAPD(err, InitDevSoundL(EMMFStateRecording, iShared.iPriority,
            iShared.iPreference));
    iShared.iMutex.Signal();

    if (err != KErrNone)
        {
        SendCmd(ECmdDnLinkError, err);
        }
    iMaxBufLen = DetermineMaxBufferLen();

    // Client must set these before querying!
    iG711CodecMode = CVoIPFormatIntfc::EG711ALaw;
    iILBCCodecMode = CVoIPFormatIntfc::EiLBC20mSecFrame;

    TRACE_PRN_FN_EXT;
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::ThreadFunction
// Thread Startup function
// -----------------------------------------------------------------------------
//
TInt CVoIPUplinkThread::ThreadFunction(TAny* aData)
    {
    // get a pointer to the shared data object
    TSharedData& shared = *((TSharedData*) aData);

    // we can set the sync flag here
    shared.iMutex.Wait();

    // create a cleanup stack
    CTrapCleanup* cleanupStack = CTrapCleanup::New();

    if (!cleanupStack)
        {
        shared.iMutex.Signal();
        return KErrNoMemory;
        }

    CVoIPUplinkThread* thread = 0;
    TRAPD(err, thread = CVoIPUplinkThread::NewL(shared));
    if (err != KErrNone)
        {
        shared.iMutex.Signal();
        return err;
        }

    shared.iMutex.Signal();

    // if we're still here, active scheduler has been constructed
    // start wait loop which runs until it's time to end the thread
    CActiveScheduler::Start();

    // Termination cleanup
    delete thread;
    delete cleanupStack;

    TRACE_PRN_N(_L("CVoIPUplinkThread::ThreadFunction : Thread CLOSED"));
    return KErrNone;
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::Start
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::Start()
    {
    TRACE_PRN_FN_ENT;

    TInt err = KErrNotReady;

    if (iStatus == EReady)
        {
        TRAP(err, iDevSound->RecordInitL());
        TRACE_PRN_IF_ERR(err);

        if (err != KErrNone)
            {
            SendCmd(ECmdUpLinkError, err);
            iStatus = EReady;
            }
#ifdef _DEBUG
        else
            {
            iSamplesRecCount = 0;
            }
#endif
        }

    TRACE_PRN_FN_EXT;
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::Stop
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::Stop()
    {
    TRACE_PRN_FN_ENT;

    if (iStatus == EStreaming)
        {
        iDevSound->Stop();
        iStatus = EReady;
        }

    TRACE_PRN_FN_EXT;
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::InitializeComplete
// A callback from the DevSound indicating completion of the initialization.
// It will send config data to the D/S and configure the encoder via CI.
// If everything goes well, the state of the thread is set EReady.
// The initialization completion message is sent back to the main thread.
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::InitializeComplete(TInt aError)
    {
    TRACE_PRN_FN_ENT;

    TInt err = aError;

    if (err == KErrNone)
        {
        TMMFCapabilities conf;
        conf = iDevSound->Config();
        conf.iRate = EMMFSampleRate8000Hz;
        conf.iChannels = EMMFMono;
        TRAP(err, iDevSound->SetConfigL(conf));
        if (err == KErrNone)
            {
            // We are ready to stream even in case of later CI setting failure
            iStatus = EReady;
            TInt gain = iDevSound->MaxGain();
            iShared.iMutex.Wait();
            iShared.iMaxGain = gain;
            iShared.iMutex.Signal();
            }

        // Init Custom Interface API to the Encoder
        TRAPD(err0, SetCodecCiL());
        if (err0 != KErrNone)
            {
            // DEBUG only
            // Can ignore error - the encoder is not fully configured but
            // can still run in the default mode.
            TRACE_PRN_IF_ERR(err0);
            }
        }

    // Notify the main thread
    SendCmd(ECmdUplinkInitComplete, err);

    TRACE_PRN_IF_ERR(err);
    TRACE_PRN_FN_EXT;
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::BufferToBeEmptied
// From MDevSoundObserver
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::BufferToBeEmptied(CMMFBuffer* aBuffer)
    {
    //    TRACE_PRN_N1(_L("SAMPLES-RECORDED [%d]"), iSamplesRecCount++);

    iDevSoundBufPtr = static_cast<CMMFDataBuffer*> (aBuffer);
    TDes8& data = iDevSoundBufPtr->Data();

#ifndef __WINSCW__
    iBufLen = iDevSoundBufPtr->BufferSize();
#else //__WINSCW__
    // Buffer size is always 4kB in WINS (PCM). Since we are testing stub
    // adaptaions of all other supported codecs, and WINS playback is not
    // supported anyway, we can throw away generated frames and use only
    // up to the max length of other codecs just to keep the loopback alive.
    iBufLen = iMaxBufLen;
    data.SetLength(iBufLen);
#endif //__WINSCW__

    TRACE_PRN_N1(_L("VoIP->UPL: BTBE->LEN [%d]"), iBufLen);

    // Adjust/create RChunk if necessary
    TInt err = DoChunk(KChunkUPL, iBufLen, iMaxBufLen);

    if (err != KErrNone)
        {
        Stop();
        iMsgBuffer.iStatus = err;
        }
    else
        {
        // Pass buffer parameters to the client
        iMsgBuffer.iStatus = iChunk.Handle();
        iMsgBuffer.iInt = iBufLen;

        // Copy data over to RChunk
        TPtr8 dataPtr(iChunk.Base(), iBufLen, iMaxBufLen);
        dataPtr = data;
        iStatus = EStreaming;
        }

    // Notify client there is buffer ready to be emptied
    iMsgBuffer.iRequest = ECmdEmptyBuffer;
    iMsgQueue.Send(iMsgBuffer);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::BufferEmptied
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::BufferEmptied()
    {
    //    TRACE_PRN_N(_L("VoIP->UPL: BE"));

    iDevSound->RecordData();
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::RecordError
// From MDevSoundObserver
// Recording error is send to the main thread.
// The state of recorder is rolled back to EReady.
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::RecordError(TInt aError)
    {
    TRACE_PRN_IF_ERR(aError);

#ifdef _DEBUG
    iSamplesRecCount = 0;
#endif
    iStatus = EReady;
    SendCmd(ECmdUpLinkError, aError);
    }

// ---------------------------------------------------------------------------
// CVoIPUplinkThread::SetCodecCiL
//
// ---------------------------------------------------------------------------
//
void CVoIPUplinkThread::SetCodecCiL()
    {
    TRACE_PRN_FN_ENT;

    switch (iCodecID)
        {
        case KMccFourCCIdG711:
            {
            if (!iG711EncoderIntfc)
                {
                iG711EncoderIntfc = CG711EncoderIntfc::NewL(*iDevSound);
                }
            break;
            }
        case KMccFourCCIdG729:
            {
            if (!iG729EncoderIntfc)
                {
                iG729EncoderIntfc = CG729EncoderIntfc::NewL(*iDevSound);
                }
            break;
            }
        case KMccFourCCIdILBC:
            {
            if (!iIlbcEncoderIntfc)
                {
                iIlbcEncoderIntfc = CIlbcEncoderIntfc::NewL(*iDevSound);
                }
            break;
            }
        default:
            {
            break;
            }
        }

    if (!iSpeechEncoderConfig && iCodecID != KMMFFourCCCodePCM16)
        {
        iSpeechEncoderConfig = CSpeechEncoderConfig::NewL(*iDevSound);
        }

    TRACE_PRN_FN_EXT;
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::SetGain
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::SetGain()
    {
    iShared.iMutex.Wait();
    TInt gain = iShared.iInt;
    iShared.iMutex.Signal();
    iDevSound->SetGain(gain);
    TRACE_PRN_N1(_L("VoIP->UPL: SetGain [%d]"), gain);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::GetGain
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::GetGain()
    {
    TInt gain = iDevSound->Gain();
    iShared.iMutex.Wait();
    iShared.iInt = gain;
    iShared.iMutex.Signal();
    SendCmd(ECmdGetGainComplete);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::SetIlbcCodecMode
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::SetIlbcCodecMode()
    {
    TInt err = KErrNotSupported;

    if (iStatus == EReady)
        {
        iShared.iMutex.Wait();
        iILBCCodecMode = iShared.iCodecSettings.iILBCCodecMode;
        iShared.iMutex.Signal();

        if (iIlbcEncoderIntfc && iCodecID == KMccFourCCIdILBC)
            {
            if (iILBCCodecMode == CVoIPFormatIntfc::EiLBC20mSecFrame)
                {
                err = iIlbcEncoderIntfc->SetEncoderMode(
                        CIlbcEncoderIntfc::E20msFrame);
                TRACE_PRN_0(
                        _L("VoIP->CVoIPUplinkThread[0x%x]::SetIlbcCodecMode [20ms Frame]"));
                }
            else
                {
                if (iILBCCodecMode == CVoIPFormatIntfc::EiLBC30mSecFrame)
                    {
                    err = iIlbcEncoderIntfc->SetEncoderMode(
                            CIlbcEncoderIntfc::E30msFrame);
                    TRACE_PRN_0(
                            _L("VoIP->CVoIPUplinkThread[0x%x]::SetIlbcCodecMode [30ms Frame]"));
                    }
                }
            }
        }

    if (err != KErrNone)
        {
        SendCmd(ECmdUpLinkError, err);
        }

    TRACE_PRN_IF_ERR(err);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::GetIlbcCodecMode
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::GetIlbcCodecMode()
    {
    // not available through CIs -> return local value
    iShared.iMutex.Wait();
    iShared.iCodecSettings.iILBCCodecMode = iILBCCodecMode;
    iShared.iMutex.Signal();
    SendCmd(ECmdGetIlbcCodecModeComplete);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::SetG711CodecMode
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::SetG711CodecMode()
    {
    TInt err = KErrNotSupported;

    if (iStatus == EReady)
        {
        iShared.iMutex.Wait();
        iG711CodecMode = iShared.iCodecSettings.iG711CodecMode;
        iShared.iMutex.Signal();

        if (iG711EncoderIntfc && iCodecID == KMccFourCCIdG711)
            {
            if (iG711CodecMode == CVoIPFormatIntfc::EG711ALaw)
                {
                err = iG711EncoderIntfc->SetEncoderMode(
                        CG711EncoderIntfc::EEncALaw);
                TRACE_PRN_0(
                        _L("VoIP->CVoIPUplinkThread[0x%x]::SetG711CodecMode [ALaw]"));
                }
            else
                {
                if (iG711CodecMode == CVoIPFormatIntfc::EG711uLaw)
                    {
                    err = iG711EncoderIntfc->SetEncoderMode(
                            CG711EncoderIntfc::EEncULaw);
                    TRACE_PRN_0(
                            _L("VoIP->CVoIPUplinkThread[0x%x]::SetG711CodecMode [uLaw]"));
                    }
                }
            }
        }

    if (err != KErrNone)
        {
        SendCmd(ECmdUpLinkError, err);
        }

    TRACE_PRN_IF_ERR(err);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::GetG711CodecMode
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::GetG711CodecMode()
    {
    // not available through CIs -> return local value
    iShared.iMutex.Wait();
    iShared.iCodecSettings.iG711CodecMode = iG711CodecMode;
    iShared.iMutex.Signal();
    SendCmd(ECmdGetG711CodecModeComplete);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::GetSupportedBitrates
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::GetSupportedBitrates()
    {
    TInt err = KErrNotSupported;

    if (iSpeechEncoderConfig)
        {
        RArray<TUint> bitrates;
        err = iSpeechEncoderConfig->GetSupportedBitrates(bitrates);
        iShared.iMutex.Wait();
        iShared.iCodecSettings.iArrBitrates = bitrates;
        iShared.iMutex.Signal();
        }

    SendCmd(ECmdGetSupportedBitratesComplete, err);
    TRACE_PRN_IF_ERR(err);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::SetBitrate
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::SetBitrate()
    {
    TInt err = KErrNotSupported;
    iShared.iMutex.Wait();
    TUint bitrate = iShared.iCodecSettings.iBitrate;
    iShared.iMutex.Signal();

    if (iSpeechEncoderConfig)
        {
        err = iSpeechEncoderConfig->SetBitrate(bitrate);
        TRACE_PRN_N1(_L("VoIP->UPL: SetBitrate [%d]"), bitrate);
        }

    if (err != KErrNone)
        {
        SendCmd(ECmdUpLinkError, err);
        }

    TRACE_PRN_IF_ERR(err);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::GetBitrate
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::GetBitrate()
    {
    TInt err = KErrNotSupported;
    TUint bitrate = 0;

    if (iSpeechEncoderConfig)
        {
        err = iSpeechEncoderConfig->GetBitrate(bitrate);
        }

    iShared.iMutex.Wait();
    iShared.iCodecSettings.iBitrate = bitrate;
    iShared.iMutex.Signal();
    SendCmd(ECmdGetBitrateComplete, err);
    TRACE_PRN_IF_ERR(err);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::SetVad
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::SetVad()
    {
    TInt err = KErrNotSupported;
    iShared.iMutex.Wait();
    TBool vad = iShared.iCodecSettings.iVad;
    iShared.iMutex.Signal();

    switch (iCodecID)
        {
        case KMccFourCCIdG711:
            {
            if (iG711EncoderIntfc)
                {
                err = iG711EncoderIntfc->SetVadMode(vad);
                }
            break;
            }
        case KMccFourCCIdG729:
            {
            if (iG729EncoderIntfc)
                {
                err = iG729EncoderIntfc->SetVadMode(vad);
                }
            break;
            }
        case KMccFourCCIdILBC:
            {
            if (iIlbcEncoderIntfc)
                {
                err = iIlbcEncoderIntfc->SetVadMode(vad);
                }
            break;
            }
        case KMccFourCCIdAMRNB:
            {
            if (iSpeechEncoderConfig)
                {
                err = iSpeechEncoderConfig->SetVadMode(vad);
                }
            break;
            }
        default:
            {
            break; //KErrNotSupported
            }
        }

    if (err != KErrNone)
        {
        SendCmd(ECmdUpLinkError, err);
        }

    TRACE_PRN_IF_ERR(err);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::GetVad
//
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::GetVad()
    {
    TInt err = KErrNotSupported;
    TBool vad = EFalse;

    switch (iCodecID)
        {
        case KMccFourCCIdG711:
            {
            if (iG711EncoderIntfc)
                {
                err = iG711EncoderIntfc->GetVadMode(vad);
                }
            break;
            }
        case KMccFourCCIdG729:
            {
            if (iG729EncoderIntfc)
                {
                err = iG729EncoderIntfc->GetVadMode(vad);
                }
            break;
            }
        case KMccFourCCIdILBC:
            {
            if (iIlbcEncoderIntfc)
                {
                err = iIlbcEncoderIntfc->GetVadMode(vad);
                }
            break;
            }
        case KMccFourCCIdAMRNB:
            {
            if (iSpeechEncoderConfig)
                {
                err = iSpeechEncoderConfig->GetVadMode(vad);
                }
            break;
            }
        default:
            {
            break; //KErrNotSupported
            }
        }

    iShared.iMutex.Wait();
    iShared.iCodecSettings.iVad = vad;
    iShared.iMutex.Signal();
    SendCmd(ECmdGetVadComplete, err);
    TRACE_PRN_IF_ERR(err);
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread::SendCmd
// Completes active object's request and sets shared data command value to
// one of the user requested commands.
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::SendCmd(TUserCommand aCmd, TInt aError)
    {
    iShared.iMutex.Wait();

    iShared.iCmd = aCmd;
    TRequestStatus* status = iShared.iMnThreadStatus;

    if (status)
        {
        if (status->Int() == KRequestPending)
            {
            RThread t;
            TInt err = t.Open(iShared.iMainThreadID);
            if (err == KErrNone)
                {
                t.RequestComplete(status, aError);
                }
            }
        }

    iShared.iMutex.Signal();
    }

// -----------------------------------------------------------------------------
// CVoIPUplinkThread
// From MQueueHandlerObserverSrv
// -----------------------------------------------------------------------------
//
void CVoIPUplinkThread::Event(TInt aEventType, TInt /*aError*/)
    {
    switch (aEventType)
        {
        case ECmdStartUplink:
            {
            Start();
            break;
            }
        case ECmdStopUplink:
            {
            if (iStatus == EStreaming)
                {
                Stop();
                }
            break;
            }
        case ECmdBufferEmptied:
            {
            BufferEmptied();
            break;
            }
        case ECmdGetGain:
            {
            GetGain();
            break;
            }
        case ECmdSetGain:
            {
            SetGain();
            break;
            }
        case ECmdSetIlbcCodecMode:
            {
            SetIlbcCodecMode();
            break;
            }
        case ECmdGetIlbcCodecMode:
            {
            GetIlbcCodecMode();
            break;
            }
        case ECmdSetG711CodecMode:
            {
            SetG711CodecMode();
            break;
            }
        case ECmdGetG711CodecMode:
            {
            GetG711CodecMode();
            break;
            }
        case ECmdGetSupportedBitrates:
            {
            GetSupportedBitrates();
            break;
            }
        case ECmdSetBitrate:
            {
            SetBitrate();
            break;
            }
        case ECmdGetBitrate:
            {
            GetBitrate();
            break;
            }
        case ECmdSetVad:
            {
            SetVad();
            break;
            }
        case ECmdGetVad:
            {
            GetVad();
            break;
            }
        case ECmdTerminateThread:
        default:
            {
            if (iStatus == EStreaming)
                {
                Stop();
                }
            // if unknown exception is raised, just exit this thread
            CActiveScheduler::Stop();
            break;
            }
        }
    }

// End of file
