/*
 * Copyright 2019, 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 "CCodecBuffers"
#include <utils/Log.h>

#include <numeric>

#include <C2AllocatorGralloc.h>
#include <C2PlatformSupport.h>

#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/foundation/MediaDefs.h>
#include <media/stagefright/CodecBase.h>
#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/SkipCutBuffer.h>
#include <mediadrm/ICrypto.h>

#include "CCodecBuffers.h"
#include "Codec2Mapper.h"

namespace android {

namespace {

constexpr uint32_t PIXEL_FORMAT_UNKNOWN = 0;

sp<GraphicBlockBuffer> AllocateInputGraphicBuffer(
        const std::shared_ptr<C2BlockPool> &pool,
        const sp<AMessage> &format,
        uint32_t pixelFormat,
        const C2MemoryUsage &usage,
        const std::shared_ptr<LocalBufferPool> &localBufferPool) {
    int32_t width, height;
    if (!format->findInt32("width", &width) || !format->findInt32("height", &height)) {
        ALOGD("format lacks width or height");
        return nullptr;
    }

    int64_t usageValue = 0;
    (void)format->findInt64("android._C2MemoryUsage", &usageValue);
    C2MemoryUsage fullUsage{usageValue | usage.expected};

    std::shared_ptr<C2GraphicBlock> block;
    c2_status_t err = pool->fetchGraphicBlock(
            align(width, 2), align(height, 2), pixelFormat, fullUsage, &block);
    if (err != C2_OK) {
        ALOGD("fetch graphic block failed: %d", err);
        return nullptr;
    }

    return GraphicBlockBuffer::Allocate(
            format,
            block,
            [localBufferPool](size_t capacity) {
                return localBufferPool->newBuffer(capacity);
            });
}

}  // namespace

// CCodecBuffers

void CCodecBuffers::setFormat(const sp<AMessage> &format) {
    CHECK(format != nullptr);
    mFormat = format;
}

sp<AMessage> CCodecBuffers::dupFormat() {
    return mFormat != nullptr ? mFormat->dup() : nullptr;
}

void CCodecBuffers::handleImageData(const sp<Codec2Buffer> &buffer) {
    sp<ABuffer> imageDataCandidate = buffer->getImageData();
    if (imageDataCandidate == nullptr) {
        if (mFormatWithImageData) {
            // We previously sent the format with image data, so use the same format.
            buffer->setFormat(mFormatWithImageData);
        }
        return;
    }
    if (!mLastImageData
            || imageDataCandidate->size() != mLastImageData->size()
            || memcmp(imageDataCandidate->data(),
                      mLastImageData->data(),
                      mLastImageData->size()) != 0) {
        ALOGD("[%s] updating image-data", mName);
        mFormatWithImageData = dupFormat();
        mLastImageData = imageDataCandidate;
        mFormatWithImageData->setBuffer("image-data", imageDataCandidate);
        MediaImage2 *img = (MediaImage2*)imageDataCandidate->data();
        if (img->mNumPlanes > 0 && img->mType != img->MEDIA_IMAGE_TYPE_UNKNOWN) {
            int32_t stride = img->mPlane[0].mRowInc;
            mFormatWithImageData->setInt32(KEY_STRIDE, stride);
            mFormatWithImageData->setInt32(KEY_WIDTH, img->mWidth);
            mFormatWithImageData->setInt32(KEY_HEIGHT, img->mHeight);
            ALOGD("[%s] updating stride = %d, width: %d, height: %d",
                  mName, stride, img->mWidth, img->mHeight);
            if (img->mNumPlanes > 1 && stride > 0) {
                int64_t offsetDelta =
                    (int64_t)img->mPlane[1].mOffset - (int64_t)img->mPlane[0].mOffset;
                int32_t vstride = int32_t(offsetDelta / stride);
                mFormatWithImageData->setInt32(KEY_SLICE_HEIGHT, vstride);
                ALOGD("[%s] updating vstride = %d", mName, vstride);
                buffer->setRange(
                        img->mPlane[0].mOffset,
                        buffer->size() - img->mPlane[0].mOffset);
            }
        }
    }
    buffer->setFormat(mFormatWithImageData);
}

uint32_t CCodecBuffers::getPixelFormatIfApplicable() { return PIXEL_FORMAT_UNKNOWN; }

bool CCodecBuffers::resetPixelFormatIfApplicable() { return false; }

// InputBuffers

sp<Codec2Buffer> InputBuffers::cloneAndReleaseBuffer(const sp<MediaCodecBuffer> &buffer) {
    sp<Codec2Buffer> copy = createNewBuffer();
    if (copy == nullptr) {
        return nullptr;
    }
    std::shared_ptr<C2Buffer> c2buffer;
    if (!releaseBuffer(buffer, &c2buffer, true)) {
        return nullptr;
    }
    if (!copy->canCopy(c2buffer)) {
        return nullptr;
    }
    if (!copy->copy(c2buffer)) {
        return nullptr;
    }
    copy->meta()->extend(buffer->meta());
    return copy;
}

// MultiAccessUnitSkipCutBuffer for buffer and bufferInfos

class MultiAccessUnitSkipCutBuffer : public SkipCutBuffer {

public:
    explicit MultiAccessUnitSkipCutBuffer(
            int32_t skip, int32_t cut, size_t num16BitChannels):
        SkipCutBuffer(skip, cut, num16BitChannels),
        mFrontPaddingDelay(0), mSize(0) {
    }
    void clearAll() {
        mInfos.clear();
        mFrontPaddingDelay = 0;
        mSize = 0;
        SkipCutBuffer::clear();
    }

    virtual ~MultiAccessUnitSkipCutBuffer() {

    }

    void submitMultiAccessUnits(
            const sp<MediaCodecBuffer>& buffer,
            int32_t sampleRate, size_t num16BitChannels,
            std::shared_ptr<const C2AccessUnitInfos::output> &infos) {
        if (infos == nullptr) {
            // there is nothing to do more.
            SkipCutBuffer::submit(buffer);
            return;
        }
        typedef WrapperObject<std::vector<AccessUnitInfo>> BufferInfosWrapper;
        CHECK_EQ(mSize, SkipCutBuffer::size());
        sp<BufferInfosWrapper> bufferInfos{new BufferInfosWrapper(decltype(bufferInfos->value)())};
        uint32_t availableSize = buffer->size() + SkipCutBuffer::size();
        uint32_t frontPadding = mFrontPadding;
        int32_t lastEmptyAccessUnitIndex = -1;
        int64_t byteInUs = 0;
        if (sampleRate > 0 && num16BitChannels > 0) {
            byteInUs = (1000000u / (sampleRate * num16BitChannels * 2));
        }
        if (frontPadding > 0) {
            mInfos.clear();
            mSize = 0;
        }
        for (int i = 0 ; i < infos->flexCount() && frontPadding > 0; i++) {
            uint32_t flagsInPadding = 0;
            int64_t timeInPadding = 0;
            if (infos->m.values[i].size <= frontPadding) {
                // we have more front padding so this buffer is not going to be used.
                int32_t consumed = infos->m.values[i].size;
                frontPadding -= consumed;
                mFrontPaddingDelay += byteInUs * (consumed);
                availableSize -= consumed;
                flagsInPadding |= toMediaCodecFlags(infos->m.values[i].flags);
                timeInPadding = infos->m.values[i].timestamp;
            } else {
                C2AccessUnitInfosStruct info = infos->m.values[i];
                mFrontPaddingDelay +=  byteInUs * (frontPadding);
                info.size -= frontPadding;
                info.timestamp -= mFrontPaddingDelay;
                availableSize -= frontPadding;
                flagsInPadding |= toMediaCodecFlags(infos->m.values[i].flags);
                timeInPadding = infos->m.values[i].timestamp;
                frontPadding = 0;
                mInfos.push_back(info);
                mSize += info.size;
            }
            if (flagsInPadding != 0) {
                bufferInfos->value.emplace_back(
                        flagsInPadding, 0, timeInPadding);
            }
            lastEmptyAccessUnitIndex = i;
        }
        if (frontPadding <= 0) {
            // process what's already in the buffer first
            auto it = mInfos.begin();
            while (it != mInfos.end() && availableSize > mBackPadding) {
                // we have samples to send out.
                if ((availableSize - it->size) >= mBackPadding) {
                    // this is totally used here.
                    int32_t consumed = it->size;
                    bufferInfos->value.emplace_back(
                            toMediaCodecFlags(it->flags), consumed, it->timestamp);
                    availableSize -= consumed;
                    mSize -= consumed;
                    it = mInfos.erase(it);
                } else {
                    int32_t consumed = availableSize - mBackPadding;
                    bufferInfos->value.emplace_back(
                            toMediaCodecFlags(it->flags),
                            consumed,
                            it->timestamp);
                    it->size -= consumed;
                    it->timestamp += consumed * byteInUs;
                    availableSize -= consumed;
                    mSize -= consumed;
                    it++;
                }
            }
            // if buffer has more process all of it and keep the remaining info.
            for (int i = (lastEmptyAccessUnitIndex + 1) ; i < infos->flexCount() ; i++) {
                // upddate updatedInfo and mInfos
                if (availableSize > mBackPadding) {
                    // we have to take data from the new buffer.
                    if (availableSize - infos->m.values[i].size >= mBackPadding) {
                        // we are using this info
                        int32_t consumed = infos->m.values[i].size;
                        bufferInfos->value.emplace_back(
                                toMediaCodecFlags(infos->m.values[i].flags),
                                consumed,
                                infos->m.values[i].timestamp - mFrontPaddingDelay);
                        availableSize -= consumed;
                    } else {
                        // if we need to update the size
                        C2AccessUnitInfosStruct info = infos->m.values[i];
                        int32_t consumed = availableSize - mBackPadding;
                        bufferInfos->value.emplace_back(
                                toMediaCodecFlags(infos->m.values[i].flags),
                                consumed,
                                infos->m.values[i].timestamp - mFrontPaddingDelay);
                        info.size -= consumed;
                        info.timestamp = info.timestamp - mFrontPaddingDelay +
                                consumed * byteInUs;
                        mInfos.push_back(info);
                        availableSize -= consumed;
                        mSize += info.size;
                    }
                } else {
                    // we have to maintain infos
                    C2AccessUnitInfosStruct info = infos->m.values[i];
                    info.timestamp -= mFrontPaddingDelay;
                    mInfos.push_back(info);
                    mSize += info.size;
                }
            }
        }
        SkipCutBuffer::submit(buffer);
        infos = nullptr;
        if (!bufferInfos->value.empty()) {
            buffer->meta()->setObject("accessUnitInfo", bufferInfos);
        }
    }
protected:
    // Flags can come with individual BufferInfos
    // when used with large frame audio
    constexpr static std::initializer_list<std::pair<uint32_t, uint32_t>> flagList = {
            {BUFFER_FLAG_CODEC_CONFIG, C2FrameData::FLAG_CODEC_CONFIG},
            {BUFFER_FLAG_END_OF_STREAM, C2FrameData::FLAG_END_OF_STREAM},
            {BUFFER_FLAG_DECODE_ONLY, C2FrameData::FLAG_DROP_FRAME}
    };

    static uint32_t toMediaCodecFlags(uint32_t flags) {
        return std::transform_reduce(
                flagList.begin(), flagList.end(),
                0u,
                std::bit_or{},
                [flags](const std::pair<uint32_t, uint32_t> &entry) {
                    return (flags & entry.second) ? entry.first : 0;
                });
    }
    std::list<C2AccessUnitInfosStruct> mInfos;
    int64_t mFrontPaddingDelay;
    size_t mSize;
};

// OutputBuffers

OutputBuffers::OutputBuffers(const char *componentName, const char *name)
    : CCodecBuffers(componentName, name) { }

OutputBuffers::~OutputBuffers() = default;

void OutputBuffers::initSkipCutBuffer(
        int32_t delay, int32_t padding, int32_t sampleRate, int32_t channelCount) {
    CHECK(mSkipCutBuffer == nullptr);
    mDelay = delay;
    mPadding = padding;
    mSampleRate = sampleRate;
    mChannelCount = channelCount;
    setSkipCutBuffer(delay, padding);
}

void OutputBuffers::updateSkipCutBuffer(int32_t sampleRate, int32_t channelCount) {
    if (mSkipCutBuffer == nullptr) {
        return;
    }
    if (mSampleRate == sampleRate && mChannelCount == channelCount) {
        return;
    }
    int32_t delay = mDelay;
    int32_t padding = mPadding;
    if (sampleRate != mSampleRate) {
        delay = ((int64_t)delay * sampleRate) / mSampleRate;
        padding = ((int64_t)padding * sampleRate) / mSampleRate;
    }
    mSampleRate = sampleRate;
    mChannelCount = channelCount;
    setSkipCutBuffer(delay, padding);
}

void OutputBuffers::updateSkipCutBuffer(const sp<AMessage> &format) {
    AString mediaType;
    if (format->findString(KEY_MIME, &mediaType)
            && mediaType == MIMETYPE_AUDIO_RAW) {
        int32_t channelCount;
        int32_t sampleRate;
        if (format->findInt32(KEY_CHANNEL_COUNT, &channelCount)
                && format->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
            updateSkipCutBuffer(sampleRate, channelCount);
        }
    }
}

void OutputBuffers::submit(const sp<MediaCodecBuffer> &buffer) {
    if (mSkipCutBuffer != nullptr) {
        mSkipCutBuffer->submit(buffer);
    }
}

bool OutputBuffers::submit(const sp<MediaCodecBuffer> &buffer, int32_t sampleRate,
            int32_t channelCount, std::shared_ptr<const C2AccessUnitInfos::output> &infos) {
    if (mSkipCutBuffer == nullptr) {
        return false;
    }
    mSkipCutBuffer->submitMultiAccessUnits(buffer, sampleRate, channelCount, infos);
    return true;
}

void OutputBuffers::setSkipCutBuffer(int32_t skip, int32_t cut) {
    if (mSkipCutBuffer != nullptr) {
        size_t prevSize = mSkipCutBuffer->size();
        if (prevSize != 0u) {
            ALOGD("[%s] Replacing SkipCutBuffer holding %zu bytes", mName, prevSize);
        }
    }
    mSkipCutBuffer = new MultiAccessUnitSkipCutBuffer(skip, cut, mChannelCount);
}

bool OutputBuffers::convert(
        const std::shared_ptr<C2Buffer> &src, sp<Codec2Buffer> *dst) {
    if (src && src->data().type() != C2BufferData::LINEAR) {
        return false;
    }
    int32_t configEncoding = kAudioEncodingPcm16bit;
    int32_t codecEncoding = kAudioEncodingPcm16bit;
    if (mFormat->findInt32("android._codec-pcm-encoding", &codecEncoding)
            && mFormat->findInt32("android._config-pcm-encoding", &configEncoding)) {
        if (mSrcEncoding != codecEncoding || mDstEncoding != configEncoding) {
            if (codecEncoding != configEncoding) {
                mDataConverter = AudioConverter::Create(
                        (AudioEncoding)codecEncoding, (AudioEncoding)configEncoding);
                ALOGD_IF(mDataConverter, "[%s] Converter created from %d to %d",
                         mName, codecEncoding, configEncoding);
                mFormatWithConverter = mFormat->dup();
                mFormatWithConverter->setInt32(KEY_PCM_ENCODING, configEncoding);
            } else {
                mDataConverter = nullptr;
                mFormatWithConverter = nullptr;
            }
            mSrcEncoding = codecEncoding;
            mDstEncoding = configEncoding;
        }
        if (int encoding; !mFormat->findInt32(KEY_PCM_ENCODING, &encoding)
                || encoding != mDstEncoding) {
        }
    }
    if (!mDataConverter) {
        return false;
    }
    sp<MediaCodecBuffer> srcBuffer;
    if (src) {
        srcBuffer = ConstLinearBlockBuffer::Allocate(mFormat, src);
    } else {
        srcBuffer = new MediaCodecBuffer(mFormat, new ABuffer(0));
    }
    if (!srcBuffer) {
        return false;
    }
    if (!*dst) {
        *dst = new Codec2Buffer(
                mFormat,
                new ABuffer(mDataConverter->targetSize(srcBuffer->size())));
    }
    sp<MediaCodecBuffer> dstBuffer = *dst;
    status_t err = mDataConverter->convert(srcBuffer, dstBuffer);
    if (err != OK) {
        ALOGD("[%s] buffer conversion failed: %d", mName, err);
        return false;
    }
    dstBuffer->setFormat(mFormatWithConverter);
    return true;
}

void OutputBuffers::clearStash() {
    mPending.clear();
    mReorderStash.clear();
    mDepth = 0;
    mKey = C2Config::ORDINAL;
}

void OutputBuffers::flushStash() {
    for (StashEntry& e : mPending) {
        e.notify = false;
    }
    for (StashEntry& e : mReorderStash) {
        e.notify = false;
    }
}

uint32_t OutputBuffers::getReorderDepth() const {
    return mDepth;
}

void OutputBuffers::setReorderDepth(uint32_t depth) {
    mPending.splice(mPending.end(), mReorderStash);
    mDepth = depth;
}

void OutputBuffers::setReorderKey(C2Config::ordinal_key_t key) {
    mPending.splice(mPending.end(), mReorderStash);
    mKey = key;
}

void OutputBuffers::pushToStash(
        const std::shared_ptr<C2Buffer>& buffer,
        bool notify,
        int64_t timestamp,
        int32_t flags,
        const sp<AMessage>& format,
        const C2WorkOrdinalStruct& ordinal) {
    bool eos = flags & BUFFER_FLAG_END_OF_STREAM;
    if (!buffer && eos) {
        // TRICKY: we may be violating ordering of the stash here. Because we
        // don't expect any more emplace() calls after this, the ordering should
        // not matter.
        mReorderStash.emplace_back(
                buffer, notify, timestamp, flags, format, ordinal);
    } else {
        flags = flags & ~BUFFER_FLAG_END_OF_STREAM;
        auto it = mReorderStash.begin();
        for (; it != mReorderStash.end(); ++it) {
            if (less(ordinal, it->ordinal)) {
                break;
            }
        }
        mReorderStash.emplace(it,
                buffer, notify, timestamp, flags, format, ordinal);
        if (eos) {
            mReorderStash.back().flags =
                mReorderStash.back().flags | BUFFER_FLAG_END_OF_STREAM;
        }
    }
    while (!mReorderStash.empty() && mReorderStash.size() > mDepth) {
        mPending.push_back(mReorderStash.front());
        mReorderStash.pop_front();
    }
    ALOGV("[%s] %s: pushToStash -- pending size = %zu", mName, __func__, mPending.size());
}

OutputBuffers::BufferAction OutputBuffers::popFromStashAndRegister(
        std::shared_ptr<C2Buffer>* c2Buffer,
        size_t* index,
        sp<MediaCodecBuffer>* outBuffer) {
    if (mPending.empty()) {
        return SKIP;
    }

    // Retrieve the first entry.
    StashEntry &entry = mPending.front();

    *c2Buffer = entry.buffer;
    sp<AMessage> outputFormat = entry.format;

    if (entry.notify && mFormat != outputFormat) {
        updateSkipCutBuffer(outputFormat);
        // Trigger image data processing to the new format
        mLastImageData.clear();
        ALOGV("[%s] popFromStashAndRegister: output format reference changed: %p -> %p",
                mName, mFormat.get(), outputFormat.get());
        ALOGD("[%s] popFromStashAndRegister: at %lldus, output format changed to %s",
                mName, (long long)entry.timestamp, outputFormat->debugString().c_str());
        setFormat(outputFormat);
    }

    // Flushing mReorderStash because no other buffers should come after output
    // EOS.
    if (entry.flags & BUFFER_FLAG_END_OF_STREAM) {
        // Flush reorder stash
        setReorderDepth(0);
    }

    if (!entry.notify) {
        mPending.pop_front();
        return DISCARD;
    }

    // Try to register the buffer.
    status_t err = registerBuffer(*c2Buffer, index, outBuffer);
    if (err != OK) {
        if (err != WOULD_BLOCK) {
            return REALLOCATE;
        }
        return RETRY;
    }

    // Append information from the front stash entry to outBuffer.
    (*outBuffer)->meta()->setInt64("timeUs", entry.timestamp);
    (*outBuffer)->meta()->setInt32("flags", entry.flags);
    (*outBuffer)->meta()->setInt64("frameIndex", entry.ordinal.frameIndex.peekll());
    ALOGV("[%s] popFromStashAndRegister: "
          "out buffer index = %zu [%p] => %p + %zu (%lld)",
          mName, *index, outBuffer->get(),
          (*outBuffer)->data(), (*outBuffer)->size(),
          (long long)entry.timestamp);

    // The front entry of mPending will be removed now that the registration
    // succeeded.
    mPending.pop_front();
    return NOTIFY_CLIENT;
}

bool OutputBuffers::popPending(StashEntry *entry) {
    if (mPending.empty()) {
        return false;
    }
    *entry = mPending.front();
    mPending.pop_front();
    return true;
}

void OutputBuffers::deferPending(const OutputBuffers::StashEntry &entry) {
    mPending.push_front(entry);
}

bool OutputBuffers::hasPending() const {
    return !mPending.empty();
}

bool OutputBuffers::less(
        const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) const {
    switch (mKey) {
        case C2Config::ORDINAL:   return o1.frameIndex < o2.frameIndex;
        case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
        case C2Config::CUSTOM:    return o1.customOrdinal < o2.customOrdinal;
        default:
            ALOGD("Unrecognized key; default to timestamp");
            return o1.frameIndex < o2.frameIndex;
    }
}

// LocalBufferPool

constexpr size_t kInitialPoolCapacity = kMaxLinearBufferSize;
constexpr size_t kMaxPoolCapacity = kMaxLinearBufferSize * 32;

std::shared_ptr<LocalBufferPool> LocalBufferPool::Create() {
    return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(kInitialPoolCapacity));
}

sp<ABuffer> LocalBufferPool::newBuffer(size_t capacity) {
    Mutex::Autolock lock(mMutex);
    auto it = std::find_if(
            mPool.begin(), mPool.end(),
            [capacity](const std::vector<uint8_t> &vec) {
                return vec.capacity() >= capacity;
            });
    if (it != mPool.end()) {
        sp<ABuffer> buffer = new VectorBuffer(std::move(*it), shared_from_this());
        mPool.erase(it);
        return buffer;
    }
    if (mUsedSize + capacity > mPoolCapacity) {
        while (!mPool.empty()) {
            mUsedSize -= mPool.back().capacity();
            mPool.pop_back();
        }
        while (mUsedSize + capacity > mPoolCapacity && mPoolCapacity * 2 <= kMaxPoolCapacity) {
            ALOGD("Increasing local buffer pool capacity from %zu to %zu",
                  mPoolCapacity, mPoolCapacity * 2);
            mPoolCapacity *= 2;
        }
        if (mUsedSize + capacity > mPoolCapacity) {
            ALOGD("mUsedSize = %zu, capacity = %zu, mPoolCapacity = %zu",
                    mUsedSize, capacity, mPoolCapacity);
            return nullptr;
        }
    }
    std::vector<uint8_t> vec(capacity);
    mUsedSize += vec.capacity();
    return new VectorBuffer(std::move(vec), shared_from_this());
}

LocalBufferPool::VectorBuffer::VectorBuffer(
        std::vector<uint8_t> &&vec, const std::shared_ptr<LocalBufferPool> &pool)
    : ABuffer(vec.data(), vec.capacity()),
      mVec(std::move(vec)),
      mPool(pool) {
}

LocalBufferPool::VectorBuffer::~VectorBuffer() {
    std::shared_ptr<LocalBufferPool> pool = mPool.lock();
    if (pool) {
        // If pool is alive, return the vector back to the pool so that
        // it can be recycled.
        pool->returnVector(std::move(mVec));
    }
}

void LocalBufferPool::returnVector(std::vector<uint8_t> &&vec) {
    Mutex::Autolock lock(mMutex);
    mPool.push_front(std::move(vec));
}

// FlexBuffersImpl

size_t FlexBuffersImpl::assignSlot(const sp<Codec2Buffer> &buffer) {
    for (size_t i = 0; i < mBuffers.size(); ++i) {
        if (mBuffers[i].clientBuffer == nullptr
                && mBuffers[i].compBuffer.expired()) {
            mBuffers[i].clientBuffer = buffer;
            return i;
        }
    }
    mBuffers.push_back({ buffer, std::weak_ptr<C2Buffer>() });
    return mBuffers.size() - 1;
}

bool FlexBuffersImpl::releaseSlot(
        const sp<MediaCodecBuffer> &buffer,
        std::shared_ptr<C2Buffer> *c2buffer,
        bool release) {
    sp<Codec2Buffer> clientBuffer;
    size_t index = mBuffers.size();
    for (size_t i = 0; i < mBuffers.size(); ++i) {
        if (mBuffers[i].clientBuffer == buffer) {
            clientBuffer = mBuffers[i].clientBuffer;
            if (release) {
                mBuffers[i].clientBuffer.clear();
            }
            index = i;
            break;
        }
    }
    if (clientBuffer == nullptr) {
        ALOGV("[%s] %s: No matching buffer found", mName, __func__);
        return false;
    }
    std::shared_ptr<C2Buffer> result = mBuffers[index].compBuffer.lock();
    if (!result) {
        result = clientBuffer->asC2Buffer();
        clientBuffer->clearC2BufferRefs();
        mBuffers[index].compBuffer = result;
    }
    if (c2buffer) {
        *c2buffer = result;
    }
    return true;
}

bool FlexBuffersImpl::expireComponentBuffer(const std::shared_ptr<C2Buffer> &c2buffer) {
    for (size_t i = 0; i < mBuffers.size(); ++i) {
        std::shared_ptr<C2Buffer> compBuffer =
                mBuffers[i].compBuffer.lock();
        if (!compBuffer || compBuffer != c2buffer) {
            continue;
        }
        mBuffers[i].compBuffer.reset();
        ALOGV("[%s] codec released buffer #%zu", mName, i);
        return true;
    }
    ALOGV("[%s] codec released an unknown buffer", mName);
    return false;
}

void FlexBuffersImpl::flush() {
    ALOGV("[%s] buffers are flushed %zu", mName, mBuffers.size());
    mBuffers.clear();
}

size_t FlexBuffersImpl::numActiveSlots() const {
    return std::count_if(
            mBuffers.begin(), mBuffers.end(),
            [](const Entry &entry) {
                return (entry.clientBuffer != nullptr
                        || !entry.compBuffer.expired());
            });
}

size_t FlexBuffersImpl::numComponentBuffers() const {
    return std::count_if(
            mBuffers.begin(), mBuffers.end(),
            [](const Entry &entry) {
                return !entry.compBuffer.expired();
            });
}

// BuffersArrayImpl

void BuffersArrayImpl::initialize(
        const FlexBuffersImpl &impl,
        size_t minSize,
        std::function<sp<Codec2Buffer>()> allocate) {
    mImplName = impl.mImplName + "[N]";
    mName = mImplName.c_str();
    for (size_t i = 0; i < impl.mBuffers.size(); ++i) {
        sp<Codec2Buffer> clientBuffer = impl.mBuffers[i].clientBuffer;
        bool ownedByClient = (clientBuffer != nullptr);
        if (!ownedByClient) {
            clientBuffer = allocate();
        }
        mBuffers.push_back({ clientBuffer, impl.mBuffers[i].compBuffer, ownedByClient });
    }
    ALOGV("[%s] converted %zu buffers to array mode of %zu", mName, mBuffers.size(), minSize);
    for (size_t i = impl.mBuffers.size(); i < minSize; ++i) {
        mBuffers.push_back({ allocate(), std::weak_ptr<C2Buffer>(), false });
    }
}

status_t BuffersArrayImpl::grabBuffer(
        size_t *index,
        sp<Codec2Buffer> *buffer,
        std::function<bool(const sp<Codec2Buffer> &)> match) {
    // allBuffersDontMatch remains true if all buffers are available but
    // match() returns false for every buffer.
    bool allBuffersDontMatch = true;
    for (size_t i = 0; i < mBuffers.size(); ++i) {
        if (!mBuffers[i].ownedByClient && mBuffers[i].compBuffer.expired()) {
            if (match(mBuffers[i].clientBuffer)) {
                mBuffers[i].ownedByClient = true;
                *buffer = mBuffers[i].clientBuffer;
                (*buffer)->meta()->clear();
                (*buffer)->setRange(0, (*buffer)->capacity());
                *index = i;
                return OK;
            }
        } else {
            allBuffersDontMatch = false;
        }
    }
    return allBuffersDontMatch ? NO_MEMORY : WOULD_BLOCK;
}

bool BuffersArrayImpl::returnBuffer(
        const sp<MediaCodecBuffer> &buffer,
        std::shared_ptr<C2Buffer> *c2buffer,
        bool release) {
    sp<Codec2Buffer> clientBuffer;
    size_t index = mBuffers.size();
    for (size_t i = 0; i < mBuffers.size(); ++i) {
        if (mBuffers[i].clientBuffer == buffer) {
            if (!mBuffers[i].ownedByClient) {
                ALOGD("[%s] Client returned a buffer it does not own according to our record: %zu",
                      mName, i);
            }
            clientBuffer = mBuffers[i].clientBuffer;
            if (release) {
                mBuffers[i].ownedByClient = false;
            }
            index = i;
            break;
        }
    }
    if (clientBuffer == nullptr) {
        ALOGV("[%s] %s: No matching buffer found", mName, __func__);
        return false;
    }
    ALOGV("[%s] %s: matching buffer found (index=%zu)", mName, __func__, index);
    std::shared_ptr<C2Buffer> result = mBuffers[index].compBuffer.lock();
    if (!result) {
        result = clientBuffer->asC2Buffer();
        clientBuffer->clearC2BufferRefs();
        mBuffers[index].compBuffer = result;
    }
    if (c2buffer) {
        *c2buffer = result;
    }
    return true;
}

bool BuffersArrayImpl::expireComponentBuffer(const std::shared_ptr<C2Buffer> &c2buffer) {
    for (size_t i = 0; i < mBuffers.size(); ++i) {
        std::shared_ptr<C2Buffer> compBuffer =
                mBuffers[i].compBuffer.lock();
        if (!compBuffer) {
            continue;
        }
        if (c2buffer == compBuffer) {
            if (mBuffers[i].ownedByClient) {
                // This should not happen.
                ALOGD("[%s] codec released a buffer owned by client "
                      "(index %zu)", mName, i);
            }
            mBuffers[i].compBuffer.reset();
            ALOGV("[%s] codec released buffer #%zu(array mode)", mName, i);
            return true;
        }
    }
    ALOGV("[%s] codec released an unknown buffer (array mode)", mName);
    return false;
}

void BuffersArrayImpl::getArray(Vector<sp<MediaCodecBuffer>> *array) const {
    array->clear();
    for (const Entry &entry : mBuffers) {
        array->push(entry.clientBuffer);
    }
}

void BuffersArrayImpl::flush() {
    for (Entry &entry : mBuffers) {
        entry.ownedByClient = false;
    }
}

void BuffersArrayImpl::realloc(std::function<sp<Codec2Buffer>()> alloc) {
    size_t size = mBuffers.size();
    mBuffers.clear();
    for (size_t i = 0; i < size; ++i) {
        mBuffers.push_back({ alloc(), std::weak_ptr<C2Buffer>(), false });
    }
}

void BuffersArrayImpl::grow(
        size_t newSize, std::function<sp<Codec2Buffer>()> alloc) {
    CHECK_LT(mBuffers.size(), newSize);
    while (mBuffers.size() < newSize) {
        mBuffers.push_back({ alloc(), std::weak_ptr<C2Buffer>(), false });
    }
}

size_t BuffersArrayImpl::numActiveSlots() const {
    return std::count_if(
            mBuffers.begin(), mBuffers.end(),
            [](const Entry &entry) {
                return entry.ownedByClient || !entry.compBuffer.expired();
            });
}

size_t BuffersArrayImpl::arraySize() const {
    return mBuffers.size();
}

// InputBuffersArray

void InputBuffersArray::initialize(
        const FlexBuffersImpl &impl,
        size_t minSize,
        std::function<sp<Codec2Buffer>()> allocate) {
    mAllocate = allocate;
    mImpl.initialize(impl, minSize, allocate);
}

void InputBuffersArray::getArray(Vector<sp<MediaCodecBuffer>> *array) const {
    mImpl.getArray(array);
}

bool InputBuffersArray::requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) {
    sp<Codec2Buffer> c2Buffer;
    status_t err = mImpl.grabBuffer(index, &c2Buffer);
    if (err == OK) {
        c2Buffer->setFormat(mFormat);
        handleImageData(c2Buffer);
        *buffer = c2Buffer;
        return true;
    }
    return false;
}

bool InputBuffersArray::releaseBuffer(
        const sp<MediaCodecBuffer> &buffer,
        std::shared_ptr<C2Buffer> *c2buffer,
        bool release) {
    return mImpl.returnBuffer(buffer, c2buffer, release);
}

bool InputBuffersArray::expireComponentBuffer(
        const std::shared_ptr<C2Buffer> &c2buffer) {
    return mImpl.expireComponentBuffer(c2buffer);
}

void InputBuffersArray::flush() {
    mImpl.flush();
}

size_t InputBuffersArray::numActiveSlots() const {
    return mImpl.numActiveSlots();
}

sp<Codec2Buffer> InputBuffersArray::createNewBuffer() {
    return mAllocate();
}

// SlotInputBuffers

bool SlotInputBuffers::requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) {
    sp<Codec2Buffer> newBuffer = createNewBuffer();
    *index = mImpl.assignSlot(newBuffer);
    *buffer = newBuffer;
    return true;
}

bool SlotInputBuffers::releaseBuffer(
        const sp<MediaCodecBuffer> &buffer,
        std::shared_ptr<C2Buffer> *c2buffer,
        bool release) {
    return mImpl.releaseSlot(buffer, c2buffer, release);
}

bool SlotInputBuffers::expireComponentBuffer(
        const std::shared_ptr<C2Buffer> &c2buffer) {
    return mImpl.expireComponentBuffer(c2buffer);
}

void SlotInputBuffers::flush() {
    mImpl.flush();
}

std::unique_ptr<InputBuffers> SlotInputBuffers::toArrayMode(size_t) {
    TRESPASS("Array mode should not be called at non-legacy mode");
    return nullptr;
}

size_t SlotInputBuffers::numActiveSlots() const {
    return mImpl.numActiveSlots();
}

sp<Codec2Buffer> SlotInputBuffers::createNewBuffer() {
    return new DummyContainerBuffer{mFormat, nullptr};
}

// LinearInputBuffers

bool LinearInputBuffers::requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) {
    sp<Codec2Buffer> newBuffer = createNewBuffer();
    if (newBuffer == nullptr) {
        return false;
    }
    *index = mImpl.assignSlot(newBuffer);
    *buffer = newBuffer;
    return true;
}

bool LinearInputBuffers::releaseBuffer(
        const sp<MediaCodecBuffer> &buffer,
        std::shared_ptr<C2Buffer> *c2buffer,
        bool release) {
    return mImpl.releaseSlot(buffer, c2buffer, release);
}

bool LinearInputBuffers::expireComponentBuffer(
        const std::shared_ptr<C2Buffer> &c2buffer) {
    return mImpl.expireComponentBuffer(c2buffer);
}

void LinearInputBuffers::flush() {
    // This is no-op by default unless we're in array mode where we need to keep
    // track of the flushed work.
    mImpl.flush();
}

std::unique_ptr<InputBuffers> LinearInputBuffers::toArrayMode(size_t size) {
    std::unique_ptr<InputBuffersArray> array(
            new InputBuffersArray(mComponentName.c_str(), "1D-Input[N]"));
    array->setPool(mPool);
    array->setFormat(mFormat);
    array->initialize(
            mImpl,
            size,
            [pool = mPool, format = mFormat] () -> sp<Codec2Buffer> {
                return Alloc(pool, format);
            });
    return std::move(array);
}

size_t LinearInputBuffers::numActiveSlots() const {
    return mImpl.numActiveSlots();
}

// static
sp<Codec2Buffer> LinearInputBuffers::Alloc(
        const std::shared_ptr<C2BlockPool> &pool, const sp<AMessage> &format) {
    int32_t capacity = kLinearBufferSize;
    (void)format->findInt32(KEY_MAX_INPUT_SIZE, &capacity);
    if ((size_t)capacity > kMaxLinearBufferSize) {
        ALOGD("client requested %d, capped to %zu", capacity, kMaxLinearBufferSize);
        capacity = kMaxLinearBufferSize;
    }

    int64_t usageValue = 0;
    (void)format->findInt64("android._C2MemoryUsage", &usageValue);
    C2MemoryUsage usage{usageValue | C2MemoryUsage::CPU_READ | C2MemoryUsage::CPU_WRITE};
    std::shared_ptr<C2LinearBlock> block;

    c2_status_t err = pool->fetchLinearBlock(capacity, usage, &block);
    if (err != C2_OK) {
        return nullptr;
    }

    return LinearBlockBuffer::Allocate(format, block);
}

sp<Codec2Buffer> LinearInputBuffers::createNewBuffer() {
    return Alloc(mPool, mFormat);
}

// EncryptedLinearInputBuffers

EncryptedLinearInputBuffers::EncryptedLinearInputBuffers(
        bool secure,
        const sp<MemoryDealer> &dealer,
        const sp<ICrypto> &crypto,
        int32_t heapSeqNum,
        size_t capacity,
        size_t numInputSlots,
        const char *componentName, const char *name)
    : LinearInputBuffers(componentName, name),
      mUsage({0, 0}),
      mDealer(dealer),
      mCrypto(crypto),
      mMemoryVector(new std::vector<Entry>){
    if (secure) {
        mUsage = { C2MemoryUsage::READ_PROTECTED, 0 };
    } else {
        mUsage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
    }
    for (size_t i = 0; i < numInputSlots; ++i) {
        sp<IMemory> memory = mDealer->allocate(capacity);
        if (memory == nullptr) {
            ALOGD("[%s] Failed to allocate memory from dealer: only %zu slots allocated",
                  mName, i);
            break;
        }
        mMemoryVector->push_back({std::weak_ptr<C2LinearBlock>(), memory, heapSeqNum});
    }
}

std::unique_ptr<InputBuffers> EncryptedLinearInputBuffers::toArrayMode(size_t size) {
    std::unique_ptr<InputBuffersArray> array(
            new InputBuffersArray(mComponentName.c_str(), "1D-EncryptedInput[N]"));
    array->setPool(mPool);
    array->setFormat(mFormat);
    array->initialize(
            mImpl,
            size,
            [pool = mPool,
             format = mFormat,
             usage = mUsage,
             memoryVector = mMemoryVector] () -> sp<Codec2Buffer> {
                return Alloc(pool, format, usage, memoryVector);
            });
    return std::move(array);
}


// static
sp<Codec2Buffer> EncryptedLinearInputBuffers::Alloc(
        const std::shared_ptr<C2BlockPool> &pool,
        const sp<AMessage> &format,
        C2MemoryUsage usage,
        const std::shared_ptr<std::vector<EncryptedLinearInputBuffers::Entry>> &memoryVector) {
    int32_t capacity = kLinearBufferSize;
    (void)format->findInt32(KEY_MAX_INPUT_SIZE, &capacity);
    if ((size_t)capacity > kMaxLinearBufferSize) {
        ALOGD("client requested %d, capped to %zu", capacity, kMaxLinearBufferSize);
        capacity = kMaxLinearBufferSize;
    }

    sp<IMemory> memory;
    size_t slot = 0;
    int32_t heapSeqNum = -1;
    for (; slot < memoryVector->size(); ++slot) {
        if (memoryVector->at(slot).block.expired()) {
            memory = memoryVector->at(slot).memory;
            heapSeqNum = memoryVector->at(slot).heapSeqNum;
            break;
        }
    }
    if (memory == nullptr) {
        return nullptr;
    }

    int64_t usageValue = 0;
    (void)format->findInt64("android._C2MemoryUsage", &usageValue);
    usage = C2MemoryUsage(usage.expected | usageValue);

    std::shared_ptr<C2LinearBlock> block;
    c2_status_t err = pool->fetchLinearBlock(capacity, usage, &block);
    if (err != C2_OK || block == nullptr) {
        return nullptr;
    }

    memoryVector->at(slot).block = block;
    return new EncryptedLinearBlockBuffer(format, block, memory, heapSeqNum);
}

sp<Codec2Buffer> EncryptedLinearInputBuffers::createNewBuffer() {
    // TODO: android_2020
    return nullptr;
}

// GraphicMetadataInputBuffers

GraphicMetadataInputBuffers::GraphicMetadataInputBuffers(
        const char *componentName, const char *name)
    : InputBuffers(componentName, name),
      mImpl(mName),
      mStore(GetCodec2PlatformAllocatorStore()) { }

bool GraphicMetadataInputBuffers::requestNewBuffer(
        size_t *index, sp<MediaCodecBuffer> *buffer) {
    sp<Codec2Buffer> newBuffer = createNewBuffer();
    if (newBuffer == nullptr) {
        return false;
    }
    *index = mImpl.assignSlot(newBuffer);
    *buffer = newBuffer;
    return true;
}

bool GraphicMetadataInputBuffers::releaseBuffer(
        const sp<MediaCodecBuffer> &buffer,
        std::shared_ptr<C2Buffer> *c2buffer,
        bool release) {
    return mImpl.releaseSlot(buffer, c2buffer, release);
}

bool GraphicMetadataInputBuffers::expireComponentBuffer(
        const std::shared_ptr<C2Buffer> &c2buffer) {
    return mImpl.expireComponentBuffer(c2buffer);
}

void GraphicMetadataInputBuffers::flush() {
    // This is no-op by default unless we're in array mode where we need to keep
    // track of the flushed work.
}

std::unique_ptr<InputBuffers> GraphicMetadataInputBuffers::toArrayMode(
        size_t size) {
    std::shared_ptr<C2Allocator> alloc;
    c2_status_t err = mStore->fetchAllocator(mPool->getAllocatorId(), &alloc);
    if (err != C2_OK) {
        return nullptr;
    }
    std::unique_ptr<InputBuffersArray> array(
            new InputBuffersArray(mComponentName.c_str(), "2D-MetaInput[N]"));
    array->setPool(mPool);
    array->setFormat(mFormat);
    array->initialize(
            mImpl,
            size,
            [format = mFormat, alloc]() -> sp<Codec2Buffer> {
                return new GraphicMetadataBuffer(format, alloc);
            });
    return std::move(array);
}

size_t GraphicMetadataInputBuffers::numActiveSlots() const {
    return mImpl.numActiveSlots();
}

sp<Codec2Buffer> GraphicMetadataInputBuffers::createNewBuffer() {
    std::shared_ptr<C2Allocator> alloc;
    c2_status_t err = mStore->fetchAllocator(mPool->getAllocatorId(), &alloc);
    if (err != C2_OK) {
        return nullptr;
    }
    return new GraphicMetadataBuffer(mFormat, alloc);
}

// GraphicInputBuffers

GraphicInputBuffers::GraphicInputBuffers(
        const char *componentName, const char *name)
    : InputBuffers(componentName, name),
      mImpl(mName),
      mLocalBufferPool(LocalBufferPool::Create()),
      mPixelFormat(PIXEL_FORMAT_UNKNOWN) { }

bool GraphicInputBuffers::requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) {
    sp<Codec2Buffer> newBuffer = createNewBuffer();
    if (newBuffer == nullptr) {
        return false;
    }
    *index = mImpl.assignSlot(newBuffer);
    handleImageData(newBuffer);
    *buffer = newBuffer;
    return true;
}

bool GraphicInputBuffers::releaseBuffer(
        const sp<MediaCodecBuffer> &buffer,
        std::shared_ptr<C2Buffer> *c2buffer,
        bool release) {
    return mImpl.releaseSlot(buffer, c2buffer, release);
}

bool GraphicInputBuffers::expireComponentBuffer(
        const std::shared_ptr<C2Buffer> &c2buffer) {
    return mImpl.expireComponentBuffer(c2buffer);
}

void GraphicInputBuffers::flush() {
    // This is no-op by default unless we're in array mode where we need to keep
    // track of the flushed work.
}

static uint32_t extractPixelFormat(const sp<AMessage> &format) {
    int32_t frameworkColorFormat = 0;
    if (!format->findInt32("android._color-format", &frameworkColorFormat)) {
        return PIXEL_FORMAT_UNKNOWN;
    }
    uint32_t pixelFormat = PIXEL_FORMAT_UNKNOWN;
    if (C2Mapper::mapPixelFormatFrameworkToCodec(frameworkColorFormat, &pixelFormat)) {
        return pixelFormat;
    }
    return PIXEL_FORMAT_UNKNOWN;
}

std::unique_ptr<InputBuffers> GraphicInputBuffers::toArrayMode(size_t size) {
    std::unique_ptr<InputBuffersArray> array(
            new InputBuffersArray(mComponentName.c_str(), "2D-BB-Input[N]"));
    array->setPool(mPool);
    array->setFormat(mFormat);
    uint32_t pixelFormat = extractPixelFormat(mFormat);
    array->initialize(
            mImpl,
            size,
            [pool = mPool, format = mFormat, lbp = mLocalBufferPool, pixelFormat]()
                    -> sp<Codec2Buffer> {
                C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
                return AllocateInputGraphicBuffer(
                        pool, format, pixelFormat, usage, lbp);
            });
    return std::move(array);
}

size_t GraphicInputBuffers::numActiveSlots() const {
    return mImpl.numActiveSlots();
}

sp<Codec2Buffer> GraphicInputBuffers::createNewBuffer() {
    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
    mPixelFormat = extractPixelFormat(mFormat);
    return AllocateInputGraphicBuffer(
            mPool, mFormat, mPixelFormat, usage, mLocalBufferPool);
}

uint32_t GraphicInputBuffers::getPixelFormatIfApplicable() { return mPixelFormat; }

bool GraphicInputBuffers::resetPixelFormatIfApplicable() {
    mPixelFormat = PIXEL_FORMAT_UNKNOWN;
    return true;
}

// OutputBuffersArray

void OutputBuffersArray::initialize(
        const FlexBuffersImpl &impl,
        size_t minSize,
        std::function<sp<Codec2Buffer>()> allocate) {
    mAlloc = allocate;
    mImpl.initialize(impl, minSize, allocate);
}

status_t OutputBuffersArray::registerBuffer(
        const std::shared_ptr<C2Buffer> &buffer,
        size_t *index,
        sp<MediaCodecBuffer> *clientBuffer) {
    sp<Codec2Buffer> c2Buffer;
    status_t err = mImpl.grabBuffer(
            index,
            &c2Buffer,
            [buffer](const sp<Codec2Buffer> &clientBuffer) {
                return clientBuffer->canCopy(buffer);
            });
    if (err == WOULD_BLOCK) {
        ALOGV("[%s] buffers temporarily not available", mName);
        return err;
    } else if (err != OK) {
        ALOGD("[%s] grabBuffer failed: %d", mName, err);
        return err;
    }
    c2Buffer->setFormat(mFormat);
    if (!convert(buffer, &c2Buffer) && !c2Buffer->copy(buffer)) {
        ALOGD("[%s] copy buffer failed", mName);
        return WOULD_BLOCK;
    }
    if (buffer && buffer->hasInfo(C2AccessUnitInfos::output::PARAM_TYPE)) {
        std::shared_ptr<const C2AccessUnitInfos::output> bufferMetadata =
                        std::static_pointer_cast<const C2AccessUnitInfos::output>(
                        buffer->getInfo(C2AccessUnitInfos::output::PARAM_TYPE));
        if (submit(c2Buffer, mSampleRate, mChannelCount, bufferMetadata)) {
            buffer->removeInfo(C2AccessUnitInfos::output::PARAM_TYPE);
        }
    } else {
        submit(c2Buffer);
    }
    handleImageData(c2Buffer);
    *clientBuffer = c2Buffer;
    ALOGV("[%s] grabbed buffer %zu", mName, *index);
    return OK;
}

status_t OutputBuffersArray::registerCsd(
        const C2StreamInitDataInfo::output *csd,
        size_t *index,
        sp<MediaCodecBuffer> *clientBuffer) {
    sp<Codec2Buffer> c2Buffer;
    status_t err = mImpl.grabBuffer(
            index,
            &c2Buffer,
            [csd](const sp<Codec2Buffer> &clientBuffer) {
                return clientBuffer->base() != nullptr
                        && clientBuffer->capacity() >= csd->flexCount();
            });
    if (err != OK) {
        return err;
    }
    memcpy(c2Buffer->base(), csd->m.value, csd->flexCount());
    c2Buffer->setRange(0, csd->flexCount());
    c2Buffer->setFormat(mFormat);
    *clientBuffer = c2Buffer;
    return OK;
}

bool OutputBuffersArray::releaseBuffer(
        const sp<MediaCodecBuffer> &buffer, std::shared_ptr<C2Buffer> *c2buffer) {
    return mImpl.returnBuffer(buffer, c2buffer, true);
}

void OutputBuffersArray::flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) {
    (void)flushedWork;
    mImpl.flush();
    if (mSkipCutBuffer != nullptr) {
        mSkipCutBuffer->clearAll();
    }
}

void OutputBuffersArray::getArray(Vector<sp<MediaCodecBuffer>> *array) const {
    mImpl.getArray(array);
}

size_t OutputBuffersArray::numActiveSlots() const {
    return mImpl.numActiveSlots();
}

void OutputBuffersArray::realloc(const std::shared_ptr<C2Buffer> &c2buffer) {
    switch (c2buffer->data().type()) {
        case C2BufferData::LINEAR: {
            uint32_t size = kLinearBufferSize;
            const std::vector<C2ConstLinearBlock> &linear_blocks = c2buffer->data().linearBlocks();
            const uint32_t block_size = linear_blocks.front().size();
            if (block_size < kMaxLinearBufferSize / 2) {
                size = block_size * 2;
            } else {
                size = kMaxLinearBufferSize;
            }
            mAlloc = [format = mFormat, size] {
                return new LocalLinearBuffer(format, new ABuffer(size));
            };
            ALOGD("[%s] reallocating with linear buffer of size %u", mName, size);
            break;
        }

        case C2BufferData::GRAPHIC: {
            // This is only called for RawGraphicOutputBuffers.
            mAlloc = [format = mFormat,
                      lbp = LocalBufferPool::Create()] {
                return ConstGraphicBlockBuffer::AllocateEmpty(
                        format,
                        [lbp](size_t capacity) {
                            return lbp->newBuffer(capacity);
                        });
            };
            ALOGD("[%s] reallocating with graphic buffer: format = %s",
                  mName, mFormat->debugString().c_str());
            break;
        }

        case C2BufferData::INVALID:         [[fallthrough]];
        case C2BufferData::LINEAR_CHUNKS:   [[fallthrough]];
        case C2BufferData::GRAPHIC_CHUNKS:  [[fallthrough]];
        default:
            ALOGD("Unsupported type: %d", (int)c2buffer->data().type());
            return;
    }
    mImpl.realloc(mAlloc);
}

void OutputBuffersArray::grow(size_t newSize) {
    mImpl.grow(newSize, mAlloc);
}

void OutputBuffersArray::transferFrom(OutputBuffers* source) {
    mFormat = source->mFormat;
    mSkipCutBuffer = source->mSkipCutBuffer;
    mPending = std::move(source->mPending);
    mReorderStash = std::move(source->mReorderStash);
    mDepth = source->mDepth;
    mKey = source->mKey;
}

// FlexOutputBuffers

status_t FlexOutputBuffers::registerBuffer(
        const std::shared_ptr<C2Buffer> &buffer,
        size_t *index,
        sp<MediaCodecBuffer> *clientBuffer) {
    sp<Codec2Buffer> newBuffer;
    if (!convert(buffer, &newBuffer)) {
        newBuffer = wrap(buffer);
        if (newBuffer == nullptr) {
            return NO_MEMORY;
        }
        newBuffer->setFormat(mFormat);
    }
    *index = mImpl.assignSlot(newBuffer);
    handleImageData(newBuffer);
    *clientBuffer = newBuffer;

    extractPixelFormatFromC2Buffer(buffer);
    ALOGV("[%s] registered buffer %zu", mName, *index);
    return OK;
}

status_t FlexOutputBuffers::registerCsd(
        const C2StreamInitDataInfo::output *csd,
        size_t *index,
        sp<MediaCodecBuffer> *clientBuffer) {
    sp<Codec2Buffer> newBuffer = new LocalLinearBuffer(
            mFormat, ABuffer::CreateAsCopy(csd->m.value, csd->flexCount()));
    *index = mImpl.assignSlot(newBuffer);
    *clientBuffer = newBuffer;
    return OK;
}

bool FlexOutputBuffers::releaseBuffer(
        const sp<MediaCodecBuffer> &buffer,
        std::shared_ptr<C2Buffer> *c2buffer) {
    return mImpl.releaseSlot(buffer, c2buffer, true);
}

void FlexOutputBuffers::flush(
        const std::list<std::unique_ptr<C2Work>> &flushedWork) {
    (void) flushedWork;
    // This is no-op by default unless we're in array mode where we need to keep
    // track of the flushed work.
}

std::unique_ptr<OutputBuffersArray> FlexOutputBuffers::toArrayMode(size_t size) {
    std::unique_ptr<OutputBuffersArray> array(new OutputBuffersArray(mComponentName.c_str()));
    array->transferFrom(this);
    std::function<sp<Codec2Buffer>()> alloc = getAlloc();
    array->initialize(mImpl, size, alloc);
    return array;
}

size_t FlexOutputBuffers::numActiveSlots() const {
    return mImpl.numActiveSlots();
}

bool FlexOutputBuffers::extractPixelFormatFromC2Buffer(const std::shared_ptr<C2Buffer> &buffer) {
    if (buffer == nullptr) {
        return false;
    }
    const C2BufferData &data = buffer->data();
    // only extract the first pixel format in a metric session.
    if (mPixelFormat != PIXEL_FORMAT_UNKNOWN || data.type() != C2BufferData::GRAPHIC
            || data.graphicBlocks().empty()) {
        return false;
    }
    const C2Handle *const handle = data.graphicBlocks().front().handle();
    uint32_t pf = ExtractFormatFromCodec2GrallocHandle(handle);
    if (pf == PIXEL_FORMAT_UNKNOWN) {
        return false;
    }
    mPixelFormat = pf;
    return true;
}

bool FlexOutputBuffers::resetPixelFormatIfApplicable() {
    mPixelFormat = PIXEL_FORMAT_UNKNOWN;
    return true;
}

uint32_t FlexOutputBuffers::getPixelFormatIfApplicable() { return mPixelFormat; }

// LinearOutputBuffers

void LinearOutputBuffers::flush(
        const std::list<std::unique_ptr<C2Work>> &flushedWork) {
    if (mSkipCutBuffer != nullptr) {
        mSkipCutBuffer->clearAll();
    }
    FlexOutputBuffers::flush(flushedWork);
}

sp<Codec2Buffer> LinearOutputBuffers::wrap(const std::shared_ptr<C2Buffer> &buffer) {
    if (buffer == nullptr) {
        ALOGD("[%s] received null buffer", mName);
        return new LocalLinearBuffer(mFormat, new ABuffer(0));
    }
    if (buffer->data().type() != C2BufferData::LINEAR) {
        ALOGW("[%s] non-linear buffer %d", mName, buffer->data().type());
        // We expect linear output buffers from the component.
        return nullptr;
    }
    if (buffer->data().linearBlocks().size() != 1u) {
        ALOGW("[%s] no linear buffers", mName);
        // We expect one and only one linear block from the component.
        return nullptr;
    }
    if (buffer->data().linearBlocks().front().size() == 0) {
        ALOGD("[%s] received 0-sized buffer", mName);
        return new LocalLinearBuffer(mFormat, new ABuffer(0));
    }
    sp<Codec2Buffer> clientBuffer = ConstLinearBlockBuffer::Allocate(mFormat, buffer);
    if (clientBuffer == nullptr) {
        ALOGD("[%s] ConstLinearBlockBuffer::Allocate failed", mName);
        return nullptr;
    }
    submit(clientBuffer);
    return clientBuffer;
}

std::function<sp<Codec2Buffer>()> LinearOutputBuffers::getAlloc() {
    return [format = mFormat]{
        // TODO: proper max output size
        return new LocalLinearBuffer(format, new ABuffer(kLinearBufferSize));
    };
}

// GraphicOutputBuffers

sp<Codec2Buffer> GraphicOutputBuffers::wrap(const std::shared_ptr<C2Buffer> &buffer) {
    return new DummyContainerBuffer(mFormat, buffer);
}

std::function<sp<Codec2Buffer>()> GraphicOutputBuffers::getAlloc() {
    return [format = mFormat]{
        return new DummyContainerBuffer(format);
    };
}

// RawGraphicOutputBuffers

RawGraphicOutputBuffers::RawGraphicOutputBuffers(
        const char *componentName, const char *name)
    : FlexOutputBuffers(componentName, name),
      mLocalBufferPool(LocalBufferPool::Create()) { }

sp<Codec2Buffer> RawGraphicOutputBuffers::wrap(const std::shared_ptr<C2Buffer> &buffer) {
    if (buffer == nullptr) {
        return new Codec2Buffer(mFormat, new ABuffer(nullptr, 0));
    } else {
        return ConstGraphicBlockBuffer::Allocate(
                mFormat,
                buffer,
                [lbp = mLocalBufferPool](size_t capacity) {
                    return lbp->newBuffer(capacity);
                });
    }
}

std::function<sp<Codec2Buffer>()> RawGraphicOutputBuffers::getAlloc() {
    return [format = mFormat, lbp = mLocalBufferPool]{
        return ConstGraphicBlockBuffer::AllocateEmpty(
                format,
                [lbp](size_t capacity) {
                    return lbp->newBuffer(capacity);
                });
    };
}

}  // namespace android
