/*
 * Copyright 2015 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 "MediaSync"
#include <inttypes.h>

#include <gui/BufferQueue.h>
#include <gui/IGraphicBufferConsumer.h>
#include <gui/IGraphicBufferProducer.h>

#include <media/AudioTrack.h>
#include <media/stagefright/MediaClock.h>
#include <media/stagefright/MediaSync.h>
#include <media/stagefright/VideoFrameScheduler.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AMessage.h>

#include <ui/GraphicBuffer.h>

#include <system/window.h>

// Maximum late time allowed for a video frame to be rendered. When a video
// frame arrives later than this number, it will be discarded without rendering.
static const int64_t kMaxAllowedVideoLateTimeUs = 40000ll;

namespace android {

// static
sp<MediaSync> MediaSync::create() {
    sp<MediaSync> sync = new MediaSync();
    sync->mLooper->registerHandler(sync);
    return sync;
}

MediaSync::MediaSync()
      : mIsAbandoned(false),
        mMutex(),
        mReleaseCondition(),
        mNumOutstandingBuffers(0),
        mUsageFlagsFromOutput(0),
        mMaxAcquiredBufferCount(1),
        mReturnPendingInputFrame(false),
        mNativeSampleRateInHz(0),
        mNumFramesWritten(0),
        mHasAudio(false),
        mNextBufferItemMediaUs(-1),
        mPlaybackRate(0.0) {
    mMediaClock = new MediaClock;

    // initialize settings
    mPlaybackSettings = AUDIO_PLAYBACK_RATE_DEFAULT;
    mPlaybackSettings.mSpeed = mPlaybackRate;

    mLooper = new ALooper;
    mLooper->setName("MediaSync");
    mLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
}

MediaSync::~MediaSync() {
    if (mInput != NULL) {
        mInput->consumerDisconnect();
    }
    if (mOutput != NULL) {
        mOutput->disconnect(NATIVE_WINDOW_API_MEDIA);
    }

    if (mLooper != NULL) {
        mLooper->unregisterHandler(id());
        mLooper->stop();
    }
}

status_t MediaSync::setSurface(const sp<IGraphicBufferProducer> &output) {
    Mutex::Autolock lock(mMutex);

    if (output == mOutput) {
        return NO_ERROR;  // same output surface.
    }

    if (output == NULL && mSyncSettings.mSource == AVSYNC_SOURCE_VSYNC) {
        ALOGE("setSurface: output surface is used as sync source and cannot be removed.");
        return INVALID_OPERATION;
    }

    if (output != NULL) {
        int newUsage = 0;
        output->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &newUsage);

        // Check usage flags only when current output surface has been used to create input surface.
        if (mOutput != NULL && mInput != NULL) {
            int ignoredFlags = (GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER
                    | GRALLOC_USAGE_EXTERNAL_DISP);
            // New output surface is not allowed to add new usage flag except ignored ones.
            if ((newUsage & ~(mUsageFlagsFromOutput | ignoredFlags)) != 0) {
                ALOGE("setSurface: new output surface has new usage flag not used by current one.");
                return BAD_VALUE;
            }
        }

        // Try to connect to new output surface. If failed, current output surface will not
        // be changed.
        IGraphicBufferProducer::QueueBufferOutput queueBufferOutput;
        sp<OutputListener> listener(new OutputListener(this, output));
        IInterface::asBinder(output)->linkToDeath(listener);
        status_t status =
            output->connect(listener,
                            NATIVE_WINDOW_API_MEDIA,
                            true /* producerControlledByApp */,
                            &queueBufferOutput);
        if (status != NO_ERROR) {
            ALOGE("setSurface: failed to connect (%d)", status);
            return status;
        }

        if (mFrameScheduler == NULL) {
            mFrameScheduler = new VideoFrameScheduler();
            mFrameScheduler->init();
        }
    }

    if (mOutput != NULL) {
        mOutput->disconnect(NATIVE_WINDOW_API_MEDIA);
        while (!mBuffersSentToOutput.isEmpty()) {
            returnBufferToInput_l(mBuffersSentToOutput.valueAt(0), Fence::NO_FENCE);
            mBuffersSentToOutput.removeItemsAt(0);
        }
    }

    mOutput = output;

    return NO_ERROR;
}

// |audioTrack| is used only for querying information.
status_t MediaSync::setAudioTrack(const sp<AudioTrack> &audioTrack) {
    Mutex::Autolock lock(mMutex);

    // TODO: support audio track change.
    if (mAudioTrack != NULL) {
        ALOGE("setAudioTrack: audioTrack has already been configured.");
        return INVALID_OPERATION;
    }

    if (audioTrack == NULL && mSyncSettings.mSource == AVSYNC_SOURCE_AUDIO) {
        ALOGE("setAudioTrack: audioTrack is used as sync source and cannot be removed.");
        return INVALID_OPERATION;
    }

    if (audioTrack != NULL) {
        // check if audio track supports the playback settings
        if (mPlaybackSettings.mSpeed != 0.f
                && audioTrack->setPlaybackRate(mPlaybackSettings) != OK) {
            ALOGE("playback settings are not supported by the audio track");
            return INVALID_OPERATION;
        }
        uint32_t nativeSampleRateInHz = audioTrack->getOriginalSampleRate();
        if (nativeSampleRateInHz <= 0) {
            ALOGE("setAudioTrack: native sample rate should be positive.");
            return BAD_VALUE;
        }
        mAudioTrack = audioTrack;
        mNativeSampleRateInHz = nativeSampleRateInHz;
        (void)setPlaybackSettings_l(mPlaybackSettings);
    }
    else {
        mAudioTrack = NULL;
        mNativeSampleRateInHz = 0;
    }

    // potentially resync to new source
    resync_l();
    return OK;
}

status_t MediaSync::createInputSurface(
        sp<IGraphicBufferProducer> *outBufferProducer) {
    if (outBufferProducer == NULL) {
        return BAD_VALUE;
    }

    Mutex::Autolock lock(mMutex);

    if (mOutput == NULL) {
        return NO_INIT;
    }

    if (mInput != NULL) {
        return INVALID_OPERATION;
    }

    sp<IGraphicBufferProducer> bufferProducer;
    sp<IGraphicBufferConsumer> bufferConsumer;
    BufferQueue::createBufferQueue(&bufferProducer, &bufferConsumer);

    sp<InputListener> listener(new InputListener(this));
    IInterface::asBinder(bufferConsumer)->linkToDeath(listener);
    status_t status =
        bufferConsumer->consumerConnect(listener, false /* controlledByApp */);
    if (status == NO_ERROR) {
        bufferConsumer->setConsumerName(String8("MediaSync"));
        // propagate usage bits from output surface
        mUsageFlagsFromOutput = 0;
        mOutput->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &mUsageFlagsFromOutput);
        bufferConsumer->setConsumerUsageBits(mUsageFlagsFromOutput);
        *outBufferProducer = bufferProducer;
        mInput = bufferConsumer;

        // set undequeued buffer count
        int minUndequeuedBuffers;
        mOutput->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBuffers);
        mMaxAcquiredBufferCount = minUndequeuedBuffers;
        bufferConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBufferCount);
    }
    return status;
}

void MediaSync::resync_l() {
    AVSyncSource src = mSyncSettings.mSource;
    if (src == AVSYNC_SOURCE_DEFAULT) {
        if (mAudioTrack != NULL) {
            src = AVSYNC_SOURCE_AUDIO;
        } else {
            src = AVSYNC_SOURCE_SYSTEM_CLOCK;
        }
    }

    // TODO: resync ourselves to the current clock (e.g. on sync source change)
    updatePlaybackRate_l(mPlaybackRate);
}

void MediaSync::updatePlaybackRate_l(float rate) {
    if (rate > mPlaybackRate) {
        mNextBufferItemMediaUs = -1;
    }
    mPlaybackRate = rate;
    // TODO: update frame scheduler with this info
    mMediaClock->setPlaybackRate(rate);
    onDrainVideo_l();
}

sp<const MediaClock> MediaSync::getMediaClock() {
    return mMediaClock;
}

status_t MediaSync::getPlayTimeForPendingAudioFrames(int64_t *outTimeUs) {
    Mutex::Autolock lock(mMutex);
    // User should check the playback rate if it doesn't want to receive a
    // huge number for play time.
    if (mPlaybackRate == 0.0f) {
        *outTimeUs = INT64_MAX;
        return OK;
    }

    uint32_t numFramesPlayed = 0;
    if (mAudioTrack != NULL) {
        status_t res = mAudioTrack->getPosition(&numFramesPlayed);
        if (res != OK) {
            return res;
        }
    }

    int64_t numPendingFrames = mNumFramesWritten - numFramesPlayed;
    if (numPendingFrames < 0) {
        numPendingFrames = 0;
        ALOGW("getPlayTimeForPendingAudioFrames: pending frame count is negative.");
    }
    double timeUs = numPendingFrames * 1000000.0
            / (mNativeSampleRateInHz * (double)mPlaybackRate);
    if (timeUs > (double)INT64_MAX) {
        // Overflow.
        *outTimeUs = INT64_MAX;
        ALOGW("getPlayTimeForPendingAudioFrames: play time for pending audio frames "
              "is too high, possibly due to super low playback rate(%f)", mPlaybackRate);
    } else {
        *outTimeUs = (int64_t)timeUs;
    }

    return OK;
}

status_t MediaSync::updateQueuedAudioData(
        size_t sizeInBytes, int64_t presentationTimeUs) {
    if (sizeInBytes == 0) {
        return OK;
    }

    Mutex::Autolock lock(mMutex);

    if (mAudioTrack == NULL) {
        ALOGW("updateQueuedAudioData: audioTrack has NOT been configured.");
        return INVALID_OPERATION;
    }

    int64_t numFrames = sizeInBytes / mAudioTrack->frameSize();
    int64_t maxMediaTimeUs = presentationTimeUs
            + getDurationIfPlayedAtNativeSampleRate_l(numFrames);

    int64_t nowUs = ALooper::GetNowUs();
    int64_t nowMediaUs = presentationTimeUs
            - getDurationIfPlayedAtNativeSampleRate_l(mNumFramesWritten)
            + getPlayedOutAudioDurationMedia_l(nowUs);

    mNumFramesWritten += numFrames;

    int64_t oldRealTime = -1;
    if (mNextBufferItemMediaUs != -1) {
        oldRealTime = getRealTime(mNextBufferItemMediaUs, nowUs);
    }

    mMediaClock->updateAnchor(nowMediaUs, nowUs, maxMediaTimeUs);
    mHasAudio = true;

    if (oldRealTime != -1) {
        int64_t newRealTime = getRealTime(mNextBufferItemMediaUs, nowUs);
        if (newRealTime >= oldRealTime) {
            return OK;
        }
    }

    mNextBufferItemMediaUs = -1;
    onDrainVideo_l();
    return OK;
}

void MediaSync::setName(const AString &name) {
    Mutex::Autolock lock(mMutex);
    mInput->setConsumerName(String8(name.c_str()));
}

void MediaSync::flush() {
    Mutex::Autolock lock(mMutex);
    if (mFrameScheduler != NULL) {
        mFrameScheduler->restart();
    }
    while (!mBufferItems.empty()) {
        BufferItem *bufferItem = &*mBufferItems.begin();
        returnBufferToInput_l(bufferItem->mGraphicBuffer, bufferItem->mFence);
        mBufferItems.erase(mBufferItems.begin());
    }
    mNextBufferItemMediaUs = -1;
    mNumFramesWritten = 0;
    mReturnPendingInputFrame = true;
    mReleaseCondition.signal();
    mMediaClock->clearAnchor();
}

status_t MediaSync::setVideoFrameRateHint(float rate) {
    Mutex::Autolock lock(mMutex);
    if (rate < 0.f) {
        return BAD_VALUE;
    }
    if (mFrameScheduler != NULL) {
        mFrameScheduler->init(rate);
    }
    return OK;
}

float MediaSync::getVideoFrameRate() {
    Mutex::Autolock lock(mMutex);
    if (mFrameScheduler != NULL) {
        float fps = mFrameScheduler->getFrameRate();
        if (fps > 0.f) {
            return fps;
        }
    }

    // we don't have or know the frame rate
    return -1.f;
}

status_t MediaSync::setSyncSettings(const AVSyncSettings &syncSettings) {
    // validate settings
    if (syncSettings.mSource >= AVSYNC_SOURCE_MAX
            || syncSettings.mAudioAdjustMode >= AVSYNC_AUDIO_ADJUST_MODE_MAX
            || syncSettings.mTolerance < 0.f
            || syncSettings.mTolerance >= AVSYNC_TOLERANCE_MAX) {
        return BAD_VALUE;
    }

    Mutex::Autolock lock(mMutex);

    // verify that we have the sync source
    switch (syncSettings.mSource) {
        case AVSYNC_SOURCE_AUDIO:
            if (mAudioTrack == NULL) {
                ALOGE("setSyncSettings: audio sync source requires an audio track");
                return BAD_VALUE;
            }
            break;
        case AVSYNC_SOURCE_VSYNC:
            if (mOutput == NULL) {
                ALOGE("setSyncSettings: vsync sync source requires an output surface");
                return BAD_VALUE;
            }
            break;
        default:
            break;
    }

    mSyncSettings = syncSettings;
    resync_l();
    return OK;
}

void MediaSync::getSyncSettings(AVSyncSettings *syncSettings) {
    Mutex::Autolock lock(mMutex);
    *syncSettings = mSyncSettings;
}

status_t MediaSync::setPlaybackSettings(const AudioPlaybackRate &rate) {
    Mutex::Autolock lock(mMutex);

    status_t err = setPlaybackSettings_l(rate);
    if (err == OK) {
        // TODO: adjust rate if using VSYNC as source
        updatePlaybackRate_l(rate.mSpeed);
    }
    return err;
}

status_t MediaSync::setPlaybackSettings_l(const AudioPlaybackRate &rate) {
    if (rate.mSpeed < 0.f || rate.mPitch < 0.f) {
        // We don't validate other audio settings.
        // They will be validated when/if audiotrack is set.
        return BAD_VALUE;
    }

    if (mAudioTrack != NULL) {
        if (rate.mSpeed == 0.f) {
            mAudioTrack->pause();
        } else {
            status_t err = mAudioTrack->setPlaybackRate(rate);
            if (err != OK) {
                return BAD_VALUE;
            }

            // ignore errors
            (void)mAudioTrack->start();
        }
    }
    mPlaybackSettings = rate;
    return OK;
}

void MediaSync::getPlaybackSettings(AudioPlaybackRate *rate) {
    Mutex::Autolock lock(mMutex);
    *rate = mPlaybackSettings;
}

int64_t MediaSync::getRealTime(int64_t mediaTimeUs, int64_t nowUs) {
    int64_t realUs;
    if (mMediaClock->getRealTimeFor(mediaTimeUs, &realUs) != OK) {
        // If failed to get current position, e.g. due to audio clock is
        // not ready, then just play out video immediately without delay.
        return nowUs;
    }
    return realUs;
}

int64_t MediaSync::getDurationIfPlayedAtNativeSampleRate_l(int64_t numFrames) {
    return (numFrames * 1000000LL / mNativeSampleRateInHz);
}

int64_t MediaSync::getPlayedOutAudioDurationMedia_l(int64_t nowUs) {
    CHECK(mAudioTrack != NULL);

    uint32_t numFramesPlayed;
    int64_t numFramesPlayedAtUs;
    AudioTimestamp ts;

    status_t res = mAudioTrack->getTimestamp(ts);
    if (res == OK) {
        // case 1: mixing audio tracks.
        numFramesPlayed = ts.mPosition;
        numFramesPlayedAtUs = ts.mTime.tv_sec * 1000000LL + ts.mTime.tv_nsec / 1000;
        //ALOGD("getTimestamp: OK %d %lld",
        //      numFramesPlayed, (long long)numFramesPlayedAtUs);
    } else if (res == WOULD_BLOCK) {
        // case 2: transitory state on start of a new track
        numFramesPlayed = 0;
        numFramesPlayedAtUs = nowUs;
        //ALOGD("getTimestamp: WOULD_BLOCK %d %lld",
        //      numFramesPlayed, (long long)numFramesPlayedAtUs);
    } else {
        // case 3: transitory at new track or audio fast tracks.
        res = mAudioTrack->getPosition(&numFramesPlayed);
        CHECK_EQ(res, (status_t)OK);
        numFramesPlayedAtUs = nowUs;
        numFramesPlayedAtUs += 1000LL * mAudioTrack->latency() / 2; /* XXX */
        //ALOGD("getPosition: %d %lld", numFramesPlayed, (long long)numFramesPlayedAtUs);
    }

    //can't be negative until 12.4 hrs, test.
    //CHECK_EQ(numFramesPlayed & (1 << 31), 0);
    int64_t durationUs =
        getDurationIfPlayedAtNativeSampleRate_l(numFramesPlayed)
            + nowUs - numFramesPlayedAtUs;
    if (durationUs < 0) {
        // Occurs when numFramesPlayed position is very small and the following:
        // (1) In case 1, the time nowUs is computed before getTimestamp() is
        //     called and numFramesPlayedAtUs is greater than nowUs by time more
        //     than numFramesPlayed.
        // (2) In case 3, using getPosition and adding mAudioTrack->latency()
        //     to numFramesPlayedAtUs, by a time amount greater than
        //     numFramesPlayed.
        //
        // Both of these are transitory conditions.
        ALOGV("getPlayedOutAudioDurationMedia_l: negative duration %lld "
              "set to zero", (long long)durationUs);
        durationUs = 0;
    }
    ALOGV("getPlayedOutAudioDurationMedia_l(%lld) nowUs(%lld) frames(%u) "
          "framesAt(%lld)",
          (long long)durationUs, (long long)nowUs, numFramesPlayed,
          (long long)numFramesPlayedAtUs);
    return durationUs;
}

void MediaSync::onDrainVideo_l() {
    if (!isPlaying()) {
        return;
    }

    while (!mBufferItems.empty()) {
        int64_t nowUs = ALooper::GetNowUs();
        BufferItem *bufferItem = &*mBufferItems.begin();
        int64_t itemMediaUs = bufferItem->mTimestamp / 1000;
        int64_t itemRealUs = getRealTime(itemMediaUs, nowUs);

        // adjust video frame PTS based on vsync
        itemRealUs = mFrameScheduler->schedule(itemRealUs * 1000) / 1000;
        int64_t twoVsyncsUs = 2 * (mFrameScheduler->getVsyncPeriod() / 1000);

        // post 2 display refreshes before rendering is due
        if (itemRealUs <= nowUs + twoVsyncsUs) {
            ALOGV("adjusting PTS from %lld to %lld",
                    (long long)bufferItem->mTimestamp / 1000, (long long)itemRealUs);
            bufferItem->mTimestamp = itemRealUs * 1000;
            bufferItem->mIsAutoTimestamp = false;

            if (mHasAudio) {
                if (nowUs - itemRealUs <= kMaxAllowedVideoLateTimeUs) {
                    renderOneBufferItem_l(*bufferItem);
                } else {
                    // too late.
                    returnBufferToInput_l(
                            bufferItem->mGraphicBuffer, bufferItem->mFence);
                    mFrameScheduler->restart();
                }
            } else {
                // always render video buffer in video-only mode.
                renderOneBufferItem_l(*bufferItem);

                // smooth out videos >= 10fps
                mMediaClock->updateAnchor(
                        itemMediaUs, nowUs, itemMediaUs + 100000);
            }

            mBufferItems.erase(mBufferItems.begin());
            mNextBufferItemMediaUs = -1;
        } else {
            if (mNextBufferItemMediaUs == -1
                    || mNextBufferItemMediaUs > itemMediaUs) {
                sp<AMessage> msg = new AMessage(kWhatDrainVideo, this);
                msg->post(itemRealUs - nowUs - twoVsyncsUs);
                mNextBufferItemMediaUs = itemMediaUs;
            }
            break;
        }
    }
}

void MediaSync::onFrameAvailableFromInput() {
    Mutex::Autolock lock(mMutex);

    const static nsecs_t kAcquireWaitTimeout = 2000000000; // 2 seconds

    mReturnPendingInputFrame = false;

    // If there are too many outstanding buffers, wait until a buffer is
    // released back to the input in onBufferReleased.
    // NOTE: BufferQueue allows dequeuing maxAcquiredBufferCount + 1 buffers
    while (mNumOutstandingBuffers > mMaxAcquiredBufferCount
            && !mIsAbandoned && !mReturnPendingInputFrame) {
        if (mReleaseCondition.waitRelative(mMutex, kAcquireWaitTimeout) != OK) {
            ALOGI_IF(mPlaybackRate != 0.f, "still waiting to release a buffer before acquire");
        }

        // If the sync is abandoned while we are waiting, the release
        // condition variable will be broadcast, and we should just return
        // without attempting to do anything more (since the input queue will
        // also be abandoned).
        if (mIsAbandoned) {
            return;
        }
    }

    // Acquire and detach the buffer from the input.
    BufferItem bufferItem;
    status_t status = mInput->acquireBuffer(&bufferItem, 0 /* presentWhen */);
    if (status != NO_ERROR) {
        ALOGE("acquiring buffer from input failed (%d)", status);
        return;
    }
    ++mNumOutstandingBuffers;

    ALOGV("acquired buffer %#llx from input", (long long)bufferItem.mGraphicBuffer->getId());

    status = mInput->detachBuffer(bufferItem.mSlot);
    if (status != NO_ERROR) {
        ALOGE("detaching buffer from input failed (%d)", status);
        if (status == NO_INIT) {
            // If the input has been abandoned, move on.
            onAbandoned_l(true /* isInput */);
        }
        return;
    }

    if (mBuffersFromInput.indexOfKey(bufferItem.mGraphicBuffer->getId()) >= 0) {
        // Something is wrong since this buffer should be at our hands, bail.
        ALOGE("received buffer multiple times from input");
        mInput->consumerDisconnect();
        onAbandoned_l(true /* isInput */);
        return;
    }
    mBuffersFromInput.add(bufferItem.mGraphicBuffer->getId(), bufferItem.mGraphicBuffer);

    // If flush happened while waiting for a buffer to be released, simply return it
    // TRICKY: do it here after it is detached so that we don't have to cache mGraphicBuffer.
    if (mReturnPendingInputFrame) {
        mReturnPendingInputFrame = false;
        returnBufferToInput_l(bufferItem.mGraphicBuffer, bufferItem.mFence);
        return;
    }

    mBufferItems.push_back(bufferItem);

    if (mBufferItems.size() == 1) {
        onDrainVideo_l();
    }
}

void MediaSync::renderOneBufferItem_l(const BufferItem &bufferItem) {
    IGraphicBufferProducer::QueueBufferInput queueInput(
            bufferItem.mTimestamp,
            bufferItem.mIsAutoTimestamp,
            bufferItem.mDataSpace,
            bufferItem.mCrop,
            static_cast<int32_t>(bufferItem.mScalingMode),
            bufferItem.mTransform,
            bufferItem.mFence);

    // Attach and queue the buffer to the output.
    int slot;
    mOutput->setGenerationNumber(bufferItem.mGraphicBuffer->getGenerationNumber());
    status_t status = mOutput->attachBuffer(&slot, bufferItem.mGraphicBuffer);
    ALOGE_IF(status != NO_ERROR, "attaching buffer to output failed (%d)", status);
    if (status == NO_ERROR) {
        IGraphicBufferProducer::QueueBufferOutput queueOutput;
        status = mOutput->queueBuffer(slot, queueInput, &queueOutput);
        ALOGE_IF(status != NO_ERROR, "queueing buffer to output failed (%d)", status);
    }

    if (status != NO_ERROR) {
        returnBufferToInput_l(bufferItem.mGraphicBuffer, bufferItem.mFence);
        if (status == NO_INIT) {
            // If the output has been abandoned, move on.
            onAbandoned_l(false /* isInput */);
        }
        return;
    }

    if (mBuffersSentToOutput.indexOfKey(bufferItem.mGraphicBuffer->getId()) >= 0) {
        // Something is wrong since this buffer should be held by output now, bail.
        mInput->consumerDisconnect();
        onAbandoned_l(true /* isInput */);
        return;
    }
    mBuffersSentToOutput.add(bufferItem.mGraphicBuffer->getId(), bufferItem.mGraphicBuffer);

    ALOGV("queued buffer %#llx to output", (long long)bufferItem.mGraphicBuffer->getId());
}

void MediaSync::onBufferReleasedByOutput(sp<IGraphicBufferProducer> &output) {
    Mutex::Autolock lock(mMutex);

    if (output != mOutput) {
        return;  // This is not the current output, ignore.
    }

    sp<GraphicBuffer> buffer;
    sp<Fence> fence;
    status_t status = mOutput->detachNextBuffer(&buffer, &fence);
    ALOGE_IF(status != NO_ERROR, "detaching buffer from output failed (%d)", status);

    if (status == NO_INIT) {
        // If the output has been abandoned, we can't do anything else,
        // since buffer is invalid.
        onAbandoned_l(false /* isInput */);
        return;
    }

    ALOGV("detached buffer %#llx from output", (long long)buffer->getId());

    // If we've been abandoned, we can't return the buffer to the input, so just
    // move on.
    if (mIsAbandoned) {
        return;
    }

    ssize_t ix = mBuffersSentToOutput.indexOfKey(buffer->getId());
    if (ix < 0) {
        // The buffer is unknown, maybe leftover, ignore.
        return;
    }
    mBuffersSentToOutput.removeItemsAt(ix);

    returnBufferToInput_l(buffer, fence);
}

void MediaSync::returnBufferToInput_l(
        const sp<GraphicBuffer> &buffer, const sp<Fence> &fence) {
    ssize_t ix = mBuffersFromInput.indexOfKey(buffer->getId());
    if (ix < 0) {
        // The buffer is unknown, something is wrong, bail.
        ALOGE("output returned unknown buffer");
        mOutput->disconnect(NATIVE_WINDOW_API_MEDIA);
        onAbandoned_l(false /* isInput */);
        return;
    }
    sp<GraphicBuffer> oldBuffer = mBuffersFromInput.valueAt(ix);
    mBuffersFromInput.removeItemsAt(ix);

    // Attach and release the buffer back to the input.
    int consumerSlot;
    status_t status = mInput->attachBuffer(&consumerSlot, oldBuffer);
    ALOGE_IF(status != NO_ERROR, "attaching buffer to input failed (%d)", status);
    if (status == NO_ERROR) {
        status = mInput->releaseBuffer(consumerSlot, 0 /* frameNumber */,
                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, fence);
        ALOGE_IF(status != NO_ERROR, "releasing buffer to input failed (%d)", status);
    }

    // Notify any waiting onFrameAvailable calls.
    --mNumOutstandingBuffers;
    mReleaseCondition.signal();

    if (status == NO_ERROR) {
        ALOGV("released buffer %#llx to input", (long long)oldBuffer->getId());
    }
}

void MediaSync::onAbandoned_l(bool isInput) {
    ALOGE("the %s has abandoned me", (isInput ? "input" : "output"));
    if (!mIsAbandoned) {
        if (isInput) {
            mOutput->disconnect(NATIVE_WINDOW_API_MEDIA);
        } else {
            mInput->consumerDisconnect();
        }
        mIsAbandoned = true;
    }
    mReleaseCondition.broadcast();
}

void MediaSync::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatDrainVideo:
        {
            Mutex::Autolock lock(mMutex);
            if (mNextBufferItemMediaUs != -1) {
                int64_t nowUs = ALooper::GetNowUs();
                int64_t itemRealUs = getRealTime(mNextBufferItemMediaUs, nowUs);

                // The message could arrive earlier than expected due to
                // various reasons, e.g., media clock has been changed because
                // of new anchor time or playback rate. In such cases, the
                // message needs to be re-posted.
                if (itemRealUs > nowUs) {
                    msg->post(itemRealUs - nowUs);
                    break;
                }
            }

            onDrainVideo_l();
            break;
        }

        default:
            TRESPASS();
            break;
    }
}

MediaSync::InputListener::InputListener(const sp<MediaSync> &sync)
      : mSync(sync) {}

MediaSync::InputListener::~InputListener() {}

void MediaSync::InputListener::onFrameAvailable(const BufferItem &/* item */) {
    mSync->onFrameAvailableFromInput();
}

// We don't care about sideband streams, since we won't relay them.
void MediaSync::InputListener::onSidebandStreamChanged() {
    ALOGE("onSidebandStreamChanged: got sideband stream unexpectedly.");
}


void MediaSync::InputListener::binderDied(const wp<IBinder> &/* who */) {
    Mutex::Autolock lock(mSync->mMutex);
    mSync->onAbandoned_l(true /* isInput */);
}

MediaSync::OutputListener::OutputListener(const sp<MediaSync> &sync,
        const sp<IGraphicBufferProducer> &output)
      : mSync(sync),
        mOutput(output) {}

MediaSync::OutputListener::~OutputListener() {}

void MediaSync::OutputListener::onBufferReleased() {
    mSync->onBufferReleasedByOutput(mOutput);
}

void MediaSync::OutputListener::binderDied(const wp<IBinder> &/* who */) {
    Mutex::Autolock lock(mSync->mMutex);
    mSync->onAbandoned_l(false /* isInput */);
}

} // namespace android
