/*
**
** Copyright 2008, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

//#define LOG_NDEBUG 0
#define LOG_TAG "AudioOutput"
#include <utils/Log.h>

#include "android_audio_output.h"

#include <sys/prctl.h>
#include <sys/resource.h>
#include <utils/threads.h>
#include <media/AudioTrack.h>

using namespace android;

// TODO: dynamic buffer count based on sample rate and # channels
static const int kNumOutputBuffers = 4;

// maximum allowed clock drift before correction
static const int32 kMaxClockDriftInMsecs = 25;    // should be tight enough for reasonable sync
static const int32 kMaxClockCorrection = 100;     // maximum clock correction per update

/*
/ Packet Video Audio MIO component
/
/ This implementation routes audio to AudioFlinger. Audio buffers are
/ enqueued in a message queue to a separate audio output thread. Once
/ the buffers have been successfully written, they are returned through
/ another message queue to the MIO and from there back to the engine.
/ This separation is necessary because most of the PV API is not
/ thread-safe.
*/
OSCL_EXPORT_REF AndroidAudioOutput::AndroidAudioOutput() :
        AndroidAudioMIO("AndroidAudioOutput"),
        iExitAudioThread(false),
        iReturnBuffers(false),
        iActiveTiming(NULL)
{
    LOGV("constructor");
    iClockTimeOfWriting_ns = 0;
    iInputFrameSizeInBytes = 0;

    // semaphore used to communicate between this  mio and the audio output thread
    iAudioThreadSem = new OsclSemaphore();
    iAudioThreadSem->Create(0);
    iAudioThreadTermSem = new OsclSemaphore();
    iAudioThreadTermSem->Create(0);
    iAudioThreadReturnSem = new OsclSemaphore();
    iAudioThreadReturnSem->Create(0);
    iAudioThreadCreatedSem = new OsclSemaphore();
    iAudioThreadCreatedSem->Create(0);

    // locks to access the queues by this mio and by the audio output thread
    iOSSRequestQueueLock.Create();
    iOSSRequestQueue.reserve(iWriteResponseQueue.capacity());

    // create active timing object
    OsclMemAllocator alloc;
    OsclAny*ptr = alloc.allocate(sizeof(AndroidAudioMIOActiveTimingSupport));
    if (ptr)
    {
        iActiveTiming = new(ptr)AndroidAudioMIOActiveTimingSupport(kMaxClockDriftInMsecs, kMaxClockCorrection);
        iActiveTiming->setThreadSemaphore(iAudioThreadSem);
    }
}

OSCL_EXPORT_REF AndroidAudioOutput::~AndroidAudioOutput()
{
    LOGV("destructor");

    // make sure output thread has exited
    RequestAndWaitForThreadExit();

    // cleanup active timing object
    if (iActiveTiming)
    {
        iActiveTiming->~AndroidAudioMIOActiveTimingSupport();
        OsclMemAllocator alloc;
        alloc.deallocate(iActiveTiming);
    }

    // clean up some thread interface objects
    iAudioThreadSem->Close();
    delete iAudioThreadSem;
    iAudioThreadTermSem->Close();
    delete iAudioThreadTermSem;
    iAudioThreadReturnSem->Close();
    delete iAudioThreadReturnSem;
    iAudioThreadCreatedSem->Close();
    delete iAudioThreadCreatedSem;

    iOSSRequestQueueLock.Close();
}

PVMFCommandId AndroidAudioOutput::QueryInterface(const PVUuid& aUuid, PVInterface*& aInterfacePtr, const OsclAny* aContext)
{
    LOGV("QueryInterface in");
    // check for active timing extension
    if (iActiveTiming && (aUuid == PvmiClockExtensionInterfaceUuid))
    {
        PvmiClockExtensionInterface* myInterface = OSCL_STATIC_CAST(PvmiClockExtensionInterface*, iActiveTiming);
        aInterfacePtr = OSCL_STATIC_CAST(PVInterface*, myInterface);
        return QueueCmdResponse(PVMFSuccess, aContext);
    }

    // pass to base class
    else return AndroidAudioMIO::QueryInterface(aUuid, aInterfacePtr, aContext);
}

PVMFCommandId AndroidAudioOutput::Stop(const OsclAny* aContext)
{
    LOGV("AndroidAudioOutput Stop (%p)", aContext);
    // return all buffer by writecomplete
    returnAllBuffers();
    return AndroidAudioMIO::Stop(aContext);
}

PVMFCommandId AndroidAudioOutput::Reset(const OsclAny* aContext)
{
    LOGV("AndroidAudioOutput Reset (%p)", aContext);
    // return all buffer by writecomplete
    returnAllBuffers();
    // request output thread to exit
    RequestAndWaitForThreadExit();
    return AndroidAudioMIO::Reset(aContext);
}

void AndroidAudioOutput::cancelCommand(PVMFCommandId command_id)
{
    LOGV("cancelCommand (%u) RequestQ size %d", command_id, iOSSRequestQueue.size());
    iOSSRequestQueueLock.Lock();
    for (uint32 i = 0; i < iOSSRequestQueue.size(); i++)
    {
        if (iOSSRequestQueue[i].iCmdId == command_id)
        {
            iDataQueued -= iOSSRequestQueue[i].iDataLen;
            if (iPeer)
                iPeer->writeComplete(PVMFSuccess, iOSSRequestQueue[i].iCmdId, (OsclAny*)iOSSRequestQueue[i].iContext);
            iOSSRequestQueue.erase(&iOSSRequestQueue[i]);
            break;
        }
    }
    iOSSRequestQueueLock.Unlock();
    LOGV("cancelCommand data queued = %u", iDataQueued);

    ProcessWriteResponseQueue();
}

void AndroidAudioOutput::returnAllBuffers()
{
    LOGV("returnAllBuffers RequestQ size %d", iOSSRequestQueue.size());
    iOSSRequestQueueLock.Lock();
    while (iOSSRequestQueue.size())
    {
        iDataQueued -= iOSSRequestQueue[0].iDataLen;
        if (iPeer)
            iPeer->writeComplete(PVMFSuccess, iOSSRequestQueue[0].iCmdId, (OsclAny*)iOSSRequestQueue[0].iContext);
        iOSSRequestQueue.erase(&iOSSRequestQueue[0]);
    }
    iOSSRequestQueueLock.Unlock();
    LOGV("returnAllBuffers data queued = %u", iDataQueued);
    if (iAudioThreadSem && iAudioThreadCreatedAndMIOConfigured)
    {
        LOGV("signal thread to return buffers");
        iReturnBuffers = true;
        iAudioThreadSem->Signal();
        while (iAudioThreadReturnSem->Wait() != OsclProcStatus::SUCCESS_ERROR)
            ;
        LOGV("return buffers signal completed");
    }
}


PVMFCommandId AndroidAudioOutput::DiscardData(PVMFTimestamp aTimestamp, const OsclAny* aContext)
{
    LOGV("DiscardData timestamp(%u) RequestQ size %d", aTimestamp, iOSSRequestQueue.size());

    if (iActiveTiming)
    {
        LOGV("Force clock update");
        iActiveTiming->ForceClockUpdate();
    }

    bool sched = false;
    PVMFCommandId audcmdid;
    const OsclAny* context;
    PVMFTimestamp timestamp;

    // the OSSRequest queue should be drained
    // all the buffers in them should be returned to the engine
    // writeComplete cannot be called from here
    // thus the best way is to queue the buffers onto the write response queue
    // and then call RunIfNotReady
    iOSSRequestQueueLock.Lock();
    for (int32 i = (iOSSRequestQueue.size() - 1); i >= 0; i--)
    {
        if (iOSSRequestQueue[i].iTimestamp < aTimestamp)
        {
            audcmdid = iOSSRequestQueue[i].iCmdId;
            context = iOSSRequestQueue[i].iContext;
            timestamp = iOSSRequestQueue[i].iTimestamp;
            iDataQueued -= iOSSRequestQueue[i].iDataLen;
            LOGV("discard buffer (%d) context(%p) timestamp(%u) Datalen(%d)", audcmdid, context, timestamp, iOSSRequestQueue[i].iDataLen);
            iOSSRequestQueue.erase(&iOSSRequestQueue[i]);
            sched = true;

            WriteResponse resp(PVMFSuccess, audcmdid, context, timestamp);
            iWriteResponseQueueLock.Lock();
            iWriteResponseQueue.push_back(resp);
            iWriteResponseQueueLock.Unlock();
        }
    }
    LOGV("DiscardData data queued = %u, setting flush pending", iDataQueued);
    iFlushPending = true;

    iOSSRequestQueueLock.Unlock();

    if (sched)
        RunIfNotReady();

    return AndroidAudioMIO::DiscardData(aTimestamp, aContext);
}

void AndroidAudioOutput::RequestAndWaitForThreadExit()
{
    LOGV("RequestAndWaitForThreadExit In");
    if (iAudioThreadSem && iAudioThreadCreatedAndMIOConfigured)
    {
        LOGV("signal thread for exit");
        iExitAudioThread = true;
        iAudioThreadSem->Signal();
        while (iAudioThreadTermSem->Wait() != OsclProcStatus::SUCCESS_ERROR)
            ;
        LOGV("thread term signal received");
        iAudioThreadCreatedAndMIOConfigured = false;
    }
}

void AndroidAudioOutput::setParametersSync(PvmiMIOSession aSession, PvmiKvp* aParameters,
        int num_elements, PvmiKvp * & aRet_kvp)
{
    LOGV("AndroidAudioOutput setParametersSync In");
    AndroidAudioMIO::setParametersSync(aSession, aParameters, num_elements, aRet_kvp);

    // initialize thread when we have enough information
    if (iAudioSamplingRateValid && iAudioNumChannelsValid && iAudioFormat != PVMF_MIME_FORMAT_UNKNOWN)
    {
        LOGV("start audio thread");
        OsclThread AudioOutput_Thread;
        iExitAudioThread = false;
        iReturnBuffers = false;
        OsclProcStatus::eOsclProcError ret = AudioOutput_Thread.Create((TOsclThreadFuncPtr)start_audout_thread_func,
                                             0, (TOsclThreadFuncArg)this, Start_on_creation);

        //Don't signal the MIO node that the configuration is complete until the driver latency has been set
        while (iAudioThreadCreatedSem->Wait() != OsclProcStatus::SUCCESS_ERROR)
            ;

        if (OsclProcStatus::SUCCESS_ERROR == ret)
        {
            iAudioThreadCreatedAndMIOConfigured = true;
            if (iObserver)
            {
                LOGV("event PVMFMIOConfigurationComplete to peer");
                iObserver->ReportInfoEvent(PVMFMIOConfigurationComplete);
            }
        }
        else
        {
            iAudioThreadCreatedAndMIOConfigured = false;
            if (iObserver)
            {
                LOGE("event PVMFErrResourceConfiguration to peer");
                iObserver->ReportErrorEvent(PVMFErrResourceConfiguration);
            }
        }
    }
    LOGV("AndroidAudioOutput setParametersSync out");
}

void AndroidAudioOutput::Run()
{
    // if running, update clock
    if ((iState == STATE_MIO_STARTED) && iInputFrameSizeInBytes)
    {
        uint32 msecsQueued = iDataQueued / iInputFrameSizeInBytes * iActiveTiming->msecsPerFrame();
        LOGV("%u msecs of data queued, %u bytes of data queued", msecsQueued, iDataQueued);
        iActiveTiming->UpdateClock();
    }
    AndroidAudioMIO::Run();
}

void AndroidAudioOutput::writeAudioBuffer(uint8* aData, uint32 aDataLen, PVMFCommandId cmdId, OsclAny* aContext, PVMFTimestamp aTimestamp)
{
    // queue up buffer and signal audio thread to process it
    LOGV("writeAudioBuffer :: DataLen(%d), cmdId(%d), Context(%p), Timestamp (%d)", aDataLen, cmdId, aContext, aTimestamp);
    OSSRequest req(aData, aDataLen, cmdId, aContext, aTimestamp);
    iOSSRequestQueueLock.Lock();
    iOSSRequestQueue.push_back(req);
    iDataQueued += aDataLen;

    // wake up the audio output thread to process this buffer only if clock has started running
    if (iActiveTiming->clockState() == PVMFMediaClock::RUNNING)
    {
        LOGV("clock is ticking signal thread for data");
        iAudioThreadSem->Signal();
    }
    iOSSRequestQueueLock.Unlock();
}

//------------------------------------------------------------------------
// audio thread
//

#undef LOG_TAG
#define LOG_TAG "audiothread"

// this is the audio output thread
// used to send data to the linux audio output device
// communicates with the audio MIO via a semaphore, a request queue and a response queue
/*static*/ int AndroidAudioOutput::start_audout_thread_func(TOsclThreadFuncArg arg)
{
    LOGV("start_audout_thread_func in");
    AndroidAudioOutput *obj = (AndroidAudioOutput *)arg;
    prctl(PR_SET_NAME, (unsigned long) "audio out", 0, 0, 0);
    int err = obj->audout_thread_func();
    LOGV("start_audout_thread_func out return code %d", err);
    return err;
}

int AndroidAudioOutput::audout_thread_func()
{
    enum { IDLE, STOPPED, STARTED, PAUSED } state = IDLE;
    int64_t lastClock = 0;

    // LOGD("audout_thread_func");

#if defined(HAVE_SCHED_SETSCHEDULER) && defined(HAVE_GETTID)
    setpriority(PRIO_PROCESS, gettid(), ANDROID_PRIORITY_AUDIO);
#endif

    if (iAudioNumChannelsValid == false || iAudioSamplingRateValid == false || iAudioFormat == PVMF_MIME_FORMAT_UNKNOWN)
    {
        LOGE("channel count or sample rate is invalid");
        return -1;
    }

    LOGV("Creating AudioTrack object: rate=%d, channels=%d, buffers=%d", iAudioSamplingRate, iAudioNumChannels, kNumOutputBuffers);
    status_t ret = mAudioSink->open(iAudioSamplingRate, iAudioNumChannels, ((iAudioFormat == PVMF_MIME_PCM8) ? AudioSystem::PCM_8_BIT : AudioSystem::PCM_16_BIT), kNumOutputBuffers);
    iAudioSamplingRateValid = false; // purpose of these flags is over here, reset these for next validation recording.
    iAudioNumChannelsValid  = false;
    iAudioFormat = PVMF_MIME_FORMAT_UNKNOWN;
    if (ret != 0)
    {
        iAudioThreadCreatedAndMIOConfigured = false;
        LOGE("Error creating AudioTrack");
        return -1;
    }

    // calculate timing data
    int outputFrameSizeInBytes = mAudioSink->frameSize();
    float msecsPerFrame = mAudioSink->msecsPerFrame();
    uint32 latency = mAudioSink->latency();
    LOGV("driver latency(%u),outputFrameSizeInBytes(%d),msecsPerFrame(%f),frame count(%d)", latency, outputFrameSizeInBytes, msecsPerFrame, mAudioSink->frameCount());

    // initialize active timing
    iActiveTiming->setFrameRate(msecsPerFrame);
    iActiveTiming->setDriverLatency(latency);

    iAudioThreadCreatedSem->Signal();
    // this must be set after iActiveTiming->setFrameRate to prevent race
    // condition in Run()
    iInputFrameSizeInBytes = outputFrameSizeInBytes;

    // buffer management
    uint32 bytesAvailInBuffer = 0;
    uint32 bytesToWrite;
    uint32 bytesWritten;
    uint8* data = 0;
    uint32 len = 0;
    PVMFCommandId cmdid = 0;
    const OsclAny* context = 0;
    PVMFTimestamp timestamp = 0;

    // wait for signal from MIO thread
    LOGV("wait for signal");
    iAudioThreadSem->Wait();
    LOGV("ready to work");

    while (1)
    {
        // if paused, stop the output track
        switch (iActiveTiming->clockState())
        {
            case PVMFMediaClock::RUNNING:
                // start output
                if (state != STARTED)
                {
                    if (iFlushPending)
                    {
                        LOGV("flush");
                        mAudioSink->flush();
                        iFlushPending = false;
                        bytesAvailInBuffer = 0;
                        iClockTimeOfWriting_ns = 0;
                        // discard partial buffer and send response to MIO
                        if (data && len)
                        {
                            LOGV("discard partial buffer and send response to MIO");
                            sendResponse(cmdid, context, timestamp);
                            data = 0;
                            len = 0;
                        }
                    }
                    if (iDataQueued || len)
                    {
                        LOGV("start");
                        mAudioSink->start();
                        state = STARTED;
                    }
                    else
                    {
                        LOGV("clock running and no data queued - don't start track");
                    }
                }
                else
                {
                    LOGV("audio sink already in started state");
                }
                break;
            case PVMFMediaClock::STOPPED:
                LOGV("clock has been stopped...");
            case PVMFMediaClock::PAUSED:
                if (state == STARTED)
                {
                    LOGV("pause");
                    mAudioSink->pause();
                }
                state = PAUSED;
                if (!iExitAudioThread && !iReturnBuffers)
                {
                    LOGV("wait");
                    iAudioThreadSem->Wait();
                    LOGV("awake");
                }
                break;
            default:
                break;
        }
        // if out of data, check the request queue
        if (len == 0)
        {
            //LOGV("no playable data, Request Q size %d",iOSSRequestQueue.size());
            iOSSRequestQueueLock.Lock();
            bool empty = iOSSRequestQueue.empty();
            if (!empty)
            {
                data = iOSSRequestQueue[0].iData;
                len = iOSSRequestQueue[0].iDataLen;
                cmdid = iOSSRequestQueue[0].iCmdId;
                context = iOSSRequestQueue[0].iContext;
                timestamp = iOSSRequestQueue[0].iTimestamp;
                iDataQueued -= len;
                iOSSRequestQueue.erase(&iOSSRequestQueue[0]);
                LOGV("receive buffer (%d), timestamp = %u data queued = %u", cmdid, timestamp, iDataQueued);
            }
            iOSSRequestQueueLock.Unlock();

            // if queue is empty, wait for more work
            // FIXME: Why do end up here so many times when stopping?
            if (empty && !iExitAudioThread && !iReturnBuffers)
            {
                LOGV("queue is empty, wait for more work");
                iAudioThreadSem->Wait();
            }

            // empty buffer means "End-Of-Stream" - send response to MIO
            else if (len == 0)
            {
                LOGV("EOS");
                state = STOPPED;
                mAudioSink->stop();
                if (!iExitAudioThread)
                {
                    nsecs_t interval_nanosec = 0; // Interval between last writetime and EOS processing time in nanosec
                    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
                    LOGV("now = %lld ,iClockTimeOfWriting_ns = %lld", now, iClockTimeOfWriting_ns);
                    if (now >= iClockTimeOfWriting_ns)
                    {
                        interval_nanosec = now - iClockTimeOfWriting_ns;
                    }
                    else  //when timevalue wraps
                    {
                        interval_nanosec = 0;
                    }
                    LOGV(" I am early,going for sleep for latency = %u millsec, interval_nanosec = %lld", latency, interval_nanosec);
                    struct timespec requested_time_delay, remaining;
                    requested_time_delay.tv_sec = latency / 1000;
                    nsecs_t latency_nanosec = (latency % 1000) * 1000 * 1000;
                    if (interval_nanosec < latency_nanosec)
                    {
                        requested_time_delay.tv_nsec = latency_nanosec - interval_nanosec;
                        nanosleep(&requested_time_delay, &remaining);
                        LOGV(" Wow, what a great siesta....send response to engine");
                    }
                    else // interval is greater than latency so no need of sleep
                    {
                        LOGV(" No time to sleep :( send response to engine anyways");
                    }
                    iClockTimeOfWriting_ns = 0;
                    sendResponse(cmdid, context, timestamp);
                }
            }
        }

        if (iReturnBuffers)
        {
            LOGV("Return buffers from the audio thread");
            if (len) sendResponse(cmdid, context, timestamp);
            iReturnBuffers = false;
            data = 0;
            len = 0;
            iAudioThreadReturnSem->Signal();
        }

        // check for exit signal
        if (iExitAudioThread)
        {
            LOGV("exit received");
            if (len) sendResponse(cmdid, context, timestamp);
            break;
        }

        // data to output?
        if (len && (state == STARTED) && !iExitAudioThread)
        {

            // always align to AudioFlinger buffer boundary
            if (bytesAvailInBuffer == 0)
                bytesAvailInBuffer = mAudioSink->bufferSize();

            bytesToWrite = bytesAvailInBuffer > len ? len : bytesAvailInBuffer;
            //LOGV("16 bit :: cmdid = %d, len = %u, bytesAvailInBuffer = %u, bytesToWrite = %u", cmdid, len, bytesAvailInBuffer, bytesToWrite);
            bytesWritten = mAudioSink->write(data, bytesToWrite);
            if (bytesWritten != bytesToWrite)
            {
                LOGE("Error writing audio data");
                iAudioThreadSem->Wait();
            }
            data += bytesWritten;
            len -= bytesWritten;
            iClockTimeOfWriting_ns = systemTime(SYSTEM_TIME_MONOTONIC);


            // count bytes sent
            bytesAvailInBuffer -= bytesWritten;

            // update frame count for latency calculation
            iActiveTiming->incFrameCount(bytesWritten / outputFrameSizeInBytes);
            //LOGV("outputFrameSizeInBytes = %u,bytesWritten = %u,bytesAvailInBuffer = %u", outputFrameSizeInBytes,bytesWritten,bytesAvailInBuffer);
            // if done with buffer - send response to MIO
            if (data && !len)
            {
                LOGV("done with the data cmdid %d, context %p, timestamp %d ", cmdid, context, timestamp);
                sendResponse(cmdid, context, timestamp);
                data = 0;
            }
        }
    } // while loop

    LOGV("stop and delete track");
    mAudioSink->stop();
    iClockTimeOfWriting_ns = 0;

    // LOGD("audout_thread_func exit");
    iAudioThreadTermSem->Signal();

    return 0;
}

