/*
 * Copyright (C) 2013 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.
 */

#include <inttypes.h>

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

#define STRINGIFY_ENUMS // for asString in HardwareAPI.h/VideoAPI.h

#include <media/stagefright/bqhelper/GraphicBufferSource.h>
#include <media/stagefright/bqhelper/FrameDropper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/ColorUtils.h>
#include <media/stagefright/foundation/FileDescriptor.h>

#include <android-base/properties.h>
#include <media/hardware/MetadataBufferType.h>
#include <ui/GraphicBuffer.h>
#include <gui/BufferItem.h>
#include <gui/BufferQueue.h>
#include <gui/bufferqueue/1.0/WGraphicBufferProducer.h>
#include <gui/bufferqueue/2.0/B2HGraphicBufferProducer.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/IGraphicBufferConsumer.h>
#include <media/hardware/HardwareAPI.h>

#include <inttypes.h>

#include <functional>
#include <memory>
#include <cmath>

namespace android {

namespace {
// kTimestampFluctuation is an upper bound of timestamp fluctuation from the
// source that GraphicBufferSource allows. The unit of kTimestampFluctuation is
// frames. More specifically, GraphicBufferSource will drop a frame if
//
// expectedNewFrametimestamp - actualNewFrameTimestamp <
//     (0.5 - kTimestampFluctuation) * expectedtimePeriodBetweenFrames
//
// where
// - expectedNewFrameTimestamp is the calculated ideal timestamp of the new
//   incoming frame
// - actualNewFrameTimestamp is the timestamp received from the source
// - expectedTimePeriodBetweenFrames is the ideal difference of the timestamps
//   of two adjacent frames
//
// See GraphicBufferSource::calculateCodecTimestamp_l() for more detail about
// how kTimestampFluctuation is used.
//
// kTimestampFluctuation should be non-negative. A higher value causes a smaller
// chance of dropping frames, but at the same time a higher bound on the
// difference between the source timestamp and the interpreted (snapped)
// timestamp.
//
// The value of 0.05 means that GraphicBufferSource expects the input timestamps
// to fluctuate no more than 5% from the regular time period.
//
// TODO: Justify the choice of this value, or make it configurable.
constexpr double kTimestampFluctuation = 0.05;
}

/**
 * A copiable object managing a buffer in the buffer cache managed by the producer. This object
 * holds a reference to the buffer, and maintains which buffer slot it belongs to (if any), and
 * whether it is still in a buffer slot. It also maintains whether there are any outstanging acquire
 * references to it (by buffers acquired from the slot) mainly so that we can keep a debug
 * count of how many buffers we need to still release back to the producer.
 */
struct GraphicBufferSource::CachedBuffer {
    /**
     * Token that is used to track acquire counts (as opposed to all references to this object).
     */
    struct Acquirable { };

    /**
     * Create using a buffer cached in a slot.
     */
    CachedBuffer(slot_id slot, const sp<GraphicBuffer> &graphicBuffer)
        : mIsCached(true),
          mSlot(slot),
          mGraphicBuffer(graphicBuffer),
          mAcquirable(std::make_shared<Acquirable>()) {
    }

    /**
     * Returns the cache slot that this buffer is cached in, or -1 if it is no longer cached.
     *
     * This assumes that -1 slot id is invalid; though, it is just a benign collision used for
     * debugging. This object explicitly manages whether it is still cached.
     */
    slot_id getSlot() const {
        return mIsCached ? mSlot : -1;
    }

    /**
     * Returns the cached buffer.
     */
    sp<GraphicBuffer> getGraphicBuffer() const {
        return mGraphicBuffer;
    }

    /**
     * Checks whether this buffer is still in the buffer cache.
     */
    bool isCached() const {
        return mIsCached;
    }

    /**
     * Checks whether this buffer has an acquired reference.
     */
    bool isAcquired() const {
        return mAcquirable.use_count() > 1;
    }

    /**
     * Gets and returns a shared acquired reference.
     */
    std::shared_ptr<Acquirable> getAcquirable() {
        return mAcquirable;
    }

private:
    friend void GraphicBufferSource::discardBufferAtSlotIndex_l(ssize_t);

    /**
     * This method to be called when the buffer is no longer in the buffer cache.
     * Called from discardBufferAtSlotIndex_l.
     */
    void onDroppedFromCache() {
        CHECK_DBG(mIsCached);
        mIsCached = false;
    }

    bool mIsCached;
    slot_id mSlot;
    sp<GraphicBuffer> mGraphicBuffer;
    std::shared_ptr<Acquirable> mAcquirable;
};

/**
 * A copiable object managing a buffer acquired from the producer. This must always be a cached
 * buffer. This objects also manages its acquire fence and any release fences that may be returned
 * by the encoder for this buffer (this buffer may be queued to the encoder multiple times).
 * If no release fences are added by the encoder, the acquire fence is returned as the release
 * fence for this - as it is assumed that noone waited for the acquire fence. Otherwise, it is
 * assumed that the encoder has waited for the acquire fence (or returned it as the release
 * fence).
 */
struct GraphicBufferSource::AcquiredBuffer {
    AcquiredBuffer(
            const std::shared_ptr<CachedBuffer> &buffer,
            std::function<void(AcquiredBuffer *)> onReleased,
            const sp<Fence> &acquireFence)
        : mBuffer(buffer),
          mAcquirable(buffer->getAcquirable()),
          mAcquireFence(acquireFence),
          mGotReleaseFences(false),
          mOnReleased(onReleased) {
    }

    /**
     * Adds a release fence returned by the encoder to this object. If this is called with an
     * valid file descriptor, it is added to the list of release fences. These are returned to the
     * producer on release() as a merged fence. Regardless of the validity of the file descriptor,
     * we take note that a release fence was attempted to be added and the acquire fence can now be
     * assumed as acquired.
     */
    void addReleaseFenceFd(int fenceFd) {
        // save all release fences - these will be propagated to the producer if this buffer is
        // ever released to it
        if (fenceFd >= 0) {
            mReleaseFenceFds.push_back(fenceFd);
        }
        mGotReleaseFences = true;
    }

    /**
     * Returns the acquire fence file descriptor associated with this object.
     */
    int getAcquireFenceFd() {
        if (mAcquireFence == nullptr || !mAcquireFence->isValid()) {
            return -1;
        }
        return mAcquireFence->dup();
    }

    /**
     * Returns whether the buffer is still in the buffer cache.
     */
    bool isCached() const {
        return mBuffer->isCached();
    }

    /**
     * Returns the acquired buffer.
     */
    sp<GraphicBuffer> getGraphicBuffer() const {
        return mBuffer->getGraphicBuffer();
    }

    /**
     * Returns the slot that this buffer is cached at, or -1 otherwise.
     *
     * This assumes that -1 slot id is invalid; though, it is just a benign collision used for
     * debugging. This object explicitly manages whether it is still cached.
     */
    slot_id getSlot() const {
        return mBuffer->getSlot();
    }

    /**
     * Creates and returns a release fence object from the acquire fence and/or any release fences
     * added. If no release fences were added (even if invalid), returns the acquire fence.
     * Otherwise, it returns a merged fence from all the valid release fences added.
     */
    sp<Fence> getReleaseFence() {
        // If did not receive release fences, we assume this buffer was not consumed (it was
        // discarded or dropped). In this case release the acquire fence as the release fence.
        // We do this here to avoid a dup, close and recreation of the Fence object.
        if (!mGotReleaseFences) {
            return mAcquireFence;
        }
        sp<Fence> ret = getReleaseFence(0, mReleaseFenceFds.size());
        // clear fds as fence took ownership of them
        mReleaseFenceFds.clear();
        return ret;
    }

    // this video buffer is no longer referenced by the codec (or kept for later encoding)
    // it is now safe to release to the producer
    ~AcquiredBuffer() {
        //mAcquirable.clear();
        mOnReleased(this);
        // mOnRelease method should call getReleaseFence() that releases all fds but just in case
        ALOGW_IF(!mReleaseFenceFds.empty(), "release fences were not obtained, closing fds");
        for (int fildes : mReleaseFenceFds) {
            ::close(fildes);
            TRESPASS_DBG();
        }
    }

private:
    std::shared_ptr<GraphicBufferSource::CachedBuffer> mBuffer;
    std::shared_ptr<GraphicBufferSource::CachedBuffer::Acquirable> mAcquirable;
    sp<Fence> mAcquireFence;
    Vector<int> mReleaseFenceFds;
    bool mGotReleaseFences;
    std::function<void(AcquiredBuffer *)> mOnReleased;

    /**
     * Creates and returns a release fence from 0 or more release fence file descriptors in from
     * the specified range in the array.
     *
     * @param start start index
     * @param num   number of release fds to merge
     */
    sp<Fence> getReleaseFence(size_t start, size_t num) const {
        if (num == 0) {
            return Fence::NO_FENCE;
        } else if (num == 1) {
            return new Fence(mReleaseFenceFds[start]);
        } else {
            return Fence::merge("GBS::AB",
                                getReleaseFence(start, num >> 1),
                                getReleaseFence(start + (num >> 1), num - (num >> 1)));
        }
    }
};

struct GraphicBufferSource::ConsumerProxy : public BufferQueue::ConsumerListener {
    ConsumerProxy(const wp<GraphicBufferSource> &gbs) : mGbs(gbs) {}

    ~ConsumerProxy() = default;

    void onFrameAvailable(const BufferItem& item) override {
        sp<GraphicBufferSource> gbs = mGbs.promote();
        if (gbs != nullptr) {
            gbs->onFrameAvailable(item);
        }
    }

    void onBuffersReleased() override {
        sp<GraphicBufferSource> gbs = mGbs.promote();
        if (gbs != nullptr) {
            gbs->onBuffersReleased();
        }
    }

    void onSidebandStreamChanged() override {
        sp<GraphicBufferSource> gbs = mGbs.promote();
        if (gbs != nullptr) {
            gbs->onSidebandStreamChanged();
        }
    }

private:
    // Note that GraphicBufferSource is holding an sp to us, we can't hold
    // an sp back to GraphicBufferSource as the circular dependency will
    // make both immortal.
    wp<GraphicBufferSource> mGbs;
};

GraphicBufferSource::GraphicBufferSource() :
    mInitCheck(UNKNOWN_ERROR),
    mNumAvailableUnacquiredBuffers(0),
    mNumOutstandingAcquires(0),
    mEndOfStream(false),
    mEndOfStreamSent(false),
    mLastDataspace(HAL_DATASPACE_UNKNOWN),
    mExecuting(false),
    mSuspended(false),
    mLastFrameTimestampUs(-1),
    mStopTimeUs(-1),
    mLastActionTimeUs(-1LL),
    mSkipFramesBeforeNs(-1LL),
    mFrameRepeatIntervalUs(-1LL),
    mRepeatLastFrameGeneration(0),
    mOutstandingFrameRepeatCount(0),
    mFrameRepeatBlockedOnCodecBuffer(false),
    mFps(-1.0),
    mCaptureFps(-1.0),
    mBaseCaptureUs(-1LL),
    mBaseFrameUs(-1LL),
    mFrameCount(0),
    mPrevCaptureUs(-1LL),
    mPrevFrameUs(-1LL),
    mInputBufferTimeOffsetUs(0LL) {
    ALOGV("GraphicBufferSource");

    String8 name("GraphicBufferSource");

    BufferQueue::createBufferQueue(&mProducer, &mConsumer);
    mConsumer->setConsumerName(name);

    // create the consumer listener interface, and hold sp so that this
    // interface lives as long as the GraphicBufferSource.
    mConsumerProxy = new ConsumerProxy(this);

    sp<IConsumerListener> proxy =
            new BufferQueue::ProxyConsumerListener(mConsumerProxy);

    mInitCheck = mConsumer->consumerConnect(proxy, false);
    if (mInitCheck != NO_ERROR) {
        ALOGE("Error connecting to BufferQueue: %s (%d)",
                strerror(-mInitCheck), mInitCheck);
        return;
    }

    memset(&mDefaultColorAspectsPacked, 0, sizeof(mDefaultColorAspectsPacked));

    CHECK(mInitCheck == NO_ERROR);
}

GraphicBufferSource::~GraphicBufferSource() {
    ALOGV("~GraphicBufferSource");
    {
        // all acquired buffers must be freed with the mutex locked otherwise our debug assertion
        // may trigger
        Mutex::Autolock autoLock(mMutex);
        mAvailableBuffers.clear();
        mSubmittedCodecBuffers.clear();
        mLatestBuffer.mBuffer.reset();
    }

    if (mNumOutstandingAcquires != 0) {
        ALOGW("potential buffer leak: acquired=%d", mNumOutstandingAcquires);
        TRESPASS_DBG();
    }
    if (mConsumer != NULL) {
        status_t err = mConsumer->consumerDisconnect();
        if (err != NO_ERROR) {
            ALOGW("consumerDisconnect failed: %d", err);
        }
    }
}

sp<IGraphicBufferProducer> GraphicBufferSource::getIGraphicBufferProducer() const {
    return mProducer;
}

sp<::android::hardware::graphics::bufferqueue::V1_0::IGraphicBufferProducer>
GraphicBufferSource::getHGraphicBufferProducer_V1_0() const {
    using TWGraphicBufferProducer = ::android::TWGraphicBufferProducer<
        ::android::hardware::graphics::bufferqueue::V1_0::IGraphicBufferProducer>;

    return new TWGraphicBufferProducer(getIGraphicBufferProducer());
}

sp<::android::hardware::graphics::bufferqueue::V2_0::IGraphicBufferProducer>
GraphicBufferSource::getHGraphicBufferProducer() const {
    return new ::android::hardware::graphics::bufferqueue::V2_0::utils::
                    B2HGraphicBufferProducer(getIGraphicBufferProducer());
}

status_t GraphicBufferSource::start() {
    Mutex::Autolock autoLock(mMutex);
    ALOGV("--> start; available=%zu, submittable=%zd",
            mAvailableBuffers.size(), mFreeCodecBuffers.size());
    CHECK(!mExecuting);
    mExecuting = true;
    mLastDataspace = HAL_DATASPACE_UNKNOWN;
    ALOGV("clearing last dataSpace");

    // Start by loading up as many buffers as possible.  We want to do this,
    // rather than just submit the first buffer, to avoid a degenerate case:
    // if all BQ buffers arrive before we start executing, and we only submit
    // one here, the other BQ buffers will just sit until we get notified
    // that the codec buffer has been released.  We'd then acquire and
    // submit a single additional buffer, repeatedly, never using more than
    // one codec buffer simultaneously.  (We could instead try to submit
    // all BQ buffers whenever any codec buffer is freed, but if we get the
    // initial conditions right that will never be useful.)
    while (haveAvailableBuffers_l()) {
        if (!fillCodecBuffer_l()) {
            ALOGV("stop load with available=%zu+%d",
                    mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
            break;
        }
    }

    ALOGV("done loading initial frames, available=%zu+%d",
            mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);

    // If EOS has already been signaled, and there are no more frames to
    // submit, try to send EOS now as well.
    if (mStopTimeUs == -1 && mEndOfStream && !haveAvailableBuffers_l()) {
        submitEndOfInputStream_l();
    }

    if (mFrameRepeatIntervalUs > 0LL && mLooper == NULL) {
        mReflector = new AHandlerReflector<GraphicBufferSource>(this);

        mLooper = new ALooper;
        mLooper->registerHandler(mReflector);
        mLooper->start();

        if (mLatestBuffer.mBuffer != nullptr) {
            queueFrameRepeat_l();
        }
    }

    return OK;
}

status_t GraphicBufferSource::stop() {
    ALOGV("stop");

    Mutex::Autolock autoLock(mMutex);

    if (mExecuting) {
        // We are only interested in the transition from executing->idle,
        // not loaded->idle.
        mExecuting = false;
    }
    return OK;
}

status_t GraphicBufferSource::release(){
    sp<ALooper> looper;
    {
        Mutex::Autolock autoLock(mMutex);
        looper = mLooper;
        if (mLooper != NULL) {
            mLooper->unregisterHandler(mReflector->id());
            mReflector.clear();

            mLooper.clear();
        }

        ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
                mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
                mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);

        // Codec is no longer executing.  Releasing all buffers to bq.
        mFreeCodecBuffers.clear();
        mSubmittedCodecBuffers.clear();
        mLatestBuffer.mBuffer.reset();
        mComponent.clear();
        mExecuting = false;
    }
    if (looper != NULL) {
        looper->stop();
    }
    return OK;
}

status_t GraphicBufferSource::onInputBufferAdded(codec_buffer_id bufferId) {
    Mutex::Autolock autoLock(mMutex);

    if (mExecuting) {
        // This should never happen -- buffers can only be allocated when
        // transitioning from "loaded" to "idle".
        ALOGE("addCodecBuffer: buffer added while executing");
        return INVALID_OPERATION;
    }

    ALOGV("addCodecBuffer: bufferId=%u", bufferId);

    mFreeCodecBuffers.push_back(bufferId);
    return OK;
}

status_t GraphicBufferSource::onInputBufferEmptied(codec_buffer_id bufferId, int fenceFd) {
    Mutex::Autolock autoLock(mMutex);
    FileDescriptor::Autoclose fence(fenceFd);

    ssize_t cbi = mSubmittedCodecBuffers.indexOfKey(bufferId);
    if (cbi < 0) {
        // This should never happen.
        ALOGE("onInputBufferEmptied: buffer not recognized (bufferId=%u)", bufferId);
        return BAD_VALUE;
    }

    std::shared_ptr<AcquiredBuffer> buffer = mSubmittedCodecBuffers.valueAt(cbi);

    // Move buffer to available buffers
    mSubmittedCodecBuffers.removeItemsAt(cbi);
    mFreeCodecBuffers.push_back(bufferId);

    // header->nFilledLen may not be the original value, so we can't compare
    // that to zero to see of this was the EOS buffer.  Instead we just
    // see if there is a null AcquiredBuffer, which should only ever happen for EOS.
    if (buffer == nullptr) {
        if (!(mEndOfStream && mEndOfStreamSent)) {
            // This can happen when broken code sends us the same buffer twice in a row.
            ALOGE("onInputBufferEmptied: non-EOS null buffer (bufferId=%u)", bufferId);
        } else {
            ALOGV("onInputBufferEmptied: EOS null buffer (bufferId=%u@%zd)", bufferId, cbi);
        }
        // No GraphicBuffer to deal with, no additional input or output is expected, so just return.
        return BAD_VALUE;
    }

    if (!mExecuting) {
        // this is fine since this could happen when going from Idle to Loaded
        ALOGV("onInputBufferEmptied: no longer executing (bufferId=%u@%zd)", bufferId, cbi);
        return OK;
    }

    ALOGV("onInputBufferEmptied: bufferId=%d@%zd [slot=%d, useCount=%ld, handle=%p] acquired=%d",
            bufferId, cbi, buffer->getSlot(), buffer.use_count(), buffer->getGraphicBuffer()->handle,
            mNumOutstandingAcquires);

    buffer->addReleaseFenceFd(fence.release());
    // release codec reference for video buffer just in case remove does not it
    buffer.reset();

    if (haveAvailableBuffers_l()) {
        // Fill this codec buffer.
        CHECK(!mEndOfStreamSent);
        ALOGV("onInputBufferEmptied: buffer freed, feeding codec (available=%zu+%d, eos=%d)",
                mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers, mEndOfStream);
        fillCodecBuffer_l();
    } else if (mEndOfStream && mStopTimeUs == -1) {
        // No frames available, but EOS is pending and no stop time, so use this buffer to
        // send that.
        ALOGV("onInputBufferEmptied: buffer freed, submitting EOS");
        submitEndOfInputStream_l();
    } else if (mFrameRepeatBlockedOnCodecBuffer) {
        bool success = repeatLatestBuffer_l();
        ALOGV("onInputBufferEmptied: completing deferred repeatLatestBuffer_l %s",
                success ? "SUCCESS" : "FAILURE");
        mFrameRepeatBlockedOnCodecBuffer = false;
    }

    // releaseReleasableBuffers_l();
    return OK;
}

void GraphicBufferSource::onDataspaceChanged_l(
        android_dataspace dataspace, android_pixel_format pixelFormat) {
    ALOGD("got buffer with new dataSpace %#x", dataspace);
    mLastDataspace = dataspace;

    if (ColorUtils::convertDataSpaceToV0(dataspace)) {
        mComponent->dispatchDataSpaceChanged(
                mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
    }
}

bool GraphicBufferSource::fillCodecBuffer_l() {
    CHECK(mExecuting && haveAvailableBuffers_l());

    if (mFreeCodecBuffers.empty()) {
        // No buffers available, bail.
        ALOGV("fillCodecBuffer_l: no codec buffers, available=%zu+%d",
                mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
        return false;
    }

    VideoBuffer item;
    if (mAvailableBuffers.empty()) {
        ALOGV("fillCodecBuffer_l: acquiring available buffer, available=%zu+%d",
                mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
        if (acquireBuffer_l(&item) != OK) {
            ALOGE("fillCodecBuffer_l: failed to acquire available buffer");
            return false;
        }
    } else {
        ALOGV("fillCodecBuffer_l: getting available buffer, available=%zu+%d",
                mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
        item = *mAvailableBuffers.begin();
        mAvailableBuffers.erase(mAvailableBuffers.begin());
    }

    int64_t itemTimeUs = item.mTimestampNs / 1000;

    // Process ActionItem in the Queue if there is any. If a buffer's timestamp
    // is smaller than the first action's timestamp, no action need to be performed.
    // If buffer's timestamp is larger or equal than the last action's timestamp,
    // only the last action needs to be performed as all the acitions before the
    // the action are overridden by the last action. For the other cases, traverse
    // the Queue to find the newest action that with timestamp smaller or equal to
    // the buffer's timestamp. For example, an action queue like
    // [pause 1us], [resume 2us], [pause 3us], [resume 4us], [pause 5us].... Upon
    // receiving a buffer with timestamp 3.5us, only the action [pause, 3us] needs
    // to be handled and [pause, 1us], [resume 2us] will be discarded.
    bool done = false;
    bool seeStopAction = false;
    if (!mActionQueue.empty()) {
        // First scan to check if bufferTimestamp is smaller than first action's timestamp.
        ActionItem nextAction = *(mActionQueue.begin());
        if (itemTimeUs < nextAction.mActionTimeUs) {
            ALOGV("No action. buffer timestamp %lld us < action timestamp: %lld us",
                (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
            // All the actions are ahead. No action need to perform now.
            // Release the buffer if is in suspended state, or process the buffer
            // if not in suspended state.
            done = true;
        }

        if (!done) {
            // Find the newest action that with timestamp smaller than itemTimeUs. Then
            // remove all the actions before and include the newest action.
            List<ActionItem>::iterator it = mActionQueue.begin();
            while (it != mActionQueue.end() && it->mActionTimeUs <= itemTimeUs
                    && nextAction.mAction != ActionItem::STOP) {
                nextAction = *it;
                ++it;
            }
            mActionQueue.erase(mActionQueue.begin(), it);

            CHECK(itemTimeUs >= nextAction.mActionTimeUs);
            switch (nextAction.mAction) {
                case ActionItem::PAUSE:
                {
                    mSuspended = true;
                    ALOGV("RUNNING/PAUSE -> PAUSE at buffer %lld us  PAUSE Time: %lld us",
                            (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
                    break;
                }
                case ActionItem::RESUME:
                {
                    mSuspended = false;
                    ALOGV("PAUSE/RUNNING -> RUNNING at buffer %lld us  RESUME Time: %lld us",
                            (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
                    break;
                }
                case ActionItem::STOP:
                {
                    ALOGV("RUNNING/PAUSE -> STOP at buffer %lld us  STOP Time: %lld us",
                            (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
                    // Clear the whole ActionQueue as recording is done
                    mActionQueue.clear();
                    seeStopAction = true;
                    break;
                }
                default:
                    TRESPASS_DBG("Unknown action type");
                    // return true here because we did consume an available buffer, so the
                    // loop in start will eventually terminate even if we hit this.
                    return false;
            }
        }
    }

    if (seeStopAction) {
        // Clear all the buffers before setting mEndOfStream and signal EndOfInputStream.
        releaseAllAvailableBuffers_l();
        mEndOfStream = true;
        submitEndOfInputStream_l();
        return true;
    }

    if (mSuspended) {
        return true;
    }

    int err = UNKNOWN_ERROR;

    // only submit sample if start time is unspecified, or sample
    // is queued after the specified start time
    if (mSkipFramesBeforeNs < 0LL || item.mTimestampNs >= mSkipFramesBeforeNs) {
        // if start time is set, offset time stamp by start time
        if (mSkipFramesBeforeNs > 0) {
            item.mTimestampNs -= mSkipFramesBeforeNs;
        }

        int64_t timeUs = item.mTimestampNs / 1000;
        if (mFrameDropper != NULL && mFrameDropper->shouldDrop(timeUs)) {
            ALOGV("skipping frame (%lld) to meet max framerate", static_cast<long long>(timeUs));
            // set err to OK so that the skipped frame can still be saved as the lastest frame
            err = OK;
        } else {
            err = submitBuffer_l(item); // this takes shared ownership of the acquired buffer on succeess
        }
    }

    if (err != OK) {
        ALOGV("submitBuffer_l failed, will release bq slot %d", item.mBuffer->getSlot());
        return true;
    } else {
        // Don't set the last buffer id if we're not repeating,
        // we'll be holding on to the last buffer for nothing.
        if (mFrameRepeatIntervalUs > 0LL) {
            setLatestBuffer_l(item);
        }
        ALOGV("buffer submitted [slot=%d, useCount=%ld] acquired=%d",
                item.mBuffer->getSlot(), item.mBuffer.use_count(), mNumOutstandingAcquires);
        mLastFrameTimestampUs = itemTimeUs;
    }

    return true;
}

bool GraphicBufferSource::repeatLatestBuffer_l() {
    CHECK(mExecuting && !haveAvailableBuffers_l());

    if (mLatestBuffer.mBuffer == nullptr || mSuspended) {
        return false;
    }

    if (mFreeCodecBuffers.empty()) {
        // No buffers available, bail.
        ALOGV("repeatLatestBuffer_l: no codec buffers.");
        return false;
    }

    if (!mLatestBuffer.mBuffer->isCached()) {
        ALOGV("repeatLatestBuffer_l: slot was discarded, but repeating our own reference");
    }

    // it is ok to update the timestamp of latest buffer as it is only used for submission
    status_t err = submitBuffer_l(mLatestBuffer);
    if (err != OK) {
        return false;
    }

    /* repeat last frame up to kRepeatLastFrameCount times.
     * in case of static scene, a single repeat might not get rid of encoder
     * ghosting completely, refresh a couple more times to get better quality
     */
    if (--mOutstandingFrameRepeatCount > 0) {
        // set up timestamp for repeat frame
        mLatestBuffer.mTimestampNs += mFrameRepeatIntervalUs * 1000;
        queueFrameRepeat_l();
    }

    return true;
}

void GraphicBufferSource::setLatestBuffer_l(const VideoBuffer &item) {
    mLatestBuffer = item;

    ALOGV("setLatestBuffer_l: [slot=%d, useCount=%ld]",
            mLatestBuffer.mBuffer->getSlot(), mLatestBuffer.mBuffer.use_count());

    mOutstandingFrameRepeatCount = kRepeatLastFrameCount;
    // set up timestamp for repeat frame
    mLatestBuffer.mTimestampNs += mFrameRepeatIntervalUs * 1000;
    queueFrameRepeat_l();
}

void GraphicBufferSource::queueFrameRepeat_l() {
    mFrameRepeatBlockedOnCodecBuffer = false;

    if (mReflector != NULL) {
        sp<AMessage> msg = new AMessage(kWhatRepeatLastFrame, mReflector);
        msg->setInt32("generation", ++mRepeatLastFrameGeneration);
        msg->post(mFrameRepeatIntervalUs);
    }
}

#ifdef __clang__
__attribute__((no_sanitize("integer")))
#endif
bool GraphicBufferSource::calculateCodecTimestamp_l(
        nsecs_t bufferTimeNs, int64_t *codecTimeUs) {
    int64_t timeUs = bufferTimeNs / 1000;
    timeUs += mInputBufferTimeOffsetUs;

    if (mCaptureFps > 0.
            && (mFps > 2 * mCaptureFps
            || mCaptureFps > 2 * mFps)) {
        // Time lapse or slow motion mode
        if (mPrevCaptureUs < 0LL) {
            // first capture
            mPrevCaptureUs = mBaseCaptureUs = timeUs;
            // adjust the first sample timestamp.
            mPrevFrameUs = mBaseFrameUs =
                    std::llround((timeUs * mCaptureFps) / mFps);
            mFrameCount = 0;
        } else if (mSnapTimestamps) {
            double nFrames = (timeUs - mPrevCaptureUs) * mCaptureFps / 1000000;
            if (nFrames < 0.5 - kTimestampFluctuation) {
                // skip this frame as it's too close to previous capture
                ALOGD("skipping frame, timeUs %lld",
                      static_cast<long long>(timeUs));
                return false;
            }
            // snap to nearest capture point
            if (nFrames <= 1.0) {
                nFrames = 1.0;
            }
            mFrameCount += std::llround(nFrames);
            mPrevCaptureUs = mBaseCaptureUs + std::llround(
                    mFrameCount * 1000000 / mCaptureFps);
            mPrevFrameUs = mBaseFrameUs + std::llround(
                    mFrameCount * 1000000 / mFps);
        } else {
            if (timeUs <= mPrevCaptureUs) {
                if (mFrameDropper != NULL && mFrameDropper->disabled()) {
                    // Warn only, client has disabled frame drop logic possibly for image
                    // encoding cases where camera's ZSL mode could send out of order frames.
                    ALOGW("Received frame that's going backward in time");
                } else {
                    // Drop the frame if it's going backward in time. Bad timestamp
                    // could disrupt encoder's rate control completely.
                    ALOGW("Dropping frame that's going backward in time");
                    return false;
                }
            }
            mPrevCaptureUs = timeUs;
            mPrevFrameUs = mBaseFrameUs + std::llround(
                    (timeUs - mBaseCaptureUs) * (mCaptureFps / mFps));
        }

        ALOGV("timeUs %lld, captureUs %lld, frameUs %lld",
                static_cast<long long>(timeUs),
                static_cast<long long>(mPrevCaptureUs),
                static_cast<long long>(mPrevFrameUs));
    } else {
        if (timeUs <= mPrevFrameUs) {
            if (mFrameDropper != NULL && mFrameDropper->disabled()) {
                // Warn only, client has disabled frame drop logic possibly for image
                // encoding cases where camera's ZSL mode could send out of order frames.
                ALOGW("Received frame that's going backward in time");
            } else {
                // Drop the frame if it's going backward in time. Bad timestamp
                // could disrupt encoder's rate control completely.
                ALOGW("Dropping frame that's going backward in time");
                return false;
            }
        }

        mPrevFrameUs = timeUs;
    }

    *codecTimeUs = mPrevFrameUs;
    return true;
}

status_t GraphicBufferSource::submitBuffer_l(const VideoBuffer &item) {
    CHECK(!mFreeCodecBuffers.empty());
    uint32_t codecBufferId = *mFreeCodecBuffers.begin();

    ALOGV("submitBuffer_l [slot=%d, bufferId=%d]", item.mBuffer->getSlot(), codecBufferId);

    int64_t codecTimeUs;
    if (!calculateCodecTimestamp_l(item.mTimestampNs, &codecTimeUs)) {
        return UNKNOWN_ERROR;
    }

    if ((android_dataspace)item.mDataspace != mLastDataspace) {
        onDataspaceChanged_l(
                item.mDataspace,
                (android_pixel_format)item.mBuffer->getGraphicBuffer()->format);
    }

    std::shared_ptr<AcquiredBuffer> buffer = item.mBuffer;
    // use a GraphicBuffer for now as component is using GraphicBuffers to hold references
    // and it requires this graphic buffer to be able to hold its reference
    // and thus we would need to create a new GraphicBuffer from an ANWBuffer separate from the
    // acquired GraphicBuffer.
    // TODO: this can be reworked globally to use ANWBuffer references
    sp<GraphicBuffer> graphicBuffer = buffer->getGraphicBuffer();
    status_t err = mComponent->submitBuffer(
            codecBufferId, graphicBuffer, codecTimeUs, buffer->getAcquireFenceFd());

    if (err != OK) {
        ALOGW("WARNING: emptyGraphicBuffer failed: 0x%x", err);
        return err;
    }

    mFreeCodecBuffers.erase(mFreeCodecBuffers.begin());

    ssize_t cbix = mSubmittedCodecBuffers.add(codecBufferId, buffer);
    ALOGV("emptyGraphicBuffer succeeded, bufferId=%u@%zd bufhandle=%p",
            codecBufferId, cbix, graphicBuffer->handle);
    return OK;
}

void GraphicBufferSource::submitEndOfInputStream_l() {
    CHECK(mEndOfStream);
    if (mEndOfStreamSent) {
        ALOGV("EOS already sent");
        return;
    }

    if (mFreeCodecBuffers.empty()) {
        ALOGV("submitEndOfInputStream_l: no codec buffers available");
        return;
    }
    uint32_t codecBufferId = *mFreeCodecBuffers.begin();

    // We reject any additional incoming graphic buffers. There is no acquired buffer used for EOS
    status_t err = mComponent->submitEos(codecBufferId);
    if (err != OK) {
        ALOGW("emptyDirectBuffer EOS failed: 0x%x", err);
    } else {
        mFreeCodecBuffers.erase(mFreeCodecBuffers.begin());
        ssize_t cbix = mSubmittedCodecBuffers.add(codecBufferId, nullptr);
        ALOGV("submitEndOfInputStream_l: buffer submitted, bufferId=%u@%zd", codecBufferId, cbix);
        mEndOfStreamSent = true;

        // no need to hold onto any buffers for frame repeating
        ++mRepeatLastFrameGeneration;
        mLatestBuffer.mBuffer.reset();
    }
}

status_t GraphicBufferSource::acquireBuffer_l(VideoBuffer *ab) {
    BufferItem bi;
    status_t err = mConsumer->acquireBuffer(&bi, 0);
    if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
        // shouldn't happen
        ALOGW("acquireBuffer_l: frame was not available");
        return err;
    } else if (err != OK) {
        ALOGW("acquireBuffer_l: failed with err=%d", err);
        return err;
    }
    --mNumAvailableUnacquiredBuffers;

    // Manage our buffer cache.
    std::shared_ptr<CachedBuffer> buffer;
    ssize_t bsi = mBufferSlots.indexOfKey(bi.mSlot);
    if (bi.mGraphicBuffer != NULL) {
        // replace/initialize slot with new buffer
        ALOGV("acquireBuffer_l: %s buffer slot %d", bsi < 0 ? "setting" : "UPDATING", bi.mSlot);
        if (bsi >= 0) {
            discardBufferAtSlotIndex_l(bsi);
        } else {
            bsi = mBufferSlots.add(bi.mSlot, nullptr);
        }
        buffer = std::make_shared<CachedBuffer>(bi.mSlot, bi.mGraphicBuffer);
        mBufferSlots.replaceValueAt(bsi, buffer);
    } else {
        buffer = mBufferSlots.valueAt(bsi);
    }
    int64_t frameNum = bi.mFrameNumber;

    std::shared_ptr<AcquiredBuffer> acquiredBuffer =
        std::make_shared<AcquiredBuffer>(
                buffer,
                [frameNum, this](AcquiredBuffer *buffer){
                    // AcquiredBuffer's destructor should always be called when mMutex is locked.
                    // If we had a reentrant mutex, we could just lock it again to ensure this.
                    if (mMutex.tryLock() == 0) {
                        TRESPASS_DBG();
                        mMutex.unlock();
                    }

                    // we can release buffers immediately if not using adapters
                    // alternately, we could add them to mSlotsToRelease, but we would
                    // somehow need to propagate frame number to that queue
                    if (buffer->isCached()) {
                        --mNumOutstandingAcquires;
                        mConsumer->releaseBuffer(buffer->getSlot(), frameNum,
                                                 buffer->getReleaseFence());
                    }
                },
                bi.mFence);
    VideoBuffer videoBuffer{acquiredBuffer, bi.mTimestamp, bi.mDataSpace};
    *ab = videoBuffer;
    ++mNumOutstandingAcquires;
    return OK;
}

// BufferQueue::ConsumerListener callback
void GraphicBufferSource::onFrameAvailable(const BufferItem& item __unused) {
    Mutex::Autolock autoLock(mMutex);

    ALOGV("onFrameAvailable: executing=%d available=%zu+%d",
            mExecuting, mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
    ++mNumAvailableUnacquiredBuffers;

    // For BufferQueue we cannot acquire a buffer if we cannot immediately feed it to the codec
    // UNLESS we are discarding this buffer (acquiring and immediately releasing it), which makes
    // this an ugly logic.
    // NOTE: We could also rely on our debug counter but that is meant only as a debug counter.
    if (!areWeDiscardingAvailableBuffers_l() && mFreeCodecBuffers.empty()) {
        // we may not be allowed to acquire a possibly encodable buffer, so just note that
        // it is available
        ALOGV("onFrameAvailable: cannot acquire buffer right now, do it later");

        ++mRepeatLastFrameGeneration; // cancel any pending frame repeat
        return;
    }

    VideoBuffer buffer;
    status_t err = acquireBuffer_l(&buffer);
    if (err != OK) {
        ALOGE("onFrameAvailable: acquireBuffer returned err=%d", err);
    } else {
        onBufferAcquired_l(buffer);
    }
}

bool GraphicBufferSource::areWeDiscardingAvailableBuffers_l() {
    return mEndOfStreamSent // already sent EOS to codec
            || mComponent == nullptr // there is no codec connected
            || (mSuspended && mActionQueue.empty()) // we are suspended and not waiting for
                                                    // any further action
            || !mExecuting;
}

void GraphicBufferSource::onBufferAcquired_l(const VideoBuffer &buffer) {
    if (mEndOfStreamSent) {
        // This should only be possible if a new buffer was queued after
        // EOS was signaled, i.e. the app is misbehaving.
        ALOGW("onFrameAvailable: EOS is sent, ignoring frame");
    } else if (mComponent == NULL || (mSuspended && mActionQueue.empty())) {
        // FIXME: if we are suspended but have a resume queued we will stop repeating the last
        // frame. Is that the desired behavior?
        ALOGV("onFrameAvailable: suspended, ignoring frame");
    } else {
        ++mRepeatLastFrameGeneration; // cancel any pending frame repeat
        mAvailableBuffers.push_back(buffer);
        if (mExecuting) {
            fillCodecBuffer_l();
        }
    }
}

// BufferQueue::ConsumerListener callback
void GraphicBufferSource::onBuffersReleased() {
    Mutex::Autolock lock(mMutex);

    uint64_t slotMask;
    uint64_t releaseMask;
    if (mConsumer->getReleasedBuffers(&releaseMask) != NO_ERROR) {
        slotMask = 0xffffffffffffffffULL;
        ALOGW("onBuffersReleased: unable to get released buffer set");
    } else {
        slotMask = releaseMask;
        ALOGV("onBuffersReleased: 0x%016" PRIx64, slotMask);
    }

    AString unpopulated;
    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
        if ((slotMask & 0x01) != 0) {
            if (!discardBufferInSlot_l(i)) {
                if (!unpopulated.empty()) {
                    unpopulated.append(", ");
                }
                unpopulated.append(i);
            }
        }
        slotMask >>= 1;
    }
    if (!unpopulated.empty()) {
        ALOGW("released unpopulated slots: [%s]", unpopulated.c_str());
    }
}

bool GraphicBufferSource::discardBufferInSlot_l(GraphicBufferSource::slot_id i) {
    ssize_t bsi = mBufferSlots.indexOfKey(i);
    if (bsi < 0) {
        return false;
    } else {
        discardBufferAtSlotIndex_l(bsi);
        mBufferSlots.removeItemsAt(bsi);
        return true;
    }
}

void GraphicBufferSource::discardBufferAtSlotIndex_l(ssize_t bsi) {
    const std::shared_ptr<CachedBuffer>& buffer = mBufferSlots.valueAt(bsi);
    // use -2 if there is no latest buffer, and -1 if it is no longer cached
    slot_id latestBufferSlot =
        mLatestBuffer.mBuffer == nullptr ? -2 : mLatestBuffer.mBuffer->getSlot();
    ALOGV("releasing acquired buffer: [slot=%d, useCount=%ld], latest: [slot=%d]",
            mBufferSlots.keyAt(bsi), buffer.use_count(), latestBufferSlot);
    mBufferSlots.valueAt(bsi)->onDroppedFromCache();

    // If the slot of an acquired buffer is discarded, that buffer will not have to be
    // released to the producer, so account it here. However, it is possible that the
    // acquired buffer has already been discarded so check if it still is.
    if (buffer->isAcquired()) {
        --mNumOutstandingAcquires;
    }

    // clear the buffer reference (not technically needed as caller either replaces or deletes
    // it; done here for safety).
    mBufferSlots.editValueAt(bsi).reset();
    CHECK_DBG(buffer == nullptr);
}

void GraphicBufferSource::releaseAllAvailableBuffers_l() {
    mAvailableBuffers.clear();
    while (mNumAvailableUnacquiredBuffers > 0) {
        VideoBuffer item;
        if (acquireBuffer_l(&item) != OK) {
            ALOGW("releaseAllAvailableBuffers: failed to acquire available unacquired buffer");
            break;
        }
    }
}

// BufferQueue::ConsumerListener callback
void GraphicBufferSource::onSidebandStreamChanged() {
    ALOG_ASSERT(false, "GraphicBufferSource can't consume sideband streams");
}

status_t GraphicBufferSource::configure(
        const sp<ComponentWrapper>& component,
        int32_t dataSpace,
        int32_t bufferCount,
        uint32_t frameWidth,
        uint32_t frameHeight,
        uint32_t consumerUsage) {
    uint64_t consumerUsage64 = static_cast<uint64_t>(consumerUsage);
    return configure(component, dataSpace, bufferCount,
                     frameWidth, frameHeight, consumerUsage64);
}

status_t GraphicBufferSource::configure(
        const sp<ComponentWrapper>& component,
        int32_t dataSpace,
        int32_t bufferCount,
        uint32_t frameWidth,
        uint32_t frameHeight,
        uint64_t consumerUsage) {
    if (component == NULL) {
        return BAD_VALUE;
    }


    // Call setMaxAcquiredBufferCount without lock.
    // setMaxAcquiredBufferCount could call back to onBuffersReleased
    // if the buffer count change results in releasing of existing buffers,
    // which would lead to deadlock.
    status_t err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
    if (err != NO_ERROR) {
        ALOGE("Unable to set BQ max acquired buffer count to %u: %d",
                bufferCount, err);
        return err;
    }

    {
        Mutex::Autolock autoLock(mMutex);
        mComponent = component;

        err = mConsumer->setDefaultBufferSize(frameWidth, frameHeight);
        if (err != NO_ERROR) {
            ALOGE("Unable to set BQ default buffer size to %ux%u: %d",
                    frameWidth, frameHeight, err);
            return err;
        }

        consumerUsage |= GRALLOC_USAGE_HW_VIDEO_ENCODER;
        mConsumer->setConsumerUsageBits(consumerUsage);

        // Set impl. defined format as default. Depending on the usage flags
        // the device-specific implementation will derive the exact format.
        err = mConsumer->setDefaultBufferFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
        if (err != NO_ERROR) {
            ALOGE("Failed to configure surface default format ret: %d", err);
            return err;
        }

        // Sets the default buffer data space
        ALOGD("setting dataspace: %#x, acquired=%d", dataSpace, mNumOutstandingAcquires);
        mConsumer->setDefaultBufferDataSpace((android_dataspace)dataSpace);
        mLastDataspace = (android_dataspace)dataSpace;

        mExecuting = false;
        mSuspended = false;
        mEndOfStream = false;
        mEndOfStreamSent = false;
        mSkipFramesBeforeNs = -1LL;
        mFrameDropper.clear();
        mFrameRepeatIntervalUs = -1LL;
        mRepeatLastFrameGeneration = 0;
        mOutstandingFrameRepeatCount = 0;
        mLatestBuffer.mBuffer.reset();
        mFrameRepeatBlockedOnCodecBuffer = false;
        mFps = -1.0;
        mCaptureFps = -1.0;
        mBaseCaptureUs = -1LL;
        mBaseFrameUs = -1LL;
        mPrevCaptureUs = -1LL;
        mPrevFrameUs = -1LL;
        mFrameCount = 0;
        mInputBufferTimeOffsetUs = 0;
        mStopTimeUs = -1;
        mActionQueue.clear();
    }

    return OK;
}

status_t GraphicBufferSource::setSuspend(bool suspend, int64_t suspendStartTimeUs) {
    ALOGV("setSuspend=%d at time %lld us", suspend, (long long)suspendStartTimeUs);

    Mutex::Autolock autoLock(mMutex);

    if (mStopTimeUs != -1) {
        ALOGE("setSuspend failed as STOP action is pending");
        return INVALID_OPERATION;
    }

    // Push the action to the queue.
    if (suspendStartTimeUs != -1) {
        // suspendStartTimeUs must be smaller or equal to current systemTime.
        int64_t currentSystemTimeUs = systemTime() / 1000;
        if (suspendStartTimeUs > currentSystemTimeUs) {
            ALOGE("setSuspend failed. %lld is larger than current system time %lld us",
                    (long long)suspendStartTimeUs, (long long)currentSystemTimeUs);
            return INVALID_OPERATION;
        }
        if (mLastActionTimeUs != -1 && suspendStartTimeUs < mLastActionTimeUs) {
            ALOGE("setSuspend failed. %lld is smaller than last action time %lld us",
                    (long long)suspendStartTimeUs, (long long)mLastActionTimeUs);
            return INVALID_OPERATION;
        }
        mLastActionTimeUs = suspendStartTimeUs;
        ActionItem action;
        action.mAction = suspend ? ActionItem::PAUSE : ActionItem::RESUME;
        action.mActionTimeUs = suspendStartTimeUs;
        ALOGV("Push %s action into actionQueue", suspend ? "PAUSE" : "RESUME");
        mActionQueue.push_back(action);
    } else {
        if (suspend) {
            mSuspended = true;
            releaseAllAvailableBuffers_l();
            return OK;
        } else {
            mSuspended = false;
            if (mExecuting && !haveAvailableBuffers_l()
                    && mFrameRepeatBlockedOnCodecBuffer) {
                if (repeatLatestBuffer_l()) {
                    ALOGV("suspend/deferred repeatLatestBuffer_l SUCCESS");
                    mFrameRepeatBlockedOnCodecBuffer = false;
                } else {
                    ALOGV("suspend/deferred repeatLatestBuffer_l FAILURE");
                }
            }
        }
    }
    return OK;
}

status_t GraphicBufferSource::setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs) {
    ALOGV("setRepeatPreviousFrameDelayUs: delayUs=%lld", (long long)repeatAfterUs);

    Mutex::Autolock autoLock(mMutex);

    if (mExecuting || repeatAfterUs <= 0LL) {
        return INVALID_OPERATION;
    }

    mFrameRepeatIntervalUs = repeatAfterUs;
    return OK;
}

status_t GraphicBufferSource::setTimeOffsetUs(int64_t timeOffsetUs) {
    Mutex::Autolock autoLock(mMutex);

    // timeOffsetUs must be negative for adjustment.
    if (timeOffsetUs >= 0LL) {
        return INVALID_OPERATION;
    }

    mInputBufferTimeOffsetUs = timeOffsetUs;
    return OK;
}

status_t GraphicBufferSource::setMaxFps(float maxFps) {
    ALOGV("setMaxFps: maxFps=%lld", (long long)maxFps);

    Mutex::Autolock autoLock(mMutex);

    if (mExecuting) {
        return INVALID_OPERATION;
    }

    mFrameDropper = new FrameDropper();
    status_t err = mFrameDropper->setMaxFrameRate(maxFps);
    if (err != OK) {
        mFrameDropper.clear();
        return err;
    }

    return OK;
}

status_t GraphicBufferSource::setStartTimeUs(int64_t skipFramesBeforeUs) {
    ALOGV("setStartTimeUs: skipFramesBeforeUs=%lld", (long long)skipFramesBeforeUs);

    Mutex::Autolock autoLock(mMutex);

    mSkipFramesBeforeNs =
            (skipFramesBeforeUs > 0 && skipFramesBeforeUs <= INT64_MAX / 1000) ?
            (skipFramesBeforeUs * 1000) : -1LL;

    return OK;
}

status_t GraphicBufferSource::setStopTimeUs(int64_t stopTimeUs) {
    ALOGV("setStopTimeUs: %lld us", (long long)stopTimeUs);
    Mutex::Autolock autoLock(mMutex);

    if (mStopTimeUs != -1) {
        // Ignore if stop time has already been set
        return OK;
    }

    // stopTimeUs must be smaller or equal to current systemTime.
    int64_t currentSystemTimeUs = systemTime() / 1000;
    if (stopTimeUs > currentSystemTimeUs) {
        ALOGE("setStopTimeUs failed. %lld is larger than current system time %lld us",
            (long long)stopTimeUs, (long long)currentSystemTimeUs);
        return INVALID_OPERATION;
    }
    if (mLastActionTimeUs != -1 && stopTimeUs < mLastActionTimeUs) {
        ALOGE("setSuspend failed. %lld is smaller than last action time %lld us",
            (long long)stopTimeUs, (long long)mLastActionTimeUs);
        return INVALID_OPERATION;
    }
    mLastActionTimeUs = stopTimeUs;
    ActionItem action;
    action.mAction = ActionItem::STOP;
    action.mActionTimeUs = stopTimeUs;
    mActionQueue.push_back(action);
    mStopTimeUs = stopTimeUs;
    return OK;
}

status_t GraphicBufferSource::getStopTimeOffsetUs(int64_t *stopTimeOffsetUs) {
    ALOGV("getStopTimeOffsetUs");
    Mutex::Autolock autoLock(mMutex);
    if (mStopTimeUs == -1) {
        ALOGW("Fail to return stopTimeOffsetUs as stop time is not set");
        return INVALID_OPERATION;
    }
    *stopTimeOffsetUs =
        mLastFrameTimestampUs == -1 ? 0 : mStopTimeUs - mLastFrameTimestampUs;
    return OK;
}

status_t GraphicBufferSource::setTimeLapseConfig(double fps, double captureFps) {
    ALOGV("setTimeLapseConfig: fps=%lg, captureFps=%lg",
            fps, captureFps);
    Mutex::Autolock autoLock(mMutex);

    if (mExecuting || !(fps > 0) || !(captureFps > 0)) {
        return INVALID_OPERATION;
    }

    mFps = fps;
    mCaptureFps = captureFps;
    if (captureFps > fps) {
        mSnapTimestamps = 1 == base::GetIntProperty(
                "debug.stagefright.snap_timestamps", int64_t(0));
    } else {
        mSnapTimestamps = false;
    }

    return OK;
}

status_t GraphicBufferSource::setColorAspects(int32_t aspectsPacked) {
    Mutex::Autolock autoLock(mMutex);
    mDefaultColorAspectsPacked = aspectsPacked;
    ColorAspects colorAspects = ColorUtils::unpackToColorAspects(aspectsPacked);
    ALOGD("requesting color aspects (R:%d(%s), P:%d(%s), M:%d(%s), T:%d(%s))",
            colorAspects.mRange, asString(colorAspects.mRange),
            colorAspects.mPrimaries, asString(colorAspects.mPrimaries),
            colorAspects.mMatrixCoeffs, asString(colorAspects.mMatrixCoeffs),
            colorAspects.mTransfer, asString(colorAspects.mTransfer));

    return OK;
}

status_t GraphicBufferSource::signalEndOfInputStream() {
    Mutex::Autolock autoLock(mMutex);
    ALOGV("signalEndOfInputStream: executing=%d available=%zu+%d eos=%d",
            mExecuting, mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers, mEndOfStream);

    if (mEndOfStream) {
        ALOGE("EOS was already signaled");
        return INVALID_OPERATION;
    }

    // Set the end-of-stream flag.  If no frames are pending from the
    // BufferQueue, and a codec buffer is available, and we're executing,
    // and there is no stop timestamp, we initiate the EOS from here.
    // Otherwise, we'll let codecBufferEmptied() (or start) do it.
    //
    // Note: if there are no pending frames and all codec buffers are
    // available, we *must* submit the EOS from here or we'll just
    // stall since no future events are expected.
    mEndOfStream = true;

    if (mStopTimeUs == -1 && mExecuting && !haveAvailableBuffers_l()) {
        submitEndOfInputStream_l();
    }

    return OK;
}

void GraphicBufferSource::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatRepeatLastFrame:
        {
            Mutex::Autolock autoLock(mMutex);

            int32_t generation;
            CHECK(msg->findInt32("generation", &generation));

            if (generation != mRepeatLastFrameGeneration) {
                // stale
                break;
            }

            if (!mExecuting || haveAvailableBuffers_l()) {
                break;
            }

            bool success = repeatLatestBuffer_l();
            if (success) {
                ALOGV("repeatLatestBuffer_l SUCCESS");
            } else {
                ALOGV("repeatLatestBuffer_l FAILURE");
                mFrameRepeatBlockedOnCodecBuffer = true;
            }
            break;
        }

        default:
            TRESPASS();
    }
}

}  // namespace android
